[ewg] [PATCH] iscsi backport for OFED1.4

Doron Shoham dorons at voltaire.com
Tue Jun 17 08:24:04 PDT 2008


Add iscsi backports for OFED1.4

Signed-off-by: Doron Shoham <dorons at voltaire.com>
---
 ...1_sync_kernel_code_with_release_2.0-869.2.patch |  705 ++
 ...1_sync_kernel_code_with_release_2.0-869.2.patch |  705 ++
 ...1_sync_kernel_code_with_release_2.0-869.2.patch |  705 ++
 ...1_sync_kernel_code_with_release_2.0-869.2.patch |  705 ++
 ...1_sync_kernel_code_with_release_2.0-869.2.patch |  705 ++
 ...1_sync_kernel_code_with_release_2.0-869.2.patch |  705 ++
 ...iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch | 7774 ++++++++++++++++++++
 .../backport/2.6.9_U4/iscsi_02_add_to_2_6_9.patch  |  180 +
 .../2.6.9_U4/iscsi_03_add_session_wq.patch         |   76 +
 .../2.6.9_U4/iscsi_04_inet_sock_to_opt.patch       |   13 +
 .../iscsi_05_release_host_lock_before_eh.patch     |   60 +
 .../backport/2.6.9_U4/iscsi_06_scsi_addons.patch   |   75 +
 ...iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch | 7774 ++++++++++++++++++++
 .../backport/2.6.9_U5/iscsi_02_add_to_2_6_9.patch  |  180 +
 .../2.6.9_U5/iscsi_03_add_session_wq.patch         |   76 +
 .../2.6.9_U5/iscsi_04_inet_sock_to_opt.patch       |   13 +
 .../iscsi_05_release_host_lock_before_eh.patch     |   60 +
 .../backport/2.6.9_U5/iscsi_06_scsi_addons.patch   |   75 +
 ...iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch | 7774 ++++++++++++++++++++
 .../backport/2.6.9_U6/iscsi_02_add_to_2_6_9.patch  |  180 +
 .../2.6.9_U6/iscsi_03_add_session_wq.patch         |   76 +
 .../2.6.9_U6/iscsi_04_inet_sock_to_opt.patch       |   13 +
 .../iscsi_05_release_host_lock_before_eh.patch     |   60 +
 .../backport/2.6.9_U6/iscsi_06_scsi_addons.patch   |   75 +
 24 files changed, 28764 insertions(+), 0 deletions(-)
 create mode 100644 kernel_patches/backport/2.6.16_sles10/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
 create mode 100644 kernel_patches/backport/2.6.16_sles10_sp1/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
 create mode 100644 kernel_patches/backport/2.6.16_sles10_sp2/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
 create mode 100644 kernel_patches/backport/2.6.18-EL5.1/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
 create mode 100644 kernel_patches/backport/2.6.18-EL5.2/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
 create mode 100644 kernel_patches/backport/2.6.18_FC6/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
 create mode 100644 kernel_patches/backport/2.6.9_U4/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
 create mode 100644 kernel_patches/backport/2.6.9_U4/iscsi_02_add_to_2_6_9.patch
 create mode 100644 kernel_patches/backport/2.6.9_U4/iscsi_03_add_session_wq.patch
 create mode 100644 kernel_patches/backport/2.6.9_U4/iscsi_04_inet_sock_to_opt.patch
 create mode 100644 kernel_patches/backport/2.6.9_U4/iscsi_05_release_host_lock_before_eh.patch
 create mode 100644 kernel_patches/backport/2.6.9_U4/iscsi_06_scsi_addons.patch
 create mode 100644 kernel_patches/backport/2.6.9_U5/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
 create mode 100644 kernel_patches/backport/2.6.9_U5/iscsi_02_add_to_2_6_9.patch
 create mode 100644 kernel_patches/backport/2.6.9_U5/iscsi_03_add_session_wq.patch
 create mode 100644 kernel_patches/backport/2.6.9_U5/iscsi_04_inet_sock_to_opt.patch
 create mode 100644 kernel_patches/backport/2.6.9_U5/iscsi_05_release_host_lock_before_eh.patch
 create mode 100644 kernel_patches/backport/2.6.9_U5/iscsi_06_scsi_addons.patch
 create mode 100644 kernel_patches/backport/2.6.9_U6/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
 create mode 100644 kernel_patches/backport/2.6.9_U6/iscsi_02_add_to_2_6_9.patch
 create mode 100644 kernel_patches/backport/2.6.9_U6/iscsi_03_add_session_wq.patch
 create mode 100644 kernel_patches/backport/2.6.9_U6/iscsi_04_inet_sock_to_opt.patch
 create mode 100644 kernel_patches/backport/2.6.9_U6/iscsi_05_release_host_lock_before_eh.patch
 create mode 100644 kernel_patches/backport/2.6.9_U6/iscsi_06_scsi_addons.patch

diff --git a/kernel_patches/backport/2.6.16_sles10/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch b/kernel_patches/backport/2.6.16_sles10/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
new file mode 100644
index 0000000..17ed782
--- /dev/null
+++ b/kernel_patches/backport/2.6.16_sles10/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
@@ -0,0 +1,705 @@
+From e1c4fe05b38843eb39fd6bae6ebc8cc8ce5d73a4 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 26 May 2008 11:27:56 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_release_2.0-869.2
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            |   31 +++----
+ drivers/scsi/libiscsi.c             |  169 +++++++---------------------------
+ drivers/scsi/scsi_transport_iscsi.c |  107 +++++++++++-----------
+ include/scsi/libiscsi.h             |    1 -
+ 4 files changed, 101 insertions(+), 207 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..8a17867 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -528,7 +528,6 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+ 	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
+ 	if (tcp_conn->in.datalen == 0)
+@@ -543,10 +542,10 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	tcp_ctask->exp_datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > scsi_bufflen(sc)) {
+ 		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+ 		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++		          tcp_conn->in.datalen, scsi_bufflen(sc));
+ 		return ISCSI_ERR_DATA_OFFSET;
+ 	}
+ 
+@@ -559,8 +558,8 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 			if (res_count > 0 &&
+ 			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
++			     res_count <= scsi_bufflen(sc)))
++				scsi_set_resid(sc, res_count);
+ 			else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+@@ -671,11 +670,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
++	if (r2t->data_offset + r2t->data_length > scsi_bufflen(ctask->sc)) {
+ 		iscsi_conn_printk(KERN_ERR, conn,
+ 				  "invalid R2T with data len %u at offset %u "
+ 				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
++				  r2t->data_offset, scsi_bufflen(ctask->sc));
+ 		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+ 			    sizeof(void*));
+ 		return ISCSI_ERR_DATALEN;
+@@ -772,7 +771,6 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		if (tcp_conn->in.datalen) {
+ 			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+ 
+ 			/*
+ 			 * Setup copy of Data-In into the Scsi_Cmnd
+@@ -790,8 +788,8 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 				  tcp_ctask->data_offset,
+ 				  tcp_conn->in.datalen);
+ 			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
++						     scsi_sglist(ctask->sc),
++						     scsi_sg_count(ctask->sc),
+ 						     tcp_ctask->data_offset,
+ 						     tcp_conn->in.datalen,
+ 						     iscsi_tcp_process_data_in,
+@@ -1334,8 +1332,7 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+ 		return 0;
+ 
+ 	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
++	err = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc), scsi_sg_count(sc),
+ 				       0, ctask->imm_count);
+ 	if (err)
+ 		return err;
+@@ -1389,7 +1386,6 @@ iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+ 	int rc = 0;
+ 
+ flush:
+@@ -1416,8 +1412,9 @@ flush:
+ 				ctask->itt, tcp_ctask->sent, ctask->data_count);
+ 
+ 		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
++					      tcp_ctask->sent,
+ 					      ctask->data_count);
+ 		if (rc)
+ 			goto fail;
+@@ -1463,8 +1460,8 @@ flush:
+ 		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+ 					sizeof(struct iscsi_hdr));
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
+ 					      r2t->data_offset + r2t->sent,
+ 					      r2t->data_count);
+ 		if (rc)
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..9975095 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -137,70 +137,6 @@ static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+ 	return 0;
+ }
+ 
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -214,7 +150,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
++	unsigned hdrlength;
+ 	int rc;
+ 
+ 	ctask->hdr_len = 0;
+@@ -225,30 +161,17 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	hdr->flags = ISCSI_ATTR_SIMPLE;
+ 	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+ 	hdr->itt = build_itt(ctask->itt, session->age);
++	hdr->data_length = cpu_to_be32(scsi_bufflen(sc));
+ 	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+ 	session->cmdsn++;
+ 	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
++	memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++	if (sc->cmd_len < MAX_COMMAND_SIZE)
++		memset(&hdr->cdb[sc->cmd_len], 0,
++			MAX_COMMAND_SIZE - sc->cmd_len);
+ 
+ 	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -269,19 +192,19 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (scsi_bufflen(sc) >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(scsi_bufflen(sc),
+ 							conn->max_xmit_dlength);
+ 			hton24(hdr->dlength, ctask->imm_count);
+ 		} else
+ 			zero_data(hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min((session->first_burst),
++				(scsi_bufflen(sc))) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+@@ -291,7 +214,6 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	} else {
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+@@ -310,12 +232,10 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		return EIO;
+ 
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
++	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x len %d "
++		"cmdsn %d win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, scsi_bufflen(sc),
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	return 0;
+ }
+@@ -378,12 +298,7 @@ static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		conn->session->tt->cleanup_cmd_task(conn, ctask);
+ 
+ 	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	if (conn->ctask == ctask)
+ 		conn->ctask = NULL;
+ 	/* release ref from queuecommand */
+@@ -518,18 +433,6 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
+-
+ 	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+ 	                   ISCSI_FLAG_CMD_OVERFLOW)) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+@@ -537,11 +440,13 @@ invalid_datalen:
+ 		if (res_count > 0 &&
+ 		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+ 		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+ 			scsi_set_resid(sc, res_count);
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
++	                          ISCSI_FLAG_CMD_BIDI_OVERFLOW))
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+@@ -730,7 +635,9 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+ 						   datalen))
+ 					rc = ISCSI_ERR_CONN_FAILED;
+-			}
++			} else
++				mod_timer(&conn->transport_timer,
++					  jiffies + conn->recv_timeout);
+ 			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		default:
+@@ -1197,12 +1104,7 @@ reject:
+ fault:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	sc->scsi_done(sc);
+ 	spin_lock(host->host_lock);
+ 	return 0;
+@@ -1453,19 +1355,20 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ {
+ 	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+ 	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	unsigned long recv_timeout, next_timeout = 0, last_recv;
+ 
+ 	spin_lock(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto done;
+ 
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
++	recv_timeout = conn->recv_timeout;
++	if (!recv_timeout)
+ 		goto done;
+ 
+-	timeout *= HZ;
++	recv_timeout *= HZ;
+ 	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
++	if (conn->ping_mtask &&
++	    time_before_eq(conn->last_ping + (conn->ping_timeout * HZ),
+ 			   jiffies)) {
+ 		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+ 				  "expired, last rx %lu, last ping %lu, "
+@@ -1476,15 +1379,13 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ 		return;
+ 	}
+ 
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
++	if (time_before_eq(last_recv + recv_timeout, jiffies)) {
++		/* send a ping to try to provoke some traffic */
++		debug_scsi("Sending nopout as ping on conn %p\n", conn);
++		iscsi_send_nopout(conn, NULL);
++		next_timeout = conn->last_ping + (conn->ping_timeout * HZ);
+ 	} else
+-		next_timeout = last_recv + timeout;
++		next_timeout = last_recv + recv_timeout;
+ 
+ 	debug_scsi("Setting next tmo %lu\n", next_timeout);
+ 	mod_timer(&conn->transport_timer, next_timeout);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..ca7bb6f 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -40,13 +40,13 @@ struct iscsi_internal {
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+@@ -63,12 +63,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +78,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +104,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -121,7 +119,7 @@ static struct attribute_group iscsi_transport_group = {
+ 
+ 
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -141,7 +139,7 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ }
+ 
+ static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
++			     struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -1339,8 +1337,11 @@ iscsi_if_rx(struct sk_buff *skb)
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1349,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1375,16 +1375,17 @@ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+ iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+ iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
+ 
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
++
+ /*
+  * iSCSI session attrs
+  */
+ #define iscsi_session_attr_show(param, perm)				\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+ 									\
+ 	if (perm && !capable(CAP_SYS_ADMIN))				\
+@@ -1416,10 +1417,9 @@ iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+ iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+ 
+ static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
++show_priv_session_state(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);
+ 	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+ }
+ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+@@ -1427,11 +1427,9 @@ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1446,10 +1444,9 @@ iscsi_priv_session_attr(recovery_tmo, "%d");
+  */
+ #define iscsi_host_attr_show(param)					\
+ static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_host_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
++	struct Scsi_Host *shost = transport_class_to_shost(cdev);	\
+ 	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+ 	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+ }
+@@ -1466,7 +1463,7 @@ iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+ 
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1471,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,7 +1479,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1490,7 +1487,7 @@ do {									\
+ #define SETUP_HOST_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->host_attrs[count] = &class_device_attr_host_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,15 +1578,15 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+@@ -1666,8 +1663,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1691,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..7b90b63 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -225,7 +225,6 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+ struct iscsi_pool {
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.16_sles10_sp1/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch b/kernel_patches/backport/2.6.16_sles10_sp1/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
new file mode 100644
index 0000000..17ed782
--- /dev/null
+++ b/kernel_patches/backport/2.6.16_sles10_sp1/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
@@ -0,0 +1,705 @@
+From e1c4fe05b38843eb39fd6bae6ebc8cc8ce5d73a4 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 26 May 2008 11:27:56 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_release_2.0-869.2
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            |   31 +++----
+ drivers/scsi/libiscsi.c             |  169 +++++++---------------------------
+ drivers/scsi/scsi_transport_iscsi.c |  107 +++++++++++-----------
+ include/scsi/libiscsi.h             |    1 -
+ 4 files changed, 101 insertions(+), 207 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..8a17867 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -528,7 +528,6 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+ 	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
+ 	if (tcp_conn->in.datalen == 0)
+@@ -543,10 +542,10 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	tcp_ctask->exp_datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > scsi_bufflen(sc)) {
+ 		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+ 		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++		          tcp_conn->in.datalen, scsi_bufflen(sc));
+ 		return ISCSI_ERR_DATA_OFFSET;
+ 	}
+ 
+@@ -559,8 +558,8 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 			if (res_count > 0 &&
+ 			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
++			     res_count <= scsi_bufflen(sc)))
++				scsi_set_resid(sc, res_count);
+ 			else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+@@ -671,11 +670,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
++	if (r2t->data_offset + r2t->data_length > scsi_bufflen(ctask->sc)) {
+ 		iscsi_conn_printk(KERN_ERR, conn,
+ 				  "invalid R2T with data len %u at offset %u "
+ 				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
++				  r2t->data_offset, scsi_bufflen(ctask->sc));
+ 		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+ 			    sizeof(void*));
+ 		return ISCSI_ERR_DATALEN;
+@@ -772,7 +771,6 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		if (tcp_conn->in.datalen) {
+ 			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+ 
+ 			/*
+ 			 * Setup copy of Data-In into the Scsi_Cmnd
+@@ -790,8 +788,8 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 				  tcp_ctask->data_offset,
+ 				  tcp_conn->in.datalen);
+ 			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
++						     scsi_sglist(ctask->sc),
++						     scsi_sg_count(ctask->sc),
+ 						     tcp_ctask->data_offset,
+ 						     tcp_conn->in.datalen,
+ 						     iscsi_tcp_process_data_in,
+@@ -1334,8 +1332,7 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+ 		return 0;
+ 
+ 	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
++	err = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc), scsi_sg_count(sc),
+ 				       0, ctask->imm_count);
+ 	if (err)
+ 		return err;
+@@ -1389,7 +1386,6 @@ iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+ 	int rc = 0;
+ 
+ flush:
+@@ -1416,8 +1412,9 @@ flush:
+ 				ctask->itt, tcp_ctask->sent, ctask->data_count);
+ 
+ 		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
++					      tcp_ctask->sent,
+ 					      ctask->data_count);
+ 		if (rc)
+ 			goto fail;
+@@ -1463,8 +1460,8 @@ flush:
+ 		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+ 					sizeof(struct iscsi_hdr));
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
+ 					      r2t->data_offset + r2t->sent,
+ 					      r2t->data_count);
+ 		if (rc)
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..9975095 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -137,70 +137,6 @@ static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+ 	return 0;
+ }
+ 
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -214,7 +150,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
++	unsigned hdrlength;
+ 	int rc;
+ 
+ 	ctask->hdr_len = 0;
+@@ -225,30 +161,17 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	hdr->flags = ISCSI_ATTR_SIMPLE;
+ 	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+ 	hdr->itt = build_itt(ctask->itt, session->age);
++	hdr->data_length = cpu_to_be32(scsi_bufflen(sc));
+ 	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+ 	session->cmdsn++;
+ 	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
++	memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++	if (sc->cmd_len < MAX_COMMAND_SIZE)
++		memset(&hdr->cdb[sc->cmd_len], 0,
++			MAX_COMMAND_SIZE - sc->cmd_len);
+ 
+ 	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -269,19 +192,19 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (scsi_bufflen(sc) >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(scsi_bufflen(sc),
+ 							conn->max_xmit_dlength);
+ 			hton24(hdr->dlength, ctask->imm_count);
+ 		} else
+ 			zero_data(hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min((session->first_burst),
++				(scsi_bufflen(sc))) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+@@ -291,7 +214,6 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	} else {
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+@@ -310,12 +232,10 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		return EIO;
+ 
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
++	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x len %d "
++		"cmdsn %d win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, scsi_bufflen(sc),
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	return 0;
+ }
+@@ -378,12 +298,7 @@ static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		conn->session->tt->cleanup_cmd_task(conn, ctask);
+ 
+ 	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	if (conn->ctask == ctask)
+ 		conn->ctask = NULL;
+ 	/* release ref from queuecommand */
+@@ -518,18 +433,6 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
+-
+ 	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+ 	                   ISCSI_FLAG_CMD_OVERFLOW)) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+@@ -537,11 +440,13 @@ invalid_datalen:
+ 		if (res_count > 0 &&
+ 		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+ 		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+ 			scsi_set_resid(sc, res_count);
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
++	                          ISCSI_FLAG_CMD_BIDI_OVERFLOW))
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+@@ -730,7 +635,9 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+ 						   datalen))
+ 					rc = ISCSI_ERR_CONN_FAILED;
+-			}
++			} else
++				mod_timer(&conn->transport_timer,
++					  jiffies + conn->recv_timeout);
+ 			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		default:
+@@ -1197,12 +1104,7 @@ reject:
+ fault:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	sc->scsi_done(sc);
+ 	spin_lock(host->host_lock);
+ 	return 0;
+@@ -1453,19 +1355,20 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ {
+ 	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+ 	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	unsigned long recv_timeout, next_timeout = 0, last_recv;
+ 
+ 	spin_lock(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto done;
+ 
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
++	recv_timeout = conn->recv_timeout;
++	if (!recv_timeout)
+ 		goto done;
+ 
+-	timeout *= HZ;
++	recv_timeout *= HZ;
+ 	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
++	if (conn->ping_mtask &&
++	    time_before_eq(conn->last_ping + (conn->ping_timeout * HZ),
+ 			   jiffies)) {
+ 		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+ 				  "expired, last rx %lu, last ping %lu, "
+@@ -1476,15 +1379,13 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ 		return;
+ 	}
+ 
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
++	if (time_before_eq(last_recv + recv_timeout, jiffies)) {
++		/* send a ping to try to provoke some traffic */
++		debug_scsi("Sending nopout as ping on conn %p\n", conn);
++		iscsi_send_nopout(conn, NULL);
++		next_timeout = conn->last_ping + (conn->ping_timeout * HZ);
+ 	} else
+-		next_timeout = last_recv + timeout;
++		next_timeout = last_recv + recv_timeout;
+ 
+ 	debug_scsi("Setting next tmo %lu\n", next_timeout);
+ 	mod_timer(&conn->transport_timer, next_timeout);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..ca7bb6f 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -40,13 +40,13 @@ struct iscsi_internal {
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+@@ -63,12 +63,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +78,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +104,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -121,7 +119,7 @@ static struct attribute_group iscsi_transport_group = {
+ 
+ 
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -141,7 +139,7 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ }
+ 
+ static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
++			     struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -1339,8 +1337,11 @@ iscsi_if_rx(struct sk_buff *skb)
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1349,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1375,16 +1375,17 @@ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+ iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+ iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
+ 
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
++
+ /*
+  * iSCSI session attrs
+  */
+ #define iscsi_session_attr_show(param, perm)				\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+ 									\
+ 	if (perm && !capable(CAP_SYS_ADMIN))				\
+@@ -1416,10 +1417,9 @@ iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+ iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+ 
+ static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
++show_priv_session_state(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);
+ 	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+ }
+ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+@@ -1427,11 +1427,9 @@ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1446,10 +1444,9 @@ iscsi_priv_session_attr(recovery_tmo, "%d");
+  */
+ #define iscsi_host_attr_show(param)					\
+ static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_host_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
++	struct Scsi_Host *shost = transport_class_to_shost(cdev);	\
+ 	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+ 	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+ }
+@@ -1466,7 +1463,7 @@ iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+ 
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1471,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,7 +1479,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1490,7 +1487,7 @@ do {									\
+ #define SETUP_HOST_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->host_attrs[count] = &class_device_attr_host_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,15 +1578,15 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+@@ -1666,8 +1663,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1691,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..7b90b63 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -225,7 +225,6 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+ struct iscsi_pool {
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.16_sles10_sp2/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch b/kernel_patches/backport/2.6.16_sles10_sp2/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
new file mode 100644
index 0000000..17ed782
--- /dev/null
+++ b/kernel_patches/backport/2.6.16_sles10_sp2/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
@@ -0,0 +1,705 @@
+From e1c4fe05b38843eb39fd6bae6ebc8cc8ce5d73a4 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 26 May 2008 11:27:56 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_release_2.0-869.2
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            |   31 +++----
+ drivers/scsi/libiscsi.c             |  169 +++++++---------------------------
+ drivers/scsi/scsi_transport_iscsi.c |  107 +++++++++++-----------
+ include/scsi/libiscsi.h             |    1 -
+ 4 files changed, 101 insertions(+), 207 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..8a17867 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -528,7 +528,6 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+ 	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
+ 	if (tcp_conn->in.datalen == 0)
+@@ -543,10 +542,10 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	tcp_ctask->exp_datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > scsi_bufflen(sc)) {
+ 		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+ 		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++		          tcp_conn->in.datalen, scsi_bufflen(sc));
+ 		return ISCSI_ERR_DATA_OFFSET;
+ 	}
+ 
+@@ -559,8 +558,8 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 			if (res_count > 0 &&
+ 			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
++			     res_count <= scsi_bufflen(sc)))
++				scsi_set_resid(sc, res_count);
+ 			else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+@@ -671,11 +670,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
++	if (r2t->data_offset + r2t->data_length > scsi_bufflen(ctask->sc)) {
+ 		iscsi_conn_printk(KERN_ERR, conn,
+ 				  "invalid R2T with data len %u at offset %u "
+ 				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
++				  r2t->data_offset, scsi_bufflen(ctask->sc));
+ 		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+ 			    sizeof(void*));
+ 		return ISCSI_ERR_DATALEN;
+@@ -772,7 +771,6 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		if (tcp_conn->in.datalen) {
+ 			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+ 
+ 			/*
+ 			 * Setup copy of Data-In into the Scsi_Cmnd
+@@ -790,8 +788,8 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 				  tcp_ctask->data_offset,
+ 				  tcp_conn->in.datalen);
+ 			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
++						     scsi_sglist(ctask->sc),
++						     scsi_sg_count(ctask->sc),
+ 						     tcp_ctask->data_offset,
+ 						     tcp_conn->in.datalen,
+ 						     iscsi_tcp_process_data_in,
+@@ -1334,8 +1332,7 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+ 		return 0;
+ 
+ 	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
++	err = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc), scsi_sg_count(sc),
+ 				       0, ctask->imm_count);
+ 	if (err)
+ 		return err;
+@@ -1389,7 +1386,6 @@ iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+ 	int rc = 0;
+ 
+ flush:
+@@ -1416,8 +1412,9 @@ flush:
+ 				ctask->itt, tcp_ctask->sent, ctask->data_count);
+ 
+ 		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
++					      tcp_ctask->sent,
+ 					      ctask->data_count);
+ 		if (rc)
+ 			goto fail;
+@@ -1463,8 +1460,8 @@ flush:
+ 		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+ 					sizeof(struct iscsi_hdr));
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
+ 					      r2t->data_offset + r2t->sent,
+ 					      r2t->data_count);
+ 		if (rc)
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..9975095 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -137,70 +137,6 @@ static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+ 	return 0;
+ }
+ 
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -214,7 +150,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
++	unsigned hdrlength;
+ 	int rc;
+ 
+ 	ctask->hdr_len = 0;
+@@ -225,30 +161,17 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	hdr->flags = ISCSI_ATTR_SIMPLE;
+ 	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+ 	hdr->itt = build_itt(ctask->itt, session->age);
++	hdr->data_length = cpu_to_be32(scsi_bufflen(sc));
+ 	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+ 	session->cmdsn++;
+ 	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
++	memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++	if (sc->cmd_len < MAX_COMMAND_SIZE)
++		memset(&hdr->cdb[sc->cmd_len], 0,
++			MAX_COMMAND_SIZE - sc->cmd_len);
+ 
+ 	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -269,19 +192,19 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (scsi_bufflen(sc) >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(scsi_bufflen(sc),
+ 							conn->max_xmit_dlength);
+ 			hton24(hdr->dlength, ctask->imm_count);
+ 		} else
+ 			zero_data(hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min((session->first_burst),
++				(scsi_bufflen(sc))) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+@@ -291,7 +214,6 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	} else {
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+@@ -310,12 +232,10 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		return EIO;
+ 
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
++	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x len %d "
++		"cmdsn %d win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, scsi_bufflen(sc),
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	return 0;
+ }
+@@ -378,12 +298,7 @@ static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		conn->session->tt->cleanup_cmd_task(conn, ctask);
+ 
+ 	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	if (conn->ctask == ctask)
+ 		conn->ctask = NULL;
+ 	/* release ref from queuecommand */
+@@ -518,18 +433,6 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
+-
+ 	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+ 	                   ISCSI_FLAG_CMD_OVERFLOW)) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+@@ -537,11 +440,13 @@ invalid_datalen:
+ 		if (res_count > 0 &&
+ 		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+ 		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+ 			scsi_set_resid(sc, res_count);
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
++	                          ISCSI_FLAG_CMD_BIDI_OVERFLOW))
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+@@ -730,7 +635,9 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+ 						   datalen))
+ 					rc = ISCSI_ERR_CONN_FAILED;
+-			}
++			} else
++				mod_timer(&conn->transport_timer,
++					  jiffies + conn->recv_timeout);
+ 			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		default:
+@@ -1197,12 +1104,7 @@ reject:
+ fault:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	sc->scsi_done(sc);
+ 	spin_lock(host->host_lock);
+ 	return 0;
+@@ -1453,19 +1355,20 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ {
+ 	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+ 	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	unsigned long recv_timeout, next_timeout = 0, last_recv;
+ 
+ 	spin_lock(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto done;
+ 
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
++	recv_timeout = conn->recv_timeout;
++	if (!recv_timeout)
+ 		goto done;
+ 
+-	timeout *= HZ;
++	recv_timeout *= HZ;
+ 	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
++	if (conn->ping_mtask &&
++	    time_before_eq(conn->last_ping + (conn->ping_timeout * HZ),
+ 			   jiffies)) {
+ 		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+ 				  "expired, last rx %lu, last ping %lu, "
+@@ -1476,15 +1379,13 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ 		return;
+ 	}
+ 
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
++	if (time_before_eq(last_recv + recv_timeout, jiffies)) {
++		/* send a ping to try to provoke some traffic */
++		debug_scsi("Sending nopout as ping on conn %p\n", conn);
++		iscsi_send_nopout(conn, NULL);
++		next_timeout = conn->last_ping + (conn->ping_timeout * HZ);
+ 	} else
+-		next_timeout = last_recv + timeout;
++		next_timeout = last_recv + recv_timeout;
+ 
+ 	debug_scsi("Setting next tmo %lu\n", next_timeout);
+ 	mod_timer(&conn->transport_timer, next_timeout);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..ca7bb6f 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -40,13 +40,13 @@ struct iscsi_internal {
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+@@ -63,12 +63,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +78,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +104,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -121,7 +119,7 @@ static struct attribute_group iscsi_transport_group = {
+ 
+ 
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -141,7 +139,7 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ }
+ 
+ static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
++			     struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -1339,8 +1337,11 @@ iscsi_if_rx(struct sk_buff *skb)
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1349,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1375,16 +1375,17 @@ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+ iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+ iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
+ 
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
++
+ /*
+  * iSCSI session attrs
+  */
+ #define iscsi_session_attr_show(param, perm)				\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+ 									\
+ 	if (perm && !capable(CAP_SYS_ADMIN))				\
+@@ -1416,10 +1417,9 @@ iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+ iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+ 
+ static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
++show_priv_session_state(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);
+ 	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+ }
+ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+@@ -1427,11 +1427,9 @@ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1446,10 +1444,9 @@ iscsi_priv_session_attr(recovery_tmo, "%d");
+  */
+ #define iscsi_host_attr_show(param)					\
+ static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_host_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
++	struct Scsi_Host *shost = transport_class_to_shost(cdev);	\
+ 	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+ 	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+ }
+@@ -1466,7 +1463,7 @@ iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+ 
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1471,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,7 +1479,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1490,7 +1487,7 @@ do {									\
+ #define SETUP_HOST_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->host_attrs[count] = &class_device_attr_host_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,15 +1578,15 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+@@ -1666,8 +1663,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1691,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..7b90b63 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -225,7 +225,6 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+ struct iscsi_pool {
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.18-EL5.1/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch b/kernel_patches/backport/2.6.18-EL5.1/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
new file mode 100644
index 0000000..17ed782
--- /dev/null
+++ b/kernel_patches/backport/2.6.18-EL5.1/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
@@ -0,0 +1,705 @@
+From e1c4fe05b38843eb39fd6bae6ebc8cc8ce5d73a4 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 26 May 2008 11:27:56 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_release_2.0-869.2
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            |   31 +++----
+ drivers/scsi/libiscsi.c             |  169 +++++++---------------------------
+ drivers/scsi/scsi_transport_iscsi.c |  107 +++++++++++-----------
+ include/scsi/libiscsi.h             |    1 -
+ 4 files changed, 101 insertions(+), 207 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..8a17867 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -528,7 +528,6 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+ 	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
+ 	if (tcp_conn->in.datalen == 0)
+@@ -543,10 +542,10 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	tcp_ctask->exp_datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > scsi_bufflen(sc)) {
+ 		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+ 		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++		          tcp_conn->in.datalen, scsi_bufflen(sc));
+ 		return ISCSI_ERR_DATA_OFFSET;
+ 	}
+ 
+@@ -559,8 +558,8 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 			if (res_count > 0 &&
+ 			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
++			     res_count <= scsi_bufflen(sc)))
++				scsi_set_resid(sc, res_count);
+ 			else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+@@ -671,11 +670,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
++	if (r2t->data_offset + r2t->data_length > scsi_bufflen(ctask->sc)) {
+ 		iscsi_conn_printk(KERN_ERR, conn,
+ 				  "invalid R2T with data len %u at offset %u "
+ 				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
++				  r2t->data_offset, scsi_bufflen(ctask->sc));
+ 		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+ 			    sizeof(void*));
+ 		return ISCSI_ERR_DATALEN;
+@@ -772,7 +771,6 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		if (tcp_conn->in.datalen) {
+ 			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+ 
+ 			/*
+ 			 * Setup copy of Data-In into the Scsi_Cmnd
+@@ -790,8 +788,8 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 				  tcp_ctask->data_offset,
+ 				  tcp_conn->in.datalen);
+ 			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
++						     scsi_sglist(ctask->sc),
++						     scsi_sg_count(ctask->sc),
+ 						     tcp_ctask->data_offset,
+ 						     tcp_conn->in.datalen,
+ 						     iscsi_tcp_process_data_in,
+@@ -1334,8 +1332,7 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+ 		return 0;
+ 
+ 	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
++	err = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc), scsi_sg_count(sc),
+ 				       0, ctask->imm_count);
+ 	if (err)
+ 		return err;
+@@ -1389,7 +1386,6 @@ iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+ 	int rc = 0;
+ 
+ flush:
+@@ -1416,8 +1412,9 @@ flush:
+ 				ctask->itt, tcp_ctask->sent, ctask->data_count);
+ 
+ 		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
++					      tcp_ctask->sent,
+ 					      ctask->data_count);
+ 		if (rc)
+ 			goto fail;
+@@ -1463,8 +1460,8 @@ flush:
+ 		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+ 					sizeof(struct iscsi_hdr));
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
+ 					      r2t->data_offset + r2t->sent,
+ 					      r2t->data_count);
+ 		if (rc)
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..9975095 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -137,70 +137,6 @@ static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+ 	return 0;
+ }
+ 
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -214,7 +150,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
++	unsigned hdrlength;
+ 	int rc;
+ 
+ 	ctask->hdr_len = 0;
+@@ -225,30 +161,17 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	hdr->flags = ISCSI_ATTR_SIMPLE;
+ 	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+ 	hdr->itt = build_itt(ctask->itt, session->age);
++	hdr->data_length = cpu_to_be32(scsi_bufflen(sc));
+ 	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+ 	session->cmdsn++;
+ 	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
++	memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++	if (sc->cmd_len < MAX_COMMAND_SIZE)
++		memset(&hdr->cdb[sc->cmd_len], 0,
++			MAX_COMMAND_SIZE - sc->cmd_len);
+ 
+ 	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -269,19 +192,19 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (scsi_bufflen(sc) >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(scsi_bufflen(sc),
+ 							conn->max_xmit_dlength);
+ 			hton24(hdr->dlength, ctask->imm_count);
+ 		} else
+ 			zero_data(hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min((session->first_burst),
++				(scsi_bufflen(sc))) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+@@ -291,7 +214,6 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	} else {
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+@@ -310,12 +232,10 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		return EIO;
+ 
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
++	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x len %d "
++		"cmdsn %d win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, scsi_bufflen(sc),
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	return 0;
+ }
+@@ -378,12 +298,7 @@ static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		conn->session->tt->cleanup_cmd_task(conn, ctask);
+ 
+ 	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	if (conn->ctask == ctask)
+ 		conn->ctask = NULL;
+ 	/* release ref from queuecommand */
+@@ -518,18 +433,6 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
+-
+ 	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+ 	                   ISCSI_FLAG_CMD_OVERFLOW)) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+@@ -537,11 +440,13 @@ invalid_datalen:
+ 		if (res_count > 0 &&
+ 		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+ 		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+ 			scsi_set_resid(sc, res_count);
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
++	                          ISCSI_FLAG_CMD_BIDI_OVERFLOW))
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+@@ -730,7 +635,9 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+ 						   datalen))
+ 					rc = ISCSI_ERR_CONN_FAILED;
+-			}
++			} else
++				mod_timer(&conn->transport_timer,
++					  jiffies + conn->recv_timeout);
+ 			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		default:
+@@ -1197,12 +1104,7 @@ reject:
+ fault:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	sc->scsi_done(sc);
+ 	spin_lock(host->host_lock);
+ 	return 0;
+@@ -1453,19 +1355,20 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ {
+ 	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+ 	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	unsigned long recv_timeout, next_timeout = 0, last_recv;
+ 
+ 	spin_lock(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto done;
+ 
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
++	recv_timeout = conn->recv_timeout;
++	if (!recv_timeout)
+ 		goto done;
+ 
+-	timeout *= HZ;
++	recv_timeout *= HZ;
+ 	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
++	if (conn->ping_mtask &&
++	    time_before_eq(conn->last_ping + (conn->ping_timeout * HZ),
+ 			   jiffies)) {
+ 		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+ 				  "expired, last rx %lu, last ping %lu, "
+@@ -1476,15 +1379,13 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ 		return;
+ 	}
+ 
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
++	if (time_before_eq(last_recv + recv_timeout, jiffies)) {
++		/* send a ping to try to provoke some traffic */
++		debug_scsi("Sending nopout as ping on conn %p\n", conn);
++		iscsi_send_nopout(conn, NULL);
++		next_timeout = conn->last_ping + (conn->ping_timeout * HZ);
+ 	} else
+-		next_timeout = last_recv + timeout;
++		next_timeout = last_recv + recv_timeout;
+ 
+ 	debug_scsi("Setting next tmo %lu\n", next_timeout);
+ 	mod_timer(&conn->transport_timer, next_timeout);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..ca7bb6f 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -40,13 +40,13 @@ struct iscsi_internal {
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+@@ -63,12 +63,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +78,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +104,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -121,7 +119,7 @@ static struct attribute_group iscsi_transport_group = {
+ 
+ 
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -141,7 +139,7 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ }
+ 
+ static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
++			     struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -1339,8 +1337,11 @@ iscsi_if_rx(struct sk_buff *skb)
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1349,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1375,16 +1375,17 @@ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+ iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+ iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
+ 
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
++
+ /*
+  * iSCSI session attrs
+  */
+ #define iscsi_session_attr_show(param, perm)				\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+ 									\
+ 	if (perm && !capable(CAP_SYS_ADMIN))				\
+@@ -1416,10 +1417,9 @@ iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+ iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+ 
+ static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
++show_priv_session_state(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);
+ 	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+ }
+ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+@@ -1427,11 +1427,9 @@ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1446,10 +1444,9 @@ iscsi_priv_session_attr(recovery_tmo, "%d");
+  */
+ #define iscsi_host_attr_show(param)					\
+ static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_host_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
++	struct Scsi_Host *shost = transport_class_to_shost(cdev);	\
+ 	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+ 	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+ }
+@@ -1466,7 +1463,7 @@ iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+ 
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1471,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,7 +1479,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1490,7 +1487,7 @@ do {									\
+ #define SETUP_HOST_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->host_attrs[count] = &class_device_attr_host_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,15 +1578,15 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+@@ -1666,8 +1663,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1691,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..7b90b63 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -225,7 +225,6 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+ struct iscsi_pool {
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.18-EL5.2/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch b/kernel_patches/backport/2.6.18-EL5.2/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
new file mode 100644
index 0000000..17ed782
--- /dev/null
+++ b/kernel_patches/backport/2.6.18-EL5.2/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
@@ -0,0 +1,705 @@
+From e1c4fe05b38843eb39fd6bae6ebc8cc8ce5d73a4 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 26 May 2008 11:27:56 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_release_2.0-869.2
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            |   31 +++----
+ drivers/scsi/libiscsi.c             |  169 +++++++---------------------------
+ drivers/scsi/scsi_transport_iscsi.c |  107 +++++++++++-----------
+ include/scsi/libiscsi.h             |    1 -
+ 4 files changed, 101 insertions(+), 207 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..8a17867 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -528,7 +528,6 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+ 	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
+ 	if (tcp_conn->in.datalen == 0)
+@@ -543,10 +542,10 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	tcp_ctask->exp_datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > scsi_bufflen(sc)) {
+ 		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+ 		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++		          tcp_conn->in.datalen, scsi_bufflen(sc));
+ 		return ISCSI_ERR_DATA_OFFSET;
+ 	}
+ 
+@@ -559,8 +558,8 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 			if (res_count > 0 &&
+ 			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
++			     res_count <= scsi_bufflen(sc)))
++				scsi_set_resid(sc, res_count);
+ 			else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+@@ -671,11 +670,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
++	if (r2t->data_offset + r2t->data_length > scsi_bufflen(ctask->sc)) {
+ 		iscsi_conn_printk(KERN_ERR, conn,
+ 				  "invalid R2T with data len %u at offset %u "
+ 				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
++				  r2t->data_offset, scsi_bufflen(ctask->sc));
+ 		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+ 			    sizeof(void*));
+ 		return ISCSI_ERR_DATALEN;
+@@ -772,7 +771,6 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		if (tcp_conn->in.datalen) {
+ 			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+ 
+ 			/*
+ 			 * Setup copy of Data-In into the Scsi_Cmnd
+@@ -790,8 +788,8 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 				  tcp_ctask->data_offset,
+ 				  tcp_conn->in.datalen);
+ 			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
++						     scsi_sglist(ctask->sc),
++						     scsi_sg_count(ctask->sc),
+ 						     tcp_ctask->data_offset,
+ 						     tcp_conn->in.datalen,
+ 						     iscsi_tcp_process_data_in,
+@@ -1334,8 +1332,7 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+ 		return 0;
+ 
+ 	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
++	err = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc), scsi_sg_count(sc),
+ 				       0, ctask->imm_count);
+ 	if (err)
+ 		return err;
+@@ -1389,7 +1386,6 @@ iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+ 	int rc = 0;
+ 
+ flush:
+@@ -1416,8 +1412,9 @@ flush:
+ 				ctask->itt, tcp_ctask->sent, ctask->data_count);
+ 
+ 		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
++					      tcp_ctask->sent,
+ 					      ctask->data_count);
+ 		if (rc)
+ 			goto fail;
+@@ -1463,8 +1460,8 @@ flush:
+ 		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+ 					sizeof(struct iscsi_hdr));
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
+ 					      r2t->data_offset + r2t->sent,
+ 					      r2t->data_count);
+ 		if (rc)
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..9975095 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -137,70 +137,6 @@ static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+ 	return 0;
+ }
+ 
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -214,7 +150,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
++	unsigned hdrlength;
+ 	int rc;
+ 
+ 	ctask->hdr_len = 0;
+@@ -225,30 +161,17 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	hdr->flags = ISCSI_ATTR_SIMPLE;
+ 	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+ 	hdr->itt = build_itt(ctask->itt, session->age);
++	hdr->data_length = cpu_to_be32(scsi_bufflen(sc));
+ 	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+ 	session->cmdsn++;
+ 	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
++	memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++	if (sc->cmd_len < MAX_COMMAND_SIZE)
++		memset(&hdr->cdb[sc->cmd_len], 0,
++			MAX_COMMAND_SIZE - sc->cmd_len);
+ 
+ 	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -269,19 +192,19 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (scsi_bufflen(sc) >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(scsi_bufflen(sc),
+ 							conn->max_xmit_dlength);
+ 			hton24(hdr->dlength, ctask->imm_count);
+ 		} else
+ 			zero_data(hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min((session->first_burst),
++				(scsi_bufflen(sc))) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+@@ -291,7 +214,6 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	} else {
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+@@ -310,12 +232,10 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		return EIO;
+ 
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
++	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x len %d "
++		"cmdsn %d win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, scsi_bufflen(sc),
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	return 0;
+ }
+@@ -378,12 +298,7 @@ static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		conn->session->tt->cleanup_cmd_task(conn, ctask);
+ 
+ 	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	if (conn->ctask == ctask)
+ 		conn->ctask = NULL;
+ 	/* release ref from queuecommand */
+@@ -518,18 +433,6 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
+-
+ 	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+ 	                   ISCSI_FLAG_CMD_OVERFLOW)) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+@@ -537,11 +440,13 @@ invalid_datalen:
+ 		if (res_count > 0 &&
+ 		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+ 		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+ 			scsi_set_resid(sc, res_count);
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
++	                          ISCSI_FLAG_CMD_BIDI_OVERFLOW))
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+@@ -730,7 +635,9 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+ 						   datalen))
+ 					rc = ISCSI_ERR_CONN_FAILED;
+-			}
++			} else
++				mod_timer(&conn->transport_timer,
++					  jiffies + conn->recv_timeout);
+ 			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		default:
+@@ -1197,12 +1104,7 @@ reject:
+ fault:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	sc->scsi_done(sc);
+ 	spin_lock(host->host_lock);
+ 	return 0;
+@@ -1453,19 +1355,20 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ {
+ 	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+ 	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	unsigned long recv_timeout, next_timeout = 0, last_recv;
+ 
+ 	spin_lock(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto done;
+ 
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
++	recv_timeout = conn->recv_timeout;
++	if (!recv_timeout)
+ 		goto done;
+ 
+-	timeout *= HZ;
++	recv_timeout *= HZ;
+ 	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
++	if (conn->ping_mtask &&
++	    time_before_eq(conn->last_ping + (conn->ping_timeout * HZ),
+ 			   jiffies)) {
+ 		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+ 				  "expired, last rx %lu, last ping %lu, "
+@@ -1476,15 +1379,13 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ 		return;
+ 	}
+ 
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
++	if (time_before_eq(last_recv + recv_timeout, jiffies)) {
++		/* send a ping to try to provoke some traffic */
++		debug_scsi("Sending nopout as ping on conn %p\n", conn);
++		iscsi_send_nopout(conn, NULL);
++		next_timeout = conn->last_ping + (conn->ping_timeout * HZ);
+ 	} else
+-		next_timeout = last_recv + timeout;
++		next_timeout = last_recv + recv_timeout;
+ 
+ 	debug_scsi("Setting next tmo %lu\n", next_timeout);
+ 	mod_timer(&conn->transport_timer, next_timeout);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..ca7bb6f 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -40,13 +40,13 @@ struct iscsi_internal {
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+@@ -63,12 +63,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +78,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +104,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -121,7 +119,7 @@ static struct attribute_group iscsi_transport_group = {
+ 
+ 
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -141,7 +139,7 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ }
+ 
+ static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
++			     struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -1339,8 +1337,11 @@ iscsi_if_rx(struct sk_buff *skb)
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1349,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1375,16 +1375,17 @@ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+ iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+ iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
+ 
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
++
+ /*
+  * iSCSI session attrs
+  */
+ #define iscsi_session_attr_show(param, perm)				\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+ 									\
+ 	if (perm && !capable(CAP_SYS_ADMIN))				\
+@@ -1416,10 +1417,9 @@ iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+ iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+ 
+ static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
++show_priv_session_state(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);
+ 	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+ }
+ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+@@ -1427,11 +1427,9 @@ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1446,10 +1444,9 @@ iscsi_priv_session_attr(recovery_tmo, "%d");
+  */
+ #define iscsi_host_attr_show(param)					\
+ static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_host_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
++	struct Scsi_Host *shost = transport_class_to_shost(cdev);	\
+ 	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+ 	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+ }
+@@ -1466,7 +1463,7 @@ iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+ 
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1471,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,7 +1479,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1490,7 +1487,7 @@ do {									\
+ #define SETUP_HOST_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->host_attrs[count] = &class_device_attr_host_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,15 +1578,15 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+@@ -1666,8 +1663,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1691,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..7b90b63 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -225,7 +225,6 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+ struct iscsi_pool {
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.18_FC6/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch b/kernel_patches/backport/2.6.18_FC6/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
new file mode 100644
index 0000000..17ed782
--- /dev/null
+++ b/kernel_patches/backport/2.6.18_FC6/iscsi_01_sync_kernel_code_with_release_2.0-869.2.patch
@@ -0,0 +1,705 @@
+From e1c4fe05b38843eb39fd6bae6ebc8cc8ce5d73a4 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 26 May 2008 11:27:56 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_release_2.0-869.2
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            |   31 +++----
+ drivers/scsi/libiscsi.c             |  169 +++++++---------------------------
+ drivers/scsi/scsi_transport_iscsi.c |  107 +++++++++++-----------
+ include/scsi/libiscsi.h             |    1 -
+ 4 files changed, 101 insertions(+), 207 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..8a17867 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -528,7 +528,6 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+ 	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
+ 	if (tcp_conn->in.datalen == 0)
+@@ -543,10 +542,10 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	tcp_ctask->exp_datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > scsi_bufflen(sc)) {
+ 		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+ 		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++		          tcp_conn->in.datalen, scsi_bufflen(sc));
+ 		return ISCSI_ERR_DATA_OFFSET;
+ 	}
+ 
+@@ -559,8 +558,8 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 			if (res_count > 0 &&
+ 			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
++			     res_count <= scsi_bufflen(sc)))
++				scsi_set_resid(sc, res_count);
+ 			else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+@@ -671,11 +670,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
++	if (r2t->data_offset + r2t->data_length > scsi_bufflen(ctask->sc)) {
+ 		iscsi_conn_printk(KERN_ERR, conn,
+ 				  "invalid R2T with data len %u at offset %u "
+ 				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
++				  r2t->data_offset, scsi_bufflen(ctask->sc));
+ 		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+ 			    sizeof(void*));
+ 		return ISCSI_ERR_DATALEN;
+@@ -772,7 +771,6 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		if (tcp_conn->in.datalen) {
+ 			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+ 
+ 			/*
+ 			 * Setup copy of Data-In into the Scsi_Cmnd
+@@ -790,8 +788,8 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 				  tcp_ctask->data_offset,
+ 				  tcp_conn->in.datalen);
+ 			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
++						     scsi_sglist(ctask->sc),
++						     scsi_sg_count(ctask->sc),
+ 						     tcp_ctask->data_offset,
+ 						     tcp_conn->in.datalen,
+ 						     iscsi_tcp_process_data_in,
+@@ -1334,8 +1332,7 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+ 		return 0;
+ 
+ 	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
++	err = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc), scsi_sg_count(sc),
+ 				       0, ctask->imm_count);
+ 	if (err)
+ 		return err;
+@@ -1389,7 +1386,6 @@ iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+ 	int rc = 0;
+ 
+ flush:
+@@ -1416,8 +1412,9 @@ flush:
+ 				ctask->itt, tcp_ctask->sent, ctask->data_count);
+ 
+ 		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
++					      tcp_ctask->sent,
+ 					      ctask->data_count);
+ 		if (rc)
+ 			goto fail;
+@@ -1463,8 +1460,8 @@ flush:
+ 		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+ 					sizeof(struct iscsi_hdr));
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
++		rc = iscsi_tcp_send_data_prep(conn, scsi_sglist(sc),
++					      scsi_sg_count(sc),
+ 					      r2t->data_offset + r2t->sent,
+ 					      r2t->data_count);
+ 		if (rc)
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..9975095 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -137,70 +137,6 @@ static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+ 	return 0;
+ }
+ 
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -214,7 +150,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
++	unsigned hdrlength;
+ 	int rc;
+ 
+ 	ctask->hdr_len = 0;
+@@ -225,30 +161,17 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	hdr->flags = ISCSI_ATTR_SIMPLE;
+ 	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+ 	hdr->itt = build_itt(ctask->itt, session->age);
++	hdr->data_length = cpu_to_be32(scsi_bufflen(sc));
+ 	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+ 	session->cmdsn++;
+ 	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
++	memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++	if (sc->cmd_len < MAX_COMMAND_SIZE)
++		memset(&hdr->cdb[sc->cmd_len], 0,
++			MAX_COMMAND_SIZE - sc->cmd_len);
+ 
+ 	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -269,19 +192,19 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (scsi_bufflen(sc) >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(scsi_bufflen(sc),
+ 							conn->max_xmit_dlength);
+ 			hton24(hdr->dlength, ctask->imm_count);
+ 		} else
+ 			zero_data(hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min((session->first_burst),
++				(scsi_bufflen(sc))) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+@@ -291,7 +214,6 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 	} else {
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+@@ -310,12 +232,10 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		return EIO;
+ 
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
++	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x len %d "
++		"cmdsn %d win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, scsi_bufflen(sc),
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	return 0;
+ }
+@@ -378,12 +298,7 @@ static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		conn->session->tt->cleanup_cmd_task(conn, ctask);
+ 
+ 	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	if (conn->ctask == ctask)
+ 		conn->ctask = NULL;
+ 	/* release ref from queuecommand */
+@@ -518,18 +433,6 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
+-
+ 	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+ 	                   ISCSI_FLAG_CMD_OVERFLOW)) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+@@ -537,11 +440,13 @@ invalid_datalen:
+ 		if (res_count > 0 &&
+ 		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+ 		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+ 			scsi_set_resid(sc, res_count);
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
++	                          ISCSI_FLAG_CMD_BIDI_OVERFLOW))
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+@@ -730,7 +635,9 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+ 						   datalen))
+ 					rc = ISCSI_ERR_CONN_FAILED;
+-			}
++			} else
++				mod_timer(&conn->transport_timer,
++					  jiffies + conn->recv_timeout);
+ 			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		default:
+@@ -1197,12 +1104,7 @@ reject:
+ fault:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	scsi_set_resid(sc, scsi_bufflen(sc));
+ 	sc->scsi_done(sc);
+ 	spin_lock(host->host_lock);
+ 	return 0;
+@@ -1453,19 +1355,20 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ {
+ 	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+ 	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	unsigned long recv_timeout, next_timeout = 0, last_recv;
+ 
+ 	spin_lock(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto done;
+ 
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
++	recv_timeout = conn->recv_timeout;
++	if (!recv_timeout)
+ 		goto done;
+ 
+-	timeout *= HZ;
++	recv_timeout *= HZ;
+ 	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
++	if (conn->ping_mtask &&
++	    time_before_eq(conn->last_ping + (conn->ping_timeout * HZ),
+ 			   jiffies)) {
+ 		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+ 				  "expired, last rx %lu, last ping %lu, "
+@@ -1476,15 +1379,13 @@ static void iscsi_check_transport_timeouts(unsigned long data)
+ 		return;
+ 	}
+ 
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
++	if (time_before_eq(last_recv + recv_timeout, jiffies)) {
++		/* send a ping to try to provoke some traffic */
++		debug_scsi("Sending nopout as ping on conn %p\n", conn);
++		iscsi_send_nopout(conn, NULL);
++		next_timeout = conn->last_ping + (conn->ping_timeout * HZ);
+ 	} else
+-		next_timeout = last_recv + timeout;
++		next_timeout = last_recv + recv_timeout;
+ 
+ 	debug_scsi("Setting next tmo %lu\n", next_timeout);
+ 	mod_timer(&conn->transport_timer, next_timeout);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..ca7bb6f 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -40,13 +40,13 @@ struct iscsi_internal {
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+@@ -63,12 +63,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +78,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +104,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -121,7 +119,7 @@ static struct attribute_group iscsi_transport_group = {
+ 
+ 
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -141,7 +139,7 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ }
+ 
+ static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
++			     struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -1339,8 +1337,11 @@ iscsi_if_rx(struct sk_buff *skb)
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1349,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1375,16 +1375,17 @@ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+ iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+ iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
+ 
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
++
+ /*
+  * iSCSI session attrs
+  */
+ #define iscsi_session_attr_show(param, perm)				\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+ 									\
+ 	if (perm && !capable(CAP_SYS_ADMIN))				\
+@@ -1416,10 +1417,9 @@ iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+ iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+ 
+ static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
++show_priv_session_state(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);
+ 	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+ }
+ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+@@ -1427,11 +1427,9 @@ static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1446,10 +1444,9 @@ iscsi_priv_session_attr(recovery_tmo, "%d");
+  */
+ #define iscsi_host_attr_show(param)					\
+ static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_host_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
++	struct Scsi_Host *shost = transport_class_to_shost(cdev);	\
+ 	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+ 	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+ }
+@@ -1466,7 +1463,7 @@ iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+ 
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1471,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,7 +1479,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1490,7 +1487,7 @@ do {									\
+ #define SETUP_HOST_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->host_attrs[count] = &class_device_attr_host_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,15 +1578,15 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+@@ -1666,8 +1663,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1691,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..7b90b63 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -225,7 +225,6 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+ struct iscsi_pool {
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.9_U4/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch b/kernel_patches/backport/2.6.9_U4/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
new file mode 100644
index 0000000..87d18ee
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U4/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
@@ -0,0 +1,7774 @@
+From 53de31f7a414a03b7939c20237ecb6e599631338 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 2 Jun 2008 12:01:07 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            | 2313 +++++++++++++++++++----------------
+ drivers/scsi/iscsi_tcp.h            |  131 ++-
+ drivers/scsi/libiscsi.c             | 1799 +++++++++------------------
+ drivers/scsi/scsi_transport_iscsi.c |  867 +++++---------
+ include/scsi/iscsi_if.h             |   54 -
+ include/scsi/libiscsi.h             |  188 +---
+ include/scsi/scsi_transport_iscsi.h |   65 +-
+ 7 files changed, 2296 insertions(+), 3121 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..c9a3abf 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -29,15 +29,14 @@
+ #include <linux/types.h>
+ #include <linux/list.h>
+ #include <linux/inet.h>
+-#include <linux/file.h>
+ #include <linux/blkdev.h>
+ #include <linux/crypto.h>
+ #include <linux/delay.h>
+ #include <linux/kfifo.h>
+ #include <linux/scatterlist.h>
++#include <linux/mutex.h>
+ #include <net/tcp.h>
+ #include <scsi/scsi_cmnd.h>
+-#include <scsi/scsi_device.h>
+ #include <scsi/scsi_host.h>
+ #include <scsi/scsi.h>
+ #include <scsi/scsi_transport_iscsi.h>
+@@ -48,7 +47,7 @@ MODULE_AUTHOR("Dmitry Yusupov <dmitry_yus at yahoo.com>, "
+ 	      "Alex Aizman <itn780 at yahoo.com>");
+ MODULE_DESCRIPTION("iSCSI/TCP data-path");
+ MODULE_LICENSE("GPL");
+-#undef DEBUG_TCP
++/* #define DEBUG_TCP */
+ #define DEBUG_ASSERT
+ 
+ #ifdef DEBUG_TCP
+@@ -67,429 +66,118 @@ MODULE_LICENSE("GPL");
+ static unsigned int iscsi_max_lun = 512;
+ module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);
+ 
+-static int iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-				   struct iscsi_segment *segment);
+-
+-/*
+- * Scatterlist handling: inside the iscsi_segment, we
+- * remember an index into the scatterlist, and set data/size
+- * to the current scatterlist entry. For highmem pages, we
+- * kmap as needed.
+- *
+- * Note that the page is unmapped when we return from
+- * TCP's data_ready handler, so we may end up mapping and
+- * unmapping the same page repeatedly. The whole reason
+- * for this is that we shouldn't keep the page mapped
+- * outside the softirq.
+- */
+-
+-/**
+- * iscsi_tcp_segment_init_sg - init indicated scatterlist entry
+- * @segment: the buffer object
+- * @sg: scatterlist
+- * @offset: byte offset into that sg entry
+- *
+- * This function sets up the segment so that subsequent
+- * data is copied to the indicated sg entry, at the given
+- * offset.
+- */
+ static inline void
+-iscsi_tcp_segment_init_sg(struct iscsi_segment *segment,
+-			  struct scatterlist *sg, unsigned int offset)
++iscsi_buf_init_iov(struct iscsi_buf *ibuf, char *vbuf, int size)
+ {
+-	segment->sg = sg;
+-	segment->sg_offset = offset;
+-	segment->size = min(sg->length - offset,
+-			    segment->total_size - segment->total_copied);
+-	segment->data = NULL;
++	ibuf->sg.page = virt_to_page(vbuf);
++	ibuf->sg.offset = offset_in_page(vbuf);
++	ibuf->sg.length = size;
++	ibuf->sent = 0;
++	ibuf->use_sendmsg = 1;
+ }
+ 
+-/**
+- * iscsi_tcp_segment_map - map the current S/G page
+- * @segment: iscsi_segment
+- * @recv: 1 if called from recv path
+- *
+- * We only need to possibly kmap data if scatter lists are being used,
+- * because the iscsi passthrough and internal IO paths will never use high
+- * mem pages.
+- */
+ static inline void
+-iscsi_tcp_segment_map(struct iscsi_segment *segment, int recv)
++iscsi_buf_init_sg(struct iscsi_buf *ibuf, struct scatterlist *sg)
+ {
+-	struct scatterlist *sg;
+-
+-	if (segment->data != NULL || !segment->sg)
+-		return;
+-
+-	sg = segment->sg;
+-	BUG_ON(segment->sg_mapped);
+-	BUG_ON(sg->length == 0);
+-
++	ibuf->sg.page = sg->page;
++	ibuf->sg.offset = sg->offset;
++	ibuf->sg.length = sg->length;
+ 	/*
+-	 * If the page count is greater than one it is ok to send
+-	 * to the network layer's zero copy send path. If not we
+-	 * have to go the slow sendmsg path. We always map for the
+-	 * recv path.
++	 * Fastpath: sg element fits into single page
+ 	 */
+-	if (page_count(sg_page(sg)) >= 1 && !recv)
+-		return;
+-
+-	debug_tcp("iscsi_tcp_segment_map %s %p\n", recv ? "recv" : "xmit",
+-		  segment);
+-	segment->sg_mapped = kmap_atomic(sg_page(sg), KM_SOFTIRQ0);
+-	segment->data = segment->sg_mapped + sg->offset + segment->sg_offset;
++	if (sg->length + sg->offset <= PAGE_SIZE && !PageSlab(sg->page))
++		ibuf->use_sendmsg = 0;
++	else
++		ibuf->use_sendmsg = 1;
++	ibuf->sent = 0;
+ }
+ 
+-static inline void
+-iscsi_tcp_segment_unmap(struct iscsi_segment *segment)
++static inline int
++iscsi_buf_left(struct iscsi_buf *ibuf)
+ {
+-	debug_tcp("iscsi_tcp_segment_unmap %p\n", segment);
++	int rc;
+ 
+-	if (segment->sg_mapped) {
+-		debug_tcp("iscsi_tcp_segment_unmap valid\n");
+-		kunmap_atomic(segment->sg_mapped, KM_SOFTIRQ0);
+-		segment->sg_mapped = NULL;
+-		segment->data = NULL;
+-	}
++	rc = ibuf->sg.length - ibuf->sent;
++	BUG_ON(rc < 0);
++	return rc;
+ }
+ 
+-/*
+- * Splice the digest buffer into the buffer
+- */
+ static inline void
+-iscsi_tcp_segment_splice_digest(struct iscsi_segment *segment, void *digest)
++iscsi_hdr_digest(struct iscsi_conn *conn, struct iscsi_buf *buf,
++		 u8* crc)
+ {
+-	segment->data = digest;
+-	segment->digest_len = ISCSI_DIGEST_SIZE;
+-	segment->total_size += ISCSI_DIGEST_SIZE;
+-	segment->size = ISCSI_DIGEST_SIZE;
+-	segment->copied = 0;
+-	segment->sg = NULL;
+-	segment->hash = NULL;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++
++	crypto_hash_digest(&tcp_conn->tx_hash, &buf->sg, buf->sg.length, crc);
++	buf->sg.length = tcp_conn->hdr_size;
+ }
+ 
+-/**
+- * iscsi_tcp_segment_done - check whether the segment is complete
+- * @segment: iscsi segment to check
+- * @recv: set to one of this is called from the recv path
+- * @copied: number of bytes copied
+- *
+- * Check if we're done receiving this segment. If the receive
+- * buffer is full but we expect more data, move on to the
+- * next entry in the scatterlist.
+- *
+- * If the amount of data we received isn't a multiple of 4,
+- * we will transparently receive the pad bytes, too.
+- *
+- * This function must be re-entrant.
+- */
+ static inline int
+-iscsi_tcp_segment_done(struct iscsi_segment *segment, int recv, unsigned copied)
++iscsi_hdr_extract(struct iscsi_tcp_conn *tcp_conn)
+ {
+-	static unsigned char padbuf[ISCSI_PAD_LEN];
+-	struct scatterlist sg;
+-	unsigned int pad;
++	struct sk_buff *skb = tcp_conn->in.skb;
++
++	tcp_conn->in.zero_copy_hdr = 0;
+ 
+-	debug_tcp("copied %u %u size %u %s\n", segment->copied, copied,
+-		  segment->size, recv ? "recv" : "xmit");
+-	if (segment->hash && copied) {
++	if (tcp_conn->in.copy >= tcp_conn->hdr_size &&
++	    tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER) {
+ 		/*
+-		 * If a segment is kmapd we must unmap it before sending
+-		 * to the crypto layer since that will try to kmap it again.
++		 * Zero-copy PDU Header: using connection context
++		 * to store header pointer.
+ 		 */
+-		iscsi_tcp_segment_unmap(segment);
+-
+-		if (!segment->data) {
+-			sg_init_table(&sg, 1);
+-			sg_set_page(&sg, sg_page(segment->sg), copied,
+-				    segment->copied + segment->sg_offset +
+-							segment->sg->offset);
+-		} else
+-			sg_init_one(&sg, segment->data + segment->copied,
+-				    copied);
+-		crypto_hash_update(segment->hash, &sg, copied);
+-	}
+-
+-	segment->copied += copied;
+-	if (segment->copied < segment->size) {
+-		iscsi_tcp_segment_map(segment, recv);
+-		return 0;
+-	}
+-
+-	segment->total_copied += segment->copied;
+-	segment->copied = 0;
+-	segment->size = 0;
+-
+-	/* Unmap the current scatterlist page, if there is one. */
+-	iscsi_tcp_segment_unmap(segment);
+-
+-	/* Do we have more scatterlist entries? */
+-	debug_tcp("total copied %u total size %u\n", segment->total_copied,
+-		   segment->total_size);
+-	if (segment->total_copied < segment->total_size) {
+-		/* Proceed to the next entry in the scatterlist. */
+-		iscsi_tcp_segment_init_sg(segment, sg_next(segment->sg),
+-					  0);
+-		iscsi_tcp_segment_map(segment, recv);
+-		BUG_ON(segment->size == 0);
+-		return 0;
+-	}
+-
+-	/* Do we need to handle padding? */
+-	pad = iscsi_padding(segment->total_copied);
+-	if (pad != 0) {
+-		debug_tcp("consume %d pad bytes\n", pad);
+-		segment->total_size += pad;
+-		segment->size = pad;
+-		segment->data = padbuf;
+-		return 0;
+-	}
+-
+-	/*
+-	 * Set us up for transferring the data digest. hdr digest
+-	 * is completely handled in hdr done function.
+-	 */
+-	if (segment->hash) {
+-		crypto_hash_final(segment->hash, segment->digest);
+-		iscsi_tcp_segment_splice_digest(segment,
+-				 recv ? segment->recv_digest : segment->digest);
+-		return 0;
+-	}
+-
+-	return 1;
+-}
+-
+-/**
+- * iscsi_tcp_xmit_segment - transmit segment
+- * @tcp_conn: the iSCSI TCP connection
+- * @segment: the buffer to transmnit
+- *
+- * This function transmits as much of the buffer as
+- * the network layer will accept, and returns the number of
+- * bytes transmitted.
+- *
+- * If CRC hashing is enabled, the function will compute the
+- * hash as it goes. When the entire segment has been transmitted,
+- * it will retrieve the hash value and send it as well.
+- */
+-static int
+-iscsi_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn,
+-		       struct iscsi_segment *segment)
+-{
+-	struct socket *sk = tcp_conn->sock;
+-	unsigned int copied = 0;
+-	int r = 0;
+-
+-	while (!iscsi_tcp_segment_done(segment, 0, r)) {
+-		struct scatterlist *sg;
+-		unsigned int offset, copy;
+-		int flags = 0;
+-
+-		r = 0;
+-		offset = segment->copied;
+-		copy = segment->size - offset;
+-
+-		if (segment->total_copied + segment->size < segment->total_size)
+-			flags |= MSG_MORE;
+-
+-		/* Use sendpage if we can; else fall back to sendmsg */
+-		if (!segment->data) {
+-			sg = segment->sg;
+-			offset += segment->sg_offset + sg->offset;
+-			r = tcp_conn->sendpage(sk, sg_page(sg), offset, copy,
+-					       flags);
++		if (skb_shinfo(skb)->frag_list == NULL &&
++		    !skb_shinfo(skb)->nr_frags) {
++			tcp_conn->in.hdr = (struct iscsi_hdr *)
++				((char*)skb->data + tcp_conn->in.offset);
++			tcp_conn->in.zero_copy_hdr = 1;
+ 		} else {
+-			struct msghdr msg = { .msg_flags = flags };
+-			struct kvec iov = {
+-				.iov_base = segment->data + offset,
+-				.iov_len = copy
+-			};
+-
+-			r = kernel_sendmsg(sk, &msg, &iov, 1, copy);
+-		}
+-
+-		if (r < 0) {
+-			iscsi_tcp_segment_unmap(segment);
+-			if (copied || r == -EAGAIN)
+-				break;
+-			return r;
++			/* ignoring return code since we checked
++			 * in.copy before */
++			skb_copy_bits(skb, tcp_conn->in.offset,
++				&tcp_conn->hdr, tcp_conn->hdr_size);
++			tcp_conn->in.hdr = &tcp_conn->hdr;
+ 		}
+-		copied += r;
+-	}
+-	return copied;
+-}
+-
+-/**
+- * iscsi_tcp_segment_recv - copy data to segment
+- * @tcp_conn: the iSCSI TCP connection
+- * @segment: the buffer to copy to
+- * @ptr: data pointer
+- * @len: amount of data available
+- *
+- * This function copies up to @len bytes to the
+- * given buffer, and returns the number of bytes
+- * consumed, which can actually be less than @len.
+- *
+- * If hash digest is enabled, the function will update the
+- * hash while copying.
+- * Combining these two operations doesn't buy us a lot (yet),
+- * but in the future we could implement combined copy+crc,
+- * just way we do for network layer checksums.
+- */
+-static int
+-iscsi_tcp_segment_recv(struct iscsi_tcp_conn *tcp_conn,
+-		       struct iscsi_segment *segment, const void *ptr,
+-		       unsigned int len)
+-{
+-	unsigned int copy = 0, copied = 0;
+-
+-	while (!iscsi_tcp_segment_done(segment, 1, copy)) {
+-		if (copied == len) {
+-			debug_tcp("iscsi_tcp_segment_recv copied %d bytes\n",
+-				  len);
+-			break;
+-		}
+-
+-		copy = min(len - copied, segment->size - segment->copied);
+-		debug_tcp("iscsi_tcp_segment_recv copying %d\n", copy);
+-		memcpy(segment->data + segment->copied, ptr + copied, copy);
+-		copied += copy;
+-	}
+-	return copied;
+-}
+-
+-static inline void
+-iscsi_tcp_dgst_header(struct hash_desc *hash, const void *hdr, size_t hdrlen,
+-		      unsigned char digest[ISCSI_DIGEST_SIZE])
+-{
+-	struct scatterlist sg;
++		tcp_conn->in.offset += tcp_conn->hdr_size;
++		tcp_conn->in.copy -= tcp_conn->hdr_size;
++	} else {
++		int hdr_remains;
++		int copylen;
+ 
+-	sg_init_one(&sg, hdr, hdrlen);
+-	crypto_hash_digest(hash, &sg, hdrlen, digest);
+-}
++		/*
++		 * PDU header scattered across SKB's,
++		 * copying it... This'll happen quite rarely.
++		 */
+ 
+-static inline int
+-iscsi_tcp_dgst_verify(struct iscsi_tcp_conn *tcp_conn,
+-		      struct iscsi_segment *segment)
+-{
+-	if (!segment->digest_len)
+-		return 1;
++		if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER)
++			tcp_conn->in.hdr_offset = 0;
+ 
+-	if (memcmp(segment->recv_digest, segment->digest,
+-		   segment->digest_len)) {
+-		debug_scsi("digest mismatch\n");
+-		return 0;
+-	}
++		hdr_remains = tcp_conn->hdr_size - tcp_conn->in.hdr_offset;
++		BUG_ON(hdr_remains <= 0);
+ 
+-	return 1;
+-}
++		copylen = min(tcp_conn->in.copy, hdr_remains);
++		skb_copy_bits(skb, tcp_conn->in.offset,
++			(char*)&tcp_conn->hdr + tcp_conn->in.hdr_offset,
++			copylen);
+ 
+-/*
+- * Helper function to set up segment buffer
+- */
+-static inline void
+-__iscsi_segment_init(struct iscsi_segment *segment, size_t size,
+-		     iscsi_segment_done_fn_t *done, struct hash_desc *hash)
+-{
+-	memset(segment, 0, sizeof(*segment));
+-	segment->total_size = size;
+-	segment->done = done;
+-
+-	if (hash) {
+-		segment->hash = hash;
+-		crypto_hash_init(hash);
+-	}
+-}
++		debug_tcp("PDU gather offset %d bytes %d in.offset %d "
++		       "in.copy %d\n", tcp_conn->in.hdr_offset, copylen,
++		       tcp_conn->in.offset, tcp_conn->in.copy);
+ 
+-static inline void
+-iscsi_segment_init_linear(struct iscsi_segment *segment, void *data,
+-			  size_t size, iscsi_segment_done_fn_t *done,
+-			  struct hash_desc *hash)
+-{
+-	__iscsi_segment_init(segment, size, done, hash);
+-	segment->data = data;
+-	segment->size = size;
+-}
+-
+-static inline int
+-iscsi_segment_seek_sg(struct iscsi_segment *segment,
+-		      struct scatterlist *sg_list, unsigned int sg_count,
+-		      unsigned int offset, size_t size,
+-		      iscsi_segment_done_fn_t *done, struct hash_desc *hash)
+-{
+-	struct scatterlist *sg;
+-	unsigned int i;
+-
+-	debug_scsi("iscsi_segment_seek_sg offset %u size %llu\n",
+-		  offset, size);
+-	__iscsi_segment_init(segment, size, done, hash);
+-	for_each_sg(sg_list, sg, sg_count, i) {
+-		debug_scsi("sg %d, len %u offset %u\n", i, sg->length,
+-			   sg->offset);
+-		if (offset < sg->length) {
+-			iscsi_tcp_segment_init_sg(segment, sg, offset);
+-			return 0;
++		tcp_conn->in.offset += copylen;
++		tcp_conn->in.copy -= copylen;
++		if (copylen < hdr_remains)  {
++			tcp_conn->in_progress = IN_PROGRESS_HEADER_GATHER;
++			tcp_conn->in.hdr_offset += copylen;
++		        return -EAGAIN;
+ 		}
+-		offset -= sg->length;
++		tcp_conn->in.hdr = &tcp_conn->hdr;
++		tcp_conn->discontiguous_hdr_cnt++;
++	        tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 	}
+ 
+-	return ISCSI_ERR_DATA_OFFSET;
+-}
+-
+-/**
+- * iscsi_tcp_hdr_recv_prep - prep segment for hdr reception
+- * @tcp_conn: iscsi connection to prep for
+- *
+- * This function always passes NULL for the hash argument, because when this
+- * function is called we do not yet know the final size of the header and want
+- * to delay the digest processing until we know that.
+- */
+-static void
+-iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn)
+-{
+-	debug_tcp("iscsi_tcp_hdr_recv_prep(%p%s)\n", tcp_conn,
+-		  tcp_conn->iscsi_conn->hdrdgst_en ? ", digest enabled" : "");
+-	iscsi_segment_init_linear(&tcp_conn->in.segment,
+-				tcp_conn->in.hdr_buf, sizeof(struct iscsi_hdr),
+-				iscsi_tcp_hdr_recv_done, NULL);
+-}
+-
+-/*
+- * Handle incoming reply to any other type of command
+- */
+-static int
+-iscsi_tcp_data_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-			 struct iscsi_segment *segment)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	int rc = 0;
+-
+-	if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-		return ISCSI_ERR_DATA_DGST;
+-
+-	rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr,
+-			conn->data, tcp_conn->in.datalen);
+-	if (rc)
+-		return rc;
+-
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+ 	return 0;
+ }
+ 
+-static void
+-iscsi_tcp_data_recv_prep(struct iscsi_tcp_conn *tcp_conn)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct hash_desc *rx_hash = NULL;
+-
+-	if (conn->datadgst_en)
+-		rx_hash = &tcp_conn->rx_hash;
+-
+-	iscsi_segment_init_linear(&tcp_conn->in.segment,
+-				conn->data, tcp_conn->in.datalen,
+-				iscsi_tcp_data_recv_done, rx_hash);
+-}
+-
+ /*
+  * must be called with session lock
+  */
+@@ -498,6 +186,7 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct iscsi_r2t_info *r2t;
++	struct scsi_cmnd *sc;
+ 
+ 	/* flush ctask's r2t queues */
+ 	while (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*))) {
+@@ -506,12 +195,12 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 		debug_scsi("iscsi_tcp_cleanup_ctask pending r2t dropped\n");
+ 	}
+ 
+-	r2t = tcp_ctask->r2t;
+-	if (r2t != NULL) {
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
+-		tcp_ctask->r2t = NULL;
+-	}
++	sc = ctask->sc;
++	if (unlikely(!sc))
++		return;
++
++	tcp_ctask->xmstate = XMSTATE_IDLE;
++	tcp_ctask->r2t = NULL;
+ }
+ 
+ /**
+@@ -522,49 +211,52 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ static int
+ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
++	int rc;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)tcp_conn->in.hdr;
+ 	struct iscsi_session *session = conn->session;
+-	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc)
++		return rc;
++	/*
++	 * setup Data-In byte counter (gets decremented..)
++	 */
++	ctask->data_count = tcp_conn->in.datalen;
++
+ 	if (tcp_conn->in.datalen == 0)
+ 		return 0;
+ 
+-	if (tcp_ctask->exp_datasn != datasn) {
+-		debug_tcp("%s: ctask->exp_datasn(%d) != rhdr->datasn(%d)\n",
+-		          __FUNCTION__, tcp_ctask->exp_datasn, datasn);
++	if (ctask->datasn != datasn)
+ 		return ISCSI_ERR_DATASN;
+-	}
+ 
+-	tcp_ctask->exp_datasn++;
++	ctask->datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
+-		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+-		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > ctask->total_length)
+ 		return ISCSI_ERR_DATA_OFFSET;
+-	}
+ 
+ 	if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) {
+-		sc->result = (DID_OK << 16) | rhdr->cmd_status;
++		struct scsi_cmnd *sc = ctask->sc;
++
+ 		conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+-		if (rhdr->flags & (ISCSI_FLAG_DATA_UNDERFLOW |
+-		                   ISCSI_FLAG_DATA_OVERFLOW)) {
++		if (rhdr->flags & ISCSI_FLAG_DATA_UNDERFLOW) {
+ 			int res_count = be32_to_cpu(rhdr->residual_count);
+ 
+ 			if (res_count > 0 &&
+-			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
+-			else
++			    res_count <= sc->request_bufflen) {
++				sc->resid = res_count;
++				sc->result = (DID_OK << 16) | rhdr->cmd_status;
++			} else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+-		}
++		} else if (rhdr->flags & ISCSI_FLAG_DATA_OVERFLOW) {
++			sc->resid = be32_to_cpu(rhdr->residual_count);
++			sc->result = (DID_OK << 16) | rhdr->cmd_status;
++		} else
++			sc->result = (DID_OK << 16) | rhdr->cmd_status;
+ 	}
+ 
+ 	conn->datain_pdus_cnt++;
+@@ -588,6 +280,7 @@ iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 			struct iscsi_r2t_info *r2t)
+ {
+ 	struct iscsi_data *hdr;
++	struct scsi_cmnd *sc = ctask->sc;
+ 
+ 	hdr = &r2t->dtask.hdr;
+ 	memset(hdr, 0, sizeof(struct iscsi_data));
+@@ -611,6 +304,43 @@ iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 	conn->dataout_pdus_cnt++;
+ 
+ 	r2t->sent = 0;
++
++	iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr,
++			   sizeof(struct iscsi_hdr));
++
++	if (sc->use_sg) {
++		int i, sg_count = 0;
++		struct scatterlist *sg = sc->request_buffer;
++
++		r2t->sg = NULL;
++		for (i = 0; i < sc->use_sg; i++, sg += 1) {
++			/* FIXME: prefetch ? */
++			if (sg_count + sg->length > r2t->data_offset) {
++				int page_offset;
++
++				/* sg page found! */
++
++				/* offset within this page */
++				page_offset = r2t->data_offset - sg_count;
++
++				/* fill in this buffer */
++				iscsi_buf_init_sg(&r2t->sendbuf, sg);
++				r2t->sendbuf.sg.offset += page_offset;
++				r2t->sendbuf.sg.length -= page_offset;
++
++				/* xmit logic will continue with next one */
++				r2t->sg = sg + 1;
++				break;
++			}
++			sg_count += sg->length;
++		}
++		BUG_ON(r2t->sg == NULL);
++	} else {
++		iscsi_buf_init_iov(&r2t->sendbuf,
++			    (char*)sc->request_buffer + r2t->data_offset,
++			    r2t->data_count);
++		r2t->sg = NULL;
++	}
+ }
+ 
+ /**
+@@ -630,25 +360,27 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	int rc;
+ 
+ 	if (tcp_conn->in.datalen) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2t with datalen %d\n",
+-				  tcp_conn->in.datalen);
++		printk(KERN_ERR "iscsi_tcp: invalid R2t with datalen %d\n",
++		       tcp_conn->in.datalen);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+-	if (tcp_ctask->exp_datasn != r2tsn){
+-		debug_tcp("%s: ctask->exp_datasn(%d) != rhdr->r2tsn(%d)\n",
+-		          __FUNCTION__, tcp_ctask->exp_datasn, r2tsn);
++	if (tcp_ctask->exp_r2tsn && tcp_ctask->exp_r2tsn != r2tsn)
+ 		return ISCSI_ERR_R2TSN;
+-	}
+ 
+-	/* fill-in new R2T associated with the task */
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc)
++		return rc;
+ 
+-	if (!ctask->sc || session->state != ISCSI_STATE_LOGGED_IN) {
+-		iscsi_conn_printk(KERN_INFO, conn,
+-				  "dropping R2T itt %d in recovery.\n",
+-				  ctask->itt);
++	/* FIXME: use R2TSN to detect missing R2T */
++
++	/* fill-in new R2T associated with the task */
++	spin_lock(&session->lock);
++	if (!ctask->sc || ctask->mtask ||
++	     session->state != ISCSI_STATE_LOGGED_IN) {
++		printk(KERN_INFO "iscsi_tcp: dropping R2T itt %d in "
++		       "recovery...\n", ctask->itt);
++		spin_unlock(&session->lock);
+ 		return 0;
+ 	}
+ 
+@@ -658,10 +390,8 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	r2t->exp_statsn = rhdr->statsn;
+ 	r2t->data_length = be32_to_cpu(rhdr->data_length);
+ 	if (r2t->data_length == 0) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2T with zero data len\n");
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
++		printk(KERN_ERR "iscsi_tcp: invalid R2T with zero data len\n");
++		spin_unlock(&session->lock);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+@@ -671,13 +401,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2T with data len %u at offset %u "
+-				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
++	if (r2t->data_offset + r2t->data_length > ctask->total_length) {
++		spin_unlock(&session->lock);
++		printk(KERN_ERR "iscsi_tcp: invalid R2T with data len %u at "
++		       "offset %u and total length %d\n", r2t->data_length,
++		       r2t->data_offset, ctask->total_length);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+@@ -686,134 +414,108 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 	iscsi_solicit_data_init(conn, ctask, r2t);
+ 
+-	tcp_ctask->exp_datasn = r2tsn + 1;
++	tcp_ctask->exp_r2tsn = r2tsn + 1;
+ 	__kfifo_put(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*));
+-	conn->r2t_pdus_cnt++;
++	tcp_ctask->xmstate |= XMSTATE_SOL_HDR;
++	list_move_tail(&ctask->running, &conn->xmitqueue);
+ 
+-	iscsi_requeue_ctask(ctask);
+-	return 0;
+-}
+-
+-/*
+- * Handle incoming reply to DataIn command
+- */
+-static int
+-iscsi_tcp_process_data_in(struct iscsi_tcp_conn *tcp_conn,
+-			  struct iscsi_segment *segment)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct iscsi_hdr *hdr = tcp_conn->in.hdr;
+-	int rc;
+-
+-	if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-		return ISCSI_ERR_DATA_DGST;
+-
+-	/* check for non-exceptional status */
+-	if (hdr->flags & ISCSI_FLAG_DATA_STATUS) {
+-		rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
+-		if (rc)
+-			return rc;
+-	}
++	scsi_queue_work(session->host, &conn->xmitwork);
++	conn->r2t_pdus_cnt++;
++	spin_unlock(&session->lock);
+ 
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+ 	return 0;
+ }
+ 
+-/**
+- * iscsi_tcp_hdr_dissect - process PDU header
+- * @conn: iSCSI connection
+- * @hdr: PDU header
+- *
+- * This function analyzes the header of the PDU received,
+- * and performs several sanity checks. If the PDU is accompanied
+- * by data, the receive buffer is set up to copy the incoming data
+- * to the correct location.
+- */
+ static int
+-iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
++iscsi_tcp_hdr_recv(struct iscsi_conn *conn)
+ {
+ 	int rc = 0, opcode, ahslen;
++	struct iscsi_hdr *hdr;
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_cmd_task *ctask;
+-	uint32_t itt;
++	uint32_t cdgst, rdgst = 0, itt;
++
++	hdr = tcp_conn->in.hdr;
+ 
+ 	/* verify PDU length */
+ 	tcp_conn->in.datalen = ntoh24(hdr->dlength);
+ 	if (tcp_conn->in.datalen > conn->max_recv_dlength) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "iscsi_tcp: datalen %d > %d\n",
+-				  tcp_conn->in.datalen, conn->max_recv_dlength);
++		printk(KERN_ERR "iscsi_tcp: datalen %d > %d\n",
++		       tcp_conn->in.datalen, conn->max_recv_dlength);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
++	tcp_conn->data_copied = 0;
+ 
+-	/* Additional header segments. So far, we don't
+-	 * process additional headers.
+-	 */
++	/* read AHS */
+ 	ahslen = hdr->hlength << 2;
++	tcp_conn->in.offset += ahslen;
++	tcp_conn->in.copy -= ahslen;
++	if (tcp_conn->in.copy < 0) {
++		printk(KERN_ERR "iscsi_tcp: can't handle AHS with length "
++		       "%d bytes\n", ahslen);
++		return ISCSI_ERR_AHSLEN;
++	}
++
++	/* calculate read padding */
++	tcp_conn->in.padding = tcp_conn->in.datalen & (ISCSI_PAD_LEN-1);
++	if (tcp_conn->in.padding) {
++		tcp_conn->in.padding = ISCSI_PAD_LEN - tcp_conn->in.padding;
++		debug_scsi("read padding %d bytes\n", tcp_conn->in.padding);
++	}
++
++	if (conn->hdrdgst_en) {
++		struct scatterlist sg;
++
++		sg_init_one(&sg, (u8 *)hdr,
++			    sizeof(struct iscsi_hdr) + ahslen);
++		crypto_hash_digest(&tcp_conn->rx_hash, &sg, sg.length,
++				   (u8 *)&cdgst);
++		rdgst = *(uint32_t*)((char*)hdr + sizeof(struct iscsi_hdr) +
++				     ahslen);
++		if (cdgst != rdgst) {
++			printk(KERN_ERR "iscsi_tcp: hdrdgst error "
++			       "recv 0x%x calc 0x%x\n", rdgst, cdgst);
++			return ISCSI_ERR_HDR_DGST;
++		}
++	}
+ 
+ 	opcode = hdr->opcode & ISCSI_OPCODE_MASK;
+ 	/* verify itt (itt encoding: age+cid+itt) */
+ 	rc = iscsi_verify_itt(conn, hdr, &itt);
+-	if (rc)
++	if (rc == ISCSI_ERR_NO_SCSI_CMD) {
++		tcp_conn->in.datalen = 0; /* force drop */
++		return 0;
++	} else if (rc)
+ 		return rc;
+ 
+-	debug_tcp("opcode 0x%x ahslen %d datalen %d\n",
+-		  opcode, ahslen, tcp_conn->in.datalen);
++	debug_tcp("opcode 0x%x offset %d copy %d ahslen %d datalen %d\n",
++		  opcode, tcp_conn->in.offset, tcp_conn->in.copy,
++		  ahslen, tcp_conn->in.datalen);
+ 
+ 	switch(opcode) {
+ 	case ISCSI_OP_SCSI_DATA_IN:
+-		ctask = session->cmds[itt];
+-		spin_lock(&conn->session->lock);
+-		rc = iscsi_data_rsp(conn, ctask);
+-		spin_unlock(&conn->session->lock);
++		tcp_conn->in.ctask = session->cmds[itt];
++		rc = iscsi_data_rsp(conn, tcp_conn->in.ctask);
+ 		if (rc)
+ 			return rc;
+-		if (tcp_conn->in.datalen) {
+-			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+-
+-			/*
+-			 * Setup copy of Data-In into the Scsi_Cmnd
+-			 * Scatterlist case:
+-			 * We set up the iscsi_segment to point to the next
+-			 * scatterlist entry to copy to. As we go along,
+-			 * we move on to the next scatterlist entry and
+-			 * update the digest per-entry.
+-			 */
+-			if (conn->datadgst_en)
+-				rx_hash = &tcp_conn->rx_hash;
+-
+-			debug_tcp("iscsi_tcp_begin_data_in(%p, offset=%d, "
+-				  "datalen=%d)\n", tcp_conn,
+-				  tcp_ctask->data_offset,
+-				  tcp_conn->in.datalen);
+-			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
+-						     tcp_ctask->data_offset,
+-						     tcp_conn->in.datalen,
+-						     iscsi_tcp_process_data_in,
+-						     rx_hash);
+-		}
+ 		/* fall through */
+ 	case ISCSI_OP_SCSI_CMD_RSP:
+-		if (tcp_conn->in.datalen) {
+-			iscsi_tcp_data_recv_prep(tcp_conn);
+-			return 0;
+-		}
+-		rc = iscsi_complete_pdu(conn, hdr, NULL, 0);
++		tcp_conn->in.ctask = session->cmds[itt];
++		if (tcp_conn->in.datalen)
++			goto copy_hdr;
++
++		spin_lock(&session->lock);
++		rc = __iscsi_complete_pdu(conn, hdr, NULL, 0);
++		spin_unlock(&session->lock);
+ 		break;
+ 	case ISCSI_OP_R2T:
+-		ctask = session->cmds[itt];
++		tcp_conn->in.ctask = session->cmds[itt];
+ 		if (ahslen)
+ 			rc = ISCSI_ERR_AHSLEN;
+-		else if (ctask->sc->sc_data_direction == DMA_TO_DEVICE) {
+-			spin_lock(&session->lock);
+-			rc = iscsi_r2t_rsp(conn, ctask);
+-			spin_unlock(&session->lock);
+-		} else
++		else if (tcp_conn->in.ctask->sc->sc_data_direction ==
++								DMA_TO_DEVICE)
++			rc = iscsi_r2t_rsp(conn, tcp_conn->in.ctask);
++		else
+ 			rc = ISCSI_ERR_PROTO;
+ 		break;
+ 	case ISCSI_OP_LOGIN_RSP:
+@@ -825,24 +527,18 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		 * than 8K, but there are no targets that currently do this.
+ 		 * For now we fail until we find a vendor that needs it
+ 		 */
+-		if (ISCSI_DEF_MAX_RECV_SEG_LEN < tcp_conn->in.datalen) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "iscsi_tcp: received buffer of "
+-					  "len %u but conn buffer is only %u "
+-					  "(opcode %0x)\n",
+-					  tcp_conn->in.datalen,
+-					  ISCSI_DEF_MAX_RECV_SEG_LEN, opcode);
++		if (ISCSI_DEF_MAX_RECV_SEG_LEN <
++		    tcp_conn->in.datalen) {
++			printk(KERN_ERR "iscsi_tcp: received buffer of len %u "
++			      "but conn buffer is only %u (opcode %0x)\n",
++			      tcp_conn->in.datalen,
++			      ISCSI_DEF_MAX_RECV_SEG_LEN, opcode);
+ 			rc = ISCSI_ERR_PROTO;
+ 			break;
+ 		}
+ 
+-		/* If there's data coming in with the response,
+-		 * receive it to the connection's buffer.
+-		 */
+-		if (tcp_conn->in.datalen) {
+-			iscsi_tcp_data_recv_prep(tcp_conn);
+-			return 0;
+-		}
++		if (tcp_conn->in.datalen)
++			goto copy_hdr;
+ 	/* fall through */
+ 	case ISCSI_OP_LOGOUT_RSP:
+ 	case ISCSI_OP_NOOP_IN:
+@@ -854,161 +550,457 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		break;
+ 	}
+ 
+-	if (rc == 0) {
+-		/* Anything that comes with data should have
+-		 * been handled above. */
+-		if (tcp_conn->in.datalen)
+-			return ISCSI_ERR_PROTO;
+-		iscsi_tcp_hdr_recv_prep(tcp_conn);
++	return rc;
++
++copy_hdr:
++	/*
++	 * if we did zero copy for the header but we will need multiple
++	 * skbs to complete the command then we have to copy the header
++	 * for later use
++	 */
++	if (tcp_conn->in.zero_copy_hdr && tcp_conn->in.copy <=
++	   (tcp_conn->in.datalen + tcp_conn->in.padding +
++	    (conn->datadgst_en ? 4 : 0))) {
++		debug_tcp("Copying header for later use. in.copy %d in.datalen"
++			  " %d\n", tcp_conn->in.copy, tcp_conn->in.datalen);
++		memcpy(&tcp_conn->hdr, tcp_conn->in.hdr,
++		       sizeof(struct iscsi_hdr));
++		tcp_conn->in.hdr = &tcp_conn->hdr;
++		tcp_conn->in.zero_copy_hdr = 0;
+ 	}
++	return 0;
++}
+ 
+-	return rc;
++/**
++ * iscsi_ctask_copy - copy skb bits to the destanation cmd task
++ * @conn: iscsi tcp connection
++ * @ctask: scsi command task
++ * @buf: buffer to copy to
++ * @buf_size: size of buffer
++ * @offset: offset within the buffer
++ *
++ * Notes:
++ *	The function calls skb_copy_bits() and updates per-connection and
++ *	per-cmd byte counters.
++ *
++ *	Read counters (in bytes):
++ *
++ *	conn->in.offset		offset within in progress SKB
++ *	conn->in.copy		left to copy from in progress SKB
++ *				including padding
++ *	conn->in.copied		copied already from in progress SKB
++ *	conn->data_copied	copied already from in progress buffer
++ *	ctask->sent		total bytes sent up to the MidLayer
++ *	ctask->data_count	left to copy from in progress Data-In
++ *	buf_left		left to copy from in progress buffer
++ **/
++static inline int
++iscsi_ctask_copy(struct iscsi_tcp_conn *tcp_conn, struct iscsi_cmd_task *ctask,
++		void *buf, int buf_size, int offset)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int buf_left = buf_size - (tcp_conn->data_copied + offset);
++	int size = min(tcp_conn->in.copy, buf_left);
++	int rc;
++
++	size = min(size, ctask->data_count);
++
++	debug_tcp("ctask_copy %d bytes at offset %d copied %d\n",
++	       size, tcp_conn->in.offset, tcp_conn->in.copied);
++
++	BUG_ON(size <= 0);
++	BUG_ON(tcp_ctask->sent + size > ctask->total_length);
++
++	rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
++			   (char*)buf + (offset + tcp_conn->data_copied), size);
++	/* must fit into skb->len */
++	BUG_ON(rc);
++
++	tcp_conn->in.offset += size;
++	tcp_conn->in.copy -= size;
++	tcp_conn->in.copied += size;
++	tcp_conn->data_copied += size;
++	tcp_ctask->sent += size;
++	ctask->data_count -= size;
++
++	BUG_ON(tcp_conn->in.copy < 0);
++	BUG_ON(ctask->data_count < 0);
++
++	if (buf_size != (tcp_conn->data_copied + offset)) {
++		if (!ctask->data_count) {
++			BUG_ON(buf_size - tcp_conn->data_copied < 0);
++			/* done with this PDU */
++			return buf_size - tcp_conn->data_copied;
++		}
++		return -EAGAIN;
++	}
++
++	/* done with this buffer or with both - PDU and buffer */
++	tcp_conn->data_copied = 0;
++	return 0;
+ }
+ 
+ /**
+- * iscsi_tcp_hdr_recv_done - process PDU header
++ * iscsi_tcp_copy - copy skb bits to the destanation buffer
++ * @conn: iscsi tcp connection
+  *
+- * This is the callback invoked when the PDU header has
+- * been received. If the header is followed by additional
+- * header segments, we go back for more data.
+- */
+-static int
+-iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-			struct iscsi_segment *segment)
++ * Notes:
++ *	The function calls skb_copy_bits() and updates per-connection
++ *	byte counters.
++ **/
++static inline int
++iscsi_tcp_copy(struct iscsi_conn *conn, int buf_size)
+ {
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct iscsi_hdr *hdr;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int buf_left = buf_size - tcp_conn->data_copied;
++	int size = min(tcp_conn->in.copy, buf_left);
++	int rc;
++
++	debug_tcp("tcp_copy %d bytes at offset %d copied %d\n",
++	       size, tcp_conn->in.offset, tcp_conn->data_copied);
++	BUG_ON(size <= 0);
++
++	rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
++			   (char*)conn->data + tcp_conn->data_copied, size);
++	BUG_ON(rc);
++
++	tcp_conn->in.offset += size;
++	tcp_conn->in.copy -= size;
++	tcp_conn->in.copied += size;
++	tcp_conn->data_copied += size;
++
++	if (buf_size != tcp_conn->data_copied)
++		return -EAGAIN;
++
++	return 0;
++}
++
++static inline void
++partial_sg_digest_update(struct hash_desc *desc, struct scatterlist *sg,
++			 int offset, int length)
++{
++	struct scatterlist temp;
++
++	memcpy(&temp, sg, sizeof(struct scatterlist));
++	temp.offset = offset;
++	temp.length = length;
++	crypto_hash_update(desc, &temp, length);
++}
++
++static void
++iscsi_recv_digest_update(struct iscsi_tcp_conn *tcp_conn, char* buf, int len)
++{
++	struct scatterlist tmp;
++
++	sg_init_one(&tmp, buf, len);
++	crypto_hash_update(&tcp_conn->rx_hash, &tmp, len);
++}
+ 
+-	/* Check if there are additional header segments
+-	 * *prior* to computing the digest, because we
+-	 * may need to go back to the caller for more.
++static int iscsi_scsi_data_in(struct iscsi_conn *conn)
++{
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	struct iscsi_cmd_task *ctask = tcp_conn->in.ctask;
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct scsi_cmnd *sc = ctask->sc;
++	struct scatterlist *sg;
++	int i, offset, rc = 0;
++
++	BUG_ON((void*)ctask != sc->SCp.ptr);
++
++	/*
++	 * copying Data-In into the Scsi_Cmnd
+ 	 */
+-	hdr = (struct iscsi_hdr *) tcp_conn->in.hdr_buf;
+-	if (segment->copied == sizeof(struct iscsi_hdr) && hdr->hlength) {
+-		/* Bump the header length - the caller will
+-		 * just loop around and get the AHS for us, and
+-		 * call again. */
+-		unsigned int ahslen = hdr->hlength << 2;
+-
+-		/* Make sure we don't overflow */
+-		if (sizeof(*hdr) + ahslen > sizeof(tcp_conn->in.hdr_buf))
+-			return ISCSI_ERR_AHSLEN;
+-
+-		segment->total_size += ahslen;
+-		segment->size += ahslen;
+-		return 0;
++	if (!sc->use_sg) {
++		i = ctask->data_count;
++		rc = iscsi_ctask_copy(tcp_conn, ctask, sc->request_buffer,
++				      sc->request_bufflen,
++				      tcp_ctask->data_offset);
++		if (rc == -EAGAIN)
++			return rc;
++		if (conn->datadgst_en)
++			iscsi_recv_digest_update(tcp_conn, sc->request_buffer,
++						 i);
++		rc = 0;
++		goto done;
+ 	}
+ 
+-	/* We're done processing the header. See if we're doing
+-	 * header digests; if so, set up the recv_digest buffer
+-	 * and go back for more. */
+-	if (conn->hdrdgst_en) {
+-		if (segment->digest_len == 0) {
+-			iscsi_tcp_segment_splice_digest(segment,
+-							segment->recv_digest);
+-			return 0;
++	offset = tcp_ctask->data_offset;
++	sg = sc->request_buffer;
++
++	if (tcp_ctask->data_offset)
++		for (i = 0; i < tcp_ctask->sg_count; i++)
++			offset -= sg[i].length;
++	/* we've passed through partial sg*/
++	if (offset < 0)
++		offset = 0;
++
++	for (i = tcp_ctask->sg_count; i < sc->use_sg; i++) {
++		char *dest;
++
++		dest = kmap_atomic(sg[i].page, KM_SOFTIRQ0);
++		rc = iscsi_ctask_copy(tcp_conn, ctask, dest + sg[i].offset,
++				      sg[i].length, offset);
++		kunmap_atomic(dest, KM_SOFTIRQ0);
++		if (rc == -EAGAIN)
++			/* continue with the next SKB/PDU */
++			return rc;
++		if (!rc) {
++			if (conn->datadgst_en) {
++				if (!offset)
++					crypto_hash_update(
++							&tcp_conn->rx_hash,
++							&sg[i], sg[i].length);
++				else
++					partial_sg_digest_update(
++							&tcp_conn->rx_hash,
++							&sg[i],
++							sg[i].offset + offset,
++							sg[i].length - offset);
++			}
++			offset = 0;
++			tcp_ctask->sg_count++;
+ 		}
+-		iscsi_tcp_dgst_header(&tcp_conn->rx_hash, hdr,
+-				      segment->total_copied - ISCSI_DIGEST_SIZE,
+-				      segment->digest);
+ 
+-		if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-			return ISCSI_ERR_HDR_DGST;
++		if (!ctask->data_count) {
++			if (rc && conn->datadgst_en)
++				/*
++				 * data-in is complete, but buffer not...
++				 */
++				partial_sg_digest_update(&tcp_conn->rx_hash,
++							 &sg[i],
++							 sg[i].offset,
++							 sg[i].length-rc);
++			rc = 0;
++			break;
++		}
++
++		if (!tcp_conn->in.copy)
++			return -EAGAIN;
++	}
++	BUG_ON(ctask->data_count);
++
++done:
++	/* check for non-exceptional status */
++	if (tcp_conn->in.hdr->flags & ISCSI_FLAG_DATA_STATUS) {
++		debug_scsi("done [sc %lx res %d itt 0x%x flags 0x%x]\n",
++			   (long)sc, sc->result, ctask->itt,
++			   tcp_conn->in.hdr->flags);
++		spin_lock(&conn->session->lock);
++		__iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
++		spin_unlock(&conn->session->lock);
+ 	}
+ 
+-	tcp_conn->in.hdr = hdr;
+-	return iscsi_tcp_hdr_dissect(conn, hdr);
++	return rc;
++}
++
++static int
++iscsi_data_recv(struct iscsi_conn *conn)
++{
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int rc = 0, opcode;
++
++	opcode = tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK;
++	switch (opcode) {
++	case ISCSI_OP_SCSI_DATA_IN:
++		rc = iscsi_scsi_data_in(conn);
++		break;
++	case ISCSI_OP_SCSI_CMD_RSP:
++	case ISCSI_OP_TEXT_RSP:
++	case ISCSI_OP_LOGIN_RSP:
++	case ISCSI_OP_ASYNC_EVENT:
++	case ISCSI_OP_REJECT:
++		/*
++		 * Collect data segment to the connection's data
++		 * placeholder
++		 */
++		if (iscsi_tcp_copy(conn, tcp_conn->in.datalen)) {
++			rc = -EAGAIN;
++			goto exit;
++		}
++
++		rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, conn->data,
++					tcp_conn->in.datalen);
++		if (!rc && conn->datadgst_en && opcode != ISCSI_OP_LOGIN_RSP)
++			iscsi_recv_digest_update(tcp_conn, conn->data,
++			  			tcp_conn->in.datalen);
++		break;
++	default:
++		BUG_ON(1);
++	}
++exit:
++	return rc;
+ }
+ 
+ /**
+- * iscsi_tcp_recv - TCP receive in sendfile fashion
++ * iscsi_tcp_data_recv - TCP receive in sendfile fashion
+  * @rd_desc: read descriptor
+  * @skb: socket buffer
+  * @offset: offset in skb
+  * @len: skb->len - offset
+  **/
+ static int
+-iscsi_tcp_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
+-	       unsigned int offset, size_t len)
++iscsi_tcp_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
++		unsigned int offset, size_t len)
+ {
++	int rc;
+ 	struct iscsi_conn *conn = rd_desc->arg.data;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->in.segment;
+-	struct skb_seq_state seq;
+-	unsigned int consumed = 0;
+-	int rc = 0;
++	int processed;
++	char pad[ISCSI_PAD_LEN];
++	struct scatterlist sg;
+ 
+-	debug_tcp("in %d bytes\n", skb->len - offset);
++	/*
++	 * Save current SKB and its offset in the corresponding
++	 * connection context.
++	 */
++	tcp_conn->in.copy = skb->len - offset;
++	tcp_conn->in.offset = offset;
++	tcp_conn->in.skb = skb;
++	tcp_conn->in.len = tcp_conn->in.copy;
++	BUG_ON(tcp_conn->in.copy <= 0);
++	debug_tcp("in %d bytes\n", tcp_conn->in.copy);
++
++more:
++	tcp_conn->in.copied = 0;
++	rc = 0;
+ 
+ 	if (unlikely(conn->suspend_rx)) {
+ 		debug_tcp("conn %d Rx suspended!\n", conn->id);
+ 		return 0;
+ 	}
+ 
+-	skb_prepare_seq_read(skb, offset, skb->len, &seq);
+-	while (1) {
+-		unsigned int avail;
+-		const u8 *ptr;
++	if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER ||
++	    tcp_conn->in_progress == IN_PROGRESS_HEADER_GATHER) {
++		rc = iscsi_hdr_extract(tcp_conn);
++		if (rc) {
++		       if (rc == -EAGAIN)
++				goto nomore;
++		       else {
++				iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++				return 0;
++		       }
++		}
+ 
+-		avail = skb_seq_read(consumed, &ptr, &seq);
+-		if (avail == 0) {
+-			debug_tcp("no more data avail. Consumed %d\n",
+-				  consumed);
+-			break;
++		/*
++		 * Verify and process incoming PDU header.
++		 */
++		rc = iscsi_tcp_hdr_recv(conn);
++		if (!rc && tcp_conn->in.datalen) {
++			if (conn->datadgst_en)
++				crypto_hash_init(&tcp_conn->rx_hash);
++			tcp_conn->in_progress = IN_PROGRESS_DATA_RECV;
++		} else if (rc) {
++			iscsi_conn_failure(conn, rc);
++			return 0;
+ 		}
+-		BUG_ON(segment->copied >= segment->size);
+-
+-		debug_tcp("skb %p ptr=%p avail=%u\n", skb, ptr, avail);
+-		rc = iscsi_tcp_segment_recv(tcp_conn, segment, ptr, avail);
+-		BUG_ON(rc == 0);
+-		consumed += rc;
+-
+-		if (segment->total_copied >= segment->total_size) {
+-			debug_tcp("segment done\n");
+-			rc = segment->done(tcp_conn, segment);
+-			if (rc != 0) {
+-				skb_abort_seq_read(&seq);
+-				goto error;
+-			}
++	}
+ 
+-			/* The done() functions sets up the
+-			 * next segment. */
++	if (tcp_conn->in_progress == IN_PROGRESS_DDIGEST_RECV) {
++		uint32_t recv_digest;
++
++		debug_tcp("extra data_recv offset %d copy %d\n",
++			  tcp_conn->in.offset, tcp_conn->in.copy);
++		rc = iscsi_tcp_copy(conn, sizeof(uint32_t));
++		if (rc) {
++			if (rc == -EAGAIN)
++				goto again;
++			iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++			return 0;
++		}
++
++		memcpy(&recv_digest, conn->data, sizeof(uint32_t));
++		if (recv_digest != tcp_conn->in.datadgst) {
++			debug_tcp("iscsi_tcp: data digest error!"
++				  "0x%x != 0x%x\n", recv_digest,
++				  tcp_conn->in.datadgst);
++			iscsi_conn_failure(conn, ISCSI_ERR_DATA_DGST);
++			return 0;
++		} else {
++			debug_tcp("iscsi_tcp: data digest match!"
++				  "0x%x == 0x%x\n", recv_digest,
++				  tcp_conn->in.datadgst);
++			tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 		}
+ 	}
+-	skb_abort_seq_read(&seq);
+-	conn->rxdata_octets += consumed;
+-	return consumed;
+ 
+-error:
+-	debug_tcp("Error receiving PDU, errno=%d\n", rc);
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-	return 0;
++	if (tcp_conn->in_progress == IN_PROGRESS_DATA_RECV &&
++	   tcp_conn->in.copy) {
++
++		debug_tcp("data_recv offset %d copy %d\n",
++		       tcp_conn->in.offset, tcp_conn->in.copy);
++
++		rc = iscsi_data_recv(conn);
++		if (rc) {
++			if (rc == -EAGAIN)
++				goto again;
++			iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++			return 0;
++		}
++		tcp_conn->in.copy -= tcp_conn->in.padding;
++		tcp_conn->in.offset += tcp_conn->in.padding;
++		if (conn->datadgst_en) {
++			if (tcp_conn->in.padding) {
++				debug_tcp("padding -> %d\n",
++					  tcp_conn->in.padding);
++				memset(pad, 0, tcp_conn->in.padding);
++				sg_init_one(&sg, pad, tcp_conn->in.padding);
++				crypto_hash_update(&tcp_conn->rx_hash,
++						   &sg, sg.length);
++			}
++			crypto_hash_final(&tcp_conn->rx_hash,
++					  (u8 *) &tcp_conn->in.datadgst);
++			debug_tcp("rx digest 0x%x\n", tcp_conn->in.datadgst);
++			tcp_conn->in_progress = IN_PROGRESS_DDIGEST_RECV;
++			tcp_conn->data_copied = 0;
++		} else
++			tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
++	}
++
++	debug_tcp("f, processed %d from out of %d padding %d\n",
++	       tcp_conn->in.offset - offset, (int)len, tcp_conn->in.padding);
++	BUG_ON(tcp_conn->in.offset - offset > len);
++
++	if (tcp_conn->in.offset - offset != len) {
++		debug_tcp("continue to process %d bytes\n",
++		       (int)len - (tcp_conn->in.offset - offset));
++		goto more;
++	}
++
++nomore:
++	processed = tcp_conn->in.offset - offset;
++	BUG_ON(processed == 0);
++	return processed;
++
++again:
++	processed = tcp_conn->in.offset - offset;
++	debug_tcp("c, processed %d from out of %d rd_desc_cnt %d\n",
++	          processed, (int)len, (int)rd_desc->count);
++	BUG_ON(processed == 0);
++	BUG_ON(processed > len);
++
++	conn->rxdata_octets += processed;
++	return processed;
+ }
+ 
+ static void
+ iscsi_tcp_data_ready(struct sock *sk, int flag)
+ {
+ 	struct iscsi_conn *conn = sk->sk_user_data;
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 	read_descriptor_t rd_desc;
+ 
+ 	read_lock(&sk->sk_callback_lock);
+ 
+ 	/*
+-	 * Use rd_desc to pass 'conn' to iscsi_tcp_recv.
++	 * Use rd_desc to pass 'conn' to iscsi_tcp_data_recv.
+ 	 * We set count to 1 because we want the network layer to
+-	 * hand us all the skbs that are available. iscsi_tcp_recv
++	 * hand us all the skbs that are available. iscsi_tcp_data_recv
+ 	 * handled pdus that cross buffers or pdus that still need data.
+ 	 */
+ 	rd_desc.arg.data = conn;
+ 	rd_desc.count = 1;
+-	tcp_read_sock(sk, &rd_desc, iscsi_tcp_recv);
++	tcp_read_sock(sk, &rd_desc, iscsi_tcp_data_recv);
+ 
+ 	read_unlock(&sk->sk_callback_lock);
+-
+-	/* If we had to (atomically) map a highmem page,
+-	 * unmap it now. */
+-	iscsi_tcp_segment_unmap(&tcp_conn->in.segment);
+ }
+ 
+ static void
+@@ -1088,173 +1080,121 @@ iscsi_conn_restore_callbacks(struct iscsi_tcp_conn *tcp_conn)
+ }
+ 
+ /**
+- * iscsi_xmit - TCP transmit
+- **/
+-static int
+-iscsi_xmit(struct iscsi_conn *conn)
++ * iscsi_send - generic send routine
++ * @sk: kernel's socket
++ * @buf: buffer to write from
++ * @size: actual size to write
++ * @flags: socket's flags
++ */
++static inline int
++iscsi_send(struct iscsi_conn *conn, struct iscsi_buf *buf, int size, int flags)
+ {
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->out.segment;
+-	unsigned int consumed = 0;
+-	int rc = 0;
+-
+-	while (1) {
+-		rc = iscsi_tcp_xmit_segment(tcp_conn, segment);
+-		if (rc < 0)
+-			goto error;
+-		if (rc == 0)
+-			break;
+-
+-		consumed += rc;
++	struct socket *sk = tcp_conn->sock;
++	int offset = buf->sg.offset + buf->sent, res;
+ 
+-		if (segment->total_copied >= segment->total_size) {
+-			if (segment->done != NULL) {
+-				rc = segment->done(tcp_conn, segment);
+-				if (rc < 0)
+-					goto error;
+-			}
+-		}
++	/*
++	 * if we got use_sg=0 or are sending something we kmallocd
++	 * then we did not have to do kmap (kmap returns page_address)
++	 *
++	 * if we got use_sg > 0, but had to drop down, we do not
++	 * set clustering so this should only happen for that
++	 * slab case.
++	 */
++	if (buf->use_sendmsg)
++		res = sock_no_sendpage(sk, buf->sg.page, offset, size, flags);
++	else
++		res = tcp_conn->sendpage(sk, buf->sg.page, offset, size, flags);
++
++	if (res >= 0) {
++		conn->txdata_octets += res;
++		buf->sent += res;
++		return res;
+ 	}
+ 
+-	debug_tcp("xmit %d bytes\n", consumed);
+-
+-	conn->txdata_octets += consumed;
+-	return consumed;
+-
+-error:
+-	/* Transmit error. We could initiate error recovery
+-	 * here. */
+-	debug_tcp("Error sending PDU, errno=%d\n", rc);
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-	return rc;
++	tcp_conn->sendpage_failures_cnt++;
++	if (res == -EAGAIN)
++		res = -ENOBUFS;
++	else
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	return res;
+ }
+ 
+ /**
+- * iscsi_tcp_xmit_qlen - return the number of bytes queued for xmit
+- */
+-static inline int
+-iscsi_tcp_xmit_qlen(struct iscsi_conn *conn)
+-{
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->out.segment;
+-
+-	return segment->total_copied - segment->total_size;
+-}
+-
++ * iscsi_sendhdr - send PDU Header via tcp_sendpage()
++ * @conn: iscsi connection
++ * @buf: buffer to write from
++ * @datalen: lenght of data to be sent after the header
++ *
++ * Notes:
++ *	(Tx, Fast Path)
++ **/
+ static inline int
+-iscsi_tcp_flush(struct iscsi_conn *conn)
++iscsi_sendhdr(struct iscsi_conn *conn, struct iscsi_buf *buf, int datalen)
+ {
+-	int rc;
+-
+-	while (iscsi_tcp_xmit_qlen(conn)) {
+-		rc = iscsi_xmit(conn);
+-		if (rc == 0)
++	int flags = 0; /* MSG_DONTWAIT; */
++	int res, size;
++
++	size = buf->sg.length - buf->sent;
++	BUG_ON(buf->sent + size > buf->sg.length);
++	if (buf->sent + size != buf->sg.length || datalen)
++		flags |= MSG_MORE;
++
++	res = iscsi_send(conn, buf, size, flags);
++	debug_tcp("sendhdr %d bytes, sent %d res %d\n", size, buf->sent, res);
++	if (res >= 0) {
++		if (size != res)
+ 			return -EAGAIN;
+-		if (rc < 0)
+-			return rc;
++		return 0;
+ 	}
+ 
+-	return 0;
+-}
+-
+-/*
+- * This is called when we're done sending the header.
+- * Simply copy the data_segment to the send segment, and return.
+- */
+-static int
+-iscsi_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn,
+-			struct iscsi_segment *segment)
+-{
+-	tcp_conn->out.segment = tcp_conn->out.data_segment;
+-	debug_tcp("Header done. Next segment size %u total_size %u\n",
+-		  tcp_conn->out.segment.size, tcp_conn->out.segment.total_size);
+-	return 0;
++	return res;
+ }
+ 
+-static void
+-iscsi_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen)
++/**
++ * iscsi_sendpage - send one page of iSCSI Data-Out.
++ * @conn: iscsi connection
++ * @buf: buffer to write from
++ * @count: remaining data
++ * @sent: number of bytes sent
++ *
++ * Notes:
++ *	(Tx, Fast Path)
++ **/
++static inline int
++iscsi_sendpage(struct iscsi_conn *conn, struct iscsi_buf *buf,
++	       int *count, int *sent)
+ {
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-
+-	debug_tcp("%s(%p%s)\n", __FUNCTION__, tcp_conn,
+-			conn->hdrdgst_en? ", digest enabled" : "");
+-
+-	/* Clear the data segment - needs to be filled in by the
+-	 * caller using iscsi_tcp_send_data_prep() */
+-	memset(&tcp_conn->out.data_segment, 0, sizeof(struct iscsi_segment));
+-
+-	/* If header digest is enabled, compute the CRC and
+-	 * place the digest into the same buffer. We make
+-	 * sure that both iscsi_tcp_ctask and mtask have
+-	 * sufficient room.
+-	 */
+-	if (conn->hdrdgst_en) {
+-		iscsi_tcp_dgst_header(&tcp_conn->tx_hash, hdr, hdrlen,
+-				      hdr + hdrlen);
+-		hdrlen += ISCSI_DIGEST_SIZE;
++	int flags = 0; /* MSG_DONTWAIT; */
++	int res, size;
++
++	size = buf->sg.length - buf->sent;
++	BUG_ON(buf->sent + size > buf->sg.length);
++	if (size > *count)
++		size = *count;
++	if (buf->sent + size != buf->sg.length || *count != size)
++		flags |= MSG_MORE;
++
++	res = iscsi_send(conn, buf, size, flags);
++	debug_tcp("sendpage: %d bytes, sent %d left %d sent %d res %d\n",
++		  size, buf->sent, *count, *sent, res);
++	if (res >= 0) {
++		*count -= res;
++		*sent += res;
++		if (size != res)
++			return -EAGAIN;
++		return 0;
+ 	}
+ 
+-	/* Remember header pointer for later, when we need
+-	 * to decide whether there's a payload to go along
+-	 * with the header. */
+-	tcp_conn->out.hdr = hdr;
+-
+-	iscsi_segment_init_linear(&tcp_conn->out.segment, hdr, hdrlen,
+-				iscsi_tcp_send_hdr_done, NULL);
+-}
+-
+-/*
+- * Prepare the send buffer for the payload data.
+- * Padding and checksumming will all be taken care
+- * of by the iscsi_segment routines.
+- */
+-static int
+-iscsi_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg,
+-			 unsigned int count, unsigned int offset,
+-			 unsigned int len)
+-{
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct hash_desc *tx_hash = NULL;
+-	unsigned int hdr_spec_len;
+-
+-	debug_tcp("%s(%p, offset=%d, datalen=%d%s)\n", __FUNCTION__,
+-			tcp_conn, offset, len,
+-			conn->datadgst_en? ", digest enabled" : "");
+-
+-	/* Make sure the datalen matches what the caller
+-	   said he would send. */
+-	hdr_spec_len = ntoh24(tcp_conn->out.hdr->dlength);
+-	WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
+-
+-	if (conn->datadgst_en)
+-		tx_hash = &tcp_conn->tx_hash;
+-
+-	return iscsi_segment_seek_sg(&tcp_conn->out.data_segment,
+-				   sg, count, offset, len,
+-				   NULL, tx_hash);
++	return res;
+ }
+ 
+-static void
+-iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data,
+-				   size_t len)
++static inline void
++iscsi_data_digest_init(struct iscsi_tcp_conn *tcp_conn,
++		      struct iscsi_tcp_cmd_task *tcp_ctask)
+ {
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct hash_desc *tx_hash = NULL;
+-	unsigned int hdr_spec_len;
+-
+-	debug_tcp("%s(%p, datalen=%d%s)\n", __FUNCTION__, tcp_conn, len,
+-		  conn->datadgst_en? ", digest enabled" : "");
+-
+-	/* Make sure the datalen matches what the caller
+-	   said he would send. */
+-	hdr_spec_len = ntoh24(tcp_conn->out.hdr->dlength);
+-	WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
+-
+-	if (conn->datadgst_en)
+-		tx_hash = &tcp_conn->tx_hash;
+-
+-	iscsi_segment_init_linear(&tcp_conn->out.data_segment,
+-				data, len, NULL, tx_hash);
++	crypto_hash_init(&tcp_conn->tx_hash);
++	tcp_ctask->digest_count = 4;
+ }
+ 
+ /**
+@@ -1270,17 +1210,13 @@ iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data,
+  *
+  *	Called under connection lock.
+  **/
+-static int
++static void
+ iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+-			struct iscsi_r2t_info *r2t)
++			struct iscsi_r2t_info *r2t, int left)
+ {
+ 	struct iscsi_data *hdr;
+-	int new_offset, left;
+-
+-	BUG_ON(r2t->data_length - r2t->sent < 0);
+-	left = r2t->data_length - r2t->sent;
+-	if (left == 0)
+-		return 0;
++	struct scsi_cmnd *sc = ctask->sc;
++	int new_offset;
+ 
+ 	hdr = &r2t->dtask.hdr;
+ 	memset(hdr, 0, sizeof(struct iscsi_data));
+@@ -1301,47 +1237,81 @@ iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		r2t->data_count = left;
+ 		hdr->flags = ISCSI_FLAG_CMD_FINAL;
+ 	}
+-
+ 	conn->dataout_pdus_cnt++;
+-	return 1;
++
++	iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr,
++			   sizeof(struct iscsi_hdr));
++
++	if (iscsi_buf_left(&r2t->sendbuf))
++		return;
++
++	if (sc->use_sg) {
++		iscsi_buf_init_sg(&r2t->sendbuf, r2t->sg);
++		r2t->sg += 1;
++	} else {
++		iscsi_buf_init_iov(&r2t->sendbuf,
++			    (char*)sc->request_buffer + new_offset,
++			    r2t->data_count);
++		r2t->sg = NULL;
++	}
++}
++
++static void iscsi_set_padding(struct iscsi_tcp_cmd_task *tcp_ctask,
++			      unsigned long len)
++{
++	tcp_ctask->pad_count = len & (ISCSI_PAD_LEN - 1);
++	if (!tcp_ctask->pad_count)
++		return;
++
++	tcp_ctask->pad_count = ISCSI_PAD_LEN - tcp_ctask->pad_count;
++	debug_scsi("write padding %d bytes\n", tcp_ctask->pad_count);
++	tcp_ctask->xmstate |= XMSTATE_W_PAD;
+ }
+ 
+ /**
+- * iscsi_tcp_ctask - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
++ * iscsi_tcp_cmd_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
+  * @conn: iscsi connection
+  * @ctask: scsi command task
+  * @sc: scsi command
+  **/
+-static int
+-iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
++static void
++iscsi_tcp_cmd_init(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-	struct iscsi_conn *conn = ctask->conn;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	int err;
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 	BUG_ON(__kfifo_len(tcp_ctask->r2tqueue));
++
+ 	tcp_ctask->sent = 0;
+-	tcp_ctask->exp_datasn = 0;
++	tcp_ctask->sg_count = 0;
+ 
+-	/* Prepare PDU, optionally w/ immediate data */
+-	debug_scsi("ctask deq [cid %d itt 0x%x imm %d unsol %d]\n",
+-		    conn->id, ctask->itt, ctask->imm_count,
+-		    ctask->unsol_count);
+-	iscsi_tcp_send_hdr_prep(conn, ctask->hdr, ctask->hdr_len);
++	if (sc->sc_data_direction == DMA_TO_DEVICE) {
++		tcp_ctask->xmstate = XMSTATE_W_HDR;
++		tcp_ctask->exp_r2tsn = 0;
++		BUG_ON(ctask->total_length == 0);
+ 
+-	if (!ctask->imm_count)
+-		return 0;
++		if (sc->use_sg) {
++			struct scatterlist *sg = sc->request_buffer;
+ 
+-	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
+-				       0, ctask->imm_count);
+-	if (err)
+-		return err;
+-	tcp_ctask->sent += ctask->imm_count;
+-	ctask->imm_count = 0;
+-	return 0;
++			iscsi_buf_init_sg(&tcp_ctask->sendbuf, sg);
++			tcp_ctask->sg = sg + 1;
++			tcp_ctask->bad_sg = sg + sc->use_sg;
++		} else {
++			iscsi_buf_init_iov(&tcp_ctask->sendbuf,
++					   sc->request_buffer,
++					   sc->request_bufflen);
++			tcp_ctask->sg = NULL;
++			tcp_ctask->bad_sg = NULL;
++		}
++		debug_scsi("cmd [itt 0x%x total %d imm_data %d "
++			   "unsol count %d, unsol offset %d]\n",
++			   ctask->itt, ctask->total_length, ctask->imm_count,
++			   ctask->unsol_count, ctask->unsol_offset);
++	} else
++		tcp_ctask->xmstate = XMSTATE_R_HDR;
++
++	iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)ctask->hdr,
++			    sizeof(struct iscsi_hdr));
+ }
+ 
+ /**
+@@ -1353,130 +1323,428 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+  *	The function can return -EAGAIN in which case caller must
+  *	call it again later, or recover. '0' return code means successful
+  *	xmit.
++ *
++ *	Management xmit state machine consists of two states:
++ *		IN_PROGRESS_IMM_HEAD - PDU Header xmit in progress
++ *		IN_PROGRESS_IMM_DATA - PDU Data xmit in progress
+  **/
+ static int
+ iscsi_tcp_mtask_xmit(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
+ {
++	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 	int rc;
+ 
+-	/* Flush any pending data first. */
+-	rc = iscsi_tcp_flush(conn);
+-	if (rc < 0)
+-		return rc;
++	debug_scsi("mtask deq [cid %d state %x itt 0x%x]\n",
++		conn->id, tcp_mtask->xmstate, mtask->itt);
++
++	if (tcp_mtask->xmstate & XMSTATE_IMM_HDR) {
++		tcp_mtask->xmstate &= ~XMSTATE_IMM_HDR;
++		if (mtask->data_count)
++			tcp_mtask->xmstate |= XMSTATE_IMM_DATA;
++		if (conn->c_stage != ISCSI_CONN_INITIAL_STAGE &&
++		    conn->stop_stage != STOP_CONN_RECOVER &&
++		    conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &tcp_mtask->headbuf,
++					(u8*)tcp_mtask->hdrext);
++		rc = iscsi_sendhdr(conn, &tcp_mtask->headbuf,
++				   mtask->data_count);
++		if (rc) {
++			tcp_mtask->xmstate |= XMSTATE_IMM_HDR;
++			if (mtask->data_count)
++				tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA;
++			return rc;
++		}
++	}
+ 
++	if (tcp_mtask->xmstate & XMSTATE_IMM_DATA) {
++		BUG_ON(!mtask->data_count);
++		tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA;
++		/* FIXME: implement.
++		 * Virtual buffer could be spreaded across multiple pages...
++		 */
++		do {
++			int rc;
++
++			rc = iscsi_sendpage(conn, &tcp_mtask->sendbuf,
++					&mtask->data_count, &tcp_mtask->sent);
++			if (rc) {
++				tcp_mtask->xmstate |= XMSTATE_IMM_DATA;
++				return rc;
++			}
++		} while (mtask->data_count);
++	}
++
++	BUG_ON(tcp_mtask->xmstate != XMSTATE_IDLE);
+ 	if (mtask->hdr->itt == RESERVED_ITT) {
+ 		struct iscsi_session *session = conn->session;
+ 
+ 		spin_lock_bh(&session->lock);
+-		iscsi_free_mgmt_task(conn, mtask);
++		list_del(&conn->mtask->running);
++		__kfifo_put(session->mgmtpool.queue, (void*)&conn->mtask,
++			    sizeof(void*));
+ 		spin_unlock_bh(&session->lock);
+ 	}
++	return 0;
++}
++
++static inline int
++iscsi_send_read_hdr(struct iscsi_conn *conn,
++		    struct iscsi_tcp_cmd_task *tcp_ctask)
++{
++	int rc;
++
++	tcp_ctask->xmstate &= ~XMSTATE_R_HDR;
++	if (conn->hdrdgst_en)
++		iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++				 (u8*)tcp_ctask->hdrext);
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, 0);
++	if (!rc) {
++		BUG_ON(tcp_ctask->xmstate != XMSTATE_IDLE);
++		return 0; /* wait for Data-In */
++	}
++	tcp_ctask->xmstate |= XMSTATE_R_HDR;
++	return rc;
++}
++
++static inline int
++iscsi_send_write_hdr(struct iscsi_conn *conn,
++		     struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc;
++
++	tcp_ctask->xmstate &= ~XMSTATE_W_HDR;
++	if (conn->hdrdgst_en)
++		iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++				 (u8*)tcp_ctask->hdrext);
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->imm_count);
++	if (rc) {
++		tcp_ctask->xmstate |= XMSTATE_W_HDR;
++		return rc;
++	}
++
++	if (ctask->imm_count) {
++		tcp_ctask->xmstate |= XMSTATE_IMM_DATA;
++		iscsi_set_padding(tcp_ctask, ctask->imm_count);
+ 
++		if (ctask->conn->datadgst_en) {
++			iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask);
++			tcp_ctask->immdigest = 0;
++		}
++	}
++
++	if (ctask->unsol_count)
++		tcp_ctask->xmstate |= XMSTATE_UNS_HDR | XMSTATE_UNS_INIT;
+ 	return 0;
+ }
+ 
+-/*
+- * iscsi_tcp_ctask_xmit - xmit normal PDU task
+- * @conn: iscsi connection
+- * @ctask: iscsi command task
+- *
+- * We're expected to return 0 when everything was transmitted succesfully,
+- * -EAGAIN if there's still data in the queue, or != 0 for any other kind
+- * of error.
+- */
+ static int
+-iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++iscsi_send_padding(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+-	int rc = 0;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int sent = 0, rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_W_PAD) {
++		iscsi_buf_init_iov(&tcp_ctask->sendbuf, (char*)&tcp_ctask->pad,
++				   tcp_ctask->pad_count);
++		if (conn->datadgst_en)
++			crypto_hash_update(&tcp_conn->tx_hash,
++					   &tcp_ctask->sendbuf.sg,
++					   tcp_ctask->sendbuf.sg.length);
++	} else if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_PAD))
++		return 0;
+ 
+-flush:
+-	/* Flush any pending data first. */
+-	rc = iscsi_tcp_flush(conn);
+-	if (rc < 0)
+-		return rc;
++	tcp_ctask->xmstate &= ~XMSTATE_W_PAD;
++	tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_PAD;
++	debug_scsi("sending %d pad bytes for itt 0x%x\n",
++		   tcp_ctask->pad_count, ctask->itt);
++	rc = iscsi_sendpage(conn, &tcp_ctask->sendbuf, &tcp_ctask->pad_count,
++			   &sent);
++	if (rc) {
++		debug_scsi("padding send failed %d\n", rc);
++		tcp_ctask->xmstate |= XMSTATE_W_RESEND_PAD;
++	}
++	return rc;
++}
+ 
+-	/* Are we done already? */
+-	if (sc->sc_data_direction != DMA_TO_DEVICE)
++static int
++iscsi_send_digest(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
++			struct iscsi_buf *buf, uint32_t *digest)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask;
++	struct iscsi_tcp_conn *tcp_conn;
++	int rc, sent = 0;
++
++	if (!conn->datadgst_en)
+ 		return 0;
+ 
+-	if (ctask->unsol_count != 0) {
+-		struct iscsi_data *hdr = &tcp_ctask->unsol_dtask.hdr;
++	tcp_ctask = ctask->dd_data;
++	tcp_conn = conn->dd_data;
+ 
+-		/* Prepare a header for the unsolicited PDU.
+-		 * The amount of data we want to send will be
+-		 * in ctask->data_count.
+-		 * FIXME: return the data count instead.
+-		 */
+-		iscsi_prep_unsolicit_data_pdu(ctask, hdr);
++	if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_DATA_DIGEST)) {
++		crypto_hash_final(&tcp_conn->tx_hash, (u8*)digest);
++		iscsi_buf_init_iov(buf, (char*)digest, 4);
++	}
++	tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_DATA_DIGEST;
++
++	rc = iscsi_sendpage(conn, buf, &tcp_ctask->digest_count, &sent);
++	if (!rc)
++		debug_scsi("sent digest 0x%x for itt 0x%x\n", *digest,
++			  ctask->itt);
++	else {
++		debug_scsi("sending digest 0x%x failed for itt 0x%x!\n",
++			  *digest, ctask->itt);
++		tcp_ctask->xmstate |= XMSTATE_W_RESEND_DATA_DIGEST;
++	}
++	return rc;
++}
+ 
+-		debug_tcp("unsol dout [itt 0x%x doff %d dlen %d]\n",
+-				ctask->itt, tcp_ctask->sent, ctask->data_count);
++static int
++iscsi_send_data(struct iscsi_cmd_task *ctask, struct iscsi_buf *sendbuf,
++		struct scatterlist **sg, int *sent, int *count,
++		struct iscsi_buf *digestbuf, uint32_t *digest)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_conn *conn = ctask->conn;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int rc, buf_sent, offset;
++
++	while (*count) {
++		buf_sent = 0;
++		offset = sendbuf->sent;
++
++		rc = iscsi_sendpage(conn, sendbuf, count, &buf_sent);
++		*sent = *sent + buf_sent;
++		if (buf_sent && conn->datadgst_en)
++			partial_sg_digest_update(&tcp_conn->tx_hash,
++				&sendbuf->sg, sendbuf->sg.offset + offset,
++				buf_sent);
++		if (!iscsi_buf_left(sendbuf) && *sg != tcp_ctask->bad_sg) {
++			iscsi_buf_init_sg(sendbuf, *sg);
++			*sg = *sg + 1;
++		}
+ 
+-		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
+-					      ctask->data_count);
+ 		if (rc)
+-			goto fail;
+-		tcp_ctask->sent += ctask->data_count;
+-		ctask->unsol_count -= ctask->data_count;
+-		goto flush;
+-	} else {
+-		struct iscsi_session *session = conn->session;
+-		struct iscsi_r2t_info *r2t;
++			return rc;
++	}
++
++	rc = iscsi_send_padding(conn, ctask);
++	if (rc)
++		return rc;
++
++	return iscsi_send_digest(conn, ctask, digestbuf, digest);
++}
++
++static int
++iscsi_send_unsol_hdr(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_data_task *dtask;
++	int rc;
++
++	tcp_ctask->xmstate |= XMSTATE_UNS_DATA;
++	if (tcp_ctask->xmstate & XMSTATE_UNS_INIT) {
++		dtask = &tcp_ctask->unsol_dtask;
++
++		iscsi_prep_unsolicit_data_pdu(ctask, &dtask->hdr);
++		iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)&dtask->hdr,
++				   sizeof(struct iscsi_hdr));
++		if (conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++					(u8*)dtask->hdrext);
++
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_INIT;
++		iscsi_set_padding(tcp_ctask, ctask->data_count);
++	}
++
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->data_count);
++	if (rc) {
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA;
++		tcp_ctask->xmstate |= XMSTATE_UNS_HDR;
++		return rc;
++	}
++
++	if (conn->datadgst_en) {
++		dtask = &tcp_ctask->unsol_dtask;
++		iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask);
++		dtask->digest = 0;
++	}
++
++	debug_scsi("uns dout [itt 0x%x dlen %d sent %d]\n",
++		   ctask->itt, ctask->unsol_count, tcp_ctask->sent);
++	return 0;
++}
+ 
+-		/* All unsolicited PDUs sent. Check for solicited PDUs.
++static int
++iscsi_send_unsol_pdu(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_UNS_HDR) {
++		BUG_ON(!ctask->unsol_count);
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_HDR;
++send_hdr:
++		rc = iscsi_send_unsol_hdr(conn, ctask);
++		if (rc)
++			return rc;
++	}
++
++	if (tcp_ctask->xmstate & XMSTATE_UNS_DATA) {
++		struct iscsi_data_task *dtask = &tcp_ctask->unsol_dtask;
++		int start = tcp_ctask->sent;
++
++		rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg,
++				     &tcp_ctask->sent, &ctask->data_count,
++				     &dtask->digestbuf, &dtask->digest);
++		ctask->unsol_count -= tcp_ctask->sent - start;
++		if (rc)
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA;
++		/*
++		 * Done with the Data-Out. Next, check if we need
++		 * to send another unsolicited Data-Out.
+ 		 */
+-		spin_lock_bh(&session->lock);
+-		r2t = tcp_ctask->r2t;
+-		if (r2t != NULL) {
+-			/* Continue with this R2T? */
+-			if (!iscsi_solicit_data_cont(conn, ctask, r2t)) {
+-				debug_scsi("  done with r2t %p\n", r2t);
+-
+-				__kfifo_put(tcp_ctask->r2tpool.queue,
+-					    (void*)&r2t, sizeof(void*));
+-				tcp_ctask->r2t = r2t = NULL;
+-			}
++		if (ctask->unsol_count) {
++			debug_scsi("sending more uns\n");
++			tcp_ctask->xmstate |= XMSTATE_UNS_INIT;
++			goto send_hdr;
+ 		}
++	}
++	return 0;
++}
+ 
+-		if (r2t == NULL) {
++static int iscsi_send_sol_pdu(struct iscsi_conn *conn,
++			      struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_session *session = conn->session;
++	struct iscsi_r2t_info *r2t;
++	struct iscsi_data_task *dtask;
++	int left, rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_SOL_HDR) {
++		tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++		tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++		if (!tcp_ctask->r2t) {
++			spin_lock_bh(&session->lock);
+ 			__kfifo_get(tcp_ctask->r2tqueue, (void*)&tcp_ctask->r2t,
+ 				    sizeof(void*));
+-			r2t = tcp_ctask->r2t;
++			spin_unlock_bh(&session->lock);
++		}
++send_hdr:
++		r2t = tcp_ctask->r2t;
++		dtask = &r2t->dtask;
++
++		if (conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &r2t->headbuf,
++					(u8*)dtask->hdrext);
++		rc = iscsi_sendhdr(conn, &r2t->headbuf, r2t->data_count);
++		if (rc) {
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate |= XMSTATE_SOL_HDR;
++			return rc;
+ 		}
+-		spin_unlock_bh(&session->lock);
+ 
+-		/* Waiting for more R2Ts to arrive. */
+-		if (r2t == NULL) {
+-			debug_tcp("no R2Ts yet\n");
+-			return 0;
++		if (conn->datadgst_en) {
++			iscsi_data_digest_init(conn->dd_data, tcp_ctask);
++			dtask->digest = 0;
+ 		}
+ 
+-		debug_scsi("sol dout %p [dsn %d itt 0x%x doff %d dlen %d]\n",
+-			r2t, r2t->solicit_datasn - 1, ctask->itt,
+-			r2t->data_offset + r2t->sent, r2t->data_count);
++		iscsi_set_padding(tcp_ctask, r2t->data_count);
++		debug_scsi("sol dout [dsn %d itt 0x%x dlen %d sent %d]\n",
++			r2t->solicit_datasn - 1, ctask->itt, r2t->data_count,
++			r2t->sent);
++	}
+ 
+-		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+-					sizeof(struct iscsi_hdr));
++	if (tcp_ctask->xmstate & XMSTATE_SOL_DATA) {
++		r2t = tcp_ctask->r2t;
++		dtask = &r2t->dtask;
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
+-					      r2t->data_offset + r2t->sent,
+-					      r2t->data_count);
++		rc = iscsi_send_data(ctask, &r2t->sendbuf, &r2t->sg,
++				     &r2t->sent, &r2t->data_count,
++				     &dtask->digestbuf, &dtask->digest);
+ 		if (rc)
+-			goto fail;
+-		tcp_ctask->sent += r2t->data_count;
+-		r2t->sent += r2t->data_count;
+-		goto flush;
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA;
++
++		/*
++		 * Done with this Data-Out. Next, check if we have
++		 * to send another Data-Out for this R2T.
++		 */
++		BUG_ON(r2t->data_length - r2t->sent < 0);
++		left = r2t->data_length - r2t->sent;
++		if (left) {
++			iscsi_solicit_data_cont(conn, ctask, r2t, left);
++			tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++			goto send_hdr;
++		}
++
++		/*
++		 * Done with this R2T. Check if there are more
++		 * outstanding R2Ts ready to be processed.
++		 */
++		spin_lock_bh(&session->lock);
++		tcp_ctask->r2t = NULL;
++		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
++			    sizeof(void*));
++		if (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t,
++				sizeof(void*))) {
++			tcp_ctask->r2t = r2t;
++			tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++			spin_unlock_bh(&session->lock);
++			goto send_hdr;
++		}
++		spin_unlock_bh(&session->lock);
+ 	}
+ 	return 0;
+-fail:
+-	iscsi_conn_failure(conn, rc);
+-	return -EIO;
++}
++
++static int
++iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc = 0;
++
++	debug_scsi("ctask deq [cid %d xmstate %x itt 0x%x]\n",
++		conn->id, tcp_ctask->xmstate, ctask->itt);
++
++	/*
++	 * serialize with TMF AbortTask
++	 */
++	if (ctask->mtask)
++		return rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_R_HDR)
++		return iscsi_send_read_hdr(conn, tcp_ctask);
++
++	if (tcp_ctask->xmstate & XMSTATE_W_HDR) {
++		rc = iscsi_send_write_hdr(conn, ctask);
++		if (rc)
++			return rc;
++	}
++
++	if (tcp_ctask->xmstate & XMSTATE_IMM_DATA) {
++		rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg,
++				     &tcp_ctask->sent, &ctask->imm_count,
++				     &tcp_ctask->immbuf, &tcp_ctask->immdigest);
++		if (rc)
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_IMM_DATA;
++	}
++
++	rc = iscsi_send_unsol_pdu(conn, ctask);
++	if (rc)
++		return rc;
++
++	rc = iscsi_send_sol_pdu(conn, ctask);
++	if (rc)
++		return rc;
++
++	return rc;
+ }
+ 
+ static struct iscsi_cls_conn *
+@@ -1502,29 +1770,37 @@ iscsi_tcp_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 
+ 	conn->dd_data = tcp_conn;
+ 	tcp_conn->iscsi_conn = conn;
++	tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
++	/* initial operational parameters */
++	tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
+ 
+ 	tcp_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+ 						  CRYPTO_ALG_ASYNC);
+ 	tcp_conn->tx_hash.flags = 0;
+-	if (IS_ERR(tcp_conn->tx_hash.tfm))
++	if (IS_ERR(tcp_conn->tx_hash.tfm)) {
++		printk(KERN_ERR "Could not create connection due to crc32c "
++		       "loading error %ld. Make sure the crc32c module is "
++		       "built as a module or into the kernel\n",
++			PTR_ERR(tcp_conn->tx_hash.tfm));
+ 		goto free_tcp_conn;
++	}
+ 
+ 	tcp_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+ 						  CRYPTO_ALG_ASYNC);
+ 	tcp_conn->rx_hash.flags = 0;
+-	if (IS_ERR(tcp_conn->rx_hash.tfm))
++	if (IS_ERR(tcp_conn->rx_hash.tfm)) {
++		printk(KERN_ERR "Could not create connection due to crc32c "
++		       "loading error %ld. Make sure the crc32c module is "
++		       "built as a module or into the kernel\n",
++			PTR_ERR(tcp_conn->rx_hash.tfm));
+ 		goto free_tx_tfm;
++	}
+ 
+ 	return cls_conn;
+ 
+ free_tx_tfm:
+ 	crypto_free_hash(tcp_conn->tx_hash.tfm);
+ free_tcp_conn:
+-	iscsi_conn_printk(KERN_ERR, conn,
+-			  "Could not create connection due to crc32c "
+-			  "loading error. Make sure the crc32c "
+-			  "module is built as a module or into the "
+-			  "kernel\n");
+ 	kfree(tcp_conn);
+ tcp_conn_alloc_fail:
+ 	iscsi_conn_teardown(cls_conn);
+@@ -1534,22 +1810,18 @@ tcp_conn_alloc_fail:
+ static void
+ iscsi_tcp_release_conn(struct iscsi_conn *conn)
+ {
+-	struct iscsi_session *session = conn->session;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct socket *sock = tcp_conn->sock;
+ 
+-	if (!sock)
++	if (!tcp_conn->sock)
+ 		return;
+ 
+-	sock_hold(sock->sk);
++	sock_hold(tcp_conn->sock->sk);
+ 	iscsi_conn_restore_callbacks(tcp_conn);
+-	sock_put(sock->sk);
++	sock_put(tcp_conn->sock->sk);
+ 
+-	spin_lock_bh(&session->lock);
++	sock_release(tcp_conn->sock);
+ 	tcp_conn->sock = NULL;
+ 	conn->recv_lock = NULL;
+-	spin_unlock_bh(&session->lock);
+-	sockfd_put(sock);
+ }
+ 
+ static void
+@@ -1573,49 +1845,11 @@ static void
+ iscsi_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 
+ 	iscsi_conn_stop(cls_conn, flag);
+ 	iscsi_tcp_release_conn(conn);
+-}
+-
+-static int iscsi_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock,
+-			      char *buf, int *port,
+-			      int (*getname)(struct socket *, struct sockaddr *,
+-					int *addrlen))
+-{
+-	struct sockaddr_storage *addr;
+-	struct sockaddr_in6 *sin6;
+-	struct sockaddr_in *sin;
+-	int rc = 0, len;
+-
+-	addr = kmalloc(sizeof(*addr), GFP_KERNEL);
+-	if (!addr)
+-		return -ENOMEM;
+-
+-	if (getname(sock, (struct sockaddr *) addr, &len)) {
+-		rc = -ENODEV;
+-		goto free_addr;
+-	}
+-
+-	switch (addr->ss_family) {
+-	case AF_INET:
+-		sin = (struct sockaddr_in *)addr;
+-		spin_lock_bh(&conn->session->lock);
+-		sprintf(buf, NIPQUAD_FMT, NIPQUAD(sin->sin_addr.s_addr));
+-		*port = be16_to_cpu(sin->sin_port);
+-		spin_unlock_bh(&conn->session->lock);
+-		break;
+-	case AF_INET6:
+-		sin6 = (struct sockaddr_in6 *)addr;
+-		spin_lock_bh(&conn->session->lock);
+-		sprintf(buf, NIP6_FMT, NIP6(sin6->sin6_addr));
+-		*port = be16_to_cpu(sin6->sin6_port);
+-		spin_unlock_bh(&conn->session->lock);
+-		break;
+-	}
+-free_addr:
+-	kfree(addr);
+-	return rc;
++	tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
+ }
+ 
+ static int
+@@ -1632,28 +1866,13 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
+ 	/* lookup for existing socket */
+ 	sock = sockfd_lookup((int)transport_eph, &err);
+ 	if (!sock) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "sockfd_lookup failed %d\n", err);
++		printk(KERN_ERR "iscsi_tcp: sockfd_lookup failed %d\n", err);
+ 		return -EEXIST;
+ 	}
+-	/*
+-	 * copy these values now because if we drop the session
+-	 * userspace may still want to query the values since we will
+-	 * be using them for the reconnect
+-	 */
+-	err = iscsi_tcp_get_addr(conn, sock, conn->portal_address,
+-				 &conn->portal_port, kernel_getpeername);
+-	if (err)
+-		goto free_socket;
+-
+-	err = iscsi_tcp_get_addr(conn, sock, conn->local_address,
+-				&conn->local_port, kernel_getsockname);
+-	if (err)
+-		goto free_socket;
+ 
+ 	err = iscsi_conn_bind(cls_session, cls_conn, is_leading);
+ 	if (err)
+-		goto free_socket;
++		return err;
+ 
+ 	/* bind iSCSI connection and socket */
+ 	tcp_conn->sock = sock;
+@@ -1676,27 +1895,26 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
+ 	/*
+ 	 * set receive state machine into initial state
+ 	 */
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+-	return 0;
++	tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 
+-free_socket:
+-	sockfd_put(sock);
+-	return err;
++	return 0;
+ }
+ 
+ /* called with host lock */
+ static void
+-iscsi_tcp_mtask_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
++iscsi_tcp_mgmt_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask,
++		    char *data, uint32_t data_size)
+ {
+-	debug_scsi("mtask deq [cid %d itt 0x%x]\n", conn->id, mtask->itt);
++	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 
+-	/* Prepare PDU, optionally w/ immediate data */
+-	iscsi_tcp_send_hdr_prep(conn, mtask->hdr, sizeof(*mtask->hdr));
++	iscsi_buf_init_iov(&tcp_mtask->headbuf, (char*)mtask->hdr,
++			   sizeof(struct iscsi_hdr));
++	tcp_mtask->xmstate = XMSTATE_IMM_HDR;
++	tcp_mtask->sent = 0;
+ 
+-	/* If we have immediate data, attach a payload */
+ 	if (mtask->data_count)
+-		iscsi_tcp_send_linear_data_prepare(conn, mtask->data,
+-						   mtask->data_count);
++		iscsi_buf_init_iov(&tcp_mtask->sendbuf, (char*)mtask->data,
++				    mtask->data_count);
+ }
+ 
+ static int
+@@ -1719,7 +1937,8 @@ iscsi_r2tpool_alloc(struct iscsi_session *session)
+ 		 */
+ 
+ 		/* R2T pool */
+-		if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4, NULL,
++		if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4,
++				    (void***)&tcp_ctask->r2ts,
+ 				    sizeof(struct iscsi_r2t_info))) {
+ 			goto r2t_alloc_fail;
+ 		}
+@@ -1728,7 +1947,8 @@ iscsi_r2tpool_alloc(struct iscsi_session *session)
+ 		tcp_ctask->r2tqueue = kfifo_alloc(
+ 		      session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL);
+ 		if (tcp_ctask->r2tqueue == ERR_PTR(-ENOMEM)) {
+-			iscsi_pool_free(&tcp_ctask->r2tpool);
++			iscsi_pool_free(&tcp_ctask->r2tpool,
++					(void**)tcp_ctask->r2ts);
+ 			goto r2t_alloc_fail;
+ 		}
+ 	}
+@@ -1741,7 +1961,8 @@ r2t_alloc_fail:
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 		kfifo_free(tcp_ctask->r2tqueue);
+-		iscsi_pool_free(&tcp_ctask->r2tpool);
++		iscsi_pool_free(&tcp_ctask->r2tpool,
++				(void**)tcp_ctask->r2ts);
+ 	}
+ 	return -ENOMEM;
+ }
+@@ -1756,7 +1977,8 @@ iscsi_r2tpool_free(struct iscsi_session *session)
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 		kfifo_free(tcp_ctask->r2tqueue);
+-		iscsi_pool_free(&tcp_ctask->r2tpool);
++		iscsi_pool_free(&tcp_ctask->r2tpool,
++				(void**)tcp_ctask->r2ts);
+ 	}
+ }
+ 
+@@ -1772,6 +1994,9 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
+ 	switch(param) {
+ 	case ISCSI_PARAM_HDRDGST_EN:
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
++		tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
++		if (conn->hdrdgst_en)
++			tcp_conn->hdr_size += sizeof(__u32);
+ 		break;
+ 	case ISCSI_PARAM_DATADGST_EN:
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
+@@ -1780,12 +2005,12 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
+ 		break;
+ 	case ISCSI_PARAM_MAX_R2T:
+ 		sscanf(buf, "%d", &value);
+-		if (value <= 0 || !is_power_of_2(value))
+-			return -EINVAL;
+-		if (session->max_r2t == value)
++		if (session->max_r2t == roundup_pow_of_two(value))
+ 			break;
+ 		iscsi_r2tpool_free(session);
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
++		if (session->max_r2t & (session->max_r2t - 1))
++			session->max_r2t = roundup_pow_of_two(session->max_r2t);
+ 		if (iscsi_r2tpool_alloc(session))
+ 			return -ENOMEM;
+ 		break;
+@@ -1801,18 +2026,41 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 			 enum iscsi_param param, char *buf)
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	struct inet_sock *inet;
++	struct ipv6_pinfo *np;
++	struct sock *sk;
+ 	int len;
+ 
+ 	switch(param) {
+ 	case ISCSI_PARAM_CONN_PORT:
+-		spin_lock_bh(&conn->session->lock);
+-		len = sprintf(buf, "%hu\n", conn->portal_port);
+-		spin_unlock_bh(&conn->session->lock);
++		mutex_lock(&conn->xmitmutex);
++		if (!tcp_conn->sock) {
++			mutex_unlock(&conn->xmitmutex);
++			return -EINVAL;
++		}
++
++		inet = inet_sk(tcp_conn->sock->sk);
++		len = sprintf(buf, "%hu\n", be16_to_cpu(inet->dport));
++		mutex_unlock(&conn->xmitmutex);
+ 		break;
+ 	case ISCSI_PARAM_CONN_ADDRESS:
+-		spin_lock_bh(&conn->session->lock);
+-		len = sprintf(buf, "%s\n", conn->portal_address);
+-		spin_unlock_bh(&conn->session->lock);
++		mutex_lock(&conn->xmitmutex);
++		if (!tcp_conn->sock) {
++			mutex_unlock(&conn->xmitmutex);
++			return -EINVAL;
++		}
++
++		sk = tcp_conn->sock->sk;
++		if (sk->sk_family == PF_INET) {
++			inet = inet_sk(sk);
++			len = sprintf(buf, NIPQUAD_FMT "\n",
++				      NIPQUAD(inet->daddr));
++		} else {
++			np = inet6_sk(sk);
++			len = sprintf(buf, NIP6_FMT "\n", NIP6(np->daddr));
++		}
++		mutex_unlock(&conn->xmitmutex);
+ 		break;
+ 	default:
+ 		return iscsi_conn_get_param(cls_conn, param, buf);
+@@ -1821,29 +2069,6 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 	return len;
+ }
+ 
+-static int
+-iscsi_tcp_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf)
+-{
+-        struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-	int len;
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_IPADDRESS:
+-		spin_lock_bh(&session->lock);
+-		if (!session->leadconn)
+-			len = -ENODEV;
+-		else
+-			len = sprintf(buf, "%s\n",
+-				     session->leadconn->local_address);
+-		spin_unlock_bh(&session->lock);
+-		break;
+-	default:
+-		return iscsi_host_get_param(shost, param, buf);
+-	}
+-	return len;
+-}
+-
+ static void
+ iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats)
+ {
+@@ -1871,7 +2096,6 @@ iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats)
+ static struct iscsi_cls_session *
+ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 			 struct scsi_transport_template *scsit,
+-			 uint16_t cmds_max, uint16_t qdepth,
+ 			 uint32_t initial_cmdsn, uint32_t *hostno)
+ {
+ 	struct iscsi_cls_session *cls_session;
+@@ -1879,7 +2103,7 @@ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 	uint32_t hn;
+ 	int cmd_i;
+ 
+-	cls_session = iscsi_session_setup(iscsit, scsit, cmds_max, qdepth,
++	cls_session = iscsi_session_setup(iscsit, scsit,
+ 					 sizeof(struct iscsi_tcp_cmd_task),
+ 					 sizeof(struct iscsi_tcp_mgmt_task),
+ 					 initial_cmdsn, &hn);
+@@ -1892,15 +2116,14 @@ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 		struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+-		ctask->hdr = &tcp_ctask->hdr.cmd_hdr;
+-		ctask->hdr_max = sizeof(tcp_ctask->hdr) - ISCSI_DIGEST_SIZE;
++		ctask->hdr = &tcp_ctask->hdr;
+ 	}
+ 
+ 	for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) {
+ 		struct iscsi_mgmt_task *mtask = session->mgmt_cmds[cmd_i];
+ 		struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 
+-		mtask->hdr = (struct iscsi_hdr *) &tcp_mtask->hdr;
++		mtask->hdr = &tcp_mtask->hdr;
+ 	}
+ 
+ 	if (iscsi_r2tpool_alloc(class_to_transport_session(cls_session)))
+@@ -1919,27 +2142,17 @@ static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
+ 	iscsi_session_teardown(cls_session);
+ }
+ 
+-static int iscsi_tcp_slave_configure(struct scsi_device *sdev)
+-{
+-	blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_ANY);
+-	blk_queue_dma_alignment(sdev->request_queue, 0);
+-	return 0;
+-}
+-
+ static struct scsi_host_template iscsi_sht = {
+-	.module			= THIS_MODULE,
+ 	.name			= "iSCSI Initiator over TCP/IP",
+ 	.queuecommand           = iscsi_queuecommand,
+ 	.change_queue_depth	= iscsi_change_queue_depth,
+-	.can_queue		= ISCSI_DEF_XMIT_CMDS_MAX - 1,
+-	.sg_tablesize		= 4096,
++	.can_queue		= ISCSI_XMIT_CMDS_MAX - 1,
++	.sg_tablesize		= ISCSI_SG_TABLESIZE,
+ 	.max_sectors		= 0xFFFF,
+ 	.cmd_per_lun		= ISCSI_DEF_CMD_PER_LUN,
+ 	.eh_abort_handler       = iscsi_eh_abort,
+-	.eh_device_reset_handler= iscsi_eh_device_reset,
+ 	.eh_host_reset_handler	= iscsi_eh_host_reset,
+ 	.use_clustering         = DISABLE_CLUSTERING,
+-	.slave_configure        = iscsi_tcp_slave_configure,
+ 	.proc_name		= "iscsi_tcp",
+ 	.this_id		= -1,
+ };
+@@ -1966,19 +2179,12 @@ static struct iscsi_transport iscsi_tcp_transport = {
+ 				  ISCSI_EXP_STATSN |
+ 				  ISCSI_PERSISTENT_PORT |
+ 				  ISCSI_PERSISTENT_ADDRESS |
+-				  ISCSI_TARGET_NAME | ISCSI_TPGT |
+-				  ISCSI_USERNAME | ISCSI_PASSWORD |
+-				  ISCSI_USERNAME_IN | ISCSI_PASSWORD_IN |
+-				  ISCSI_FAST_ABORT | ISCSI_ABORT_TMO |
+-				  ISCSI_LU_RESET_TMO |
+-				  ISCSI_PING_TMO | ISCSI_RECV_TMO,
+-	.host_param_mask	= ISCSI_HOST_HWADDRESS | ISCSI_HOST_IPADDRESS |
+-				  ISCSI_HOST_INITIATOR_NAME |
+-				  ISCSI_HOST_NETDEV_NAME,
++				  ISCSI_TARGET_NAME |
++				  ISCSI_TPGT,
+ 	.host_template		= &iscsi_sht,
+ 	.conndata_size		= sizeof(struct iscsi_conn),
+ 	.max_conn		= 1,
+-	.max_cmd_len		= 16,
++	.max_cmd_len		= ISCSI_TCP_MAX_CMD_LEN,
+ 	/* session management */
+ 	.create_session		= iscsi_tcp_session_create,
+ 	.destroy_session	= iscsi_tcp_session_destroy,
+@@ -1991,14 +2197,11 @@ static struct iscsi_transport iscsi_tcp_transport = {
+ 	.get_session_param	= iscsi_session_get_param,
+ 	.start_conn		= iscsi_conn_start,
+ 	.stop_conn		= iscsi_tcp_conn_stop,
+-	/* iscsi host params */
+-	.get_host_param		= iscsi_tcp_host_get_param,
+-	.set_host_param		= iscsi_host_set_param,
+ 	/* IO */
+ 	.send_pdu		= iscsi_conn_send_pdu,
+ 	.get_stats		= iscsi_conn_get_stats,
+-	.init_cmd_task		= iscsi_tcp_ctask_init,
+-	.init_mgmt_task		= iscsi_tcp_mtask_init,
++	.init_cmd_task		= iscsi_tcp_cmd_init,
++	.init_mgmt_task		= iscsi_tcp_mgmt_init,
+ 	.xmit_cmd_task		= iscsi_tcp_ctask_xmit,
+ 	.xmit_mgmt_task		= iscsi_tcp_mtask_xmit,
+ 	.cleanup_cmd_task	= iscsi_tcp_cleanup_ctask,
+diff --git a/drivers/scsi/iscsi_tcp.h b/drivers/scsi/iscsi_tcp.h
+index ed0b991..3273683 100644
+--- a/drivers/scsi/iscsi_tcp.h
++++ b/drivers/scsi/iscsi_tcp.h
+@@ -24,61 +24,68 @@
+ 
+ #include <scsi/libiscsi.h>
+ 
++/* Socket's Receive state machine */
++#define IN_PROGRESS_WAIT_HEADER		0x0
++#define IN_PROGRESS_HEADER_GATHER	0x1
++#define IN_PROGRESS_DATA_RECV		0x2
++#define IN_PROGRESS_DDIGEST_RECV	0x3
++
++/* xmit state machine */
++#define XMSTATE_IDLE			0x0
++#define XMSTATE_R_HDR			0x1
++#define XMSTATE_W_HDR			0x2
++#define XMSTATE_IMM_HDR			0x4
++#define XMSTATE_IMM_DATA		0x8
++#define XMSTATE_UNS_INIT		0x10
++#define XMSTATE_UNS_HDR			0x20
++#define XMSTATE_UNS_DATA		0x40
++#define XMSTATE_SOL_HDR			0x80
++#define XMSTATE_SOL_DATA		0x100
++#define XMSTATE_W_PAD			0x200
++#define XMSTATE_W_RESEND_PAD		0x400
++#define XMSTATE_W_RESEND_DATA_DIGEST	0x800
++
++#define ISCSI_PAD_LEN			4
++#define ISCSI_SG_TABLESIZE		SG_ALL
++#define ISCSI_TCP_MAX_CMD_LEN		16
++
+ struct crypto_hash;
+ struct socket;
+-struct iscsi_tcp_conn;
+-struct iscsi_segment;
+-
+-typedef int iscsi_segment_done_fn_t(struct iscsi_tcp_conn *,
+-				    struct iscsi_segment *);
+-
+-struct iscsi_segment {
+-	unsigned char		*data;
+-	unsigned int		size;
+-	unsigned int		copied;
+-	unsigned int		total_size;
+-	unsigned int		total_copied;
+-
+-	struct hash_desc	*hash;
+-	unsigned char		recv_digest[ISCSI_DIGEST_SIZE];
+-	unsigned char		digest[ISCSI_DIGEST_SIZE];
+-	unsigned int		digest_len;
+-
+-	struct scatterlist	*sg;
+-	void			*sg_mapped;
+-	unsigned int		sg_offset;
+-
+-	iscsi_segment_done_fn_t	*done;
+-};
+ 
+ /* Socket connection recieve helper */
+ struct iscsi_tcp_recv {
+ 	struct iscsi_hdr	*hdr;
+-	struct iscsi_segment	segment;
+-
+-	/* Allocate buffer for BHS + AHS */
+-	uint32_t		hdr_buf[64];
++	struct sk_buff		*skb;
++	int			offset;
++	int			len;
++	int			hdr_offset;
++	int			copy;
++	int			copied;
++	int			padding;
++	struct iscsi_cmd_task	*ctask;		/* current cmd in progress */
+ 
+ 	/* copied and flipped values */
+ 	int			datalen;
+-};
+-
+-/* Socket connection send helper */
+-struct iscsi_tcp_send {
+-	struct iscsi_hdr	*hdr;
+-	struct iscsi_segment	segment;
+-	struct iscsi_segment	data_segment;
++	int			datadgst;
++	char			zero_copy_hdr;
+ };
+ 
+ struct iscsi_tcp_conn {
+ 	struct iscsi_conn	*iscsi_conn;
+ 	struct socket		*sock;
++	struct iscsi_hdr	hdr;		/* header placeholder */
++	char			hdrext[4*sizeof(__u16) +
++				    sizeof(__u32)];
++	int			data_copied;
+ 	int			stop_stage;	/* conn_stop() flag: *
+ 						 * stop to recover,  *
+ 						 * stop to terminate */
++	/* iSCSI connection-wide sequencing */
++	int			hdr_size;	/* PDU header size */
++
+ 	/* control data */
+ 	struct iscsi_tcp_recv	in;		/* TCP receive context */
+-	struct iscsi_tcp_send	out;		/* TCP send context */
++	int			in_progress;	/* connection state machine */
+ 
+ 	/* old values for socket callbacks */
+ 	void			(*old_data_ready)(struct sock *, int);
+@@ -93,19 +100,29 @@ struct iscsi_tcp_conn {
+ 	uint32_t		sendpage_failures_cnt;
+ 	uint32_t		discontiguous_hdr_cnt;
+ 
+-	int			error;
+-
+ 	ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int);
+ };
+ 
++struct iscsi_buf {
++	struct scatterlist	sg;
++	unsigned int		sent;
++	char			use_sendmsg;
++};
++
+ struct iscsi_data_task {
+ 	struct iscsi_data	hdr;			/* PDU */
+-	char			hdrext[ISCSI_DIGEST_SIZE];/* Header-Digest */
++	char			hdrext[sizeof(__u32)];	/* Header-Digest */
++	struct iscsi_buf	digestbuf;		/* digest buffer */
++	uint32_t		digest;			/* data digest */
+ };
+ 
+ struct iscsi_tcp_mgmt_task {
+ 	struct iscsi_hdr	hdr;
+-	char			hdrext[ISCSI_DIGEST_SIZE]; /* Header-Digest */
++	char			hdrext[sizeof(__u32)]; /* Header-Digest */
++	int			xmstate;	/* mgmt xmit progress */
++	struct iscsi_buf	headbuf;	/* header buffer */
++	struct iscsi_buf	sendbuf;	/* in progress buffer */
++	int			sent;
+ };
+ 
+ struct iscsi_r2t_info {
+@@ -113,26 +130,38 @@ struct iscsi_r2t_info {
+ 	__be32			exp_statsn;	/* copied from R2T */
+ 	uint32_t		data_length;	/* copied from R2T */
+ 	uint32_t		data_offset;	/* copied from R2T */
++	struct iscsi_buf	headbuf;	/* Data-Out Header Buffer */
++	struct iscsi_buf	sendbuf;	/* Data-Out in progress buffer*/
+ 	int			sent;		/* R2T sequence progress */
+ 	int			data_count;	/* DATA-Out payload progress */
++	struct scatterlist	*sg;		/* per-R2T SG list */
+ 	int			solicit_datasn;
+-	struct iscsi_data_task	dtask;		/* Data-Out header buf */
++	struct iscsi_data_task   dtask;        /* which data task */
+ };
+ 
+ struct iscsi_tcp_cmd_task {
+-	struct iscsi_hdr_buff {
+-		struct iscsi_cmd	cmd_hdr;
+-		char			hdrextbuf[ISCSI_MAX_AHS_SIZE +
+-		                                  ISCSI_DIGEST_SIZE];
+-	} hdr;
+-
++	struct iscsi_cmd	hdr;
++	char			hdrext[4*sizeof(__u16)+	/* AHS */
++				    sizeof(__u32)];	/* HeaderDigest */
++	char			pad[ISCSI_PAD_LEN];
++	int			pad_count;		/* padded bytes */
++	struct iscsi_buf	headbuf;		/* header buf (xmit) */
++	struct iscsi_buf	sendbuf;		/* in progress buffer*/
++	int			xmstate;		/* xmit xtate machine */
+ 	int			sent;
+-	uint32_t		exp_datasn;	/* expected target's R2TSN/DataSN */
++	struct scatterlist	*sg;			/* per-cmd SG list  */
++	struct scatterlist	*bad_sg;		/* assert statement */
++	int			sg_count;		/* SG's to process  */
++	uint32_t		exp_r2tsn;
+ 	int			data_offset;
+-	struct iscsi_r2t_info	*r2t;		/* in progress R2T    */
+-	struct iscsi_pool	r2tpool;
++	struct iscsi_r2t_info	*r2t;			/* in progress R2T    */
++	struct iscsi_queue	r2tpool;
+ 	struct kfifo		*r2tqueue;
+-	struct iscsi_data_task	unsol_dtask;	/* Data-Out header buf */
++	struct iscsi_r2t_info	**r2ts;
++	int			digest_count;
++	uint32_t		immdigest;		/* for imm data */
++	struct iscsi_buf	immbuf;			/* for imm data digest */
++	struct iscsi_data_task	unsol_dtask;	/* unsol data task */
+ };
+ 
+ #endif /* ISCSI_H */
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..3f5b9b4 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -22,9 +22,9 @@
+  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+  */
+ #include <linux/types.h>
++#include <linux/mutex.h>
+ #include <linux/kfifo.h>
+ #include <linux/delay.h>
+-#include <linux/log2.h>
+ #include <asm/unaligned.h>
+ #include <net/tcp.h>
+ #include <scsi/scsi_cmnd.h>
+@@ -46,53 +46,27 @@ class_to_transport_session(struct iscsi_cls_session *cls_session)
+ }
+ EXPORT_SYMBOL_GPL(class_to_transport_session);
+ 
+-/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */
+-#define SNA32_CHECK 2147483648UL
++#define INVALID_SN_DELTA	0xffff
+ 
+-static int iscsi_sna_lt(u32 n1, u32 n2)
+-{
+-	return n1 != n2 && ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) ||
+-			    (n1 > n2 && (n2 - n1 < SNA32_CHECK)));
+-}
+-
+-/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */
+-static int iscsi_sna_lte(u32 n1, u32 n2)
+-{
+-	return n1 == n2 || ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) ||
+-			    (n1 > n2 && (n2 - n1 < SNA32_CHECK)));
+-}
+-
+-void
+-iscsi_update_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr)
++int
++iscsi_check_assign_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr)
+ {
+ 	uint32_t max_cmdsn = be32_to_cpu(hdr->max_cmdsn);
+ 	uint32_t exp_cmdsn = be32_to_cpu(hdr->exp_cmdsn);
+ 
+-	/*
+-	 * standard specifies this check for when to update expected and
+-	 * max sequence numbers
+-	 */
+-	if (iscsi_sna_lt(max_cmdsn, exp_cmdsn - 1))
+-		return;
+-
+-	if (exp_cmdsn != session->exp_cmdsn &&
+-	    !iscsi_sna_lt(exp_cmdsn, session->exp_cmdsn))
++	if (max_cmdsn < exp_cmdsn -1 &&
++	    max_cmdsn > exp_cmdsn - INVALID_SN_DELTA)
++		return ISCSI_ERR_MAX_CMDSN;
++	if (max_cmdsn > session->max_cmdsn ||
++	    max_cmdsn < session->max_cmdsn - INVALID_SN_DELTA)
++		session->max_cmdsn = max_cmdsn;
++	if (exp_cmdsn > session->exp_cmdsn ||
++	    exp_cmdsn < session->exp_cmdsn - INVALID_SN_DELTA)
+ 		session->exp_cmdsn = exp_cmdsn;
+ 
+-	if (max_cmdsn != session->max_cmdsn &&
+-	    !iscsi_sna_lt(max_cmdsn, session->max_cmdsn)) {
+-		session->max_cmdsn = max_cmdsn;
+-		/*
+-		 * if the window closed with IO queued, then kick the
+-		 * xmit thread
+-		 */
+-		if (!list_empty(&session->leadconn->xmitqueue) ||
+-		    !list_empty(&session->leadconn->mgmtqueue))
+-			scsi_queue_work(session->host,
+-					&session->leadconn->xmitwork);
+-	}
++	return 0;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_update_cmdsn);
++EXPORT_SYMBOL_GPL(iscsi_check_assign_cmdsn);
+ 
+ void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *ctask,
+ 				   struct iscsi_data *hdr)
+@@ -123,84 +97,6 @@ void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *ctask,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_prep_unsolicit_data_pdu);
+ 
+-static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+-{
+-	unsigned exp_len = ctask->hdr_len + len;
+-
+-	if (exp_len > ctask->hdr_max) {
+-		WARN_ON(1);
+-		return -EINVAL;
+-	}
+-
+-	WARN_ON(len & (ISCSI_PAD_LEN - 1)); /* caller must pad the AHS */
+-	ctask->hdr_len = exp_len;
+-	return 0;
+-}
+-
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -208,47 +104,26 @@ static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+  * Prep basic iSCSI PDU fields for a scsi cmd pdu. The LLD should set
+  * fields like dlength or final based on how much data it sends
+  */
+-static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
++static void iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
+-	int rc;
+-
+-	ctask->hdr_len = 0;
+-	rc = iscsi_add_hdr(ctask, sizeof(*hdr));
+-	if (rc)
+-		return rc;
+-	hdr->opcode = ISCSI_OP_SCSI_CMD;
+-	hdr->flags = ISCSI_ATTR_SIMPLE;
+-	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+-	hdr->itt = build_itt(ctask->itt, session->age);
+-	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+-	session->cmdsn++;
+-	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
+ 
+-	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
++        hdr->opcode = ISCSI_OP_SCSI_CMD;
++        hdr->flags = ISCSI_ATTR_SIMPLE;
++        int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
++        hdr->itt = build_itt(ctask->itt, conn->id, session->age);
++        hdr->data_length = cpu_to_be32(sc->request_bufflen);
++        hdr->cmdsn = cpu_to_be32(session->cmdsn);
++        session->cmdsn++;
++        hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
++        memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++        memset(&hdr->cdb[sc->cmd_len], 0, MAX_COMMAND_SIZE - sc->cmd_len);
++
++	ctask->data_count = 0;
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -264,61 +139,43 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		 *
+ 		 *      pad_count       bytes to be sent as zero-padding
+ 		 */
++		ctask->imm_count = 0;
+ 		ctask->unsol_count = 0;
+ 		ctask->unsol_offset = 0;
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (ctask->total_length >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(ctask->total_length,
+ 							conn->max_xmit_dlength);
+-			hton24(hdr->dlength, ctask->imm_count);
++			hton24(ctask->hdr->dlength, ctask->imm_count);
+ 		} else
+-			zero_data(hdr->dlength);
++			zero_data(ctask->hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min(session->first_burst,
++				ctask->total_length) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+ 		if (!ctask->unsol_count)
+ 			/* No unsolicit Data-Out's */
+-			hdr->flags |= ISCSI_FLAG_CMD_FINAL;
++			ctask->hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 	} else {
++		ctask->datasn = 0;
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+ 	}
+ 
+-	/* calculate size of additional header segments (AHSs) */
+-	hdrlength = ctask->hdr_len - sizeof(*hdr);
+-
+-	WARN_ON(hdrlength & (ISCSI_PAD_LEN-1));
+-	hdrlength /= ISCSI_PAD_LEN;
+-
+-	WARN_ON(hdrlength >= 256);
+-	hdr->hlength = hdrlength & 0xFF;
+-
+-	if (conn->session->tt->init_cmd_task(conn->ctask))
+-		return EIO;
+-
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
+-		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+-	return 0;
+ }
++EXPORT_SYMBOL_GPL(iscsi_prep_scsi_cmd_pdu);
+ 
+ /**
+  * iscsi_complete_command - return command back to scsi-ml
+@@ -330,16 +187,13 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+  */
+ static void iscsi_complete_command(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_conn *conn = ctask->conn;
+-	struct iscsi_session *session = conn->session;
++	struct iscsi_session *session = ctask->conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 
+ 	ctask->state = ISCSI_TASK_COMPLETED;
+ 	ctask->sc = NULL;
+ 	/* SCSI eh reuses commands to verify us */
+ 	sc->SCp.ptr = NULL;
+-	if (conn->ctask == ctask)
+-		conn->ctask = NULL;
+ 	list_del_init(&ctask->running);
+ 	__kfifo_put(session->cmdpool.queue, (void*)&ctask, sizeof(void*));
+ 	sc->scsi_done(sc);
+@@ -350,124 +204,27 @@ static void __iscsi_get_ctask(struct iscsi_cmd_task *ctask)
+ 	atomic_inc(&ctask->refcount);
+ }
+ 
++static void iscsi_get_ctask(struct iscsi_cmd_task *ctask)
++{
++	spin_lock_bh(&ctask->conn->session->lock);
++	__iscsi_get_ctask(ctask);
++	spin_unlock_bh(&ctask->conn->session->lock);
++}
++
+ static void __iscsi_put_ctask(struct iscsi_cmd_task *ctask)
+ {
+ 	if (atomic_dec_and_test(&ctask->refcount))
+ 		iscsi_complete_command(ctask);
+ }
+ 
+-/*
+- * session lock must be held
+- */
+-static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+-			 int err)
++static void iscsi_put_ctask(struct iscsi_cmd_task *ctask)
+ {
+-	struct scsi_cmnd *sc;
+-
+-	sc = ctask->sc;
+-	if (!sc)
+-		return;
+-
+-	if (ctask->state == ISCSI_TASK_PENDING)
+-		/*
+-		 * cmd never made it to the xmit thread, so we should not count
+-		 * the cmd in the sequencing
+-		 */
+-		conn->session->queued_cmdsn--;
+-	else
+-		conn->session->tt->cleanup_cmd_task(conn, ctask);
+-
+-	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
+-	if (conn->ctask == ctask)
+-		conn->ctask = NULL;
+-	/* release ref from queuecommand */
++	spin_lock_bh(&ctask->conn->session->lock);
+ 	__iscsi_put_ctask(ctask);
++	spin_unlock_bh(&ctask->conn->session->lock);
+ }
+ 
+ /**
+- * iscsi_free_mgmt_task - return mgmt task back to pool
+- * @conn: iscsi connection
+- * @mtask: mtask
+- *
+- * Must be called with session lock.
+- */
+-void iscsi_free_mgmt_task(struct iscsi_conn *conn,
+-			  struct iscsi_mgmt_task *mtask)
+-{
+-	list_del_init(&mtask->running);
+-	if (conn->login_mtask == mtask)
+-		return;
+-
+-	if (conn->ping_mtask == mtask)
+-		conn->ping_mtask = NULL;
+-	__kfifo_put(conn->session->mgmtpool.queue,
+-		    (void*)&mtask, sizeof(void*));
+-}
+-EXPORT_SYMBOL_GPL(iscsi_free_mgmt_task);
+-
+-static struct iscsi_mgmt_task *
+-__iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-		      char *data, uint32_t data_size)
+-{
+-	struct iscsi_session *session = conn->session;
+-	struct iscsi_mgmt_task *mtask;
+-
+-	if (session->state == ISCSI_STATE_TERMINATE)
+-		return NULL;
+-
+-	if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) ||
+-	    hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
+-		/*
+-		 * Login and Text are sent serially, in
+-		 * request-followed-by-response sequence.
+-		 * Same mtask can be used. Same ITT must be used.
+-		 * Note that login_mtask is preallocated at conn_create().
+-		 */
+-		mtask = conn->login_mtask;
+-	else {
+-		BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
+-		BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
+-
+-		if (!__kfifo_get(session->mgmtpool.queue,
+-				 (void*)&mtask, sizeof(void*)))
+-			return NULL;
+-	}
+-
+-	if (data_size) {
+-		memcpy(mtask->data, data, data_size);
+-		mtask->data_count = data_size;
+-	} else
+-		mtask->data_count = 0;
+-
+-	memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr));
+-	INIT_LIST_HEAD(&mtask->running);
+-	list_add_tail(&mtask->running, &conn->mgmtqueue);
+-	return mtask;
+-}
+-
+-int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr,
+-			char *data, uint32_t data_size)
+-{
+-	struct iscsi_conn *conn = cls_conn->dd_data;
+-	struct iscsi_session *session = conn->session;
+-	int err = 0;
+-
+-	spin_lock_bh(&session->lock);
+-	if (!__iscsi_conn_send_pdu(conn, hdr, data, data_size))
+-		err = -EPERM;
+-	spin_unlock_bh(&session->lock);
+-	scsi_queue_work(session->host, &conn->xmitwork);
+-	return err;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
+-
+-/**
+  * iscsi_cmd_rsp - SCSI Command Response processing
+  * @conn: iscsi connection
+  * @hdr: iscsi header
+@@ -478,15 +235,21 @@ EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
+  * iscsi_cmd_rsp sets up the scsi_cmnd fields based on the PDU and
+  * then completes the command and task.
+  **/
+-static void iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-			       struct iscsi_cmd_task *ctask, char *data,
+-			       int datalen)
++static int iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			      struct iscsi_cmd_task *ctask, char *data,
++			      int datalen)
+ {
++	int rc;
+ 	struct iscsi_cmd_rsp *rhdr = (struct iscsi_cmd_rsp *)hdr;
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc) {
++		sc->result = DID_ERROR << 16;
++		goto out;
++	}
++
+ 	conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+ 
+ 	sc->result = (DID_OK << 16) | rhdr->cmd_status;
+@@ -501,9 +264,8 @@ static void iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 
+ 		if (datalen < 2) {
+ invalid_datalen:
+-			iscsi_conn_printk(KERN_ERR,  conn,
+-					 "Got CHECK_CONDITION but invalid data "
+-					 "buffer size of %d\n", datalen);
++			printk(KERN_ERR "iscsi: Got CHECK_CONDITION but "
++			       "invalid data buffer size of %d\n", datalen);
+ 			sc->result = DID_BAD_TARGET << 16;
+ 			goto out;
+ 		}
+@@ -518,36 +280,28 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	if (sc->sc_data_direction == DMA_TO_DEVICE)
++		goto out;
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+-	                   ISCSI_FLAG_CMD_OVERFLOW)) {
++	if (rhdr->flags & ISCSI_FLAG_CMD_UNDERFLOW) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+ 
+-		if (res_count > 0 &&
+-		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+-			scsi_set_resid(sc, res_count);
++		if (res_count > 0 && res_count <= sc->request_bufflen)
++			sc->resid = res_count;
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & ISCSI_FLAG_CMD_BIDI_UNDERFLOW)
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++	else if (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW)
++		sc->resid = be32_to_cpu(rhdr->residual_count);
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+ 	conn->scsirsp_pdus_cnt++;
+ 
+ 	__iscsi_put_ctask(ctask);
++	return rc;
+ }
+ 
+ static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+@@ -557,51 +311,18 @@ static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 	conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+ 	conn->tmfrsp_pdus_cnt++;
+ 
+-	if (conn->tmf_state != TMF_QUEUED)
++	if (conn->tmabort_state != TMABORT_INITIAL)
+ 		return;
+ 
+ 	if (tmf->response == ISCSI_TMF_RSP_COMPLETE)
+-		conn->tmf_state = TMF_SUCCESS;
++		conn->tmabort_state = TMABORT_SUCCESS;
+ 	else if (tmf->response == ISCSI_TMF_RSP_NO_TASK)
+-		conn->tmf_state = TMF_NOT_FOUND;
++		conn->tmabort_state = TMABORT_NOT_FOUND;
+ 	else
+-		conn->tmf_state = TMF_FAILED;
++		conn->tmabort_state = TMABORT_FAILED;
+ 	wake_up(&conn->ehwait);
+ }
+ 
+-static void iscsi_send_nopout(struct iscsi_conn *conn, struct iscsi_nopin *rhdr)
+-{
+-        struct iscsi_nopout hdr;
+-	struct iscsi_mgmt_task *mtask;
+-
+-	if (!rhdr && conn->ping_mtask)
+-		return;
+-
+-	memset(&hdr, 0, sizeof(struct iscsi_nopout));
+-	hdr.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE;
+-	hdr.flags = ISCSI_FLAG_CMD_FINAL;
+-
+-	if (rhdr) {
+-		memcpy(hdr.lun, rhdr->lun, 8);
+-		hdr.ttt = rhdr->ttt;
+-		hdr.itt = RESERVED_ITT;
+-	} else
+-		hdr.ttt = RESERVED_ITT;
+-
+-	mtask = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)&hdr, NULL, 0);
+-	if (!mtask) {
+-		iscsi_conn_printk(KERN_ERR, conn, "Could not send nopout\n");
+-		return;
+-	}
+-
+-	/* only track our nops */
+-	if (!rhdr) {
+-		conn->ping_mtask = mtask;
+-		conn->last_ping = jiffies;
+-	}
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-
+ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			       char *data, int datalen)
+ {
+@@ -618,10 +339,9 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		if (ntoh24(reject->dlength) >= sizeof(struct iscsi_hdr)) {
+ 			memcpy(&rejected_pdu, data, sizeof(struct iscsi_hdr));
+ 			itt = get_itt(rejected_pdu.itt);
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "itt 0x%x had pdu (op 0x%x) rejected "
+-					  "due to DataDigest error.\n", itt,
+-					  rejected_pdu.opcode);
++			printk(KERN_ERR "itt 0x%x had pdu (op 0x%x) rejected "
++				"due to DataDigest error.\n", itt,
++				rejected_pdu.opcode);
+ 		}
+ 	}
+ 	return 0;
+@@ -638,8 +358,8 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+  * queuecommand or send generic. session lock must be held and verify
+  * itt must have been called.
+  */
+-static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-				char *data, int datalen)
++int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			 char *data, int datalen)
+ {
+ 	struct iscsi_session *session = conn->session;
+ 	int opcode = hdr->opcode & ISCSI_OPCODE_MASK, rc = 0;
+@@ -647,7 +367,6 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	struct iscsi_mgmt_task *mtask;
+ 	uint32_t itt;
+ 
+-	conn->last_recv = jiffies;
+ 	if (hdr->itt != RESERVED_ITT)
+ 		itt = get_itt(hdr->itt);
+ 	else
+@@ -662,8 +381,8 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		switch(opcode) {
+ 		case ISCSI_OP_SCSI_CMD_RSP:
+ 			BUG_ON((void*)ctask != ctask->sc->SCp.ptr);
+-			iscsi_scsi_cmd_rsp(conn, hdr, ctask, data,
+-					   datalen);
++			rc = iscsi_scsi_cmd_rsp(conn, hdr, ctask, data,
++						datalen);
+ 			break;
+ 		case ISCSI_OP_SCSI_DATA_IN:
+ 			BUG_ON((void*)ctask != ctask->sc->SCp.ptr);
+@@ -686,7 +405,11 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		debug_scsi("immrsp [op 0x%x cid %d itt 0x%x len %d]\n",
+ 			   opcode, conn->id, mtask->itt, datalen);
+ 
+-		iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr);
++		rc = iscsi_check_assign_cmdsn(session,
++					      (struct iscsi_nopin*)hdr);
++		if (rc)
++			goto done;
++
+ 		switch(opcode) {
+ 		case ISCSI_OP_LOGOUT_RSP:
+ 			if (datalen) {
+@@ -703,7 +426,10 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			 */
+ 			if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen))
+ 				rc = ISCSI_ERR_CONN_FAILED;
+-			iscsi_free_mgmt_task(conn, mtask);
++			list_del(&mtask->running);
++			if (conn->login_mtask != mtask)
++				__kfifo_put(session->mgmtpool.queue,
++					    (void*)&mtask, sizeof(void*));
+ 			break;
+ 		case ISCSI_OP_SCSI_TMFUNC_RSP:
+ 			if (datalen) {
+@@ -712,33 +438,30 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			}
+ 
+ 			iscsi_tmf_rsp(conn, hdr);
+-			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		case ISCSI_OP_NOOP_IN:
+-			if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) ||
+-			    datalen) {
++			if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) || datalen) {
+ 				rc = ISCSI_ERR_PROTO;
+ 				break;
+ 			}
+ 			conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+ 
+-			if (conn->ping_mtask != mtask) {
+-				/*
+-				 * If this is not in response to one of our
+-				 * nops then it must be from userspace.
+-				 */
+-				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+-						   datalen))
+-					rc = ISCSI_ERR_CONN_FAILED;
+-			}
+-			iscsi_free_mgmt_task(conn, mtask);
++			if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen))
++				rc = ISCSI_ERR_CONN_FAILED;
++			list_del(&mtask->running);
++			if (conn->login_mtask != mtask)
++				__kfifo_put(session->mgmtpool.queue,
++					    (void*)&mtask, sizeof(void*));
+ 			break;
+ 		default:
+ 			rc = ISCSI_ERR_BAD_OPCODE;
+ 			break;
+ 		}
+ 	} else if (itt == ~0U) {
+-		iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr);
++		rc = iscsi_check_assign_cmdsn(session,
++					     (struct iscsi_nopin*)hdr);
++		if (rc)
++			goto done;
+ 
+ 		switch(opcode) {
+ 		case ISCSI_OP_NOOP_IN:
+@@ -750,7 +473,8 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG))
+ 				break;
+ 
+-			iscsi_send_nopout(conn, (struct iscsi_nopin*)hdr);
++			if (iscsi_recv_pdu(conn->cls_conn, hdr, NULL, 0))
++				rc = ISCSI_ERR_CONN_FAILED;
+ 			break;
+ 		case ISCSI_OP_REJECT:
+ 			rc = iscsi_handle_reject(conn, hdr, data, datalen);
+@@ -767,8 +491,10 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	} else
+ 		rc = ISCSI_ERR_BAD_ITT;
+ 
++done:
+ 	return rc;
+ }
++EXPORT_SYMBOL_GPL(__iscsi_complete_pdu);
+ 
+ int iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		       char *data, int datalen)
+@@ -793,13 +519,18 @@ int iscsi_verify_itt(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	if (hdr->itt != RESERVED_ITT) {
+ 		if (((__force u32)hdr->itt & ISCSI_AGE_MASK) !=
+ 		    (session->age << ISCSI_AGE_SHIFT)) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "received itt %x expected session "
+-					  "age (%x)\n", (__force u32)hdr->itt,
+-					  session->age & ISCSI_AGE_MASK);
++			printk(KERN_ERR "iscsi: received itt %x expected "
++				"session age (%x)\n", (__force u32)hdr->itt,
++				session->age & ISCSI_AGE_MASK);
+ 			return ISCSI_ERR_BAD_ITT;
+ 		}
+ 
++		if (((__force u32)hdr->itt & ISCSI_CID_MASK) !=
++		    (conn->id << ISCSI_CID_SHIFT)) {
++			printk(KERN_ERR "iscsi: received itt %x, expected "
++				"CID (%x)\n", (__force u32)hdr->itt, conn->id);
++			return ISCSI_ERR_BAD_ITT;
++		}
+ 		itt = get_itt(hdr->itt);
+ 	} else
+ 		itt = ~0U;
+@@ -808,17 +539,16 @@ int iscsi_verify_itt(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		ctask = session->cmds[itt];
+ 
+ 		if (!ctask->sc) {
+-			iscsi_conn_printk(KERN_INFO, conn, "dropping ctask "
+-					  "with itt 0x%x\n", ctask->itt);
++			printk(KERN_INFO "iscsi: dropping ctask with "
++			       "itt 0x%x\n", ctask->itt);
+ 			/* force drop */
+ 			return ISCSI_ERR_NO_SCSI_CMD;
+ 		}
+ 
+ 		if (ctask->sc->SCp.phase != session->age) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "iscsi: ctask's session age %d, "
+-					  "expected %d\n", ctask->sc->SCp.phase,
+-					  session->age);
++			printk(KERN_ERR "iscsi: ctask's session age %d, "
++				"expected %d\n", ctask->sc->SCp.phase,
++				session->age);
+ 			return ISCSI_ERR_SESSION_FAILED;
+ 		}
+ 	}
+@@ -848,110 +578,30 @@ void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_failure);
+ 
+-static void iscsi_prep_mtask(struct iscsi_conn *conn,
+-			     struct iscsi_mgmt_task *mtask)
+-{
+-	struct iscsi_session *session = conn->session;
+-	struct iscsi_hdr *hdr = mtask->hdr;
+-	struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr;
+-
+-	if (hdr->opcode != (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) &&
+-	    hdr->opcode != (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
+-		nop->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	/*
+-	 * pre-format CmdSN for outgoing PDU.
+-	 */
+-	nop->cmdsn = cpu_to_be32(session->cmdsn);
+-	if (hdr->itt != RESERVED_ITT) {
+-		hdr->itt = build_itt(mtask->itt, session->age);
+-		/*
+-		 * TODO: We always use immediate, so we never hit this.
+-		 * If we start to send tmfs or nops as non-immediate then
+-		 * we should start checking the cmdsn numbers for mgmt tasks.
+-		 */
+-		if (conn->c_stage == ISCSI_CONN_STARTED &&
+-		    !(hdr->opcode & ISCSI_OP_IMMEDIATE)) {
+-			session->queued_cmdsn++;
+-			session->cmdsn++;
+-		}
+-	}
+-
+-	if (session->tt->init_mgmt_task)
+-		session->tt->init_mgmt_task(conn, mtask);
+-
+-	debug_scsi("mgmtpdu [op 0x%x hdr->itt 0x%x datalen %d]\n",
+-		   hdr->opcode & ISCSI_OPCODE_MASK, hdr->itt,
+-		   mtask->data_count);
+-}
+-
+ static int iscsi_xmit_mtask(struct iscsi_conn *conn)
+ {
+ 	struct iscsi_hdr *hdr = conn->mtask->hdr;
+-	int rc;
+-
+-	if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT)
+-		conn->session->state = ISCSI_STATE_LOGGING_OUT;
+-	spin_unlock_bh(&conn->session->lock);
++	int rc, was_logout = 0;
+ 
++	if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT) {
++		conn->session->state = ISCSI_STATE_IN_RECOVERY;
++		iscsi_block_session(session_to_cls(conn->session));
++		was_logout = 1;
++	}
+ 	rc = conn->session->tt->xmit_mgmt_task(conn, conn->mtask);
+-	spin_lock_bh(&conn->session->lock);
+ 	if (rc)
+ 		return rc;
+ 
+ 	/* done with this in-progress mtask */
+ 	conn->mtask = NULL;
+-	return 0;
+-}
+-
+-static int iscsi_check_cmdsn_window_closed(struct iscsi_conn *conn)
+-{
+-	struct iscsi_session *session = conn->session;
+ 
+-	/*
+-	 * Check for iSCSI window and take care of CmdSN wrap-around
+-	 */
+-	if (!iscsi_sna_lte(session->queued_cmdsn, session->max_cmdsn)) {
+-		debug_scsi("iSCSI CmdSN closed. ExpCmdSn %u MaxCmdSN %u "
+-			   "CmdSN %u/%u\n", session->exp_cmdsn,
+-			   session->max_cmdsn, session->cmdsn,
+-			   session->queued_cmdsn);
+-		return -ENOSPC;
++	if (was_logout) {
++		set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
++		return -ENODATA;
+ 	}
+ 	return 0;
+ }
+ 
+-static int iscsi_xmit_ctask(struct iscsi_conn *conn)
+-{
+-	struct iscsi_cmd_task *ctask = conn->ctask;
+-	int rc;
+-
+-	__iscsi_get_ctask(ctask);
+-	spin_unlock_bh(&conn->session->lock);
+-	rc = conn->session->tt->xmit_cmd_task(conn, ctask);
+-	spin_lock_bh(&conn->session->lock);
+-	__iscsi_put_ctask(ctask);
+-	if (!rc)
+-		/* done with this ctask */
+-		conn->ctask = NULL;
+-	return rc;
+-}
+-
+-/**
+- * iscsi_requeue_ctask - requeue ctask to run from session workqueue
+- * @ctask: ctask to requeue
+- *
+- * LLDs that need to run a ctask from the session workqueue should call
+- * this. The session lock must be held.
+- */
+-void iscsi_requeue_ctask(struct iscsi_cmd_task *ctask)
+-{
+-	struct iscsi_conn *conn = ctask->conn;
+-
+-	list_move_tail(&ctask->running, &conn->requeue);
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-EXPORT_SYMBOL_GPL(iscsi_requeue_ctask);
+-
+ /**
+  * iscsi_data_xmit - xmit any command into the scheduled connection
+  * @conn: iscsi connection
+@@ -963,106 +613,106 @@ EXPORT_SYMBOL_GPL(iscsi_requeue_ctask);
+  **/
+ static int iscsi_data_xmit(struct iscsi_conn *conn)
+ {
++	struct iscsi_transport *tt;
+ 	int rc = 0;
+ 
+-	spin_lock_bh(&conn->session->lock);
+ 	if (unlikely(conn->suspend_tx)) {
+ 		debug_scsi("conn %d Tx suspended!\n", conn->id);
+-		spin_unlock_bh(&conn->session->lock);
+ 		return -ENODATA;
+ 	}
++	tt = conn->session->tt;
++
++	/*
++	 * Transmit in the following order:
++	 *
++	 * 1) un-finished xmit (ctask or mtask)
++	 * 2) immediate control PDUs
++	 * 3) write data
++	 * 4) SCSI commands
++	 * 5) non-immediate control PDUs
++	 *
++	 * No need to lock around __kfifo_get as long as
++	 * there's one producer and one consumer.
++	 */
++
++	BUG_ON(conn->ctask && conn->mtask);
+ 
+ 	if (conn->ctask) {
+-		rc = iscsi_xmit_ctask(conn);
++		iscsi_get_ctask(conn->ctask);
++		rc = tt->xmit_cmd_task(conn, conn->ctask);
++		iscsi_put_ctask(conn->ctask);
+ 		if (rc)
+ 			goto again;
++		/* done with this in-progress ctask */
++		conn->ctask = NULL;
+ 	}
+-
+ 	if (conn->mtask) {
+ 		rc = iscsi_xmit_mtask(conn);
+ 	        if (rc)
+ 		        goto again;
+ 	}
+ 
+-	/*
+-	 * process mgmt pdus like nops before commands since we should
+-	 * only have one nop-out as a ping from us and targets should not
+-	 * overflow us with nop-ins
+-	 */
+-check_mgmt:
+-	while (!list_empty(&conn->mgmtqueue)) {
+-		conn->mtask = list_entry(conn->mgmtqueue.next,
+-					 struct iscsi_mgmt_task, running);
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
+-			iscsi_free_mgmt_task(conn, conn->mtask);
+-			conn->mtask = NULL;
+-			continue;
+-		}
+-
+-		iscsi_prep_mtask(conn, conn->mtask);
+-		list_move_tail(conn->mgmtqueue.next, &conn->mgmt_run_list);
+-		rc = iscsi_xmit_mtask(conn);
+-		if (rc)
+-			goto again;
++	/* process immediate first */
++        if (unlikely(__kfifo_len(conn->immqueue))) {
++	        while (__kfifo_get(conn->immqueue, (void*)&conn->mtask,
++			           sizeof(void*))) {
++			spin_lock_bh(&conn->session->lock);
++			list_add_tail(&conn->mtask->running,
++				      &conn->mgmt_run_list);
++			spin_unlock_bh(&conn->session->lock);
++			rc = iscsi_xmit_mtask(conn);
++		        if (rc)
++			        goto again;
++	        }
+ 	}
+ 
+-	/* process pending command queue */
++	/* process command queue */
++	spin_lock_bh(&conn->session->lock);
+ 	while (!list_empty(&conn->xmitqueue)) {
+-		if (conn->tmf_state == TMF_QUEUED)
+-			break;
+-
++		/*
++		 * iscsi tcp may readd the task to the xmitqueue to send
++		 * write data
++		 */
+ 		conn->ctask = list_entry(conn->xmitqueue.next,
+ 					 struct iscsi_cmd_task, running);
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
+-			fail_command(conn, conn->ctask, DID_IMM_RETRY << 16);
+-			continue;
+-		}
+-		if (iscsi_prep_scsi_cmd_pdu(conn->ctask)) {
+-			fail_command(conn, conn->ctask, DID_ABORT << 16);
+-			continue;
+-		}
+-
+ 		conn->ctask->state = ISCSI_TASK_RUNNING;
+ 		list_move_tail(conn->xmitqueue.next, &conn->run_list);
+-		rc = iscsi_xmit_ctask(conn);
+-		if (rc)
+-			goto again;
+-		/*
+-		 * we could continuously get new ctask requests so
+-		 * we need to check the mgmt queue for nops that need to
+-		 * be sent to aviod starvation
+-		 */
+-		if (!list_empty(&conn->mgmtqueue))
+-			goto check_mgmt;
+-	}
+-
+-	while (!list_empty(&conn->requeue)) {
+-		if (conn->session->fast_abort && conn->tmf_state != TMF_INITIAL)
+-			break;
++		__iscsi_get_ctask(conn->ctask);
++		spin_unlock_bh(&conn->session->lock);
+ 
+-		/*
+-		 * we always do fastlogout - conn stop code will clean up.
+-		 */
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT)
+-			break;
++		rc = tt->xmit_cmd_task(conn, conn->ctask);
+ 
+-		conn->ctask = list_entry(conn->requeue.next,
+-					 struct iscsi_cmd_task, running);
+-		conn->ctask->state = ISCSI_TASK_RUNNING;
+-		list_move_tail(conn->requeue.next, &conn->run_list);
+-		rc = iscsi_xmit_ctask(conn);
+-		if (rc)
++		spin_lock_bh(&conn->session->lock);
++		__iscsi_put_ctask(conn->ctask);
++		if (rc) {
++			spin_unlock_bh(&conn->session->lock);
+ 			goto again;
+-		if (!list_empty(&conn->mgmtqueue))
+-			goto check_mgmt;
++		}
+ 	}
+ 	spin_unlock_bh(&conn->session->lock);
++	/* done with this ctask */
++	conn->ctask = NULL;
++
++	/* process the rest control plane PDUs, if any */
++        if (unlikely(__kfifo_len(conn->mgmtqueue))) {
++	        while (__kfifo_get(conn->mgmtqueue, (void*)&conn->mtask,
++			           sizeof(void*))) {
++			spin_lock_bh(&conn->session->lock);
++			list_add_tail(&conn->mtask->running,
++				      &conn->mgmt_run_list);
++			spin_unlock_bh(&conn->session->lock);
++			rc = iscsi_xmit_mtask(conn);
++		        if (rc)
++			        goto again;
++	        }
++	}
++
+ 	return -ENODATA;
+ 
+ again:
+ 	if (unlikely(conn->suspend_tx))
+-		rc = -ENODATA;
+-	spin_unlock_bh(&conn->session->lock);
++		return -ENODATA;
++
+ 	return rc;
+ }
+ 
+@@ -1074,9 +724,11 @@ static void iscsi_xmitworker(struct work_struct *work)
+ 	/*
+ 	 * serialize Xmit worker on a per-connection basis.
+ 	 */
++	mutex_lock(&conn->xmitmutex);
+ 	do {
+ 		rc = iscsi_data_xmit(conn);
+ 	} while (rc >= 0 || rc == -EAGAIN);
++	mutex_unlock(&conn->xmitmutex);
+ }
+ 
+ enum {
+@@ -1088,8 +740,6 @@ enum {
+ 	FAILURE_SESSION_TERMINATE,
+ 	FAILURE_SESSION_IN_RECOVERY,
+ 	FAILURE_SESSION_RECOVERY_TIMEOUT,
+-	FAILURE_SESSION_LOGGING_OUT,
+-	FAILURE_SESSION_NOT_READY,
+ };
+ 
+ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+@@ -1105,16 +755,9 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 	sc->SCp.ptr = NULL;
+ 
+ 	host = sc->device->host;
+-	spin_unlock(host->host_lock);
+-
+ 	session = iscsi_hostdata(host->hostdata);
+-	spin_lock(&session->lock);
+ 
+-	reason = iscsi_session_chkready(session_to_cls(session));
+-	if (reason) {
+-		sc->result = reason;
+-		goto fault;
+-	}
++	spin_lock(&session->lock);
+ 
+ 	/*
+ 	 * ISCSI_STATE_FAILED is a temp. state. The recovery
+@@ -1129,82 +772,77 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 		 * be entering our queuecommand while a block is starting
+ 		 * up because the block code is not locked)
+ 		 */
+-		switch (session->state) {
+-		case ISCSI_STATE_IN_RECOVERY:
++		if (session->state == ISCSI_STATE_IN_RECOVERY) {
+ 			reason = FAILURE_SESSION_IN_RECOVERY;
+-			sc->result = DID_IMM_RETRY << 16;
+-			break;
+-		case ISCSI_STATE_LOGGING_OUT:
+-			reason = FAILURE_SESSION_LOGGING_OUT;
+-			sc->result = DID_IMM_RETRY << 16;
+-			break;
+-		case ISCSI_STATE_RECOVERY_FAILED:
++			goto reject;
++		}
++
++		if (session->state == ISCSI_STATE_RECOVERY_FAILED)
+ 			reason = FAILURE_SESSION_RECOVERY_TIMEOUT;
+-			sc->result = DID_NO_CONNECT << 16;
+-			break;
+-		case ISCSI_STATE_TERMINATE:
++		else if (session->state == ISCSI_STATE_TERMINATE)
+ 			reason = FAILURE_SESSION_TERMINATE;
+-			sc->result = DID_NO_CONNECT << 16;
+-			break;
+-		default:
++		else
+ 			reason = FAILURE_SESSION_FREED;
+-			sc->result = DID_NO_CONNECT << 16;
+-		}
+ 		goto fault;
+ 	}
+ 
++	/*
++	 * Check for iSCSI window and take care of CmdSN wrap-around
++	 */
++	if ((int)(session->max_cmdsn - session->cmdsn) < 0) {
++		reason = FAILURE_WINDOW_CLOSED;
++		goto reject;
++	}
++
+ 	conn = session->leadconn;
+ 	if (!conn) {
+ 		reason = FAILURE_SESSION_FREED;
+-		sc->result = DID_NO_CONNECT << 16;
+ 		goto fault;
+ 	}
+ 
+-	if (iscsi_check_cmdsn_window_closed(conn)) {
+-		reason = FAILURE_WINDOW_CLOSED;
+-		goto reject;
+-	}
+-
+ 	if (!__kfifo_get(session->cmdpool.queue, (void*)&ctask,
+ 			 sizeof(void*))) {
+ 		reason = FAILURE_OOM;
+ 		goto reject;
+ 	}
+-	session->queued_cmdsn++;
+-
+ 	sc->SCp.phase = session->age;
+ 	sc->SCp.ptr = (char *)ctask;
+ 
+ 	atomic_set(&ctask->refcount, 1);
+ 	ctask->state = ISCSI_TASK_PENDING;
++	ctask->mtask = NULL;
+ 	ctask->conn = conn;
+ 	ctask->sc = sc;
+ 	INIT_LIST_HEAD(&ctask->running);
++	ctask->total_length = sc->request_bufflen;
++	iscsi_prep_scsi_cmd_pdu(ctask);
++
++	session->tt->init_cmd_task(ctask);
+ 
+ 	list_add_tail(&ctask->running, &conn->xmitqueue);
++	debug_scsi(
++	       "ctask enq [%s cid %d sc %p cdb 0x%x itt 0x%x len %d cmdsn %d "
++		"win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, sc->request_bufflen,
++		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	spin_unlock(&session->lock);
+ 
+ 	scsi_queue_work(host, &conn->xmitwork);
+-	spin_lock(host->host_lock);
+ 	return 0;
+ 
+ reject:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("cmd 0x%x rejected (%d)\n", sc->cmnd[0], reason);
+-	spin_lock(host->host_lock);
+ 	return SCSI_MLQUEUE_HOST_BUSY;
+ 
+ fault:
+ 	spin_unlock(&session->lock);
+-	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	printk(KERN_ERR "iscsi: cmd 0x%x is not queued (%d)\n",
++	       sc->cmnd[0], reason);
++	sc->result = (DID_NO_CONNECT << 16);
++	sc->resid = sc->request_bufflen;
+ 	sc->scsi_done(sc);
+-	spin_lock(host->host_lock);
+ 	return 0;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_queuecommand);
+@@ -1218,15 +856,106 @@ int iscsi_change_queue_depth(struct scsi_device *sdev, int depth)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_change_queue_depth);
+ 
++static int
++iscsi_conn_send_generic(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			char *data, uint32_t data_size)
++{
++	struct iscsi_session *session = conn->session;
++	struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr;
++	struct iscsi_mgmt_task *mtask;
++
++	spin_lock_bh(&session->lock);
++	if (session->state == ISCSI_STATE_TERMINATE) {
++		spin_unlock_bh(&session->lock);
++		return -EPERM;
++	}
++	if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) ||
++	    hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
++		/*
++		 * Login and Text are sent serially, in
++		 * request-followed-by-response sequence.
++		 * Same mtask can be used. Same ITT must be used.
++		 * Note that login_mtask is preallocated at conn_create().
++		 */
++		mtask = conn->login_mtask;
++	else {
++		BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
++		BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
++
++		nop->exp_statsn = cpu_to_be32(conn->exp_statsn);
++		if (!__kfifo_get(session->mgmtpool.queue,
++				 (void*)&mtask, sizeof(void*))) {
++			spin_unlock_bh(&session->lock);
++			return -ENOSPC;
++		}
++	}
++
++	/*
++	 * pre-format CmdSN for outgoing PDU.
++	 */
++	if (hdr->itt != RESERVED_ITT) {
++		hdr->itt = build_itt(mtask->itt, conn->id, session->age);
++		nop->cmdsn = cpu_to_be32(session->cmdsn);
++		if (conn->c_stage == ISCSI_CONN_STARTED &&
++		    !(hdr->opcode & ISCSI_OP_IMMEDIATE))
++			session->cmdsn++;
++	} else
++		/* do not advance CmdSN */
++		nop->cmdsn = cpu_to_be32(session->cmdsn);
++
++	if (data_size) {
++		memcpy(mtask->data, data, data_size);
++		mtask->data_count = data_size;
++	} else
++		mtask->data_count = 0;
++
++	INIT_LIST_HEAD(&mtask->running);
++	memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr));
++	if (session->tt->init_mgmt_task)
++		session->tt->init_mgmt_task(conn, mtask, data, data_size);
++	spin_unlock_bh(&session->lock);
++
++	debug_scsi("mgmtpdu [op 0x%x hdr->itt 0x%x datalen %d]\n",
++		   hdr->opcode, hdr->itt, data_size);
++
++	/*
++	 * since send_pdu() could be called at least from two contexts,
++	 * we need to serialize __kfifo_put, so we don't have to take
++	 * additional lock on fast data-path
++	 */
++        if (hdr->opcode & ISCSI_OP_IMMEDIATE)
++	        __kfifo_put(conn->immqueue, (void*)&mtask, sizeof(void*));
++	else
++	        __kfifo_put(conn->mgmtqueue, (void*)&mtask, sizeof(void*));
++
++	scsi_queue_work(session->host, &conn->xmitwork);
++	return 0;
++}
++
++int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr,
++			char *data, uint32_t data_size)
++{
++	struct iscsi_conn *conn = cls_conn->dd_data;
++	int rc;
++
++	mutex_lock(&conn->xmitmutex);
++	rc = iscsi_conn_send_generic(conn, hdr, data, data_size);
++	mutex_unlock(&conn->xmitmutex);
++
++	return rc;
++}
++EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
++
+ void iscsi_session_recovery_timedout(struct iscsi_cls_session *cls_session)
+ {
+ 	struct iscsi_session *session = class_to_transport_session(cls_session);
++	struct iscsi_conn *conn = session->leadconn;
+ 
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN) {
+ 		session->state = ISCSI_STATE_RECOVERY_FAILED;
+-		if (session->leadconn)
+-			wake_up(&session->leadconn->ehwait);
++		if (conn)
++			wake_up(&conn->ehwait);
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ }
+@@ -1237,25 +966,30 @@ int iscsi_eh_host_reset(struct scsi_cmnd *sc)
+ 	struct Scsi_Host *host = sc->device->host;
+ 	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+ 	struct iscsi_conn *conn = session->leadconn;
++	int fail_session = 0;
+ 
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_TERMINATE) {
+ failed:
+ 		debug_scsi("failing host reset: session terminated "
+ 			   "[CID %d age %d]\n", conn->id, session->age);
+ 		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return FAILED;
+ 	}
+ 
++	if (sc->SCp.phase == session->age) {
++		debug_scsi("failing connection CID %d due to SCSI host reset\n",
++			   conn->id);
++		fail_session = 1;
++	}
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
+ 	/*
+ 	 * we drop the lock here but the leadconn cannot be destoyed while
+ 	 * we are in the scsi eh
+ 	 */
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	if (fail_session)
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+ 
+ 	debug_scsi("iscsi_eh_host_reset wait for relogin\n");
+ 	wait_event_interruptible(conn->ehwait,
+@@ -1265,497 +999,324 @@ failed:
+ 	if (signal_pending(current))
+ 		flush_signals(current);
+ 
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_LOGGED_IN)
+-		iscsi_session_printk(KERN_INFO, session,
+-				     "host reset succeeded\n");
++		printk(KERN_INFO "iscsi: host reset succeeded\n");
+ 	else
+ 		goto failed;
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
+ 	return SUCCESS;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_eh_host_reset);
+ 
+-static void iscsi_tmf_timedout(unsigned long data)
++static void iscsi_tmabort_timedout(unsigned long data)
+ {
+-	struct iscsi_conn *conn = (struct iscsi_conn *)data;
++	struct iscsi_cmd_task *ctask = (struct iscsi_cmd_task *)data;
++	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+ 
+ 	spin_lock(&session->lock);
+-	if (conn->tmf_state == TMF_QUEUED) {
+-		conn->tmf_state = TMF_TIMEDOUT;
+-		debug_scsi("tmf timedout\n");
++	if (conn->tmabort_state == TMABORT_INITIAL) {
++		conn->tmabort_state = TMABORT_TIMEDOUT;
++		debug_scsi("tmabort timedout [sc %p itt 0x%x]\n",
++			ctask->sc, ctask->itt);
+ 		/* unblock eh_abort() */
+ 		wake_up(&conn->ehwait);
+ 	}
+ 	spin_unlock(&session->lock);
+ }
+ 
+-static int iscsi_exec_task_mgmt_fn(struct iscsi_conn *conn,
+-				   struct iscsi_tm *hdr, int age,
+-				   int timeout)
++/* must be called with the mutex lock */
++static int iscsi_exec_abort_task(struct scsi_cmnd *sc,
++				 struct iscsi_cmd_task *ctask)
+ {
++	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+-	struct iscsi_mgmt_task *mtask;
++	struct iscsi_tm *hdr = &conn->tmhdr;
++	int rc;
+ 
+-	mtask = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)hdr,
+-				      NULL, 0);
+-	if (!mtask) {
+-		spin_unlock_bh(&session->lock);
++	/*
++	 * ctask timed out but session is OK requests must be serialized.
++	 */
++	memset(hdr, 0, sizeof(struct iscsi_tm));
++	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
++	hdr->flags = ISCSI_TM_FUNC_ABORT_TASK;
++	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
++	memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun));
++	hdr->rtt = ctask->hdr->itt;
++	hdr->refcmdsn = ctask->hdr->cmdsn;
++
++	rc = iscsi_conn_send_generic(conn, (struct iscsi_hdr *)hdr,
++				     NULL, 0);
++	if (rc) {
+ 		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		spin_lock_bh(&session->lock);
+-		debug_scsi("tmf exec failure\n");
+-		return -EPERM;
++		debug_scsi("abort sent failure [itt 0x%x] %d\n", ctask->itt,
++		           rc);
++		return rc;
+ 	}
+-	conn->tmfcmd_pdus_cnt++;
+-	conn->tmf_timer.expires = timeout * HZ + jiffies;
+-	conn->tmf_timer.function = iscsi_tmf_timedout;
+-	conn->tmf_timer.data = (unsigned long)conn;
+-	add_timer(&conn->tmf_timer);
+-	debug_scsi("tmf set timeout\n");
+ 
++	debug_scsi("abort sent [itt 0x%x]\n", ctask->itt);
++
++	spin_lock_bh(&session->lock);
++	ctask->mtask = (struct iscsi_mgmt_task *)
++			session->mgmt_cmds[get_itt(hdr->itt) -
++					ISCSI_MGMT_ITT_OFFSET];
++
++	if (conn->tmabort_state == TMABORT_INITIAL) {
++		conn->tmfcmd_pdus_cnt++;
++		conn->tmabort_timer.expires = 10*HZ + jiffies;
++		conn->tmabort_timer.function = iscsi_tmabort_timedout;
++		conn->tmabort_timer.data = (unsigned long)ctask;
++		add_timer(&conn->tmabort_timer);
++		debug_scsi("abort set timeout [itt 0x%x]\n", ctask->itt);
++	}
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
+-	scsi_queue_work(session->host, &conn->xmitwork);
++	mutex_unlock(&conn->xmitmutex);
+ 
+ 	/*
+ 	 * block eh thread until:
+ 	 *
+-	 * 1) tmf response
+-	 * 2) tmf timeout
++	 * 1) abort response
++	 * 2) abort timeout
+ 	 * 3) session is terminated or restarted or userspace has
+ 	 * given up on recovery
+ 	 */
+-	wait_event_interruptible(conn->ehwait, age != session->age ||
++	wait_event_interruptible(conn->ehwait,
++				 sc->SCp.phase != session->age ||
+ 				 session->state != ISCSI_STATE_LOGGED_IN ||
+-				 conn->tmf_state != TMF_QUEUED);
++				 conn->tmabort_state != TMABORT_INITIAL);
+ 	if (signal_pending(current))
+ 		flush_signals(current);
+-	del_timer_sync(&conn->tmf_timer);
++	del_timer_sync(&conn->tmabort_timer);
+ 
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+-	/* if the session drops it will clean up the mtask */
+-	if (age != session->age ||
+-	    session->state != ISCSI_STATE_LOGGED_IN)
+-		return -ENOTCONN;
++	mutex_lock(&conn->xmitmutex);
+ 	return 0;
+ }
+ 
+ /*
+- * Fail commands. session lock held and recv side suspended and xmit
+- * thread flushed
++ * xmit mutex and session lock must be held
+  */
+-static void fail_all_commands(struct iscsi_conn *conn, unsigned lun,
+-			      int error)
++static struct iscsi_mgmt_task *
++iscsi_remove_mgmt_task(struct kfifo *fifo, uint32_t itt)
+ {
+-	struct iscsi_cmd_task *ctask, *tmp;
++	int i, nr_tasks = __kfifo_len(fifo) / sizeof(void*);
++	struct iscsi_mgmt_task *task;
+ 
+-	if (conn->ctask && (conn->ctask->sc->device->lun == lun || lun == -1))
+-		conn->ctask = NULL;
++	debug_scsi("searching %d tasks\n", nr_tasks);
+ 
+-	/* flush pending */
+-	list_for_each_entry_safe(ctask, tmp, &conn->xmitqueue, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing pending sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, error << 16);
+-		}
+-	}
++	for (i = 0; i < nr_tasks; i++) {
++		__kfifo_get(fifo, (void*)&task, sizeof(void*));
++		debug_scsi("check task %u\n", task->itt);
+ 
+-	list_for_each_entry_safe(ctask, tmp, &conn->requeue, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing requeued sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, error << 16);
++		if (task->itt == itt) {
++			debug_scsi("matched task\n");
++			return task;
+ 		}
+-	}
+ 
+-	/* fail all other running */
+-	list_for_each_entry_safe(ctask, tmp, &conn->run_list, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing in progress sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, DID_BUS_BUSY << 16);
+-		}
++		__kfifo_put(fifo, (void*)&task, sizeof(void*));
+ 	}
++	return NULL;
+ }
+ 
+-static void iscsi_suspend_tx(struct iscsi_conn *conn)
+-{
+-	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+-	scsi_flush_work(conn->session->host);
+-}
+-
+-static void iscsi_start_tx(struct iscsi_conn *conn)
+-{
+-	clear_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-
+-static enum scsi_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd)
++static int iscsi_ctask_mtask_cleanup(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_cls_session *cls_session;
+-	struct iscsi_session *session;
+-	struct iscsi_conn *conn;
+-	enum scsi_eh_timer_return rc = EH_NOT_HANDLED;
+-
+-	cls_session = starget_to_session(scsi_target(scmd->device));
+-	session = class_to_transport_session(cls_session);
+-
+-	debug_scsi("scsi cmd %p timedout\n", scmd);
+-
+-	spin_lock(&session->lock);
+-	if (session->state != ISCSI_STATE_LOGGED_IN) {
+-		/*
+-		 * We are probably in the middle of iscsi recovery so let
+-		 * that complete and handle the error.
+-		 */
+-		rc = EH_RESET_TIMER;
+-		goto done;
+-	}
++	struct iscsi_conn *conn = ctask->conn;
++	struct iscsi_session *session = conn->session;
+ 
+-	conn = session->leadconn;
+-	if (!conn) {
+-		/* In the middle of shuting down */
+-		rc = EH_RESET_TIMER;
+-		goto done;
+-	}
++	if (!ctask->mtask)
++		return -EINVAL;
+ 
+-	if (!conn->recv_timeout && !conn->ping_timeout)
+-		goto done;
+-	/*
+-	 * if the ping timedout then we are in the middle of cleaning up
+-	 * and can let the iscsi eh handle it
+-	 */
+-	if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ) +
+-			    (conn->ping_timeout * HZ), jiffies))
+-		rc = EH_RESET_TIMER;
+-	/*
+-	 * if we are about to check the transport then give the command
+-	 * more time
+-	 */
+-	if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ),
+-			   jiffies))
+-		rc = EH_RESET_TIMER;
+-	/* if in the middle of checking the transport then give us more time */
+-	if (conn->ping_mtask)
+-		rc = EH_RESET_TIMER;
+-done:
+-	spin_unlock(&session->lock);
+-	debug_scsi("return %s\n", rc == EH_RESET_TIMER ? "timer reset" : "nh");
+-	return rc;
++	if (!iscsi_remove_mgmt_task(conn->immqueue, ctask->mtask->itt))
++		list_del(&ctask->mtask->running);
++	__kfifo_put(session->mgmtpool.queue, (void*)&ctask->mtask,
++		    sizeof(void*));
++	ctask->mtask = NULL;
++	return 0;
+ }
+ 
+-static void iscsi_check_transport_timeouts(unsigned long data)
++/*
++ * session lock and xmitmutex must be held
++ */
++static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
++			 int err)
+ {
+-	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+-	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	struct scsi_cmnd *sc;
+ 
+-	spin_lock(&session->lock);
+-	if (session->state != ISCSI_STATE_LOGGED_IN)
+-		goto done;
+-
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
+-		goto done;
+-
+-	timeout *= HZ;
+-	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
+-			   jiffies)) {
+-		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+-				  "expired, last rx %lu, last ping %lu, "
+-				  "now %lu\n", conn->ping_timeout, last_recv,
+-				  conn->last_ping, jiffies);
+-		spin_unlock(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	sc = ctask->sc;
++	if (!sc)
+ 		return;
+-	}
+-
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
+-	} else
+-		next_timeout = last_recv + timeout;
+ 
+-	debug_scsi("Setting next tmo %lu\n", next_timeout);
+-	mod_timer(&conn->transport_timer, next_timeout);
+-done:
+-	spin_unlock(&session->lock);
+-}
++	conn->session->tt->cleanup_cmd_task(conn, ctask);
++	iscsi_ctask_mtask_cleanup(ctask);
+ 
+-static void iscsi_prep_abort_task_pdu(struct iscsi_cmd_task *ctask,
+-				      struct iscsi_tm *hdr)
+-{
+-	memset(hdr, 0, sizeof(*hdr));
+-	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
+-	hdr->flags = ISCSI_TM_FUNC_ABORT_TASK & ISCSI_FLAG_TM_FUNC_MASK;
+-	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+-	memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun));
+-	hdr->rtt = ctask->hdr->itt;
+-	hdr->refcmdsn = ctask->hdr->cmdsn;
++	sc->result = err;
++	sc->resid = sc->request_bufflen;
++	/* release ref from queuecommand */
++	__iscsi_put_ctask(ctask);
+ }
+ 
+ int iscsi_eh_abort(struct scsi_cmnd *sc)
+ {
+-	struct Scsi_Host *host = sc->device->host;
+-	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+-	struct iscsi_conn *conn;
+ 	struct iscsi_cmd_task *ctask;
+-	struct iscsi_tm *hdr;
+-	int rc, age;
++	struct iscsi_conn *conn;
++	struct iscsi_session *session;
++	int rc;
+ 
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+ 	/*
+ 	 * if session was ISCSI_STATE_IN_RECOVERY then we may not have
+ 	 * got the command.
+ 	 */
+ 	if (!sc->SCp.ptr) {
+ 		debug_scsi("sc never reached iscsi layer or it completed.\n");
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return SUCCESS;
+ 	}
+ 
++	ctask = (struct iscsi_cmd_task *)sc->SCp.ptr;
++	conn = ctask->conn;
++	session = conn->session;
++
++	conn->eh_abort_cnt++;
++	debug_scsi("aborting [sc %p itt 0x%x]\n", sc, ctask->itt);
++
++	mutex_lock(&conn->xmitmutex);
++	spin_lock_bh(&session->lock);
++
+ 	/*
+ 	 * If we are not logged in or we have started a new session
+ 	 * then let the host reset code handle this
+ 	 */
+-	if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN ||
+-	    sc->SCp.phase != session->age) {
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+-		return FAILED;
+-	}
+-
+-	conn = session->leadconn;
+-	conn->eh_abort_cnt++;
+-	age = session->age;
+-
+-	ctask = (struct iscsi_cmd_task *)sc->SCp.ptr;
+-	debug_scsi("aborting [sc %p itt 0x%x]\n", sc, ctask->itt);
++	if (session->state != ISCSI_STATE_LOGGED_IN ||
++	    sc->SCp.phase != session->age)
++		goto failed;
+ 
+ 	/* ctask completed before time out */
+ 	if (!ctask->sc) {
++		spin_unlock_bh(&session->lock);
+ 		debug_scsi("sc completed while abort in progress\n");
+-		goto success;
++		goto success_rel_mutex;
+ 	}
+ 
+-	if (ctask->state == ISCSI_TASK_PENDING) {
+-		fail_command(conn, ctask, DID_ABORT << 16);
+-		goto success;
++	/* what should we do here ? */
++	if (conn->ctask == ctask) {
++		printk(KERN_INFO "iscsi: sc %p itt 0x%x partially sent. "
++		       "Failing abort\n", sc, ctask->itt);
++		goto failed;
+ 	}
+ 
+-	/* only have one tmf outstanding at a time */
+-	if (conn->tmf_state != TMF_INITIAL)
+-		goto failed;
+-	conn->tmf_state = TMF_QUEUED;
++	if (ctask->state == ISCSI_TASK_PENDING)
++		goto success_cleanup;
+ 
+-	hdr = &conn->tmhdr;
+-	iscsi_prep_abort_task_pdu(ctask, hdr);
++	conn->tmabort_state = TMABORT_INITIAL;
+ 
+-	if (iscsi_exec_task_mgmt_fn(conn, hdr, age, session->abort_timeout)) {
+-		rc = FAILED;
++	spin_unlock_bh(&session->lock);
++	rc = iscsi_exec_abort_task(sc, ctask);
++	spin_lock_bh(&session->lock);
++
++	if (rc || sc->SCp.phase != session->age ||
++	    session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto failed;
+-	}
++	iscsi_ctask_mtask_cleanup(ctask);
+ 
+-	switch (conn->tmf_state) {
+-	case TMF_SUCCESS:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_suspend_tx(conn);
+-		/*
+-		 * clean up task if aborted. grab the recv lock as a writer
+-		 */
+-		write_lock_bh(conn->recv_lock);
+-		spin_lock(&session->lock);
+-		fail_command(conn, ctask, DID_ABORT << 16);
+-		conn->tmf_state = TMF_INITIAL;
+-		spin_unlock(&session->lock);
+-		write_unlock_bh(conn->recv_lock);
+-		iscsi_start_tx(conn);
+-		goto success_unlocked;
+-	case TMF_TIMEDOUT:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		goto failed_unlocked;
+-	case TMF_NOT_FOUND:
+-		if (!sc->SCp.ptr) {
+-			conn->tmf_state = TMF_INITIAL;
++	switch (conn->tmabort_state) {
++	case TMABORT_SUCCESS:
++		goto success_cleanup;
++	case TMABORT_NOT_FOUND:
++		if (!ctask->sc) {
+ 			/* ctask completed before tmf abort response */
++			spin_unlock_bh(&session->lock);
+ 			debug_scsi("sc completed while abort in progress\n");
+-			goto success;
++			goto success_rel_mutex;
+ 		}
+ 		/* fall through */
+ 	default:
+-		conn->tmf_state = TMF_INITIAL;
++		/* timedout or failed */
++		spin_unlock_bh(&session->lock);
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++		spin_lock_bh(&session->lock);
+ 		goto failed;
+ 	}
+ 
+-success:
+-	spin_unlock_bh(&session->lock);
+-success_unlocked:
++success_cleanup:
+ 	debug_scsi("abort success [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
+-	mutex_unlock(&session->eh_mutex);
+-	return SUCCESS;
+-
+-failed:
+ 	spin_unlock_bh(&session->lock);
+-failed_unlocked:
+-	debug_scsi("abort failed [sc %p itt 0x%x]\n", sc,
+-		    ctask ? ctask->itt : 0);
+-	mutex_unlock(&session->eh_mutex);
+-	return FAILED;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_eh_abort);
+ 
+-static void iscsi_prep_lun_reset_pdu(struct scsi_cmnd *sc, struct iscsi_tm *hdr)
+-{
+-	memset(hdr, 0, sizeof(*hdr));
+-	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
+-	hdr->flags = ISCSI_TM_FUNC_LOGICAL_UNIT_RESET & ISCSI_FLAG_TM_FUNC_MASK;
+-	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+-	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+-	hdr->rtt = RESERVED_ITT;
+-}
+-
+-int iscsi_eh_device_reset(struct scsi_cmnd *sc)
+-{
+-	struct Scsi_Host *host = sc->device->host;
+-	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+-	struct iscsi_conn *conn;
+-	struct iscsi_tm *hdr;
+-	int rc = FAILED;
+-
+-	debug_scsi("LU Reset [sc %p lun %u]\n", sc, sc->device->lun);
+-
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+ 	/*
+-	 * Just check if we are not logged in. We cannot check for
+-	 * the phase because the reset could come from a ioctl.
++	 * clean up task if aborted. we have the xmitmutex so grab
++	 * the recv lock as a writer
+ 	 */
+-	if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN)
+-		goto unlock;
+-	conn = session->leadconn;
+-
+-	/* only have one tmf outstanding at a time */
+-	if (conn->tmf_state != TMF_INITIAL)
+-		goto unlock;
+-	conn->tmf_state = TMF_QUEUED;
+-
+-	hdr = &conn->tmhdr;
+-	iscsi_prep_lun_reset_pdu(sc, hdr);
+-
+-	if (iscsi_exec_task_mgmt_fn(conn, hdr, session->age,
+-				    session->lu_reset_timeout)) {
+-		rc = FAILED;
+-		goto unlock;
+-	}
+-
+-	switch (conn->tmf_state) {
+-	case TMF_SUCCESS:
+-		break;
+-	case TMF_TIMEDOUT:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		goto done;
+-	default:
+-		conn->tmf_state = TMF_INITIAL;
+-		goto unlock;
+-	}
+-
+-	rc = SUCCESS;
+-	spin_unlock_bh(&session->lock);
+-
+-	iscsi_suspend_tx(conn);
+-	/* need to grab the recv lock then session lock */
+ 	write_lock_bh(conn->recv_lock);
+ 	spin_lock(&session->lock);
+-	fail_all_commands(conn, sc->device->lun, DID_ERROR);
+-	conn->tmf_state = TMF_INITIAL;
++	fail_command(conn, ctask, DID_ABORT << 16);
+ 	spin_unlock(&session->lock);
+ 	write_unlock_bh(conn->recv_lock);
+ 
+-	iscsi_start_tx(conn);
+-	goto done;
++success_rel_mutex:
++	mutex_unlock(&conn->xmitmutex);
++	return SUCCESS;
+ 
+-unlock:
++failed:
+ 	spin_unlock_bh(&session->lock);
+-done:
+-	debug_scsi("iscsi_eh_device_reset %s\n",
+-		  rc == SUCCESS ? "SUCCESS" : "FAILED");
+-	mutex_unlock(&session->eh_mutex);
+-	return rc;
++	mutex_unlock(&conn->xmitmutex);
++
++	debug_scsi("abort failed [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
++	return FAILED;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_eh_device_reset);
++EXPORT_SYMBOL_GPL(iscsi_eh_abort);
+ 
+-/*
+- * Pre-allocate a pool of @max items of @item_size. By default, the pool
+- * should be accessed via kfifo_{get,put} on q->queue.
+- * Optionally, the caller can obtain the array of object pointers
+- * by passing in a non-NULL @items pointer
+- */
+ int
+-iscsi_pool_init(struct iscsi_pool *q, int max, void ***items, int item_size)
++iscsi_pool_init(struct iscsi_queue *q, int max, void ***items, int item_size)
+ {
+-	int i, num_arrays = 1;
++	int i;
+ 
+-	memset(q, 0, sizeof(*q));
++	*items = kmalloc(max * sizeof(void*), GFP_KERNEL);
++	if (*items == NULL)
++		return -ENOMEM;
+ 
+ 	q->max = max;
+-
+-	/* If the user passed an items pointer, he wants a copy of
+-	 * the array. */
+-	if (items)
+-		num_arrays++;
+-	q->pool = kzalloc(num_arrays * max * sizeof(void*), GFP_KERNEL);
+-	if (q->pool == NULL)
+-		goto enomem;
++	q->pool = kmalloc(max * sizeof(void*), GFP_KERNEL);
++	if (q->pool == NULL) {
++		kfree(*items);
++		return -ENOMEM;
++	}
+ 
+ 	q->queue = kfifo_init((void*)q->pool, max * sizeof(void*),
+ 			      GFP_KERNEL, NULL);
+-	if (q->queue == ERR_PTR(-ENOMEM))
+-		goto enomem;
++	if (q->queue == ERR_PTR(-ENOMEM)) {
++		kfree(q->pool);
++		kfree(*items);
++		return -ENOMEM;
++	}
+ 
+ 	for (i = 0; i < max; i++) {
+-		q->pool[i] = kzalloc(item_size, GFP_KERNEL);
++		q->pool[i] = kmalloc(item_size, GFP_KERNEL);
+ 		if (q->pool[i] == NULL) {
+-			q->max = i;
+-			goto enomem;
++			int j;
++
++			for (j = 0; j < i; j++)
++				kfree(q->pool[j]);
++
++			kfifo_free(q->queue);
++			kfree(q->pool);
++			kfree(*items);
++			return -ENOMEM;
+ 		}
++		memset(q->pool[i], 0, item_size);
++		(*items)[i] = q->pool[i];
+ 		__kfifo_put(q->queue, (void*)&q->pool[i], sizeof(void*));
+ 	}
+-
+-	if (items) {
+-		*items = q->pool + max;
+-		memcpy(*items, q->pool, max * sizeof(void *));
+-	}
+-
+ 	return 0;
+-
+-enomem:
+-	iscsi_pool_free(q);
+-	return -ENOMEM;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_pool_init);
+ 
+-void iscsi_pool_free(struct iscsi_pool *q)
++void iscsi_pool_free(struct iscsi_queue *q, void **items)
+ {
+ 	int i;
+ 
+ 	for (i = 0; i < q->max; i++)
+-		kfree(q->pool[i]);
+-	if (q->pool)
+-		kfree(q->pool);
++		kfree(items[i]);
++	kfree(q->pool);
++	kfree(items);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+ 
+@@ -1778,10 +1339,6 @@ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+  * iscsi_session_setup - create iscsi cls session and host and session
+  * @scsit: scsi transport template
+  * @iscsit: iscsi transport template
+- * @cmds_max: scsi host can queue
+- * @qdepth: scsi host cmds per lun
+- * @cmd_task_size: LLD ctask private data size
+- * @mgmt_task_size: LLD mtask private data size
+  * @initial_cmdsn: initial CmdSN
+  * @hostno: host no allocated
+  *
+@@ -1791,7 +1348,6 @@ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+ struct iscsi_cls_session *
+ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 		    struct scsi_transport_template *scsit,
+-		    uint16_t cmds_max, uint16_t qdepth,
+ 		    int cmd_task_size, int mgmt_task_size,
+ 		    uint32_t initial_cmdsn, uint32_t *hostno)
+ {
+@@ -1800,56 +1356,30 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 	struct iscsi_cls_session *cls_session;
+ 	int cmd_i;
+ 
+-	if (qdepth > ISCSI_MAX_CMD_PER_LUN || qdepth < 1) {
+-		if (qdepth != 0)
+-			printk(KERN_ERR "iscsi: invalid queue depth of %d. "
+-			      "Queue depth must be between 1 and %d.\n",
+-			      qdepth, ISCSI_MAX_CMD_PER_LUN);
+-		qdepth = ISCSI_DEF_CMD_PER_LUN;
+-	}
+-
+-	if (!is_power_of_2(cmds_max) || cmds_max >= ISCSI_MGMT_ITT_OFFSET ||
+-	    cmds_max < 2) {
+-		if (cmds_max != 0)
+-			printk(KERN_ERR "iscsi: invalid can_queue of %d. "
+-			       "can_queue must be a power of 2 and between "
+-			       "2 and %d - setting to %d.\n", cmds_max,
+-			       ISCSI_MGMT_ITT_OFFSET, ISCSI_DEF_XMIT_CMDS_MAX);
+-		cmds_max = ISCSI_DEF_XMIT_CMDS_MAX;
+-	}
+-
+ 	shost = scsi_host_alloc(iscsit->host_template,
+ 				hostdata_privsize(sizeof(*session)));
+ 	if (!shost)
+ 		return NULL;
+ 
+-	/* the iscsi layer takes one task for reserve */
+-	shost->can_queue = cmds_max - 1;
+-	shost->cmd_per_lun = qdepth;
+ 	shost->max_id = 1;
+ 	shost->max_channel = 0;
+ 	shost->max_lun = iscsit->max_lun;
+ 	shost->max_cmd_len = iscsit->max_cmd_len;
+ 	shost->transportt = scsit;
+ 	shost->transportt->create_work_queue = 1;
+-	shost->transportt->eh_timed_out = iscsi_eh_cmd_timed_out;
+ 	*hostno = shost->host_no;
+ 
+ 	session = iscsi_hostdata(shost->hostdata);
+ 	memset(session, 0, sizeof(struct iscsi_session));
+ 	session->host = shost;
+ 	session->state = ISCSI_STATE_FREE;
+-	session->fast_abort = 1;
+-	session->lu_reset_timeout = 15;
+-	session->abort_timeout = 10;
+ 	session->mgmtpool_max = ISCSI_MGMT_CMDS_MAX;
+-	session->cmds_max = cmds_max;
+-	session->queued_cmdsn = session->cmdsn = initial_cmdsn;
++	session->cmds_max = ISCSI_XMIT_CMDS_MAX;
++	session->cmdsn = initial_cmdsn;
+ 	session->exp_cmdsn = initial_cmdsn + 1;
+ 	session->max_cmdsn = initial_cmdsn + 1;
+ 	session->max_r2t = 1;
+ 	session->tt = iscsit;
+-	mutex_init(&session->eh_mutex);
+ 
+ 	/* initialize SCSI PDU commands pool */
+ 	if (iscsi_pool_init(&session->cmdpool, session->cmds_max,
+@@ -1904,9 +1434,9 @@ module_put:
+ cls_session_fail:
+ 	scsi_remove_host(shost);
+ add_host_fail:
+-	iscsi_pool_free(&session->mgmtpool);
++	iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds);
+ mgmtpool_alloc_fail:
+-	iscsi_pool_free(&session->cmdpool);
++	iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
+ cmdpool_alloc_fail:
+ 	scsi_host_put(shost);
+ 	return NULL;
+@@ -1926,22 +1456,14 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session)
+ 	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+ 	struct module *owner = cls_session->transport->owner;
+ 
+-	iscsi_remove_session(cls_session);
+ 	scsi_remove_host(shost);
+ 
+-	iscsi_pool_free(&session->mgmtpool);
+-	iscsi_pool_free(&session->cmdpool);
++	iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds);
++	iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
+ 
+-	kfree(session->password);
+-	kfree(session->password_in);
+-	kfree(session->username);
+-	kfree(session->username_in);
+ 	kfree(session->targetname);
+-	kfree(session->netdev);
+-	kfree(session->hwaddress);
+-	kfree(session->initiatorname);
+ 
+-	iscsi_free_session(cls_session);
++	iscsi_destroy_session(cls_session);
+ 	scsi_host_put(shost);
+ 	module_put(owner);
+ }
+@@ -1971,17 +1493,22 @@ iscsi_conn_setup(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 	conn->c_stage = ISCSI_CONN_INITIAL_STAGE;
+ 	conn->id = conn_idx;
+ 	conn->exp_statsn = 0;
+-	conn->tmf_state = TMF_INITIAL;
+-
+-	init_timer(&conn->transport_timer);
+-	conn->transport_timer.data = (unsigned long)conn;
+-	conn->transport_timer.function = iscsi_check_transport_timeouts;
+-
++	conn->tmabort_state = TMABORT_INITIAL;
+ 	INIT_LIST_HEAD(&conn->run_list);
+ 	INIT_LIST_HEAD(&conn->mgmt_run_list);
+-	INIT_LIST_HEAD(&conn->mgmtqueue);
+ 	INIT_LIST_HEAD(&conn->xmitqueue);
+-	INIT_LIST_HEAD(&conn->requeue);
++
++	/* initialize general immediate & non-immediate PDU commands queue */
++	conn->immqueue = kfifo_alloc(session->mgmtpool_max * sizeof(void*),
++			                GFP_KERNEL, NULL);
++	if (conn->immqueue == ERR_PTR(-ENOMEM))
++		goto immqueue_alloc_fail;
++
++	conn->mgmtqueue = kfifo_alloc(session->mgmtpool_max * sizeof(void*),
++			                GFP_KERNEL, NULL);
++	if (conn->mgmtqueue == ERR_PTR(-ENOMEM))
++		goto mgmtqueue_alloc_fail;
++
+ 	INIT_WORK(&conn->xmitwork, iscsi_xmitworker);
+ 
+ 	/* allocate login_mtask used for the login/text sequences */
+@@ -1999,7 +1526,8 @@ iscsi_conn_setup(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 		goto login_mtask_data_alloc_fail;
+ 	conn->login_mtask->data = conn->data = data;
+ 
+-	init_timer(&conn->tmf_timer);
++	init_timer(&conn->tmabort_timer);
++	mutex_init(&conn->xmitmutex);
+ 	init_waitqueue_head(&conn->ehwait);
+ 
+ 	return cls_conn;
+@@ -2008,6 +1536,10 @@ login_mtask_data_alloc_fail:
+ 	__kfifo_put(session->mgmtpool.queue, (void*)&conn->login_mtask,
+ 		    sizeof(void*));
+ login_mtask_alloc_fail:
++	kfifo_free(conn->mgmtqueue);
++mgmtqueue_alloc_fail:
++	kfifo_free(conn->immqueue);
++immqueue_alloc_fail:
+ 	iscsi_destroy_conn(cls_conn);
+ 	return NULL;
+ }
+@@ -2026,7 +1558,8 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	struct iscsi_session *session = conn->session;
+ 	unsigned long flags;
+ 
+-	del_timer_sync(&conn->transport_timer);
++	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
++	mutex_lock(&conn->xmitmutex);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	conn->c_stage = ISCSI_CONN_CLEANUP_WAIT;
+@@ -2039,6 +1572,8 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ 
++	mutex_unlock(&conn->xmitmutex);
++
+ 	/*
+ 	 * Block until all in-progress commands for this connection
+ 	 * time out or fail.
+@@ -2051,10 +1586,9 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 		}
+ 		spin_unlock_irqrestore(session->host->host_lock, flags);
+ 		msleep_interruptible(500);
+-		iscsi_conn_printk(KERN_INFO, conn, "iscsi conn_destroy(): "
+-				  "host_busy %d host_failed %d\n",
+-				  session->host->host_busy,
+-				  session->host->host_failed);
++		printk(KERN_INFO "iscsi: scsi conn_destroy(): host_busy %d "
++		       "host_failed %d\n", session->host->host_busy,
++		       session->host->host_failed);
+ 		/*
+ 		 * force eh_abort() to unblock
+ 		 */
+@@ -2062,17 +1596,23 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 
+ 	/* flush queued up work because we free the connection below */
+-	iscsi_suspend_tx(conn);
++	scsi_flush_work(session->host);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	kfree(conn->data);
+ 	kfree(conn->persistent_address);
+ 	__kfifo_put(session->mgmtpool.queue, (void*)&conn->login_mtask,
+ 		    sizeof(void*));
+-	if (session->leadconn == conn)
++	if (session->leadconn == conn) {
+ 		session->leadconn = NULL;
++		/* no connections exits.. reset sequencing */
++		session->cmdsn = session->max_cmdsn = session->exp_cmdsn = 1;
++	}
+ 	spin_unlock_bh(&session->lock);
+ 
++	kfifo_free(conn->immqueue);
++	kfifo_free(conn->mgmtqueue);
++
+ 	iscsi_destroy_conn(cls_conn);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_teardown);
+@@ -2083,41 +1623,21 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 	struct iscsi_session *session = conn->session;
+ 
+ 	if (!session) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "can't start unbound connection\n");
++		printk(KERN_ERR "iscsi: can't start unbound connection\n");
+ 		return -EPERM;
+ 	}
+ 
+ 	if ((session->imm_data_en || !session->initial_r2t_en) &&
+ 	     session->first_burst > session->max_burst) {
+-		iscsi_conn_printk(KERN_INFO, conn, "invalid burst lengths: "
+-				  "first_burst %d max_burst %d\n",
+-				  session->first_burst, session->max_burst);
++		printk("iscsi: invalid burst lengths: "
++		       "first_burst %d max_burst %d\n",
++		       session->first_burst, session->max_burst);
+ 		return -EINVAL;
+ 	}
+ 
+-	if (conn->ping_timeout && !conn->recv_timeout) {
+-		iscsi_conn_printk(KERN_ERR, conn, "invalid recv timeout of "
+-				  "zero. Using 5 seconds\n.");
+-		conn->recv_timeout = 5;
+-	}
+-
+-	if (conn->recv_timeout && !conn->ping_timeout) {
+-		iscsi_conn_printk(KERN_ERR, conn, "invalid ping timeout of "
+-				  "zero. Using 5 seconds.\n");
+-		conn->ping_timeout = 5;
+-	}
+-
+ 	spin_lock_bh(&session->lock);
+ 	conn->c_stage = ISCSI_CONN_STARTED;
+ 	session->state = ISCSI_STATE_LOGGED_IN;
+-	session->queued_cmdsn = session->cmdsn;
+-
+-	conn->last_recv = jiffies;
+-	conn->last_ping = jiffies;
+-	if (conn->recv_timeout && conn->ping_timeout)
+-		mod_timer(&conn->transport_timer,
+-			  jiffies + (conn->recv_timeout * HZ));
+ 
+ 	switch(conn->stop_stage) {
+ 	case STOP_CONN_RECOVER:
+@@ -2126,11 +1646,13 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 		 * commands after successful recovery
+ 		 */
+ 		conn->stop_stage = 0;
+-		conn->tmf_state = TMF_INITIAL;
++		conn->tmabort_state = TMABORT_INITIAL;
+ 		session->age++;
+-		if (session->age == 16)
+-			session->age = 0;
+-		break;
++		spin_unlock_bh(&session->lock);
++
++		iscsi_unblock_session(session_to_cls(session));
++		wake_up(&conn->ehwait);
++		return 0;
+ 	case STOP_CONN_TERM:
+ 		conn->stop_stage = 0;
+ 		break;
+@@ -2139,8 +1661,6 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ 
+-	iscsi_unblock_session(session_to_cls(session));
+-	wake_up(&conn->ehwait);
+ 	return 0;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_start);
+@@ -2151,43 +1671,59 @@ flush_control_queues(struct iscsi_session *session, struct iscsi_conn *conn)
+ 	struct iscsi_mgmt_task *mtask, *tmp;
+ 
+ 	/* handle pending */
+-	list_for_each_entry_safe(mtask, tmp, &conn->mgmtqueue, running) {
++	while (__kfifo_get(conn->immqueue, (void*)&mtask, sizeof(void*)) ||
++	       __kfifo_get(conn->mgmtqueue, (void*)&mtask, sizeof(void*))) {
++		if (mtask == conn->login_mtask)
++			continue;
+ 		debug_scsi("flushing pending mgmt task itt 0x%x\n", mtask->itt);
+-		iscsi_free_mgmt_task(conn, mtask);
++		__kfifo_put(session->mgmtpool.queue, (void*)&mtask,
++			    sizeof(void*));
+ 	}
+ 
+ 	/* handle running */
+ 	list_for_each_entry_safe(mtask, tmp, &conn->mgmt_run_list, running) {
+ 		debug_scsi("flushing running mgmt task itt 0x%x\n", mtask->itt);
+-		iscsi_free_mgmt_task(conn, mtask);
++		list_del(&mtask->running);
++
++		if (mtask == conn->login_mtask)
++			continue;
++		__kfifo_put(session->mgmtpool.queue, (void*)&mtask,
++			   sizeof(void*));
+ 	}
+ 
+ 	conn->mtask = NULL;
+ }
+ 
++/* Fail commands. Mutex and session lock held and recv side suspended */
++static void fail_all_commands(struct iscsi_conn *conn)
++{
++	struct iscsi_cmd_task *ctask, *tmp;
++
++	/* flush pending */
++	list_for_each_entry_safe(ctask, tmp, &conn->xmitqueue, running) {
++		debug_scsi("failing pending sc %p itt 0x%x\n", ctask->sc,
++			   ctask->itt);
++		fail_command(conn, ctask, DID_BUS_BUSY << 16);
++	}
++
++	/* fail all other running */
++	list_for_each_entry_safe(ctask, tmp, &conn->run_list, running) {
++		debug_scsi("failing in progress sc %p itt 0x%x\n",
++			   ctask->sc, ctask->itt);
++		fail_command(conn, ctask, DID_BUS_BUSY << 16);
++	}
++
++	conn->ctask = NULL;
++}
++
+ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 					 struct iscsi_conn *conn, int flag)
+ {
+ 	int old_stop_stage;
+ 
+-	del_timer_sync(&conn->transport_timer);
+-
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (conn->stop_stage == STOP_CONN_TERM) {
+ 		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+-		return;
+-	}
+-
+-	/*
+-	 * The LLD either freed/unset the lock on us, or userspace called
+-	 * stop but did not create a proper connection (connection was never
+-	 * bound or it was unbound then stop was called).
+-	 */
+-	if (!conn->recv_lock) {
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return;
+ 	}
+ 
+@@ -2204,14 +1740,14 @@ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 	old_stop_stage = conn->stop_stage;
+ 	conn->stop_stage = flag;
+ 	conn->c_stage = ISCSI_CONN_STOPPED;
++	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+ 	spin_unlock_bh(&session->lock);
+ 
+-	iscsi_suspend_tx(conn);
+-
+ 	write_lock_bh(conn->recv_lock);
+ 	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
+ 	write_unlock_bh(conn->recv_lock);
+ 
++	mutex_lock(&conn->xmitmutex);
+ 	/*
+ 	 * for connection level recovery we should not calculate
+ 	 * header digest. conn->hdr_size used for optimization
+@@ -2232,11 +1768,11 @@ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 	 * flush queues.
+ 	 */
+ 	spin_lock_bh(&session->lock);
+-	fail_all_commands(conn, -1,
+-			STOP_CONN_RECOVER ? DID_BUS_BUSY : DID_ERROR);
++	fail_all_commands(conn);
+ 	flush_control_queues(session, conn);
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
++	mutex_unlock(&conn->xmitmutex);
+ }
+ 
+ void iscsi_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+@@ -2250,8 +1786,7 @@ void iscsi_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+ 		iscsi_start_session_recovery(session, conn, flag);
+ 		break;
+ 	default:
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid stop flag %d\n", flag);
++		printk(KERN_ERR "iscsi: invalid stop flag %d\n", flag);
+ 	}
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_stop);
+@@ -2285,21 +1820,6 @@ int iscsi_set_param(struct iscsi_cls_conn *cls_conn,
+ 	uint32_t value;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_FAST_ABORT:
+-		sscanf(buf, "%d", &session->fast_abort);
+-		break;
+-	case ISCSI_PARAM_ABORT_TMO:
+-		sscanf(buf, "%d", &session->abort_timeout);
+-		break;
+-	case ISCSI_PARAM_LU_RESET_TMO:
+-		sscanf(buf, "%d", &session->lu_reset_timeout);
+-		break;
+-	case ISCSI_PARAM_PING_TMO:
+-		sscanf(buf, "%d", &conn->ping_timeout);
+-		break;
+-	case ISCSI_PARAM_RECV_TMO:
+-		sscanf(buf, "%d", &conn->recv_timeout);
+-		break;
+ 	case ISCSI_PARAM_MAX_RECV_DLENGTH:
+ 		sscanf(buf, "%d", &conn->max_recv_dlength);
+ 		break;
+@@ -2347,30 +1867,6 @@ int iscsi_set_param(struct iscsi_cls_conn *cls_conn,
+ 	case ISCSI_PARAM_EXP_STATSN:
+ 		sscanf(buf, "%u", &conn->exp_statsn);
+ 		break;
+-	case ISCSI_PARAM_USERNAME:
+-		kfree(session->username);
+-		session->username = kstrdup(buf, GFP_KERNEL);
+-		if (!session->username)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_USERNAME_IN:
+-		kfree(session->username_in);
+-		session->username_in = kstrdup(buf, GFP_KERNEL);
+-		if (!session->username_in)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_PASSWORD:
+-		kfree(session->password);
+-		session->password = kstrdup(buf, GFP_KERNEL);
+-		if (!session->password)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_PASSWORD_IN:
+-		kfree(session->password_in);
+-		session->password_in = kstrdup(buf, GFP_KERNEL);
+-		if (!session->password_in)
+-			return -ENOMEM;
+-		break;
+ 	case ISCSI_PARAM_TARGET_NAME:
+ 		/* this should not change between logins */
+ 		if (session->targetname)
+@@ -2414,15 +1910,6 @@ int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ 	int len;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_FAST_ABORT:
+-		len = sprintf(buf, "%d\n", session->fast_abort);
+-		break;
+-	case ISCSI_PARAM_ABORT_TMO:
+-		len = sprintf(buf, "%d\n", session->abort_timeout);
+-		break;
+-	case ISCSI_PARAM_LU_RESET_TMO:
+-		len = sprintf(buf, "%d\n", session->lu_reset_timeout);
+-		break;
+ 	case ISCSI_PARAM_INITIAL_R2T_EN:
+ 		len = sprintf(buf, "%d\n", session->initial_r2t_en);
+ 		break;
+@@ -2453,18 +1940,6 @@ int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ 	case ISCSI_PARAM_TPGT:
+ 		len = sprintf(buf, "%d\n", session->tpgt);
+ 		break;
+-	case ISCSI_PARAM_USERNAME:
+-		len = sprintf(buf, "%s\n", session->username);
+-		break;
+-	case ISCSI_PARAM_USERNAME_IN:
+-		len = sprintf(buf, "%s\n", session->username_in);
+-		break;
+-	case ISCSI_PARAM_PASSWORD:
+-		len = sprintf(buf, "%s\n", session->password);
+-		break;
+-	case ISCSI_PARAM_PASSWORD_IN:
+-		len = sprintf(buf, "%s\n", session->password_in);
+-		break;
+ 	default:
+ 		return -ENOSYS;
+ 	}
+@@ -2480,12 +1955,6 @@ int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 	int len;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_PING_TMO:
+-		len = sprintf(buf, "%u\n", conn->ping_timeout);
+-		break;
+-	case ISCSI_PARAM_RECV_TMO:
+-		len = sprintf(buf, "%u\n", conn->recv_timeout);
+-		break;
+ 	case ISCSI_PARAM_MAX_RECV_DLENGTH:
+ 		len = sprintf(buf, "%u\n", conn->max_recv_dlength);
+ 		break;
+@@ -2521,66 +1990,6 @@ int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_get_param);
+ 
+-int iscsi_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf)
+-{
+-	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-	int len;
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_NETDEV_NAME:
+-		if (!session->netdev)
+-			len = sprintf(buf, "%s\n", "default");
+-		else
+-			len = sprintf(buf, "%s\n", session->netdev);
+-		break;
+-	case ISCSI_HOST_PARAM_HWADDRESS:
+-		if (!session->hwaddress)
+-			len = sprintf(buf, "%s\n", "default");
+-		else
+-			len = sprintf(buf, "%s\n", session->hwaddress);
+-		break;
+-	case ISCSI_HOST_PARAM_INITIATOR_NAME:
+-		if (!session->initiatorname)
+-			len = sprintf(buf, "%s\n", "unknown");
+-		else
+-			len = sprintf(buf, "%s\n", session->initiatorname);
+-		break;
+-
+-	default:
+-		return -ENOSYS;
+-	}
+-
+-	return len;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_host_get_param);
+-
+-int iscsi_host_set_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf, int buflen)
+-{
+-	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_NETDEV_NAME:
+-		if (!session->netdev)
+-			session->netdev = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	case ISCSI_HOST_PARAM_HWADDRESS:
+-		if (!session->hwaddress)
+-			session->hwaddress = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	case ISCSI_HOST_PARAM_INITIATOR_NAME:
+-		if (!session->initiatorname)
+-			session->initiatorname = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	default:
+-		return -ENOSYS;
+-	}
+-
+-	return 0;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_host_set_param);
+-
+ MODULE_AUTHOR("Mike Christie");
+ MODULE_DESCRIPTION("iSCSI library functions");
+ MODULE_LICENSE("GPL");
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..cdedee5 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -30,27 +30,26 @@
+ #include <scsi/scsi_transport_iscsi.h>
+ #include <scsi/iscsi_if.h>
+ 
+-#define ISCSI_SESSION_ATTRS 19
+-#define ISCSI_CONN_ATTRS 13
+-#define ISCSI_HOST_ATTRS 4
+-#define ISCSI_TRANSPORT_VERSION "2.0-869"
++#define ISCSI_SESSION_ATTRS 11
++#define ISCSI_CONN_ATTRS 11
++#define ISCSI_HOST_ATTRS 0
++#define ISCSI_TRANSPORT_VERSION "2.0-724"
+ 
+ struct iscsi_internal {
+ 	int daemon_pid;
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+-static struct workqueue_struct *iscsi_eh_timer_workq;
+ 
+ /*
+  * list of registered transports and lock that must
+@@ -63,12 +62,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +77,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +103,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -118,10 +115,8 @@ static struct attribute_group iscsi_transport_group = {
+ 	.attrs = iscsi_transport_attrs,
+ };
+ 
+-
+-
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -129,31 +124,13 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ 	memset(ihost, 0, sizeof(*ihost));
+ 	INIT_LIST_HEAD(&ihost->sessions);
+ 	mutex_init(&ihost->mutex);
+-	atomic_set(&ihost->nr_scans, 0);
+-
+-	snprintf(ihost->scan_workq_name, KOBJ_NAME_LEN, "iscsi_scan_%d",
+-		shost->host_no);
+-	ihost->scan_workq = create_singlethread_workqueue(
+-						ihost->scan_workq_name);
+-	if (!ihost->scan_workq)
+-		return -ENOMEM;
+-	return 0;
+-}
+-
+-static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
+-{
+-	struct Scsi_Host *shost = dev_to_shost(dev);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	destroy_workqueue(ihost->scan_workq);
+ 	return 0;
+ }
+ 
+ static DECLARE_TRANSPORT_CLASS(iscsi_host_class,
+ 			       "iscsi_host",
+ 			       iscsi_setup_host,
+-			       iscsi_remove_host,
++			       NULL,
+ 			       NULL);
+ 
+ static DECLARE_TRANSPORT_CLASS(iscsi_session_class,
+@@ -224,54 +201,6 @@ static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid)
+  * The following functions can be used by LLDs that allocate
+  * their own scsi_hosts or by software iscsi LLDs
+  */
+-static struct {
+-	int value;
+-	char *name;
+-} iscsi_session_state_names[] = {
+-	{ ISCSI_SESSION_LOGGED_IN,	"LOGGED_IN" },
+-	{ ISCSI_SESSION_FAILED,		"FAILED" },
+-	{ ISCSI_SESSION_FREE,		"FREE" },
+-};
+-
+-static const char *iscsi_session_state_name(int state)
+-{
+-	int i;
+-	char *name = NULL;
+-
+-	for (i = 0; i < ARRAY_SIZE(iscsi_session_state_names); i++) {
+-		if (iscsi_session_state_names[i].value == state) {
+-			name = iscsi_session_state_names[i].name;
+-			break;
+-		}
+-	}
+-	return name;
+-}
+-
+-int iscsi_session_chkready(struct iscsi_cls_session *session)
+-{
+-	unsigned long flags;
+-	int err;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	switch (session->state) {
+-	case ISCSI_SESSION_LOGGED_IN:
+-		err = 0;
+-		break;
+-	case ISCSI_SESSION_FAILED:
+-		err = DID_IMM_RETRY << 16;
+-		break;
+-	case ISCSI_SESSION_FREE:
+-		err = DID_NO_CONNECT << 16;
+-		break;
+-	default:
+-		err = DID_NO_CONNECT << 16;
+-		break;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	return err;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_session_chkready);
+-
+ static void iscsi_session_release(struct device *dev)
+ {
+ 	struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
+@@ -287,25 +216,6 @@ static int iscsi_is_session_dev(const struct device *dev)
+ 	return dev->release == iscsi_session_release;
+ }
+ 
+-/**
+- * iscsi_scan_finished - helper to report when running scans are done
+- * @shost: scsi host
+- * @time: scan run time
+- *
+- * This function can be used by drives like qla4xxx to report to the scsi
+- * layer when the scans it kicked off at module load time are done.
+- */
+-int iscsi_scan_finished(struct Scsi_Host *shost, unsigned long time)
+-{
+-	struct iscsi_host *ihost = shost->shost_data;
+-	/*
+-	 * qla4xxx will have kicked off some session unblocks before calling
+-	 * scsi_scan_host, so just wait for them to complete.
+-	 */
+-	return !atomic_read(&ihost->nr_scans);
+-}
+-EXPORT_SYMBOL_GPL(iscsi_scan_finished);
+-
+ static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+ 			   uint id, uint lun)
+ {
+@@ -324,50 +234,14 @@ static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+ 	return 0;
+ }
+ 
+-static void iscsi_scan_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session, scan_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	if (session->state != ISCSI_SESSION_LOGGED_IN) {
+-		spin_unlock_irqrestore(&session->lock, flags);
+-		goto done;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
+-
+-	scsi_scan_target(&session->dev, 0, session->target_id,
+-			 SCAN_WILD_CARD, 1);
+-done:
+-	atomic_dec(&ihost->nr_scans);
+-}
+-
+ static void session_recovery_timedout(struct work_struct *work)
+ {
+ 	struct iscsi_cls_session *session =
+ 		container_of(work, struct iscsi_cls_session,
+ 			     recovery_work.work);
+-	unsigned long flags;
+-
+-	iscsi_cls_session_printk(KERN_INFO, session,
+-				 "session recovery timed out after %d secs\n",
+-				 session->recovery_tmo);
+ 
+-	spin_lock_irqsave(&session->lock, flags);
+-	switch (session->state) {
+-	case ISCSI_SESSION_FAILED:
+-		session->state = ISCSI_SESSION_FREE;
+-		break;
+-	case ISCSI_SESSION_LOGGED_IN:
+-	case ISCSI_SESSION_FREE:
+-		/* we raced with the unblock's flush */
+-		spin_unlock_irqrestore(&session->lock, flags);
+-		return;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
++	dev_printk(KERN_INFO, &session->dev, "iscsi: session recovery timed "
++		  "out after %d secs\n", session->recovery_tmo);
+ 
+ 	if (session->transport->session_recovery_timedout)
+ 		session->transport->session_recovery_timedout(session);
+@@ -375,103 +249,22 @@ static void session_recovery_timedout(struct work_struct *work)
+ 	scsi_target_unblock(&session->dev);
+ }
+ 
+-static void __iscsi_unblock_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     unblock_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-
+-	/*
+-	 * The recovery and unblock work get run from the same workqueue,
+-	 * so try to cancel it if it was going to run after this unblock.
+-	 */
+-	cancel_delayed_work(&session->recovery_work);
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_LOGGED_IN;
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	/* start IO */
+-	scsi_target_unblock(&session->dev);
+-	/*
+-	 * Only do kernel scanning if the driver is properly hooked into
+-	 * the async scanning code (drivers like iscsi_tcp do login and
+-	 * scanning from userspace).
+-	 */
+-	if (shost->hostt->scan_finished) {
+-		if (queue_work(ihost->scan_workq, &session->scan_work))
+-			atomic_inc(&ihost->nr_scans);
+-	}
+-}
+-
+-/**
+- * iscsi_unblock_session - set a session as logged in and start IO.
+- * @session: iscsi session
+- *
+- * Mark a session as ready to accept IO.
+- */
+ void iscsi_unblock_session(struct iscsi_cls_session *session)
+ {
+-	queue_work(iscsi_eh_timer_workq, &session->unblock_work);
+-	/*
+-	 * make sure all the events have completed before tell the driver
+-	 * it is safe
+-	 */
+-	flush_workqueue(iscsi_eh_timer_workq);
++	if (!cancel_delayed_work(&session->recovery_work))
++		flush_scheduled_work();
++	scsi_target_unblock(&session->dev);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_unblock_session);
+ 
+-static void __iscsi_block_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     block_work);
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_FAILED;
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	scsi_target_block(&session->dev);
+-	queue_delayed_work(iscsi_eh_timer_workq, &session->recovery_work,
+-			   session->recovery_tmo * HZ);
+-}
+-
+ void iscsi_block_session(struct iscsi_cls_session *session)
+ {
+-	queue_work(iscsi_eh_timer_workq, &session->block_work);
++	scsi_target_block(&session->dev);
++	schedule_delayed_work(&session->recovery_work,
++			     session->recovery_tmo * HZ);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_block_session);
+ 
+-static void __iscsi_unbind_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     unbind_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	/* Prevent new scans and make sure scanning is not in progress */
+-	mutex_lock(&ihost->mutex);
+-	if (list_empty(&session->host_list)) {
+-		mutex_unlock(&ihost->mutex);
+-		return;
+-	}
+-	list_del_init(&session->host_list);
+-	mutex_unlock(&ihost->mutex);
+-
+-	scsi_remove_target(&session->dev);
+-	iscsi_session_event(session, ISCSI_KEVENT_UNBIND_SESSION);
+-}
+-
+-static int iscsi_unbind_session(struct iscsi_cls_session *session)
+-{
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	return queue_work(ihost->scan_workq, &session->unbind_work);
+-}
+-
+ struct iscsi_cls_session *
+ iscsi_alloc_session(struct Scsi_Host *shost,
+ 		    struct iscsi_transport *transport)
+@@ -485,15 +278,9 @@ iscsi_alloc_session(struct Scsi_Host *shost,
+ 
+ 	session->transport = transport;
+ 	session->recovery_tmo = 120;
+-	session->state = ISCSI_SESSION_FREE;
+ 	INIT_DELAYED_WORK(&session->recovery_work, session_recovery_timedout);
+ 	INIT_LIST_HEAD(&session->host_list);
+ 	INIT_LIST_HEAD(&session->sess_list);
+-	INIT_WORK(&session->unblock_work, __iscsi_unblock_session);
+-	INIT_WORK(&session->block_work, __iscsi_block_session);
+-	INIT_WORK(&session->unbind_work, __iscsi_unbind_session);
+-	INIT_WORK(&session->scan_work, iscsi_scan_session);
+-	spin_lock_init(&session->lock);
+ 
+ 	/* this is released in the dev's release function */
+ 	scsi_host_get(shost);
+@@ -510,7 +297,6 @@ int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
+ {
+ 	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+ 	struct iscsi_host *ihost;
+-	unsigned long flags;
+ 	int err;
+ 
+ 	ihost = shost->shost_data;
+@@ -521,21 +307,15 @@ int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
+ 		 session->sid);
+ 	err = device_add(&session->dev);
+ 	if (err) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "could not register session's dev\n");
++		dev_printk(KERN_ERR, &session->dev, "iscsi: could not "
++			   "register session's dev\n");
+ 		goto release_host;
+ 	}
+ 	transport_register_device(&session->dev);
+ 
+-	spin_lock_irqsave(&sesslock, flags);
+-	list_add(&session->sess_list, &sesslist);
+-	spin_unlock_irqrestore(&sesslock, flags);
+-
+ 	mutex_lock(&ihost->mutex);
+ 	list_add(&session->host_list, &ihost->sessions);
+ 	mutex_unlock(&ihost->mutex);
+-
+-	iscsi_session_event(session, ISCSI_KEVENT_CREATE_SESSION);
+ 	return 0;
+ 
+ release_host:
+@@ -548,10 +328,9 @@ EXPORT_SYMBOL_GPL(iscsi_add_session);
+  * iscsi_create_session - create iscsi class session
+  * @shost: scsi host
+  * @transport: iscsi transport
+- * @target_id: which target
+  *
+  * This can be called from a LLD or iscsi_transport.
+- */
++ **/
+ struct iscsi_cls_session *
+ iscsi_create_session(struct Scsi_Host *shost,
+ 		     struct iscsi_transport *transport,
+@@ -571,65 +350,19 @@ iscsi_create_session(struct Scsi_Host *shost,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_create_session);
+ 
+-static void iscsi_conn_release(struct device *dev)
+-{
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev);
+-	struct device *parent = conn->dev.parent;
+-
+-	kfree(conn);
+-	put_device(parent);
+-}
+-
+-static int iscsi_is_conn_dev(const struct device *dev)
+-{
+-	return dev->release == iscsi_conn_release;
+-}
+-
+-static int iscsi_iter_destroy_conn_fn(struct device *dev, void *data)
+-{
+-	if (!iscsi_is_conn_dev(dev))
+-		return 0;
+-	return iscsi_destroy_conn(iscsi_dev_to_conn(dev));
+-}
+-
+ void iscsi_remove_session(struct iscsi_cls_session *session)
+ {
+ 	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+ 	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-	int err;
+-
+-	spin_lock_irqsave(&sesslock, flags);
+-	list_del(&session->sess_list);
+-	spin_unlock_irqrestore(&sesslock, flags);
+ 
+-	/* make sure there are no blocks/unblocks queued */
+-	flush_workqueue(iscsi_eh_timer_workq);
+-	/* make sure the timedout callout is not running */
+ 	if (!cancel_delayed_work(&session->recovery_work))
+-		flush_workqueue(iscsi_eh_timer_workq);
+-	/*
+-	 * If we are blocked let commands flow again. The lld or iscsi
+-	 * layer should set up the queuecommand to fail commands.
+-	 * We assume that LLD will not be calling block/unblock while
+-	 * removing the session.
+-	 */
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_FREE;
+-	spin_unlock_irqrestore(&session->lock, flags);
++		flush_scheduled_work();
+ 
+-	scsi_target_unblock(&session->dev);
+-	/* flush running scans then delete devices */
+-	flush_workqueue(ihost->scan_workq);
+-	__iscsi_unbind_session(&session->unbind_work);
++	mutex_lock(&ihost->mutex);
++	list_del(&session->host_list);
++	mutex_unlock(&ihost->mutex);
+ 
+-	/* hw iscsi may not have removed all connections from session */
+-	err = device_for_each_child(&session->dev, NULL,
+-				    iscsi_iter_destroy_conn_fn);
+-	if (err)
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Could not delete all connections "
+-					 "for session. Error %d.\n", err);
++	scsi_remove_target(&session->dev);
+ 
+ 	transport_unregister_device(&session->dev);
+ 	device_del(&session->dev);
+@@ -638,9 +371,9 @@ EXPORT_SYMBOL_GPL(iscsi_remove_session);
+ 
+ void iscsi_free_session(struct iscsi_cls_session *session)
+ {
+-	iscsi_session_event(session, ISCSI_KEVENT_DESTROY_SESSION);
+ 	put_device(&session->dev);
+ }
++
+ EXPORT_SYMBOL_GPL(iscsi_free_session);
+ 
+ /**
+@@ -649,7 +382,7 @@ EXPORT_SYMBOL_GPL(iscsi_free_session);
+  *
+  * Can be called by a LLD or iscsi_transport. There must not be
+  * any running connections.
+- */
++ **/
+ int iscsi_destroy_session(struct iscsi_cls_session *session)
+ {
+ 	iscsi_remove_session(session);
+@@ -658,6 +391,20 @@ int iscsi_destroy_session(struct iscsi_cls_session *session)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_destroy_session);
+ 
++static void iscsi_conn_release(struct device *dev)
++{
++	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev);
++	struct device *parent = conn->dev.parent;
++
++	kfree(conn);
++	put_device(parent);
++}
++
++static int iscsi_is_conn_dev(const struct device *dev)
++{
++	return dev->release == iscsi_conn_release;
++}
++
+ /**
+  * iscsi_create_conn - create iscsi class connection
+  * @session: iscsi cls session
+@@ -671,13 +418,12 @@ EXPORT_SYMBOL_GPL(iscsi_destroy_session);
+  * for software iscsi we could be trying to preallocate a connection struct
+  * in which case there could be two connection structs and cid would be
+  * non-zero.
+- */
++ **/
+ struct iscsi_cls_conn *
+ iscsi_create_conn(struct iscsi_cls_session *session, uint32_t cid)
+ {
+ 	struct iscsi_transport *transport = session->transport;
+ 	struct iscsi_cls_conn *conn;
+-	unsigned long flags;
+ 	int err;
+ 
+ 	conn = kzalloc(sizeof(*conn) + transport->conndata_size, GFP_KERNEL);
+@@ -701,16 +447,11 @@ iscsi_create_conn(struct iscsi_cls_session *session, uint32_t cid)
+ 	conn->dev.release = iscsi_conn_release;
+ 	err = device_register(&conn->dev);
+ 	if (err) {
+-		iscsi_cls_session_printk(KERN_ERR, session, "could not "
+-					 "register connection's dev\n");
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: could not register "
++			   "connection's dev\n");
+ 		goto release_parent_ref;
+ 	}
+ 	transport_register_device(&conn->dev);
+-
+-	spin_lock_irqsave(&connlock, flags);
+-	list_add(&conn->conn_list, &connlist);
+-	conn->active = 1;
+-	spin_unlock_irqrestore(&connlock, flags);
+ 	return conn;
+ 
+ release_parent_ref:
+@@ -724,23 +465,17 @@ EXPORT_SYMBOL_GPL(iscsi_create_conn);
+ 
+ /**
+  * iscsi_destroy_conn - destroy iscsi class connection
+- * @conn: iscsi cls session
++ * @session: iscsi cls session
+  *
+  * This can be called from a LLD or iscsi_transport.
+- */
++ **/
+ int iscsi_destroy_conn(struct iscsi_cls_conn *conn)
+ {
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&connlock, flags);
+-	conn->active = 0;
+-	list_del(&conn->conn_list);
+-	spin_unlock_irqrestore(&connlock, flags);
+-
+ 	transport_unregister_device(&conn->dev);
+ 	device_unregister(&conn->dev);
+ 	return 0;
+ }
++
+ EXPORT_SYMBOL_GPL(iscsi_destroy_conn);
+ 
+ /*
+@@ -809,8 +544,8 @@ int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+ 	if (!skb) {
+ 		iscsi_conn_error(conn, ISCSI_ERR_CONN_FAILED);
+-		iscsi_cls_conn_printk(KERN_ERR, conn, "can not deliver "
+-				      "control PDU: OOM\n");
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: can not deliver "
++			   "control PDU: OOM\n");
+ 		return -ENOMEM;
+ 	}
+ 
+@@ -843,8 +578,8 @@ void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
+ 
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+ 	if (!skb) {
+-		iscsi_cls_conn_printk(KERN_ERR, conn, "gracefully ignored "
+-				      "conn error (%d)\n", error);
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: gracefully ignored "
++			  "conn error (%d)\n", error);
+ 		return;
+ 	}
+ 
+@@ -858,8 +593,8 @@ void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
+ 
+ 	iscsi_broadcast_skb(skb, GFP_ATOMIC);
+ 
+-	iscsi_cls_conn_printk(KERN_INFO, conn, "detected conn error (%d)\n",
+-			      error);
++	dev_printk(KERN_INFO, &conn->dev, "iscsi: detected conn error (%d)\n",
++		   error);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_error);
+ 
+@@ -874,10 +609,12 @@ iscsi_if_send_reply(int pid, int seq, int type, int done, int multi,
+ 	int t = done ? NLMSG_DONE : type;
+ 
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+-	if (!skb) {
+-		printk(KERN_ERR "Could not allocate skb to send reply.\n");
+-		return -ENOMEM;
+-	}
++	/*
++	 * FIXME:
++	 * user is supposed to react on iferror == -ENOMEM;
++	 * see iscsi_if_rx().
++	 */
++	BUG_ON(!skb);
+ 
+ 	nlh = __nlmsg_put(skb, pid, seq, t, (len - sizeof(*nlh)), 0);
+ 	nlh->nlmsg_flags = flags;
+@@ -914,8 +651,8 @@ iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+ 
+ 		skbstat = alloc_skb(len, GFP_ATOMIC);
+ 		if (!skbstat) {
+-			iscsi_cls_conn_printk(KERN_ERR, conn, "can not "
+-					      "deliver stats: OOM\n");
++			dev_printk(KERN_ERR, &conn->dev, "iscsi: can not "
++				   "deliver stats: OOM\n");
+ 			return -ENOMEM;
+ 		}
+ 
+@@ -950,87 +687,144 @@ iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+ }
+ 
+ /**
+- * iscsi_session_event - send session destr. completion event
+- * @session: iscsi class session
+- * @event: type of event
+- */
+-int iscsi_session_event(struct iscsi_cls_session *session,
+-			enum iscsi_uevent_e event)
++ * iscsi_if_destroy_session_done - send session destr. completion event
++ * @conn: last connection for session
++ *
++ * This is called by HW iscsi LLDs to notify userpsace that its HW has
++ * removed a session.
++ **/
++int iscsi_if_destroy_session_done(struct iscsi_cls_conn *conn)
+ {
+ 	struct iscsi_internal *priv;
++	struct iscsi_cls_session *session;
+ 	struct Scsi_Host *shost;
+ 	struct iscsi_uevent *ev;
+ 	struct sk_buff  *skb;
+ 	struct nlmsghdr *nlh;
++	unsigned long flags;
+ 	int rc, len = NLMSG_SPACE(sizeof(*ev));
+ 
+-	priv = iscsi_if_transport_lookup(session->transport);
++	priv = iscsi_if_transport_lookup(conn->transport);
+ 	if (!priv)
+ 		return -EINVAL;
++
++	session = iscsi_dev_to_session(conn->dev.parent);
+ 	shost = iscsi_session_to_shost(session);
+ 
+ 	skb = alloc_skb(len, GFP_KERNEL);
+ 	if (!skb) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Cannot notify userspace of session "
+-					 "event %u\n", event);
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event\n");
+ 		return -ENOMEM;
+ 	}
+ 
+ 	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
+ 	ev = NLMSG_DATA(nlh);
+-	ev->transport_handle = iscsi_handle(session->transport);
++	ev->transport_handle = iscsi_handle(conn->transport);
++	ev->type = ISCSI_KEVENT_DESTROY_SESSION;
++	ev->r.d_session.host_no = shost->host_no;
++	ev->r.d_session.sid = session->sid;
+ 
+-	ev->type = event;
+-	switch (event) {
+-	case ISCSI_KEVENT_DESTROY_SESSION:
+-		ev->r.d_session.host_no = shost->host_no;
+-		ev->r.d_session.sid = session->sid;
+-		break;
+-	case ISCSI_KEVENT_CREATE_SESSION:
+-		ev->r.c_session_ret.host_no = shost->host_no;
+-		ev->r.c_session_ret.sid = session->sid;
+-		break;
+-	case ISCSI_KEVENT_UNBIND_SESSION:
+-		ev->r.unbind_session.host_no = shost->host_no;
+-		ev->r.unbind_session.sid = session->sid;
+-		break;
+-	default:
+-		iscsi_cls_session_printk(KERN_ERR, session, "Invalid event "
+-					 "%u.\n", event);
+-		kfree_skb(skb);
++	/*
++	 * this will occur if the daemon is not up, so we just warn
++	 * the user and when the daemon is restarted it will handle it
++	 */
++	rc = iscsi_broadcast_skb(skb, GFP_KERNEL);
++	if (rc < 0)
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session destruction event. Check iscsi daemon\n");
++
++	spin_lock_irqsave(&sesslock, flags);
++	list_del(&session->sess_list);
++	spin_unlock_irqrestore(&sesslock, flags);
++
++	spin_lock_irqsave(&connlock, flags);
++	conn->active = 0;
++	list_del(&conn->conn_list);
++	spin_unlock_irqrestore(&connlock, flags);
++
++	return rc;
++}
++EXPORT_SYMBOL_GPL(iscsi_if_destroy_session_done);
++
++/**
++ * iscsi_if_create_session_done - send session creation completion event
++ * @conn: leading connection for session
++ *
++ * This is called by HW iscsi LLDs to notify userpsace that its HW has
++ * created a session or a existing session is back in the logged in state.
++ **/
++int iscsi_if_create_session_done(struct iscsi_cls_conn *conn)
++{
++	struct iscsi_internal *priv;
++	struct iscsi_cls_session *session;
++	struct Scsi_Host *shost;
++	struct iscsi_uevent *ev;
++	struct sk_buff  *skb;
++	struct nlmsghdr *nlh;
++	unsigned long flags;
++	int rc, len = NLMSG_SPACE(sizeof(*ev));
++
++	priv = iscsi_if_transport_lookup(conn->transport);
++	if (!priv)
+ 		return -EINVAL;
++
++	session = iscsi_dev_to_session(conn->dev.parent);
++	shost = iscsi_session_to_shost(session);
++
++	skb = alloc_skb(len, GFP_KERNEL);
++	if (!skb) {
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event\n");
++		return -ENOMEM;
+ 	}
+ 
++	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
++	ev = NLMSG_DATA(nlh);
++	ev->transport_handle = iscsi_handle(conn->transport);
++	ev->type = ISCSI_UEVENT_CREATE_SESSION;
++	ev->r.c_session_ret.host_no = shost->host_no;
++	ev->r.c_session_ret.sid = session->sid;
++
+ 	/*
+ 	 * this will occur if the daemon is not up, so we just warn
+ 	 * the user and when the daemon is restarted it will handle it
+ 	 */
+ 	rc = iscsi_broadcast_skb(skb, GFP_KERNEL);
+ 	if (rc < 0)
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Cannot notify userspace of session "
+-					 "event %u. Check iscsi daemon\n",
+-					 event);
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event. Check iscsi daemon\n");
++
++	spin_lock_irqsave(&sesslock, flags);
++	list_add(&session->sess_list, &sesslist);
++	spin_unlock_irqrestore(&sesslock, flags);
++
++	spin_lock_irqsave(&connlock, flags);
++	list_add(&conn->conn_list, &connlist);
++	conn->active = 1;
++	spin_unlock_irqrestore(&connlock, flags);
+ 	return rc;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_session_event);
++EXPORT_SYMBOL_GPL(iscsi_if_create_session_done);
+ 
+ static int
+ iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_uevent *ev)
+ {
+ 	struct iscsi_transport *transport = priv->iscsi_transport;
+ 	struct iscsi_cls_session *session;
++	unsigned long flags;
+ 	uint32_t hostno;
+ 
+ 	session = transport->create_session(transport, &priv->t,
+-					    ev->u.c_session.cmds_max,
+-					    ev->u.c_session.queue_depth,
+ 					    ev->u.c_session.initial_cmdsn,
+ 					    &hostno);
+ 	if (!session)
+ 		return -ENOMEM;
+ 
++	spin_lock_irqsave(&sesslock, flags);
++	list_add(&session->sess_list, &sesslist);
++	spin_unlock_irqrestore(&sesslock, flags);
++
+ 	ev->r.c_session_ret.host_no = hostno;
+ 	ev->r.c_session_ret.sid = session->sid;
+ 	return 0;
+@@ -1041,34 +835,47 @@ iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+ {
+ 	struct iscsi_cls_conn *conn;
+ 	struct iscsi_cls_session *session;
++	unsigned long flags;
+ 
+ 	session = iscsi_session_lookup(ev->u.c_conn.sid);
+ 	if (!session) {
+-		printk(KERN_ERR "iscsi: invalid session %d.\n",
++		printk(KERN_ERR "iscsi: invalid session %d\n",
+ 		       ev->u.c_conn.sid);
+ 		return -EINVAL;
+ 	}
+ 
+ 	conn = transport->create_conn(session, ev->u.c_conn.cid);
+ 	if (!conn) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "couldn't create a new connection.");
++		printk(KERN_ERR "iscsi: couldn't create a new "
++			   "connection for session %d\n",
++			   session->sid);
+ 		return -ENOMEM;
+ 	}
+ 
+ 	ev->r.c_conn_ret.sid = session->sid;
+ 	ev->r.c_conn_ret.cid = conn->cid;
++
++	spin_lock_irqsave(&connlock, flags);
++	list_add(&conn->conn_list, &connlist);
++	conn->active = 1;
++	spin_unlock_irqrestore(&connlock, flags);
++
+ 	return 0;
+ }
+ 
+ static int
+ iscsi_if_destroy_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+ {
++	unsigned long flags;
+ 	struct iscsi_cls_conn *conn;
+ 
+ 	conn = iscsi_conn_lookup(ev->u.d_conn.sid, ev->u.d_conn.cid);
+ 	if (!conn)
+ 		return -EINVAL;
++	spin_lock_irqsave(&connlock, flags);
++	conn->active = 0;
++	list_del(&conn->conn_list);
++	spin_unlock_irqrestore(&connlock, flags);
+ 
+ 	if (transport->destroy_conn)
+ 		transport->destroy_conn(conn);
+@@ -1140,50 +947,15 @@ static int
+ iscsi_tgt_dscvr(struct iscsi_transport *transport,
+ 		struct iscsi_uevent *ev)
+ {
+-	struct Scsi_Host *shost;
+ 	struct sockaddr *dst_addr;
+-	int err;
+ 
+ 	if (!transport->tgt_dscvr)
+ 		return -EINVAL;
+ 
+-	shost = scsi_host_lookup(ev->u.tgt_dscvr.host_no);
+-	if (IS_ERR(shost)) {
+-		printk(KERN_ERR "target discovery could not find host no %u\n",
+-		       ev->u.tgt_dscvr.host_no);
+-		return -ENODEV;
+-	}
+-
+-
+ 	dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
+-	err = transport->tgt_dscvr(shost, ev->u.tgt_dscvr.type,
+-				   ev->u.tgt_dscvr.enable, dst_addr);
+-	scsi_host_put(shost);
+-	return err;
+-}
+-
+-static int
+-iscsi_set_host_param(struct iscsi_transport *transport,
+-		     struct iscsi_uevent *ev)
+-{
+-	char *data = (char*)ev + sizeof(*ev);
+-	struct Scsi_Host *shost;
+-	int err;
+-
+-	if (!transport->set_host_param)
+-		return -ENOSYS;
+-
+-	shost = scsi_host_lookup(ev->u.set_host_param.host_no);
+-	if (IS_ERR(shost)) {
+-		printk(KERN_ERR "set_host_param could not find host no %u\n",
+-		       ev->u.set_host_param.host_no);
+-		return -ENODEV;
+-	}
+-
+-	err = transport->set_host_param(shost, ev->u.set_host_param.param,
+-					data, ev->u.set_host_param.len);
+-	scsi_host_put(shost);
+-	return err;
++	return transport->tgt_dscvr(ev->u.tgt_dscvr.type,
++				    ev->u.tgt_dscvr.host_no,
++				    ev->u.tgt_dscvr.enable, dst_addr);
+ }
+ 
+ static int
+@@ -1195,6 +967,7 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 	struct iscsi_internal *priv;
+ 	struct iscsi_cls_session *session;
+ 	struct iscsi_cls_conn *conn;
++	unsigned long flags;
+ 
+ 	priv = iscsi_if_transport_lookup(iscsi_ptr(ev->transport_handle));
+ 	if (!priv)
+@@ -1212,16 +985,13 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 		break;
+ 	case ISCSI_UEVENT_DESTROY_SESSION:
+ 		session = iscsi_session_lookup(ev->u.d_session.sid);
+-		if (session)
++		if (session) {
++			spin_lock_irqsave(&sesslock, flags);
++			list_del(&session->sess_list);
++			spin_unlock_irqrestore(&sesslock, flags);
++
+ 			transport->destroy_session(session);
+-		else
+-			err = -EINVAL;
+-		break;
+-	case ISCSI_UEVENT_UNBIND_SESSION:
+-		session = iscsi_session_lookup(ev->u.d_session.sid);
+-		if (session)
+-			iscsi_unbind_session(session);
+-		else
++		} else
+ 			err = -EINVAL;
+ 		break;
+ 	case ISCSI_UEVENT_CREATE_CONN:
+@@ -1279,11 +1049,8 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 	case ISCSI_UEVENT_TGT_DSCVR:
+ 		err = iscsi_tgt_dscvr(transport, ev);
+ 		break;
+-	case ISCSI_UEVENT_SET_HOST_PARAM:
+-		err = iscsi_set_host_param(transport, ev);
+-		break;
+ 	default:
+-		err = -ENOSYS;
++		err = -EINVAL;
+ 		break;
+ 	}
+ 
+@@ -1292,55 +1059,70 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ }
+ 
+ /*
+- * Get message from skb.  Each message is processed by iscsi_if_recv_msg.
+- * Malformed skbs with wrong lengths or invalid creds are not processed.
++ * Get message from skb (based on rtnetlink_rcv_skb).  Each message is
++ * processed by iscsi_if_recv_msg.  Malformed skbs with wrong lengths or
++ * invalid creds are discarded silently.
+  */
+ static void
+-iscsi_if_rx(struct sk_buff *skb)
++iscsi_if_rx(struct sock *sk, int len)
+ {
++	struct sk_buff *skb;
++
+ 	mutex_lock(&rx_queue_mutex);
+-	while (skb->len >= NLMSG_SPACE(0)) {
+-		int err;
+-		uint32_t rlen;
+-		struct nlmsghdr	*nlh;
+-		struct iscsi_uevent *ev;
+-
+-		nlh = nlmsg_hdr(skb);
+-		if (nlh->nlmsg_len < sizeof(*nlh) ||
+-		    skb->len < nlh->nlmsg_len) {
+-			break;
++	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
++		if (NETLINK_CREDS(skb)->uid) {
++			skb_pull(skb, skb->len);
++			goto free_skb;
+ 		}
+ 
+-		ev = NLMSG_DATA(nlh);
+-		rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+-		if (rlen > skb->len)
+-			rlen = skb->len;
++		while (skb->len >= NLMSG_SPACE(0)) {
++			int err;
++			uint32_t rlen;
++			struct nlmsghdr	*nlh;
++			struct iscsi_uevent *ev;
+ 
+-		err = iscsi_if_recv_msg(skb, nlh);
+-		if (err) {
+-			ev->type = ISCSI_KEVENT_IF_ERROR;
+-			ev->iferror = err;
+-		}
+-		do {
+-			/*
+-			 * special case for GET_STATS:
+-			 * on success - sending reply and stats from
+-			 * inside of if_recv_msg(),
+-			 * on error - fall through.
+-			 */
+-			if (ev->type == ISCSI_UEVENT_GET_STATS && !err)
++			nlh = nlmsg_hdr(skb);
++			if (nlh->nlmsg_len < sizeof(*nlh) ||
++			    skb->len < nlh->nlmsg_len) {
+ 				break;
+-			err = iscsi_if_send_reply(
+-				NETLINK_CREDS(skb)->pid, nlh->nlmsg_seq,
+-				nlh->nlmsg_type, 0, 0, ev, sizeof(*ev));
+-		} while (err < 0 && err != -ECONNREFUSED);
+-		skb_pull(skb, rlen);
++			}
++
++			ev = NLMSG_DATA(nlh);
++			rlen = NLMSG_ALIGN(nlh->nlmsg_len);
++			if (rlen > skb->len)
++				rlen = skb->len;
++
++			err = iscsi_if_recv_msg(skb, nlh);
++			if (err) {
++				ev->type = ISCSI_KEVENT_IF_ERROR;
++				ev->iferror = err;
++			}
++			do {
++				/*
++				 * special case for GET_STATS:
++				 * on success - sending reply and stats from
++				 * inside of if_recv_msg(),
++				 * on error - fall through.
++				 */
++				if (ev->type == ISCSI_UEVENT_GET_STATS && !err)
++					break;
++				err = iscsi_if_send_reply(
++					NETLINK_CREDS(skb)->pid, nlh->nlmsg_seq,
++					nlh->nlmsg_type, 0, 0, ev, sizeof(*ev));
++			} while (err < 0 && err != -ECONNREFUSED);
++			skb_pull(skb, rlen);
++		}
++free_skb:
++		kfree_skb(skb);
+ 	}
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1130,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1372,66 +1153,43 @@ iscsi_conn_attr(port, ISCSI_PARAM_CONN_PORT);
+ iscsi_conn_attr(exp_statsn, ISCSI_PARAM_EXP_STATSN);
+ iscsi_conn_attr(persistent_address, ISCSI_PARAM_PERSISTENT_ADDRESS);
+ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+-iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+-iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
++
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
+ 
+ /*
+  * iSCSI session attrs
+  */
+-#define iscsi_session_attr_show(param, perm)				\
++#define iscsi_session_attr_show(param)					\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+-									\
+-	if (perm && !capable(CAP_SYS_ADMIN))				\
+-		return -EACCES;						\
+ 	return t->get_session_param(session, param, buf);		\
+ }
+ 
+-#define iscsi_session_attr(field, param, perm)				\
+-	iscsi_session_attr_show(param, perm)				\
++#define iscsi_session_attr(field, param)				\
++	iscsi_session_attr_show(param)					\
+ static ISCSI_CLASS_ATTR(sess, field, S_IRUGO, show_session_param_##param, \
+ 			NULL);
+ 
+-iscsi_session_attr(targetname, ISCSI_PARAM_TARGET_NAME, 0);
+-iscsi_session_attr(initial_r2t, ISCSI_PARAM_INITIAL_R2T_EN, 0);
+-iscsi_session_attr(max_outstanding_r2t, ISCSI_PARAM_MAX_R2T, 0);
+-iscsi_session_attr(immediate_data, ISCSI_PARAM_IMM_DATA_EN, 0);
+-iscsi_session_attr(first_burst_len, ISCSI_PARAM_FIRST_BURST, 0);
+-iscsi_session_attr(max_burst_len, ISCSI_PARAM_MAX_BURST, 0);
+-iscsi_session_attr(data_pdu_in_order, ISCSI_PARAM_PDU_INORDER_EN, 0);
+-iscsi_session_attr(data_seq_in_order, ISCSI_PARAM_DATASEQ_INORDER_EN, 0);
+-iscsi_session_attr(erl, ISCSI_PARAM_ERL, 0);
+-iscsi_session_attr(tpgt, ISCSI_PARAM_TPGT, 0);
+-iscsi_session_attr(username, ISCSI_PARAM_USERNAME, 1);
+-iscsi_session_attr(username_in, ISCSI_PARAM_USERNAME_IN, 1);
+-iscsi_session_attr(password, ISCSI_PARAM_PASSWORD, 1);
+-iscsi_session_attr(password_in, ISCSI_PARAM_PASSWORD_IN, 1);
+-iscsi_session_attr(fast_abort, ISCSI_PARAM_FAST_ABORT, 0);
+-iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+-iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+-
+-static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
+-{
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
+-	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+-}
+-static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+-			NULL);
++iscsi_session_attr(targetname, ISCSI_PARAM_TARGET_NAME);
++iscsi_session_attr(initial_r2t, ISCSI_PARAM_INITIAL_R2T_EN);
++iscsi_session_attr(max_outstanding_r2t, ISCSI_PARAM_MAX_R2T);
++iscsi_session_attr(immediate_data, ISCSI_PARAM_IMM_DATA_EN);
++iscsi_session_attr(first_burst_len, ISCSI_PARAM_FIRST_BURST);
++iscsi_session_attr(max_burst_len, ISCSI_PARAM_MAX_BURST);
++iscsi_session_attr(data_pdu_in_order, ISCSI_PARAM_PDU_INORDER_EN);
++iscsi_session_attr(data_seq_in_order, ISCSI_PARAM_DATASEQ_INORDER_EN);
++iscsi_session_attr(erl, ISCSI_PARAM_ERL);
++iscsi_session_attr(tpgt, ISCSI_PARAM_TPGT);
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1441,32 +1199,9 @@ static ISCSI_CLASS_ATTR(priv_sess, field, S_IRUGO, show_priv_session_##field, \
+ 			NULL)
+ iscsi_priv_session_attr(recovery_tmo, "%d");
+ 
+-/*
+- * iSCSI host attrs
+- */
+-#define iscsi_host_attr_show(param)					\
+-static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
+-{									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
+-	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+-	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+-}
+-
+-#define iscsi_host_attr(field, param)					\
+-	iscsi_host_attr_show(param)					\
+-static ISCSI_CLASS_ATTR(host, field, S_IRUGO, show_host_param_##param,	\
+-			NULL);
+-
+-iscsi_host_attr(netdev, ISCSI_HOST_PARAM_NETDEV_NAME);
+-iscsi_host_attr(hwaddress, ISCSI_HOST_PARAM_HWADDRESS);
+-iscsi_host_attr(ipaddress, ISCSI_HOST_PARAM_IPADDRESS);
+-iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+-
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1209,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,15 +1217,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
+-		count++;						\
+-	}								\
+-} while (0)
+-
+-#define SETUP_HOST_RD_ATTR(field, param_flag)				\
+-do {									\
+-	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,31 +1308,24 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+ 	priv->t.host_attrs.ac.class = &iscsi_host_class.class;
+ 	priv->t.host_attrs.ac.match = iscsi_host_match;
+ 	priv->t.host_size = sizeof(struct iscsi_host);
++	priv->host_attrs[0] = NULL;
+ 	transport_container_register(&priv->t.host_attrs);
+ 
+-	SETUP_HOST_RD_ATTR(netdev, ISCSI_HOST_NETDEV_NAME);
+-	SETUP_HOST_RD_ATTR(ipaddress, ISCSI_HOST_IPADDRESS);
+-	SETUP_HOST_RD_ATTR(hwaddress, ISCSI_HOST_HWADDRESS);
+-	SETUP_HOST_RD_ATTR(initiatorname, ISCSI_HOST_INITIATOR_NAME);
+-	BUG_ON(count > ISCSI_HOST_ATTRS);
+-	priv->host_attrs[count] = NULL;
+-	count = 0;
+-
+ 	/* connection parameters */
+ 	priv->conn_cont.ac.attrs = &priv->conn_attrs[0];
+ 	priv->conn_cont.ac.class = &iscsi_connection_class.class;
+@@ -1623,8 +1343,6 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	SETUP_CONN_RD_ATTR(exp_statsn, ISCSI_EXP_STATSN);
+ 	SETUP_CONN_RD_ATTR(persistent_address, ISCSI_PERSISTENT_ADDRESS);
+ 	SETUP_CONN_RD_ATTR(persistent_port, ISCSI_PERSISTENT_PORT);
+-	SETUP_CONN_RD_ATTR(ping_tmo, ISCSI_PING_TMO);
+-	SETUP_CONN_RD_ATTR(recv_tmo, ISCSI_RECV_TMO);
+ 
+ 	BUG_ON(count > ISCSI_CONN_ATTRS);
+ 	priv->conn_attrs[count] = NULL;
+@@ -1646,15 +1364,7 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	SETUP_SESSION_RD_ATTR(erl, ISCSI_ERL);
+ 	SETUP_SESSION_RD_ATTR(targetname, ISCSI_TARGET_NAME);
+ 	SETUP_SESSION_RD_ATTR(tpgt, ISCSI_TPGT);
+-	SETUP_SESSION_RD_ATTR(password, ISCSI_USERNAME);
+-	SETUP_SESSION_RD_ATTR(password_in, ISCSI_USERNAME_IN);
+-	SETUP_SESSION_RD_ATTR(username, ISCSI_PASSWORD);
+-	SETUP_SESSION_RD_ATTR(username_in, ISCSI_PASSWORD_IN);
+-	SETUP_SESSION_RD_ATTR(fast_abort, ISCSI_FAST_ABORT);
+-	SETUP_SESSION_RD_ATTR(abort_tmo, ISCSI_ABORT_TMO);
+-	SETUP_SESSION_RD_ATTR(lu_reset_tmo,ISCSI_LU_RESET_TMO);
+ 	SETUP_PRIV_SESSION_RD_ATTR(recovery_tmo);
+-	SETUP_PRIV_SESSION_RD_ATTR(state);
+ 
+ 	BUG_ON(count > ISCSI_SESSION_ATTRS);
+ 	priv->session_attrs[count] = NULL;
+@@ -1666,8 +1376,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1404,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+@@ -1734,14 +1444,8 @@ static __init int iscsi_transport_init(void)
+ 		goto unregister_session_class;
+ 	}
+ 
+-	iscsi_eh_timer_workq = create_singlethread_workqueue("iscsi_eh");
+-	if (!iscsi_eh_timer_workq)
+-		goto release_nls;
+-
+ 	return 0;
+ 
+-release_nls:
+-	netlink_kernel_release(nls);
+ unregister_session_class:
+ 	transport_class_unregister(&iscsi_session_class);
+ unregister_conn_class:
+@@ -1755,8 +1459,7 @@ unregister_transport_class:
+ 
+ static void __exit iscsi_transport_exit(void)
+ {
+-	destroy_workqueue(iscsi_eh_timer_workq);
+-	netlink_kernel_release(nls);
++	sock_release(nls->sk_socket);
+ 	transport_class_unregister(&iscsi_connection_class);
+ 	transport_class_unregister(&iscsi_session_class);
+ 	transport_class_unregister(&iscsi_host_class);
+diff --git a/include/scsi/iscsi_if.h b/include/scsi/iscsi_if.h
+index e19e584..55ebf03 100644
+--- a/include/scsi/iscsi_if.h
++++ b/include/scsi/iscsi_if.h
+@@ -48,16 +48,12 @@ enum iscsi_uevent_e {
+ 	ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT	= UEVENT_BASE + 14,
+ 
+ 	ISCSI_UEVENT_TGT_DSCVR		= UEVENT_BASE + 15,
+-	ISCSI_UEVENT_SET_HOST_PARAM	= UEVENT_BASE + 16,
+-	ISCSI_UEVENT_UNBIND_SESSION	= UEVENT_BASE + 17,
+ 
+ 	/* up events */
+ 	ISCSI_KEVENT_RECV_PDU		= KEVENT_BASE + 1,
+ 	ISCSI_KEVENT_CONN_ERROR		= KEVENT_BASE + 2,
+ 	ISCSI_KEVENT_IF_ERROR		= KEVENT_BASE + 3,
+ 	ISCSI_KEVENT_DESTROY_SESSION	= KEVENT_BASE + 4,
+-	ISCSI_KEVENT_UNBIND_SESSION	= KEVENT_BASE + 5,
+-	ISCSI_KEVENT_CREATE_SESSION	= KEVENT_BASE + 6,
+ };
+ 
+ enum iscsi_tgt_dscvr {
+@@ -75,8 +71,6 @@ struct iscsi_uevent {
+ 		/* messages u -> k */
+ 		struct msg_create_session {
+ 			uint32_t	initial_cmdsn;
+-			uint16_t	cmds_max;
+-			uint16_t	queue_depth;
+ 		} c_session;
+ 		struct msg_destroy_session {
+ 			uint32_t	sid;
+@@ -142,11 +136,6 @@ struct iscsi_uevent {
+ 			 */
+ 			uint32_t	enable;
+ 		} tgt_dscvr;
+-		struct msg_set_host_param {
+-			uint32_t	host_no;
+-			uint32_t	param; /* enum iscsi_host_param */
+-			uint32_t	len;
+-		} set_host_param;
+ 	} u;
+ 	union {
+ 		/* messages k -> u */
+@@ -159,10 +148,6 @@ struct iscsi_uevent {
+ 			uint32_t	sid;
+ 			uint32_t	cid;
+ 		} c_conn_ret;
+-		struct msg_unbind_session {
+-			uint32_t	sid;
+-			uint32_t	host_no;
+-		} unbind_session;
+ 		struct msg_recv_req {
+ 			uint32_t	sid;
+ 			uint32_t	cid;
+@@ -238,18 +223,6 @@ enum iscsi_param {
+ 	ISCSI_PARAM_CONN_PORT,
+ 	ISCSI_PARAM_CONN_ADDRESS,
+ 
+-	ISCSI_PARAM_USERNAME,
+-	ISCSI_PARAM_USERNAME_IN,
+-	ISCSI_PARAM_PASSWORD,
+-	ISCSI_PARAM_PASSWORD_IN,
+-
+-	ISCSI_PARAM_FAST_ABORT,
+-	ISCSI_PARAM_ABORT_TMO,
+-	ISCSI_PARAM_LU_RESET_TMO,
+-	ISCSI_PARAM_HOST_RESET_TMO,
+-
+-	ISCSI_PARAM_PING_TMO,
+-	ISCSI_PARAM_RECV_TMO,
+ 	/* must always be last */
+ 	ISCSI_PARAM_MAX,
+ };
+@@ -276,30 +249,6 @@ enum iscsi_param {
+ #define ISCSI_SESS_RECOVERY_TMO		(1 << ISCSI_PARAM_SESS_RECOVERY_TMO)
+ #define ISCSI_CONN_PORT			(1 << ISCSI_PARAM_CONN_PORT)
+ #define ISCSI_CONN_ADDRESS		(1 << ISCSI_PARAM_CONN_ADDRESS)
+-#define ISCSI_USERNAME			(1 << ISCSI_PARAM_USERNAME)
+-#define ISCSI_USERNAME_IN		(1 << ISCSI_PARAM_USERNAME_IN)
+-#define ISCSI_PASSWORD			(1 << ISCSI_PARAM_PASSWORD)
+-#define ISCSI_PASSWORD_IN		(1 << ISCSI_PARAM_PASSWORD_IN)
+-#define ISCSI_FAST_ABORT		(1 << ISCSI_PARAM_FAST_ABORT)
+-#define ISCSI_ABORT_TMO			(1 << ISCSI_PARAM_ABORT_TMO)
+-#define ISCSI_LU_RESET_TMO		(1 << ISCSI_PARAM_LU_RESET_TMO)
+-#define ISCSI_HOST_RESET_TMO		(1 << ISCSI_PARAM_HOST_RESET_TMO)
+-#define ISCSI_PING_TMO			(1 << ISCSI_PARAM_PING_TMO)
+-#define ISCSI_RECV_TMO			(1 << ISCSI_PARAM_RECV_TMO)
+-
+-/* iSCSI HBA params */
+-enum iscsi_host_param {
+-	ISCSI_HOST_PARAM_HWADDRESS,
+-	ISCSI_HOST_PARAM_INITIATOR_NAME,
+-	ISCSI_HOST_PARAM_NETDEV_NAME,
+-	ISCSI_HOST_PARAM_IPADDRESS,
+-	ISCSI_HOST_PARAM_MAX,
+-};
+-
+-#define ISCSI_HOST_HWADDRESS		(1 << ISCSI_HOST_PARAM_HWADDRESS)
+-#define ISCSI_HOST_INITIATOR_NAME	(1 << ISCSI_HOST_PARAM_INITIATOR_NAME)
+-#define ISCSI_HOST_NETDEV_NAME		(1 << ISCSI_HOST_PARAM_NETDEV_NAME)
+-#define ISCSI_HOST_IPADDRESS		(1 << ISCSI_HOST_PARAM_IPADDRESS)
+ 
+ #define iscsi_ptr(_handle) ((void*)(unsigned long)_handle)
+ #define iscsi_handle(_ptr) ((uint64_t)(unsigned long)_ptr)
+@@ -323,9 +272,6 @@ enum iscsi_host_param {
+ #define CAP_MULTI_CONN		0x40
+ #define CAP_TEXT_NEGO		0x80
+ #define CAP_MARKERS		0x100
+-#define CAP_FW_DB		0x200
+-#define CAP_SENDTARGETS_OFFLOAD	0x400
+-#define CAP_DATA_PATH_OFFLOAD	0x800
+ 
+ /*
+  * These flags describes reason of stop_conn() call
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..ea0816d 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -48,8 +48,9 @@ struct iscsi_nopin;
+ #define debug_scsi(fmt...)
+ #endif
+ 
+-#define ISCSI_DEF_XMIT_CMDS_MAX	128	/* must be power of 2 */
+-#define ISCSI_MGMT_CMDS_MAX	16	/* must be power of 2 */
++#define ISCSI_XMIT_CMDS_MAX	128	/* must be power of 2 */
++#define ISCSI_MGMT_CMDS_MAX	32	/* must be power of 2 */
++#define ISCSI_CONN_MAX			1
+ 
+ #define ISCSI_MGMT_ITT_OFFSET	0xa00
+ 
+@@ -57,31 +58,21 @@ struct iscsi_nopin;
+ #define ISCSI_MAX_CMD_PER_LUN		128
+ 
+ /* Task Mgmt states */
+-enum {
+-	TMF_INITIAL,
+-	TMF_QUEUED,
+-	TMF_SUCCESS,
+-	TMF_FAILED,
+-	TMF_TIMEDOUT,
+-	TMF_NOT_FOUND,
+-};
++#define TMABORT_INITIAL			0x0
++#define TMABORT_SUCCESS			0x1
++#define TMABORT_FAILED			0x2
++#define TMABORT_TIMEDOUT		0x3
++#define TMABORT_NOT_FOUND		0x4
+ 
+ /* Connection suspend "bit" */
+ #define ISCSI_SUSPEND_BIT		1
+ 
+ #define ISCSI_ITT_MASK			(0xfff)
++#define ISCSI_CID_SHIFT			12
++#define ISCSI_CID_MASK			(0xffff << ISCSI_CID_SHIFT)
+ #define ISCSI_AGE_SHIFT			28
+ #define ISCSI_AGE_MASK			(0xf << ISCSI_AGE_SHIFT)
+ 
+-#define ISCSI_ADDRESS_BUF_LEN		64
+-
+-enum {
+-	/* this is the maximum possible storage for AHSs */
+-	ISCSI_MAX_AHS_SIZE = sizeof(struct iscsi_ecdb_ahdr) +
+-				sizeof(struct iscsi_rlength_ahdr),
+-	ISCSI_DIGEST_SIZE = sizeof(__u32),
+-};
+-
+ struct iscsi_mgmt_task {
+ 	/*
+ 	 * Becuae LLDs allocate their hdr differently, this is a pointer to
+@@ -89,7 +80,7 @@ struct iscsi_mgmt_task {
+ 	 */
+ 	struct iscsi_hdr	*hdr;
+ 	char			*data;		/* mgmt payload */
+-	unsigned		data_count;	/* counts data to be sent */
++	int			data_count;	/* counts data to be sent */
+ 	uint32_t		itt;		/* this ITT */
+ 	void			*dd_data;	/* driver/transport data */
+ 	struct list_head	running;
+@@ -103,23 +94,23 @@ enum {
+ 
+ struct iscsi_cmd_task {
+ 	/*
+-	 * Because LLDs allocate their hdr differently, this is a pointer
+-	 * and length to that storage. It must be setup at session
+-	 * creation time.
++	 * Becuae LLDs allocate their hdr differently, this is a pointer to
++	 * that storage. It must be setup at session creation time.
+ 	 */
+ 	struct iscsi_cmd	*hdr;
+-	unsigned short		hdr_max;
+-	unsigned short		hdr_len;	/* accumulated size of hdr used */
+ 	int			itt;		/* this ITT */
++	int			datasn;		/* DataSN */
+ 
+ 	uint32_t		unsol_datasn;
+-	unsigned		imm_count;	/* imm-data (bytes)   */
+-	unsigned		unsol_count;	/* unsolicited (bytes)*/
++	int			imm_count;	/* imm-data (bytes)   */
++	int			unsol_count;	/* unsolicited (bytes)*/
+ 	/* offset in unsolicited stream (bytes); */
+-	unsigned		unsol_offset;
+-	unsigned		data_count;	/* remaining Data-Out */
++	int			unsol_offset;
++	int			data_count;	/* remaining Data-Out */
+ 	struct scsi_cmnd	*sc;		/* associated SCSI cmd*/
++	int			total_length;
+ 	struct iscsi_conn	*conn;		/* used connection    */
++	struct iscsi_mgmt_task	*mtask;		/* tmf mtask in progr */
+ 
+ 	/* state set/tested under session->lock */
+ 	int			state;
+@@ -128,19 +119,6 @@ struct iscsi_cmd_task {
+ 	void			*dd_data;	/* driver/transport data */
+ };
+ 
+-static inline void* iscsi_next_hdr(struct iscsi_cmd_task *ctask)
+-{
+-	return (void*)ctask->hdr + ctask->hdr_len;
+-}
+-
+-/* Connection's states */
+-enum {
+-	ISCSI_CONN_INITIAL_STAGE,
+-	ISCSI_CONN_STARTED,
+-	ISCSI_CONN_STOPPED,
+-	ISCSI_CONN_CLEANUP_WAIT,
+-};
+-
+ struct iscsi_conn {
+ 	struct iscsi_cls_conn	*cls_conn;	/* ptr to class connection */
+ 	void			*dd_data;	/* iscsi_transport data */
+@@ -154,12 +132,6 @@ struct iscsi_conn {
+ 	 * conn_stop() flag: stop to recover, stop to terminate
+ 	 */
+         int			stop_stage;
+-	struct timer_list	transport_timer;
+-	unsigned long		last_recv;
+-	unsigned long		last_ping;
+-	int			ping_timeout;
+-	int			recv_timeout;
+-	struct iscsi_mgmt_task	*ping_mtask;
+ 
+ 	/* iSCSI connection-wide sequencing */
+ 	uint32_t		exp_statsn;
+@@ -180,24 +152,30 @@ struct iscsi_conn {
+ 	struct iscsi_cmd_task	*ctask;		/* xmit ctask in progress */
+ 
+ 	/* xmit */
+-	struct list_head	mgmtqueue;	/* mgmt (control) xmit queue */
++	struct kfifo		*immqueue;	/* immediate xmit queue */
++	struct kfifo		*mgmtqueue;	/* mgmt (control) xmit queue */
+ 	struct list_head	mgmt_run_list;	/* list of control tasks */
+ 	struct list_head	xmitqueue;	/* data-path cmd queue */
+ 	struct list_head	run_list;	/* list of cmds in progress */
+-	struct list_head	requeue;	/* tasks needing another run */
+ 	struct work_struct	xmitwork;	/* per-conn. xmit workqueue */
++	/*
++	 * serializes connection xmit, access to kfifos:
++	 * xmitqueue, immqueue, mgmtqueue
++	 */
++	struct mutex		xmitmutex;
++
+ 	unsigned long		suspend_tx;	/* suspend Tx */
+ 	unsigned long		suspend_rx;	/* suspend Rx */
+ 
+ 	/* abort */
+ 	wait_queue_head_t	ehwait;		/* used in eh_abort() */
+ 	struct iscsi_tm		tmhdr;
+-	struct timer_list	tmf_timer;
+-	int			tmf_state;	/* see TMF_INITIAL, etc.*/
++	struct timer_list	tmabort_timer;
++	int			tmabort_state;	/* see TMABORT_INITIAL, etc.*/
+ 
+ 	/* negotiated params */
+-	unsigned		max_recv_dlength; /* initiator_max_recv_dsl*/
+-	unsigned		max_xmit_dlength; /* target_max_recv_dsl */
++	int			max_recv_dlength; /* initiator_max_recv_dsl*/
++	int			max_xmit_dlength; /* target_max_recv_dsl */
+ 	int			hdrdgst_en;
+ 	int			datadgst_en;
+ 	int			ifmarker_en;
+@@ -205,12 +183,6 @@ struct iscsi_conn {
+ 	/* values userspace uses to id a conn */
+ 	int			persistent_port;
+ 	char			*persistent_address;
+-	/* remote portal currently connected to */
+-	int			portal_port;
+-	char			portal_address[ISCSI_ADDRESS_BUF_LEN];
+-	/* local address */
+-	int			local_port;
+-	char			local_address[ISCSI_ADDRESS_BUF_LEN];
+ 
+ 	/* MIB-statistics */
+ 	uint64_t		txdata_octets;
+@@ -225,66 +197,34 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+-struct iscsi_pool {
++struct iscsi_queue {
+ 	struct kfifo		*queue;		/* FIFO Queue */
+ 	void			**pool;		/* Pool of elements */
+ 	int			max;		/* Max number of elements */
+ };
+ 
+-/* Session's states */
+-enum {
+-	ISCSI_STATE_FREE = 1,
+-	ISCSI_STATE_LOGGED_IN,
+-	ISCSI_STATE_FAILED,
+-	ISCSI_STATE_TERMINATE,
+-	ISCSI_STATE_IN_RECOVERY,
+-	ISCSI_STATE_RECOVERY_FAILED,
+-	ISCSI_STATE_LOGGING_OUT,
+-};
+-
+ struct iscsi_session {
+-	/*
+-	 * Syncs up the scsi eh thread with the iscsi eh thread when sending
+-	 * task management functions. This must be taken before the session
+-	 * and recv lock.
+-	 */
+-	struct mutex		eh_mutex;
+-
+ 	/* iSCSI session-wide sequencing */
+ 	uint32_t		cmdsn;
+ 	uint32_t		exp_cmdsn;
+ 	uint32_t		max_cmdsn;
+ 
+-	/* This tracks the reqs queued into the initiator */
+-	uint32_t		queued_cmdsn;
+-
+ 	/* configuration */
+-	int			abort_timeout;
+-	int			lu_reset_timeout;
+ 	int			initial_r2t_en;
+-	unsigned		max_r2t;
++	int			max_r2t;
+ 	int			imm_data_en;
+-	unsigned		first_burst;
+-	unsigned		max_burst;
++	int			first_burst;
++	int			max_burst;
+ 	int			time2wait;
+ 	int			time2retain;
+ 	int			pdu_inorder_en;
+ 	int			dataseq_inorder_en;
+ 	int			erl;
+-	int			fast_abort;
+ 	int			tpgt;
+-	char			*username;
+-	char			*username_in;
+-	char			*password;
+-	char			*password_in;
+ 	char			*targetname;
+-	char			*initiatorname;
+-	/* hw address or netdev iscsi connection is bound to */
+-	char			*hwaddress;
+-	char			*netdev;
++
+ 	/* control data */
+ 	struct iscsi_transport	*tt;
+ 	struct Scsi_Host	*host;
+@@ -300,10 +240,10 @@ struct iscsi_session {
+ 
+ 	int			cmds_max;	/* size of cmds array */
+ 	struct iscsi_cmd_task	**cmds;		/* Original Cmds arr */
+-	struct iscsi_pool	cmdpool;	/* PDU's pool */
++	struct iscsi_queue	cmdpool;	/* PDU's pool */
+ 	int			mgmtpool_max;	/* size of mgmt array */
+ 	struct iscsi_mgmt_task	**mgmt_cmds;	/* Original mgmt arr */
+-	struct iscsi_pool	mgmtpool;	/* Mgmt PDU's pool */
++	struct iscsi_queue	mgmtpool;	/* Mgmt PDU's pool */
+ };
+ 
+ /*
+@@ -312,26 +252,15 @@ struct iscsi_session {
+ extern int iscsi_change_queue_depth(struct scsi_device *sdev, int depth);
+ extern int iscsi_eh_abort(struct scsi_cmnd *sc);
+ extern int iscsi_eh_host_reset(struct scsi_cmnd *sc);
+-extern int iscsi_eh_device_reset(struct scsi_cmnd *sc);
+ extern int iscsi_queuecommand(struct scsi_cmnd *sc,
+ 			      void (*done)(struct scsi_cmnd *));
+ 
+-
+-/*
+- * iSCSI host helpers.
+- */
+-extern int iscsi_host_set_param(struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf,
+-				int buflen);
+-extern int iscsi_host_get_param(struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf);
+-
+ /*
+  * session management
+  */
+ extern struct iscsi_cls_session *
+ iscsi_session_setup(struct iscsi_transport *, struct scsi_transport_template *,
+-		    uint16_t, uint16_t, int, int, uint32_t, uint32_t *);
++		    int, int, uint32_t, uint32_t *);
+ extern void iscsi_session_teardown(struct iscsi_cls_session *);
+ extern struct iscsi_session *class_to_transport_session(struct iscsi_cls_session *);
+ extern void iscsi_session_recovery_timedout(struct iscsi_cls_session *);
+@@ -343,10 +272,6 @@ extern int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ #define session_to_cls(_sess) \
+ 	hostdata_session(_sess->host->hostdata)
+ 
+-#define iscsi_session_printk(prefix, _sess, fmt, a...)	\
+-	iscsi_cls_session_printk(prefix,		\
+-		(struct iscsi_cls_session *)session_to_cls(_sess), fmt, ##a)
+-
+ /*
+  * connection management
+  */
+@@ -361,47 +286,26 @@ extern void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err);
+ extern int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 				enum iscsi_param param, char *buf);
+ 
+-#define iscsi_conn_printk(prefix, _c, fmt, a...) \
+-	iscsi_cls_conn_printk(prefix, _c->cls_conn, fmt, ##a)
+-
+ /*
+  * pdu and task processing
+  */
+-extern void iscsi_update_cmdsn(struct iscsi_session *, struct iscsi_nopin *);
++extern int iscsi_check_assign_cmdsn(struct iscsi_session *,
++				    struct iscsi_nopin *);
+ extern void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *,
+ 					struct iscsi_data *hdr);
+ extern int iscsi_conn_send_pdu(struct iscsi_cls_conn *, struct iscsi_hdr *,
+ 				char *, uint32_t);
+ extern int iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *,
+ 			      char *, int);
++extern int __iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *,
++				char *, int);
+ extern int iscsi_verify_itt(struct iscsi_conn *, struct iscsi_hdr *,
+ 			    uint32_t *);
+-extern void iscsi_requeue_ctask(struct iscsi_cmd_task *ctask);
+-extern void iscsi_free_mgmt_task(struct iscsi_conn *conn,
+-				 struct iscsi_mgmt_task *mtask);
+ 
+ /*
+  * generic helpers
+  */
+-extern void iscsi_pool_free(struct iscsi_pool *);
+-extern int iscsi_pool_init(struct iscsi_pool *, int, void ***, int);
+-
+-/*
+- * inline functions to deal with padding.
+- */
+-static inline unsigned int
+-iscsi_padded(unsigned int len)
+-{
+-	return (len + ISCSI_PAD_LEN - 1) & ~(ISCSI_PAD_LEN - 1);
+-}
+-
+-static inline unsigned int
+-iscsi_padding(unsigned int len)
+-{
+-	len &= (ISCSI_PAD_LEN - 1);
+-	if (len)
+-		len = ISCSI_PAD_LEN - len;
+-	return len;
+-}
++extern void iscsi_pool_free(struct iscsi_queue *, void **);
++extern int iscsi_pool_init(struct iscsi_queue *, int, void ***, int);
+ 
+ #endif
+diff --git a/include/scsi/scsi_transport_iscsi.h b/include/scsi/scsi_transport_iscsi.h
+index aab1eae..d5c218d 100644
+--- a/include/scsi/scsi_transport_iscsi.h
++++ b/include/scsi/scsi_transport_iscsi.h
+@@ -24,8 +24,6 @@
+ #define SCSI_TRANSPORT_ISCSI_H
+ 
+ #include <linux/device.h>
+-#include <linux/list.h>
+-#include <linux/mutex.h>
+ #include <scsi/iscsi_if.h>
+ 
+ struct scsi_transport_template;
+@@ -81,8 +79,7 @@ struct iscsi_transport {
+ 	char *name;
+ 	unsigned int caps;
+ 	/* LLD sets this to indicate what values it can export to sysfs */
+-	uint64_t param_mask;
+-	uint64_t host_param_mask;
++	unsigned int param_mask;
+ 	struct scsi_host_template *host_template;
+ 	/* LLD connection data size */
+ 	int conndata_size;
+@@ -92,8 +89,7 @@ struct iscsi_transport {
+ 	unsigned int max_conn;
+ 	unsigned int max_cmd_len;
+ 	struct iscsi_cls_session *(*create_session) (struct iscsi_transport *it,
+-		struct scsi_transport_template *t, uint16_t, uint16_t,
+-		uint32_t sn, uint32_t *hn);
++		struct scsi_transport_template *t, uint32_t sn, uint32_t *hn);
+ 	void (*destroy_session) (struct iscsi_cls_session *session);
+ 	struct iscsi_cls_conn *(*create_conn) (struct iscsi_cls_session *sess,
+ 				uint32_t cid);
+@@ -109,18 +105,14 @@ struct iscsi_transport {
+ 			       enum iscsi_param param, char *buf);
+ 	int (*get_session_param) (struct iscsi_cls_session *session,
+ 				  enum iscsi_param param, char *buf);
+-	int (*get_host_param) (struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf);
+-	int (*set_host_param) (struct Scsi_Host *shost,
+-			       enum iscsi_host_param param, char *buf,
+-			       int buflen);
+ 	int (*send_pdu) (struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 			 char *data, uint32_t data_size);
+ 	void (*get_stats) (struct iscsi_cls_conn *conn,
+ 			   struct iscsi_stats *stats);
+-	int (*init_cmd_task) (struct iscsi_cmd_task *ctask);
++	void (*init_cmd_task) (struct iscsi_cmd_task *ctask);
+ 	void (*init_mgmt_task) (struct iscsi_conn *conn,
+-				struct iscsi_mgmt_task *mtask);
++				struct iscsi_mgmt_task *mtask,
++				char *data, uint32_t data_size);
+ 	int (*xmit_cmd_task) (struct iscsi_conn *conn,
+ 			      struct iscsi_cmd_task *ctask);
+ 	void (*cleanup_cmd_task) (struct iscsi_conn *conn,
+@@ -132,7 +124,7 @@ struct iscsi_transport {
+ 			   uint64_t *ep_handle);
+ 	int (*ep_poll) (uint64_t ep_handle, int timeout_ms);
+ 	void (*ep_disconnect) (uint64_t ep_handle);
+-	int (*tgt_dscvr) (struct Scsi_Host *shost, enum iscsi_tgt_dscvr type,
++	int (*tgt_dscvr) (enum iscsi_tgt_dscvr type, uint32_t host_no,
+ 			  uint32_t enable, struct sockaddr *dst_addr);
+ };
+ 
+@@ -149,6 +141,13 @@ extern void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error);
+ extern int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 			  char *data, uint32_t data_size);
+ 
++
++/* Connection's states */
++#define ISCSI_CONN_INITIAL_STAGE	0
++#define ISCSI_CONN_STARTED		1
++#define ISCSI_CONN_STOPPED		2
++#define ISCSI_CONN_CLEANUP_WAIT		3
++
+ struct iscsi_cls_conn {
+ 	struct list_head conn_list;	/* item in connlist */
+ 	void *dd_data;			/* LLD private data */
+@@ -162,25 +161,18 @@ struct iscsi_cls_conn {
+ #define iscsi_dev_to_conn(_dev) \
+ 	container_of(_dev, struct iscsi_cls_conn, dev)
+ 
+-#define iscsi_conn_to_session(_conn) \
+-	iscsi_dev_to_session(_conn->dev.parent)
+-
+-/* iscsi class session state */
+-enum {
+-	ISCSI_SESSION_LOGGED_IN,
+-	ISCSI_SESSION_FAILED,
+-	ISCSI_SESSION_FREE,
+-};
++/* Session's states */
++#define ISCSI_STATE_FREE		1
++#define ISCSI_STATE_LOGGED_IN		2
++#define ISCSI_STATE_FAILED		3
++#define ISCSI_STATE_TERMINATE		4
++#define ISCSI_STATE_IN_RECOVERY		5
++#define ISCSI_STATE_RECOVERY_FAILED	6
+ 
+ struct iscsi_cls_session {
+ 	struct list_head sess_list;		/* item in session_list */
+ 	struct list_head host_list;
+ 	struct iscsi_transport *transport;
+-	spinlock_t lock;
+-	struct work_struct block_work;
+-	struct work_struct unblock_work;
+-	struct work_struct scan_work;
+-	struct work_struct unbind_work;
+ 
+ 	/* recovery fields */
+ 	int recovery_tmo;
+@@ -188,7 +180,6 @@ struct iscsi_cls_session {
+ 
+ 	int target_id;
+ 
+-	int state;
+ 	int sid;				/* session id */
+ 	void *dd_data;				/* LLD private data */
+ 	struct device dev;	/* sysfs transport/container device */
+@@ -205,28 +196,18 @@ struct iscsi_cls_session {
+ 
+ struct iscsi_host {
+ 	struct list_head sessions;
+-	atomic_t nr_scans;
+ 	struct mutex mutex;
+-	struct workqueue_struct *scan_workq;
+-	char scan_workq_name[KOBJ_NAME_LEN];
+ };
+ 
+ /*
+  * session and connection functions that can be used by HW iSCSI LLDs
+  */
+-#define iscsi_cls_session_printk(prefix, _cls_session, fmt, a...) \
+-	dev_printk(prefix, &(_cls_session)->dev, fmt, ##a)
+-
+-#define iscsi_cls_conn_printk(prefix, _cls_conn, fmt, a...) \
+-	dev_printk(prefix, &(_cls_conn)->dev, fmt, ##a)
+-
+-extern int iscsi_session_chkready(struct iscsi_cls_session *session);
+ extern struct iscsi_cls_session *iscsi_alloc_session(struct Scsi_Host *shost,
+ 					struct iscsi_transport *transport);
+ extern int iscsi_add_session(struct iscsi_cls_session *session,
+ 			     unsigned int target_id);
+-extern int iscsi_session_event(struct iscsi_cls_session *session,
+-			       enum iscsi_uevent_e event);
++extern int iscsi_if_create_session_done(struct iscsi_cls_conn *conn);
++extern int iscsi_if_destroy_session_done(struct iscsi_cls_conn *conn);
+ extern struct iscsi_cls_session *iscsi_create_session(struct Scsi_Host *shost,
+ 						struct iscsi_transport *t,
+ 						unsigned int target_id);
+@@ -238,6 +219,6 @@ extern struct iscsi_cls_conn *iscsi_create_conn(struct iscsi_cls_session *sess,
+ extern int iscsi_destroy_conn(struct iscsi_cls_conn *conn);
+ extern void iscsi_unblock_session(struct iscsi_cls_session *session);
+ extern void iscsi_block_session(struct iscsi_cls_session *session);
+-extern int iscsi_scan_finished(struct Scsi_Host *shost, unsigned long time);
++
+ 
+ #endif
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.9_U4/iscsi_02_add_to_2_6_9.patch b/kernel_patches/backport/2.6.9_U4/iscsi_02_add_to_2_6_9.patch
new file mode 100644
index 0000000..1f05d95
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U4/iscsi_02_add_to_2_6_9.patch
@@ -0,0 +1,180 @@
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 4376840..11dfaf9 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -2145,7 +2145,6 @@ static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
+ static struct scsi_host_template iscsi_sht = {
+ 	.name			= "iSCSI Initiator over TCP/IP",
+ 	.queuecommand           = iscsi_queuecommand,
+-	.change_queue_depth	= iscsi_change_queue_depth,
+ 	.can_queue		= ISCSI_XMIT_CMDS_MAX - 1,
+ 	.sg_tablesize		= ISCSI_SG_TABLESIZE,
+	.max_sectors		= 0xFFFF,
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index d37048c..60f5846 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -1366,7 +1366,6 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 	shost->max_lun = iscsit->max_lun;
+ 	shost->max_cmd_len = iscsit->max_cmd_len;
+ 	shost->transportt = scsit;
+-	shost->transportt->create_work_queue = 1;
+ 	*hostno = shost->host_no;
+ 
+ 	session = iscsi_hostdata(shost->hostdata);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 8133c22..f1c68f7 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -65,6 +65,8 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define cdev_to_iscsi_internal(_cdev) \
+ 	container_of(_cdev, struct iscsi_internal, cdev)
+ 
++extern int attribute_container_init(void);
++
+ static void iscsi_transport_release(struct class_device *cdev)
+ {
+ 	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+@@ -80,6 +82,17 @@ static struct class iscsi_transport_class = {
+ 	.release = iscsi_transport_release,
+ };
+ 
++static void iscsi_host_class_release(struct class_device *class_dev)
++{
++	struct Scsi_Host *shost = transport_class_to_shost(class_dev);
++	put_device(&shost->shost_gendev);
++}
++
++struct class iscsi_host_class = {
++	.name = "iscsi_host",
++	.release = iscsi_host_class_release,
++};
++
+ static ssize_t
+ show_transport_handle(struct class_device *cdev, char *buf)
+ {
+@@ -115,10 +128,8 @@ static struct attribute_group iscsi_transport_group = {
+ 	.attrs = iscsi_transport_attrs,
+ };
+ 
+-static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct class_device *cdev)
++static int iscsi_setup_host(struct Scsi_Host *shost)
+ {
+-	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+ 
+ 	memset(ihost, 0, sizeof(*ihost));
+@@ -127,12 +138,6 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ 	return 0;
+ }
+ 
+-static DECLARE_TRANSPORT_CLASS(iscsi_host_class,
+-			       "iscsi_host",
+-			       iscsi_setup_host,
+-			       NULL,
+-			       NULL);
+-
+ static DECLARE_TRANSPORT_CLASS(iscsi_session_class,
+ 			       "iscsi_session",
+ 			       NULL,
+@@ -216,24 +221,6 @@ static int iscsi_is_session_dev(const struct device *dev)
+ 	return dev->release == iscsi_session_release;
+ }
+ 
+-static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+-			   uint id, uint lun)
+-{
+-	struct iscsi_host *ihost = shost->shost_data;
+-	struct iscsi_cls_session *session;
+-
+-	mutex_lock(&ihost->mutex);
+-	list_for_each_entry(session, &ihost->sessions, host_list) {
+-		if ((channel == SCAN_WILD_CARD || channel == 0) &&
+-		    (id == SCAN_WILD_CARD || id == session->target_id))
+-			scsi_scan_target(&session->dev, 0,
+-					 session->target_id, lun, 1);
+-	}
+-	mutex_unlock(&ihost->mutex);
+-
+-	return 0;
+-}
+-
+ static void session_recovery_timedout(struct work_struct *work)
+ {
+ 	struct iscsi_cls_session *session =
+@@ -362,8 +349,6 @@ void iscsi_remove_session(struct iscsi_cls_session *session)
+ 	list_del(&session->host_list);
+ 	mutex_unlock(&ihost->mutex);
+ 
+-	scsi_remove_target(&session->dev);
+-
+ 	transport_unregister_device(&session->dev);
+ 	device_del(&session->dev);
+ }
+@@ -1269,24 +1254,6 @@ static int iscsi_conn_match(struct attribute_container *cont,
+ 	return &priv->conn_cont.ac == cont;
+ }
+ 
+-static int iscsi_host_match(struct attribute_container *cont,
+-			    struct device *dev)
+-{
+-	struct Scsi_Host *shost;
+-	struct iscsi_internal *priv;
+-
+-	if (!scsi_is_host_device(dev))
+-		return 0;
+-
+-	shost = dev_to_shost(dev);
+-	if (!shost->transportt  ||
+-	    shost->transportt->host_attrs.ac.class != &iscsi_host_class.class)
+-		return 0;
+-
+-        priv = to_iscsi_internal(shost->transportt);
+-        return &priv->t.host_attrs.ac == cont;
+-}
+-
+ struct scsi_transport_template *
+ iscsi_register_transport(struct iscsi_transport *tt)
+ {
+@@ -1306,7 +1273,6 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	INIT_LIST_HEAD(&priv->list);
+ 	priv->daemon_pid = -1;
+ 	priv->iscsi_transport = tt;
+-	priv->t.user_scan = iscsi_user_scan;
+ 
+ 	priv->cdev.class = &iscsi_transport_class;
+ 	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
+@@ -1319,12 +1285,10 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 		goto unregister_cdev;
+ 
+ 	/* host parameters */
+-	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+-	priv->t.host_attrs.ac.class = &iscsi_host_class.class;
+-	priv->t.host_attrs.ac.match = iscsi_host_match;
++	priv->t.host_attrs = &priv->host_attrs[0];
++	priv->t.host_class = &iscsi_host_class;
++	priv->t.host_setup = iscsi_setup_host;
+ 	priv->t.host_size = sizeof(struct iscsi_host);
+-	priv->host_attrs[0] = NULL;
+-	transport_container_register(&priv->t.host_attrs);
+ 
+ 	/* connection parameters */
+ 	priv->conn_cont.ac.attrs = &priv->conn_attrs[0];
+@@ -1402,7 +1366,6 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 
+ 	transport_container_unregister(&priv->conn_cont);
+ 	transport_container_unregister(&priv->session_cont);
+-	transport_container_unregister(&priv->t.host_attrs);
+ 
+ 	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	class_device_unregister(&priv->cdev);
+@@ -1420,6 +1420,7 @@ static __init int iscsi_transport_init(void)
+ 		ISCSI_TRANSPORT_VERSION);
+ 
+	atomic_set(&iscsi_session_nr, 0);
++	attribute_container_init();
+
+	err = class_register(&iscsi_transport_class);
+ 	if (err)
+ 		return err;
diff --git a/kernel_patches/backport/2.6.9_U4/iscsi_03_add_session_wq.patch b/kernel_patches/backport/2.6.9_U4/iscsi_03_add_session_wq.patch
new file mode 100644
index 0000000..55564c5
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U4/iscsi_03_add_session_wq.patch
@@ -0,0 +1,76 @@
+diff --git a/drivers/infiniband/ulp/iser/iser_initiator.c b/drivers/infiniband/ulp/iser/iser_initiator.c
+index a6f2303..5d62cc0 100644
+--- a/drivers/infiniband/ulp/iser/iser_initiator.c
++++ b/drivers/infiniband/ulp/iser/iser_initiator.c
+@@ -610,7 +610,7 @@ void iser_snd_completion(struct iser_desc *tx_desc)
+ 
+ 	if (resume_tx) {
+ 		iser_dbg("%ld resuming tx\n",jiffies);
+-		scsi_queue_work(conn->session->host, &conn->xmitwork);
++		queue_work(conn->session->wq, &conn->xmitwork);
+ 	}
+ 
+ 	if (tx_desc->type == ISCSI_TX_CONTROL) {
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index e8020a5..43e9128 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -828,7 +828,7 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	spin_unlock(&session->lock);
+ 
+-	scsi_queue_work(host, &conn->xmitwork);
++	queue_work(session->wq, &conn->xmitwork);
+ 	return 0;
+ 
+ reject:
+@@ -928,7 +928,7 @@ iscsi_conn_send_generic(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	else
+ 	        __kfifo_put(conn->mgmtqueue, (void*)&mtask, sizeof(void*));
+ 
+-	scsi_queue_work(session->host, &conn->xmitwork);
++	queue_work(session->wq, &conn->xmitwork);
+ 	return 0;
+ }
+ 
+@@ -1415,6 +1415,9 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 		INIT_LIST_HEAD(&mtask->running);
+ 	}
+ 
++	session->wq = create_singlethread_workqueue("");
++	BUG_ON(!session->wq);
++
+ 	if (scsi_add_host(shost, NULL))
+ 		goto add_host_fail;
+ 
+@@ -1462,6 +1465,8 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session)
+ 
+ 	kfree(session->targetname);
+ 
++	destroy_workqueue(session->wq);
++
+ 	iscsi_destroy_session(cls_session);
+ 	scsi_host_put(shost);
+ 	module_put(owner);
+@@ -1595,7 +1600,7 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 
+ 	/* flush queued up work because we free the connection below */
+-	scsi_flush_work(session->host);
++	flush_workqueue(session->wq);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	kfree(conn->data);
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index ea0816d..e8a95f5 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -244,6 +244,8 @@ struct iscsi_session {
+ 	int			mgmtpool_max;	/* size of mgmt array */
+ 	struct iscsi_mgmt_task	**mgmt_cmds;	/* Original mgmt arr */
+ 	struct iscsi_queue	mgmtpool;	/* Mgmt PDU's pool */
++
++	struct workqueue_struct *wq;
+ };
+ 
+ /*
diff --git a/kernel_patches/backport/2.6.9_U4/iscsi_04_inet_sock_to_opt.patch b/kernel_patches/backport/2.6.9_U4/iscsi_04_inet_sock_to_opt.patch
new file mode 100644
index 0000000..1fb2376
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U4/iscsi_04_inet_sock_to_opt.patch
@@ -0,0 +1,13 @@
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 905efc4..f73a743 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -2027,7 +2027,7 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct inet_sock *inet;
++	struct inet_opt *inet;
+ 	struct ipv6_pinfo *np;
+ 	struct sock *sk;
+ 	int len;
diff --git a/kernel_patches/backport/2.6.9_U4/iscsi_05_release_host_lock_before_eh.patch b/kernel_patches/backport/2.6.9_U4/iscsi_05_release_host_lock_before_eh.patch
new file mode 100644
index 0000000..c994506
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U4/iscsi_05_release_host_lock_before_eh.patch
@@ -0,0 +1,60 @@
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 7db081b..211944e 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -968,12 +968,14 @@ int iscsi_eh_host_reset(struct scsi_cmnd *sc)
+ 	struct iscsi_conn *conn = session->leadconn;
+ 	int fail_session = 0;
+ 
++	spin_unlock_irq(host->host_lock);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_TERMINATE) {
+ failed:
+ 		debug_scsi("failing host reset: session terminated "
+ 			   "[CID %d age %d]\n", conn->id, session->age);
+ 		spin_unlock_bh(&session->lock);
++		spin_lock_irq(host->host_lock);
+ 		return FAILED;
+ 	}
+ 
+@@ -1005,6 +1007,7 @@ failed:
+ 	else
+ 		goto failed;
+ 	spin_unlock_bh(&session->lock);
++	spin_lock_irq(host->host_lock);
+ 
+ 	return SUCCESS;
+ }
+@@ -1162,13 +1165,16 @@ int iscsi_eh_abort(struct scsi_cmnd *sc)
+ 	struct iscsi_conn *conn;
+ 	struct iscsi_session *session;
+ 	int rc;
++	struct Scsi_Host *shost = sc->device->host;
+ 
++	spin_unlock_irq(shost->host_lock);
+ 	/*
+ 	 * if session was ISCSI_STATE_IN_RECOVERY then we may not have
+ 	 * got the command.
+ 	 */
+ 	if (!sc->SCp.ptr) {
+ 		debug_scsi("sc never reached iscsi layer or it completed.\n");
++		spin_lock_irq(shost->host_lock);
+ 		return SUCCESS;
+ 	}
+ 
+@@ -1253,6 +1259,7 @@ success_cleanup:
+ 
+ success_rel_mutex:
+ 	mutex_unlock(&conn->xmitmutex);
++	spin_lock_irq(shost->host_lock);
+ 	return SUCCESS;
+ 
+ failed:
+@@ -1260,6 +1267,7 @@ failed:
+ 	mutex_unlock(&conn->xmitmutex);
+ 
+ 	debug_scsi("abort failed [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
++	spin_lock_irq(shost->host_lock);
+ 	return FAILED;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_eh_abort);
diff --git a/kernel_patches/backport/2.6.9_U4/iscsi_06_scsi_addons.patch b/kernel_patches/backport/2.6.9_U4/iscsi_06_scsi_addons.patch
new file mode 100644
index 0000000..a114696
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U4/iscsi_06_scsi_addons.patch
@@ -0,0 +1,75 @@
+diff --git a/drivers/scsi/init.c b/drivers/scsi/init.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/init.c
+@@ -0,0 +1 @@
++#include "src/init.c"
+diff --git a/drivers/scsi/attribute_container.c b/drivers/scsi/attribute_container.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/attribute_container.c
+@@ -0,0 +1 @@
++#include "../drivers/base/attribute_container.c"
+diff --git a/drivers/scsi/transport_class.c b/drivers/scsi/transport_class.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/transport_class.c
+@@ -0,0 +1 @@
++#include "../drivers/base/transport_class.c"
+diff --git a/drivers/scsi/klist.c b/drivers/scsi/klist.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/klist.c
+@@ -0,0 +1 @@
++#include "../../lib/klist.c"
+diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi.c
+@@ -0,0 +1 @@
++#include "src/scsi.c"
+diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_lib.c
+@@ -0,0 +1 @@
++#include "src/scsi_lib.c"
+diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_scan.c
+@@ -0,0 +1 @@
++#include "src/scsi_scan.c"
+diff --git a/drivers/scsi/libiscsi_f.c b/drivers/scsi/libiscsi_f.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/libiscsi_f.c
+@@ -0,0 +1 @@
++#include "libiscsi.c"
+diff --git a/drivers/scsi/scsi_transport_iscsi_f.c b/drivers/scsi/scsi_transport_iscsi_f.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_transport_iscsi_f.c
+@@ -0,0 +1 @@
++#include "scsi_transport_iscsi.c"
+diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
+index e212608..3bf2015 100644
+--- a/drivers/scsi/Makefile
++++ b/drivers/scsi/Makefile
+@@ -3,2 +3,7 @@
+ obj-$(CONFIG_SCSI_ISCSI_ATTRS) += scsi_transport_iscsi.o
+ obj-$(CONFIG_ISCSI_TCP)        += libiscsi.o   iscsi_tcp.o
++
++CFLAGS_attribute_container.o =   $(BACKPORT_INCLUDES)/src/
++
++scsi_transport_iscsi-y := scsi_transport_iscsi_f.o scsi.o scsi_lib.o init.o klist.o attribute_container.o transport_class.o
++libiscsi-y             := libiscsi_f.o scsi_scan.o
diff --git a/kernel_patches/backport/2.6.9_U5/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch b/kernel_patches/backport/2.6.9_U5/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
new file mode 100644
index 0000000..87d18ee
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U5/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
@@ -0,0 +1,7774 @@
+From 53de31f7a414a03b7939c20237ecb6e599631338 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 2 Jun 2008 12:01:07 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            | 2313 +++++++++++++++++++----------------
+ drivers/scsi/iscsi_tcp.h            |  131 ++-
+ drivers/scsi/libiscsi.c             | 1799 +++++++++------------------
+ drivers/scsi/scsi_transport_iscsi.c |  867 +++++---------
+ include/scsi/iscsi_if.h             |   54 -
+ include/scsi/libiscsi.h             |  188 +---
+ include/scsi/scsi_transport_iscsi.h |   65 +-
+ 7 files changed, 2296 insertions(+), 3121 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..c9a3abf 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -29,15 +29,14 @@
+ #include <linux/types.h>
+ #include <linux/list.h>
+ #include <linux/inet.h>
+-#include <linux/file.h>
+ #include <linux/blkdev.h>
+ #include <linux/crypto.h>
+ #include <linux/delay.h>
+ #include <linux/kfifo.h>
+ #include <linux/scatterlist.h>
++#include <linux/mutex.h>
+ #include <net/tcp.h>
+ #include <scsi/scsi_cmnd.h>
+-#include <scsi/scsi_device.h>
+ #include <scsi/scsi_host.h>
+ #include <scsi/scsi.h>
+ #include <scsi/scsi_transport_iscsi.h>
+@@ -48,7 +47,7 @@ MODULE_AUTHOR("Dmitry Yusupov <dmitry_yus at yahoo.com>, "
+ 	      "Alex Aizman <itn780 at yahoo.com>");
+ MODULE_DESCRIPTION("iSCSI/TCP data-path");
+ MODULE_LICENSE("GPL");
+-#undef DEBUG_TCP
++/* #define DEBUG_TCP */
+ #define DEBUG_ASSERT
+ 
+ #ifdef DEBUG_TCP
+@@ -67,429 +66,118 @@ MODULE_LICENSE("GPL");
+ static unsigned int iscsi_max_lun = 512;
+ module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);
+ 
+-static int iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-				   struct iscsi_segment *segment);
+-
+-/*
+- * Scatterlist handling: inside the iscsi_segment, we
+- * remember an index into the scatterlist, and set data/size
+- * to the current scatterlist entry. For highmem pages, we
+- * kmap as needed.
+- *
+- * Note that the page is unmapped when we return from
+- * TCP's data_ready handler, so we may end up mapping and
+- * unmapping the same page repeatedly. The whole reason
+- * for this is that we shouldn't keep the page mapped
+- * outside the softirq.
+- */
+-
+-/**
+- * iscsi_tcp_segment_init_sg - init indicated scatterlist entry
+- * @segment: the buffer object
+- * @sg: scatterlist
+- * @offset: byte offset into that sg entry
+- *
+- * This function sets up the segment so that subsequent
+- * data is copied to the indicated sg entry, at the given
+- * offset.
+- */
+ static inline void
+-iscsi_tcp_segment_init_sg(struct iscsi_segment *segment,
+-			  struct scatterlist *sg, unsigned int offset)
++iscsi_buf_init_iov(struct iscsi_buf *ibuf, char *vbuf, int size)
+ {
+-	segment->sg = sg;
+-	segment->sg_offset = offset;
+-	segment->size = min(sg->length - offset,
+-			    segment->total_size - segment->total_copied);
+-	segment->data = NULL;
++	ibuf->sg.page = virt_to_page(vbuf);
++	ibuf->sg.offset = offset_in_page(vbuf);
++	ibuf->sg.length = size;
++	ibuf->sent = 0;
++	ibuf->use_sendmsg = 1;
+ }
+ 
+-/**
+- * iscsi_tcp_segment_map - map the current S/G page
+- * @segment: iscsi_segment
+- * @recv: 1 if called from recv path
+- *
+- * We only need to possibly kmap data if scatter lists are being used,
+- * because the iscsi passthrough and internal IO paths will never use high
+- * mem pages.
+- */
+ static inline void
+-iscsi_tcp_segment_map(struct iscsi_segment *segment, int recv)
++iscsi_buf_init_sg(struct iscsi_buf *ibuf, struct scatterlist *sg)
+ {
+-	struct scatterlist *sg;
+-
+-	if (segment->data != NULL || !segment->sg)
+-		return;
+-
+-	sg = segment->sg;
+-	BUG_ON(segment->sg_mapped);
+-	BUG_ON(sg->length == 0);
+-
++	ibuf->sg.page = sg->page;
++	ibuf->sg.offset = sg->offset;
++	ibuf->sg.length = sg->length;
+ 	/*
+-	 * If the page count is greater than one it is ok to send
+-	 * to the network layer's zero copy send path. If not we
+-	 * have to go the slow sendmsg path. We always map for the
+-	 * recv path.
++	 * Fastpath: sg element fits into single page
+ 	 */
+-	if (page_count(sg_page(sg)) >= 1 && !recv)
+-		return;
+-
+-	debug_tcp("iscsi_tcp_segment_map %s %p\n", recv ? "recv" : "xmit",
+-		  segment);
+-	segment->sg_mapped = kmap_atomic(sg_page(sg), KM_SOFTIRQ0);
+-	segment->data = segment->sg_mapped + sg->offset + segment->sg_offset;
++	if (sg->length + sg->offset <= PAGE_SIZE && !PageSlab(sg->page))
++		ibuf->use_sendmsg = 0;
++	else
++		ibuf->use_sendmsg = 1;
++	ibuf->sent = 0;
+ }
+ 
+-static inline void
+-iscsi_tcp_segment_unmap(struct iscsi_segment *segment)
++static inline int
++iscsi_buf_left(struct iscsi_buf *ibuf)
+ {
+-	debug_tcp("iscsi_tcp_segment_unmap %p\n", segment);
++	int rc;
+ 
+-	if (segment->sg_mapped) {
+-		debug_tcp("iscsi_tcp_segment_unmap valid\n");
+-		kunmap_atomic(segment->sg_mapped, KM_SOFTIRQ0);
+-		segment->sg_mapped = NULL;
+-		segment->data = NULL;
+-	}
++	rc = ibuf->sg.length - ibuf->sent;
++	BUG_ON(rc < 0);
++	return rc;
+ }
+ 
+-/*
+- * Splice the digest buffer into the buffer
+- */
+ static inline void
+-iscsi_tcp_segment_splice_digest(struct iscsi_segment *segment, void *digest)
++iscsi_hdr_digest(struct iscsi_conn *conn, struct iscsi_buf *buf,
++		 u8* crc)
+ {
+-	segment->data = digest;
+-	segment->digest_len = ISCSI_DIGEST_SIZE;
+-	segment->total_size += ISCSI_DIGEST_SIZE;
+-	segment->size = ISCSI_DIGEST_SIZE;
+-	segment->copied = 0;
+-	segment->sg = NULL;
+-	segment->hash = NULL;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++
++	crypto_hash_digest(&tcp_conn->tx_hash, &buf->sg, buf->sg.length, crc);
++	buf->sg.length = tcp_conn->hdr_size;
+ }
+ 
+-/**
+- * iscsi_tcp_segment_done - check whether the segment is complete
+- * @segment: iscsi segment to check
+- * @recv: set to one of this is called from the recv path
+- * @copied: number of bytes copied
+- *
+- * Check if we're done receiving this segment. If the receive
+- * buffer is full but we expect more data, move on to the
+- * next entry in the scatterlist.
+- *
+- * If the amount of data we received isn't a multiple of 4,
+- * we will transparently receive the pad bytes, too.
+- *
+- * This function must be re-entrant.
+- */
+ static inline int
+-iscsi_tcp_segment_done(struct iscsi_segment *segment, int recv, unsigned copied)
++iscsi_hdr_extract(struct iscsi_tcp_conn *tcp_conn)
+ {
+-	static unsigned char padbuf[ISCSI_PAD_LEN];
+-	struct scatterlist sg;
+-	unsigned int pad;
++	struct sk_buff *skb = tcp_conn->in.skb;
++
++	tcp_conn->in.zero_copy_hdr = 0;
+ 
+-	debug_tcp("copied %u %u size %u %s\n", segment->copied, copied,
+-		  segment->size, recv ? "recv" : "xmit");
+-	if (segment->hash && copied) {
++	if (tcp_conn->in.copy >= tcp_conn->hdr_size &&
++	    tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER) {
+ 		/*
+-		 * If a segment is kmapd we must unmap it before sending
+-		 * to the crypto layer since that will try to kmap it again.
++		 * Zero-copy PDU Header: using connection context
++		 * to store header pointer.
+ 		 */
+-		iscsi_tcp_segment_unmap(segment);
+-
+-		if (!segment->data) {
+-			sg_init_table(&sg, 1);
+-			sg_set_page(&sg, sg_page(segment->sg), copied,
+-				    segment->copied + segment->sg_offset +
+-							segment->sg->offset);
+-		} else
+-			sg_init_one(&sg, segment->data + segment->copied,
+-				    copied);
+-		crypto_hash_update(segment->hash, &sg, copied);
+-	}
+-
+-	segment->copied += copied;
+-	if (segment->copied < segment->size) {
+-		iscsi_tcp_segment_map(segment, recv);
+-		return 0;
+-	}
+-
+-	segment->total_copied += segment->copied;
+-	segment->copied = 0;
+-	segment->size = 0;
+-
+-	/* Unmap the current scatterlist page, if there is one. */
+-	iscsi_tcp_segment_unmap(segment);
+-
+-	/* Do we have more scatterlist entries? */
+-	debug_tcp("total copied %u total size %u\n", segment->total_copied,
+-		   segment->total_size);
+-	if (segment->total_copied < segment->total_size) {
+-		/* Proceed to the next entry in the scatterlist. */
+-		iscsi_tcp_segment_init_sg(segment, sg_next(segment->sg),
+-					  0);
+-		iscsi_tcp_segment_map(segment, recv);
+-		BUG_ON(segment->size == 0);
+-		return 0;
+-	}
+-
+-	/* Do we need to handle padding? */
+-	pad = iscsi_padding(segment->total_copied);
+-	if (pad != 0) {
+-		debug_tcp("consume %d pad bytes\n", pad);
+-		segment->total_size += pad;
+-		segment->size = pad;
+-		segment->data = padbuf;
+-		return 0;
+-	}
+-
+-	/*
+-	 * Set us up for transferring the data digest. hdr digest
+-	 * is completely handled in hdr done function.
+-	 */
+-	if (segment->hash) {
+-		crypto_hash_final(segment->hash, segment->digest);
+-		iscsi_tcp_segment_splice_digest(segment,
+-				 recv ? segment->recv_digest : segment->digest);
+-		return 0;
+-	}
+-
+-	return 1;
+-}
+-
+-/**
+- * iscsi_tcp_xmit_segment - transmit segment
+- * @tcp_conn: the iSCSI TCP connection
+- * @segment: the buffer to transmnit
+- *
+- * This function transmits as much of the buffer as
+- * the network layer will accept, and returns the number of
+- * bytes transmitted.
+- *
+- * If CRC hashing is enabled, the function will compute the
+- * hash as it goes. When the entire segment has been transmitted,
+- * it will retrieve the hash value and send it as well.
+- */
+-static int
+-iscsi_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn,
+-		       struct iscsi_segment *segment)
+-{
+-	struct socket *sk = tcp_conn->sock;
+-	unsigned int copied = 0;
+-	int r = 0;
+-
+-	while (!iscsi_tcp_segment_done(segment, 0, r)) {
+-		struct scatterlist *sg;
+-		unsigned int offset, copy;
+-		int flags = 0;
+-
+-		r = 0;
+-		offset = segment->copied;
+-		copy = segment->size - offset;
+-
+-		if (segment->total_copied + segment->size < segment->total_size)
+-			flags |= MSG_MORE;
+-
+-		/* Use sendpage if we can; else fall back to sendmsg */
+-		if (!segment->data) {
+-			sg = segment->sg;
+-			offset += segment->sg_offset + sg->offset;
+-			r = tcp_conn->sendpage(sk, sg_page(sg), offset, copy,
+-					       flags);
++		if (skb_shinfo(skb)->frag_list == NULL &&
++		    !skb_shinfo(skb)->nr_frags) {
++			tcp_conn->in.hdr = (struct iscsi_hdr *)
++				((char*)skb->data + tcp_conn->in.offset);
++			tcp_conn->in.zero_copy_hdr = 1;
+ 		} else {
+-			struct msghdr msg = { .msg_flags = flags };
+-			struct kvec iov = {
+-				.iov_base = segment->data + offset,
+-				.iov_len = copy
+-			};
+-
+-			r = kernel_sendmsg(sk, &msg, &iov, 1, copy);
+-		}
+-
+-		if (r < 0) {
+-			iscsi_tcp_segment_unmap(segment);
+-			if (copied || r == -EAGAIN)
+-				break;
+-			return r;
++			/* ignoring return code since we checked
++			 * in.copy before */
++			skb_copy_bits(skb, tcp_conn->in.offset,
++				&tcp_conn->hdr, tcp_conn->hdr_size);
++			tcp_conn->in.hdr = &tcp_conn->hdr;
+ 		}
+-		copied += r;
+-	}
+-	return copied;
+-}
+-
+-/**
+- * iscsi_tcp_segment_recv - copy data to segment
+- * @tcp_conn: the iSCSI TCP connection
+- * @segment: the buffer to copy to
+- * @ptr: data pointer
+- * @len: amount of data available
+- *
+- * This function copies up to @len bytes to the
+- * given buffer, and returns the number of bytes
+- * consumed, which can actually be less than @len.
+- *
+- * If hash digest is enabled, the function will update the
+- * hash while copying.
+- * Combining these two operations doesn't buy us a lot (yet),
+- * but in the future we could implement combined copy+crc,
+- * just way we do for network layer checksums.
+- */
+-static int
+-iscsi_tcp_segment_recv(struct iscsi_tcp_conn *tcp_conn,
+-		       struct iscsi_segment *segment, const void *ptr,
+-		       unsigned int len)
+-{
+-	unsigned int copy = 0, copied = 0;
+-
+-	while (!iscsi_tcp_segment_done(segment, 1, copy)) {
+-		if (copied == len) {
+-			debug_tcp("iscsi_tcp_segment_recv copied %d bytes\n",
+-				  len);
+-			break;
+-		}
+-
+-		copy = min(len - copied, segment->size - segment->copied);
+-		debug_tcp("iscsi_tcp_segment_recv copying %d\n", copy);
+-		memcpy(segment->data + segment->copied, ptr + copied, copy);
+-		copied += copy;
+-	}
+-	return copied;
+-}
+-
+-static inline void
+-iscsi_tcp_dgst_header(struct hash_desc *hash, const void *hdr, size_t hdrlen,
+-		      unsigned char digest[ISCSI_DIGEST_SIZE])
+-{
+-	struct scatterlist sg;
++		tcp_conn->in.offset += tcp_conn->hdr_size;
++		tcp_conn->in.copy -= tcp_conn->hdr_size;
++	} else {
++		int hdr_remains;
++		int copylen;
+ 
+-	sg_init_one(&sg, hdr, hdrlen);
+-	crypto_hash_digest(hash, &sg, hdrlen, digest);
+-}
++		/*
++		 * PDU header scattered across SKB's,
++		 * copying it... This'll happen quite rarely.
++		 */
+ 
+-static inline int
+-iscsi_tcp_dgst_verify(struct iscsi_tcp_conn *tcp_conn,
+-		      struct iscsi_segment *segment)
+-{
+-	if (!segment->digest_len)
+-		return 1;
++		if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER)
++			tcp_conn->in.hdr_offset = 0;
+ 
+-	if (memcmp(segment->recv_digest, segment->digest,
+-		   segment->digest_len)) {
+-		debug_scsi("digest mismatch\n");
+-		return 0;
+-	}
++		hdr_remains = tcp_conn->hdr_size - tcp_conn->in.hdr_offset;
++		BUG_ON(hdr_remains <= 0);
+ 
+-	return 1;
+-}
++		copylen = min(tcp_conn->in.copy, hdr_remains);
++		skb_copy_bits(skb, tcp_conn->in.offset,
++			(char*)&tcp_conn->hdr + tcp_conn->in.hdr_offset,
++			copylen);
+ 
+-/*
+- * Helper function to set up segment buffer
+- */
+-static inline void
+-__iscsi_segment_init(struct iscsi_segment *segment, size_t size,
+-		     iscsi_segment_done_fn_t *done, struct hash_desc *hash)
+-{
+-	memset(segment, 0, sizeof(*segment));
+-	segment->total_size = size;
+-	segment->done = done;
+-
+-	if (hash) {
+-		segment->hash = hash;
+-		crypto_hash_init(hash);
+-	}
+-}
++		debug_tcp("PDU gather offset %d bytes %d in.offset %d "
++		       "in.copy %d\n", tcp_conn->in.hdr_offset, copylen,
++		       tcp_conn->in.offset, tcp_conn->in.copy);
+ 
+-static inline void
+-iscsi_segment_init_linear(struct iscsi_segment *segment, void *data,
+-			  size_t size, iscsi_segment_done_fn_t *done,
+-			  struct hash_desc *hash)
+-{
+-	__iscsi_segment_init(segment, size, done, hash);
+-	segment->data = data;
+-	segment->size = size;
+-}
+-
+-static inline int
+-iscsi_segment_seek_sg(struct iscsi_segment *segment,
+-		      struct scatterlist *sg_list, unsigned int sg_count,
+-		      unsigned int offset, size_t size,
+-		      iscsi_segment_done_fn_t *done, struct hash_desc *hash)
+-{
+-	struct scatterlist *sg;
+-	unsigned int i;
+-
+-	debug_scsi("iscsi_segment_seek_sg offset %u size %llu\n",
+-		  offset, size);
+-	__iscsi_segment_init(segment, size, done, hash);
+-	for_each_sg(sg_list, sg, sg_count, i) {
+-		debug_scsi("sg %d, len %u offset %u\n", i, sg->length,
+-			   sg->offset);
+-		if (offset < sg->length) {
+-			iscsi_tcp_segment_init_sg(segment, sg, offset);
+-			return 0;
++		tcp_conn->in.offset += copylen;
++		tcp_conn->in.copy -= copylen;
++		if (copylen < hdr_remains)  {
++			tcp_conn->in_progress = IN_PROGRESS_HEADER_GATHER;
++			tcp_conn->in.hdr_offset += copylen;
++		        return -EAGAIN;
+ 		}
+-		offset -= sg->length;
++		tcp_conn->in.hdr = &tcp_conn->hdr;
++		tcp_conn->discontiguous_hdr_cnt++;
++	        tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 	}
+ 
+-	return ISCSI_ERR_DATA_OFFSET;
+-}
+-
+-/**
+- * iscsi_tcp_hdr_recv_prep - prep segment for hdr reception
+- * @tcp_conn: iscsi connection to prep for
+- *
+- * This function always passes NULL for the hash argument, because when this
+- * function is called we do not yet know the final size of the header and want
+- * to delay the digest processing until we know that.
+- */
+-static void
+-iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn)
+-{
+-	debug_tcp("iscsi_tcp_hdr_recv_prep(%p%s)\n", tcp_conn,
+-		  tcp_conn->iscsi_conn->hdrdgst_en ? ", digest enabled" : "");
+-	iscsi_segment_init_linear(&tcp_conn->in.segment,
+-				tcp_conn->in.hdr_buf, sizeof(struct iscsi_hdr),
+-				iscsi_tcp_hdr_recv_done, NULL);
+-}
+-
+-/*
+- * Handle incoming reply to any other type of command
+- */
+-static int
+-iscsi_tcp_data_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-			 struct iscsi_segment *segment)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	int rc = 0;
+-
+-	if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-		return ISCSI_ERR_DATA_DGST;
+-
+-	rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr,
+-			conn->data, tcp_conn->in.datalen);
+-	if (rc)
+-		return rc;
+-
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+ 	return 0;
+ }
+ 
+-static void
+-iscsi_tcp_data_recv_prep(struct iscsi_tcp_conn *tcp_conn)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct hash_desc *rx_hash = NULL;
+-
+-	if (conn->datadgst_en)
+-		rx_hash = &tcp_conn->rx_hash;
+-
+-	iscsi_segment_init_linear(&tcp_conn->in.segment,
+-				conn->data, tcp_conn->in.datalen,
+-				iscsi_tcp_data_recv_done, rx_hash);
+-}
+-
+ /*
+  * must be called with session lock
+  */
+@@ -498,6 +186,7 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct iscsi_r2t_info *r2t;
++	struct scsi_cmnd *sc;
+ 
+ 	/* flush ctask's r2t queues */
+ 	while (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*))) {
+@@ -506,12 +195,12 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 		debug_scsi("iscsi_tcp_cleanup_ctask pending r2t dropped\n");
+ 	}
+ 
+-	r2t = tcp_ctask->r2t;
+-	if (r2t != NULL) {
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
+-		tcp_ctask->r2t = NULL;
+-	}
++	sc = ctask->sc;
++	if (unlikely(!sc))
++		return;
++
++	tcp_ctask->xmstate = XMSTATE_IDLE;
++	tcp_ctask->r2t = NULL;
+ }
+ 
+ /**
+@@ -522,49 +211,52 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ static int
+ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
++	int rc;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)tcp_conn->in.hdr;
+ 	struct iscsi_session *session = conn->session;
+-	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc)
++		return rc;
++	/*
++	 * setup Data-In byte counter (gets decremented..)
++	 */
++	ctask->data_count = tcp_conn->in.datalen;
++
+ 	if (tcp_conn->in.datalen == 0)
+ 		return 0;
+ 
+-	if (tcp_ctask->exp_datasn != datasn) {
+-		debug_tcp("%s: ctask->exp_datasn(%d) != rhdr->datasn(%d)\n",
+-		          __FUNCTION__, tcp_ctask->exp_datasn, datasn);
++	if (ctask->datasn != datasn)
+ 		return ISCSI_ERR_DATASN;
+-	}
+ 
+-	tcp_ctask->exp_datasn++;
++	ctask->datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
+-		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+-		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > ctask->total_length)
+ 		return ISCSI_ERR_DATA_OFFSET;
+-	}
+ 
+ 	if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) {
+-		sc->result = (DID_OK << 16) | rhdr->cmd_status;
++		struct scsi_cmnd *sc = ctask->sc;
++
+ 		conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+-		if (rhdr->flags & (ISCSI_FLAG_DATA_UNDERFLOW |
+-		                   ISCSI_FLAG_DATA_OVERFLOW)) {
++		if (rhdr->flags & ISCSI_FLAG_DATA_UNDERFLOW) {
+ 			int res_count = be32_to_cpu(rhdr->residual_count);
+ 
+ 			if (res_count > 0 &&
+-			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
+-			else
++			    res_count <= sc->request_bufflen) {
++				sc->resid = res_count;
++				sc->result = (DID_OK << 16) | rhdr->cmd_status;
++			} else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+-		}
++		} else if (rhdr->flags & ISCSI_FLAG_DATA_OVERFLOW) {
++			sc->resid = be32_to_cpu(rhdr->residual_count);
++			sc->result = (DID_OK << 16) | rhdr->cmd_status;
++		} else
++			sc->result = (DID_OK << 16) | rhdr->cmd_status;
+ 	}
+ 
+ 	conn->datain_pdus_cnt++;
+@@ -588,6 +280,7 @@ iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 			struct iscsi_r2t_info *r2t)
+ {
+ 	struct iscsi_data *hdr;
++	struct scsi_cmnd *sc = ctask->sc;
+ 
+ 	hdr = &r2t->dtask.hdr;
+ 	memset(hdr, 0, sizeof(struct iscsi_data));
+@@ -611,6 +304,43 @@ iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 	conn->dataout_pdus_cnt++;
+ 
+ 	r2t->sent = 0;
++
++	iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr,
++			   sizeof(struct iscsi_hdr));
++
++	if (sc->use_sg) {
++		int i, sg_count = 0;
++		struct scatterlist *sg = sc->request_buffer;
++
++		r2t->sg = NULL;
++		for (i = 0; i < sc->use_sg; i++, sg += 1) {
++			/* FIXME: prefetch ? */
++			if (sg_count + sg->length > r2t->data_offset) {
++				int page_offset;
++
++				/* sg page found! */
++
++				/* offset within this page */
++				page_offset = r2t->data_offset - sg_count;
++
++				/* fill in this buffer */
++				iscsi_buf_init_sg(&r2t->sendbuf, sg);
++				r2t->sendbuf.sg.offset += page_offset;
++				r2t->sendbuf.sg.length -= page_offset;
++
++				/* xmit logic will continue with next one */
++				r2t->sg = sg + 1;
++				break;
++			}
++			sg_count += sg->length;
++		}
++		BUG_ON(r2t->sg == NULL);
++	} else {
++		iscsi_buf_init_iov(&r2t->sendbuf,
++			    (char*)sc->request_buffer + r2t->data_offset,
++			    r2t->data_count);
++		r2t->sg = NULL;
++	}
+ }
+ 
+ /**
+@@ -630,25 +360,27 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	int rc;
+ 
+ 	if (tcp_conn->in.datalen) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2t with datalen %d\n",
+-				  tcp_conn->in.datalen);
++		printk(KERN_ERR "iscsi_tcp: invalid R2t with datalen %d\n",
++		       tcp_conn->in.datalen);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+-	if (tcp_ctask->exp_datasn != r2tsn){
+-		debug_tcp("%s: ctask->exp_datasn(%d) != rhdr->r2tsn(%d)\n",
+-		          __FUNCTION__, tcp_ctask->exp_datasn, r2tsn);
++	if (tcp_ctask->exp_r2tsn && tcp_ctask->exp_r2tsn != r2tsn)
+ 		return ISCSI_ERR_R2TSN;
+-	}
+ 
+-	/* fill-in new R2T associated with the task */
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc)
++		return rc;
+ 
+-	if (!ctask->sc || session->state != ISCSI_STATE_LOGGED_IN) {
+-		iscsi_conn_printk(KERN_INFO, conn,
+-				  "dropping R2T itt %d in recovery.\n",
+-				  ctask->itt);
++	/* FIXME: use R2TSN to detect missing R2T */
++
++	/* fill-in new R2T associated with the task */
++	spin_lock(&session->lock);
++	if (!ctask->sc || ctask->mtask ||
++	     session->state != ISCSI_STATE_LOGGED_IN) {
++		printk(KERN_INFO "iscsi_tcp: dropping R2T itt %d in "
++		       "recovery...\n", ctask->itt);
++		spin_unlock(&session->lock);
+ 		return 0;
+ 	}
+ 
+@@ -658,10 +390,8 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	r2t->exp_statsn = rhdr->statsn;
+ 	r2t->data_length = be32_to_cpu(rhdr->data_length);
+ 	if (r2t->data_length == 0) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2T with zero data len\n");
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
++		printk(KERN_ERR "iscsi_tcp: invalid R2T with zero data len\n");
++		spin_unlock(&session->lock);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+@@ -671,13 +401,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2T with data len %u at offset %u "
+-				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
++	if (r2t->data_offset + r2t->data_length > ctask->total_length) {
++		spin_unlock(&session->lock);
++		printk(KERN_ERR "iscsi_tcp: invalid R2T with data len %u at "
++		       "offset %u and total length %d\n", r2t->data_length,
++		       r2t->data_offset, ctask->total_length);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+@@ -686,134 +414,108 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 	iscsi_solicit_data_init(conn, ctask, r2t);
+ 
+-	tcp_ctask->exp_datasn = r2tsn + 1;
++	tcp_ctask->exp_r2tsn = r2tsn + 1;
+ 	__kfifo_put(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*));
+-	conn->r2t_pdus_cnt++;
++	tcp_ctask->xmstate |= XMSTATE_SOL_HDR;
++	list_move_tail(&ctask->running, &conn->xmitqueue);
+ 
+-	iscsi_requeue_ctask(ctask);
+-	return 0;
+-}
+-
+-/*
+- * Handle incoming reply to DataIn command
+- */
+-static int
+-iscsi_tcp_process_data_in(struct iscsi_tcp_conn *tcp_conn,
+-			  struct iscsi_segment *segment)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct iscsi_hdr *hdr = tcp_conn->in.hdr;
+-	int rc;
+-
+-	if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-		return ISCSI_ERR_DATA_DGST;
+-
+-	/* check for non-exceptional status */
+-	if (hdr->flags & ISCSI_FLAG_DATA_STATUS) {
+-		rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
+-		if (rc)
+-			return rc;
+-	}
++	scsi_queue_work(session->host, &conn->xmitwork);
++	conn->r2t_pdus_cnt++;
++	spin_unlock(&session->lock);
+ 
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+ 	return 0;
+ }
+ 
+-/**
+- * iscsi_tcp_hdr_dissect - process PDU header
+- * @conn: iSCSI connection
+- * @hdr: PDU header
+- *
+- * This function analyzes the header of the PDU received,
+- * and performs several sanity checks. If the PDU is accompanied
+- * by data, the receive buffer is set up to copy the incoming data
+- * to the correct location.
+- */
+ static int
+-iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
++iscsi_tcp_hdr_recv(struct iscsi_conn *conn)
+ {
+ 	int rc = 0, opcode, ahslen;
++	struct iscsi_hdr *hdr;
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_cmd_task *ctask;
+-	uint32_t itt;
++	uint32_t cdgst, rdgst = 0, itt;
++
++	hdr = tcp_conn->in.hdr;
+ 
+ 	/* verify PDU length */
+ 	tcp_conn->in.datalen = ntoh24(hdr->dlength);
+ 	if (tcp_conn->in.datalen > conn->max_recv_dlength) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "iscsi_tcp: datalen %d > %d\n",
+-				  tcp_conn->in.datalen, conn->max_recv_dlength);
++		printk(KERN_ERR "iscsi_tcp: datalen %d > %d\n",
++		       tcp_conn->in.datalen, conn->max_recv_dlength);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
++	tcp_conn->data_copied = 0;
+ 
+-	/* Additional header segments. So far, we don't
+-	 * process additional headers.
+-	 */
++	/* read AHS */
+ 	ahslen = hdr->hlength << 2;
++	tcp_conn->in.offset += ahslen;
++	tcp_conn->in.copy -= ahslen;
++	if (tcp_conn->in.copy < 0) {
++		printk(KERN_ERR "iscsi_tcp: can't handle AHS with length "
++		       "%d bytes\n", ahslen);
++		return ISCSI_ERR_AHSLEN;
++	}
++
++	/* calculate read padding */
++	tcp_conn->in.padding = tcp_conn->in.datalen & (ISCSI_PAD_LEN-1);
++	if (tcp_conn->in.padding) {
++		tcp_conn->in.padding = ISCSI_PAD_LEN - tcp_conn->in.padding;
++		debug_scsi("read padding %d bytes\n", tcp_conn->in.padding);
++	}
++
++	if (conn->hdrdgst_en) {
++		struct scatterlist sg;
++
++		sg_init_one(&sg, (u8 *)hdr,
++			    sizeof(struct iscsi_hdr) + ahslen);
++		crypto_hash_digest(&tcp_conn->rx_hash, &sg, sg.length,
++				   (u8 *)&cdgst);
++		rdgst = *(uint32_t*)((char*)hdr + sizeof(struct iscsi_hdr) +
++				     ahslen);
++		if (cdgst != rdgst) {
++			printk(KERN_ERR "iscsi_tcp: hdrdgst error "
++			       "recv 0x%x calc 0x%x\n", rdgst, cdgst);
++			return ISCSI_ERR_HDR_DGST;
++		}
++	}
+ 
+ 	opcode = hdr->opcode & ISCSI_OPCODE_MASK;
+ 	/* verify itt (itt encoding: age+cid+itt) */
+ 	rc = iscsi_verify_itt(conn, hdr, &itt);
+-	if (rc)
++	if (rc == ISCSI_ERR_NO_SCSI_CMD) {
++		tcp_conn->in.datalen = 0; /* force drop */
++		return 0;
++	} else if (rc)
+ 		return rc;
+ 
+-	debug_tcp("opcode 0x%x ahslen %d datalen %d\n",
+-		  opcode, ahslen, tcp_conn->in.datalen);
++	debug_tcp("opcode 0x%x offset %d copy %d ahslen %d datalen %d\n",
++		  opcode, tcp_conn->in.offset, tcp_conn->in.copy,
++		  ahslen, tcp_conn->in.datalen);
+ 
+ 	switch(opcode) {
+ 	case ISCSI_OP_SCSI_DATA_IN:
+-		ctask = session->cmds[itt];
+-		spin_lock(&conn->session->lock);
+-		rc = iscsi_data_rsp(conn, ctask);
+-		spin_unlock(&conn->session->lock);
++		tcp_conn->in.ctask = session->cmds[itt];
++		rc = iscsi_data_rsp(conn, tcp_conn->in.ctask);
+ 		if (rc)
+ 			return rc;
+-		if (tcp_conn->in.datalen) {
+-			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+-
+-			/*
+-			 * Setup copy of Data-In into the Scsi_Cmnd
+-			 * Scatterlist case:
+-			 * We set up the iscsi_segment to point to the next
+-			 * scatterlist entry to copy to. As we go along,
+-			 * we move on to the next scatterlist entry and
+-			 * update the digest per-entry.
+-			 */
+-			if (conn->datadgst_en)
+-				rx_hash = &tcp_conn->rx_hash;
+-
+-			debug_tcp("iscsi_tcp_begin_data_in(%p, offset=%d, "
+-				  "datalen=%d)\n", tcp_conn,
+-				  tcp_ctask->data_offset,
+-				  tcp_conn->in.datalen);
+-			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
+-						     tcp_ctask->data_offset,
+-						     tcp_conn->in.datalen,
+-						     iscsi_tcp_process_data_in,
+-						     rx_hash);
+-		}
+ 		/* fall through */
+ 	case ISCSI_OP_SCSI_CMD_RSP:
+-		if (tcp_conn->in.datalen) {
+-			iscsi_tcp_data_recv_prep(tcp_conn);
+-			return 0;
+-		}
+-		rc = iscsi_complete_pdu(conn, hdr, NULL, 0);
++		tcp_conn->in.ctask = session->cmds[itt];
++		if (tcp_conn->in.datalen)
++			goto copy_hdr;
++
++		spin_lock(&session->lock);
++		rc = __iscsi_complete_pdu(conn, hdr, NULL, 0);
++		spin_unlock(&session->lock);
+ 		break;
+ 	case ISCSI_OP_R2T:
+-		ctask = session->cmds[itt];
++		tcp_conn->in.ctask = session->cmds[itt];
+ 		if (ahslen)
+ 			rc = ISCSI_ERR_AHSLEN;
+-		else if (ctask->sc->sc_data_direction == DMA_TO_DEVICE) {
+-			spin_lock(&session->lock);
+-			rc = iscsi_r2t_rsp(conn, ctask);
+-			spin_unlock(&session->lock);
+-		} else
++		else if (tcp_conn->in.ctask->sc->sc_data_direction ==
++								DMA_TO_DEVICE)
++			rc = iscsi_r2t_rsp(conn, tcp_conn->in.ctask);
++		else
+ 			rc = ISCSI_ERR_PROTO;
+ 		break;
+ 	case ISCSI_OP_LOGIN_RSP:
+@@ -825,24 +527,18 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		 * than 8K, but there are no targets that currently do this.
+ 		 * For now we fail until we find a vendor that needs it
+ 		 */
+-		if (ISCSI_DEF_MAX_RECV_SEG_LEN < tcp_conn->in.datalen) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "iscsi_tcp: received buffer of "
+-					  "len %u but conn buffer is only %u "
+-					  "(opcode %0x)\n",
+-					  tcp_conn->in.datalen,
+-					  ISCSI_DEF_MAX_RECV_SEG_LEN, opcode);
++		if (ISCSI_DEF_MAX_RECV_SEG_LEN <
++		    tcp_conn->in.datalen) {
++			printk(KERN_ERR "iscsi_tcp: received buffer of len %u "
++			      "but conn buffer is only %u (opcode %0x)\n",
++			      tcp_conn->in.datalen,
++			      ISCSI_DEF_MAX_RECV_SEG_LEN, opcode);
+ 			rc = ISCSI_ERR_PROTO;
+ 			break;
+ 		}
+ 
+-		/* If there's data coming in with the response,
+-		 * receive it to the connection's buffer.
+-		 */
+-		if (tcp_conn->in.datalen) {
+-			iscsi_tcp_data_recv_prep(tcp_conn);
+-			return 0;
+-		}
++		if (tcp_conn->in.datalen)
++			goto copy_hdr;
+ 	/* fall through */
+ 	case ISCSI_OP_LOGOUT_RSP:
+ 	case ISCSI_OP_NOOP_IN:
+@@ -854,161 +550,457 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		break;
+ 	}
+ 
+-	if (rc == 0) {
+-		/* Anything that comes with data should have
+-		 * been handled above. */
+-		if (tcp_conn->in.datalen)
+-			return ISCSI_ERR_PROTO;
+-		iscsi_tcp_hdr_recv_prep(tcp_conn);
++	return rc;
++
++copy_hdr:
++	/*
++	 * if we did zero copy for the header but we will need multiple
++	 * skbs to complete the command then we have to copy the header
++	 * for later use
++	 */
++	if (tcp_conn->in.zero_copy_hdr && tcp_conn->in.copy <=
++	   (tcp_conn->in.datalen + tcp_conn->in.padding +
++	    (conn->datadgst_en ? 4 : 0))) {
++		debug_tcp("Copying header for later use. in.copy %d in.datalen"
++			  " %d\n", tcp_conn->in.copy, tcp_conn->in.datalen);
++		memcpy(&tcp_conn->hdr, tcp_conn->in.hdr,
++		       sizeof(struct iscsi_hdr));
++		tcp_conn->in.hdr = &tcp_conn->hdr;
++		tcp_conn->in.zero_copy_hdr = 0;
+ 	}
++	return 0;
++}
+ 
+-	return rc;
++/**
++ * iscsi_ctask_copy - copy skb bits to the destanation cmd task
++ * @conn: iscsi tcp connection
++ * @ctask: scsi command task
++ * @buf: buffer to copy to
++ * @buf_size: size of buffer
++ * @offset: offset within the buffer
++ *
++ * Notes:
++ *	The function calls skb_copy_bits() and updates per-connection and
++ *	per-cmd byte counters.
++ *
++ *	Read counters (in bytes):
++ *
++ *	conn->in.offset		offset within in progress SKB
++ *	conn->in.copy		left to copy from in progress SKB
++ *				including padding
++ *	conn->in.copied		copied already from in progress SKB
++ *	conn->data_copied	copied already from in progress buffer
++ *	ctask->sent		total bytes sent up to the MidLayer
++ *	ctask->data_count	left to copy from in progress Data-In
++ *	buf_left		left to copy from in progress buffer
++ **/
++static inline int
++iscsi_ctask_copy(struct iscsi_tcp_conn *tcp_conn, struct iscsi_cmd_task *ctask,
++		void *buf, int buf_size, int offset)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int buf_left = buf_size - (tcp_conn->data_copied + offset);
++	int size = min(tcp_conn->in.copy, buf_left);
++	int rc;
++
++	size = min(size, ctask->data_count);
++
++	debug_tcp("ctask_copy %d bytes at offset %d copied %d\n",
++	       size, tcp_conn->in.offset, tcp_conn->in.copied);
++
++	BUG_ON(size <= 0);
++	BUG_ON(tcp_ctask->sent + size > ctask->total_length);
++
++	rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
++			   (char*)buf + (offset + tcp_conn->data_copied), size);
++	/* must fit into skb->len */
++	BUG_ON(rc);
++
++	tcp_conn->in.offset += size;
++	tcp_conn->in.copy -= size;
++	tcp_conn->in.copied += size;
++	tcp_conn->data_copied += size;
++	tcp_ctask->sent += size;
++	ctask->data_count -= size;
++
++	BUG_ON(tcp_conn->in.copy < 0);
++	BUG_ON(ctask->data_count < 0);
++
++	if (buf_size != (tcp_conn->data_copied + offset)) {
++		if (!ctask->data_count) {
++			BUG_ON(buf_size - tcp_conn->data_copied < 0);
++			/* done with this PDU */
++			return buf_size - tcp_conn->data_copied;
++		}
++		return -EAGAIN;
++	}
++
++	/* done with this buffer or with both - PDU and buffer */
++	tcp_conn->data_copied = 0;
++	return 0;
+ }
+ 
+ /**
+- * iscsi_tcp_hdr_recv_done - process PDU header
++ * iscsi_tcp_copy - copy skb bits to the destanation buffer
++ * @conn: iscsi tcp connection
+  *
+- * This is the callback invoked when the PDU header has
+- * been received. If the header is followed by additional
+- * header segments, we go back for more data.
+- */
+-static int
+-iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-			struct iscsi_segment *segment)
++ * Notes:
++ *	The function calls skb_copy_bits() and updates per-connection
++ *	byte counters.
++ **/
++static inline int
++iscsi_tcp_copy(struct iscsi_conn *conn, int buf_size)
+ {
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct iscsi_hdr *hdr;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int buf_left = buf_size - tcp_conn->data_copied;
++	int size = min(tcp_conn->in.copy, buf_left);
++	int rc;
++
++	debug_tcp("tcp_copy %d bytes at offset %d copied %d\n",
++	       size, tcp_conn->in.offset, tcp_conn->data_copied);
++	BUG_ON(size <= 0);
++
++	rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
++			   (char*)conn->data + tcp_conn->data_copied, size);
++	BUG_ON(rc);
++
++	tcp_conn->in.offset += size;
++	tcp_conn->in.copy -= size;
++	tcp_conn->in.copied += size;
++	tcp_conn->data_copied += size;
++
++	if (buf_size != tcp_conn->data_copied)
++		return -EAGAIN;
++
++	return 0;
++}
++
++static inline void
++partial_sg_digest_update(struct hash_desc *desc, struct scatterlist *sg,
++			 int offset, int length)
++{
++	struct scatterlist temp;
++
++	memcpy(&temp, sg, sizeof(struct scatterlist));
++	temp.offset = offset;
++	temp.length = length;
++	crypto_hash_update(desc, &temp, length);
++}
++
++static void
++iscsi_recv_digest_update(struct iscsi_tcp_conn *tcp_conn, char* buf, int len)
++{
++	struct scatterlist tmp;
++
++	sg_init_one(&tmp, buf, len);
++	crypto_hash_update(&tcp_conn->rx_hash, &tmp, len);
++}
+ 
+-	/* Check if there are additional header segments
+-	 * *prior* to computing the digest, because we
+-	 * may need to go back to the caller for more.
++static int iscsi_scsi_data_in(struct iscsi_conn *conn)
++{
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	struct iscsi_cmd_task *ctask = tcp_conn->in.ctask;
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct scsi_cmnd *sc = ctask->sc;
++	struct scatterlist *sg;
++	int i, offset, rc = 0;
++
++	BUG_ON((void*)ctask != sc->SCp.ptr);
++
++	/*
++	 * copying Data-In into the Scsi_Cmnd
+ 	 */
+-	hdr = (struct iscsi_hdr *) tcp_conn->in.hdr_buf;
+-	if (segment->copied == sizeof(struct iscsi_hdr) && hdr->hlength) {
+-		/* Bump the header length - the caller will
+-		 * just loop around and get the AHS for us, and
+-		 * call again. */
+-		unsigned int ahslen = hdr->hlength << 2;
+-
+-		/* Make sure we don't overflow */
+-		if (sizeof(*hdr) + ahslen > sizeof(tcp_conn->in.hdr_buf))
+-			return ISCSI_ERR_AHSLEN;
+-
+-		segment->total_size += ahslen;
+-		segment->size += ahslen;
+-		return 0;
++	if (!sc->use_sg) {
++		i = ctask->data_count;
++		rc = iscsi_ctask_copy(tcp_conn, ctask, sc->request_buffer,
++				      sc->request_bufflen,
++				      tcp_ctask->data_offset);
++		if (rc == -EAGAIN)
++			return rc;
++		if (conn->datadgst_en)
++			iscsi_recv_digest_update(tcp_conn, sc->request_buffer,
++						 i);
++		rc = 0;
++		goto done;
+ 	}
+ 
+-	/* We're done processing the header. See if we're doing
+-	 * header digests; if so, set up the recv_digest buffer
+-	 * and go back for more. */
+-	if (conn->hdrdgst_en) {
+-		if (segment->digest_len == 0) {
+-			iscsi_tcp_segment_splice_digest(segment,
+-							segment->recv_digest);
+-			return 0;
++	offset = tcp_ctask->data_offset;
++	sg = sc->request_buffer;
++
++	if (tcp_ctask->data_offset)
++		for (i = 0; i < tcp_ctask->sg_count; i++)
++			offset -= sg[i].length;
++	/* we've passed through partial sg*/
++	if (offset < 0)
++		offset = 0;
++
++	for (i = tcp_ctask->sg_count; i < sc->use_sg; i++) {
++		char *dest;
++
++		dest = kmap_atomic(sg[i].page, KM_SOFTIRQ0);
++		rc = iscsi_ctask_copy(tcp_conn, ctask, dest + sg[i].offset,
++				      sg[i].length, offset);
++		kunmap_atomic(dest, KM_SOFTIRQ0);
++		if (rc == -EAGAIN)
++			/* continue with the next SKB/PDU */
++			return rc;
++		if (!rc) {
++			if (conn->datadgst_en) {
++				if (!offset)
++					crypto_hash_update(
++							&tcp_conn->rx_hash,
++							&sg[i], sg[i].length);
++				else
++					partial_sg_digest_update(
++							&tcp_conn->rx_hash,
++							&sg[i],
++							sg[i].offset + offset,
++							sg[i].length - offset);
++			}
++			offset = 0;
++			tcp_ctask->sg_count++;
+ 		}
+-		iscsi_tcp_dgst_header(&tcp_conn->rx_hash, hdr,
+-				      segment->total_copied - ISCSI_DIGEST_SIZE,
+-				      segment->digest);
+ 
+-		if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-			return ISCSI_ERR_HDR_DGST;
++		if (!ctask->data_count) {
++			if (rc && conn->datadgst_en)
++				/*
++				 * data-in is complete, but buffer not...
++				 */
++				partial_sg_digest_update(&tcp_conn->rx_hash,
++							 &sg[i],
++							 sg[i].offset,
++							 sg[i].length-rc);
++			rc = 0;
++			break;
++		}
++
++		if (!tcp_conn->in.copy)
++			return -EAGAIN;
++	}
++	BUG_ON(ctask->data_count);
++
++done:
++	/* check for non-exceptional status */
++	if (tcp_conn->in.hdr->flags & ISCSI_FLAG_DATA_STATUS) {
++		debug_scsi("done [sc %lx res %d itt 0x%x flags 0x%x]\n",
++			   (long)sc, sc->result, ctask->itt,
++			   tcp_conn->in.hdr->flags);
++		spin_lock(&conn->session->lock);
++		__iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
++		spin_unlock(&conn->session->lock);
+ 	}
+ 
+-	tcp_conn->in.hdr = hdr;
+-	return iscsi_tcp_hdr_dissect(conn, hdr);
++	return rc;
++}
++
++static int
++iscsi_data_recv(struct iscsi_conn *conn)
++{
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int rc = 0, opcode;
++
++	opcode = tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK;
++	switch (opcode) {
++	case ISCSI_OP_SCSI_DATA_IN:
++		rc = iscsi_scsi_data_in(conn);
++		break;
++	case ISCSI_OP_SCSI_CMD_RSP:
++	case ISCSI_OP_TEXT_RSP:
++	case ISCSI_OP_LOGIN_RSP:
++	case ISCSI_OP_ASYNC_EVENT:
++	case ISCSI_OP_REJECT:
++		/*
++		 * Collect data segment to the connection's data
++		 * placeholder
++		 */
++		if (iscsi_tcp_copy(conn, tcp_conn->in.datalen)) {
++			rc = -EAGAIN;
++			goto exit;
++		}
++
++		rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, conn->data,
++					tcp_conn->in.datalen);
++		if (!rc && conn->datadgst_en && opcode != ISCSI_OP_LOGIN_RSP)
++			iscsi_recv_digest_update(tcp_conn, conn->data,
++			  			tcp_conn->in.datalen);
++		break;
++	default:
++		BUG_ON(1);
++	}
++exit:
++	return rc;
+ }
+ 
+ /**
+- * iscsi_tcp_recv - TCP receive in sendfile fashion
++ * iscsi_tcp_data_recv - TCP receive in sendfile fashion
+  * @rd_desc: read descriptor
+  * @skb: socket buffer
+  * @offset: offset in skb
+  * @len: skb->len - offset
+  **/
+ static int
+-iscsi_tcp_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
+-	       unsigned int offset, size_t len)
++iscsi_tcp_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
++		unsigned int offset, size_t len)
+ {
++	int rc;
+ 	struct iscsi_conn *conn = rd_desc->arg.data;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->in.segment;
+-	struct skb_seq_state seq;
+-	unsigned int consumed = 0;
+-	int rc = 0;
++	int processed;
++	char pad[ISCSI_PAD_LEN];
++	struct scatterlist sg;
+ 
+-	debug_tcp("in %d bytes\n", skb->len - offset);
++	/*
++	 * Save current SKB and its offset in the corresponding
++	 * connection context.
++	 */
++	tcp_conn->in.copy = skb->len - offset;
++	tcp_conn->in.offset = offset;
++	tcp_conn->in.skb = skb;
++	tcp_conn->in.len = tcp_conn->in.copy;
++	BUG_ON(tcp_conn->in.copy <= 0);
++	debug_tcp("in %d bytes\n", tcp_conn->in.copy);
++
++more:
++	tcp_conn->in.copied = 0;
++	rc = 0;
+ 
+ 	if (unlikely(conn->suspend_rx)) {
+ 		debug_tcp("conn %d Rx suspended!\n", conn->id);
+ 		return 0;
+ 	}
+ 
+-	skb_prepare_seq_read(skb, offset, skb->len, &seq);
+-	while (1) {
+-		unsigned int avail;
+-		const u8 *ptr;
++	if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER ||
++	    tcp_conn->in_progress == IN_PROGRESS_HEADER_GATHER) {
++		rc = iscsi_hdr_extract(tcp_conn);
++		if (rc) {
++		       if (rc == -EAGAIN)
++				goto nomore;
++		       else {
++				iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++				return 0;
++		       }
++		}
+ 
+-		avail = skb_seq_read(consumed, &ptr, &seq);
+-		if (avail == 0) {
+-			debug_tcp("no more data avail. Consumed %d\n",
+-				  consumed);
+-			break;
++		/*
++		 * Verify and process incoming PDU header.
++		 */
++		rc = iscsi_tcp_hdr_recv(conn);
++		if (!rc && tcp_conn->in.datalen) {
++			if (conn->datadgst_en)
++				crypto_hash_init(&tcp_conn->rx_hash);
++			tcp_conn->in_progress = IN_PROGRESS_DATA_RECV;
++		} else if (rc) {
++			iscsi_conn_failure(conn, rc);
++			return 0;
+ 		}
+-		BUG_ON(segment->copied >= segment->size);
+-
+-		debug_tcp("skb %p ptr=%p avail=%u\n", skb, ptr, avail);
+-		rc = iscsi_tcp_segment_recv(tcp_conn, segment, ptr, avail);
+-		BUG_ON(rc == 0);
+-		consumed += rc;
+-
+-		if (segment->total_copied >= segment->total_size) {
+-			debug_tcp("segment done\n");
+-			rc = segment->done(tcp_conn, segment);
+-			if (rc != 0) {
+-				skb_abort_seq_read(&seq);
+-				goto error;
+-			}
++	}
+ 
+-			/* The done() functions sets up the
+-			 * next segment. */
++	if (tcp_conn->in_progress == IN_PROGRESS_DDIGEST_RECV) {
++		uint32_t recv_digest;
++
++		debug_tcp("extra data_recv offset %d copy %d\n",
++			  tcp_conn->in.offset, tcp_conn->in.copy);
++		rc = iscsi_tcp_copy(conn, sizeof(uint32_t));
++		if (rc) {
++			if (rc == -EAGAIN)
++				goto again;
++			iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++			return 0;
++		}
++
++		memcpy(&recv_digest, conn->data, sizeof(uint32_t));
++		if (recv_digest != tcp_conn->in.datadgst) {
++			debug_tcp("iscsi_tcp: data digest error!"
++				  "0x%x != 0x%x\n", recv_digest,
++				  tcp_conn->in.datadgst);
++			iscsi_conn_failure(conn, ISCSI_ERR_DATA_DGST);
++			return 0;
++		} else {
++			debug_tcp("iscsi_tcp: data digest match!"
++				  "0x%x == 0x%x\n", recv_digest,
++				  tcp_conn->in.datadgst);
++			tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 		}
+ 	}
+-	skb_abort_seq_read(&seq);
+-	conn->rxdata_octets += consumed;
+-	return consumed;
+ 
+-error:
+-	debug_tcp("Error receiving PDU, errno=%d\n", rc);
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-	return 0;
++	if (tcp_conn->in_progress == IN_PROGRESS_DATA_RECV &&
++	   tcp_conn->in.copy) {
++
++		debug_tcp("data_recv offset %d copy %d\n",
++		       tcp_conn->in.offset, tcp_conn->in.copy);
++
++		rc = iscsi_data_recv(conn);
++		if (rc) {
++			if (rc == -EAGAIN)
++				goto again;
++			iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++			return 0;
++		}
++		tcp_conn->in.copy -= tcp_conn->in.padding;
++		tcp_conn->in.offset += tcp_conn->in.padding;
++		if (conn->datadgst_en) {
++			if (tcp_conn->in.padding) {
++				debug_tcp("padding -> %d\n",
++					  tcp_conn->in.padding);
++				memset(pad, 0, tcp_conn->in.padding);
++				sg_init_one(&sg, pad, tcp_conn->in.padding);
++				crypto_hash_update(&tcp_conn->rx_hash,
++						   &sg, sg.length);
++			}
++			crypto_hash_final(&tcp_conn->rx_hash,
++					  (u8 *) &tcp_conn->in.datadgst);
++			debug_tcp("rx digest 0x%x\n", tcp_conn->in.datadgst);
++			tcp_conn->in_progress = IN_PROGRESS_DDIGEST_RECV;
++			tcp_conn->data_copied = 0;
++		} else
++			tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
++	}
++
++	debug_tcp("f, processed %d from out of %d padding %d\n",
++	       tcp_conn->in.offset - offset, (int)len, tcp_conn->in.padding);
++	BUG_ON(tcp_conn->in.offset - offset > len);
++
++	if (tcp_conn->in.offset - offset != len) {
++		debug_tcp("continue to process %d bytes\n",
++		       (int)len - (tcp_conn->in.offset - offset));
++		goto more;
++	}
++
++nomore:
++	processed = tcp_conn->in.offset - offset;
++	BUG_ON(processed == 0);
++	return processed;
++
++again:
++	processed = tcp_conn->in.offset - offset;
++	debug_tcp("c, processed %d from out of %d rd_desc_cnt %d\n",
++	          processed, (int)len, (int)rd_desc->count);
++	BUG_ON(processed == 0);
++	BUG_ON(processed > len);
++
++	conn->rxdata_octets += processed;
++	return processed;
+ }
+ 
+ static void
+ iscsi_tcp_data_ready(struct sock *sk, int flag)
+ {
+ 	struct iscsi_conn *conn = sk->sk_user_data;
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 	read_descriptor_t rd_desc;
+ 
+ 	read_lock(&sk->sk_callback_lock);
+ 
+ 	/*
+-	 * Use rd_desc to pass 'conn' to iscsi_tcp_recv.
++	 * Use rd_desc to pass 'conn' to iscsi_tcp_data_recv.
+ 	 * We set count to 1 because we want the network layer to
+-	 * hand us all the skbs that are available. iscsi_tcp_recv
++	 * hand us all the skbs that are available. iscsi_tcp_data_recv
+ 	 * handled pdus that cross buffers or pdus that still need data.
+ 	 */
+ 	rd_desc.arg.data = conn;
+ 	rd_desc.count = 1;
+-	tcp_read_sock(sk, &rd_desc, iscsi_tcp_recv);
++	tcp_read_sock(sk, &rd_desc, iscsi_tcp_data_recv);
+ 
+ 	read_unlock(&sk->sk_callback_lock);
+-
+-	/* If we had to (atomically) map a highmem page,
+-	 * unmap it now. */
+-	iscsi_tcp_segment_unmap(&tcp_conn->in.segment);
+ }
+ 
+ static void
+@@ -1088,173 +1080,121 @@ iscsi_conn_restore_callbacks(struct iscsi_tcp_conn *tcp_conn)
+ }
+ 
+ /**
+- * iscsi_xmit - TCP transmit
+- **/
+-static int
+-iscsi_xmit(struct iscsi_conn *conn)
++ * iscsi_send - generic send routine
++ * @sk: kernel's socket
++ * @buf: buffer to write from
++ * @size: actual size to write
++ * @flags: socket's flags
++ */
++static inline int
++iscsi_send(struct iscsi_conn *conn, struct iscsi_buf *buf, int size, int flags)
+ {
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->out.segment;
+-	unsigned int consumed = 0;
+-	int rc = 0;
+-
+-	while (1) {
+-		rc = iscsi_tcp_xmit_segment(tcp_conn, segment);
+-		if (rc < 0)
+-			goto error;
+-		if (rc == 0)
+-			break;
+-
+-		consumed += rc;
++	struct socket *sk = tcp_conn->sock;
++	int offset = buf->sg.offset + buf->sent, res;
+ 
+-		if (segment->total_copied >= segment->total_size) {
+-			if (segment->done != NULL) {
+-				rc = segment->done(tcp_conn, segment);
+-				if (rc < 0)
+-					goto error;
+-			}
+-		}
++	/*
++	 * if we got use_sg=0 or are sending something we kmallocd
++	 * then we did not have to do kmap (kmap returns page_address)
++	 *
++	 * if we got use_sg > 0, but had to drop down, we do not
++	 * set clustering so this should only happen for that
++	 * slab case.
++	 */
++	if (buf->use_sendmsg)
++		res = sock_no_sendpage(sk, buf->sg.page, offset, size, flags);
++	else
++		res = tcp_conn->sendpage(sk, buf->sg.page, offset, size, flags);
++
++	if (res >= 0) {
++		conn->txdata_octets += res;
++		buf->sent += res;
++		return res;
+ 	}
+ 
+-	debug_tcp("xmit %d bytes\n", consumed);
+-
+-	conn->txdata_octets += consumed;
+-	return consumed;
+-
+-error:
+-	/* Transmit error. We could initiate error recovery
+-	 * here. */
+-	debug_tcp("Error sending PDU, errno=%d\n", rc);
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-	return rc;
++	tcp_conn->sendpage_failures_cnt++;
++	if (res == -EAGAIN)
++		res = -ENOBUFS;
++	else
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	return res;
+ }
+ 
+ /**
+- * iscsi_tcp_xmit_qlen - return the number of bytes queued for xmit
+- */
+-static inline int
+-iscsi_tcp_xmit_qlen(struct iscsi_conn *conn)
+-{
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->out.segment;
+-
+-	return segment->total_copied - segment->total_size;
+-}
+-
++ * iscsi_sendhdr - send PDU Header via tcp_sendpage()
++ * @conn: iscsi connection
++ * @buf: buffer to write from
++ * @datalen: lenght of data to be sent after the header
++ *
++ * Notes:
++ *	(Tx, Fast Path)
++ **/
+ static inline int
+-iscsi_tcp_flush(struct iscsi_conn *conn)
++iscsi_sendhdr(struct iscsi_conn *conn, struct iscsi_buf *buf, int datalen)
+ {
+-	int rc;
+-
+-	while (iscsi_tcp_xmit_qlen(conn)) {
+-		rc = iscsi_xmit(conn);
+-		if (rc == 0)
++	int flags = 0; /* MSG_DONTWAIT; */
++	int res, size;
++
++	size = buf->sg.length - buf->sent;
++	BUG_ON(buf->sent + size > buf->sg.length);
++	if (buf->sent + size != buf->sg.length || datalen)
++		flags |= MSG_MORE;
++
++	res = iscsi_send(conn, buf, size, flags);
++	debug_tcp("sendhdr %d bytes, sent %d res %d\n", size, buf->sent, res);
++	if (res >= 0) {
++		if (size != res)
+ 			return -EAGAIN;
+-		if (rc < 0)
+-			return rc;
++		return 0;
+ 	}
+ 
+-	return 0;
+-}
+-
+-/*
+- * This is called when we're done sending the header.
+- * Simply copy the data_segment to the send segment, and return.
+- */
+-static int
+-iscsi_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn,
+-			struct iscsi_segment *segment)
+-{
+-	tcp_conn->out.segment = tcp_conn->out.data_segment;
+-	debug_tcp("Header done. Next segment size %u total_size %u\n",
+-		  tcp_conn->out.segment.size, tcp_conn->out.segment.total_size);
+-	return 0;
++	return res;
+ }
+ 
+-static void
+-iscsi_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen)
++/**
++ * iscsi_sendpage - send one page of iSCSI Data-Out.
++ * @conn: iscsi connection
++ * @buf: buffer to write from
++ * @count: remaining data
++ * @sent: number of bytes sent
++ *
++ * Notes:
++ *	(Tx, Fast Path)
++ **/
++static inline int
++iscsi_sendpage(struct iscsi_conn *conn, struct iscsi_buf *buf,
++	       int *count, int *sent)
+ {
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-
+-	debug_tcp("%s(%p%s)\n", __FUNCTION__, tcp_conn,
+-			conn->hdrdgst_en? ", digest enabled" : "");
+-
+-	/* Clear the data segment - needs to be filled in by the
+-	 * caller using iscsi_tcp_send_data_prep() */
+-	memset(&tcp_conn->out.data_segment, 0, sizeof(struct iscsi_segment));
+-
+-	/* If header digest is enabled, compute the CRC and
+-	 * place the digest into the same buffer. We make
+-	 * sure that both iscsi_tcp_ctask and mtask have
+-	 * sufficient room.
+-	 */
+-	if (conn->hdrdgst_en) {
+-		iscsi_tcp_dgst_header(&tcp_conn->tx_hash, hdr, hdrlen,
+-				      hdr + hdrlen);
+-		hdrlen += ISCSI_DIGEST_SIZE;
++	int flags = 0; /* MSG_DONTWAIT; */
++	int res, size;
++
++	size = buf->sg.length - buf->sent;
++	BUG_ON(buf->sent + size > buf->sg.length);
++	if (size > *count)
++		size = *count;
++	if (buf->sent + size != buf->sg.length || *count != size)
++		flags |= MSG_MORE;
++
++	res = iscsi_send(conn, buf, size, flags);
++	debug_tcp("sendpage: %d bytes, sent %d left %d sent %d res %d\n",
++		  size, buf->sent, *count, *sent, res);
++	if (res >= 0) {
++		*count -= res;
++		*sent += res;
++		if (size != res)
++			return -EAGAIN;
++		return 0;
+ 	}
+ 
+-	/* Remember header pointer for later, when we need
+-	 * to decide whether there's a payload to go along
+-	 * with the header. */
+-	tcp_conn->out.hdr = hdr;
+-
+-	iscsi_segment_init_linear(&tcp_conn->out.segment, hdr, hdrlen,
+-				iscsi_tcp_send_hdr_done, NULL);
+-}
+-
+-/*
+- * Prepare the send buffer for the payload data.
+- * Padding and checksumming will all be taken care
+- * of by the iscsi_segment routines.
+- */
+-static int
+-iscsi_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg,
+-			 unsigned int count, unsigned int offset,
+-			 unsigned int len)
+-{
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct hash_desc *tx_hash = NULL;
+-	unsigned int hdr_spec_len;
+-
+-	debug_tcp("%s(%p, offset=%d, datalen=%d%s)\n", __FUNCTION__,
+-			tcp_conn, offset, len,
+-			conn->datadgst_en? ", digest enabled" : "");
+-
+-	/* Make sure the datalen matches what the caller
+-	   said he would send. */
+-	hdr_spec_len = ntoh24(tcp_conn->out.hdr->dlength);
+-	WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
+-
+-	if (conn->datadgst_en)
+-		tx_hash = &tcp_conn->tx_hash;
+-
+-	return iscsi_segment_seek_sg(&tcp_conn->out.data_segment,
+-				   sg, count, offset, len,
+-				   NULL, tx_hash);
++	return res;
+ }
+ 
+-static void
+-iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data,
+-				   size_t len)
++static inline void
++iscsi_data_digest_init(struct iscsi_tcp_conn *tcp_conn,
++		      struct iscsi_tcp_cmd_task *tcp_ctask)
+ {
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct hash_desc *tx_hash = NULL;
+-	unsigned int hdr_spec_len;
+-
+-	debug_tcp("%s(%p, datalen=%d%s)\n", __FUNCTION__, tcp_conn, len,
+-		  conn->datadgst_en? ", digest enabled" : "");
+-
+-	/* Make sure the datalen matches what the caller
+-	   said he would send. */
+-	hdr_spec_len = ntoh24(tcp_conn->out.hdr->dlength);
+-	WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
+-
+-	if (conn->datadgst_en)
+-		tx_hash = &tcp_conn->tx_hash;
+-
+-	iscsi_segment_init_linear(&tcp_conn->out.data_segment,
+-				data, len, NULL, tx_hash);
++	crypto_hash_init(&tcp_conn->tx_hash);
++	tcp_ctask->digest_count = 4;
+ }
+ 
+ /**
+@@ -1270,17 +1210,13 @@ iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data,
+  *
+  *	Called under connection lock.
+  **/
+-static int
++static void
+ iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+-			struct iscsi_r2t_info *r2t)
++			struct iscsi_r2t_info *r2t, int left)
+ {
+ 	struct iscsi_data *hdr;
+-	int new_offset, left;
+-
+-	BUG_ON(r2t->data_length - r2t->sent < 0);
+-	left = r2t->data_length - r2t->sent;
+-	if (left == 0)
+-		return 0;
++	struct scsi_cmnd *sc = ctask->sc;
++	int new_offset;
+ 
+ 	hdr = &r2t->dtask.hdr;
+ 	memset(hdr, 0, sizeof(struct iscsi_data));
+@@ -1301,47 +1237,81 @@ iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		r2t->data_count = left;
+ 		hdr->flags = ISCSI_FLAG_CMD_FINAL;
+ 	}
+-
+ 	conn->dataout_pdus_cnt++;
+-	return 1;
++
++	iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr,
++			   sizeof(struct iscsi_hdr));
++
++	if (iscsi_buf_left(&r2t->sendbuf))
++		return;
++
++	if (sc->use_sg) {
++		iscsi_buf_init_sg(&r2t->sendbuf, r2t->sg);
++		r2t->sg += 1;
++	} else {
++		iscsi_buf_init_iov(&r2t->sendbuf,
++			    (char*)sc->request_buffer + new_offset,
++			    r2t->data_count);
++		r2t->sg = NULL;
++	}
++}
++
++static void iscsi_set_padding(struct iscsi_tcp_cmd_task *tcp_ctask,
++			      unsigned long len)
++{
++	tcp_ctask->pad_count = len & (ISCSI_PAD_LEN - 1);
++	if (!tcp_ctask->pad_count)
++		return;
++
++	tcp_ctask->pad_count = ISCSI_PAD_LEN - tcp_ctask->pad_count;
++	debug_scsi("write padding %d bytes\n", tcp_ctask->pad_count);
++	tcp_ctask->xmstate |= XMSTATE_W_PAD;
+ }
+ 
+ /**
+- * iscsi_tcp_ctask - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
++ * iscsi_tcp_cmd_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
+  * @conn: iscsi connection
+  * @ctask: scsi command task
+  * @sc: scsi command
+  **/
+-static int
+-iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
++static void
++iscsi_tcp_cmd_init(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-	struct iscsi_conn *conn = ctask->conn;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	int err;
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 	BUG_ON(__kfifo_len(tcp_ctask->r2tqueue));
++
+ 	tcp_ctask->sent = 0;
+-	tcp_ctask->exp_datasn = 0;
++	tcp_ctask->sg_count = 0;
+ 
+-	/* Prepare PDU, optionally w/ immediate data */
+-	debug_scsi("ctask deq [cid %d itt 0x%x imm %d unsol %d]\n",
+-		    conn->id, ctask->itt, ctask->imm_count,
+-		    ctask->unsol_count);
+-	iscsi_tcp_send_hdr_prep(conn, ctask->hdr, ctask->hdr_len);
++	if (sc->sc_data_direction == DMA_TO_DEVICE) {
++		tcp_ctask->xmstate = XMSTATE_W_HDR;
++		tcp_ctask->exp_r2tsn = 0;
++		BUG_ON(ctask->total_length == 0);
+ 
+-	if (!ctask->imm_count)
+-		return 0;
++		if (sc->use_sg) {
++			struct scatterlist *sg = sc->request_buffer;
+ 
+-	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
+-				       0, ctask->imm_count);
+-	if (err)
+-		return err;
+-	tcp_ctask->sent += ctask->imm_count;
+-	ctask->imm_count = 0;
+-	return 0;
++			iscsi_buf_init_sg(&tcp_ctask->sendbuf, sg);
++			tcp_ctask->sg = sg + 1;
++			tcp_ctask->bad_sg = sg + sc->use_sg;
++		} else {
++			iscsi_buf_init_iov(&tcp_ctask->sendbuf,
++					   sc->request_buffer,
++					   sc->request_bufflen);
++			tcp_ctask->sg = NULL;
++			tcp_ctask->bad_sg = NULL;
++		}
++		debug_scsi("cmd [itt 0x%x total %d imm_data %d "
++			   "unsol count %d, unsol offset %d]\n",
++			   ctask->itt, ctask->total_length, ctask->imm_count,
++			   ctask->unsol_count, ctask->unsol_offset);
++	} else
++		tcp_ctask->xmstate = XMSTATE_R_HDR;
++
++	iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)ctask->hdr,
++			    sizeof(struct iscsi_hdr));
+ }
+ 
+ /**
+@@ -1353,130 +1323,428 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+  *	The function can return -EAGAIN in which case caller must
+  *	call it again later, or recover. '0' return code means successful
+  *	xmit.
++ *
++ *	Management xmit state machine consists of two states:
++ *		IN_PROGRESS_IMM_HEAD - PDU Header xmit in progress
++ *		IN_PROGRESS_IMM_DATA - PDU Data xmit in progress
+  **/
+ static int
+ iscsi_tcp_mtask_xmit(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
+ {
++	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 	int rc;
+ 
+-	/* Flush any pending data first. */
+-	rc = iscsi_tcp_flush(conn);
+-	if (rc < 0)
+-		return rc;
++	debug_scsi("mtask deq [cid %d state %x itt 0x%x]\n",
++		conn->id, tcp_mtask->xmstate, mtask->itt);
++
++	if (tcp_mtask->xmstate & XMSTATE_IMM_HDR) {
++		tcp_mtask->xmstate &= ~XMSTATE_IMM_HDR;
++		if (mtask->data_count)
++			tcp_mtask->xmstate |= XMSTATE_IMM_DATA;
++		if (conn->c_stage != ISCSI_CONN_INITIAL_STAGE &&
++		    conn->stop_stage != STOP_CONN_RECOVER &&
++		    conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &tcp_mtask->headbuf,
++					(u8*)tcp_mtask->hdrext);
++		rc = iscsi_sendhdr(conn, &tcp_mtask->headbuf,
++				   mtask->data_count);
++		if (rc) {
++			tcp_mtask->xmstate |= XMSTATE_IMM_HDR;
++			if (mtask->data_count)
++				tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA;
++			return rc;
++		}
++	}
+ 
++	if (tcp_mtask->xmstate & XMSTATE_IMM_DATA) {
++		BUG_ON(!mtask->data_count);
++		tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA;
++		/* FIXME: implement.
++		 * Virtual buffer could be spreaded across multiple pages...
++		 */
++		do {
++			int rc;
++
++			rc = iscsi_sendpage(conn, &tcp_mtask->sendbuf,
++					&mtask->data_count, &tcp_mtask->sent);
++			if (rc) {
++				tcp_mtask->xmstate |= XMSTATE_IMM_DATA;
++				return rc;
++			}
++		} while (mtask->data_count);
++	}
++
++	BUG_ON(tcp_mtask->xmstate != XMSTATE_IDLE);
+ 	if (mtask->hdr->itt == RESERVED_ITT) {
+ 		struct iscsi_session *session = conn->session;
+ 
+ 		spin_lock_bh(&session->lock);
+-		iscsi_free_mgmt_task(conn, mtask);
++		list_del(&conn->mtask->running);
++		__kfifo_put(session->mgmtpool.queue, (void*)&conn->mtask,
++			    sizeof(void*));
+ 		spin_unlock_bh(&session->lock);
+ 	}
++	return 0;
++}
++
++static inline int
++iscsi_send_read_hdr(struct iscsi_conn *conn,
++		    struct iscsi_tcp_cmd_task *tcp_ctask)
++{
++	int rc;
++
++	tcp_ctask->xmstate &= ~XMSTATE_R_HDR;
++	if (conn->hdrdgst_en)
++		iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++				 (u8*)tcp_ctask->hdrext);
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, 0);
++	if (!rc) {
++		BUG_ON(tcp_ctask->xmstate != XMSTATE_IDLE);
++		return 0; /* wait for Data-In */
++	}
++	tcp_ctask->xmstate |= XMSTATE_R_HDR;
++	return rc;
++}
++
++static inline int
++iscsi_send_write_hdr(struct iscsi_conn *conn,
++		     struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc;
++
++	tcp_ctask->xmstate &= ~XMSTATE_W_HDR;
++	if (conn->hdrdgst_en)
++		iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++				 (u8*)tcp_ctask->hdrext);
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->imm_count);
++	if (rc) {
++		tcp_ctask->xmstate |= XMSTATE_W_HDR;
++		return rc;
++	}
++
++	if (ctask->imm_count) {
++		tcp_ctask->xmstate |= XMSTATE_IMM_DATA;
++		iscsi_set_padding(tcp_ctask, ctask->imm_count);
+ 
++		if (ctask->conn->datadgst_en) {
++			iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask);
++			tcp_ctask->immdigest = 0;
++		}
++	}
++
++	if (ctask->unsol_count)
++		tcp_ctask->xmstate |= XMSTATE_UNS_HDR | XMSTATE_UNS_INIT;
+ 	return 0;
+ }
+ 
+-/*
+- * iscsi_tcp_ctask_xmit - xmit normal PDU task
+- * @conn: iscsi connection
+- * @ctask: iscsi command task
+- *
+- * We're expected to return 0 when everything was transmitted succesfully,
+- * -EAGAIN if there's still data in the queue, or != 0 for any other kind
+- * of error.
+- */
+ static int
+-iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++iscsi_send_padding(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+-	int rc = 0;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int sent = 0, rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_W_PAD) {
++		iscsi_buf_init_iov(&tcp_ctask->sendbuf, (char*)&tcp_ctask->pad,
++				   tcp_ctask->pad_count);
++		if (conn->datadgst_en)
++			crypto_hash_update(&tcp_conn->tx_hash,
++					   &tcp_ctask->sendbuf.sg,
++					   tcp_ctask->sendbuf.sg.length);
++	} else if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_PAD))
++		return 0;
+ 
+-flush:
+-	/* Flush any pending data first. */
+-	rc = iscsi_tcp_flush(conn);
+-	if (rc < 0)
+-		return rc;
++	tcp_ctask->xmstate &= ~XMSTATE_W_PAD;
++	tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_PAD;
++	debug_scsi("sending %d pad bytes for itt 0x%x\n",
++		   tcp_ctask->pad_count, ctask->itt);
++	rc = iscsi_sendpage(conn, &tcp_ctask->sendbuf, &tcp_ctask->pad_count,
++			   &sent);
++	if (rc) {
++		debug_scsi("padding send failed %d\n", rc);
++		tcp_ctask->xmstate |= XMSTATE_W_RESEND_PAD;
++	}
++	return rc;
++}
+ 
+-	/* Are we done already? */
+-	if (sc->sc_data_direction != DMA_TO_DEVICE)
++static int
++iscsi_send_digest(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
++			struct iscsi_buf *buf, uint32_t *digest)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask;
++	struct iscsi_tcp_conn *tcp_conn;
++	int rc, sent = 0;
++
++	if (!conn->datadgst_en)
+ 		return 0;
+ 
+-	if (ctask->unsol_count != 0) {
+-		struct iscsi_data *hdr = &tcp_ctask->unsol_dtask.hdr;
++	tcp_ctask = ctask->dd_data;
++	tcp_conn = conn->dd_data;
+ 
+-		/* Prepare a header for the unsolicited PDU.
+-		 * The amount of data we want to send will be
+-		 * in ctask->data_count.
+-		 * FIXME: return the data count instead.
+-		 */
+-		iscsi_prep_unsolicit_data_pdu(ctask, hdr);
++	if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_DATA_DIGEST)) {
++		crypto_hash_final(&tcp_conn->tx_hash, (u8*)digest);
++		iscsi_buf_init_iov(buf, (char*)digest, 4);
++	}
++	tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_DATA_DIGEST;
++
++	rc = iscsi_sendpage(conn, buf, &tcp_ctask->digest_count, &sent);
++	if (!rc)
++		debug_scsi("sent digest 0x%x for itt 0x%x\n", *digest,
++			  ctask->itt);
++	else {
++		debug_scsi("sending digest 0x%x failed for itt 0x%x!\n",
++			  *digest, ctask->itt);
++		tcp_ctask->xmstate |= XMSTATE_W_RESEND_DATA_DIGEST;
++	}
++	return rc;
++}
+ 
+-		debug_tcp("unsol dout [itt 0x%x doff %d dlen %d]\n",
+-				ctask->itt, tcp_ctask->sent, ctask->data_count);
++static int
++iscsi_send_data(struct iscsi_cmd_task *ctask, struct iscsi_buf *sendbuf,
++		struct scatterlist **sg, int *sent, int *count,
++		struct iscsi_buf *digestbuf, uint32_t *digest)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_conn *conn = ctask->conn;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int rc, buf_sent, offset;
++
++	while (*count) {
++		buf_sent = 0;
++		offset = sendbuf->sent;
++
++		rc = iscsi_sendpage(conn, sendbuf, count, &buf_sent);
++		*sent = *sent + buf_sent;
++		if (buf_sent && conn->datadgst_en)
++			partial_sg_digest_update(&tcp_conn->tx_hash,
++				&sendbuf->sg, sendbuf->sg.offset + offset,
++				buf_sent);
++		if (!iscsi_buf_left(sendbuf) && *sg != tcp_ctask->bad_sg) {
++			iscsi_buf_init_sg(sendbuf, *sg);
++			*sg = *sg + 1;
++		}
+ 
+-		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
+-					      ctask->data_count);
+ 		if (rc)
+-			goto fail;
+-		tcp_ctask->sent += ctask->data_count;
+-		ctask->unsol_count -= ctask->data_count;
+-		goto flush;
+-	} else {
+-		struct iscsi_session *session = conn->session;
+-		struct iscsi_r2t_info *r2t;
++			return rc;
++	}
++
++	rc = iscsi_send_padding(conn, ctask);
++	if (rc)
++		return rc;
++
++	return iscsi_send_digest(conn, ctask, digestbuf, digest);
++}
++
++static int
++iscsi_send_unsol_hdr(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_data_task *dtask;
++	int rc;
++
++	tcp_ctask->xmstate |= XMSTATE_UNS_DATA;
++	if (tcp_ctask->xmstate & XMSTATE_UNS_INIT) {
++		dtask = &tcp_ctask->unsol_dtask;
++
++		iscsi_prep_unsolicit_data_pdu(ctask, &dtask->hdr);
++		iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)&dtask->hdr,
++				   sizeof(struct iscsi_hdr));
++		if (conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++					(u8*)dtask->hdrext);
++
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_INIT;
++		iscsi_set_padding(tcp_ctask, ctask->data_count);
++	}
++
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->data_count);
++	if (rc) {
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA;
++		tcp_ctask->xmstate |= XMSTATE_UNS_HDR;
++		return rc;
++	}
++
++	if (conn->datadgst_en) {
++		dtask = &tcp_ctask->unsol_dtask;
++		iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask);
++		dtask->digest = 0;
++	}
++
++	debug_scsi("uns dout [itt 0x%x dlen %d sent %d]\n",
++		   ctask->itt, ctask->unsol_count, tcp_ctask->sent);
++	return 0;
++}
+ 
+-		/* All unsolicited PDUs sent. Check for solicited PDUs.
++static int
++iscsi_send_unsol_pdu(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_UNS_HDR) {
++		BUG_ON(!ctask->unsol_count);
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_HDR;
++send_hdr:
++		rc = iscsi_send_unsol_hdr(conn, ctask);
++		if (rc)
++			return rc;
++	}
++
++	if (tcp_ctask->xmstate & XMSTATE_UNS_DATA) {
++		struct iscsi_data_task *dtask = &tcp_ctask->unsol_dtask;
++		int start = tcp_ctask->sent;
++
++		rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg,
++				     &tcp_ctask->sent, &ctask->data_count,
++				     &dtask->digestbuf, &dtask->digest);
++		ctask->unsol_count -= tcp_ctask->sent - start;
++		if (rc)
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA;
++		/*
++		 * Done with the Data-Out. Next, check if we need
++		 * to send another unsolicited Data-Out.
+ 		 */
+-		spin_lock_bh(&session->lock);
+-		r2t = tcp_ctask->r2t;
+-		if (r2t != NULL) {
+-			/* Continue with this R2T? */
+-			if (!iscsi_solicit_data_cont(conn, ctask, r2t)) {
+-				debug_scsi("  done with r2t %p\n", r2t);
+-
+-				__kfifo_put(tcp_ctask->r2tpool.queue,
+-					    (void*)&r2t, sizeof(void*));
+-				tcp_ctask->r2t = r2t = NULL;
+-			}
++		if (ctask->unsol_count) {
++			debug_scsi("sending more uns\n");
++			tcp_ctask->xmstate |= XMSTATE_UNS_INIT;
++			goto send_hdr;
+ 		}
++	}
++	return 0;
++}
+ 
+-		if (r2t == NULL) {
++static int iscsi_send_sol_pdu(struct iscsi_conn *conn,
++			      struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_session *session = conn->session;
++	struct iscsi_r2t_info *r2t;
++	struct iscsi_data_task *dtask;
++	int left, rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_SOL_HDR) {
++		tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++		tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++		if (!tcp_ctask->r2t) {
++			spin_lock_bh(&session->lock);
+ 			__kfifo_get(tcp_ctask->r2tqueue, (void*)&tcp_ctask->r2t,
+ 				    sizeof(void*));
+-			r2t = tcp_ctask->r2t;
++			spin_unlock_bh(&session->lock);
++		}
++send_hdr:
++		r2t = tcp_ctask->r2t;
++		dtask = &r2t->dtask;
++
++		if (conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &r2t->headbuf,
++					(u8*)dtask->hdrext);
++		rc = iscsi_sendhdr(conn, &r2t->headbuf, r2t->data_count);
++		if (rc) {
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate |= XMSTATE_SOL_HDR;
++			return rc;
+ 		}
+-		spin_unlock_bh(&session->lock);
+ 
+-		/* Waiting for more R2Ts to arrive. */
+-		if (r2t == NULL) {
+-			debug_tcp("no R2Ts yet\n");
+-			return 0;
++		if (conn->datadgst_en) {
++			iscsi_data_digest_init(conn->dd_data, tcp_ctask);
++			dtask->digest = 0;
+ 		}
+ 
+-		debug_scsi("sol dout %p [dsn %d itt 0x%x doff %d dlen %d]\n",
+-			r2t, r2t->solicit_datasn - 1, ctask->itt,
+-			r2t->data_offset + r2t->sent, r2t->data_count);
++		iscsi_set_padding(tcp_ctask, r2t->data_count);
++		debug_scsi("sol dout [dsn %d itt 0x%x dlen %d sent %d]\n",
++			r2t->solicit_datasn - 1, ctask->itt, r2t->data_count,
++			r2t->sent);
++	}
+ 
+-		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+-					sizeof(struct iscsi_hdr));
++	if (tcp_ctask->xmstate & XMSTATE_SOL_DATA) {
++		r2t = tcp_ctask->r2t;
++		dtask = &r2t->dtask;
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
+-					      r2t->data_offset + r2t->sent,
+-					      r2t->data_count);
++		rc = iscsi_send_data(ctask, &r2t->sendbuf, &r2t->sg,
++				     &r2t->sent, &r2t->data_count,
++				     &dtask->digestbuf, &dtask->digest);
+ 		if (rc)
+-			goto fail;
+-		tcp_ctask->sent += r2t->data_count;
+-		r2t->sent += r2t->data_count;
+-		goto flush;
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA;
++
++		/*
++		 * Done with this Data-Out. Next, check if we have
++		 * to send another Data-Out for this R2T.
++		 */
++		BUG_ON(r2t->data_length - r2t->sent < 0);
++		left = r2t->data_length - r2t->sent;
++		if (left) {
++			iscsi_solicit_data_cont(conn, ctask, r2t, left);
++			tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++			goto send_hdr;
++		}
++
++		/*
++		 * Done with this R2T. Check if there are more
++		 * outstanding R2Ts ready to be processed.
++		 */
++		spin_lock_bh(&session->lock);
++		tcp_ctask->r2t = NULL;
++		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
++			    sizeof(void*));
++		if (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t,
++				sizeof(void*))) {
++			tcp_ctask->r2t = r2t;
++			tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++			spin_unlock_bh(&session->lock);
++			goto send_hdr;
++		}
++		spin_unlock_bh(&session->lock);
+ 	}
+ 	return 0;
+-fail:
+-	iscsi_conn_failure(conn, rc);
+-	return -EIO;
++}
++
++static int
++iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc = 0;
++
++	debug_scsi("ctask deq [cid %d xmstate %x itt 0x%x]\n",
++		conn->id, tcp_ctask->xmstate, ctask->itt);
++
++	/*
++	 * serialize with TMF AbortTask
++	 */
++	if (ctask->mtask)
++		return rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_R_HDR)
++		return iscsi_send_read_hdr(conn, tcp_ctask);
++
++	if (tcp_ctask->xmstate & XMSTATE_W_HDR) {
++		rc = iscsi_send_write_hdr(conn, ctask);
++		if (rc)
++			return rc;
++	}
++
++	if (tcp_ctask->xmstate & XMSTATE_IMM_DATA) {
++		rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg,
++				     &tcp_ctask->sent, &ctask->imm_count,
++				     &tcp_ctask->immbuf, &tcp_ctask->immdigest);
++		if (rc)
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_IMM_DATA;
++	}
++
++	rc = iscsi_send_unsol_pdu(conn, ctask);
++	if (rc)
++		return rc;
++
++	rc = iscsi_send_sol_pdu(conn, ctask);
++	if (rc)
++		return rc;
++
++	return rc;
+ }
+ 
+ static struct iscsi_cls_conn *
+@@ -1502,29 +1770,37 @@ iscsi_tcp_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 
+ 	conn->dd_data = tcp_conn;
+ 	tcp_conn->iscsi_conn = conn;
++	tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
++	/* initial operational parameters */
++	tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
+ 
+ 	tcp_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+ 						  CRYPTO_ALG_ASYNC);
+ 	tcp_conn->tx_hash.flags = 0;
+-	if (IS_ERR(tcp_conn->tx_hash.tfm))
++	if (IS_ERR(tcp_conn->tx_hash.tfm)) {
++		printk(KERN_ERR "Could not create connection due to crc32c "
++		       "loading error %ld. Make sure the crc32c module is "
++		       "built as a module or into the kernel\n",
++			PTR_ERR(tcp_conn->tx_hash.tfm));
+ 		goto free_tcp_conn;
++	}
+ 
+ 	tcp_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+ 						  CRYPTO_ALG_ASYNC);
+ 	tcp_conn->rx_hash.flags = 0;
+-	if (IS_ERR(tcp_conn->rx_hash.tfm))
++	if (IS_ERR(tcp_conn->rx_hash.tfm)) {
++		printk(KERN_ERR "Could not create connection due to crc32c "
++		       "loading error %ld. Make sure the crc32c module is "
++		       "built as a module or into the kernel\n",
++			PTR_ERR(tcp_conn->rx_hash.tfm));
+ 		goto free_tx_tfm;
++	}
+ 
+ 	return cls_conn;
+ 
+ free_tx_tfm:
+ 	crypto_free_hash(tcp_conn->tx_hash.tfm);
+ free_tcp_conn:
+-	iscsi_conn_printk(KERN_ERR, conn,
+-			  "Could not create connection due to crc32c "
+-			  "loading error. Make sure the crc32c "
+-			  "module is built as a module or into the "
+-			  "kernel\n");
+ 	kfree(tcp_conn);
+ tcp_conn_alloc_fail:
+ 	iscsi_conn_teardown(cls_conn);
+@@ -1534,22 +1810,18 @@ tcp_conn_alloc_fail:
+ static void
+ iscsi_tcp_release_conn(struct iscsi_conn *conn)
+ {
+-	struct iscsi_session *session = conn->session;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct socket *sock = tcp_conn->sock;
+ 
+-	if (!sock)
++	if (!tcp_conn->sock)
+ 		return;
+ 
+-	sock_hold(sock->sk);
++	sock_hold(tcp_conn->sock->sk);
+ 	iscsi_conn_restore_callbacks(tcp_conn);
+-	sock_put(sock->sk);
++	sock_put(tcp_conn->sock->sk);
+ 
+-	spin_lock_bh(&session->lock);
++	sock_release(tcp_conn->sock);
+ 	tcp_conn->sock = NULL;
+ 	conn->recv_lock = NULL;
+-	spin_unlock_bh(&session->lock);
+-	sockfd_put(sock);
+ }
+ 
+ static void
+@@ -1573,49 +1845,11 @@ static void
+ iscsi_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 
+ 	iscsi_conn_stop(cls_conn, flag);
+ 	iscsi_tcp_release_conn(conn);
+-}
+-
+-static int iscsi_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock,
+-			      char *buf, int *port,
+-			      int (*getname)(struct socket *, struct sockaddr *,
+-					int *addrlen))
+-{
+-	struct sockaddr_storage *addr;
+-	struct sockaddr_in6 *sin6;
+-	struct sockaddr_in *sin;
+-	int rc = 0, len;
+-
+-	addr = kmalloc(sizeof(*addr), GFP_KERNEL);
+-	if (!addr)
+-		return -ENOMEM;
+-
+-	if (getname(sock, (struct sockaddr *) addr, &len)) {
+-		rc = -ENODEV;
+-		goto free_addr;
+-	}
+-
+-	switch (addr->ss_family) {
+-	case AF_INET:
+-		sin = (struct sockaddr_in *)addr;
+-		spin_lock_bh(&conn->session->lock);
+-		sprintf(buf, NIPQUAD_FMT, NIPQUAD(sin->sin_addr.s_addr));
+-		*port = be16_to_cpu(sin->sin_port);
+-		spin_unlock_bh(&conn->session->lock);
+-		break;
+-	case AF_INET6:
+-		sin6 = (struct sockaddr_in6 *)addr;
+-		spin_lock_bh(&conn->session->lock);
+-		sprintf(buf, NIP6_FMT, NIP6(sin6->sin6_addr));
+-		*port = be16_to_cpu(sin6->sin6_port);
+-		spin_unlock_bh(&conn->session->lock);
+-		break;
+-	}
+-free_addr:
+-	kfree(addr);
+-	return rc;
++	tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
+ }
+ 
+ static int
+@@ -1632,28 +1866,13 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
+ 	/* lookup for existing socket */
+ 	sock = sockfd_lookup((int)transport_eph, &err);
+ 	if (!sock) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "sockfd_lookup failed %d\n", err);
++		printk(KERN_ERR "iscsi_tcp: sockfd_lookup failed %d\n", err);
+ 		return -EEXIST;
+ 	}
+-	/*
+-	 * copy these values now because if we drop the session
+-	 * userspace may still want to query the values since we will
+-	 * be using them for the reconnect
+-	 */
+-	err = iscsi_tcp_get_addr(conn, sock, conn->portal_address,
+-				 &conn->portal_port, kernel_getpeername);
+-	if (err)
+-		goto free_socket;
+-
+-	err = iscsi_tcp_get_addr(conn, sock, conn->local_address,
+-				&conn->local_port, kernel_getsockname);
+-	if (err)
+-		goto free_socket;
+ 
+ 	err = iscsi_conn_bind(cls_session, cls_conn, is_leading);
+ 	if (err)
+-		goto free_socket;
++		return err;
+ 
+ 	/* bind iSCSI connection and socket */
+ 	tcp_conn->sock = sock;
+@@ -1676,27 +1895,26 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
+ 	/*
+ 	 * set receive state machine into initial state
+ 	 */
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+-	return 0;
++	tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 
+-free_socket:
+-	sockfd_put(sock);
+-	return err;
++	return 0;
+ }
+ 
+ /* called with host lock */
+ static void
+-iscsi_tcp_mtask_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
++iscsi_tcp_mgmt_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask,
++		    char *data, uint32_t data_size)
+ {
+-	debug_scsi("mtask deq [cid %d itt 0x%x]\n", conn->id, mtask->itt);
++	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 
+-	/* Prepare PDU, optionally w/ immediate data */
+-	iscsi_tcp_send_hdr_prep(conn, mtask->hdr, sizeof(*mtask->hdr));
++	iscsi_buf_init_iov(&tcp_mtask->headbuf, (char*)mtask->hdr,
++			   sizeof(struct iscsi_hdr));
++	tcp_mtask->xmstate = XMSTATE_IMM_HDR;
++	tcp_mtask->sent = 0;
+ 
+-	/* If we have immediate data, attach a payload */
+ 	if (mtask->data_count)
+-		iscsi_tcp_send_linear_data_prepare(conn, mtask->data,
+-						   mtask->data_count);
++		iscsi_buf_init_iov(&tcp_mtask->sendbuf, (char*)mtask->data,
++				    mtask->data_count);
+ }
+ 
+ static int
+@@ -1719,7 +1937,8 @@ iscsi_r2tpool_alloc(struct iscsi_session *session)
+ 		 */
+ 
+ 		/* R2T pool */
+-		if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4, NULL,
++		if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4,
++				    (void***)&tcp_ctask->r2ts,
+ 				    sizeof(struct iscsi_r2t_info))) {
+ 			goto r2t_alloc_fail;
+ 		}
+@@ -1728,7 +1947,8 @@ iscsi_r2tpool_alloc(struct iscsi_session *session)
+ 		tcp_ctask->r2tqueue = kfifo_alloc(
+ 		      session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL);
+ 		if (tcp_ctask->r2tqueue == ERR_PTR(-ENOMEM)) {
+-			iscsi_pool_free(&tcp_ctask->r2tpool);
++			iscsi_pool_free(&tcp_ctask->r2tpool,
++					(void**)tcp_ctask->r2ts);
+ 			goto r2t_alloc_fail;
+ 		}
+ 	}
+@@ -1741,7 +1961,8 @@ r2t_alloc_fail:
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 		kfifo_free(tcp_ctask->r2tqueue);
+-		iscsi_pool_free(&tcp_ctask->r2tpool);
++		iscsi_pool_free(&tcp_ctask->r2tpool,
++				(void**)tcp_ctask->r2ts);
+ 	}
+ 	return -ENOMEM;
+ }
+@@ -1756,7 +1977,8 @@ iscsi_r2tpool_free(struct iscsi_session *session)
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 		kfifo_free(tcp_ctask->r2tqueue);
+-		iscsi_pool_free(&tcp_ctask->r2tpool);
++		iscsi_pool_free(&tcp_ctask->r2tpool,
++				(void**)tcp_ctask->r2ts);
+ 	}
+ }
+ 
+@@ -1772,6 +1994,9 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
+ 	switch(param) {
+ 	case ISCSI_PARAM_HDRDGST_EN:
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
++		tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
++		if (conn->hdrdgst_en)
++			tcp_conn->hdr_size += sizeof(__u32);
+ 		break;
+ 	case ISCSI_PARAM_DATADGST_EN:
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
+@@ -1780,12 +2005,12 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
+ 		break;
+ 	case ISCSI_PARAM_MAX_R2T:
+ 		sscanf(buf, "%d", &value);
+-		if (value <= 0 || !is_power_of_2(value))
+-			return -EINVAL;
+-		if (session->max_r2t == value)
++		if (session->max_r2t == roundup_pow_of_two(value))
+ 			break;
+ 		iscsi_r2tpool_free(session);
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
++		if (session->max_r2t & (session->max_r2t - 1))
++			session->max_r2t = roundup_pow_of_two(session->max_r2t);
+ 		if (iscsi_r2tpool_alloc(session))
+ 			return -ENOMEM;
+ 		break;
+@@ -1801,18 +2026,41 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 			 enum iscsi_param param, char *buf)
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	struct inet_sock *inet;
++	struct ipv6_pinfo *np;
++	struct sock *sk;
+ 	int len;
+ 
+ 	switch(param) {
+ 	case ISCSI_PARAM_CONN_PORT:
+-		spin_lock_bh(&conn->session->lock);
+-		len = sprintf(buf, "%hu\n", conn->portal_port);
+-		spin_unlock_bh(&conn->session->lock);
++		mutex_lock(&conn->xmitmutex);
++		if (!tcp_conn->sock) {
++			mutex_unlock(&conn->xmitmutex);
++			return -EINVAL;
++		}
++
++		inet = inet_sk(tcp_conn->sock->sk);
++		len = sprintf(buf, "%hu\n", be16_to_cpu(inet->dport));
++		mutex_unlock(&conn->xmitmutex);
+ 		break;
+ 	case ISCSI_PARAM_CONN_ADDRESS:
+-		spin_lock_bh(&conn->session->lock);
+-		len = sprintf(buf, "%s\n", conn->portal_address);
+-		spin_unlock_bh(&conn->session->lock);
++		mutex_lock(&conn->xmitmutex);
++		if (!tcp_conn->sock) {
++			mutex_unlock(&conn->xmitmutex);
++			return -EINVAL;
++		}
++
++		sk = tcp_conn->sock->sk;
++		if (sk->sk_family == PF_INET) {
++			inet = inet_sk(sk);
++			len = sprintf(buf, NIPQUAD_FMT "\n",
++				      NIPQUAD(inet->daddr));
++		} else {
++			np = inet6_sk(sk);
++			len = sprintf(buf, NIP6_FMT "\n", NIP6(np->daddr));
++		}
++		mutex_unlock(&conn->xmitmutex);
+ 		break;
+ 	default:
+ 		return iscsi_conn_get_param(cls_conn, param, buf);
+@@ -1821,29 +2069,6 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 	return len;
+ }
+ 
+-static int
+-iscsi_tcp_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf)
+-{
+-        struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-	int len;
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_IPADDRESS:
+-		spin_lock_bh(&session->lock);
+-		if (!session->leadconn)
+-			len = -ENODEV;
+-		else
+-			len = sprintf(buf, "%s\n",
+-				     session->leadconn->local_address);
+-		spin_unlock_bh(&session->lock);
+-		break;
+-	default:
+-		return iscsi_host_get_param(shost, param, buf);
+-	}
+-	return len;
+-}
+-
+ static void
+ iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats)
+ {
+@@ -1871,7 +2096,6 @@ iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats)
+ static struct iscsi_cls_session *
+ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 			 struct scsi_transport_template *scsit,
+-			 uint16_t cmds_max, uint16_t qdepth,
+ 			 uint32_t initial_cmdsn, uint32_t *hostno)
+ {
+ 	struct iscsi_cls_session *cls_session;
+@@ -1879,7 +2103,7 @@ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 	uint32_t hn;
+ 	int cmd_i;
+ 
+-	cls_session = iscsi_session_setup(iscsit, scsit, cmds_max, qdepth,
++	cls_session = iscsi_session_setup(iscsit, scsit,
+ 					 sizeof(struct iscsi_tcp_cmd_task),
+ 					 sizeof(struct iscsi_tcp_mgmt_task),
+ 					 initial_cmdsn, &hn);
+@@ -1892,15 +2116,14 @@ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 		struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+-		ctask->hdr = &tcp_ctask->hdr.cmd_hdr;
+-		ctask->hdr_max = sizeof(tcp_ctask->hdr) - ISCSI_DIGEST_SIZE;
++		ctask->hdr = &tcp_ctask->hdr;
+ 	}
+ 
+ 	for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) {
+ 		struct iscsi_mgmt_task *mtask = session->mgmt_cmds[cmd_i];
+ 		struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 
+-		mtask->hdr = (struct iscsi_hdr *) &tcp_mtask->hdr;
++		mtask->hdr = &tcp_mtask->hdr;
+ 	}
+ 
+ 	if (iscsi_r2tpool_alloc(class_to_transport_session(cls_session)))
+@@ -1919,27 +2142,17 @@ static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
+ 	iscsi_session_teardown(cls_session);
+ }
+ 
+-static int iscsi_tcp_slave_configure(struct scsi_device *sdev)
+-{
+-	blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_ANY);
+-	blk_queue_dma_alignment(sdev->request_queue, 0);
+-	return 0;
+-}
+-
+ static struct scsi_host_template iscsi_sht = {
+-	.module			= THIS_MODULE,
+ 	.name			= "iSCSI Initiator over TCP/IP",
+ 	.queuecommand           = iscsi_queuecommand,
+ 	.change_queue_depth	= iscsi_change_queue_depth,
+-	.can_queue		= ISCSI_DEF_XMIT_CMDS_MAX - 1,
+-	.sg_tablesize		= 4096,
++	.can_queue		= ISCSI_XMIT_CMDS_MAX - 1,
++	.sg_tablesize		= ISCSI_SG_TABLESIZE,
+ 	.max_sectors		= 0xFFFF,
+ 	.cmd_per_lun		= ISCSI_DEF_CMD_PER_LUN,
+ 	.eh_abort_handler       = iscsi_eh_abort,
+-	.eh_device_reset_handler= iscsi_eh_device_reset,
+ 	.eh_host_reset_handler	= iscsi_eh_host_reset,
+ 	.use_clustering         = DISABLE_CLUSTERING,
+-	.slave_configure        = iscsi_tcp_slave_configure,
+ 	.proc_name		= "iscsi_tcp",
+ 	.this_id		= -1,
+ };
+@@ -1966,19 +2179,12 @@ static struct iscsi_transport iscsi_tcp_transport = {
+ 				  ISCSI_EXP_STATSN |
+ 				  ISCSI_PERSISTENT_PORT |
+ 				  ISCSI_PERSISTENT_ADDRESS |
+-				  ISCSI_TARGET_NAME | ISCSI_TPGT |
+-				  ISCSI_USERNAME | ISCSI_PASSWORD |
+-				  ISCSI_USERNAME_IN | ISCSI_PASSWORD_IN |
+-				  ISCSI_FAST_ABORT | ISCSI_ABORT_TMO |
+-				  ISCSI_LU_RESET_TMO |
+-				  ISCSI_PING_TMO | ISCSI_RECV_TMO,
+-	.host_param_mask	= ISCSI_HOST_HWADDRESS | ISCSI_HOST_IPADDRESS |
+-				  ISCSI_HOST_INITIATOR_NAME |
+-				  ISCSI_HOST_NETDEV_NAME,
++				  ISCSI_TARGET_NAME |
++				  ISCSI_TPGT,
+ 	.host_template		= &iscsi_sht,
+ 	.conndata_size		= sizeof(struct iscsi_conn),
+ 	.max_conn		= 1,
+-	.max_cmd_len		= 16,
++	.max_cmd_len		= ISCSI_TCP_MAX_CMD_LEN,
+ 	/* session management */
+ 	.create_session		= iscsi_tcp_session_create,
+ 	.destroy_session	= iscsi_tcp_session_destroy,
+@@ -1991,14 +2197,11 @@ static struct iscsi_transport iscsi_tcp_transport = {
+ 	.get_session_param	= iscsi_session_get_param,
+ 	.start_conn		= iscsi_conn_start,
+ 	.stop_conn		= iscsi_tcp_conn_stop,
+-	/* iscsi host params */
+-	.get_host_param		= iscsi_tcp_host_get_param,
+-	.set_host_param		= iscsi_host_set_param,
+ 	/* IO */
+ 	.send_pdu		= iscsi_conn_send_pdu,
+ 	.get_stats		= iscsi_conn_get_stats,
+-	.init_cmd_task		= iscsi_tcp_ctask_init,
+-	.init_mgmt_task		= iscsi_tcp_mtask_init,
++	.init_cmd_task		= iscsi_tcp_cmd_init,
++	.init_mgmt_task		= iscsi_tcp_mgmt_init,
+ 	.xmit_cmd_task		= iscsi_tcp_ctask_xmit,
+ 	.xmit_mgmt_task		= iscsi_tcp_mtask_xmit,
+ 	.cleanup_cmd_task	= iscsi_tcp_cleanup_ctask,
+diff --git a/drivers/scsi/iscsi_tcp.h b/drivers/scsi/iscsi_tcp.h
+index ed0b991..3273683 100644
+--- a/drivers/scsi/iscsi_tcp.h
++++ b/drivers/scsi/iscsi_tcp.h
+@@ -24,61 +24,68 @@
+ 
+ #include <scsi/libiscsi.h>
+ 
++/* Socket's Receive state machine */
++#define IN_PROGRESS_WAIT_HEADER		0x0
++#define IN_PROGRESS_HEADER_GATHER	0x1
++#define IN_PROGRESS_DATA_RECV		0x2
++#define IN_PROGRESS_DDIGEST_RECV	0x3
++
++/* xmit state machine */
++#define XMSTATE_IDLE			0x0
++#define XMSTATE_R_HDR			0x1
++#define XMSTATE_W_HDR			0x2
++#define XMSTATE_IMM_HDR			0x4
++#define XMSTATE_IMM_DATA		0x8
++#define XMSTATE_UNS_INIT		0x10
++#define XMSTATE_UNS_HDR			0x20
++#define XMSTATE_UNS_DATA		0x40
++#define XMSTATE_SOL_HDR			0x80
++#define XMSTATE_SOL_DATA		0x100
++#define XMSTATE_W_PAD			0x200
++#define XMSTATE_W_RESEND_PAD		0x400
++#define XMSTATE_W_RESEND_DATA_DIGEST	0x800
++
++#define ISCSI_PAD_LEN			4
++#define ISCSI_SG_TABLESIZE		SG_ALL
++#define ISCSI_TCP_MAX_CMD_LEN		16
++
+ struct crypto_hash;
+ struct socket;
+-struct iscsi_tcp_conn;
+-struct iscsi_segment;
+-
+-typedef int iscsi_segment_done_fn_t(struct iscsi_tcp_conn *,
+-				    struct iscsi_segment *);
+-
+-struct iscsi_segment {
+-	unsigned char		*data;
+-	unsigned int		size;
+-	unsigned int		copied;
+-	unsigned int		total_size;
+-	unsigned int		total_copied;
+-
+-	struct hash_desc	*hash;
+-	unsigned char		recv_digest[ISCSI_DIGEST_SIZE];
+-	unsigned char		digest[ISCSI_DIGEST_SIZE];
+-	unsigned int		digest_len;
+-
+-	struct scatterlist	*sg;
+-	void			*sg_mapped;
+-	unsigned int		sg_offset;
+-
+-	iscsi_segment_done_fn_t	*done;
+-};
+ 
+ /* Socket connection recieve helper */
+ struct iscsi_tcp_recv {
+ 	struct iscsi_hdr	*hdr;
+-	struct iscsi_segment	segment;
+-
+-	/* Allocate buffer for BHS + AHS */
+-	uint32_t		hdr_buf[64];
++	struct sk_buff		*skb;
++	int			offset;
++	int			len;
++	int			hdr_offset;
++	int			copy;
++	int			copied;
++	int			padding;
++	struct iscsi_cmd_task	*ctask;		/* current cmd in progress */
+ 
+ 	/* copied and flipped values */
+ 	int			datalen;
+-};
+-
+-/* Socket connection send helper */
+-struct iscsi_tcp_send {
+-	struct iscsi_hdr	*hdr;
+-	struct iscsi_segment	segment;
+-	struct iscsi_segment	data_segment;
++	int			datadgst;
++	char			zero_copy_hdr;
+ };
+ 
+ struct iscsi_tcp_conn {
+ 	struct iscsi_conn	*iscsi_conn;
+ 	struct socket		*sock;
++	struct iscsi_hdr	hdr;		/* header placeholder */
++	char			hdrext[4*sizeof(__u16) +
++				    sizeof(__u32)];
++	int			data_copied;
+ 	int			stop_stage;	/* conn_stop() flag: *
+ 						 * stop to recover,  *
+ 						 * stop to terminate */
++	/* iSCSI connection-wide sequencing */
++	int			hdr_size;	/* PDU header size */
++
+ 	/* control data */
+ 	struct iscsi_tcp_recv	in;		/* TCP receive context */
+-	struct iscsi_tcp_send	out;		/* TCP send context */
++	int			in_progress;	/* connection state machine */
+ 
+ 	/* old values for socket callbacks */
+ 	void			(*old_data_ready)(struct sock *, int);
+@@ -93,19 +100,29 @@ struct iscsi_tcp_conn {
+ 	uint32_t		sendpage_failures_cnt;
+ 	uint32_t		discontiguous_hdr_cnt;
+ 
+-	int			error;
+-
+ 	ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int);
+ };
+ 
++struct iscsi_buf {
++	struct scatterlist	sg;
++	unsigned int		sent;
++	char			use_sendmsg;
++};
++
+ struct iscsi_data_task {
+ 	struct iscsi_data	hdr;			/* PDU */
+-	char			hdrext[ISCSI_DIGEST_SIZE];/* Header-Digest */
++	char			hdrext[sizeof(__u32)];	/* Header-Digest */
++	struct iscsi_buf	digestbuf;		/* digest buffer */
++	uint32_t		digest;			/* data digest */
+ };
+ 
+ struct iscsi_tcp_mgmt_task {
+ 	struct iscsi_hdr	hdr;
+-	char			hdrext[ISCSI_DIGEST_SIZE]; /* Header-Digest */
++	char			hdrext[sizeof(__u32)]; /* Header-Digest */
++	int			xmstate;	/* mgmt xmit progress */
++	struct iscsi_buf	headbuf;	/* header buffer */
++	struct iscsi_buf	sendbuf;	/* in progress buffer */
++	int			sent;
+ };
+ 
+ struct iscsi_r2t_info {
+@@ -113,26 +130,38 @@ struct iscsi_r2t_info {
+ 	__be32			exp_statsn;	/* copied from R2T */
+ 	uint32_t		data_length;	/* copied from R2T */
+ 	uint32_t		data_offset;	/* copied from R2T */
++	struct iscsi_buf	headbuf;	/* Data-Out Header Buffer */
++	struct iscsi_buf	sendbuf;	/* Data-Out in progress buffer*/
+ 	int			sent;		/* R2T sequence progress */
+ 	int			data_count;	/* DATA-Out payload progress */
++	struct scatterlist	*sg;		/* per-R2T SG list */
+ 	int			solicit_datasn;
+-	struct iscsi_data_task	dtask;		/* Data-Out header buf */
++	struct iscsi_data_task   dtask;        /* which data task */
+ };
+ 
+ struct iscsi_tcp_cmd_task {
+-	struct iscsi_hdr_buff {
+-		struct iscsi_cmd	cmd_hdr;
+-		char			hdrextbuf[ISCSI_MAX_AHS_SIZE +
+-		                                  ISCSI_DIGEST_SIZE];
+-	} hdr;
+-
++	struct iscsi_cmd	hdr;
++	char			hdrext[4*sizeof(__u16)+	/* AHS */
++				    sizeof(__u32)];	/* HeaderDigest */
++	char			pad[ISCSI_PAD_LEN];
++	int			pad_count;		/* padded bytes */
++	struct iscsi_buf	headbuf;		/* header buf (xmit) */
++	struct iscsi_buf	sendbuf;		/* in progress buffer*/
++	int			xmstate;		/* xmit xtate machine */
+ 	int			sent;
+-	uint32_t		exp_datasn;	/* expected target's R2TSN/DataSN */
++	struct scatterlist	*sg;			/* per-cmd SG list  */
++	struct scatterlist	*bad_sg;		/* assert statement */
++	int			sg_count;		/* SG's to process  */
++	uint32_t		exp_r2tsn;
+ 	int			data_offset;
+-	struct iscsi_r2t_info	*r2t;		/* in progress R2T    */
+-	struct iscsi_pool	r2tpool;
++	struct iscsi_r2t_info	*r2t;			/* in progress R2T    */
++	struct iscsi_queue	r2tpool;
+ 	struct kfifo		*r2tqueue;
+-	struct iscsi_data_task	unsol_dtask;	/* Data-Out header buf */
++	struct iscsi_r2t_info	**r2ts;
++	int			digest_count;
++	uint32_t		immdigest;		/* for imm data */
++	struct iscsi_buf	immbuf;			/* for imm data digest */
++	struct iscsi_data_task	unsol_dtask;	/* unsol data task */
+ };
+ 
+ #endif /* ISCSI_H */
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..3f5b9b4 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -22,9 +22,9 @@
+  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+  */
+ #include <linux/types.h>
++#include <linux/mutex.h>
+ #include <linux/kfifo.h>
+ #include <linux/delay.h>
+-#include <linux/log2.h>
+ #include <asm/unaligned.h>
+ #include <net/tcp.h>
+ #include <scsi/scsi_cmnd.h>
+@@ -46,53 +46,27 @@ class_to_transport_session(struct iscsi_cls_session *cls_session)
+ }
+ EXPORT_SYMBOL_GPL(class_to_transport_session);
+ 
+-/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */
+-#define SNA32_CHECK 2147483648UL
++#define INVALID_SN_DELTA	0xffff
+ 
+-static int iscsi_sna_lt(u32 n1, u32 n2)
+-{
+-	return n1 != n2 && ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) ||
+-			    (n1 > n2 && (n2 - n1 < SNA32_CHECK)));
+-}
+-
+-/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */
+-static int iscsi_sna_lte(u32 n1, u32 n2)
+-{
+-	return n1 == n2 || ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) ||
+-			    (n1 > n2 && (n2 - n1 < SNA32_CHECK)));
+-}
+-
+-void
+-iscsi_update_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr)
++int
++iscsi_check_assign_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr)
+ {
+ 	uint32_t max_cmdsn = be32_to_cpu(hdr->max_cmdsn);
+ 	uint32_t exp_cmdsn = be32_to_cpu(hdr->exp_cmdsn);
+ 
+-	/*
+-	 * standard specifies this check for when to update expected and
+-	 * max sequence numbers
+-	 */
+-	if (iscsi_sna_lt(max_cmdsn, exp_cmdsn - 1))
+-		return;
+-
+-	if (exp_cmdsn != session->exp_cmdsn &&
+-	    !iscsi_sna_lt(exp_cmdsn, session->exp_cmdsn))
++	if (max_cmdsn < exp_cmdsn -1 &&
++	    max_cmdsn > exp_cmdsn - INVALID_SN_DELTA)
++		return ISCSI_ERR_MAX_CMDSN;
++	if (max_cmdsn > session->max_cmdsn ||
++	    max_cmdsn < session->max_cmdsn - INVALID_SN_DELTA)
++		session->max_cmdsn = max_cmdsn;
++	if (exp_cmdsn > session->exp_cmdsn ||
++	    exp_cmdsn < session->exp_cmdsn - INVALID_SN_DELTA)
+ 		session->exp_cmdsn = exp_cmdsn;
+ 
+-	if (max_cmdsn != session->max_cmdsn &&
+-	    !iscsi_sna_lt(max_cmdsn, session->max_cmdsn)) {
+-		session->max_cmdsn = max_cmdsn;
+-		/*
+-		 * if the window closed with IO queued, then kick the
+-		 * xmit thread
+-		 */
+-		if (!list_empty(&session->leadconn->xmitqueue) ||
+-		    !list_empty(&session->leadconn->mgmtqueue))
+-			scsi_queue_work(session->host,
+-					&session->leadconn->xmitwork);
+-	}
++	return 0;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_update_cmdsn);
++EXPORT_SYMBOL_GPL(iscsi_check_assign_cmdsn);
+ 
+ void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *ctask,
+ 				   struct iscsi_data *hdr)
+@@ -123,84 +97,6 @@ void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *ctask,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_prep_unsolicit_data_pdu);
+ 
+-static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+-{
+-	unsigned exp_len = ctask->hdr_len + len;
+-
+-	if (exp_len > ctask->hdr_max) {
+-		WARN_ON(1);
+-		return -EINVAL;
+-	}
+-
+-	WARN_ON(len & (ISCSI_PAD_LEN - 1)); /* caller must pad the AHS */
+-	ctask->hdr_len = exp_len;
+-	return 0;
+-}
+-
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -208,47 +104,26 @@ static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+  * Prep basic iSCSI PDU fields for a scsi cmd pdu. The LLD should set
+  * fields like dlength or final based on how much data it sends
+  */
+-static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
++static void iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
+-	int rc;
+-
+-	ctask->hdr_len = 0;
+-	rc = iscsi_add_hdr(ctask, sizeof(*hdr));
+-	if (rc)
+-		return rc;
+-	hdr->opcode = ISCSI_OP_SCSI_CMD;
+-	hdr->flags = ISCSI_ATTR_SIMPLE;
+-	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+-	hdr->itt = build_itt(ctask->itt, session->age);
+-	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+-	session->cmdsn++;
+-	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
+ 
+-	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
++        hdr->opcode = ISCSI_OP_SCSI_CMD;
++        hdr->flags = ISCSI_ATTR_SIMPLE;
++        int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
++        hdr->itt = build_itt(ctask->itt, conn->id, session->age);
++        hdr->data_length = cpu_to_be32(sc->request_bufflen);
++        hdr->cmdsn = cpu_to_be32(session->cmdsn);
++        session->cmdsn++;
++        hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
++        memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++        memset(&hdr->cdb[sc->cmd_len], 0, MAX_COMMAND_SIZE - sc->cmd_len);
++
++	ctask->data_count = 0;
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -264,61 +139,43 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		 *
+ 		 *      pad_count       bytes to be sent as zero-padding
+ 		 */
++		ctask->imm_count = 0;
+ 		ctask->unsol_count = 0;
+ 		ctask->unsol_offset = 0;
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (ctask->total_length >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(ctask->total_length,
+ 							conn->max_xmit_dlength);
+-			hton24(hdr->dlength, ctask->imm_count);
++			hton24(ctask->hdr->dlength, ctask->imm_count);
+ 		} else
+-			zero_data(hdr->dlength);
++			zero_data(ctask->hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min(session->first_burst,
++				ctask->total_length) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+ 		if (!ctask->unsol_count)
+ 			/* No unsolicit Data-Out's */
+-			hdr->flags |= ISCSI_FLAG_CMD_FINAL;
++			ctask->hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 	} else {
++		ctask->datasn = 0;
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+ 	}
+ 
+-	/* calculate size of additional header segments (AHSs) */
+-	hdrlength = ctask->hdr_len - sizeof(*hdr);
+-
+-	WARN_ON(hdrlength & (ISCSI_PAD_LEN-1));
+-	hdrlength /= ISCSI_PAD_LEN;
+-
+-	WARN_ON(hdrlength >= 256);
+-	hdr->hlength = hdrlength & 0xFF;
+-
+-	if (conn->session->tt->init_cmd_task(conn->ctask))
+-		return EIO;
+-
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
+-		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+-	return 0;
+ }
++EXPORT_SYMBOL_GPL(iscsi_prep_scsi_cmd_pdu);
+ 
+ /**
+  * iscsi_complete_command - return command back to scsi-ml
+@@ -330,16 +187,13 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+  */
+ static void iscsi_complete_command(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_conn *conn = ctask->conn;
+-	struct iscsi_session *session = conn->session;
++	struct iscsi_session *session = ctask->conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 
+ 	ctask->state = ISCSI_TASK_COMPLETED;
+ 	ctask->sc = NULL;
+ 	/* SCSI eh reuses commands to verify us */
+ 	sc->SCp.ptr = NULL;
+-	if (conn->ctask == ctask)
+-		conn->ctask = NULL;
+ 	list_del_init(&ctask->running);
+ 	__kfifo_put(session->cmdpool.queue, (void*)&ctask, sizeof(void*));
+ 	sc->scsi_done(sc);
+@@ -350,124 +204,27 @@ static void __iscsi_get_ctask(struct iscsi_cmd_task *ctask)
+ 	atomic_inc(&ctask->refcount);
+ }
+ 
++static void iscsi_get_ctask(struct iscsi_cmd_task *ctask)
++{
++	spin_lock_bh(&ctask->conn->session->lock);
++	__iscsi_get_ctask(ctask);
++	spin_unlock_bh(&ctask->conn->session->lock);
++}
++
+ static void __iscsi_put_ctask(struct iscsi_cmd_task *ctask)
+ {
+ 	if (atomic_dec_and_test(&ctask->refcount))
+ 		iscsi_complete_command(ctask);
+ }
+ 
+-/*
+- * session lock must be held
+- */
+-static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+-			 int err)
++static void iscsi_put_ctask(struct iscsi_cmd_task *ctask)
+ {
+-	struct scsi_cmnd *sc;
+-
+-	sc = ctask->sc;
+-	if (!sc)
+-		return;
+-
+-	if (ctask->state == ISCSI_TASK_PENDING)
+-		/*
+-		 * cmd never made it to the xmit thread, so we should not count
+-		 * the cmd in the sequencing
+-		 */
+-		conn->session->queued_cmdsn--;
+-	else
+-		conn->session->tt->cleanup_cmd_task(conn, ctask);
+-
+-	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
+-	if (conn->ctask == ctask)
+-		conn->ctask = NULL;
+-	/* release ref from queuecommand */
++	spin_lock_bh(&ctask->conn->session->lock);
+ 	__iscsi_put_ctask(ctask);
++	spin_unlock_bh(&ctask->conn->session->lock);
+ }
+ 
+ /**
+- * iscsi_free_mgmt_task - return mgmt task back to pool
+- * @conn: iscsi connection
+- * @mtask: mtask
+- *
+- * Must be called with session lock.
+- */
+-void iscsi_free_mgmt_task(struct iscsi_conn *conn,
+-			  struct iscsi_mgmt_task *mtask)
+-{
+-	list_del_init(&mtask->running);
+-	if (conn->login_mtask == mtask)
+-		return;
+-
+-	if (conn->ping_mtask == mtask)
+-		conn->ping_mtask = NULL;
+-	__kfifo_put(conn->session->mgmtpool.queue,
+-		    (void*)&mtask, sizeof(void*));
+-}
+-EXPORT_SYMBOL_GPL(iscsi_free_mgmt_task);
+-
+-static struct iscsi_mgmt_task *
+-__iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-		      char *data, uint32_t data_size)
+-{
+-	struct iscsi_session *session = conn->session;
+-	struct iscsi_mgmt_task *mtask;
+-
+-	if (session->state == ISCSI_STATE_TERMINATE)
+-		return NULL;
+-
+-	if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) ||
+-	    hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
+-		/*
+-		 * Login and Text are sent serially, in
+-		 * request-followed-by-response sequence.
+-		 * Same mtask can be used. Same ITT must be used.
+-		 * Note that login_mtask is preallocated at conn_create().
+-		 */
+-		mtask = conn->login_mtask;
+-	else {
+-		BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
+-		BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
+-
+-		if (!__kfifo_get(session->mgmtpool.queue,
+-				 (void*)&mtask, sizeof(void*)))
+-			return NULL;
+-	}
+-
+-	if (data_size) {
+-		memcpy(mtask->data, data, data_size);
+-		mtask->data_count = data_size;
+-	} else
+-		mtask->data_count = 0;
+-
+-	memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr));
+-	INIT_LIST_HEAD(&mtask->running);
+-	list_add_tail(&mtask->running, &conn->mgmtqueue);
+-	return mtask;
+-}
+-
+-int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr,
+-			char *data, uint32_t data_size)
+-{
+-	struct iscsi_conn *conn = cls_conn->dd_data;
+-	struct iscsi_session *session = conn->session;
+-	int err = 0;
+-
+-	spin_lock_bh(&session->lock);
+-	if (!__iscsi_conn_send_pdu(conn, hdr, data, data_size))
+-		err = -EPERM;
+-	spin_unlock_bh(&session->lock);
+-	scsi_queue_work(session->host, &conn->xmitwork);
+-	return err;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
+-
+-/**
+  * iscsi_cmd_rsp - SCSI Command Response processing
+  * @conn: iscsi connection
+  * @hdr: iscsi header
+@@ -478,15 +235,21 @@ EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
+  * iscsi_cmd_rsp sets up the scsi_cmnd fields based on the PDU and
+  * then completes the command and task.
+  **/
+-static void iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-			       struct iscsi_cmd_task *ctask, char *data,
+-			       int datalen)
++static int iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			      struct iscsi_cmd_task *ctask, char *data,
++			      int datalen)
+ {
++	int rc;
+ 	struct iscsi_cmd_rsp *rhdr = (struct iscsi_cmd_rsp *)hdr;
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc) {
++		sc->result = DID_ERROR << 16;
++		goto out;
++	}
++
+ 	conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+ 
+ 	sc->result = (DID_OK << 16) | rhdr->cmd_status;
+@@ -501,9 +264,8 @@ static void iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 
+ 		if (datalen < 2) {
+ invalid_datalen:
+-			iscsi_conn_printk(KERN_ERR,  conn,
+-					 "Got CHECK_CONDITION but invalid data "
+-					 "buffer size of %d\n", datalen);
++			printk(KERN_ERR "iscsi: Got CHECK_CONDITION but "
++			       "invalid data buffer size of %d\n", datalen);
+ 			sc->result = DID_BAD_TARGET << 16;
+ 			goto out;
+ 		}
+@@ -518,36 +280,28 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	if (sc->sc_data_direction == DMA_TO_DEVICE)
++		goto out;
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+-	                   ISCSI_FLAG_CMD_OVERFLOW)) {
++	if (rhdr->flags & ISCSI_FLAG_CMD_UNDERFLOW) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+ 
+-		if (res_count > 0 &&
+-		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+-			scsi_set_resid(sc, res_count);
++		if (res_count > 0 && res_count <= sc->request_bufflen)
++			sc->resid = res_count;
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & ISCSI_FLAG_CMD_BIDI_UNDERFLOW)
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++	else if (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW)
++		sc->resid = be32_to_cpu(rhdr->residual_count);
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+ 	conn->scsirsp_pdus_cnt++;
+ 
+ 	__iscsi_put_ctask(ctask);
++	return rc;
+ }
+ 
+ static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+@@ -557,51 +311,18 @@ static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 	conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+ 	conn->tmfrsp_pdus_cnt++;
+ 
+-	if (conn->tmf_state != TMF_QUEUED)
++	if (conn->tmabort_state != TMABORT_INITIAL)
+ 		return;
+ 
+ 	if (tmf->response == ISCSI_TMF_RSP_COMPLETE)
+-		conn->tmf_state = TMF_SUCCESS;
++		conn->tmabort_state = TMABORT_SUCCESS;
+ 	else if (tmf->response == ISCSI_TMF_RSP_NO_TASK)
+-		conn->tmf_state = TMF_NOT_FOUND;
++		conn->tmabort_state = TMABORT_NOT_FOUND;
+ 	else
+-		conn->tmf_state = TMF_FAILED;
++		conn->tmabort_state = TMABORT_FAILED;
+ 	wake_up(&conn->ehwait);
+ }
+ 
+-static void iscsi_send_nopout(struct iscsi_conn *conn, struct iscsi_nopin *rhdr)
+-{
+-        struct iscsi_nopout hdr;
+-	struct iscsi_mgmt_task *mtask;
+-
+-	if (!rhdr && conn->ping_mtask)
+-		return;
+-
+-	memset(&hdr, 0, sizeof(struct iscsi_nopout));
+-	hdr.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE;
+-	hdr.flags = ISCSI_FLAG_CMD_FINAL;
+-
+-	if (rhdr) {
+-		memcpy(hdr.lun, rhdr->lun, 8);
+-		hdr.ttt = rhdr->ttt;
+-		hdr.itt = RESERVED_ITT;
+-	} else
+-		hdr.ttt = RESERVED_ITT;
+-
+-	mtask = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)&hdr, NULL, 0);
+-	if (!mtask) {
+-		iscsi_conn_printk(KERN_ERR, conn, "Could not send nopout\n");
+-		return;
+-	}
+-
+-	/* only track our nops */
+-	if (!rhdr) {
+-		conn->ping_mtask = mtask;
+-		conn->last_ping = jiffies;
+-	}
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-
+ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			       char *data, int datalen)
+ {
+@@ -618,10 +339,9 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		if (ntoh24(reject->dlength) >= sizeof(struct iscsi_hdr)) {
+ 			memcpy(&rejected_pdu, data, sizeof(struct iscsi_hdr));
+ 			itt = get_itt(rejected_pdu.itt);
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "itt 0x%x had pdu (op 0x%x) rejected "
+-					  "due to DataDigest error.\n", itt,
+-					  rejected_pdu.opcode);
++			printk(KERN_ERR "itt 0x%x had pdu (op 0x%x) rejected "
++				"due to DataDigest error.\n", itt,
++				rejected_pdu.opcode);
+ 		}
+ 	}
+ 	return 0;
+@@ -638,8 +358,8 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+  * queuecommand or send generic. session lock must be held and verify
+  * itt must have been called.
+  */
+-static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-				char *data, int datalen)
++int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			 char *data, int datalen)
+ {
+ 	struct iscsi_session *session = conn->session;
+ 	int opcode = hdr->opcode & ISCSI_OPCODE_MASK, rc = 0;
+@@ -647,7 +367,6 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	struct iscsi_mgmt_task *mtask;
+ 	uint32_t itt;
+ 
+-	conn->last_recv = jiffies;
+ 	if (hdr->itt != RESERVED_ITT)
+ 		itt = get_itt(hdr->itt);
+ 	else
+@@ -662,8 +381,8 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		switch(opcode) {
+ 		case ISCSI_OP_SCSI_CMD_RSP:
+ 			BUG_ON((void*)ctask != ctask->sc->SCp.ptr);
+-			iscsi_scsi_cmd_rsp(conn, hdr, ctask, data,
+-					   datalen);
++			rc = iscsi_scsi_cmd_rsp(conn, hdr, ctask, data,
++						datalen);
+ 			break;
+ 		case ISCSI_OP_SCSI_DATA_IN:
+ 			BUG_ON((void*)ctask != ctask->sc->SCp.ptr);
+@@ -686,7 +405,11 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		debug_scsi("immrsp [op 0x%x cid %d itt 0x%x len %d]\n",
+ 			   opcode, conn->id, mtask->itt, datalen);
+ 
+-		iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr);
++		rc = iscsi_check_assign_cmdsn(session,
++					      (struct iscsi_nopin*)hdr);
++		if (rc)
++			goto done;
++
+ 		switch(opcode) {
+ 		case ISCSI_OP_LOGOUT_RSP:
+ 			if (datalen) {
+@@ -703,7 +426,10 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			 */
+ 			if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen))
+ 				rc = ISCSI_ERR_CONN_FAILED;
+-			iscsi_free_mgmt_task(conn, mtask);
++			list_del(&mtask->running);
++			if (conn->login_mtask != mtask)
++				__kfifo_put(session->mgmtpool.queue,
++					    (void*)&mtask, sizeof(void*));
+ 			break;
+ 		case ISCSI_OP_SCSI_TMFUNC_RSP:
+ 			if (datalen) {
+@@ -712,33 +438,30 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			}
+ 
+ 			iscsi_tmf_rsp(conn, hdr);
+-			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		case ISCSI_OP_NOOP_IN:
+-			if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) ||
+-			    datalen) {
++			if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) || datalen) {
+ 				rc = ISCSI_ERR_PROTO;
+ 				break;
+ 			}
+ 			conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+ 
+-			if (conn->ping_mtask != mtask) {
+-				/*
+-				 * If this is not in response to one of our
+-				 * nops then it must be from userspace.
+-				 */
+-				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+-						   datalen))
+-					rc = ISCSI_ERR_CONN_FAILED;
+-			}
+-			iscsi_free_mgmt_task(conn, mtask);
++			if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen))
++				rc = ISCSI_ERR_CONN_FAILED;
++			list_del(&mtask->running);
++			if (conn->login_mtask != mtask)
++				__kfifo_put(session->mgmtpool.queue,
++					    (void*)&mtask, sizeof(void*));
+ 			break;
+ 		default:
+ 			rc = ISCSI_ERR_BAD_OPCODE;
+ 			break;
+ 		}
+ 	} else if (itt == ~0U) {
+-		iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr);
++		rc = iscsi_check_assign_cmdsn(session,
++					     (struct iscsi_nopin*)hdr);
++		if (rc)
++			goto done;
+ 
+ 		switch(opcode) {
+ 		case ISCSI_OP_NOOP_IN:
+@@ -750,7 +473,8 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG))
+ 				break;
+ 
+-			iscsi_send_nopout(conn, (struct iscsi_nopin*)hdr);
++			if (iscsi_recv_pdu(conn->cls_conn, hdr, NULL, 0))
++				rc = ISCSI_ERR_CONN_FAILED;
+ 			break;
+ 		case ISCSI_OP_REJECT:
+ 			rc = iscsi_handle_reject(conn, hdr, data, datalen);
+@@ -767,8 +491,10 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	} else
+ 		rc = ISCSI_ERR_BAD_ITT;
+ 
++done:
+ 	return rc;
+ }
++EXPORT_SYMBOL_GPL(__iscsi_complete_pdu);
+ 
+ int iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		       char *data, int datalen)
+@@ -793,13 +519,18 @@ int iscsi_verify_itt(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	if (hdr->itt != RESERVED_ITT) {
+ 		if (((__force u32)hdr->itt & ISCSI_AGE_MASK) !=
+ 		    (session->age << ISCSI_AGE_SHIFT)) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "received itt %x expected session "
+-					  "age (%x)\n", (__force u32)hdr->itt,
+-					  session->age & ISCSI_AGE_MASK);
++			printk(KERN_ERR "iscsi: received itt %x expected "
++				"session age (%x)\n", (__force u32)hdr->itt,
++				session->age & ISCSI_AGE_MASK);
+ 			return ISCSI_ERR_BAD_ITT;
+ 		}
+ 
++		if (((__force u32)hdr->itt & ISCSI_CID_MASK) !=
++		    (conn->id << ISCSI_CID_SHIFT)) {
++			printk(KERN_ERR "iscsi: received itt %x, expected "
++				"CID (%x)\n", (__force u32)hdr->itt, conn->id);
++			return ISCSI_ERR_BAD_ITT;
++		}
+ 		itt = get_itt(hdr->itt);
+ 	} else
+ 		itt = ~0U;
+@@ -808,17 +539,16 @@ int iscsi_verify_itt(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		ctask = session->cmds[itt];
+ 
+ 		if (!ctask->sc) {
+-			iscsi_conn_printk(KERN_INFO, conn, "dropping ctask "
+-					  "with itt 0x%x\n", ctask->itt);
++			printk(KERN_INFO "iscsi: dropping ctask with "
++			       "itt 0x%x\n", ctask->itt);
+ 			/* force drop */
+ 			return ISCSI_ERR_NO_SCSI_CMD;
+ 		}
+ 
+ 		if (ctask->sc->SCp.phase != session->age) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "iscsi: ctask's session age %d, "
+-					  "expected %d\n", ctask->sc->SCp.phase,
+-					  session->age);
++			printk(KERN_ERR "iscsi: ctask's session age %d, "
++				"expected %d\n", ctask->sc->SCp.phase,
++				session->age);
+ 			return ISCSI_ERR_SESSION_FAILED;
+ 		}
+ 	}
+@@ -848,110 +578,30 @@ void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_failure);
+ 
+-static void iscsi_prep_mtask(struct iscsi_conn *conn,
+-			     struct iscsi_mgmt_task *mtask)
+-{
+-	struct iscsi_session *session = conn->session;
+-	struct iscsi_hdr *hdr = mtask->hdr;
+-	struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr;
+-
+-	if (hdr->opcode != (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) &&
+-	    hdr->opcode != (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
+-		nop->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	/*
+-	 * pre-format CmdSN for outgoing PDU.
+-	 */
+-	nop->cmdsn = cpu_to_be32(session->cmdsn);
+-	if (hdr->itt != RESERVED_ITT) {
+-		hdr->itt = build_itt(mtask->itt, session->age);
+-		/*
+-		 * TODO: We always use immediate, so we never hit this.
+-		 * If we start to send tmfs or nops as non-immediate then
+-		 * we should start checking the cmdsn numbers for mgmt tasks.
+-		 */
+-		if (conn->c_stage == ISCSI_CONN_STARTED &&
+-		    !(hdr->opcode & ISCSI_OP_IMMEDIATE)) {
+-			session->queued_cmdsn++;
+-			session->cmdsn++;
+-		}
+-	}
+-
+-	if (session->tt->init_mgmt_task)
+-		session->tt->init_mgmt_task(conn, mtask);
+-
+-	debug_scsi("mgmtpdu [op 0x%x hdr->itt 0x%x datalen %d]\n",
+-		   hdr->opcode & ISCSI_OPCODE_MASK, hdr->itt,
+-		   mtask->data_count);
+-}
+-
+ static int iscsi_xmit_mtask(struct iscsi_conn *conn)
+ {
+ 	struct iscsi_hdr *hdr = conn->mtask->hdr;
+-	int rc;
+-
+-	if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT)
+-		conn->session->state = ISCSI_STATE_LOGGING_OUT;
+-	spin_unlock_bh(&conn->session->lock);
++	int rc, was_logout = 0;
+ 
++	if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT) {
++		conn->session->state = ISCSI_STATE_IN_RECOVERY;
++		iscsi_block_session(session_to_cls(conn->session));
++		was_logout = 1;
++	}
+ 	rc = conn->session->tt->xmit_mgmt_task(conn, conn->mtask);
+-	spin_lock_bh(&conn->session->lock);
+ 	if (rc)
+ 		return rc;
+ 
+ 	/* done with this in-progress mtask */
+ 	conn->mtask = NULL;
+-	return 0;
+-}
+-
+-static int iscsi_check_cmdsn_window_closed(struct iscsi_conn *conn)
+-{
+-	struct iscsi_session *session = conn->session;
+ 
+-	/*
+-	 * Check for iSCSI window and take care of CmdSN wrap-around
+-	 */
+-	if (!iscsi_sna_lte(session->queued_cmdsn, session->max_cmdsn)) {
+-		debug_scsi("iSCSI CmdSN closed. ExpCmdSn %u MaxCmdSN %u "
+-			   "CmdSN %u/%u\n", session->exp_cmdsn,
+-			   session->max_cmdsn, session->cmdsn,
+-			   session->queued_cmdsn);
+-		return -ENOSPC;
++	if (was_logout) {
++		set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
++		return -ENODATA;
+ 	}
+ 	return 0;
+ }
+ 
+-static int iscsi_xmit_ctask(struct iscsi_conn *conn)
+-{
+-	struct iscsi_cmd_task *ctask = conn->ctask;
+-	int rc;
+-
+-	__iscsi_get_ctask(ctask);
+-	spin_unlock_bh(&conn->session->lock);
+-	rc = conn->session->tt->xmit_cmd_task(conn, ctask);
+-	spin_lock_bh(&conn->session->lock);
+-	__iscsi_put_ctask(ctask);
+-	if (!rc)
+-		/* done with this ctask */
+-		conn->ctask = NULL;
+-	return rc;
+-}
+-
+-/**
+- * iscsi_requeue_ctask - requeue ctask to run from session workqueue
+- * @ctask: ctask to requeue
+- *
+- * LLDs that need to run a ctask from the session workqueue should call
+- * this. The session lock must be held.
+- */
+-void iscsi_requeue_ctask(struct iscsi_cmd_task *ctask)
+-{
+-	struct iscsi_conn *conn = ctask->conn;
+-
+-	list_move_tail(&ctask->running, &conn->requeue);
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-EXPORT_SYMBOL_GPL(iscsi_requeue_ctask);
+-
+ /**
+  * iscsi_data_xmit - xmit any command into the scheduled connection
+  * @conn: iscsi connection
+@@ -963,106 +613,106 @@ EXPORT_SYMBOL_GPL(iscsi_requeue_ctask);
+  **/
+ static int iscsi_data_xmit(struct iscsi_conn *conn)
+ {
++	struct iscsi_transport *tt;
+ 	int rc = 0;
+ 
+-	spin_lock_bh(&conn->session->lock);
+ 	if (unlikely(conn->suspend_tx)) {
+ 		debug_scsi("conn %d Tx suspended!\n", conn->id);
+-		spin_unlock_bh(&conn->session->lock);
+ 		return -ENODATA;
+ 	}
++	tt = conn->session->tt;
++
++	/*
++	 * Transmit in the following order:
++	 *
++	 * 1) un-finished xmit (ctask or mtask)
++	 * 2) immediate control PDUs
++	 * 3) write data
++	 * 4) SCSI commands
++	 * 5) non-immediate control PDUs
++	 *
++	 * No need to lock around __kfifo_get as long as
++	 * there's one producer and one consumer.
++	 */
++
++	BUG_ON(conn->ctask && conn->mtask);
+ 
+ 	if (conn->ctask) {
+-		rc = iscsi_xmit_ctask(conn);
++		iscsi_get_ctask(conn->ctask);
++		rc = tt->xmit_cmd_task(conn, conn->ctask);
++		iscsi_put_ctask(conn->ctask);
+ 		if (rc)
+ 			goto again;
++		/* done with this in-progress ctask */
++		conn->ctask = NULL;
+ 	}
+-
+ 	if (conn->mtask) {
+ 		rc = iscsi_xmit_mtask(conn);
+ 	        if (rc)
+ 		        goto again;
+ 	}
+ 
+-	/*
+-	 * process mgmt pdus like nops before commands since we should
+-	 * only have one nop-out as a ping from us and targets should not
+-	 * overflow us with nop-ins
+-	 */
+-check_mgmt:
+-	while (!list_empty(&conn->mgmtqueue)) {
+-		conn->mtask = list_entry(conn->mgmtqueue.next,
+-					 struct iscsi_mgmt_task, running);
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
+-			iscsi_free_mgmt_task(conn, conn->mtask);
+-			conn->mtask = NULL;
+-			continue;
+-		}
+-
+-		iscsi_prep_mtask(conn, conn->mtask);
+-		list_move_tail(conn->mgmtqueue.next, &conn->mgmt_run_list);
+-		rc = iscsi_xmit_mtask(conn);
+-		if (rc)
+-			goto again;
++	/* process immediate first */
++        if (unlikely(__kfifo_len(conn->immqueue))) {
++	        while (__kfifo_get(conn->immqueue, (void*)&conn->mtask,
++			           sizeof(void*))) {
++			spin_lock_bh(&conn->session->lock);
++			list_add_tail(&conn->mtask->running,
++				      &conn->mgmt_run_list);
++			spin_unlock_bh(&conn->session->lock);
++			rc = iscsi_xmit_mtask(conn);
++		        if (rc)
++			        goto again;
++	        }
+ 	}
+ 
+-	/* process pending command queue */
++	/* process command queue */
++	spin_lock_bh(&conn->session->lock);
+ 	while (!list_empty(&conn->xmitqueue)) {
+-		if (conn->tmf_state == TMF_QUEUED)
+-			break;
+-
++		/*
++		 * iscsi tcp may readd the task to the xmitqueue to send
++		 * write data
++		 */
+ 		conn->ctask = list_entry(conn->xmitqueue.next,
+ 					 struct iscsi_cmd_task, running);
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
+-			fail_command(conn, conn->ctask, DID_IMM_RETRY << 16);
+-			continue;
+-		}
+-		if (iscsi_prep_scsi_cmd_pdu(conn->ctask)) {
+-			fail_command(conn, conn->ctask, DID_ABORT << 16);
+-			continue;
+-		}
+-
+ 		conn->ctask->state = ISCSI_TASK_RUNNING;
+ 		list_move_tail(conn->xmitqueue.next, &conn->run_list);
+-		rc = iscsi_xmit_ctask(conn);
+-		if (rc)
+-			goto again;
+-		/*
+-		 * we could continuously get new ctask requests so
+-		 * we need to check the mgmt queue for nops that need to
+-		 * be sent to aviod starvation
+-		 */
+-		if (!list_empty(&conn->mgmtqueue))
+-			goto check_mgmt;
+-	}
+-
+-	while (!list_empty(&conn->requeue)) {
+-		if (conn->session->fast_abort && conn->tmf_state != TMF_INITIAL)
+-			break;
++		__iscsi_get_ctask(conn->ctask);
++		spin_unlock_bh(&conn->session->lock);
+ 
+-		/*
+-		 * we always do fastlogout - conn stop code will clean up.
+-		 */
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT)
+-			break;
++		rc = tt->xmit_cmd_task(conn, conn->ctask);
+ 
+-		conn->ctask = list_entry(conn->requeue.next,
+-					 struct iscsi_cmd_task, running);
+-		conn->ctask->state = ISCSI_TASK_RUNNING;
+-		list_move_tail(conn->requeue.next, &conn->run_list);
+-		rc = iscsi_xmit_ctask(conn);
+-		if (rc)
++		spin_lock_bh(&conn->session->lock);
++		__iscsi_put_ctask(conn->ctask);
++		if (rc) {
++			spin_unlock_bh(&conn->session->lock);
+ 			goto again;
+-		if (!list_empty(&conn->mgmtqueue))
+-			goto check_mgmt;
++		}
+ 	}
+ 	spin_unlock_bh(&conn->session->lock);
++	/* done with this ctask */
++	conn->ctask = NULL;
++
++	/* process the rest control plane PDUs, if any */
++        if (unlikely(__kfifo_len(conn->mgmtqueue))) {
++	        while (__kfifo_get(conn->mgmtqueue, (void*)&conn->mtask,
++			           sizeof(void*))) {
++			spin_lock_bh(&conn->session->lock);
++			list_add_tail(&conn->mtask->running,
++				      &conn->mgmt_run_list);
++			spin_unlock_bh(&conn->session->lock);
++			rc = iscsi_xmit_mtask(conn);
++		        if (rc)
++			        goto again;
++	        }
++	}
++
+ 	return -ENODATA;
+ 
+ again:
+ 	if (unlikely(conn->suspend_tx))
+-		rc = -ENODATA;
+-	spin_unlock_bh(&conn->session->lock);
++		return -ENODATA;
++
+ 	return rc;
+ }
+ 
+@@ -1074,9 +724,11 @@ static void iscsi_xmitworker(struct work_struct *work)
+ 	/*
+ 	 * serialize Xmit worker on a per-connection basis.
+ 	 */
++	mutex_lock(&conn->xmitmutex);
+ 	do {
+ 		rc = iscsi_data_xmit(conn);
+ 	} while (rc >= 0 || rc == -EAGAIN);
++	mutex_unlock(&conn->xmitmutex);
+ }
+ 
+ enum {
+@@ -1088,8 +740,6 @@ enum {
+ 	FAILURE_SESSION_TERMINATE,
+ 	FAILURE_SESSION_IN_RECOVERY,
+ 	FAILURE_SESSION_RECOVERY_TIMEOUT,
+-	FAILURE_SESSION_LOGGING_OUT,
+-	FAILURE_SESSION_NOT_READY,
+ };
+ 
+ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+@@ -1105,16 +755,9 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 	sc->SCp.ptr = NULL;
+ 
+ 	host = sc->device->host;
+-	spin_unlock(host->host_lock);
+-
+ 	session = iscsi_hostdata(host->hostdata);
+-	spin_lock(&session->lock);
+ 
+-	reason = iscsi_session_chkready(session_to_cls(session));
+-	if (reason) {
+-		sc->result = reason;
+-		goto fault;
+-	}
++	spin_lock(&session->lock);
+ 
+ 	/*
+ 	 * ISCSI_STATE_FAILED is a temp. state. The recovery
+@@ -1129,82 +772,77 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 		 * be entering our queuecommand while a block is starting
+ 		 * up because the block code is not locked)
+ 		 */
+-		switch (session->state) {
+-		case ISCSI_STATE_IN_RECOVERY:
++		if (session->state == ISCSI_STATE_IN_RECOVERY) {
+ 			reason = FAILURE_SESSION_IN_RECOVERY;
+-			sc->result = DID_IMM_RETRY << 16;
+-			break;
+-		case ISCSI_STATE_LOGGING_OUT:
+-			reason = FAILURE_SESSION_LOGGING_OUT;
+-			sc->result = DID_IMM_RETRY << 16;
+-			break;
+-		case ISCSI_STATE_RECOVERY_FAILED:
++			goto reject;
++		}
++
++		if (session->state == ISCSI_STATE_RECOVERY_FAILED)
+ 			reason = FAILURE_SESSION_RECOVERY_TIMEOUT;
+-			sc->result = DID_NO_CONNECT << 16;
+-			break;
+-		case ISCSI_STATE_TERMINATE:
++		else if (session->state == ISCSI_STATE_TERMINATE)
+ 			reason = FAILURE_SESSION_TERMINATE;
+-			sc->result = DID_NO_CONNECT << 16;
+-			break;
+-		default:
++		else
+ 			reason = FAILURE_SESSION_FREED;
+-			sc->result = DID_NO_CONNECT << 16;
+-		}
+ 		goto fault;
+ 	}
+ 
++	/*
++	 * Check for iSCSI window and take care of CmdSN wrap-around
++	 */
++	if ((int)(session->max_cmdsn - session->cmdsn) < 0) {
++		reason = FAILURE_WINDOW_CLOSED;
++		goto reject;
++	}
++
+ 	conn = session->leadconn;
+ 	if (!conn) {
+ 		reason = FAILURE_SESSION_FREED;
+-		sc->result = DID_NO_CONNECT << 16;
+ 		goto fault;
+ 	}
+ 
+-	if (iscsi_check_cmdsn_window_closed(conn)) {
+-		reason = FAILURE_WINDOW_CLOSED;
+-		goto reject;
+-	}
+-
+ 	if (!__kfifo_get(session->cmdpool.queue, (void*)&ctask,
+ 			 sizeof(void*))) {
+ 		reason = FAILURE_OOM;
+ 		goto reject;
+ 	}
+-	session->queued_cmdsn++;
+-
+ 	sc->SCp.phase = session->age;
+ 	sc->SCp.ptr = (char *)ctask;
+ 
+ 	atomic_set(&ctask->refcount, 1);
+ 	ctask->state = ISCSI_TASK_PENDING;
++	ctask->mtask = NULL;
+ 	ctask->conn = conn;
+ 	ctask->sc = sc;
+ 	INIT_LIST_HEAD(&ctask->running);
++	ctask->total_length = sc->request_bufflen;
++	iscsi_prep_scsi_cmd_pdu(ctask);
++
++	session->tt->init_cmd_task(ctask);
+ 
+ 	list_add_tail(&ctask->running, &conn->xmitqueue);
++	debug_scsi(
++	       "ctask enq [%s cid %d sc %p cdb 0x%x itt 0x%x len %d cmdsn %d "
++		"win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, sc->request_bufflen,
++		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	spin_unlock(&session->lock);
+ 
+ 	scsi_queue_work(host, &conn->xmitwork);
+-	spin_lock(host->host_lock);
+ 	return 0;
+ 
+ reject:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("cmd 0x%x rejected (%d)\n", sc->cmnd[0], reason);
+-	spin_lock(host->host_lock);
+ 	return SCSI_MLQUEUE_HOST_BUSY;
+ 
+ fault:
+ 	spin_unlock(&session->lock);
+-	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	printk(KERN_ERR "iscsi: cmd 0x%x is not queued (%d)\n",
++	       sc->cmnd[0], reason);
++	sc->result = (DID_NO_CONNECT << 16);
++	sc->resid = sc->request_bufflen;
+ 	sc->scsi_done(sc);
+-	spin_lock(host->host_lock);
+ 	return 0;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_queuecommand);
+@@ -1218,15 +856,106 @@ int iscsi_change_queue_depth(struct scsi_device *sdev, int depth)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_change_queue_depth);
+ 
++static int
++iscsi_conn_send_generic(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			char *data, uint32_t data_size)
++{
++	struct iscsi_session *session = conn->session;
++	struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr;
++	struct iscsi_mgmt_task *mtask;
++
++	spin_lock_bh(&session->lock);
++	if (session->state == ISCSI_STATE_TERMINATE) {
++		spin_unlock_bh(&session->lock);
++		return -EPERM;
++	}
++	if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) ||
++	    hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
++		/*
++		 * Login and Text are sent serially, in
++		 * request-followed-by-response sequence.
++		 * Same mtask can be used. Same ITT must be used.
++		 * Note that login_mtask is preallocated at conn_create().
++		 */
++		mtask = conn->login_mtask;
++	else {
++		BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
++		BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
++
++		nop->exp_statsn = cpu_to_be32(conn->exp_statsn);
++		if (!__kfifo_get(session->mgmtpool.queue,
++				 (void*)&mtask, sizeof(void*))) {
++			spin_unlock_bh(&session->lock);
++			return -ENOSPC;
++		}
++	}
++
++	/*
++	 * pre-format CmdSN for outgoing PDU.
++	 */
++	if (hdr->itt != RESERVED_ITT) {
++		hdr->itt = build_itt(mtask->itt, conn->id, session->age);
++		nop->cmdsn = cpu_to_be32(session->cmdsn);
++		if (conn->c_stage == ISCSI_CONN_STARTED &&
++		    !(hdr->opcode & ISCSI_OP_IMMEDIATE))
++			session->cmdsn++;
++	} else
++		/* do not advance CmdSN */
++		nop->cmdsn = cpu_to_be32(session->cmdsn);
++
++	if (data_size) {
++		memcpy(mtask->data, data, data_size);
++		mtask->data_count = data_size;
++	} else
++		mtask->data_count = 0;
++
++	INIT_LIST_HEAD(&mtask->running);
++	memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr));
++	if (session->tt->init_mgmt_task)
++		session->tt->init_mgmt_task(conn, mtask, data, data_size);
++	spin_unlock_bh(&session->lock);
++
++	debug_scsi("mgmtpdu [op 0x%x hdr->itt 0x%x datalen %d]\n",
++		   hdr->opcode, hdr->itt, data_size);
++
++	/*
++	 * since send_pdu() could be called at least from two contexts,
++	 * we need to serialize __kfifo_put, so we don't have to take
++	 * additional lock on fast data-path
++	 */
++        if (hdr->opcode & ISCSI_OP_IMMEDIATE)
++	        __kfifo_put(conn->immqueue, (void*)&mtask, sizeof(void*));
++	else
++	        __kfifo_put(conn->mgmtqueue, (void*)&mtask, sizeof(void*));
++
++	scsi_queue_work(session->host, &conn->xmitwork);
++	return 0;
++}
++
++int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr,
++			char *data, uint32_t data_size)
++{
++	struct iscsi_conn *conn = cls_conn->dd_data;
++	int rc;
++
++	mutex_lock(&conn->xmitmutex);
++	rc = iscsi_conn_send_generic(conn, hdr, data, data_size);
++	mutex_unlock(&conn->xmitmutex);
++
++	return rc;
++}
++EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
++
+ void iscsi_session_recovery_timedout(struct iscsi_cls_session *cls_session)
+ {
+ 	struct iscsi_session *session = class_to_transport_session(cls_session);
++	struct iscsi_conn *conn = session->leadconn;
+ 
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN) {
+ 		session->state = ISCSI_STATE_RECOVERY_FAILED;
+-		if (session->leadconn)
+-			wake_up(&session->leadconn->ehwait);
++		if (conn)
++			wake_up(&conn->ehwait);
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ }
+@@ -1237,25 +966,30 @@ int iscsi_eh_host_reset(struct scsi_cmnd *sc)
+ 	struct Scsi_Host *host = sc->device->host;
+ 	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+ 	struct iscsi_conn *conn = session->leadconn;
++	int fail_session = 0;
+ 
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_TERMINATE) {
+ failed:
+ 		debug_scsi("failing host reset: session terminated "
+ 			   "[CID %d age %d]\n", conn->id, session->age);
+ 		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return FAILED;
+ 	}
+ 
++	if (sc->SCp.phase == session->age) {
++		debug_scsi("failing connection CID %d due to SCSI host reset\n",
++			   conn->id);
++		fail_session = 1;
++	}
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
+ 	/*
+ 	 * we drop the lock here but the leadconn cannot be destoyed while
+ 	 * we are in the scsi eh
+ 	 */
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	if (fail_session)
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+ 
+ 	debug_scsi("iscsi_eh_host_reset wait for relogin\n");
+ 	wait_event_interruptible(conn->ehwait,
+@@ -1265,497 +999,324 @@ failed:
+ 	if (signal_pending(current))
+ 		flush_signals(current);
+ 
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_LOGGED_IN)
+-		iscsi_session_printk(KERN_INFO, session,
+-				     "host reset succeeded\n");
++		printk(KERN_INFO "iscsi: host reset succeeded\n");
+ 	else
+ 		goto failed;
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
+ 	return SUCCESS;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_eh_host_reset);
+ 
+-static void iscsi_tmf_timedout(unsigned long data)
++static void iscsi_tmabort_timedout(unsigned long data)
+ {
+-	struct iscsi_conn *conn = (struct iscsi_conn *)data;
++	struct iscsi_cmd_task *ctask = (struct iscsi_cmd_task *)data;
++	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+ 
+ 	spin_lock(&session->lock);
+-	if (conn->tmf_state == TMF_QUEUED) {
+-		conn->tmf_state = TMF_TIMEDOUT;
+-		debug_scsi("tmf timedout\n");
++	if (conn->tmabort_state == TMABORT_INITIAL) {
++		conn->tmabort_state = TMABORT_TIMEDOUT;
++		debug_scsi("tmabort timedout [sc %p itt 0x%x]\n",
++			ctask->sc, ctask->itt);
+ 		/* unblock eh_abort() */
+ 		wake_up(&conn->ehwait);
+ 	}
+ 	spin_unlock(&session->lock);
+ }
+ 
+-static int iscsi_exec_task_mgmt_fn(struct iscsi_conn *conn,
+-				   struct iscsi_tm *hdr, int age,
+-				   int timeout)
++/* must be called with the mutex lock */
++static int iscsi_exec_abort_task(struct scsi_cmnd *sc,
++				 struct iscsi_cmd_task *ctask)
+ {
++	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+-	struct iscsi_mgmt_task *mtask;
++	struct iscsi_tm *hdr = &conn->tmhdr;
++	int rc;
+ 
+-	mtask = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)hdr,
+-				      NULL, 0);
+-	if (!mtask) {
+-		spin_unlock_bh(&session->lock);
++	/*
++	 * ctask timed out but session is OK requests must be serialized.
++	 */
++	memset(hdr, 0, sizeof(struct iscsi_tm));
++	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
++	hdr->flags = ISCSI_TM_FUNC_ABORT_TASK;
++	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
++	memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun));
++	hdr->rtt = ctask->hdr->itt;
++	hdr->refcmdsn = ctask->hdr->cmdsn;
++
++	rc = iscsi_conn_send_generic(conn, (struct iscsi_hdr *)hdr,
++				     NULL, 0);
++	if (rc) {
+ 		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		spin_lock_bh(&session->lock);
+-		debug_scsi("tmf exec failure\n");
+-		return -EPERM;
++		debug_scsi("abort sent failure [itt 0x%x] %d\n", ctask->itt,
++		           rc);
++		return rc;
+ 	}
+-	conn->tmfcmd_pdus_cnt++;
+-	conn->tmf_timer.expires = timeout * HZ + jiffies;
+-	conn->tmf_timer.function = iscsi_tmf_timedout;
+-	conn->tmf_timer.data = (unsigned long)conn;
+-	add_timer(&conn->tmf_timer);
+-	debug_scsi("tmf set timeout\n");
+ 
++	debug_scsi("abort sent [itt 0x%x]\n", ctask->itt);
++
++	spin_lock_bh(&session->lock);
++	ctask->mtask = (struct iscsi_mgmt_task *)
++			session->mgmt_cmds[get_itt(hdr->itt) -
++					ISCSI_MGMT_ITT_OFFSET];
++
++	if (conn->tmabort_state == TMABORT_INITIAL) {
++		conn->tmfcmd_pdus_cnt++;
++		conn->tmabort_timer.expires = 10*HZ + jiffies;
++		conn->tmabort_timer.function = iscsi_tmabort_timedout;
++		conn->tmabort_timer.data = (unsigned long)ctask;
++		add_timer(&conn->tmabort_timer);
++		debug_scsi("abort set timeout [itt 0x%x]\n", ctask->itt);
++	}
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
+-	scsi_queue_work(session->host, &conn->xmitwork);
++	mutex_unlock(&conn->xmitmutex);
+ 
+ 	/*
+ 	 * block eh thread until:
+ 	 *
+-	 * 1) tmf response
+-	 * 2) tmf timeout
++	 * 1) abort response
++	 * 2) abort timeout
+ 	 * 3) session is terminated or restarted or userspace has
+ 	 * given up on recovery
+ 	 */
+-	wait_event_interruptible(conn->ehwait, age != session->age ||
++	wait_event_interruptible(conn->ehwait,
++				 sc->SCp.phase != session->age ||
+ 				 session->state != ISCSI_STATE_LOGGED_IN ||
+-				 conn->tmf_state != TMF_QUEUED);
++				 conn->tmabort_state != TMABORT_INITIAL);
+ 	if (signal_pending(current))
+ 		flush_signals(current);
+-	del_timer_sync(&conn->tmf_timer);
++	del_timer_sync(&conn->tmabort_timer);
+ 
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+-	/* if the session drops it will clean up the mtask */
+-	if (age != session->age ||
+-	    session->state != ISCSI_STATE_LOGGED_IN)
+-		return -ENOTCONN;
++	mutex_lock(&conn->xmitmutex);
+ 	return 0;
+ }
+ 
+ /*
+- * Fail commands. session lock held and recv side suspended and xmit
+- * thread flushed
++ * xmit mutex and session lock must be held
+  */
+-static void fail_all_commands(struct iscsi_conn *conn, unsigned lun,
+-			      int error)
++static struct iscsi_mgmt_task *
++iscsi_remove_mgmt_task(struct kfifo *fifo, uint32_t itt)
+ {
+-	struct iscsi_cmd_task *ctask, *tmp;
++	int i, nr_tasks = __kfifo_len(fifo) / sizeof(void*);
++	struct iscsi_mgmt_task *task;
+ 
+-	if (conn->ctask && (conn->ctask->sc->device->lun == lun || lun == -1))
+-		conn->ctask = NULL;
++	debug_scsi("searching %d tasks\n", nr_tasks);
+ 
+-	/* flush pending */
+-	list_for_each_entry_safe(ctask, tmp, &conn->xmitqueue, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing pending sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, error << 16);
+-		}
+-	}
++	for (i = 0; i < nr_tasks; i++) {
++		__kfifo_get(fifo, (void*)&task, sizeof(void*));
++		debug_scsi("check task %u\n", task->itt);
+ 
+-	list_for_each_entry_safe(ctask, tmp, &conn->requeue, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing requeued sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, error << 16);
++		if (task->itt == itt) {
++			debug_scsi("matched task\n");
++			return task;
+ 		}
+-	}
+ 
+-	/* fail all other running */
+-	list_for_each_entry_safe(ctask, tmp, &conn->run_list, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing in progress sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, DID_BUS_BUSY << 16);
+-		}
++		__kfifo_put(fifo, (void*)&task, sizeof(void*));
+ 	}
++	return NULL;
+ }
+ 
+-static void iscsi_suspend_tx(struct iscsi_conn *conn)
+-{
+-	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+-	scsi_flush_work(conn->session->host);
+-}
+-
+-static void iscsi_start_tx(struct iscsi_conn *conn)
+-{
+-	clear_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-
+-static enum scsi_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd)
++static int iscsi_ctask_mtask_cleanup(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_cls_session *cls_session;
+-	struct iscsi_session *session;
+-	struct iscsi_conn *conn;
+-	enum scsi_eh_timer_return rc = EH_NOT_HANDLED;
+-
+-	cls_session = starget_to_session(scsi_target(scmd->device));
+-	session = class_to_transport_session(cls_session);
+-
+-	debug_scsi("scsi cmd %p timedout\n", scmd);
+-
+-	spin_lock(&session->lock);
+-	if (session->state != ISCSI_STATE_LOGGED_IN) {
+-		/*
+-		 * We are probably in the middle of iscsi recovery so let
+-		 * that complete and handle the error.
+-		 */
+-		rc = EH_RESET_TIMER;
+-		goto done;
+-	}
++	struct iscsi_conn *conn = ctask->conn;
++	struct iscsi_session *session = conn->session;
+ 
+-	conn = session->leadconn;
+-	if (!conn) {
+-		/* In the middle of shuting down */
+-		rc = EH_RESET_TIMER;
+-		goto done;
+-	}
++	if (!ctask->mtask)
++		return -EINVAL;
+ 
+-	if (!conn->recv_timeout && !conn->ping_timeout)
+-		goto done;
+-	/*
+-	 * if the ping timedout then we are in the middle of cleaning up
+-	 * and can let the iscsi eh handle it
+-	 */
+-	if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ) +
+-			    (conn->ping_timeout * HZ), jiffies))
+-		rc = EH_RESET_TIMER;
+-	/*
+-	 * if we are about to check the transport then give the command
+-	 * more time
+-	 */
+-	if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ),
+-			   jiffies))
+-		rc = EH_RESET_TIMER;
+-	/* if in the middle of checking the transport then give us more time */
+-	if (conn->ping_mtask)
+-		rc = EH_RESET_TIMER;
+-done:
+-	spin_unlock(&session->lock);
+-	debug_scsi("return %s\n", rc == EH_RESET_TIMER ? "timer reset" : "nh");
+-	return rc;
++	if (!iscsi_remove_mgmt_task(conn->immqueue, ctask->mtask->itt))
++		list_del(&ctask->mtask->running);
++	__kfifo_put(session->mgmtpool.queue, (void*)&ctask->mtask,
++		    sizeof(void*));
++	ctask->mtask = NULL;
++	return 0;
+ }
+ 
+-static void iscsi_check_transport_timeouts(unsigned long data)
++/*
++ * session lock and xmitmutex must be held
++ */
++static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
++			 int err)
+ {
+-	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+-	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	struct scsi_cmnd *sc;
+ 
+-	spin_lock(&session->lock);
+-	if (session->state != ISCSI_STATE_LOGGED_IN)
+-		goto done;
+-
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
+-		goto done;
+-
+-	timeout *= HZ;
+-	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
+-			   jiffies)) {
+-		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+-				  "expired, last rx %lu, last ping %lu, "
+-				  "now %lu\n", conn->ping_timeout, last_recv,
+-				  conn->last_ping, jiffies);
+-		spin_unlock(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	sc = ctask->sc;
++	if (!sc)
+ 		return;
+-	}
+-
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
+-	} else
+-		next_timeout = last_recv + timeout;
+ 
+-	debug_scsi("Setting next tmo %lu\n", next_timeout);
+-	mod_timer(&conn->transport_timer, next_timeout);
+-done:
+-	spin_unlock(&session->lock);
+-}
++	conn->session->tt->cleanup_cmd_task(conn, ctask);
++	iscsi_ctask_mtask_cleanup(ctask);
+ 
+-static void iscsi_prep_abort_task_pdu(struct iscsi_cmd_task *ctask,
+-				      struct iscsi_tm *hdr)
+-{
+-	memset(hdr, 0, sizeof(*hdr));
+-	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
+-	hdr->flags = ISCSI_TM_FUNC_ABORT_TASK & ISCSI_FLAG_TM_FUNC_MASK;
+-	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+-	memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun));
+-	hdr->rtt = ctask->hdr->itt;
+-	hdr->refcmdsn = ctask->hdr->cmdsn;
++	sc->result = err;
++	sc->resid = sc->request_bufflen;
++	/* release ref from queuecommand */
++	__iscsi_put_ctask(ctask);
+ }
+ 
+ int iscsi_eh_abort(struct scsi_cmnd *sc)
+ {
+-	struct Scsi_Host *host = sc->device->host;
+-	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+-	struct iscsi_conn *conn;
+ 	struct iscsi_cmd_task *ctask;
+-	struct iscsi_tm *hdr;
+-	int rc, age;
++	struct iscsi_conn *conn;
++	struct iscsi_session *session;
++	int rc;
+ 
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+ 	/*
+ 	 * if session was ISCSI_STATE_IN_RECOVERY then we may not have
+ 	 * got the command.
+ 	 */
+ 	if (!sc->SCp.ptr) {
+ 		debug_scsi("sc never reached iscsi layer or it completed.\n");
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return SUCCESS;
+ 	}
+ 
++	ctask = (struct iscsi_cmd_task *)sc->SCp.ptr;
++	conn = ctask->conn;
++	session = conn->session;
++
++	conn->eh_abort_cnt++;
++	debug_scsi("aborting [sc %p itt 0x%x]\n", sc, ctask->itt);
++
++	mutex_lock(&conn->xmitmutex);
++	spin_lock_bh(&session->lock);
++
+ 	/*
+ 	 * If we are not logged in or we have started a new session
+ 	 * then let the host reset code handle this
+ 	 */
+-	if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN ||
+-	    sc->SCp.phase != session->age) {
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+-		return FAILED;
+-	}
+-
+-	conn = session->leadconn;
+-	conn->eh_abort_cnt++;
+-	age = session->age;
+-
+-	ctask = (struct iscsi_cmd_task *)sc->SCp.ptr;
+-	debug_scsi("aborting [sc %p itt 0x%x]\n", sc, ctask->itt);
++	if (session->state != ISCSI_STATE_LOGGED_IN ||
++	    sc->SCp.phase != session->age)
++		goto failed;
+ 
+ 	/* ctask completed before time out */
+ 	if (!ctask->sc) {
++		spin_unlock_bh(&session->lock);
+ 		debug_scsi("sc completed while abort in progress\n");
+-		goto success;
++		goto success_rel_mutex;
+ 	}
+ 
+-	if (ctask->state == ISCSI_TASK_PENDING) {
+-		fail_command(conn, ctask, DID_ABORT << 16);
+-		goto success;
++	/* what should we do here ? */
++	if (conn->ctask == ctask) {
++		printk(KERN_INFO "iscsi: sc %p itt 0x%x partially sent. "
++		       "Failing abort\n", sc, ctask->itt);
++		goto failed;
+ 	}
+ 
+-	/* only have one tmf outstanding at a time */
+-	if (conn->tmf_state != TMF_INITIAL)
+-		goto failed;
+-	conn->tmf_state = TMF_QUEUED;
++	if (ctask->state == ISCSI_TASK_PENDING)
++		goto success_cleanup;
+ 
+-	hdr = &conn->tmhdr;
+-	iscsi_prep_abort_task_pdu(ctask, hdr);
++	conn->tmabort_state = TMABORT_INITIAL;
+ 
+-	if (iscsi_exec_task_mgmt_fn(conn, hdr, age, session->abort_timeout)) {
+-		rc = FAILED;
++	spin_unlock_bh(&session->lock);
++	rc = iscsi_exec_abort_task(sc, ctask);
++	spin_lock_bh(&session->lock);
++
++	if (rc || sc->SCp.phase != session->age ||
++	    session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto failed;
+-	}
++	iscsi_ctask_mtask_cleanup(ctask);
+ 
+-	switch (conn->tmf_state) {
+-	case TMF_SUCCESS:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_suspend_tx(conn);
+-		/*
+-		 * clean up task if aborted. grab the recv lock as a writer
+-		 */
+-		write_lock_bh(conn->recv_lock);
+-		spin_lock(&session->lock);
+-		fail_command(conn, ctask, DID_ABORT << 16);
+-		conn->tmf_state = TMF_INITIAL;
+-		spin_unlock(&session->lock);
+-		write_unlock_bh(conn->recv_lock);
+-		iscsi_start_tx(conn);
+-		goto success_unlocked;
+-	case TMF_TIMEDOUT:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		goto failed_unlocked;
+-	case TMF_NOT_FOUND:
+-		if (!sc->SCp.ptr) {
+-			conn->tmf_state = TMF_INITIAL;
++	switch (conn->tmabort_state) {
++	case TMABORT_SUCCESS:
++		goto success_cleanup;
++	case TMABORT_NOT_FOUND:
++		if (!ctask->sc) {
+ 			/* ctask completed before tmf abort response */
++			spin_unlock_bh(&session->lock);
+ 			debug_scsi("sc completed while abort in progress\n");
+-			goto success;
++			goto success_rel_mutex;
+ 		}
+ 		/* fall through */
+ 	default:
+-		conn->tmf_state = TMF_INITIAL;
++		/* timedout or failed */
++		spin_unlock_bh(&session->lock);
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++		spin_lock_bh(&session->lock);
+ 		goto failed;
+ 	}
+ 
+-success:
+-	spin_unlock_bh(&session->lock);
+-success_unlocked:
++success_cleanup:
+ 	debug_scsi("abort success [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
+-	mutex_unlock(&session->eh_mutex);
+-	return SUCCESS;
+-
+-failed:
+ 	spin_unlock_bh(&session->lock);
+-failed_unlocked:
+-	debug_scsi("abort failed [sc %p itt 0x%x]\n", sc,
+-		    ctask ? ctask->itt : 0);
+-	mutex_unlock(&session->eh_mutex);
+-	return FAILED;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_eh_abort);
+ 
+-static void iscsi_prep_lun_reset_pdu(struct scsi_cmnd *sc, struct iscsi_tm *hdr)
+-{
+-	memset(hdr, 0, sizeof(*hdr));
+-	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
+-	hdr->flags = ISCSI_TM_FUNC_LOGICAL_UNIT_RESET & ISCSI_FLAG_TM_FUNC_MASK;
+-	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+-	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+-	hdr->rtt = RESERVED_ITT;
+-}
+-
+-int iscsi_eh_device_reset(struct scsi_cmnd *sc)
+-{
+-	struct Scsi_Host *host = sc->device->host;
+-	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+-	struct iscsi_conn *conn;
+-	struct iscsi_tm *hdr;
+-	int rc = FAILED;
+-
+-	debug_scsi("LU Reset [sc %p lun %u]\n", sc, sc->device->lun);
+-
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+ 	/*
+-	 * Just check if we are not logged in. We cannot check for
+-	 * the phase because the reset could come from a ioctl.
++	 * clean up task if aborted. we have the xmitmutex so grab
++	 * the recv lock as a writer
+ 	 */
+-	if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN)
+-		goto unlock;
+-	conn = session->leadconn;
+-
+-	/* only have one tmf outstanding at a time */
+-	if (conn->tmf_state != TMF_INITIAL)
+-		goto unlock;
+-	conn->tmf_state = TMF_QUEUED;
+-
+-	hdr = &conn->tmhdr;
+-	iscsi_prep_lun_reset_pdu(sc, hdr);
+-
+-	if (iscsi_exec_task_mgmt_fn(conn, hdr, session->age,
+-				    session->lu_reset_timeout)) {
+-		rc = FAILED;
+-		goto unlock;
+-	}
+-
+-	switch (conn->tmf_state) {
+-	case TMF_SUCCESS:
+-		break;
+-	case TMF_TIMEDOUT:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		goto done;
+-	default:
+-		conn->tmf_state = TMF_INITIAL;
+-		goto unlock;
+-	}
+-
+-	rc = SUCCESS;
+-	spin_unlock_bh(&session->lock);
+-
+-	iscsi_suspend_tx(conn);
+-	/* need to grab the recv lock then session lock */
+ 	write_lock_bh(conn->recv_lock);
+ 	spin_lock(&session->lock);
+-	fail_all_commands(conn, sc->device->lun, DID_ERROR);
+-	conn->tmf_state = TMF_INITIAL;
++	fail_command(conn, ctask, DID_ABORT << 16);
+ 	spin_unlock(&session->lock);
+ 	write_unlock_bh(conn->recv_lock);
+ 
+-	iscsi_start_tx(conn);
+-	goto done;
++success_rel_mutex:
++	mutex_unlock(&conn->xmitmutex);
++	return SUCCESS;
+ 
+-unlock:
++failed:
+ 	spin_unlock_bh(&session->lock);
+-done:
+-	debug_scsi("iscsi_eh_device_reset %s\n",
+-		  rc == SUCCESS ? "SUCCESS" : "FAILED");
+-	mutex_unlock(&session->eh_mutex);
+-	return rc;
++	mutex_unlock(&conn->xmitmutex);
++
++	debug_scsi("abort failed [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
++	return FAILED;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_eh_device_reset);
++EXPORT_SYMBOL_GPL(iscsi_eh_abort);
+ 
+-/*
+- * Pre-allocate a pool of @max items of @item_size. By default, the pool
+- * should be accessed via kfifo_{get,put} on q->queue.
+- * Optionally, the caller can obtain the array of object pointers
+- * by passing in a non-NULL @items pointer
+- */
+ int
+-iscsi_pool_init(struct iscsi_pool *q, int max, void ***items, int item_size)
++iscsi_pool_init(struct iscsi_queue *q, int max, void ***items, int item_size)
+ {
+-	int i, num_arrays = 1;
++	int i;
+ 
+-	memset(q, 0, sizeof(*q));
++	*items = kmalloc(max * sizeof(void*), GFP_KERNEL);
++	if (*items == NULL)
++		return -ENOMEM;
+ 
+ 	q->max = max;
+-
+-	/* If the user passed an items pointer, he wants a copy of
+-	 * the array. */
+-	if (items)
+-		num_arrays++;
+-	q->pool = kzalloc(num_arrays * max * sizeof(void*), GFP_KERNEL);
+-	if (q->pool == NULL)
+-		goto enomem;
++	q->pool = kmalloc(max * sizeof(void*), GFP_KERNEL);
++	if (q->pool == NULL) {
++		kfree(*items);
++		return -ENOMEM;
++	}
+ 
+ 	q->queue = kfifo_init((void*)q->pool, max * sizeof(void*),
+ 			      GFP_KERNEL, NULL);
+-	if (q->queue == ERR_PTR(-ENOMEM))
+-		goto enomem;
++	if (q->queue == ERR_PTR(-ENOMEM)) {
++		kfree(q->pool);
++		kfree(*items);
++		return -ENOMEM;
++	}
+ 
+ 	for (i = 0; i < max; i++) {
+-		q->pool[i] = kzalloc(item_size, GFP_KERNEL);
++		q->pool[i] = kmalloc(item_size, GFP_KERNEL);
+ 		if (q->pool[i] == NULL) {
+-			q->max = i;
+-			goto enomem;
++			int j;
++
++			for (j = 0; j < i; j++)
++				kfree(q->pool[j]);
++
++			kfifo_free(q->queue);
++			kfree(q->pool);
++			kfree(*items);
++			return -ENOMEM;
+ 		}
++		memset(q->pool[i], 0, item_size);
++		(*items)[i] = q->pool[i];
+ 		__kfifo_put(q->queue, (void*)&q->pool[i], sizeof(void*));
+ 	}
+-
+-	if (items) {
+-		*items = q->pool + max;
+-		memcpy(*items, q->pool, max * sizeof(void *));
+-	}
+-
+ 	return 0;
+-
+-enomem:
+-	iscsi_pool_free(q);
+-	return -ENOMEM;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_pool_init);
+ 
+-void iscsi_pool_free(struct iscsi_pool *q)
++void iscsi_pool_free(struct iscsi_queue *q, void **items)
+ {
+ 	int i;
+ 
+ 	for (i = 0; i < q->max; i++)
+-		kfree(q->pool[i]);
+-	if (q->pool)
+-		kfree(q->pool);
++		kfree(items[i]);
++	kfree(q->pool);
++	kfree(items);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+ 
+@@ -1778,10 +1339,6 @@ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+  * iscsi_session_setup - create iscsi cls session and host and session
+  * @scsit: scsi transport template
+  * @iscsit: iscsi transport template
+- * @cmds_max: scsi host can queue
+- * @qdepth: scsi host cmds per lun
+- * @cmd_task_size: LLD ctask private data size
+- * @mgmt_task_size: LLD mtask private data size
+  * @initial_cmdsn: initial CmdSN
+  * @hostno: host no allocated
+  *
+@@ -1791,7 +1348,6 @@ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+ struct iscsi_cls_session *
+ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 		    struct scsi_transport_template *scsit,
+-		    uint16_t cmds_max, uint16_t qdepth,
+ 		    int cmd_task_size, int mgmt_task_size,
+ 		    uint32_t initial_cmdsn, uint32_t *hostno)
+ {
+@@ -1800,56 +1356,30 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 	struct iscsi_cls_session *cls_session;
+ 	int cmd_i;
+ 
+-	if (qdepth > ISCSI_MAX_CMD_PER_LUN || qdepth < 1) {
+-		if (qdepth != 0)
+-			printk(KERN_ERR "iscsi: invalid queue depth of %d. "
+-			      "Queue depth must be between 1 and %d.\n",
+-			      qdepth, ISCSI_MAX_CMD_PER_LUN);
+-		qdepth = ISCSI_DEF_CMD_PER_LUN;
+-	}
+-
+-	if (!is_power_of_2(cmds_max) || cmds_max >= ISCSI_MGMT_ITT_OFFSET ||
+-	    cmds_max < 2) {
+-		if (cmds_max != 0)
+-			printk(KERN_ERR "iscsi: invalid can_queue of %d. "
+-			       "can_queue must be a power of 2 and between "
+-			       "2 and %d - setting to %d.\n", cmds_max,
+-			       ISCSI_MGMT_ITT_OFFSET, ISCSI_DEF_XMIT_CMDS_MAX);
+-		cmds_max = ISCSI_DEF_XMIT_CMDS_MAX;
+-	}
+-
+ 	shost = scsi_host_alloc(iscsit->host_template,
+ 				hostdata_privsize(sizeof(*session)));
+ 	if (!shost)
+ 		return NULL;
+ 
+-	/* the iscsi layer takes one task for reserve */
+-	shost->can_queue = cmds_max - 1;
+-	shost->cmd_per_lun = qdepth;
+ 	shost->max_id = 1;
+ 	shost->max_channel = 0;
+ 	shost->max_lun = iscsit->max_lun;
+ 	shost->max_cmd_len = iscsit->max_cmd_len;
+ 	shost->transportt = scsit;
+ 	shost->transportt->create_work_queue = 1;
+-	shost->transportt->eh_timed_out = iscsi_eh_cmd_timed_out;
+ 	*hostno = shost->host_no;
+ 
+ 	session = iscsi_hostdata(shost->hostdata);
+ 	memset(session, 0, sizeof(struct iscsi_session));
+ 	session->host = shost;
+ 	session->state = ISCSI_STATE_FREE;
+-	session->fast_abort = 1;
+-	session->lu_reset_timeout = 15;
+-	session->abort_timeout = 10;
+ 	session->mgmtpool_max = ISCSI_MGMT_CMDS_MAX;
+-	session->cmds_max = cmds_max;
+-	session->queued_cmdsn = session->cmdsn = initial_cmdsn;
++	session->cmds_max = ISCSI_XMIT_CMDS_MAX;
++	session->cmdsn = initial_cmdsn;
+ 	session->exp_cmdsn = initial_cmdsn + 1;
+ 	session->max_cmdsn = initial_cmdsn + 1;
+ 	session->max_r2t = 1;
+ 	session->tt = iscsit;
+-	mutex_init(&session->eh_mutex);
+ 
+ 	/* initialize SCSI PDU commands pool */
+ 	if (iscsi_pool_init(&session->cmdpool, session->cmds_max,
+@@ -1904,9 +1434,9 @@ module_put:
+ cls_session_fail:
+ 	scsi_remove_host(shost);
+ add_host_fail:
+-	iscsi_pool_free(&session->mgmtpool);
++	iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds);
+ mgmtpool_alloc_fail:
+-	iscsi_pool_free(&session->cmdpool);
++	iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
+ cmdpool_alloc_fail:
+ 	scsi_host_put(shost);
+ 	return NULL;
+@@ -1926,22 +1456,14 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session)
+ 	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+ 	struct module *owner = cls_session->transport->owner;
+ 
+-	iscsi_remove_session(cls_session);
+ 	scsi_remove_host(shost);
+ 
+-	iscsi_pool_free(&session->mgmtpool);
+-	iscsi_pool_free(&session->cmdpool);
++	iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds);
++	iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
+ 
+-	kfree(session->password);
+-	kfree(session->password_in);
+-	kfree(session->username);
+-	kfree(session->username_in);
+ 	kfree(session->targetname);
+-	kfree(session->netdev);
+-	kfree(session->hwaddress);
+-	kfree(session->initiatorname);
+ 
+-	iscsi_free_session(cls_session);
++	iscsi_destroy_session(cls_session);
+ 	scsi_host_put(shost);
+ 	module_put(owner);
+ }
+@@ -1971,17 +1493,22 @@ iscsi_conn_setup(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 	conn->c_stage = ISCSI_CONN_INITIAL_STAGE;
+ 	conn->id = conn_idx;
+ 	conn->exp_statsn = 0;
+-	conn->tmf_state = TMF_INITIAL;
+-
+-	init_timer(&conn->transport_timer);
+-	conn->transport_timer.data = (unsigned long)conn;
+-	conn->transport_timer.function = iscsi_check_transport_timeouts;
+-
++	conn->tmabort_state = TMABORT_INITIAL;
+ 	INIT_LIST_HEAD(&conn->run_list);
+ 	INIT_LIST_HEAD(&conn->mgmt_run_list);
+-	INIT_LIST_HEAD(&conn->mgmtqueue);
+ 	INIT_LIST_HEAD(&conn->xmitqueue);
+-	INIT_LIST_HEAD(&conn->requeue);
++
++	/* initialize general immediate & non-immediate PDU commands queue */
++	conn->immqueue = kfifo_alloc(session->mgmtpool_max * sizeof(void*),
++			                GFP_KERNEL, NULL);
++	if (conn->immqueue == ERR_PTR(-ENOMEM))
++		goto immqueue_alloc_fail;
++
++	conn->mgmtqueue = kfifo_alloc(session->mgmtpool_max * sizeof(void*),
++			                GFP_KERNEL, NULL);
++	if (conn->mgmtqueue == ERR_PTR(-ENOMEM))
++		goto mgmtqueue_alloc_fail;
++
+ 	INIT_WORK(&conn->xmitwork, iscsi_xmitworker);
+ 
+ 	/* allocate login_mtask used for the login/text sequences */
+@@ -1999,7 +1526,8 @@ iscsi_conn_setup(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 		goto login_mtask_data_alloc_fail;
+ 	conn->login_mtask->data = conn->data = data;
+ 
+-	init_timer(&conn->tmf_timer);
++	init_timer(&conn->tmabort_timer);
++	mutex_init(&conn->xmitmutex);
+ 	init_waitqueue_head(&conn->ehwait);
+ 
+ 	return cls_conn;
+@@ -2008,6 +1536,10 @@ login_mtask_data_alloc_fail:
+ 	__kfifo_put(session->mgmtpool.queue, (void*)&conn->login_mtask,
+ 		    sizeof(void*));
+ login_mtask_alloc_fail:
++	kfifo_free(conn->mgmtqueue);
++mgmtqueue_alloc_fail:
++	kfifo_free(conn->immqueue);
++immqueue_alloc_fail:
+ 	iscsi_destroy_conn(cls_conn);
+ 	return NULL;
+ }
+@@ -2026,7 +1558,8 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	struct iscsi_session *session = conn->session;
+ 	unsigned long flags;
+ 
+-	del_timer_sync(&conn->transport_timer);
++	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
++	mutex_lock(&conn->xmitmutex);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	conn->c_stage = ISCSI_CONN_CLEANUP_WAIT;
+@@ -2039,6 +1572,8 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ 
++	mutex_unlock(&conn->xmitmutex);
++
+ 	/*
+ 	 * Block until all in-progress commands for this connection
+ 	 * time out or fail.
+@@ -2051,10 +1586,9 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 		}
+ 		spin_unlock_irqrestore(session->host->host_lock, flags);
+ 		msleep_interruptible(500);
+-		iscsi_conn_printk(KERN_INFO, conn, "iscsi conn_destroy(): "
+-				  "host_busy %d host_failed %d\n",
+-				  session->host->host_busy,
+-				  session->host->host_failed);
++		printk(KERN_INFO "iscsi: scsi conn_destroy(): host_busy %d "
++		       "host_failed %d\n", session->host->host_busy,
++		       session->host->host_failed);
+ 		/*
+ 		 * force eh_abort() to unblock
+ 		 */
+@@ -2062,17 +1596,23 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 
+ 	/* flush queued up work because we free the connection below */
+-	iscsi_suspend_tx(conn);
++	scsi_flush_work(session->host);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	kfree(conn->data);
+ 	kfree(conn->persistent_address);
+ 	__kfifo_put(session->mgmtpool.queue, (void*)&conn->login_mtask,
+ 		    sizeof(void*));
+-	if (session->leadconn == conn)
++	if (session->leadconn == conn) {
+ 		session->leadconn = NULL;
++		/* no connections exits.. reset sequencing */
++		session->cmdsn = session->max_cmdsn = session->exp_cmdsn = 1;
++	}
+ 	spin_unlock_bh(&session->lock);
+ 
++	kfifo_free(conn->immqueue);
++	kfifo_free(conn->mgmtqueue);
++
+ 	iscsi_destroy_conn(cls_conn);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_teardown);
+@@ -2083,41 +1623,21 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 	struct iscsi_session *session = conn->session;
+ 
+ 	if (!session) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "can't start unbound connection\n");
++		printk(KERN_ERR "iscsi: can't start unbound connection\n");
+ 		return -EPERM;
+ 	}
+ 
+ 	if ((session->imm_data_en || !session->initial_r2t_en) &&
+ 	     session->first_burst > session->max_burst) {
+-		iscsi_conn_printk(KERN_INFO, conn, "invalid burst lengths: "
+-				  "first_burst %d max_burst %d\n",
+-				  session->first_burst, session->max_burst);
++		printk("iscsi: invalid burst lengths: "
++		       "first_burst %d max_burst %d\n",
++		       session->first_burst, session->max_burst);
+ 		return -EINVAL;
+ 	}
+ 
+-	if (conn->ping_timeout && !conn->recv_timeout) {
+-		iscsi_conn_printk(KERN_ERR, conn, "invalid recv timeout of "
+-				  "zero. Using 5 seconds\n.");
+-		conn->recv_timeout = 5;
+-	}
+-
+-	if (conn->recv_timeout && !conn->ping_timeout) {
+-		iscsi_conn_printk(KERN_ERR, conn, "invalid ping timeout of "
+-				  "zero. Using 5 seconds.\n");
+-		conn->ping_timeout = 5;
+-	}
+-
+ 	spin_lock_bh(&session->lock);
+ 	conn->c_stage = ISCSI_CONN_STARTED;
+ 	session->state = ISCSI_STATE_LOGGED_IN;
+-	session->queued_cmdsn = session->cmdsn;
+-
+-	conn->last_recv = jiffies;
+-	conn->last_ping = jiffies;
+-	if (conn->recv_timeout && conn->ping_timeout)
+-		mod_timer(&conn->transport_timer,
+-			  jiffies + (conn->recv_timeout * HZ));
+ 
+ 	switch(conn->stop_stage) {
+ 	case STOP_CONN_RECOVER:
+@@ -2126,11 +1646,13 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 		 * commands after successful recovery
+ 		 */
+ 		conn->stop_stage = 0;
+-		conn->tmf_state = TMF_INITIAL;
++		conn->tmabort_state = TMABORT_INITIAL;
+ 		session->age++;
+-		if (session->age == 16)
+-			session->age = 0;
+-		break;
++		spin_unlock_bh(&session->lock);
++
++		iscsi_unblock_session(session_to_cls(session));
++		wake_up(&conn->ehwait);
++		return 0;
+ 	case STOP_CONN_TERM:
+ 		conn->stop_stage = 0;
+ 		break;
+@@ -2139,8 +1661,6 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ 
+-	iscsi_unblock_session(session_to_cls(session));
+-	wake_up(&conn->ehwait);
+ 	return 0;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_start);
+@@ -2151,43 +1671,59 @@ flush_control_queues(struct iscsi_session *session, struct iscsi_conn *conn)
+ 	struct iscsi_mgmt_task *mtask, *tmp;
+ 
+ 	/* handle pending */
+-	list_for_each_entry_safe(mtask, tmp, &conn->mgmtqueue, running) {
++	while (__kfifo_get(conn->immqueue, (void*)&mtask, sizeof(void*)) ||
++	       __kfifo_get(conn->mgmtqueue, (void*)&mtask, sizeof(void*))) {
++		if (mtask == conn->login_mtask)
++			continue;
+ 		debug_scsi("flushing pending mgmt task itt 0x%x\n", mtask->itt);
+-		iscsi_free_mgmt_task(conn, mtask);
++		__kfifo_put(session->mgmtpool.queue, (void*)&mtask,
++			    sizeof(void*));
+ 	}
+ 
+ 	/* handle running */
+ 	list_for_each_entry_safe(mtask, tmp, &conn->mgmt_run_list, running) {
+ 		debug_scsi("flushing running mgmt task itt 0x%x\n", mtask->itt);
+-		iscsi_free_mgmt_task(conn, mtask);
++		list_del(&mtask->running);
++
++		if (mtask == conn->login_mtask)
++			continue;
++		__kfifo_put(session->mgmtpool.queue, (void*)&mtask,
++			   sizeof(void*));
+ 	}
+ 
+ 	conn->mtask = NULL;
+ }
+ 
++/* Fail commands. Mutex and session lock held and recv side suspended */
++static void fail_all_commands(struct iscsi_conn *conn)
++{
++	struct iscsi_cmd_task *ctask, *tmp;
++
++	/* flush pending */
++	list_for_each_entry_safe(ctask, tmp, &conn->xmitqueue, running) {
++		debug_scsi("failing pending sc %p itt 0x%x\n", ctask->sc,
++			   ctask->itt);
++		fail_command(conn, ctask, DID_BUS_BUSY << 16);
++	}
++
++	/* fail all other running */
++	list_for_each_entry_safe(ctask, tmp, &conn->run_list, running) {
++		debug_scsi("failing in progress sc %p itt 0x%x\n",
++			   ctask->sc, ctask->itt);
++		fail_command(conn, ctask, DID_BUS_BUSY << 16);
++	}
++
++	conn->ctask = NULL;
++}
++
+ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 					 struct iscsi_conn *conn, int flag)
+ {
+ 	int old_stop_stage;
+ 
+-	del_timer_sync(&conn->transport_timer);
+-
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (conn->stop_stage == STOP_CONN_TERM) {
+ 		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+-		return;
+-	}
+-
+-	/*
+-	 * The LLD either freed/unset the lock on us, or userspace called
+-	 * stop but did not create a proper connection (connection was never
+-	 * bound or it was unbound then stop was called).
+-	 */
+-	if (!conn->recv_lock) {
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return;
+ 	}
+ 
+@@ -2204,14 +1740,14 @@ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 	old_stop_stage = conn->stop_stage;
+ 	conn->stop_stage = flag;
+ 	conn->c_stage = ISCSI_CONN_STOPPED;
++	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+ 	spin_unlock_bh(&session->lock);
+ 
+-	iscsi_suspend_tx(conn);
+-
+ 	write_lock_bh(conn->recv_lock);
+ 	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
+ 	write_unlock_bh(conn->recv_lock);
+ 
++	mutex_lock(&conn->xmitmutex);
+ 	/*
+ 	 * for connection level recovery we should not calculate
+ 	 * header digest. conn->hdr_size used for optimization
+@@ -2232,11 +1768,11 @@ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 	 * flush queues.
+ 	 */
+ 	spin_lock_bh(&session->lock);
+-	fail_all_commands(conn, -1,
+-			STOP_CONN_RECOVER ? DID_BUS_BUSY : DID_ERROR);
++	fail_all_commands(conn);
+ 	flush_control_queues(session, conn);
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
++	mutex_unlock(&conn->xmitmutex);
+ }
+ 
+ void iscsi_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+@@ -2250,8 +1786,7 @@ void iscsi_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+ 		iscsi_start_session_recovery(session, conn, flag);
+ 		break;
+ 	default:
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid stop flag %d\n", flag);
++		printk(KERN_ERR "iscsi: invalid stop flag %d\n", flag);
+ 	}
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_stop);
+@@ -2285,21 +1820,6 @@ int iscsi_set_param(struct iscsi_cls_conn *cls_conn,
+ 	uint32_t value;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_FAST_ABORT:
+-		sscanf(buf, "%d", &session->fast_abort);
+-		break;
+-	case ISCSI_PARAM_ABORT_TMO:
+-		sscanf(buf, "%d", &session->abort_timeout);
+-		break;
+-	case ISCSI_PARAM_LU_RESET_TMO:
+-		sscanf(buf, "%d", &session->lu_reset_timeout);
+-		break;
+-	case ISCSI_PARAM_PING_TMO:
+-		sscanf(buf, "%d", &conn->ping_timeout);
+-		break;
+-	case ISCSI_PARAM_RECV_TMO:
+-		sscanf(buf, "%d", &conn->recv_timeout);
+-		break;
+ 	case ISCSI_PARAM_MAX_RECV_DLENGTH:
+ 		sscanf(buf, "%d", &conn->max_recv_dlength);
+ 		break;
+@@ -2347,30 +1867,6 @@ int iscsi_set_param(struct iscsi_cls_conn *cls_conn,
+ 	case ISCSI_PARAM_EXP_STATSN:
+ 		sscanf(buf, "%u", &conn->exp_statsn);
+ 		break;
+-	case ISCSI_PARAM_USERNAME:
+-		kfree(session->username);
+-		session->username = kstrdup(buf, GFP_KERNEL);
+-		if (!session->username)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_USERNAME_IN:
+-		kfree(session->username_in);
+-		session->username_in = kstrdup(buf, GFP_KERNEL);
+-		if (!session->username_in)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_PASSWORD:
+-		kfree(session->password);
+-		session->password = kstrdup(buf, GFP_KERNEL);
+-		if (!session->password)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_PASSWORD_IN:
+-		kfree(session->password_in);
+-		session->password_in = kstrdup(buf, GFP_KERNEL);
+-		if (!session->password_in)
+-			return -ENOMEM;
+-		break;
+ 	case ISCSI_PARAM_TARGET_NAME:
+ 		/* this should not change between logins */
+ 		if (session->targetname)
+@@ -2414,15 +1910,6 @@ int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ 	int len;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_FAST_ABORT:
+-		len = sprintf(buf, "%d\n", session->fast_abort);
+-		break;
+-	case ISCSI_PARAM_ABORT_TMO:
+-		len = sprintf(buf, "%d\n", session->abort_timeout);
+-		break;
+-	case ISCSI_PARAM_LU_RESET_TMO:
+-		len = sprintf(buf, "%d\n", session->lu_reset_timeout);
+-		break;
+ 	case ISCSI_PARAM_INITIAL_R2T_EN:
+ 		len = sprintf(buf, "%d\n", session->initial_r2t_en);
+ 		break;
+@@ -2453,18 +1940,6 @@ int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ 	case ISCSI_PARAM_TPGT:
+ 		len = sprintf(buf, "%d\n", session->tpgt);
+ 		break;
+-	case ISCSI_PARAM_USERNAME:
+-		len = sprintf(buf, "%s\n", session->username);
+-		break;
+-	case ISCSI_PARAM_USERNAME_IN:
+-		len = sprintf(buf, "%s\n", session->username_in);
+-		break;
+-	case ISCSI_PARAM_PASSWORD:
+-		len = sprintf(buf, "%s\n", session->password);
+-		break;
+-	case ISCSI_PARAM_PASSWORD_IN:
+-		len = sprintf(buf, "%s\n", session->password_in);
+-		break;
+ 	default:
+ 		return -ENOSYS;
+ 	}
+@@ -2480,12 +1955,6 @@ int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 	int len;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_PING_TMO:
+-		len = sprintf(buf, "%u\n", conn->ping_timeout);
+-		break;
+-	case ISCSI_PARAM_RECV_TMO:
+-		len = sprintf(buf, "%u\n", conn->recv_timeout);
+-		break;
+ 	case ISCSI_PARAM_MAX_RECV_DLENGTH:
+ 		len = sprintf(buf, "%u\n", conn->max_recv_dlength);
+ 		break;
+@@ -2521,66 +1990,6 @@ int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_get_param);
+ 
+-int iscsi_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf)
+-{
+-	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-	int len;
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_NETDEV_NAME:
+-		if (!session->netdev)
+-			len = sprintf(buf, "%s\n", "default");
+-		else
+-			len = sprintf(buf, "%s\n", session->netdev);
+-		break;
+-	case ISCSI_HOST_PARAM_HWADDRESS:
+-		if (!session->hwaddress)
+-			len = sprintf(buf, "%s\n", "default");
+-		else
+-			len = sprintf(buf, "%s\n", session->hwaddress);
+-		break;
+-	case ISCSI_HOST_PARAM_INITIATOR_NAME:
+-		if (!session->initiatorname)
+-			len = sprintf(buf, "%s\n", "unknown");
+-		else
+-			len = sprintf(buf, "%s\n", session->initiatorname);
+-		break;
+-
+-	default:
+-		return -ENOSYS;
+-	}
+-
+-	return len;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_host_get_param);
+-
+-int iscsi_host_set_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf, int buflen)
+-{
+-	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_NETDEV_NAME:
+-		if (!session->netdev)
+-			session->netdev = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	case ISCSI_HOST_PARAM_HWADDRESS:
+-		if (!session->hwaddress)
+-			session->hwaddress = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	case ISCSI_HOST_PARAM_INITIATOR_NAME:
+-		if (!session->initiatorname)
+-			session->initiatorname = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	default:
+-		return -ENOSYS;
+-	}
+-
+-	return 0;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_host_set_param);
+-
+ MODULE_AUTHOR("Mike Christie");
+ MODULE_DESCRIPTION("iSCSI library functions");
+ MODULE_LICENSE("GPL");
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..cdedee5 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -30,27 +30,26 @@
+ #include <scsi/scsi_transport_iscsi.h>
+ #include <scsi/iscsi_if.h>
+ 
+-#define ISCSI_SESSION_ATTRS 19
+-#define ISCSI_CONN_ATTRS 13
+-#define ISCSI_HOST_ATTRS 4
+-#define ISCSI_TRANSPORT_VERSION "2.0-869"
++#define ISCSI_SESSION_ATTRS 11
++#define ISCSI_CONN_ATTRS 11
++#define ISCSI_HOST_ATTRS 0
++#define ISCSI_TRANSPORT_VERSION "2.0-724"
+ 
+ struct iscsi_internal {
+ 	int daemon_pid;
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+-static struct workqueue_struct *iscsi_eh_timer_workq;
+ 
+ /*
+  * list of registered transports and lock that must
+@@ -63,12 +62,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +77,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +103,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -118,10 +115,8 @@ static struct attribute_group iscsi_transport_group = {
+ 	.attrs = iscsi_transport_attrs,
+ };
+ 
+-
+-
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -129,31 +124,13 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ 	memset(ihost, 0, sizeof(*ihost));
+ 	INIT_LIST_HEAD(&ihost->sessions);
+ 	mutex_init(&ihost->mutex);
+-	atomic_set(&ihost->nr_scans, 0);
+-
+-	snprintf(ihost->scan_workq_name, KOBJ_NAME_LEN, "iscsi_scan_%d",
+-		shost->host_no);
+-	ihost->scan_workq = create_singlethread_workqueue(
+-						ihost->scan_workq_name);
+-	if (!ihost->scan_workq)
+-		return -ENOMEM;
+-	return 0;
+-}
+-
+-static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
+-{
+-	struct Scsi_Host *shost = dev_to_shost(dev);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	destroy_workqueue(ihost->scan_workq);
+ 	return 0;
+ }
+ 
+ static DECLARE_TRANSPORT_CLASS(iscsi_host_class,
+ 			       "iscsi_host",
+ 			       iscsi_setup_host,
+-			       iscsi_remove_host,
++			       NULL,
+ 			       NULL);
+ 
+ static DECLARE_TRANSPORT_CLASS(iscsi_session_class,
+@@ -224,54 +201,6 @@ static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid)
+  * The following functions can be used by LLDs that allocate
+  * their own scsi_hosts or by software iscsi LLDs
+  */
+-static struct {
+-	int value;
+-	char *name;
+-} iscsi_session_state_names[] = {
+-	{ ISCSI_SESSION_LOGGED_IN,	"LOGGED_IN" },
+-	{ ISCSI_SESSION_FAILED,		"FAILED" },
+-	{ ISCSI_SESSION_FREE,		"FREE" },
+-};
+-
+-static const char *iscsi_session_state_name(int state)
+-{
+-	int i;
+-	char *name = NULL;
+-
+-	for (i = 0; i < ARRAY_SIZE(iscsi_session_state_names); i++) {
+-		if (iscsi_session_state_names[i].value == state) {
+-			name = iscsi_session_state_names[i].name;
+-			break;
+-		}
+-	}
+-	return name;
+-}
+-
+-int iscsi_session_chkready(struct iscsi_cls_session *session)
+-{
+-	unsigned long flags;
+-	int err;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	switch (session->state) {
+-	case ISCSI_SESSION_LOGGED_IN:
+-		err = 0;
+-		break;
+-	case ISCSI_SESSION_FAILED:
+-		err = DID_IMM_RETRY << 16;
+-		break;
+-	case ISCSI_SESSION_FREE:
+-		err = DID_NO_CONNECT << 16;
+-		break;
+-	default:
+-		err = DID_NO_CONNECT << 16;
+-		break;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	return err;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_session_chkready);
+-
+ static void iscsi_session_release(struct device *dev)
+ {
+ 	struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
+@@ -287,25 +216,6 @@ static int iscsi_is_session_dev(const struct device *dev)
+ 	return dev->release == iscsi_session_release;
+ }
+ 
+-/**
+- * iscsi_scan_finished - helper to report when running scans are done
+- * @shost: scsi host
+- * @time: scan run time
+- *
+- * This function can be used by drives like qla4xxx to report to the scsi
+- * layer when the scans it kicked off at module load time are done.
+- */
+-int iscsi_scan_finished(struct Scsi_Host *shost, unsigned long time)
+-{
+-	struct iscsi_host *ihost = shost->shost_data;
+-	/*
+-	 * qla4xxx will have kicked off some session unblocks before calling
+-	 * scsi_scan_host, so just wait for them to complete.
+-	 */
+-	return !atomic_read(&ihost->nr_scans);
+-}
+-EXPORT_SYMBOL_GPL(iscsi_scan_finished);
+-
+ static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+ 			   uint id, uint lun)
+ {
+@@ -324,50 +234,14 @@ static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+ 	return 0;
+ }
+ 
+-static void iscsi_scan_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session, scan_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	if (session->state != ISCSI_SESSION_LOGGED_IN) {
+-		spin_unlock_irqrestore(&session->lock, flags);
+-		goto done;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
+-
+-	scsi_scan_target(&session->dev, 0, session->target_id,
+-			 SCAN_WILD_CARD, 1);
+-done:
+-	atomic_dec(&ihost->nr_scans);
+-}
+-
+ static void session_recovery_timedout(struct work_struct *work)
+ {
+ 	struct iscsi_cls_session *session =
+ 		container_of(work, struct iscsi_cls_session,
+ 			     recovery_work.work);
+-	unsigned long flags;
+-
+-	iscsi_cls_session_printk(KERN_INFO, session,
+-				 "session recovery timed out after %d secs\n",
+-				 session->recovery_tmo);
+ 
+-	spin_lock_irqsave(&session->lock, flags);
+-	switch (session->state) {
+-	case ISCSI_SESSION_FAILED:
+-		session->state = ISCSI_SESSION_FREE;
+-		break;
+-	case ISCSI_SESSION_LOGGED_IN:
+-	case ISCSI_SESSION_FREE:
+-		/* we raced with the unblock's flush */
+-		spin_unlock_irqrestore(&session->lock, flags);
+-		return;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
++	dev_printk(KERN_INFO, &session->dev, "iscsi: session recovery timed "
++		  "out after %d secs\n", session->recovery_tmo);
+ 
+ 	if (session->transport->session_recovery_timedout)
+ 		session->transport->session_recovery_timedout(session);
+@@ -375,103 +249,22 @@ static void session_recovery_timedout(struct work_struct *work)
+ 	scsi_target_unblock(&session->dev);
+ }
+ 
+-static void __iscsi_unblock_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     unblock_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-
+-	/*
+-	 * The recovery and unblock work get run from the same workqueue,
+-	 * so try to cancel it if it was going to run after this unblock.
+-	 */
+-	cancel_delayed_work(&session->recovery_work);
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_LOGGED_IN;
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	/* start IO */
+-	scsi_target_unblock(&session->dev);
+-	/*
+-	 * Only do kernel scanning if the driver is properly hooked into
+-	 * the async scanning code (drivers like iscsi_tcp do login and
+-	 * scanning from userspace).
+-	 */
+-	if (shost->hostt->scan_finished) {
+-		if (queue_work(ihost->scan_workq, &session->scan_work))
+-			atomic_inc(&ihost->nr_scans);
+-	}
+-}
+-
+-/**
+- * iscsi_unblock_session - set a session as logged in and start IO.
+- * @session: iscsi session
+- *
+- * Mark a session as ready to accept IO.
+- */
+ void iscsi_unblock_session(struct iscsi_cls_session *session)
+ {
+-	queue_work(iscsi_eh_timer_workq, &session->unblock_work);
+-	/*
+-	 * make sure all the events have completed before tell the driver
+-	 * it is safe
+-	 */
+-	flush_workqueue(iscsi_eh_timer_workq);
++	if (!cancel_delayed_work(&session->recovery_work))
++		flush_scheduled_work();
++	scsi_target_unblock(&session->dev);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_unblock_session);
+ 
+-static void __iscsi_block_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     block_work);
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_FAILED;
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	scsi_target_block(&session->dev);
+-	queue_delayed_work(iscsi_eh_timer_workq, &session->recovery_work,
+-			   session->recovery_tmo * HZ);
+-}
+-
+ void iscsi_block_session(struct iscsi_cls_session *session)
+ {
+-	queue_work(iscsi_eh_timer_workq, &session->block_work);
++	scsi_target_block(&session->dev);
++	schedule_delayed_work(&session->recovery_work,
++			     session->recovery_tmo * HZ);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_block_session);
+ 
+-static void __iscsi_unbind_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     unbind_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	/* Prevent new scans and make sure scanning is not in progress */
+-	mutex_lock(&ihost->mutex);
+-	if (list_empty(&session->host_list)) {
+-		mutex_unlock(&ihost->mutex);
+-		return;
+-	}
+-	list_del_init(&session->host_list);
+-	mutex_unlock(&ihost->mutex);
+-
+-	scsi_remove_target(&session->dev);
+-	iscsi_session_event(session, ISCSI_KEVENT_UNBIND_SESSION);
+-}
+-
+-static int iscsi_unbind_session(struct iscsi_cls_session *session)
+-{
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	return queue_work(ihost->scan_workq, &session->unbind_work);
+-}
+-
+ struct iscsi_cls_session *
+ iscsi_alloc_session(struct Scsi_Host *shost,
+ 		    struct iscsi_transport *transport)
+@@ -485,15 +278,9 @@ iscsi_alloc_session(struct Scsi_Host *shost,
+ 
+ 	session->transport = transport;
+ 	session->recovery_tmo = 120;
+-	session->state = ISCSI_SESSION_FREE;
+ 	INIT_DELAYED_WORK(&session->recovery_work, session_recovery_timedout);
+ 	INIT_LIST_HEAD(&session->host_list);
+ 	INIT_LIST_HEAD(&session->sess_list);
+-	INIT_WORK(&session->unblock_work, __iscsi_unblock_session);
+-	INIT_WORK(&session->block_work, __iscsi_block_session);
+-	INIT_WORK(&session->unbind_work, __iscsi_unbind_session);
+-	INIT_WORK(&session->scan_work, iscsi_scan_session);
+-	spin_lock_init(&session->lock);
+ 
+ 	/* this is released in the dev's release function */
+ 	scsi_host_get(shost);
+@@ -510,7 +297,6 @@ int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
+ {
+ 	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+ 	struct iscsi_host *ihost;
+-	unsigned long flags;
+ 	int err;
+ 
+ 	ihost = shost->shost_data;
+@@ -521,21 +307,15 @@ int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
+ 		 session->sid);
+ 	err = device_add(&session->dev);
+ 	if (err) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "could not register session's dev\n");
++		dev_printk(KERN_ERR, &session->dev, "iscsi: could not "
++			   "register session's dev\n");
+ 		goto release_host;
+ 	}
+ 	transport_register_device(&session->dev);
+ 
+-	spin_lock_irqsave(&sesslock, flags);
+-	list_add(&session->sess_list, &sesslist);
+-	spin_unlock_irqrestore(&sesslock, flags);
+-
+ 	mutex_lock(&ihost->mutex);
+ 	list_add(&session->host_list, &ihost->sessions);
+ 	mutex_unlock(&ihost->mutex);
+-
+-	iscsi_session_event(session, ISCSI_KEVENT_CREATE_SESSION);
+ 	return 0;
+ 
+ release_host:
+@@ -548,10 +328,9 @@ EXPORT_SYMBOL_GPL(iscsi_add_session);
+  * iscsi_create_session - create iscsi class session
+  * @shost: scsi host
+  * @transport: iscsi transport
+- * @target_id: which target
+  *
+  * This can be called from a LLD or iscsi_transport.
+- */
++ **/
+ struct iscsi_cls_session *
+ iscsi_create_session(struct Scsi_Host *shost,
+ 		     struct iscsi_transport *transport,
+@@ -571,65 +350,19 @@ iscsi_create_session(struct Scsi_Host *shost,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_create_session);
+ 
+-static void iscsi_conn_release(struct device *dev)
+-{
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev);
+-	struct device *parent = conn->dev.parent;
+-
+-	kfree(conn);
+-	put_device(parent);
+-}
+-
+-static int iscsi_is_conn_dev(const struct device *dev)
+-{
+-	return dev->release == iscsi_conn_release;
+-}
+-
+-static int iscsi_iter_destroy_conn_fn(struct device *dev, void *data)
+-{
+-	if (!iscsi_is_conn_dev(dev))
+-		return 0;
+-	return iscsi_destroy_conn(iscsi_dev_to_conn(dev));
+-}
+-
+ void iscsi_remove_session(struct iscsi_cls_session *session)
+ {
+ 	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+ 	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-	int err;
+-
+-	spin_lock_irqsave(&sesslock, flags);
+-	list_del(&session->sess_list);
+-	spin_unlock_irqrestore(&sesslock, flags);
+ 
+-	/* make sure there are no blocks/unblocks queued */
+-	flush_workqueue(iscsi_eh_timer_workq);
+-	/* make sure the timedout callout is not running */
+ 	if (!cancel_delayed_work(&session->recovery_work))
+-		flush_workqueue(iscsi_eh_timer_workq);
+-	/*
+-	 * If we are blocked let commands flow again. The lld or iscsi
+-	 * layer should set up the queuecommand to fail commands.
+-	 * We assume that LLD will not be calling block/unblock while
+-	 * removing the session.
+-	 */
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_FREE;
+-	spin_unlock_irqrestore(&session->lock, flags);
++		flush_scheduled_work();
+ 
+-	scsi_target_unblock(&session->dev);
+-	/* flush running scans then delete devices */
+-	flush_workqueue(ihost->scan_workq);
+-	__iscsi_unbind_session(&session->unbind_work);
++	mutex_lock(&ihost->mutex);
++	list_del(&session->host_list);
++	mutex_unlock(&ihost->mutex);
+ 
+-	/* hw iscsi may not have removed all connections from session */
+-	err = device_for_each_child(&session->dev, NULL,
+-				    iscsi_iter_destroy_conn_fn);
+-	if (err)
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Could not delete all connections "
+-					 "for session. Error %d.\n", err);
++	scsi_remove_target(&session->dev);
+ 
+ 	transport_unregister_device(&session->dev);
+ 	device_del(&session->dev);
+@@ -638,9 +371,9 @@ EXPORT_SYMBOL_GPL(iscsi_remove_session);
+ 
+ void iscsi_free_session(struct iscsi_cls_session *session)
+ {
+-	iscsi_session_event(session, ISCSI_KEVENT_DESTROY_SESSION);
+ 	put_device(&session->dev);
+ }
++
+ EXPORT_SYMBOL_GPL(iscsi_free_session);
+ 
+ /**
+@@ -649,7 +382,7 @@ EXPORT_SYMBOL_GPL(iscsi_free_session);
+  *
+  * Can be called by a LLD or iscsi_transport. There must not be
+  * any running connections.
+- */
++ **/
+ int iscsi_destroy_session(struct iscsi_cls_session *session)
+ {
+ 	iscsi_remove_session(session);
+@@ -658,6 +391,20 @@ int iscsi_destroy_session(struct iscsi_cls_session *session)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_destroy_session);
+ 
++static void iscsi_conn_release(struct device *dev)
++{
++	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev);
++	struct device *parent = conn->dev.parent;
++
++	kfree(conn);
++	put_device(parent);
++}
++
++static int iscsi_is_conn_dev(const struct device *dev)
++{
++	return dev->release == iscsi_conn_release;
++}
++
+ /**
+  * iscsi_create_conn - create iscsi class connection
+  * @session: iscsi cls session
+@@ -671,13 +418,12 @@ EXPORT_SYMBOL_GPL(iscsi_destroy_session);
+  * for software iscsi we could be trying to preallocate a connection struct
+  * in which case there could be two connection structs and cid would be
+  * non-zero.
+- */
++ **/
+ struct iscsi_cls_conn *
+ iscsi_create_conn(struct iscsi_cls_session *session, uint32_t cid)
+ {
+ 	struct iscsi_transport *transport = session->transport;
+ 	struct iscsi_cls_conn *conn;
+-	unsigned long flags;
+ 	int err;
+ 
+ 	conn = kzalloc(sizeof(*conn) + transport->conndata_size, GFP_KERNEL);
+@@ -701,16 +447,11 @@ iscsi_create_conn(struct iscsi_cls_session *session, uint32_t cid)
+ 	conn->dev.release = iscsi_conn_release;
+ 	err = device_register(&conn->dev);
+ 	if (err) {
+-		iscsi_cls_session_printk(KERN_ERR, session, "could not "
+-					 "register connection's dev\n");
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: could not register "
++			   "connection's dev\n");
+ 		goto release_parent_ref;
+ 	}
+ 	transport_register_device(&conn->dev);
+-
+-	spin_lock_irqsave(&connlock, flags);
+-	list_add(&conn->conn_list, &connlist);
+-	conn->active = 1;
+-	spin_unlock_irqrestore(&connlock, flags);
+ 	return conn;
+ 
+ release_parent_ref:
+@@ -724,23 +465,17 @@ EXPORT_SYMBOL_GPL(iscsi_create_conn);
+ 
+ /**
+  * iscsi_destroy_conn - destroy iscsi class connection
+- * @conn: iscsi cls session
++ * @session: iscsi cls session
+  *
+  * This can be called from a LLD or iscsi_transport.
+- */
++ **/
+ int iscsi_destroy_conn(struct iscsi_cls_conn *conn)
+ {
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&connlock, flags);
+-	conn->active = 0;
+-	list_del(&conn->conn_list);
+-	spin_unlock_irqrestore(&connlock, flags);
+-
+ 	transport_unregister_device(&conn->dev);
+ 	device_unregister(&conn->dev);
+ 	return 0;
+ }
++
+ EXPORT_SYMBOL_GPL(iscsi_destroy_conn);
+ 
+ /*
+@@ -809,8 +544,8 @@ int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+ 	if (!skb) {
+ 		iscsi_conn_error(conn, ISCSI_ERR_CONN_FAILED);
+-		iscsi_cls_conn_printk(KERN_ERR, conn, "can not deliver "
+-				      "control PDU: OOM\n");
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: can not deliver "
++			   "control PDU: OOM\n");
+ 		return -ENOMEM;
+ 	}
+ 
+@@ -843,8 +578,8 @@ void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
+ 
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+ 	if (!skb) {
+-		iscsi_cls_conn_printk(KERN_ERR, conn, "gracefully ignored "
+-				      "conn error (%d)\n", error);
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: gracefully ignored "
++			  "conn error (%d)\n", error);
+ 		return;
+ 	}
+ 
+@@ -858,8 +593,8 @@ void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
+ 
+ 	iscsi_broadcast_skb(skb, GFP_ATOMIC);
+ 
+-	iscsi_cls_conn_printk(KERN_INFO, conn, "detected conn error (%d)\n",
+-			      error);
++	dev_printk(KERN_INFO, &conn->dev, "iscsi: detected conn error (%d)\n",
++		   error);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_error);
+ 
+@@ -874,10 +609,12 @@ iscsi_if_send_reply(int pid, int seq, int type, int done, int multi,
+ 	int t = done ? NLMSG_DONE : type;
+ 
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+-	if (!skb) {
+-		printk(KERN_ERR "Could not allocate skb to send reply.\n");
+-		return -ENOMEM;
+-	}
++	/*
++	 * FIXME:
++	 * user is supposed to react on iferror == -ENOMEM;
++	 * see iscsi_if_rx().
++	 */
++	BUG_ON(!skb);
+ 
+ 	nlh = __nlmsg_put(skb, pid, seq, t, (len - sizeof(*nlh)), 0);
+ 	nlh->nlmsg_flags = flags;
+@@ -914,8 +651,8 @@ iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+ 
+ 		skbstat = alloc_skb(len, GFP_ATOMIC);
+ 		if (!skbstat) {
+-			iscsi_cls_conn_printk(KERN_ERR, conn, "can not "
+-					      "deliver stats: OOM\n");
++			dev_printk(KERN_ERR, &conn->dev, "iscsi: can not "
++				   "deliver stats: OOM\n");
+ 			return -ENOMEM;
+ 		}
+ 
+@@ -950,87 +687,144 @@ iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+ }
+ 
+ /**
+- * iscsi_session_event - send session destr. completion event
+- * @session: iscsi class session
+- * @event: type of event
+- */
+-int iscsi_session_event(struct iscsi_cls_session *session,
+-			enum iscsi_uevent_e event)
++ * iscsi_if_destroy_session_done - send session destr. completion event
++ * @conn: last connection for session
++ *
++ * This is called by HW iscsi LLDs to notify userpsace that its HW has
++ * removed a session.
++ **/
++int iscsi_if_destroy_session_done(struct iscsi_cls_conn *conn)
+ {
+ 	struct iscsi_internal *priv;
++	struct iscsi_cls_session *session;
+ 	struct Scsi_Host *shost;
+ 	struct iscsi_uevent *ev;
+ 	struct sk_buff  *skb;
+ 	struct nlmsghdr *nlh;
++	unsigned long flags;
+ 	int rc, len = NLMSG_SPACE(sizeof(*ev));
+ 
+-	priv = iscsi_if_transport_lookup(session->transport);
++	priv = iscsi_if_transport_lookup(conn->transport);
+ 	if (!priv)
+ 		return -EINVAL;
++
++	session = iscsi_dev_to_session(conn->dev.parent);
+ 	shost = iscsi_session_to_shost(session);
+ 
+ 	skb = alloc_skb(len, GFP_KERNEL);
+ 	if (!skb) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Cannot notify userspace of session "
+-					 "event %u\n", event);
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event\n");
+ 		return -ENOMEM;
+ 	}
+ 
+ 	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
+ 	ev = NLMSG_DATA(nlh);
+-	ev->transport_handle = iscsi_handle(session->transport);
++	ev->transport_handle = iscsi_handle(conn->transport);
++	ev->type = ISCSI_KEVENT_DESTROY_SESSION;
++	ev->r.d_session.host_no = shost->host_no;
++	ev->r.d_session.sid = session->sid;
+ 
+-	ev->type = event;
+-	switch (event) {
+-	case ISCSI_KEVENT_DESTROY_SESSION:
+-		ev->r.d_session.host_no = shost->host_no;
+-		ev->r.d_session.sid = session->sid;
+-		break;
+-	case ISCSI_KEVENT_CREATE_SESSION:
+-		ev->r.c_session_ret.host_no = shost->host_no;
+-		ev->r.c_session_ret.sid = session->sid;
+-		break;
+-	case ISCSI_KEVENT_UNBIND_SESSION:
+-		ev->r.unbind_session.host_no = shost->host_no;
+-		ev->r.unbind_session.sid = session->sid;
+-		break;
+-	default:
+-		iscsi_cls_session_printk(KERN_ERR, session, "Invalid event "
+-					 "%u.\n", event);
+-		kfree_skb(skb);
++	/*
++	 * this will occur if the daemon is not up, so we just warn
++	 * the user and when the daemon is restarted it will handle it
++	 */
++	rc = iscsi_broadcast_skb(skb, GFP_KERNEL);
++	if (rc < 0)
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session destruction event. Check iscsi daemon\n");
++
++	spin_lock_irqsave(&sesslock, flags);
++	list_del(&session->sess_list);
++	spin_unlock_irqrestore(&sesslock, flags);
++
++	spin_lock_irqsave(&connlock, flags);
++	conn->active = 0;
++	list_del(&conn->conn_list);
++	spin_unlock_irqrestore(&connlock, flags);
++
++	return rc;
++}
++EXPORT_SYMBOL_GPL(iscsi_if_destroy_session_done);
++
++/**
++ * iscsi_if_create_session_done - send session creation completion event
++ * @conn: leading connection for session
++ *
++ * This is called by HW iscsi LLDs to notify userpsace that its HW has
++ * created a session or a existing session is back in the logged in state.
++ **/
++int iscsi_if_create_session_done(struct iscsi_cls_conn *conn)
++{
++	struct iscsi_internal *priv;
++	struct iscsi_cls_session *session;
++	struct Scsi_Host *shost;
++	struct iscsi_uevent *ev;
++	struct sk_buff  *skb;
++	struct nlmsghdr *nlh;
++	unsigned long flags;
++	int rc, len = NLMSG_SPACE(sizeof(*ev));
++
++	priv = iscsi_if_transport_lookup(conn->transport);
++	if (!priv)
+ 		return -EINVAL;
++
++	session = iscsi_dev_to_session(conn->dev.parent);
++	shost = iscsi_session_to_shost(session);
++
++	skb = alloc_skb(len, GFP_KERNEL);
++	if (!skb) {
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event\n");
++		return -ENOMEM;
+ 	}
+ 
++	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
++	ev = NLMSG_DATA(nlh);
++	ev->transport_handle = iscsi_handle(conn->transport);
++	ev->type = ISCSI_UEVENT_CREATE_SESSION;
++	ev->r.c_session_ret.host_no = shost->host_no;
++	ev->r.c_session_ret.sid = session->sid;
++
+ 	/*
+ 	 * this will occur if the daemon is not up, so we just warn
+ 	 * the user and when the daemon is restarted it will handle it
+ 	 */
+ 	rc = iscsi_broadcast_skb(skb, GFP_KERNEL);
+ 	if (rc < 0)
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Cannot notify userspace of session "
+-					 "event %u. Check iscsi daemon\n",
+-					 event);
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event. Check iscsi daemon\n");
++
++	spin_lock_irqsave(&sesslock, flags);
++	list_add(&session->sess_list, &sesslist);
++	spin_unlock_irqrestore(&sesslock, flags);
++
++	spin_lock_irqsave(&connlock, flags);
++	list_add(&conn->conn_list, &connlist);
++	conn->active = 1;
++	spin_unlock_irqrestore(&connlock, flags);
+ 	return rc;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_session_event);
++EXPORT_SYMBOL_GPL(iscsi_if_create_session_done);
+ 
+ static int
+ iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_uevent *ev)
+ {
+ 	struct iscsi_transport *transport = priv->iscsi_transport;
+ 	struct iscsi_cls_session *session;
++	unsigned long flags;
+ 	uint32_t hostno;
+ 
+ 	session = transport->create_session(transport, &priv->t,
+-					    ev->u.c_session.cmds_max,
+-					    ev->u.c_session.queue_depth,
+ 					    ev->u.c_session.initial_cmdsn,
+ 					    &hostno);
+ 	if (!session)
+ 		return -ENOMEM;
+ 
++	spin_lock_irqsave(&sesslock, flags);
++	list_add(&session->sess_list, &sesslist);
++	spin_unlock_irqrestore(&sesslock, flags);
++
+ 	ev->r.c_session_ret.host_no = hostno;
+ 	ev->r.c_session_ret.sid = session->sid;
+ 	return 0;
+@@ -1041,34 +835,47 @@ iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+ {
+ 	struct iscsi_cls_conn *conn;
+ 	struct iscsi_cls_session *session;
++	unsigned long flags;
+ 
+ 	session = iscsi_session_lookup(ev->u.c_conn.sid);
+ 	if (!session) {
+-		printk(KERN_ERR "iscsi: invalid session %d.\n",
++		printk(KERN_ERR "iscsi: invalid session %d\n",
+ 		       ev->u.c_conn.sid);
+ 		return -EINVAL;
+ 	}
+ 
+ 	conn = transport->create_conn(session, ev->u.c_conn.cid);
+ 	if (!conn) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "couldn't create a new connection.");
++		printk(KERN_ERR "iscsi: couldn't create a new "
++			   "connection for session %d\n",
++			   session->sid);
+ 		return -ENOMEM;
+ 	}
+ 
+ 	ev->r.c_conn_ret.sid = session->sid;
+ 	ev->r.c_conn_ret.cid = conn->cid;
++
++	spin_lock_irqsave(&connlock, flags);
++	list_add(&conn->conn_list, &connlist);
++	conn->active = 1;
++	spin_unlock_irqrestore(&connlock, flags);
++
+ 	return 0;
+ }
+ 
+ static int
+ iscsi_if_destroy_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+ {
++	unsigned long flags;
+ 	struct iscsi_cls_conn *conn;
+ 
+ 	conn = iscsi_conn_lookup(ev->u.d_conn.sid, ev->u.d_conn.cid);
+ 	if (!conn)
+ 		return -EINVAL;
++	spin_lock_irqsave(&connlock, flags);
++	conn->active = 0;
++	list_del(&conn->conn_list);
++	spin_unlock_irqrestore(&connlock, flags);
+ 
+ 	if (transport->destroy_conn)
+ 		transport->destroy_conn(conn);
+@@ -1140,50 +947,15 @@ static int
+ iscsi_tgt_dscvr(struct iscsi_transport *transport,
+ 		struct iscsi_uevent *ev)
+ {
+-	struct Scsi_Host *shost;
+ 	struct sockaddr *dst_addr;
+-	int err;
+ 
+ 	if (!transport->tgt_dscvr)
+ 		return -EINVAL;
+ 
+-	shost = scsi_host_lookup(ev->u.tgt_dscvr.host_no);
+-	if (IS_ERR(shost)) {
+-		printk(KERN_ERR "target discovery could not find host no %u\n",
+-		       ev->u.tgt_dscvr.host_no);
+-		return -ENODEV;
+-	}
+-
+-
+ 	dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
+-	err = transport->tgt_dscvr(shost, ev->u.tgt_dscvr.type,
+-				   ev->u.tgt_dscvr.enable, dst_addr);
+-	scsi_host_put(shost);
+-	return err;
+-}
+-
+-static int
+-iscsi_set_host_param(struct iscsi_transport *transport,
+-		     struct iscsi_uevent *ev)
+-{
+-	char *data = (char*)ev + sizeof(*ev);
+-	struct Scsi_Host *shost;
+-	int err;
+-
+-	if (!transport->set_host_param)
+-		return -ENOSYS;
+-
+-	shost = scsi_host_lookup(ev->u.set_host_param.host_no);
+-	if (IS_ERR(shost)) {
+-		printk(KERN_ERR "set_host_param could not find host no %u\n",
+-		       ev->u.set_host_param.host_no);
+-		return -ENODEV;
+-	}
+-
+-	err = transport->set_host_param(shost, ev->u.set_host_param.param,
+-					data, ev->u.set_host_param.len);
+-	scsi_host_put(shost);
+-	return err;
++	return transport->tgt_dscvr(ev->u.tgt_dscvr.type,
++				    ev->u.tgt_dscvr.host_no,
++				    ev->u.tgt_dscvr.enable, dst_addr);
+ }
+ 
+ static int
+@@ -1195,6 +967,7 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 	struct iscsi_internal *priv;
+ 	struct iscsi_cls_session *session;
+ 	struct iscsi_cls_conn *conn;
++	unsigned long flags;
+ 
+ 	priv = iscsi_if_transport_lookup(iscsi_ptr(ev->transport_handle));
+ 	if (!priv)
+@@ -1212,16 +985,13 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 		break;
+ 	case ISCSI_UEVENT_DESTROY_SESSION:
+ 		session = iscsi_session_lookup(ev->u.d_session.sid);
+-		if (session)
++		if (session) {
++			spin_lock_irqsave(&sesslock, flags);
++			list_del(&session->sess_list);
++			spin_unlock_irqrestore(&sesslock, flags);
++
+ 			transport->destroy_session(session);
+-		else
+-			err = -EINVAL;
+-		break;
+-	case ISCSI_UEVENT_UNBIND_SESSION:
+-		session = iscsi_session_lookup(ev->u.d_session.sid);
+-		if (session)
+-			iscsi_unbind_session(session);
+-		else
++		} else
+ 			err = -EINVAL;
+ 		break;
+ 	case ISCSI_UEVENT_CREATE_CONN:
+@@ -1279,11 +1049,8 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 	case ISCSI_UEVENT_TGT_DSCVR:
+ 		err = iscsi_tgt_dscvr(transport, ev);
+ 		break;
+-	case ISCSI_UEVENT_SET_HOST_PARAM:
+-		err = iscsi_set_host_param(transport, ev);
+-		break;
+ 	default:
+-		err = -ENOSYS;
++		err = -EINVAL;
+ 		break;
+ 	}
+ 
+@@ -1292,55 +1059,70 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ }
+ 
+ /*
+- * Get message from skb.  Each message is processed by iscsi_if_recv_msg.
+- * Malformed skbs with wrong lengths or invalid creds are not processed.
++ * Get message from skb (based on rtnetlink_rcv_skb).  Each message is
++ * processed by iscsi_if_recv_msg.  Malformed skbs with wrong lengths or
++ * invalid creds are discarded silently.
+  */
+ static void
+-iscsi_if_rx(struct sk_buff *skb)
++iscsi_if_rx(struct sock *sk, int len)
+ {
++	struct sk_buff *skb;
++
+ 	mutex_lock(&rx_queue_mutex);
+-	while (skb->len >= NLMSG_SPACE(0)) {
+-		int err;
+-		uint32_t rlen;
+-		struct nlmsghdr	*nlh;
+-		struct iscsi_uevent *ev;
+-
+-		nlh = nlmsg_hdr(skb);
+-		if (nlh->nlmsg_len < sizeof(*nlh) ||
+-		    skb->len < nlh->nlmsg_len) {
+-			break;
++	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
++		if (NETLINK_CREDS(skb)->uid) {
++			skb_pull(skb, skb->len);
++			goto free_skb;
+ 		}
+ 
+-		ev = NLMSG_DATA(nlh);
+-		rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+-		if (rlen > skb->len)
+-			rlen = skb->len;
++		while (skb->len >= NLMSG_SPACE(0)) {
++			int err;
++			uint32_t rlen;
++			struct nlmsghdr	*nlh;
++			struct iscsi_uevent *ev;
+ 
+-		err = iscsi_if_recv_msg(skb, nlh);
+-		if (err) {
+-			ev->type = ISCSI_KEVENT_IF_ERROR;
+-			ev->iferror = err;
+-		}
+-		do {
+-			/*
+-			 * special case for GET_STATS:
+-			 * on success - sending reply and stats from
+-			 * inside of if_recv_msg(),
+-			 * on error - fall through.
+-			 */
+-			if (ev->type == ISCSI_UEVENT_GET_STATS && !err)
++			nlh = nlmsg_hdr(skb);
++			if (nlh->nlmsg_len < sizeof(*nlh) ||
++			    skb->len < nlh->nlmsg_len) {
+ 				break;
+-			err = iscsi_if_send_reply(
+-				NETLINK_CREDS(skb)->pid, nlh->nlmsg_seq,
+-				nlh->nlmsg_type, 0, 0, ev, sizeof(*ev));
+-		} while (err < 0 && err != -ECONNREFUSED);
+-		skb_pull(skb, rlen);
++			}
++
++			ev = NLMSG_DATA(nlh);
++			rlen = NLMSG_ALIGN(nlh->nlmsg_len);
++			if (rlen > skb->len)
++				rlen = skb->len;
++
++			err = iscsi_if_recv_msg(skb, nlh);
++			if (err) {
++				ev->type = ISCSI_KEVENT_IF_ERROR;
++				ev->iferror = err;
++			}
++			do {
++				/*
++				 * special case for GET_STATS:
++				 * on success - sending reply and stats from
++				 * inside of if_recv_msg(),
++				 * on error - fall through.
++				 */
++				if (ev->type == ISCSI_UEVENT_GET_STATS && !err)
++					break;
++				err = iscsi_if_send_reply(
++					NETLINK_CREDS(skb)->pid, nlh->nlmsg_seq,
++					nlh->nlmsg_type, 0, 0, ev, sizeof(*ev));
++			} while (err < 0 && err != -ECONNREFUSED);
++			skb_pull(skb, rlen);
++		}
++free_skb:
++		kfree_skb(skb);
+ 	}
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1130,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1372,66 +1153,43 @@ iscsi_conn_attr(port, ISCSI_PARAM_CONN_PORT);
+ iscsi_conn_attr(exp_statsn, ISCSI_PARAM_EXP_STATSN);
+ iscsi_conn_attr(persistent_address, ISCSI_PARAM_PERSISTENT_ADDRESS);
+ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+-iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+-iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
++
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
+ 
+ /*
+  * iSCSI session attrs
+  */
+-#define iscsi_session_attr_show(param, perm)				\
++#define iscsi_session_attr_show(param)					\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+-									\
+-	if (perm && !capable(CAP_SYS_ADMIN))				\
+-		return -EACCES;						\
+ 	return t->get_session_param(session, param, buf);		\
+ }
+ 
+-#define iscsi_session_attr(field, param, perm)				\
+-	iscsi_session_attr_show(param, perm)				\
++#define iscsi_session_attr(field, param)				\
++	iscsi_session_attr_show(param)					\
+ static ISCSI_CLASS_ATTR(sess, field, S_IRUGO, show_session_param_##param, \
+ 			NULL);
+ 
+-iscsi_session_attr(targetname, ISCSI_PARAM_TARGET_NAME, 0);
+-iscsi_session_attr(initial_r2t, ISCSI_PARAM_INITIAL_R2T_EN, 0);
+-iscsi_session_attr(max_outstanding_r2t, ISCSI_PARAM_MAX_R2T, 0);
+-iscsi_session_attr(immediate_data, ISCSI_PARAM_IMM_DATA_EN, 0);
+-iscsi_session_attr(first_burst_len, ISCSI_PARAM_FIRST_BURST, 0);
+-iscsi_session_attr(max_burst_len, ISCSI_PARAM_MAX_BURST, 0);
+-iscsi_session_attr(data_pdu_in_order, ISCSI_PARAM_PDU_INORDER_EN, 0);
+-iscsi_session_attr(data_seq_in_order, ISCSI_PARAM_DATASEQ_INORDER_EN, 0);
+-iscsi_session_attr(erl, ISCSI_PARAM_ERL, 0);
+-iscsi_session_attr(tpgt, ISCSI_PARAM_TPGT, 0);
+-iscsi_session_attr(username, ISCSI_PARAM_USERNAME, 1);
+-iscsi_session_attr(username_in, ISCSI_PARAM_USERNAME_IN, 1);
+-iscsi_session_attr(password, ISCSI_PARAM_PASSWORD, 1);
+-iscsi_session_attr(password_in, ISCSI_PARAM_PASSWORD_IN, 1);
+-iscsi_session_attr(fast_abort, ISCSI_PARAM_FAST_ABORT, 0);
+-iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+-iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+-
+-static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
+-{
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
+-	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+-}
+-static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+-			NULL);
++iscsi_session_attr(targetname, ISCSI_PARAM_TARGET_NAME);
++iscsi_session_attr(initial_r2t, ISCSI_PARAM_INITIAL_R2T_EN);
++iscsi_session_attr(max_outstanding_r2t, ISCSI_PARAM_MAX_R2T);
++iscsi_session_attr(immediate_data, ISCSI_PARAM_IMM_DATA_EN);
++iscsi_session_attr(first_burst_len, ISCSI_PARAM_FIRST_BURST);
++iscsi_session_attr(max_burst_len, ISCSI_PARAM_MAX_BURST);
++iscsi_session_attr(data_pdu_in_order, ISCSI_PARAM_PDU_INORDER_EN);
++iscsi_session_attr(data_seq_in_order, ISCSI_PARAM_DATASEQ_INORDER_EN);
++iscsi_session_attr(erl, ISCSI_PARAM_ERL);
++iscsi_session_attr(tpgt, ISCSI_PARAM_TPGT);
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1441,32 +1199,9 @@ static ISCSI_CLASS_ATTR(priv_sess, field, S_IRUGO, show_priv_session_##field, \
+ 			NULL)
+ iscsi_priv_session_attr(recovery_tmo, "%d");
+ 
+-/*
+- * iSCSI host attrs
+- */
+-#define iscsi_host_attr_show(param)					\
+-static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
+-{									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
+-	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+-	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+-}
+-
+-#define iscsi_host_attr(field, param)					\
+-	iscsi_host_attr_show(param)					\
+-static ISCSI_CLASS_ATTR(host, field, S_IRUGO, show_host_param_##param,	\
+-			NULL);
+-
+-iscsi_host_attr(netdev, ISCSI_HOST_PARAM_NETDEV_NAME);
+-iscsi_host_attr(hwaddress, ISCSI_HOST_PARAM_HWADDRESS);
+-iscsi_host_attr(ipaddress, ISCSI_HOST_PARAM_IPADDRESS);
+-iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+-
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1209,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,15 +1217,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
+-		count++;						\
+-	}								\
+-} while (0)
+-
+-#define SETUP_HOST_RD_ATTR(field, param_flag)				\
+-do {									\
+-	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,31 +1308,24 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+ 	priv->t.host_attrs.ac.class = &iscsi_host_class.class;
+ 	priv->t.host_attrs.ac.match = iscsi_host_match;
+ 	priv->t.host_size = sizeof(struct iscsi_host);
++	priv->host_attrs[0] = NULL;
+ 	transport_container_register(&priv->t.host_attrs);
+ 
+-	SETUP_HOST_RD_ATTR(netdev, ISCSI_HOST_NETDEV_NAME);
+-	SETUP_HOST_RD_ATTR(ipaddress, ISCSI_HOST_IPADDRESS);
+-	SETUP_HOST_RD_ATTR(hwaddress, ISCSI_HOST_HWADDRESS);
+-	SETUP_HOST_RD_ATTR(initiatorname, ISCSI_HOST_INITIATOR_NAME);
+-	BUG_ON(count > ISCSI_HOST_ATTRS);
+-	priv->host_attrs[count] = NULL;
+-	count = 0;
+-
+ 	/* connection parameters */
+ 	priv->conn_cont.ac.attrs = &priv->conn_attrs[0];
+ 	priv->conn_cont.ac.class = &iscsi_connection_class.class;
+@@ -1623,8 +1343,6 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	SETUP_CONN_RD_ATTR(exp_statsn, ISCSI_EXP_STATSN);
+ 	SETUP_CONN_RD_ATTR(persistent_address, ISCSI_PERSISTENT_ADDRESS);
+ 	SETUP_CONN_RD_ATTR(persistent_port, ISCSI_PERSISTENT_PORT);
+-	SETUP_CONN_RD_ATTR(ping_tmo, ISCSI_PING_TMO);
+-	SETUP_CONN_RD_ATTR(recv_tmo, ISCSI_RECV_TMO);
+ 
+ 	BUG_ON(count > ISCSI_CONN_ATTRS);
+ 	priv->conn_attrs[count] = NULL;
+@@ -1646,15 +1364,7 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	SETUP_SESSION_RD_ATTR(erl, ISCSI_ERL);
+ 	SETUP_SESSION_RD_ATTR(targetname, ISCSI_TARGET_NAME);
+ 	SETUP_SESSION_RD_ATTR(tpgt, ISCSI_TPGT);
+-	SETUP_SESSION_RD_ATTR(password, ISCSI_USERNAME);
+-	SETUP_SESSION_RD_ATTR(password_in, ISCSI_USERNAME_IN);
+-	SETUP_SESSION_RD_ATTR(username, ISCSI_PASSWORD);
+-	SETUP_SESSION_RD_ATTR(username_in, ISCSI_PASSWORD_IN);
+-	SETUP_SESSION_RD_ATTR(fast_abort, ISCSI_FAST_ABORT);
+-	SETUP_SESSION_RD_ATTR(abort_tmo, ISCSI_ABORT_TMO);
+-	SETUP_SESSION_RD_ATTR(lu_reset_tmo,ISCSI_LU_RESET_TMO);
+ 	SETUP_PRIV_SESSION_RD_ATTR(recovery_tmo);
+-	SETUP_PRIV_SESSION_RD_ATTR(state);
+ 
+ 	BUG_ON(count > ISCSI_SESSION_ATTRS);
+ 	priv->session_attrs[count] = NULL;
+@@ -1666,8 +1376,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1404,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+@@ -1734,14 +1444,8 @@ static __init int iscsi_transport_init(void)
+ 		goto unregister_session_class;
+ 	}
+ 
+-	iscsi_eh_timer_workq = create_singlethread_workqueue("iscsi_eh");
+-	if (!iscsi_eh_timer_workq)
+-		goto release_nls;
+-
+ 	return 0;
+ 
+-release_nls:
+-	netlink_kernel_release(nls);
+ unregister_session_class:
+ 	transport_class_unregister(&iscsi_session_class);
+ unregister_conn_class:
+@@ -1755,8 +1459,7 @@ unregister_transport_class:
+ 
+ static void __exit iscsi_transport_exit(void)
+ {
+-	destroy_workqueue(iscsi_eh_timer_workq);
+-	netlink_kernel_release(nls);
++	sock_release(nls->sk_socket);
+ 	transport_class_unregister(&iscsi_connection_class);
+ 	transport_class_unregister(&iscsi_session_class);
+ 	transport_class_unregister(&iscsi_host_class);
+diff --git a/include/scsi/iscsi_if.h b/include/scsi/iscsi_if.h
+index e19e584..55ebf03 100644
+--- a/include/scsi/iscsi_if.h
++++ b/include/scsi/iscsi_if.h
+@@ -48,16 +48,12 @@ enum iscsi_uevent_e {
+ 	ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT	= UEVENT_BASE + 14,
+ 
+ 	ISCSI_UEVENT_TGT_DSCVR		= UEVENT_BASE + 15,
+-	ISCSI_UEVENT_SET_HOST_PARAM	= UEVENT_BASE + 16,
+-	ISCSI_UEVENT_UNBIND_SESSION	= UEVENT_BASE + 17,
+ 
+ 	/* up events */
+ 	ISCSI_KEVENT_RECV_PDU		= KEVENT_BASE + 1,
+ 	ISCSI_KEVENT_CONN_ERROR		= KEVENT_BASE + 2,
+ 	ISCSI_KEVENT_IF_ERROR		= KEVENT_BASE + 3,
+ 	ISCSI_KEVENT_DESTROY_SESSION	= KEVENT_BASE + 4,
+-	ISCSI_KEVENT_UNBIND_SESSION	= KEVENT_BASE + 5,
+-	ISCSI_KEVENT_CREATE_SESSION	= KEVENT_BASE + 6,
+ };
+ 
+ enum iscsi_tgt_dscvr {
+@@ -75,8 +71,6 @@ struct iscsi_uevent {
+ 		/* messages u -> k */
+ 		struct msg_create_session {
+ 			uint32_t	initial_cmdsn;
+-			uint16_t	cmds_max;
+-			uint16_t	queue_depth;
+ 		} c_session;
+ 		struct msg_destroy_session {
+ 			uint32_t	sid;
+@@ -142,11 +136,6 @@ struct iscsi_uevent {
+ 			 */
+ 			uint32_t	enable;
+ 		} tgt_dscvr;
+-		struct msg_set_host_param {
+-			uint32_t	host_no;
+-			uint32_t	param; /* enum iscsi_host_param */
+-			uint32_t	len;
+-		} set_host_param;
+ 	} u;
+ 	union {
+ 		/* messages k -> u */
+@@ -159,10 +148,6 @@ struct iscsi_uevent {
+ 			uint32_t	sid;
+ 			uint32_t	cid;
+ 		} c_conn_ret;
+-		struct msg_unbind_session {
+-			uint32_t	sid;
+-			uint32_t	host_no;
+-		} unbind_session;
+ 		struct msg_recv_req {
+ 			uint32_t	sid;
+ 			uint32_t	cid;
+@@ -238,18 +223,6 @@ enum iscsi_param {
+ 	ISCSI_PARAM_CONN_PORT,
+ 	ISCSI_PARAM_CONN_ADDRESS,
+ 
+-	ISCSI_PARAM_USERNAME,
+-	ISCSI_PARAM_USERNAME_IN,
+-	ISCSI_PARAM_PASSWORD,
+-	ISCSI_PARAM_PASSWORD_IN,
+-
+-	ISCSI_PARAM_FAST_ABORT,
+-	ISCSI_PARAM_ABORT_TMO,
+-	ISCSI_PARAM_LU_RESET_TMO,
+-	ISCSI_PARAM_HOST_RESET_TMO,
+-
+-	ISCSI_PARAM_PING_TMO,
+-	ISCSI_PARAM_RECV_TMO,
+ 	/* must always be last */
+ 	ISCSI_PARAM_MAX,
+ };
+@@ -276,30 +249,6 @@ enum iscsi_param {
+ #define ISCSI_SESS_RECOVERY_TMO		(1 << ISCSI_PARAM_SESS_RECOVERY_TMO)
+ #define ISCSI_CONN_PORT			(1 << ISCSI_PARAM_CONN_PORT)
+ #define ISCSI_CONN_ADDRESS		(1 << ISCSI_PARAM_CONN_ADDRESS)
+-#define ISCSI_USERNAME			(1 << ISCSI_PARAM_USERNAME)
+-#define ISCSI_USERNAME_IN		(1 << ISCSI_PARAM_USERNAME_IN)
+-#define ISCSI_PASSWORD			(1 << ISCSI_PARAM_PASSWORD)
+-#define ISCSI_PASSWORD_IN		(1 << ISCSI_PARAM_PASSWORD_IN)
+-#define ISCSI_FAST_ABORT		(1 << ISCSI_PARAM_FAST_ABORT)
+-#define ISCSI_ABORT_TMO			(1 << ISCSI_PARAM_ABORT_TMO)
+-#define ISCSI_LU_RESET_TMO		(1 << ISCSI_PARAM_LU_RESET_TMO)
+-#define ISCSI_HOST_RESET_TMO		(1 << ISCSI_PARAM_HOST_RESET_TMO)
+-#define ISCSI_PING_TMO			(1 << ISCSI_PARAM_PING_TMO)
+-#define ISCSI_RECV_TMO			(1 << ISCSI_PARAM_RECV_TMO)
+-
+-/* iSCSI HBA params */
+-enum iscsi_host_param {
+-	ISCSI_HOST_PARAM_HWADDRESS,
+-	ISCSI_HOST_PARAM_INITIATOR_NAME,
+-	ISCSI_HOST_PARAM_NETDEV_NAME,
+-	ISCSI_HOST_PARAM_IPADDRESS,
+-	ISCSI_HOST_PARAM_MAX,
+-};
+-
+-#define ISCSI_HOST_HWADDRESS		(1 << ISCSI_HOST_PARAM_HWADDRESS)
+-#define ISCSI_HOST_INITIATOR_NAME	(1 << ISCSI_HOST_PARAM_INITIATOR_NAME)
+-#define ISCSI_HOST_NETDEV_NAME		(1 << ISCSI_HOST_PARAM_NETDEV_NAME)
+-#define ISCSI_HOST_IPADDRESS		(1 << ISCSI_HOST_PARAM_IPADDRESS)
+ 
+ #define iscsi_ptr(_handle) ((void*)(unsigned long)_handle)
+ #define iscsi_handle(_ptr) ((uint64_t)(unsigned long)_ptr)
+@@ -323,9 +272,6 @@ enum iscsi_host_param {
+ #define CAP_MULTI_CONN		0x40
+ #define CAP_TEXT_NEGO		0x80
+ #define CAP_MARKERS		0x100
+-#define CAP_FW_DB		0x200
+-#define CAP_SENDTARGETS_OFFLOAD	0x400
+-#define CAP_DATA_PATH_OFFLOAD	0x800
+ 
+ /*
+  * These flags describes reason of stop_conn() call
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..ea0816d 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -48,8 +48,9 @@ struct iscsi_nopin;
+ #define debug_scsi(fmt...)
+ #endif
+ 
+-#define ISCSI_DEF_XMIT_CMDS_MAX	128	/* must be power of 2 */
+-#define ISCSI_MGMT_CMDS_MAX	16	/* must be power of 2 */
++#define ISCSI_XMIT_CMDS_MAX	128	/* must be power of 2 */
++#define ISCSI_MGMT_CMDS_MAX	32	/* must be power of 2 */
++#define ISCSI_CONN_MAX			1
+ 
+ #define ISCSI_MGMT_ITT_OFFSET	0xa00
+ 
+@@ -57,31 +58,21 @@ struct iscsi_nopin;
+ #define ISCSI_MAX_CMD_PER_LUN		128
+ 
+ /* Task Mgmt states */
+-enum {
+-	TMF_INITIAL,
+-	TMF_QUEUED,
+-	TMF_SUCCESS,
+-	TMF_FAILED,
+-	TMF_TIMEDOUT,
+-	TMF_NOT_FOUND,
+-};
++#define TMABORT_INITIAL			0x0
++#define TMABORT_SUCCESS			0x1
++#define TMABORT_FAILED			0x2
++#define TMABORT_TIMEDOUT		0x3
++#define TMABORT_NOT_FOUND		0x4
+ 
+ /* Connection suspend "bit" */
+ #define ISCSI_SUSPEND_BIT		1
+ 
+ #define ISCSI_ITT_MASK			(0xfff)
++#define ISCSI_CID_SHIFT			12
++#define ISCSI_CID_MASK			(0xffff << ISCSI_CID_SHIFT)
+ #define ISCSI_AGE_SHIFT			28
+ #define ISCSI_AGE_MASK			(0xf << ISCSI_AGE_SHIFT)
+ 
+-#define ISCSI_ADDRESS_BUF_LEN		64
+-
+-enum {
+-	/* this is the maximum possible storage for AHSs */
+-	ISCSI_MAX_AHS_SIZE = sizeof(struct iscsi_ecdb_ahdr) +
+-				sizeof(struct iscsi_rlength_ahdr),
+-	ISCSI_DIGEST_SIZE = sizeof(__u32),
+-};
+-
+ struct iscsi_mgmt_task {
+ 	/*
+ 	 * Becuae LLDs allocate their hdr differently, this is a pointer to
+@@ -89,7 +80,7 @@ struct iscsi_mgmt_task {
+ 	 */
+ 	struct iscsi_hdr	*hdr;
+ 	char			*data;		/* mgmt payload */
+-	unsigned		data_count;	/* counts data to be sent */
++	int			data_count;	/* counts data to be sent */
+ 	uint32_t		itt;		/* this ITT */
+ 	void			*dd_data;	/* driver/transport data */
+ 	struct list_head	running;
+@@ -103,23 +94,23 @@ enum {
+ 
+ struct iscsi_cmd_task {
+ 	/*
+-	 * Because LLDs allocate their hdr differently, this is a pointer
+-	 * and length to that storage. It must be setup at session
+-	 * creation time.
++	 * Becuae LLDs allocate their hdr differently, this is a pointer to
++	 * that storage. It must be setup at session creation time.
+ 	 */
+ 	struct iscsi_cmd	*hdr;
+-	unsigned short		hdr_max;
+-	unsigned short		hdr_len;	/* accumulated size of hdr used */
+ 	int			itt;		/* this ITT */
++	int			datasn;		/* DataSN */
+ 
+ 	uint32_t		unsol_datasn;
+-	unsigned		imm_count;	/* imm-data (bytes)   */
+-	unsigned		unsol_count;	/* unsolicited (bytes)*/
++	int			imm_count;	/* imm-data (bytes)   */
++	int			unsol_count;	/* unsolicited (bytes)*/
+ 	/* offset in unsolicited stream (bytes); */
+-	unsigned		unsol_offset;
+-	unsigned		data_count;	/* remaining Data-Out */
++	int			unsol_offset;
++	int			data_count;	/* remaining Data-Out */
+ 	struct scsi_cmnd	*sc;		/* associated SCSI cmd*/
++	int			total_length;
+ 	struct iscsi_conn	*conn;		/* used connection    */
++	struct iscsi_mgmt_task	*mtask;		/* tmf mtask in progr */
+ 
+ 	/* state set/tested under session->lock */
+ 	int			state;
+@@ -128,19 +119,6 @@ struct iscsi_cmd_task {
+ 	void			*dd_data;	/* driver/transport data */
+ };
+ 
+-static inline void* iscsi_next_hdr(struct iscsi_cmd_task *ctask)
+-{
+-	return (void*)ctask->hdr + ctask->hdr_len;
+-}
+-
+-/* Connection's states */
+-enum {
+-	ISCSI_CONN_INITIAL_STAGE,
+-	ISCSI_CONN_STARTED,
+-	ISCSI_CONN_STOPPED,
+-	ISCSI_CONN_CLEANUP_WAIT,
+-};
+-
+ struct iscsi_conn {
+ 	struct iscsi_cls_conn	*cls_conn;	/* ptr to class connection */
+ 	void			*dd_data;	/* iscsi_transport data */
+@@ -154,12 +132,6 @@ struct iscsi_conn {
+ 	 * conn_stop() flag: stop to recover, stop to terminate
+ 	 */
+         int			stop_stage;
+-	struct timer_list	transport_timer;
+-	unsigned long		last_recv;
+-	unsigned long		last_ping;
+-	int			ping_timeout;
+-	int			recv_timeout;
+-	struct iscsi_mgmt_task	*ping_mtask;
+ 
+ 	/* iSCSI connection-wide sequencing */
+ 	uint32_t		exp_statsn;
+@@ -180,24 +152,30 @@ struct iscsi_conn {
+ 	struct iscsi_cmd_task	*ctask;		/* xmit ctask in progress */
+ 
+ 	/* xmit */
+-	struct list_head	mgmtqueue;	/* mgmt (control) xmit queue */
++	struct kfifo		*immqueue;	/* immediate xmit queue */
++	struct kfifo		*mgmtqueue;	/* mgmt (control) xmit queue */
+ 	struct list_head	mgmt_run_list;	/* list of control tasks */
+ 	struct list_head	xmitqueue;	/* data-path cmd queue */
+ 	struct list_head	run_list;	/* list of cmds in progress */
+-	struct list_head	requeue;	/* tasks needing another run */
+ 	struct work_struct	xmitwork;	/* per-conn. xmit workqueue */
++	/*
++	 * serializes connection xmit, access to kfifos:
++	 * xmitqueue, immqueue, mgmtqueue
++	 */
++	struct mutex		xmitmutex;
++
+ 	unsigned long		suspend_tx;	/* suspend Tx */
+ 	unsigned long		suspend_rx;	/* suspend Rx */
+ 
+ 	/* abort */
+ 	wait_queue_head_t	ehwait;		/* used in eh_abort() */
+ 	struct iscsi_tm		tmhdr;
+-	struct timer_list	tmf_timer;
+-	int			tmf_state;	/* see TMF_INITIAL, etc.*/
++	struct timer_list	tmabort_timer;
++	int			tmabort_state;	/* see TMABORT_INITIAL, etc.*/
+ 
+ 	/* negotiated params */
+-	unsigned		max_recv_dlength; /* initiator_max_recv_dsl*/
+-	unsigned		max_xmit_dlength; /* target_max_recv_dsl */
++	int			max_recv_dlength; /* initiator_max_recv_dsl*/
++	int			max_xmit_dlength; /* target_max_recv_dsl */
+ 	int			hdrdgst_en;
+ 	int			datadgst_en;
+ 	int			ifmarker_en;
+@@ -205,12 +183,6 @@ struct iscsi_conn {
+ 	/* values userspace uses to id a conn */
+ 	int			persistent_port;
+ 	char			*persistent_address;
+-	/* remote portal currently connected to */
+-	int			portal_port;
+-	char			portal_address[ISCSI_ADDRESS_BUF_LEN];
+-	/* local address */
+-	int			local_port;
+-	char			local_address[ISCSI_ADDRESS_BUF_LEN];
+ 
+ 	/* MIB-statistics */
+ 	uint64_t		txdata_octets;
+@@ -225,66 +197,34 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+-struct iscsi_pool {
++struct iscsi_queue {
+ 	struct kfifo		*queue;		/* FIFO Queue */
+ 	void			**pool;		/* Pool of elements */
+ 	int			max;		/* Max number of elements */
+ };
+ 
+-/* Session's states */
+-enum {
+-	ISCSI_STATE_FREE = 1,
+-	ISCSI_STATE_LOGGED_IN,
+-	ISCSI_STATE_FAILED,
+-	ISCSI_STATE_TERMINATE,
+-	ISCSI_STATE_IN_RECOVERY,
+-	ISCSI_STATE_RECOVERY_FAILED,
+-	ISCSI_STATE_LOGGING_OUT,
+-};
+-
+ struct iscsi_session {
+-	/*
+-	 * Syncs up the scsi eh thread with the iscsi eh thread when sending
+-	 * task management functions. This must be taken before the session
+-	 * and recv lock.
+-	 */
+-	struct mutex		eh_mutex;
+-
+ 	/* iSCSI session-wide sequencing */
+ 	uint32_t		cmdsn;
+ 	uint32_t		exp_cmdsn;
+ 	uint32_t		max_cmdsn;
+ 
+-	/* This tracks the reqs queued into the initiator */
+-	uint32_t		queued_cmdsn;
+-
+ 	/* configuration */
+-	int			abort_timeout;
+-	int			lu_reset_timeout;
+ 	int			initial_r2t_en;
+-	unsigned		max_r2t;
++	int			max_r2t;
+ 	int			imm_data_en;
+-	unsigned		first_burst;
+-	unsigned		max_burst;
++	int			first_burst;
++	int			max_burst;
+ 	int			time2wait;
+ 	int			time2retain;
+ 	int			pdu_inorder_en;
+ 	int			dataseq_inorder_en;
+ 	int			erl;
+-	int			fast_abort;
+ 	int			tpgt;
+-	char			*username;
+-	char			*username_in;
+-	char			*password;
+-	char			*password_in;
+ 	char			*targetname;
+-	char			*initiatorname;
+-	/* hw address or netdev iscsi connection is bound to */
+-	char			*hwaddress;
+-	char			*netdev;
++
+ 	/* control data */
+ 	struct iscsi_transport	*tt;
+ 	struct Scsi_Host	*host;
+@@ -300,10 +240,10 @@ struct iscsi_session {
+ 
+ 	int			cmds_max;	/* size of cmds array */
+ 	struct iscsi_cmd_task	**cmds;		/* Original Cmds arr */
+-	struct iscsi_pool	cmdpool;	/* PDU's pool */
++	struct iscsi_queue	cmdpool;	/* PDU's pool */
+ 	int			mgmtpool_max;	/* size of mgmt array */
+ 	struct iscsi_mgmt_task	**mgmt_cmds;	/* Original mgmt arr */
+-	struct iscsi_pool	mgmtpool;	/* Mgmt PDU's pool */
++	struct iscsi_queue	mgmtpool;	/* Mgmt PDU's pool */
+ };
+ 
+ /*
+@@ -312,26 +252,15 @@ struct iscsi_session {
+ extern int iscsi_change_queue_depth(struct scsi_device *sdev, int depth);
+ extern int iscsi_eh_abort(struct scsi_cmnd *sc);
+ extern int iscsi_eh_host_reset(struct scsi_cmnd *sc);
+-extern int iscsi_eh_device_reset(struct scsi_cmnd *sc);
+ extern int iscsi_queuecommand(struct scsi_cmnd *sc,
+ 			      void (*done)(struct scsi_cmnd *));
+ 
+-
+-/*
+- * iSCSI host helpers.
+- */
+-extern int iscsi_host_set_param(struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf,
+-				int buflen);
+-extern int iscsi_host_get_param(struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf);
+-
+ /*
+  * session management
+  */
+ extern struct iscsi_cls_session *
+ iscsi_session_setup(struct iscsi_transport *, struct scsi_transport_template *,
+-		    uint16_t, uint16_t, int, int, uint32_t, uint32_t *);
++		    int, int, uint32_t, uint32_t *);
+ extern void iscsi_session_teardown(struct iscsi_cls_session *);
+ extern struct iscsi_session *class_to_transport_session(struct iscsi_cls_session *);
+ extern void iscsi_session_recovery_timedout(struct iscsi_cls_session *);
+@@ -343,10 +272,6 @@ extern int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ #define session_to_cls(_sess) \
+ 	hostdata_session(_sess->host->hostdata)
+ 
+-#define iscsi_session_printk(prefix, _sess, fmt, a...)	\
+-	iscsi_cls_session_printk(prefix,		\
+-		(struct iscsi_cls_session *)session_to_cls(_sess), fmt, ##a)
+-
+ /*
+  * connection management
+  */
+@@ -361,47 +286,26 @@ extern void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err);
+ extern int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 				enum iscsi_param param, char *buf);
+ 
+-#define iscsi_conn_printk(prefix, _c, fmt, a...) \
+-	iscsi_cls_conn_printk(prefix, _c->cls_conn, fmt, ##a)
+-
+ /*
+  * pdu and task processing
+  */
+-extern void iscsi_update_cmdsn(struct iscsi_session *, struct iscsi_nopin *);
++extern int iscsi_check_assign_cmdsn(struct iscsi_session *,
++				    struct iscsi_nopin *);
+ extern void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *,
+ 					struct iscsi_data *hdr);
+ extern int iscsi_conn_send_pdu(struct iscsi_cls_conn *, struct iscsi_hdr *,
+ 				char *, uint32_t);
+ extern int iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *,
+ 			      char *, int);
++extern int __iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *,
++				char *, int);
+ extern int iscsi_verify_itt(struct iscsi_conn *, struct iscsi_hdr *,
+ 			    uint32_t *);
+-extern void iscsi_requeue_ctask(struct iscsi_cmd_task *ctask);
+-extern void iscsi_free_mgmt_task(struct iscsi_conn *conn,
+-				 struct iscsi_mgmt_task *mtask);
+ 
+ /*
+  * generic helpers
+  */
+-extern void iscsi_pool_free(struct iscsi_pool *);
+-extern int iscsi_pool_init(struct iscsi_pool *, int, void ***, int);
+-
+-/*
+- * inline functions to deal with padding.
+- */
+-static inline unsigned int
+-iscsi_padded(unsigned int len)
+-{
+-	return (len + ISCSI_PAD_LEN - 1) & ~(ISCSI_PAD_LEN - 1);
+-}
+-
+-static inline unsigned int
+-iscsi_padding(unsigned int len)
+-{
+-	len &= (ISCSI_PAD_LEN - 1);
+-	if (len)
+-		len = ISCSI_PAD_LEN - len;
+-	return len;
+-}
++extern void iscsi_pool_free(struct iscsi_queue *, void **);
++extern int iscsi_pool_init(struct iscsi_queue *, int, void ***, int);
+ 
+ #endif
+diff --git a/include/scsi/scsi_transport_iscsi.h b/include/scsi/scsi_transport_iscsi.h
+index aab1eae..d5c218d 100644
+--- a/include/scsi/scsi_transport_iscsi.h
++++ b/include/scsi/scsi_transport_iscsi.h
+@@ -24,8 +24,6 @@
+ #define SCSI_TRANSPORT_ISCSI_H
+ 
+ #include <linux/device.h>
+-#include <linux/list.h>
+-#include <linux/mutex.h>
+ #include <scsi/iscsi_if.h>
+ 
+ struct scsi_transport_template;
+@@ -81,8 +79,7 @@ struct iscsi_transport {
+ 	char *name;
+ 	unsigned int caps;
+ 	/* LLD sets this to indicate what values it can export to sysfs */
+-	uint64_t param_mask;
+-	uint64_t host_param_mask;
++	unsigned int param_mask;
+ 	struct scsi_host_template *host_template;
+ 	/* LLD connection data size */
+ 	int conndata_size;
+@@ -92,8 +89,7 @@ struct iscsi_transport {
+ 	unsigned int max_conn;
+ 	unsigned int max_cmd_len;
+ 	struct iscsi_cls_session *(*create_session) (struct iscsi_transport *it,
+-		struct scsi_transport_template *t, uint16_t, uint16_t,
+-		uint32_t sn, uint32_t *hn);
++		struct scsi_transport_template *t, uint32_t sn, uint32_t *hn);
+ 	void (*destroy_session) (struct iscsi_cls_session *session);
+ 	struct iscsi_cls_conn *(*create_conn) (struct iscsi_cls_session *sess,
+ 				uint32_t cid);
+@@ -109,18 +105,14 @@ struct iscsi_transport {
+ 			       enum iscsi_param param, char *buf);
+ 	int (*get_session_param) (struct iscsi_cls_session *session,
+ 				  enum iscsi_param param, char *buf);
+-	int (*get_host_param) (struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf);
+-	int (*set_host_param) (struct Scsi_Host *shost,
+-			       enum iscsi_host_param param, char *buf,
+-			       int buflen);
+ 	int (*send_pdu) (struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 			 char *data, uint32_t data_size);
+ 	void (*get_stats) (struct iscsi_cls_conn *conn,
+ 			   struct iscsi_stats *stats);
+-	int (*init_cmd_task) (struct iscsi_cmd_task *ctask);
++	void (*init_cmd_task) (struct iscsi_cmd_task *ctask);
+ 	void (*init_mgmt_task) (struct iscsi_conn *conn,
+-				struct iscsi_mgmt_task *mtask);
++				struct iscsi_mgmt_task *mtask,
++				char *data, uint32_t data_size);
+ 	int (*xmit_cmd_task) (struct iscsi_conn *conn,
+ 			      struct iscsi_cmd_task *ctask);
+ 	void (*cleanup_cmd_task) (struct iscsi_conn *conn,
+@@ -132,7 +124,7 @@ struct iscsi_transport {
+ 			   uint64_t *ep_handle);
+ 	int (*ep_poll) (uint64_t ep_handle, int timeout_ms);
+ 	void (*ep_disconnect) (uint64_t ep_handle);
+-	int (*tgt_dscvr) (struct Scsi_Host *shost, enum iscsi_tgt_dscvr type,
++	int (*tgt_dscvr) (enum iscsi_tgt_dscvr type, uint32_t host_no,
+ 			  uint32_t enable, struct sockaddr *dst_addr);
+ };
+ 
+@@ -149,6 +141,13 @@ extern void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error);
+ extern int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 			  char *data, uint32_t data_size);
+ 
++
++/* Connection's states */
++#define ISCSI_CONN_INITIAL_STAGE	0
++#define ISCSI_CONN_STARTED		1
++#define ISCSI_CONN_STOPPED		2
++#define ISCSI_CONN_CLEANUP_WAIT		3
++
+ struct iscsi_cls_conn {
+ 	struct list_head conn_list;	/* item in connlist */
+ 	void *dd_data;			/* LLD private data */
+@@ -162,25 +161,18 @@ struct iscsi_cls_conn {
+ #define iscsi_dev_to_conn(_dev) \
+ 	container_of(_dev, struct iscsi_cls_conn, dev)
+ 
+-#define iscsi_conn_to_session(_conn) \
+-	iscsi_dev_to_session(_conn->dev.parent)
+-
+-/* iscsi class session state */
+-enum {
+-	ISCSI_SESSION_LOGGED_IN,
+-	ISCSI_SESSION_FAILED,
+-	ISCSI_SESSION_FREE,
+-};
++/* Session's states */
++#define ISCSI_STATE_FREE		1
++#define ISCSI_STATE_LOGGED_IN		2
++#define ISCSI_STATE_FAILED		3
++#define ISCSI_STATE_TERMINATE		4
++#define ISCSI_STATE_IN_RECOVERY		5
++#define ISCSI_STATE_RECOVERY_FAILED	6
+ 
+ struct iscsi_cls_session {
+ 	struct list_head sess_list;		/* item in session_list */
+ 	struct list_head host_list;
+ 	struct iscsi_transport *transport;
+-	spinlock_t lock;
+-	struct work_struct block_work;
+-	struct work_struct unblock_work;
+-	struct work_struct scan_work;
+-	struct work_struct unbind_work;
+ 
+ 	/* recovery fields */
+ 	int recovery_tmo;
+@@ -188,7 +180,6 @@ struct iscsi_cls_session {
+ 
+ 	int target_id;
+ 
+-	int state;
+ 	int sid;				/* session id */
+ 	void *dd_data;				/* LLD private data */
+ 	struct device dev;	/* sysfs transport/container device */
+@@ -205,28 +196,18 @@ struct iscsi_cls_session {
+ 
+ struct iscsi_host {
+ 	struct list_head sessions;
+-	atomic_t nr_scans;
+ 	struct mutex mutex;
+-	struct workqueue_struct *scan_workq;
+-	char scan_workq_name[KOBJ_NAME_LEN];
+ };
+ 
+ /*
+  * session and connection functions that can be used by HW iSCSI LLDs
+  */
+-#define iscsi_cls_session_printk(prefix, _cls_session, fmt, a...) \
+-	dev_printk(prefix, &(_cls_session)->dev, fmt, ##a)
+-
+-#define iscsi_cls_conn_printk(prefix, _cls_conn, fmt, a...) \
+-	dev_printk(prefix, &(_cls_conn)->dev, fmt, ##a)
+-
+-extern int iscsi_session_chkready(struct iscsi_cls_session *session);
+ extern struct iscsi_cls_session *iscsi_alloc_session(struct Scsi_Host *shost,
+ 					struct iscsi_transport *transport);
+ extern int iscsi_add_session(struct iscsi_cls_session *session,
+ 			     unsigned int target_id);
+-extern int iscsi_session_event(struct iscsi_cls_session *session,
+-			       enum iscsi_uevent_e event);
++extern int iscsi_if_create_session_done(struct iscsi_cls_conn *conn);
++extern int iscsi_if_destroy_session_done(struct iscsi_cls_conn *conn);
+ extern struct iscsi_cls_session *iscsi_create_session(struct Scsi_Host *shost,
+ 						struct iscsi_transport *t,
+ 						unsigned int target_id);
+@@ -238,6 +219,6 @@ extern struct iscsi_cls_conn *iscsi_create_conn(struct iscsi_cls_session *sess,
+ extern int iscsi_destroy_conn(struct iscsi_cls_conn *conn);
+ extern void iscsi_unblock_session(struct iscsi_cls_session *session);
+ extern void iscsi_block_session(struct iscsi_cls_session *session);
+-extern int iscsi_scan_finished(struct Scsi_Host *shost, unsigned long time);
++
+ 
+ #endif
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.9_U5/iscsi_02_add_to_2_6_9.patch b/kernel_patches/backport/2.6.9_U5/iscsi_02_add_to_2_6_9.patch
new file mode 100644
index 0000000..1f05d95
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U5/iscsi_02_add_to_2_6_9.patch
@@ -0,0 +1,180 @@
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 4376840..11dfaf9 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -2145,7 +2145,6 @@ static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
+ static struct scsi_host_template iscsi_sht = {
+ 	.name			= "iSCSI Initiator over TCP/IP",
+ 	.queuecommand           = iscsi_queuecommand,
+-	.change_queue_depth	= iscsi_change_queue_depth,
+ 	.can_queue		= ISCSI_XMIT_CMDS_MAX - 1,
+ 	.sg_tablesize		= ISCSI_SG_TABLESIZE,
+	.max_sectors		= 0xFFFF,
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index d37048c..60f5846 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -1366,7 +1366,6 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 	shost->max_lun = iscsit->max_lun;
+ 	shost->max_cmd_len = iscsit->max_cmd_len;
+ 	shost->transportt = scsit;
+-	shost->transportt->create_work_queue = 1;
+ 	*hostno = shost->host_no;
+ 
+ 	session = iscsi_hostdata(shost->hostdata);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 8133c22..f1c68f7 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -65,6 +65,8 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define cdev_to_iscsi_internal(_cdev) \
+ 	container_of(_cdev, struct iscsi_internal, cdev)
+ 
++extern int attribute_container_init(void);
++
+ static void iscsi_transport_release(struct class_device *cdev)
+ {
+ 	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+@@ -80,6 +82,17 @@ static struct class iscsi_transport_class = {
+ 	.release = iscsi_transport_release,
+ };
+ 
++static void iscsi_host_class_release(struct class_device *class_dev)
++{
++	struct Scsi_Host *shost = transport_class_to_shost(class_dev);
++	put_device(&shost->shost_gendev);
++}
++
++struct class iscsi_host_class = {
++	.name = "iscsi_host",
++	.release = iscsi_host_class_release,
++};
++
+ static ssize_t
+ show_transport_handle(struct class_device *cdev, char *buf)
+ {
+@@ -115,10 +128,8 @@ static struct attribute_group iscsi_transport_group = {
+ 	.attrs = iscsi_transport_attrs,
+ };
+ 
+-static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct class_device *cdev)
++static int iscsi_setup_host(struct Scsi_Host *shost)
+ {
+-	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+ 
+ 	memset(ihost, 0, sizeof(*ihost));
+@@ -127,12 +138,6 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ 	return 0;
+ }
+ 
+-static DECLARE_TRANSPORT_CLASS(iscsi_host_class,
+-			       "iscsi_host",
+-			       iscsi_setup_host,
+-			       NULL,
+-			       NULL);
+-
+ static DECLARE_TRANSPORT_CLASS(iscsi_session_class,
+ 			       "iscsi_session",
+ 			       NULL,
+@@ -216,24 +221,6 @@ static int iscsi_is_session_dev(const struct device *dev)
+ 	return dev->release == iscsi_session_release;
+ }
+ 
+-static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+-			   uint id, uint lun)
+-{
+-	struct iscsi_host *ihost = shost->shost_data;
+-	struct iscsi_cls_session *session;
+-
+-	mutex_lock(&ihost->mutex);
+-	list_for_each_entry(session, &ihost->sessions, host_list) {
+-		if ((channel == SCAN_WILD_CARD || channel == 0) &&
+-		    (id == SCAN_WILD_CARD || id == session->target_id))
+-			scsi_scan_target(&session->dev, 0,
+-					 session->target_id, lun, 1);
+-	}
+-	mutex_unlock(&ihost->mutex);
+-
+-	return 0;
+-}
+-
+ static void session_recovery_timedout(struct work_struct *work)
+ {
+ 	struct iscsi_cls_session *session =
+@@ -362,8 +349,6 @@ void iscsi_remove_session(struct iscsi_cls_session *session)
+ 	list_del(&session->host_list);
+ 	mutex_unlock(&ihost->mutex);
+ 
+-	scsi_remove_target(&session->dev);
+-
+ 	transport_unregister_device(&session->dev);
+ 	device_del(&session->dev);
+ }
+@@ -1269,24 +1254,6 @@ static int iscsi_conn_match(struct attribute_container *cont,
+ 	return &priv->conn_cont.ac == cont;
+ }
+ 
+-static int iscsi_host_match(struct attribute_container *cont,
+-			    struct device *dev)
+-{
+-	struct Scsi_Host *shost;
+-	struct iscsi_internal *priv;
+-
+-	if (!scsi_is_host_device(dev))
+-		return 0;
+-
+-	shost = dev_to_shost(dev);
+-	if (!shost->transportt  ||
+-	    shost->transportt->host_attrs.ac.class != &iscsi_host_class.class)
+-		return 0;
+-
+-        priv = to_iscsi_internal(shost->transportt);
+-        return &priv->t.host_attrs.ac == cont;
+-}
+-
+ struct scsi_transport_template *
+ iscsi_register_transport(struct iscsi_transport *tt)
+ {
+@@ -1306,7 +1273,6 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	INIT_LIST_HEAD(&priv->list);
+ 	priv->daemon_pid = -1;
+ 	priv->iscsi_transport = tt;
+-	priv->t.user_scan = iscsi_user_scan;
+ 
+ 	priv->cdev.class = &iscsi_transport_class;
+ 	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
+@@ -1319,12 +1285,10 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 		goto unregister_cdev;
+ 
+ 	/* host parameters */
+-	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+-	priv->t.host_attrs.ac.class = &iscsi_host_class.class;
+-	priv->t.host_attrs.ac.match = iscsi_host_match;
++	priv->t.host_attrs = &priv->host_attrs[0];
++	priv->t.host_class = &iscsi_host_class;
++	priv->t.host_setup = iscsi_setup_host;
+ 	priv->t.host_size = sizeof(struct iscsi_host);
+-	priv->host_attrs[0] = NULL;
+-	transport_container_register(&priv->t.host_attrs);
+ 
+ 	/* connection parameters */
+ 	priv->conn_cont.ac.attrs = &priv->conn_attrs[0];
+@@ -1402,7 +1366,6 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 
+ 	transport_container_unregister(&priv->conn_cont);
+ 	transport_container_unregister(&priv->session_cont);
+-	transport_container_unregister(&priv->t.host_attrs);
+ 
+ 	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	class_device_unregister(&priv->cdev);
+@@ -1420,6 +1420,7 @@ static __init int iscsi_transport_init(void)
+ 		ISCSI_TRANSPORT_VERSION);
+ 
+	atomic_set(&iscsi_session_nr, 0);
++	attribute_container_init();
+
+	err = class_register(&iscsi_transport_class);
+ 	if (err)
+ 		return err;
diff --git a/kernel_patches/backport/2.6.9_U5/iscsi_03_add_session_wq.patch b/kernel_patches/backport/2.6.9_U5/iscsi_03_add_session_wq.patch
new file mode 100644
index 0000000..55564c5
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U5/iscsi_03_add_session_wq.patch
@@ -0,0 +1,76 @@
+diff --git a/drivers/infiniband/ulp/iser/iser_initiator.c b/drivers/infiniband/ulp/iser/iser_initiator.c
+index a6f2303..5d62cc0 100644
+--- a/drivers/infiniband/ulp/iser/iser_initiator.c
++++ b/drivers/infiniband/ulp/iser/iser_initiator.c
+@@ -610,7 +610,7 @@ void iser_snd_completion(struct iser_desc *tx_desc)
+ 
+ 	if (resume_tx) {
+ 		iser_dbg("%ld resuming tx\n",jiffies);
+-		scsi_queue_work(conn->session->host, &conn->xmitwork);
++		queue_work(conn->session->wq, &conn->xmitwork);
+ 	}
+ 
+ 	if (tx_desc->type == ISCSI_TX_CONTROL) {
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index e8020a5..43e9128 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -828,7 +828,7 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	spin_unlock(&session->lock);
+ 
+-	scsi_queue_work(host, &conn->xmitwork);
++	queue_work(session->wq, &conn->xmitwork);
+ 	return 0;
+ 
+ reject:
+@@ -928,7 +928,7 @@ iscsi_conn_send_generic(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	else
+ 	        __kfifo_put(conn->mgmtqueue, (void*)&mtask, sizeof(void*));
+ 
+-	scsi_queue_work(session->host, &conn->xmitwork);
++	queue_work(session->wq, &conn->xmitwork);
+ 	return 0;
+ }
+ 
+@@ -1415,6 +1415,9 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 		INIT_LIST_HEAD(&mtask->running);
+ 	}
+ 
++	session->wq = create_singlethread_workqueue("");
++	BUG_ON(!session->wq);
++
+ 	if (scsi_add_host(shost, NULL))
+ 		goto add_host_fail;
+ 
+@@ -1462,6 +1465,8 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session)
+ 
+ 	kfree(session->targetname);
+ 
++	destroy_workqueue(session->wq);
++
+ 	iscsi_destroy_session(cls_session);
+ 	scsi_host_put(shost);
+ 	module_put(owner);
+@@ -1595,7 +1600,7 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 
+ 	/* flush queued up work because we free the connection below */
+-	scsi_flush_work(session->host);
++	flush_workqueue(session->wq);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	kfree(conn->data);
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index ea0816d..e8a95f5 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -244,6 +244,8 @@ struct iscsi_session {
+ 	int			mgmtpool_max;	/* size of mgmt array */
+ 	struct iscsi_mgmt_task	**mgmt_cmds;	/* Original mgmt arr */
+ 	struct iscsi_queue	mgmtpool;	/* Mgmt PDU's pool */
++
++	struct workqueue_struct *wq;
+ };
+ 
+ /*
diff --git a/kernel_patches/backport/2.6.9_U5/iscsi_04_inet_sock_to_opt.patch b/kernel_patches/backport/2.6.9_U5/iscsi_04_inet_sock_to_opt.patch
new file mode 100644
index 0000000..1fb2376
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U5/iscsi_04_inet_sock_to_opt.patch
@@ -0,0 +1,13 @@
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 905efc4..f73a743 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -2027,7 +2027,7 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct inet_sock *inet;
++	struct inet_opt *inet;
+ 	struct ipv6_pinfo *np;
+ 	struct sock *sk;
+ 	int len;
diff --git a/kernel_patches/backport/2.6.9_U5/iscsi_05_release_host_lock_before_eh.patch b/kernel_patches/backport/2.6.9_U5/iscsi_05_release_host_lock_before_eh.patch
new file mode 100644
index 0000000..c994506
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U5/iscsi_05_release_host_lock_before_eh.patch
@@ -0,0 +1,60 @@
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 7db081b..211944e 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -968,12 +968,14 @@ int iscsi_eh_host_reset(struct scsi_cmnd *sc)
+ 	struct iscsi_conn *conn = session->leadconn;
+ 	int fail_session = 0;
+ 
++	spin_unlock_irq(host->host_lock);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_TERMINATE) {
+ failed:
+ 		debug_scsi("failing host reset: session terminated "
+ 			   "[CID %d age %d]\n", conn->id, session->age);
+ 		spin_unlock_bh(&session->lock);
++		spin_lock_irq(host->host_lock);
+ 		return FAILED;
+ 	}
+ 
+@@ -1005,6 +1007,7 @@ failed:
+ 	else
+ 		goto failed;
+ 	spin_unlock_bh(&session->lock);
++	spin_lock_irq(host->host_lock);
+ 
+ 	return SUCCESS;
+ }
+@@ -1162,13 +1165,16 @@ int iscsi_eh_abort(struct scsi_cmnd *sc)
+ 	struct iscsi_conn *conn;
+ 	struct iscsi_session *session;
+ 	int rc;
++	struct Scsi_Host *shost = sc->device->host;
+ 
++	spin_unlock_irq(shost->host_lock);
+ 	/*
+ 	 * if session was ISCSI_STATE_IN_RECOVERY then we may not have
+ 	 * got the command.
+ 	 */
+ 	if (!sc->SCp.ptr) {
+ 		debug_scsi("sc never reached iscsi layer or it completed.\n");
++		spin_lock_irq(shost->host_lock);
+ 		return SUCCESS;
+ 	}
+ 
+@@ -1253,6 +1259,7 @@ success_cleanup:
+ 
+ success_rel_mutex:
+ 	mutex_unlock(&conn->xmitmutex);
++	spin_lock_irq(shost->host_lock);
+ 	return SUCCESS;
+ 
+ failed:
+@@ -1260,6 +1267,7 @@ failed:
+ 	mutex_unlock(&conn->xmitmutex);
+ 
+ 	debug_scsi("abort failed [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
++	spin_lock_irq(shost->host_lock);
+ 	return FAILED;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_eh_abort);
diff --git a/kernel_patches/backport/2.6.9_U5/iscsi_06_scsi_addons.patch b/kernel_patches/backport/2.6.9_U5/iscsi_06_scsi_addons.patch
new file mode 100644
index 0000000..a114696
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U5/iscsi_06_scsi_addons.patch
@@ -0,0 +1,75 @@
+diff --git a/drivers/scsi/init.c b/drivers/scsi/init.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/init.c
+@@ -0,0 +1 @@
++#include "src/init.c"
+diff --git a/drivers/scsi/attribute_container.c b/drivers/scsi/attribute_container.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/attribute_container.c
+@@ -0,0 +1 @@
++#include "../drivers/base/attribute_container.c"
+diff --git a/drivers/scsi/transport_class.c b/drivers/scsi/transport_class.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/transport_class.c
+@@ -0,0 +1 @@
++#include "../drivers/base/transport_class.c"
+diff --git a/drivers/scsi/klist.c b/drivers/scsi/klist.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/klist.c
+@@ -0,0 +1 @@
++#include "../../lib/klist.c"
+diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi.c
+@@ -0,0 +1 @@
++#include "src/scsi.c"
+diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_lib.c
+@@ -0,0 +1 @@
++#include "src/scsi_lib.c"
+diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_scan.c
+@@ -0,0 +1 @@
++#include "src/scsi_scan.c"
+diff --git a/drivers/scsi/libiscsi_f.c b/drivers/scsi/libiscsi_f.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/libiscsi_f.c
+@@ -0,0 +1 @@
++#include "libiscsi.c"
+diff --git a/drivers/scsi/scsi_transport_iscsi_f.c b/drivers/scsi/scsi_transport_iscsi_f.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_transport_iscsi_f.c
+@@ -0,0 +1 @@
++#include "scsi_transport_iscsi.c"
+diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
+index e212608..3bf2015 100644
+--- a/drivers/scsi/Makefile
++++ b/drivers/scsi/Makefile
+@@ -3,2 +3,7 @@
+ obj-$(CONFIG_SCSI_ISCSI_ATTRS) += scsi_transport_iscsi.o
+ obj-$(CONFIG_ISCSI_TCP)        += libiscsi.o   iscsi_tcp.o
++
++CFLAGS_attribute_container.o =   $(BACKPORT_INCLUDES)/src/
++
++scsi_transport_iscsi-y := scsi_transport_iscsi_f.o scsi.o scsi_lib.o init.o klist.o attribute_container.o transport_class.o
++libiscsi-y             := libiscsi_f.o scsi_scan.o
diff --git a/kernel_patches/backport/2.6.9_U6/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch b/kernel_patches/backport/2.6.9_U6/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
new file mode 100644
index 0000000..87d18ee
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U6/iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
@@ -0,0 +1,7774 @@
+From 53de31f7a414a03b7939c20237ecb6e599631338 Mon Sep 17 00:00:00 2001
+From: Doron Shoham <dorons at voltaire.com>
+Date: Mon, 2 Jun 2008 12:01:07 +0300
+Subject: [PATCH 1/1] iscsi_01_sync_kernel_code_with_ofed_1_2_5.patch
+
+Signed-off-by: Doron Shoham <dorons at voltaire.com>
+---
+ drivers/scsi/iscsi_tcp.c            | 2313 +++++++++++++++++++----------------
+ drivers/scsi/iscsi_tcp.h            |  131 ++-
+ drivers/scsi/libiscsi.c             | 1799 +++++++++------------------
+ drivers/scsi/scsi_transport_iscsi.c |  867 +++++---------
+ include/scsi/iscsi_if.h             |   54 -
+ include/scsi/libiscsi.h             |  188 +---
+ include/scsi/scsi_transport_iscsi.h |   65 +-
+ 7 files changed, 2296 insertions(+), 3121 deletions(-)
+
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 72b9b2a..c9a3abf 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -29,15 +29,14 @@
+ #include <linux/types.h>
+ #include <linux/list.h>
+ #include <linux/inet.h>
+-#include <linux/file.h>
+ #include <linux/blkdev.h>
+ #include <linux/crypto.h>
+ #include <linux/delay.h>
+ #include <linux/kfifo.h>
+ #include <linux/scatterlist.h>
++#include <linux/mutex.h>
+ #include <net/tcp.h>
+ #include <scsi/scsi_cmnd.h>
+-#include <scsi/scsi_device.h>
+ #include <scsi/scsi_host.h>
+ #include <scsi/scsi.h>
+ #include <scsi/scsi_transport_iscsi.h>
+@@ -48,7 +47,7 @@ MODULE_AUTHOR("Dmitry Yusupov <dmitry_yus at yahoo.com>, "
+ 	      "Alex Aizman <itn780 at yahoo.com>");
+ MODULE_DESCRIPTION("iSCSI/TCP data-path");
+ MODULE_LICENSE("GPL");
+-#undef DEBUG_TCP
++/* #define DEBUG_TCP */
+ #define DEBUG_ASSERT
+ 
+ #ifdef DEBUG_TCP
+@@ -67,429 +66,118 @@ MODULE_LICENSE("GPL");
+ static unsigned int iscsi_max_lun = 512;
+ module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);
+ 
+-static int iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-				   struct iscsi_segment *segment);
+-
+-/*
+- * Scatterlist handling: inside the iscsi_segment, we
+- * remember an index into the scatterlist, and set data/size
+- * to the current scatterlist entry. For highmem pages, we
+- * kmap as needed.
+- *
+- * Note that the page is unmapped when we return from
+- * TCP's data_ready handler, so we may end up mapping and
+- * unmapping the same page repeatedly. The whole reason
+- * for this is that we shouldn't keep the page mapped
+- * outside the softirq.
+- */
+-
+-/**
+- * iscsi_tcp_segment_init_sg - init indicated scatterlist entry
+- * @segment: the buffer object
+- * @sg: scatterlist
+- * @offset: byte offset into that sg entry
+- *
+- * This function sets up the segment so that subsequent
+- * data is copied to the indicated sg entry, at the given
+- * offset.
+- */
+ static inline void
+-iscsi_tcp_segment_init_sg(struct iscsi_segment *segment,
+-			  struct scatterlist *sg, unsigned int offset)
++iscsi_buf_init_iov(struct iscsi_buf *ibuf, char *vbuf, int size)
+ {
+-	segment->sg = sg;
+-	segment->sg_offset = offset;
+-	segment->size = min(sg->length - offset,
+-			    segment->total_size - segment->total_copied);
+-	segment->data = NULL;
++	ibuf->sg.page = virt_to_page(vbuf);
++	ibuf->sg.offset = offset_in_page(vbuf);
++	ibuf->sg.length = size;
++	ibuf->sent = 0;
++	ibuf->use_sendmsg = 1;
+ }
+ 
+-/**
+- * iscsi_tcp_segment_map - map the current S/G page
+- * @segment: iscsi_segment
+- * @recv: 1 if called from recv path
+- *
+- * We only need to possibly kmap data if scatter lists are being used,
+- * because the iscsi passthrough and internal IO paths will never use high
+- * mem pages.
+- */
+ static inline void
+-iscsi_tcp_segment_map(struct iscsi_segment *segment, int recv)
++iscsi_buf_init_sg(struct iscsi_buf *ibuf, struct scatterlist *sg)
+ {
+-	struct scatterlist *sg;
+-
+-	if (segment->data != NULL || !segment->sg)
+-		return;
+-
+-	sg = segment->sg;
+-	BUG_ON(segment->sg_mapped);
+-	BUG_ON(sg->length == 0);
+-
++	ibuf->sg.page = sg->page;
++	ibuf->sg.offset = sg->offset;
++	ibuf->sg.length = sg->length;
+ 	/*
+-	 * If the page count is greater than one it is ok to send
+-	 * to the network layer's zero copy send path. If not we
+-	 * have to go the slow sendmsg path. We always map for the
+-	 * recv path.
++	 * Fastpath: sg element fits into single page
+ 	 */
+-	if (page_count(sg_page(sg)) >= 1 && !recv)
+-		return;
+-
+-	debug_tcp("iscsi_tcp_segment_map %s %p\n", recv ? "recv" : "xmit",
+-		  segment);
+-	segment->sg_mapped = kmap_atomic(sg_page(sg), KM_SOFTIRQ0);
+-	segment->data = segment->sg_mapped + sg->offset + segment->sg_offset;
++	if (sg->length + sg->offset <= PAGE_SIZE && !PageSlab(sg->page))
++		ibuf->use_sendmsg = 0;
++	else
++		ibuf->use_sendmsg = 1;
++	ibuf->sent = 0;
+ }
+ 
+-static inline void
+-iscsi_tcp_segment_unmap(struct iscsi_segment *segment)
++static inline int
++iscsi_buf_left(struct iscsi_buf *ibuf)
+ {
+-	debug_tcp("iscsi_tcp_segment_unmap %p\n", segment);
++	int rc;
+ 
+-	if (segment->sg_mapped) {
+-		debug_tcp("iscsi_tcp_segment_unmap valid\n");
+-		kunmap_atomic(segment->sg_mapped, KM_SOFTIRQ0);
+-		segment->sg_mapped = NULL;
+-		segment->data = NULL;
+-	}
++	rc = ibuf->sg.length - ibuf->sent;
++	BUG_ON(rc < 0);
++	return rc;
+ }
+ 
+-/*
+- * Splice the digest buffer into the buffer
+- */
+ static inline void
+-iscsi_tcp_segment_splice_digest(struct iscsi_segment *segment, void *digest)
++iscsi_hdr_digest(struct iscsi_conn *conn, struct iscsi_buf *buf,
++		 u8* crc)
+ {
+-	segment->data = digest;
+-	segment->digest_len = ISCSI_DIGEST_SIZE;
+-	segment->total_size += ISCSI_DIGEST_SIZE;
+-	segment->size = ISCSI_DIGEST_SIZE;
+-	segment->copied = 0;
+-	segment->sg = NULL;
+-	segment->hash = NULL;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++
++	crypto_hash_digest(&tcp_conn->tx_hash, &buf->sg, buf->sg.length, crc);
++	buf->sg.length = tcp_conn->hdr_size;
+ }
+ 
+-/**
+- * iscsi_tcp_segment_done - check whether the segment is complete
+- * @segment: iscsi segment to check
+- * @recv: set to one of this is called from the recv path
+- * @copied: number of bytes copied
+- *
+- * Check if we're done receiving this segment. If the receive
+- * buffer is full but we expect more data, move on to the
+- * next entry in the scatterlist.
+- *
+- * If the amount of data we received isn't a multiple of 4,
+- * we will transparently receive the pad bytes, too.
+- *
+- * This function must be re-entrant.
+- */
+ static inline int
+-iscsi_tcp_segment_done(struct iscsi_segment *segment, int recv, unsigned copied)
++iscsi_hdr_extract(struct iscsi_tcp_conn *tcp_conn)
+ {
+-	static unsigned char padbuf[ISCSI_PAD_LEN];
+-	struct scatterlist sg;
+-	unsigned int pad;
++	struct sk_buff *skb = tcp_conn->in.skb;
++
++	tcp_conn->in.zero_copy_hdr = 0;
+ 
+-	debug_tcp("copied %u %u size %u %s\n", segment->copied, copied,
+-		  segment->size, recv ? "recv" : "xmit");
+-	if (segment->hash && copied) {
++	if (tcp_conn->in.copy >= tcp_conn->hdr_size &&
++	    tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER) {
+ 		/*
+-		 * If a segment is kmapd we must unmap it before sending
+-		 * to the crypto layer since that will try to kmap it again.
++		 * Zero-copy PDU Header: using connection context
++		 * to store header pointer.
+ 		 */
+-		iscsi_tcp_segment_unmap(segment);
+-
+-		if (!segment->data) {
+-			sg_init_table(&sg, 1);
+-			sg_set_page(&sg, sg_page(segment->sg), copied,
+-				    segment->copied + segment->sg_offset +
+-							segment->sg->offset);
+-		} else
+-			sg_init_one(&sg, segment->data + segment->copied,
+-				    copied);
+-		crypto_hash_update(segment->hash, &sg, copied);
+-	}
+-
+-	segment->copied += copied;
+-	if (segment->copied < segment->size) {
+-		iscsi_tcp_segment_map(segment, recv);
+-		return 0;
+-	}
+-
+-	segment->total_copied += segment->copied;
+-	segment->copied = 0;
+-	segment->size = 0;
+-
+-	/* Unmap the current scatterlist page, if there is one. */
+-	iscsi_tcp_segment_unmap(segment);
+-
+-	/* Do we have more scatterlist entries? */
+-	debug_tcp("total copied %u total size %u\n", segment->total_copied,
+-		   segment->total_size);
+-	if (segment->total_copied < segment->total_size) {
+-		/* Proceed to the next entry in the scatterlist. */
+-		iscsi_tcp_segment_init_sg(segment, sg_next(segment->sg),
+-					  0);
+-		iscsi_tcp_segment_map(segment, recv);
+-		BUG_ON(segment->size == 0);
+-		return 0;
+-	}
+-
+-	/* Do we need to handle padding? */
+-	pad = iscsi_padding(segment->total_copied);
+-	if (pad != 0) {
+-		debug_tcp("consume %d pad bytes\n", pad);
+-		segment->total_size += pad;
+-		segment->size = pad;
+-		segment->data = padbuf;
+-		return 0;
+-	}
+-
+-	/*
+-	 * Set us up for transferring the data digest. hdr digest
+-	 * is completely handled in hdr done function.
+-	 */
+-	if (segment->hash) {
+-		crypto_hash_final(segment->hash, segment->digest);
+-		iscsi_tcp_segment_splice_digest(segment,
+-				 recv ? segment->recv_digest : segment->digest);
+-		return 0;
+-	}
+-
+-	return 1;
+-}
+-
+-/**
+- * iscsi_tcp_xmit_segment - transmit segment
+- * @tcp_conn: the iSCSI TCP connection
+- * @segment: the buffer to transmnit
+- *
+- * This function transmits as much of the buffer as
+- * the network layer will accept, and returns the number of
+- * bytes transmitted.
+- *
+- * If CRC hashing is enabled, the function will compute the
+- * hash as it goes. When the entire segment has been transmitted,
+- * it will retrieve the hash value and send it as well.
+- */
+-static int
+-iscsi_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn,
+-		       struct iscsi_segment *segment)
+-{
+-	struct socket *sk = tcp_conn->sock;
+-	unsigned int copied = 0;
+-	int r = 0;
+-
+-	while (!iscsi_tcp_segment_done(segment, 0, r)) {
+-		struct scatterlist *sg;
+-		unsigned int offset, copy;
+-		int flags = 0;
+-
+-		r = 0;
+-		offset = segment->copied;
+-		copy = segment->size - offset;
+-
+-		if (segment->total_copied + segment->size < segment->total_size)
+-			flags |= MSG_MORE;
+-
+-		/* Use sendpage if we can; else fall back to sendmsg */
+-		if (!segment->data) {
+-			sg = segment->sg;
+-			offset += segment->sg_offset + sg->offset;
+-			r = tcp_conn->sendpage(sk, sg_page(sg), offset, copy,
+-					       flags);
++		if (skb_shinfo(skb)->frag_list == NULL &&
++		    !skb_shinfo(skb)->nr_frags) {
++			tcp_conn->in.hdr = (struct iscsi_hdr *)
++				((char*)skb->data + tcp_conn->in.offset);
++			tcp_conn->in.zero_copy_hdr = 1;
+ 		} else {
+-			struct msghdr msg = { .msg_flags = flags };
+-			struct kvec iov = {
+-				.iov_base = segment->data + offset,
+-				.iov_len = copy
+-			};
+-
+-			r = kernel_sendmsg(sk, &msg, &iov, 1, copy);
+-		}
+-
+-		if (r < 0) {
+-			iscsi_tcp_segment_unmap(segment);
+-			if (copied || r == -EAGAIN)
+-				break;
+-			return r;
++			/* ignoring return code since we checked
++			 * in.copy before */
++			skb_copy_bits(skb, tcp_conn->in.offset,
++				&tcp_conn->hdr, tcp_conn->hdr_size);
++			tcp_conn->in.hdr = &tcp_conn->hdr;
+ 		}
+-		copied += r;
+-	}
+-	return copied;
+-}
+-
+-/**
+- * iscsi_tcp_segment_recv - copy data to segment
+- * @tcp_conn: the iSCSI TCP connection
+- * @segment: the buffer to copy to
+- * @ptr: data pointer
+- * @len: amount of data available
+- *
+- * This function copies up to @len bytes to the
+- * given buffer, and returns the number of bytes
+- * consumed, which can actually be less than @len.
+- *
+- * If hash digest is enabled, the function will update the
+- * hash while copying.
+- * Combining these two operations doesn't buy us a lot (yet),
+- * but in the future we could implement combined copy+crc,
+- * just way we do for network layer checksums.
+- */
+-static int
+-iscsi_tcp_segment_recv(struct iscsi_tcp_conn *tcp_conn,
+-		       struct iscsi_segment *segment, const void *ptr,
+-		       unsigned int len)
+-{
+-	unsigned int copy = 0, copied = 0;
+-
+-	while (!iscsi_tcp_segment_done(segment, 1, copy)) {
+-		if (copied == len) {
+-			debug_tcp("iscsi_tcp_segment_recv copied %d bytes\n",
+-				  len);
+-			break;
+-		}
+-
+-		copy = min(len - copied, segment->size - segment->copied);
+-		debug_tcp("iscsi_tcp_segment_recv copying %d\n", copy);
+-		memcpy(segment->data + segment->copied, ptr + copied, copy);
+-		copied += copy;
+-	}
+-	return copied;
+-}
+-
+-static inline void
+-iscsi_tcp_dgst_header(struct hash_desc *hash, const void *hdr, size_t hdrlen,
+-		      unsigned char digest[ISCSI_DIGEST_SIZE])
+-{
+-	struct scatterlist sg;
++		tcp_conn->in.offset += tcp_conn->hdr_size;
++		tcp_conn->in.copy -= tcp_conn->hdr_size;
++	} else {
++		int hdr_remains;
++		int copylen;
+ 
+-	sg_init_one(&sg, hdr, hdrlen);
+-	crypto_hash_digest(hash, &sg, hdrlen, digest);
+-}
++		/*
++		 * PDU header scattered across SKB's,
++		 * copying it... This'll happen quite rarely.
++		 */
+ 
+-static inline int
+-iscsi_tcp_dgst_verify(struct iscsi_tcp_conn *tcp_conn,
+-		      struct iscsi_segment *segment)
+-{
+-	if (!segment->digest_len)
+-		return 1;
++		if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER)
++			tcp_conn->in.hdr_offset = 0;
+ 
+-	if (memcmp(segment->recv_digest, segment->digest,
+-		   segment->digest_len)) {
+-		debug_scsi("digest mismatch\n");
+-		return 0;
+-	}
++		hdr_remains = tcp_conn->hdr_size - tcp_conn->in.hdr_offset;
++		BUG_ON(hdr_remains <= 0);
+ 
+-	return 1;
+-}
++		copylen = min(tcp_conn->in.copy, hdr_remains);
++		skb_copy_bits(skb, tcp_conn->in.offset,
++			(char*)&tcp_conn->hdr + tcp_conn->in.hdr_offset,
++			copylen);
+ 
+-/*
+- * Helper function to set up segment buffer
+- */
+-static inline void
+-__iscsi_segment_init(struct iscsi_segment *segment, size_t size,
+-		     iscsi_segment_done_fn_t *done, struct hash_desc *hash)
+-{
+-	memset(segment, 0, sizeof(*segment));
+-	segment->total_size = size;
+-	segment->done = done;
+-
+-	if (hash) {
+-		segment->hash = hash;
+-		crypto_hash_init(hash);
+-	}
+-}
++		debug_tcp("PDU gather offset %d bytes %d in.offset %d "
++		       "in.copy %d\n", tcp_conn->in.hdr_offset, copylen,
++		       tcp_conn->in.offset, tcp_conn->in.copy);
+ 
+-static inline void
+-iscsi_segment_init_linear(struct iscsi_segment *segment, void *data,
+-			  size_t size, iscsi_segment_done_fn_t *done,
+-			  struct hash_desc *hash)
+-{
+-	__iscsi_segment_init(segment, size, done, hash);
+-	segment->data = data;
+-	segment->size = size;
+-}
+-
+-static inline int
+-iscsi_segment_seek_sg(struct iscsi_segment *segment,
+-		      struct scatterlist *sg_list, unsigned int sg_count,
+-		      unsigned int offset, size_t size,
+-		      iscsi_segment_done_fn_t *done, struct hash_desc *hash)
+-{
+-	struct scatterlist *sg;
+-	unsigned int i;
+-
+-	debug_scsi("iscsi_segment_seek_sg offset %u size %llu\n",
+-		  offset, size);
+-	__iscsi_segment_init(segment, size, done, hash);
+-	for_each_sg(sg_list, sg, sg_count, i) {
+-		debug_scsi("sg %d, len %u offset %u\n", i, sg->length,
+-			   sg->offset);
+-		if (offset < sg->length) {
+-			iscsi_tcp_segment_init_sg(segment, sg, offset);
+-			return 0;
++		tcp_conn->in.offset += copylen;
++		tcp_conn->in.copy -= copylen;
++		if (copylen < hdr_remains)  {
++			tcp_conn->in_progress = IN_PROGRESS_HEADER_GATHER;
++			tcp_conn->in.hdr_offset += copylen;
++		        return -EAGAIN;
+ 		}
+-		offset -= sg->length;
++		tcp_conn->in.hdr = &tcp_conn->hdr;
++		tcp_conn->discontiguous_hdr_cnt++;
++	        tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 	}
+ 
+-	return ISCSI_ERR_DATA_OFFSET;
+-}
+-
+-/**
+- * iscsi_tcp_hdr_recv_prep - prep segment for hdr reception
+- * @tcp_conn: iscsi connection to prep for
+- *
+- * This function always passes NULL for the hash argument, because when this
+- * function is called we do not yet know the final size of the header and want
+- * to delay the digest processing until we know that.
+- */
+-static void
+-iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn)
+-{
+-	debug_tcp("iscsi_tcp_hdr_recv_prep(%p%s)\n", tcp_conn,
+-		  tcp_conn->iscsi_conn->hdrdgst_en ? ", digest enabled" : "");
+-	iscsi_segment_init_linear(&tcp_conn->in.segment,
+-				tcp_conn->in.hdr_buf, sizeof(struct iscsi_hdr),
+-				iscsi_tcp_hdr_recv_done, NULL);
+-}
+-
+-/*
+- * Handle incoming reply to any other type of command
+- */
+-static int
+-iscsi_tcp_data_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-			 struct iscsi_segment *segment)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	int rc = 0;
+-
+-	if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-		return ISCSI_ERR_DATA_DGST;
+-
+-	rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr,
+-			conn->data, tcp_conn->in.datalen);
+-	if (rc)
+-		return rc;
+-
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+ 	return 0;
+ }
+ 
+-static void
+-iscsi_tcp_data_recv_prep(struct iscsi_tcp_conn *tcp_conn)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct hash_desc *rx_hash = NULL;
+-
+-	if (conn->datadgst_en)
+-		rx_hash = &tcp_conn->rx_hash;
+-
+-	iscsi_segment_init_linear(&tcp_conn->in.segment,
+-				conn->data, tcp_conn->in.datalen,
+-				iscsi_tcp_data_recv_done, rx_hash);
+-}
+-
+ /*
+  * must be called with session lock
+  */
+@@ -498,6 +186,7 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct iscsi_r2t_info *r2t;
++	struct scsi_cmnd *sc;
+ 
+ 	/* flush ctask's r2t queues */
+ 	while (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*))) {
+@@ -506,12 +195,12 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 		debug_scsi("iscsi_tcp_cleanup_ctask pending r2t dropped\n");
+ 	}
+ 
+-	r2t = tcp_ctask->r2t;
+-	if (r2t != NULL) {
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
+-		tcp_ctask->r2t = NULL;
+-	}
++	sc = ctask->sc;
++	if (unlikely(!sc))
++		return;
++
++	tcp_ctask->xmstate = XMSTATE_IDLE;
++	tcp_ctask->r2t = NULL;
+ }
+ 
+ /**
+@@ -522,49 +211,52 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ static int
+ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
++	int rc;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 	struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)tcp_conn->in.hdr;
+ 	struct iscsi_session *session = conn->session;
+-	struct scsi_cmnd *sc = ctask->sc;
+ 	int datasn = be32_to_cpu(rhdr->datasn);
+-	unsigned total_in_length = scsi_in(sc)->length;
+ 
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc)
++		return rc;
++	/*
++	 * setup Data-In byte counter (gets decremented..)
++	 */
++	ctask->data_count = tcp_conn->in.datalen;
++
+ 	if (tcp_conn->in.datalen == 0)
+ 		return 0;
+ 
+-	if (tcp_ctask->exp_datasn != datasn) {
+-		debug_tcp("%s: ctask->exp_datasn(%d) != rhdr->datasn(%d)\n",
+-		          __FUNCTION__, tcp_ctask->exp_datasn, datasn);
++	if (ctask->datasn != datasn)
+ 		return ISCSI_ERR_DATASN;
+-	}
+ 
+-	tcp_ctask->exp_datasn++;
++	ctask->datasn++;
+ 
+ 	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
+-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > total_in_length) {
+-		debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n",
+-		          __FUNCTION__, tcp_ctask->data_offset,
+-		          tcp_conn->in.datalen, total_in_length);
++	if (tcp_ctask->data_offset + tcp_conn->in.datalen > ctask->total_length)
+ 		return ISCSI_ERR_DATA_OFFSET;
+-	}
+ 
+ 	if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) {
+-		sc->result = (DID_OK << 16) | rhdr->cmd_status;
++		struct scsi_cmnd *sc = ctask->sc;
++
+ 		conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+-		if (rhdr->flags & (ISCSI_FLAG_DATA_UNDERFLOW |
+-		                   ISCSI_FLAG_DATA_OVERFLOW)) {
++		if (rhdr->flags & ISCSI_FLAG_DATA_UNDERFLOW) {
+ 			int res_count = be32_to_cpu(rhdr->residual_count);
+ 
+ 			if (res_count > 0 &&
+-			    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-			     res_count <= total_in_length))
+-				scsi_in(sc)->resid = res_count;
+-			else
++			    res_count <= sc->request_bufflen) {
++				sc->resid = res_count;
++				sc->result = (DID_OK << 16) | rhdr->cmd_status;
++			} else
+ 				sc->result = (DID_BAD_TARGET << 16) |
+ 					rhdr->cmd_status;
+-		}
++		} else if (rhdr->flags & ISCSI_FLAG_DATA_OVERFLOW) {
++			sc->resid = be32_to_cpu(rhdr->residual_count);
++			sc->result = (DID_OK << 16) | rhdr->cmd_status;
++		} else
++			sc->result = (DID_OK << 16) | rhdr->cmd_status;
+ 	}
+ 
+ 	conn->datain_pdus_cnt++;
+@@ -588,6 +280,7 @@ iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 			struct iscsi_r2t_info *r2t)
+ {
+ 	struct iscsi_data *hdr;
++	struct scsi_cmnd *sc = ctask->sc;
+ 
+ 	hdr = &r2t->dtask.hdr;
+ 	memset(hdr, 0, sizeof(struct iscsi_data));
+@@ -611,6 +304,43 @@ iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 	conn->dataout_pdus_cnt++;
+ 
+ 	r2t->sent = 0;
++
++	iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr,
++			   sizeof(struct iscsi_hdr));
++
++	if (sc->use_sg) {
++		int i, sg_count = 0;
++		struct scatterlist *sg = sc->request_buffer;
++
++		r2t->sg = NULL;
++		for (i = 0; i < sc->use_sg; i++, sg += 1) {
++			/* FIXME: prefetch ? */
++			if (sg_count + sg->length > r2t->data_offset) {
++				int page_offset;
++
++				/* sg page found! */
++
++				/* offset within this page */
++				page_offset = r2t->data_offset - sg_count;
++
++				/* fill in this buffer */
++				iscsi_buf_init_sg(&r2t->sendbuf, sg);
++				r2t->sendbuf.sg.offset += page_offset;
++				r2t->sendbuf.sg.length -= page_offset;
++
++				/* xmit logic will continue with next one */
++				r2t->sg = sg + 1;
++				break;
++			}
++			sg_count += sg->length;
++		}
++		BUG_ON(r2t->sg == NULL);
++	} else {
++		iscsi_buf_init_iov(&r2t->sendbuf,
++			    (char*)sc->request_buffer + r2t->data_offset,
++			    r2t->data_count);
++		r2t->sg = NULL;
++	}
+ }
+ 
+ /**
+@@ -630,25 +360,27 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	int rc;
+ 
+ 	if (tcp_conn->in.datalen) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2t with datalen %d\n",
+-				  tcp_conn->in.datalen);
++		printk(KERN_ERR "iscsi_tcp: invalid R2t with datalen %d\n",
++		       tcp_conn->in.datalen);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+-	if (tcp_ctask->exp_datasn != r2tsn){
+-		debug_tcp("%s: ctask->exp_datasn(%d) != rhdr->r2tsn(%d)\n",
+-		          __FUNCTION__, tcp_ctask->exp_datasn, r2tsn);
++	if (tcp_ctask->exp_r2tsn && tcp_ctask->exp_r2tsn != r2tsn)
+ 		return ISCSI_ERR_R2TSN;
+-	}
+ 
+-	/* fill-in new R2T associated with the task */
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc)
++		return rc;
+ 
+-	if (!ctask->sc || session->state != ISCSI_STATE_LOGGED_IN) {
+-		iscsi_conn_printk(KERN_INFO, conn,
+-				  "dropping R2T itt %d in recovery.\n",
+-				  ctask->itt);
++	/* FIXME: use R2TSN to detect missing R2T */
++
++	/* fill-in new R2T associated with the task */
++	spin_lock(&session->lock);
++	if (!ctask->sc || ctask->mtask ||
++	     session->state != ISCSI_STATE_LOGGED_IN) {
++		printk(KERN_INFO "iscsi_tcp: dropping R2T itt %d in "
++		       "recovery...\n", ctask->itt);
++		spin_unlock(&session->lock);
+ 		return 0;
+ 	}
+ 
+@@ -658,10 +390,8 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 	r2t->exp_statsn = rhdr->statsn;
+ 	r2t->data_length = be32_to_cpu(rhdr->data_length);
+ 	if (r2t->data_length == 0) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2T with zero data len\n");
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
++		printk(KERN_ERR "iscsi_tcp: invalid R2T with zero data len\n");
++		spin_unlock(&session->lock);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+@@ -671,13 +401,11 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 			    r2t->data_length, session->max_burst);
+ 
+ 	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+-	if (r2t->data_offset + r2t->data_length > scsi_out(ctask->sc)->length) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid R2T with data len %u at offset %u "
+-				  "and total length %d\n", r2t->data_length,
+-				  r2t->data_offset, scsi_out(ctask->sc)->length);
+-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
+-			    sizeof(void*));
++	if (r2t->data_offset + r2t->data_length > ctask->total_length) {
++		spin_unlock(&session->lock);
++		printk(KERN_ERR "iscsi_tcp: invalid R2T with data len %u at "
++		       "offset %u and total length %d\n", r2t->data_length,
++		       r2t->data_offset, ctask->total_length);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
+ 
+@@ -686,134 +414,108 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ 
+ 	iscsi_solicit_data_init(conn, ctask, r2t);
+ 
+-	tcp_ctask->exp_datasn = r2tsn + 1;
++	tcp_ctask->exp_r2tsn = r2tsn + 1;
+ 	__kfifo_put(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*));
+-	conn->r2t_pdus_cnt++;
++	tcp_ctask->xmstate |= XMSTATE_SOL_HDR;
++	list_move_tail(&ctask->running, &conn->xmitqueue);
+ 
+-	iscsi_requeue_ctask(ctask);
+-	return 0;
+-}
+-
+-/*
+- * Handle incoming reply to DataIn command
+- */
+-static int
+-iscsi_tcp_process_data_in(struct iscsi_tcp_conn *tcp_conn,
+-			  struct iscsi_segment *segment)
+-{
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct iscsi_hdr *hdr = tcp_conn->in.hdr;
+-	int rc;
+-
+-	if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-		return ISCSI_ERR_DATA_DGST;
+-
+-	/* check for non-exceptional status */
+-	if (hdr->flags & ISCSI_FLAG_DATA_STATUS) {
+-		rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
+-		if (rc)
+-			return rc;
+-	}
++	scsi_queue_work(session->host, &conn->xmitwork);
++	conn->r2t_pdus_cnt++;
++	spin_unlock(&session->lock);
+ 
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+ 	return 0;
+ }
+ 
+-/**
+- * iscsi_tcp_hdr_dissect - process PDU header
+- * @conn: iSCSI connection
+- * @hdr: PDU header
+- *
+- * This function analyzes the header of the PDU received,
+- * and performs several sanity checks. If the PDU is accompanied
+- * by data, the receive buffer is set up to copy the incoming data
+- * to the correct location.
+- */
+ static int
+-iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
++iscsi_tcp_hdr_recv(struct iscsi_conn *conn)
+ {
+ 	int rc = 0, opcode, ahslen;
++	struct iscsi_hdr *hdr;
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_cmd_task *ctask;
+-	uint32_t itt;
++	uint32_t cdgst, rdgst = 0, itt;
++
++	hdr = tcp_conn->in.hdr;
+ 
+ 	/* verify PDU length */
+ 	tcp_conn->in.datalen = ntoh24(hdr->dlength);
+ 	if (tcp_conn->in.datalen > conn->max_recv_dlength) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "iscsi_tcp: datalen %d > %d\n",
+-				  tcp_conn->in.datalen, conn->max_recv_dlength);
++		printk(KERN_ERR "iscsi_tcp: datalen %d > %d\n",
++		       tcp_conn->in.datalen, conn->max_recv_dlength);
+ 		return ISCSI_ERR_DATALEN;
+ 	}
++	tcp_conn->data_copied = 0;
+ 
+-	/* Additional header segments. So far, we don't
+-	 * process additional headers.
+-	 */
++	/* read AHS */
+ 	ahslen = hdr->hlength << 2;
++	tcp_conn->in.offset += ahslen;
++	tcp_conn->in.copy -= ahslen;
++	if (tcp_conn->in.copy < 0) {
++		printk(KERN_ERR "iscsi_tcp: can't handle AHS with length "
++		       "%d bytes\n", ahslen);
++		return ISCSI_ERR_AHSLEN;
++	}
++
++	/* calculate read padding */
++	tcp_conn->in.padding = tcp_conn->in.datalen & (ISCSI_PAD_LEN-1);
++	if (tcp_conn->in.padding) {
++		tcp_conn->in.padding = ISCSI_PAD_LEN - tcp_conn->in.padding;
++		debug_scsi("read padding %d bytes\n", tcp_conn->in.padding);
++	}
++
++	if (conn->hdrdgst_en) {
++		struct scatterlist sg;
++
++		sg_init_one(&sg, (u8 *)hdr,
++			    sizeof(struct iscsi_hdr) + ahslen);
++		crypto_hash_digest(&tcp_conn->rx_hash, &sg, sg.length,
++				   (u8 *)&cdgst);
++		rdgst = *(uint32_t*)((char*)hdr + sizeof(struct iscsi_hdr) +
++				     ahslen);
++		if (cdgst != rdgst) {
++			printk(KERN_ERR "iscsi_tcp: hdrdgst error "
++			       "recv 0x%x calc 0x%x\n", rdgst, cdgst);
++			return ISCSI_ERR_HDR_DGST;
++		}
++	}
+ 
+ 	opcode = hdr->opcode & ISCSI_OPCODE_MASK;
+ 	/* verify itt (itt encoding: age+cid+itt) */
+ 	rc = iscsi_verify_itt(conn, hdr, &itt);
+-	if (rc)
++	if (rc == ISCSI_ERR_NO_SCSI_CMD) {
++		tcp_conn->in.datalen = 0; /* force drop */
++		return 0;
++	} else if (rc)
+ 		return rc;
+ 
+-	debug_tcp("opcode 0x%x ahslen %d datalen %d\n",
+-		  opcode, ahslen, tcp_conn->in.datalen);
++	debug_tcp("opcode 0x%x offset %d copy %d ahslen %d datalen %d\n",
++		  opcode, tcp_conn->in.offset, tcp_conn->in.copy,
++		  ahslen, tcp_conn->in.datalen);
+ 
+ 	switch(opcode) {
+ 	case ISCSI_OP_SCSI_DATA_IN:
+-		ctask = session->cmds[itt];
+-		spin_lock(&conn->session->lock);
+-		rc = iscsi_data_rsp(conn, ctask);
+-		spin_unlock(&conn->session->lock);
++		tcp_conn->in.ctask = session->cmds[itt];
++		rc = iscsi_data_rsp(conn, tcp_conn->in.ctask);
+ 		if (rc)
+ 			return rc;
+-		if (tcp_conn->in.datalen) {
+-			struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-			struct hash_desc *rx_hash = NULL;
+-			struct scsi_data_buffer *sdb = scsi_in(ctask->sc);
+-
+-			/*
+-			 * Setup copy of Data-In into the Scsi_Cmnd
+-			 * Scatterlist case:
+-			 * We set up the iscsi_segment to point to the next
+-			 * scatterlist entry to copy to. As we go along,
+-			 * we move on to the next scatterlist entry and
+-			 * update the digest per-entry.
+-			 */
+-			if (conn->datadgst_en)
+-				rx_hash = &tcp_conn->rx_hash;
+-
+-			debug_tcp("iscsi_tcp_begin_data_in(%p, offset=%d, "
+-				  "datalen=%d)\n", tcp_conn,
+-				  tcp_ctask->data_offset,
+-				  tcp_conn->in.datalen);
+-			return iscsi_segment_seek_sg(&tcp_conn->in.segment,
+-						     sdb->table.sgl,
+-						     sdb->table.nents,
+-						     tcp_ctask->data_offset,
+-						     tcp_conn->in.datalen,
+-						     iscsi_tcp_process_data_in,
+-						     rx_hash);
+-		}
+ 		/* fall through */
+ 	case ISCSI_OP_SCSI_CMD_RSP:
+-		if (tcp_conn->in.datalen) {
+-			iscsi_tcp_data_recv_prep(tcp_conn);
+-			return 0;
+-		}
+-		rc = iscsi_complete_pdu(conn, hdr, NULL, 0);
++		tcp_conn->in.ctask = session->cmds[itt];
++		if (tcp_conn->in.datalen)
++			goto copy_hdr;
++
++		spin_lock(&session->lock);
++		rc = __iscsi_complete_pdu(conn, hdr, NULL, 0);
++		spin_unlock(&session->lock);
+ 		break;
+ 	case ISCSI_OP_R2T:
+-		ctask = session->cmds[itt];
++		tcp_conn->in.ctask = session->cmds[itt];
+ 		if (ahslen)
+ 			rc = ISCSI_ERR_AHSLEN;
+-		else if (ctask->sc->sc_data_direction == DMA_TO_DEVICE) {
+-			spin_lock(&session->lock);
+-			rc = iscsi_r2t_rsp(conn, ctask);
+-			spin_unlock(&session->lock);
+-		} else
++		else if (tcp_conn->in.ctask->sc->sc_data_direction ==
++								DMA_TO_DEVICE)
++			rc = iscsi_r2t_rsp(conn, tcp_conn->in.ctask);
++		else
+ 			rc = ISCSI_ERR_PROTO;
+ 		break;
+ 	case ISCSI_OP_LOGIN_RSP:
+@@ -825,24 +527,18 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		 * than 8K, but there are no targets that currently do this.
+ 		 * For now we fail until we find a vendor that needs it
+ 		 */
+-		if (ISCSI_DEF_MAX_RECV_SEG_LEN < tcp_conn->in.datalen) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "iscsi_tcp: received buffer of "
+-					  "len %u but conn buffer is only %u "
+-					  "(opcode %0x)\n",
+-					  tcp_conn->in.datalen,
+-					  ISCSI_DEF_MAX_RECV_SEG_LEN, opcode);
++		if (ISCSI_DEF_MAX_RECV_SEG_LEN <
++		    tcp_conn->in.datalen) {
++			printk(KERN_ERR "iscsi_tcp: received buffer of len %u "
++			      "but conn buffer is only %u (opcode %0x)\n",
++			      tcp_conn->in.datalen,
++			      ISCSI_DEF_MAX_RECV_SEG_LEN, opcode);
+ 			rc = ISCSI_ERR_PROTO;
+ 			break;
+ 		}
+ 
+-		/* If there's data coming in with the response,
+-		 * receive it to the connection's buffer.
+-		 */
+-		if (tcp_conn->in.datalen) {
+-			iscsi_tcp_data_recv_prep(tcp_conn);
+-			return 0;
+-		}
++		if (tcp_conn->in.datalen)
++			goto copy_hdr;
+ 	/* fall through */
+ 	case ISCSI_OP_LOGOUT_RSP:
+ 	case ISCSI_OP_NOOP_IN:
+@@ -854,161 +550,457 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 		break;
+ 	}
+ 
+-	if (rc == 0) {
+-		/* Anything that comes with data should have
+-		 * been handled above. */
+-		if (tcp_conn->in.datalen)
+-			return ISCSI_ERR_PROTO;
+-		iscsi_tcp_hdr_recv_prep(tcp_conn);
++	return rc;
++
++copy_hdr:
++	/*
++	 * if we did zero copy for the header but we will need multiple
++	 * skbs to complete the command then we have to copy the header
++	 * for later use
++	 */
++	if (tcp_conn->in.zero_copy_hdr && tcp_conn->in.copy <=
++	   (tcp_conn->in.datalen + tcp_conn->in.padding +
++	    (conn->datadgst_en ? 4 : 0))) {
++		debug_tcp("Copying header for later use. in.copy %d in.datalen"
++			  " %d\n", tcp_conn->in.copy, tcp_conn->in.datalen);
++		memcpy(&tcp_conn->hdr, tcp_conn->in.hdr,
++		       sizeof(struct iscsi_hdr));
++		tcp_conn->in.hdr = &tcp_conn->hdr;
++		tcp_conn->in.zero_copy_hdr = 0;
+ 	}
++	return 0;
++}
+ 
+-	return rc;
++/**
++ * iscsi_ctask_copy - copy skb bits to the destanation cmd task
++ * @conn: iscsi tcp connection
++ * @ctask: scsi command task
++ * @buf: buffer to copy to
++ * @buf_size: size of buffer
++ * @offset: offset within the buffer
++ *
++ * Notes:
++ *	The function calls skb_copy_bits() and updates per-connection and
++ *	per-cmd byte counters.
++ *
++ *	Read counters (in bytes):
++ *
++ *	conn->in.offset		offset within in progress SKB
++ *	conn->in.copy		left to copy from in progress SKB
++ *				including padding
++ *	conn->in.copied		copied already from in progress SKB
++ *	conn->data_copied	copied already from in progress buffer
++ *	ctask->sent		total bytes sent up to the MidLayer
++ *	ctask->data_count	left to copy from in progress Data-In
++ *	buf_left		left to copy from in progress buffer
++ **/
++static inline int
++iscsi_ctask_copy(struct iscsi_tcp_conn *tcp_conn, struct iscsi_cmd_task *ctask,
++		void *buf, int buf_size, int offset)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int buf_left = buf_size - (tcp_conn->data_copied + offset);
++	int size = min(tcp_conn->in.copy, buf_left);
++	int rc;
++
++	size = min(size, ctask->data_count);
++
++	debug_tcp("ctask_copy %d bytes at offset %d copied %d\n",
++	       size, tcp_conn->in.offset, tcp_conn->in.copied);
++
++	BUG_ON(size <= 0);
++	BUG_ON(tcp_ctask->sent + size > ctask->total_length);
++
++	rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
++			   (char*)buf + (offset + tcp_conn->data_copied), size);
++	/* must fit into skb->len */
++	BUG_ON(rc);
++
++	tcp_conn->in.offset += size;
++	tcp_conn->in.copy -= size;
++	tcp_conn->in.copied += size;
++	tcp_conn->data_copied += size;
++	tcp_ctask->sent += size;
++	ctask->data_count -= size;
++
++	BUG_ON(tcp_conn->in.copy < 0);
++	BUG_ON(ctask->data_count < 0);
++
++	if (buf_size != (tcp_conn->data_copied + offset)) {
++		if (!ctask->data_count) {
++			BUG_ON(buf_size - tcp_conn->data_copied < 0);
++			/* done with this PDU */
++			return buf_size - tcp_conn->data_copied;
++		}
++		return -EAGAIN;
++	}
++
++	/* done with this buffer or with both - PDU and buffer */
++	tcp_conn->data_copied = 0;
++	return 0;
+ }
+ 
+ /**
+- * iscsi_tcp_hdr_recv_done - process PDU header
++ * iscsi_tcp_copy - copy skb bits to the destanation buffer
++ * @conn: iscsi tcp connection
+  *
+- * This is the callback invoked when the PDU header has
+- * been received. If the header is followed by additional
+- * header segments, we go back for more data.
+- */
+-static int
+-iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn,
+-			struct iscsi_segment *segment)
++ * Notes:
++ *	The function calls skb_copy_bits() and updates per-connection
++ *	byte counters.
++ **/
++static inline int
++iscsi_tcp_copy(struct iscsi_conn *conn, int buf_size)
+ {
+-	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+-	struct iscsi_hdr *hdr;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int buf_left = buf_size - tcp_conn->data_copied;
++	int size = min(tcp_conn->in.copy, buf_left);
++	int rc;
++
++	debug_tcp("tcp_copy %d bytes at offset %d copied %d\n",
++	       size, tcp_conn->in.offset, tcp_conn->data_copied);
++	BUG_ON(size <= 0);
++
++	rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
++			   (char*)conn->data + tcp_conn->data_copied, size);
++	BUG_ON(rc);
++
++	tcp_conn->in.offset += size;
++	tcp_conn->in.copy -= size;
++	tcp_conn->in.copied += size;
++	tcp_conn->data_copied += size;
++
++	if (buf_size != tcp_conn->data_copied)
++		return -EAGAIN;
++
++	return 0;
++}
++
++static inline void
++partial_sg_digest_update(struct hash_desc *desc, struct scatterlist *sg,
++			 int offset, int length)
++{
++	struct scatterlist temp;
++
++	memcpy(&temp, sg, sizeof(struct scatterlist));
++	temp.offset = offset;
++	temp.length = length;
++	crypto_hash_update(desc, &temp, length);
++}
++
++static void
++iscsi_recv_digest_update(struct iscsi_tcp_conn *tcp_conn, char* buf, int len)
++{
++	struct scatterlist tmp;
++
++	sg_init_one(&tmp, buf, len);
++	crypto_hash_update(&tcp_conn->rx_hash, &tmp, len);
++}
+ 
+-	/* Check if there are additional header segments
+-	 * *prior* to computing the digest, because we
+-	 * may need to go back to the caller for more.
++static int iscsi_scsi_data_in(struct iscsi_conn *conn)
++{
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	struct iscsi_cmd_task *ctask = tcp_conn->in.ctask;
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct scsi_cmnd *sc = ctask->sc;
++	struct scatterlist *sg;
++	int i, offset, rc = 0;
++
++	BUG_ON((void*)ctask != sc->SCp.ptr);
++
++	/*
++	 * copying Data-In into the Scsi_Cmnd
+ 	 */
+-	hdr = (struct iscsi_hdr *) tcp_conn->in.hdr_buf;
+-	if (segment->copied == sizeof(struct iscsi_hdr) && hdr->hlength) {
+-		/* Bump the header length - the caller will
+-		 * just loop around and get the AHS for us, and
+-		 * call again. */
+-		unsigned int ahslen = hdr->hlength << 2;
+-
+-		/* Make sure we don't overflow */
+-		if (sizeof(*hdr) + ahslen > sizeof(tcp_conn->in.hdr_buf))
+-			return ISCSI_ERR_AHSLEN;
+-
+-		segment->total_size += ahslen;
+-		segment->size += ahslen;
+-		return 0;
++	if (!sc->use_sg) {
++		i = ctask->data_count;
++		rc = iscsi_ctask_copy(tcp_conn, ctask, sc->request_buffer,
++				      sc->request_bufflen,
++				      tcp_ctask->data_offset);
++		if (rc == -EAGAIN)
++			return rc;
++		if (conn->datadgst_en)
++			iscsi_recv_digest_update(tcp_conn, sc->request_buffer,
++						 i);
++		rc = 0;
++		goto done;
+ 	}
+ 
+-	/* We're done processing the header. See if we're doing
+-	 * header digests; if so, set up the recv_digest buffer
+-	 * and go back for more. */
+-	if (conn->hdrdgst_en) {
+-		if (segment->digest_len == 0) {
+-			iscsi_tcp_segment_splice_digest(segment,
+-							segment->recv_digest);
+-			return 0;
++	offset = tcp_ctask->data_offset;
++	sg = sc->request_buffer;
++
++	if (tcp_ctask->data_offset)
++		for (i = 0; i < tcp_ctask->sg_count; i++)
++			offset -= sg[i].length;
++	/* we've passed through partial sg*/
++	if (offset < 0)
++		offset = 0;
++
++	for (i = tcp_ctask->sg_count; i < sc->use_sg; i++) {
++		char *dest;
++
++		dest = kmap_atomic(sg[i].page, KM_SOFTIRQ0);
++		rc = iscsi_ctask_copy(tcp_conn, ctask, dest + sg[i].offset,
++				      sg[i].length, offset);
++		kunmap_atomic(dest, KM_SOFTIRQ0);
++		if (rc == -EAGAIN)
++			/* continue with the next SKB/PDU */
++			return rc;
++		if (!rc) {
++			if (conn->datadgst_en) {
++				if (!offset)
++					crypto_hash_update(
++							&tcp_conn->rx_hash,
++							&sg[i], sg[i].length);
++				else
++					partial_sg_digest_update(
++							&tcp_conn->rx_hash,
++							&sg[i],
++							sg[i].offset + offset,
++							sg[i].length - offset);
++			}
++			offset = 0;
++			tcp_ctask->sg_count++;
+ 		}
+-		iscsi_tcp_dgst_header(&tcp_conn->rx_hash, hdr,
+-				      segment->total_copied - ISCSI_DIGEST_SIZE,
+-				      segment->digest);
+ 
+-		if (!iscsi_tcp_dgst_verify(tcp_conn, segment))
+-			return ISCSI_ERR_HDR_DGST;
++		if (!ctask->data_count) {
++			if (rc && conn->datadgst_en)
++				/*
++				 * data-in is complete, but buffer not...
++				 */
++				partial_sg_digest_update(&tcp_conn->rx_hash,
++							 &sg[i],
++							 sg[i].offset,
++							 sg[i].length-rc);
++			rc = 0;
++			break;
++		}
++
++		if (!tcp_conn->in.copy)
++			return -EAGAIN;
++	}
++	BUG_ON(ctask->data_count);
++
++done:
++	/* check for non-exceptional status */
++	if (tcp_conn->in.hdr->flags & ISCSI_FLAG_DATA_STATUS) {
++		debug_scsi("done [sc %lx res %d itt 0x%x flags 0x%x]\n",
++			   (long)sc, sc->result, ctask->itt,
++			   tcp_conn->in.hdr->flags);
++		spin_lock(&conn->session->lock);
++		__iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
++		spin_unlock(&conn->session->lock);
+ 	}
+ 
+-	tcp_conn->in.hdr = hdr;
+-	return iscsi_tcp_hdr_dissect(conn, hdr);
++	return rc;
++}
++
++static int
++iscsi_data_recv(struct iscsi_conn *conn)
++{
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int rc = 0, opcode;
++
++	opcode = tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK;
++	switch (opcode) {
++	case ISCSI_OP_SCSI_DATA_IN:
++		rc = iscsi_scsi_data_in(conn);
++		break;
++	case ISCSI_OP_SCSI_CMD_RSP:
++	case ISCSI_OP_TEXT_RSP:
++	case ISCSI_OP_LOGIN_RSP:
++	case ISCSI_OP_ASYNC_EVENT:
++	case ISCSI_OP_REJECT:
++		/*
++		 * Collect data segment to the connection's data
++		 * placeholder
++		 */
++		if (iscsi_tcp_copy(conn, tcp_conn->in.datalen)) {
++			rc = -EAGAIN;
++			goto exit;
++		}
++
++		rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, conn->data,
++					tcp_conn->in.datalen);
++		if (!rc && conn->datadgst_en && opcode != ISCSI_OP_LOGIN_RSP)
++			iscsi_recv_digest_update(tcp_conn, conn->data,
++			  			tcp_conn->in.datalen);
++		break;
++	default:
++		BUG_ON(1);
++	}
++exit:
++	return rc;
+ }
+ 
+ /**
+- * iscsi_tcp_recv - TCP receive in sendfile fashion
++ * iscsi_tcp_data_recv - TCP receive in sendfile fashion
+  * @rd_desc: read descriptor
+  * @skb: socket buffer
+  * @offset: offset in skb
+  * @len: skb->len - offset
+  **/
+ static int
+-iscsi_tcp_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
+-	       unsigned int offset, size_t len)
++iscsi_tcp_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
++		unsigned int offset, size_t len)
+ {
++	int rc;
+ 	struct iscsi_conn *conn = rd_desc->arg.data;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->in.segment;
+-	struct skb_seq_state seq;
+-	unsigned int consumed = 0;
+-	int rc = 0;
++	int processed;
++	char pad[ISCSI_PAD_LEN];
++	struct scatterlist sg;
+ 
+-	debug_tcp("in %d bytes\n", skb->len - offset);
++	/*
++	 * Save current SKB and its offset in the corresponding
++	 * connection context.
++	 */
++	tcp_conn->in.copy = skb->len - offset;
++	tcp_conn->in.offset = offset;
++	tcp_conn->in.skb = skb;
++	tcp_conn->in.len = tcp_conn->in.copy;
++	BUG_ON(tcp_conn->in.copy <= 0);
++	debug_tcp("in %d bytes\n", tcp_conn->in.copy);
++
++more:
++	tcp_conn->in.copied = 0;
++	rc = 0;
+ 
+ 	if (unlikely(conn->suspend_rx)) {
+ 		debug_tcp("conn %d Rx suspended!\n", conn->id);
+ 		return 0;
+ 	}
+ 
+-	skb_prepare_seq_read(skb, offset, skb->len, &seq);
+-	while (1) {
+-		unsigned int avail;
+-		const u8 *ptr;
++	if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER ||
++	    tcp_conn->in_progress == IN_PROGRESS_HEADER_GATHER) {
++		rc = iscsi_hdr_extract(tcp_conn);
++		if (rc) {
++		       if (rc == -EAGAIN)
++				goto nomore;
++		       else {
++				iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++				return 0;
++		       }
++		}
+ 
+-		avail = skb_seq_read(consumed, &ptr, &seq);
+-		if (avail == 0) {
+-			debug_tcp("no more data avail. Consumed %d\n",
+-				  consumed);
+-			break;
++		/*
++		 * Verify and process incoming PDU header.
++		 */
++		rc = iscsi_tcp_hdr_recv(conn);
++		if (!rc && tcp_conn->in.datalen) {
++			if (conn->datadgst_en)
++				crypto_hash_init(&tcp_conn->rx_hash);
++			tcp_conn->in_progress = IN_PROGRESS_DATA_RECV;
++		} else if (rc) {
++			iscsi_conn_failure(conn, rc);
++			return 0;
+ 		}
+-		BUG_ON(segment->copied >= segment->size);
+-
+-		debug_tcp("skb %p ptr=%p avail=%u\n", skb, ptr, avail);
+-		rc = iscsi_tcp_segment_recv(tcp_conn, segment, ptr, avail);
+-		BUG_ON(rc == 0);
+-		consumed += rc;
+-
+-		if (segment->total_copied >= segment->total_size) {
+-			debug_tcp("segment done\n");
+-			rc = segment->done(tcp_conn, segment);
+-			if (rc != 0) {
+-				skb_abort_seq_read(&seq);
+-				goto error;
+-			}
++	}
+ 
+-			/* The done() functions sets up the
+-			 * next segment. */
++	if (tcp_conn->in_progress == IN_PROGRESS_DDIGEST_RECV) {
++		uint32_t recv_digest;
++
++		debug_tcp("extra data_recv offset %d copy %d\n",
++			  tcp_conn->in.offset, tcp_conn->in.copy);
++		rc = iscsi_tcp_copy(conn, sizeof(uint32_t));
++		if (rc) {
++			if (rc == -EAGAIN)
++				goto again;
++			iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++			return 0;
++		}
++
++		memcpy(&recv_digest, conn->data, sizeof(uint32_t));
++		if (recv_digest != tcp_conn->in.datadgst) {
++			debug_tcp("iscsi_tcp: data digest error!"
++				  "0x%x != 0x%x\n", recv_digest,
++				  tcp_conn->in.datadgst);
++			iscsi_conn_failure(conn, ISCSI_ERR_DATA_DGST);
++			return 0;
++		} else {
++			debug_tcp("iscsi_tcp: data digest match!"
++				  "0x%x == 0x%x\n", recv_digest,
++				  tcp_conn->in.datadgst);
++			tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 		}
+ 	}
+-	skb_abort_seq_read(&seq);
+-	conn->rxdata_octets += consumed;
+-	return consumed;
+ 
+-error:
+-	debug_tcp("Error receiving PDU, errno=%d\n", rc);
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-	return 0;
++	if (tcp_conn->in_progress == IN_PROGRESS_DATA_RECV &&
++	   tcp_conn->in.copy) {
++
++		debug_tcp("data_recv offset %d copy %d\n",
++		       tcp_conn->in.offset, tcp_conn->in.copy);
++
++		rc = iscsi_data_recv(conn);
++		if (rc) {
++			if (rc == -EAGAIN)
++				goto again;
++			iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++			return 0;
++		}
++		tcp_conn->in.copy -= tcp_conn->in.padding;
++		tcp_conn->in.offset += tcp_conn->in.padding;
++		if (conn->datadgst_en) {
++			if (tcp_conn->in.padding) {
++				debug_tcp("padding -> %d\n",
++					  tcp_conn->in.padding);
++				memset(pad, 0, tcp_conn->in.padding);
++				sg_init_one(&sg, pad, tcp_conn->in.padding);
++				crypto_hash_update(&tcp_conn->rx_hash,
++						   &sg, sg.length);
++			}
++			crypto_hash_final(&tcp_conn->rx_hash,
++					  (u8 *) &tcp_conn->in.datadgst);
++			debug_tcp("rx digest 0x%x\n", tcp_conn->in.datadgst);
++			tcp_conn->in_progress = IN_PROGRESS_DDIGEST_RECV;
++			tcp_conn->data_copied = 0;
++		} else
++			tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
++	}
++
++	debug_tcp("f, processed %d from out of %d padding %d\n",
++	       tcp_conn->in.offset - offset, (int)len, tcp_conn->in.padding);
++	BUG_ON(tcp_conn->in.offset - offset > len);
++
++	if (tcp_conn->in.offset - offset != len) {
++		debug_tcp("continue to process %d bytes\n",
++		       (int)len - (tcp_conn->in.offset - offset));
++		goto more;
++	}
++
++nomore:
++	processed = tcp_conn->in.offset - offset;
++	BUG_ON(processed == 0);
++	return processed;
++
++again:
++	processed = tcp_conn->in.offset - offset;
++	debug_tcp("c, processed %d from out of %d rd_desc_cnt %d\n",
++	          processed, (int)len, (int)rd_desc->count);
++	BUG_ON(processed == 0);
++	BUG_ON(processed > len);
++
++	conn->rxdata_octets += processed;
++	return processed;
+ }
+ 
+ static void
+ iscsi_tcp_data_ready(struct sock *sk, int flag)
+ {
+ 	struct iscsi_conn *conn = sk->sk_user_data;
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 	read_descriptor_t rd_desc;
+ 
+ 	read_lock(&sk->sk_callback_lock);
+ 
+ 	/*
+-	 * Use rd_desc to pass 'conn' to iscsi_tcp_recv.
++	 * Use rd_desc to pass 'conn' to iscsi_tcp_data_recv.
+ 	 * We set count to 1 because we want the network layer to
+-	 * hand us all the skbs that are available. iscsi_tcp_recv
++	 * hand us all the skbs that are available. iscsi_tcp_data_recv
+ 	 * handled pdus that cross buffers or pdus that still need data.
+ 	 */
+ 	rd_desc.arg.data = conn;
+ 	rd_desc.count = 1;
+-	tcp_read_sock(sk, &rd_desc, iscsi_tcp_recv);
++	tcp_read_sock(sk, &rd_desc, iscsi_tcp_data_recv);
+ 
+ 	read_unlock(&sk->sk_callback_lock);
+-
+-	/* If we had to (atomically) map a highmem page,
+-	 * unmap it now. */
+-	iscsi_tcp_segment_unmap(&tcp_conn->in.segment);
+ }
+ 
+ static void
+@@ -1088,173 +1080,121 @@ iscsi_conn_restore_callbacks(struct iscsi_tcp_conn *tcp_conn)
+ }
+ 
+ /**
+- * iscsi_xmit - TCP transmit
+- **/
+-static int
+-iscsi_xmit(struct iscsi_conn *conn)
++ * iscsi_send - generic send routine
++ * @sk: kernel's socket
++ * @buf: buffer to write from
++ * @size: actual size to write
++ * @flags: socket's flags
++ */
++static inline int
++iscsi_send(struct iscsi_conn *conn, struct iscsi_buf *buf, int size, int flags)
+ {
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->out.segment;
+-	unsigned int consumed = 0;
+-	int rc = 0;
+-
+-	while (1) {
+-		rc = iscsi_tcp_xmit_segment(tcp_conn, segment);
+-		if (rc < 0)
+-			goto error;
+-		if (rc == 0)
+-			break;
+-
+-		consumed += rc;
++	struct socket *sk = tcp_conn->sock;
++	int offset = buf->sg.offset + buf->sent, res;
+ 
+-		if (segment->total_copied >= segment->total_size) {
+-			if (segment->done != NULL) {
+-				rc = segment->done(tcp_conn, segment);
+-				if (rc < 0)
+-					goto error;
+-			}
+-		}
++	/*
++	 * if we got use_sg=0 or are sending something we kmallocd
++	 * then we did not have to do kmap (kmap returns page_address)
++	 *
++	 * if we got use_sg > 0, but had to drop down, we do not
++	 * set clustering so this should only happen for that
++	 * slab case.
++	 */
++	if (buf->use_sendmsg)
++		res = sock_no_sendpage(sk, buf->sg.page, offset, size, flags);
++	else
++		res = tcp_conn->sendpage(sk, buf->sg.page, offset, size, flags);
++
++	if (res >= 0) {
++		conn->txdata_octets += res;
++		buf->sent += res;
++		return res;
+ 	}
+ 
+-	debug_tcp("xmit %d bytes\n", consumed);
+-
+-	conn->txdata_octets += consumed;
+-	return consumed;
+-
+-error:
+-	/* Transmit error. We could initiate error recovery
+-	 * here. */
+-	debug_tcp("Error sending PDU, errno=%d\n", rc);
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-	return rc;
++	tcp_conn->sendpage_failures_cnt++;
++	if (res == -EAGAIN)
++		res = -ENOBUFS;
++	else
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	return res;
+ }
+ 
+ /**
+- * iscsi_tcp_xmit_qlen - return the number of bytes queued for xmit
+- */
+-static inline int
+-iscsi_tcp_xmit_qlen(struct iscsi_conn *conn)
+-{
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct iscsi_segment *segment = &tcp_conn->out.segment;
+-
+-	return segment->total_copied - segment->total_size;
+-}
+-
++ * iscsi_sendhdr - send PDU Header via tcp_sendpage()
++ * @conn: iscsi connection
++ * @buf: buffer to write from
++ * @datalen: lenght of data to be sent after the header
++ *
++ * Notes:
++ *	(Tx, Fast Path)
++ **/
+ static inline int
+-iscsi_tcp_flush(struct iscsi_conn *conn)
++iscsi_sendhdr(struct iscsi_conn *conn, struct iscsi_buf *buf, int datalen)
+ {
+-	int rc;
+-
+-	while (iscsi_tcp_xmit_qlen(conn)) {
+-		rc = iscsi_xmit(conn);
+-		if (rc == 0)
++	int flags = 0; /* MSG_DONTWAIT; */
++	int res, size;
++
++	size = buf->sg.length - buf->sent;
++	BUG_ON(buf->sent + size > buf->sg.length);
++	if (buf->sent + size != buf->sg.length || datalen)
++		flags |= MSG_MORE;
++
++	res = iscsi_send(conn, buf, size, flags);
++	debug_tcp("sendhdr %d bytes, sent %d res %d\n", size, buf->sent, res);
++	if (res >= 0) {
++		if (size != res)
+ 			return -EAGAIN;
+-		if (rc < 0)
+-			return rc;
++		return 0;
+ 	}
+ 
+-	return 0;
+-}
+-
+-/*
+- * This is called when we're done sending the header.
+- * Simply copy the data_segment to the send segment, and return.
+- */
+-static int
+-iscsi_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn,
+-			struct iscsi_segment *segment)
+-{
+-	tcp_conn->out.segment = tcp_conn->out.data_segment;
+-	debug_tcp("Header done. Next segment size %u total_size %u\n",
+-		  tcp_conn->out.segment.size, tcp_conn->out.segment.total_size);
+-	return 0;
++	return res;
+ }
+ 
+-static void
+-iscsi_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen)
++/**
++ * iscsi_sendpage - send one page of iSCSI Data-Out.
++ * @conn: iscsi connection
++ * @buf: buffer to write from
++ * @count: remaining data
++ * @sent: number of bytes sent
++ *
++ * Notes:
++ *	(Tx, Fast Path)
++ **/
++static inline int
++iscsi_sendpage(struct iscsi_conn *conn, struct iscsi_buf *buf,
++	       int *count, int *sent)
+ {
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-
+-	debug_tcp("%s(%p%s)\n", __FUNCTION__, tcp_conn,
+-			conn->hdrdgst_en? ", digest enabled" : "");
+-
+-	/* Clear the data segment - needs to be filled in by the
+-	 * caller using iscsi_tcp_send_data_prep() */
+-	memset(&tcp_conn->out.data_segment, 0, sizeof(struct iscsi_segment));
+-
+-	/* If header digest is enabled, compute the CRC and
+-	 * place the digest into the same buffer. We make
+-	 * sure that both iscsi_tcp_ctask and mtask have
+-	 * sufficient room.
+-	 */
+-	if (conn->hdrdgst_en) {
+-		iscsi_tcp_dgst_header(&tcp_conn->tx_hash, hdr, hdrlen,
+-				      hdr + hdrlen);
+-		hdrlen += ISCSI_DIGEST_SIZE;
++	int flags = 0; /* MSG_DONTWAIT; */
++	int res, size;
++
++	size = buf->sg.length - buf->sent;
++	BUG_ON(buf->sent + size > buf->sg.length);
++	if (size > *count)
++		size = *count;
++	if (buf->sent + size != buf->sg.length || *count != size)
++		flags |= MSG_MORE;
++
++	res = iscsi_send(conn, buf, size, flags);
++	debug_tcp("sendpage: %d bytes, sent %d left %d sent %d res %d\n",
++		  size, buf->sent, *count, *sent, res);
++	if (res >= 0) {
++		*count -= res;
++		*sent += res;
++		if (size != res)
++			return -EAGAIN;
++		return 0;
+ 	}
+ 
+-	/* Remember header pointer for later, when we need
+-	 * to decide whether there's a payload to go along
+-	 * with the header. */
+-	tcp_conn->out.hdr = hdr;
+-
+-	iscsi_segment_init_linear(&tcp_conn->out.segment, hdr, hdrlen,
+-				iscsi_tcp_send_hdr_done, NULL);
+-}
+-
+-/*
+- * Prepare the send buffer for the payload data.
+- * Padding and checksumming will all be taken care
+- * of by the iscsi_segment routines.
+- */
+-static int
+-iscsi_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg,
+-			 unsigned int count, unsigned int offset,
+-			 unsigned int len)
+-{
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct hash_desc *tx_hash = NULL;
+-	unsigned int hdr_spec_len;
+-
+-	debug_tcp("%s(%p, offset=%d, datalen=%d%s)\n", __FUNCTION__,
+-			tcp_conn, offset, len,
+-			conn->datadgst_en? ", digest enabled" : "");
+-
+-	/* Make sure the datalen matches what the caller
+-	   said he would send. */
+-	hdr_spec_len = ntoh24(tcp_conn->out.hdr->dlength);
+-	WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
+-
+-	if (conn->datadgst_en)
+-		tx_hash = &tcp_conn->tx_hash;
+-
+-	return iscsi_segment_seek_sg(&tcp_conn->out.data_segment,
+-				   sg, count, offset, len,
+-				   NULL, tx_hash);
++	return res;
+ }
+ 
+-static void
+-iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data,
+-				   size_t len)
++static inline void
++iscsi_data_digest_init(struct iscsi_tcp_conn *tcp_conn,
++		      struct iscsi_tcp_cmd_task *tcp_ctask)
+ {
+-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct hash_desc *tx_hash = NULL;
+-	unsigned int hdr_spec_len;
+-
+-	debug_tcp("%s(%p, datalen=%d%s)\n", __FUNCTION__, tcp_conn, len,
+-		  conn->datadgst_en? ", digest enabled" : "");
+-
+-	/* Make sure the datalen matches what the caller
+-	   said he would send. */
+-	hdr_spec_len = ntoh24(tcp_conn->out.hdr->dlength);
+-	WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
+-
+-	if (conn->datadgst_en)
+-		tx_hash = &tcp_conn->tx_hash;
+-
+-	iscsi_segment_init_linear(&tcp_conn->out.data_segment,
+-				data, len, NULL, tx_hash);
++	crypto_hash_init(&tcp_conn->tx_hash);
++	tcp_ctask->digest_count = 4;
+ }
+ 
+ /**
+@@ -1270,17 +1210,13 @@ iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data,
+  *
+  *	Called under connection lock.
+  **/
+-static int
++static void
+ iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+-			struct iscsi_r2t_info *r2t)
++			struct iscsi_r2t_info *r2t, int left)
+ {
+ 	struct iscsi_data *hdr;
+-	int new_offset, left;
+-
+-	BUG_ON(r2t->data_length - r2t->sent < 0);
+-	left = r2t->data_length - r2t->sent;
+-	if (left == 0)
+-		return 0;
++	struct scsi_cmnd *sc = ctask->sc;
++	int new_offset;
+ 
+ 	hdr = &r2t->dtask.hdr;
+ 	memset(hdr, 0, sizeof(struct iscsi_data));
+@@ -1301,47 +1237,81 @@ iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+ 		r2t->data_count = left;
+ 		hdr->flags = ISCSI_FLAG_CMD_FINAL;
+ 	}
+-
+ 	conn->dataout_pdus_cnt++;
+-	return 1;
++
++	iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr,
++			   sizeof(struct iscsi_hdr));
++
++	if (iscsi_buf_left(&r2t->sendbuf))
++		return;
++
++	if (sc->use_sg) {
++		iscsi_buf_init_sg(&r2t->sendbuf, r2t->sg);
++		r2t->sg += 1;
++	} else {
++		iscsi_buf_init_iov(&r2t->sendbuf,
++			    (char*)sc->request_buffer + new_offset,
++			    r2t->data_count);
++		r2t->sg = NULL;
++	}
++}
++
++static void iscsi_set_padding(struct iscsi_tcp_cmd_task *tcp_ctask,
++			      unsigned long len)
++{
++	tcp_ctask->pad_count = len & (ISCSI_PAD_LEN - 1);
++	if (!tcp_ctask->pad_count)
++		return;
++
++	tcp_ctask->pad_count = ISCSI_PAD_LEN - tcp_ctask->pad_count;
++	debug_scsi("write padding %d bytes\n", tcp_ctask->pad_count);
++	tcp_ctask->xmstate |= XMSTATE_W_PAD;
+ }
+ 
+ /**
+- * iscsi_tcp_ctask - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
++ * iscsi_tcp_cmd_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
+  * @conn: iscsi connection
+  * @ctask: scsi command task
+  * @sc: scsi command
+  **/
+-static int
+-iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
++static void
++iscsi_tcp_cmd_init(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-	struct iscsi_conn *conn = ctask->conn;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	int err;
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 	BUG_ON(__kfifo_len(tcp_ctask->r2tqueue));
++
+ 	tcp_ctask->sent = 0;
+-	tcp_ctask->exp_datasn = 0;
++	tcp_ctask->sg_count = 0;
+ 
+-	/* Prepare PDU, optionally w/ immediate data */
+-	debug_scsi("ctask deq [cid %d itt 0x%x imm %d unsol %d]\n",
+-		    conn->id, ctask->itt, ctask->imm_count,
+-		    ctask->unsol_count);
+-	iscsi_tcp_send_hdr_prep(conn, ctask->hdr, ctask->hdr_len);
++	if (sc->sc_data_direction == DMA_TO_DEVICE) {
++		tcp_ctask->xmstate = XMSTATE_W_HDR;
++		tcp_ctask->exp_r2tsn = 0;
++		BUG_ON(ctask->total_length == 0);
+ 
+-	if (!ctask->imm_count)
+-		return 0;
++		if (sc->use_sg) {
++			struct scatterlist *sg = sc->request_buffer;
+ 
+-	/* If we have immediate data, attach a payload */
+-	err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl,
+-				       scsi_out(sc)->table.nents,
+-				       0, ctask->imm_count);
+-	if (err)
+-		return err;
+-	tcp_ctask->sent += ctask->imm_count;
+-	ctask->imm_count = 0;
+-	return 0;
++			iscsi_buf_init_sg(&tcp_ctask->sendbuf, sg);
++			tcp_ctask->sg = sg + 1;
++			tcp_ctask->bad_sg = sg + sc->use_sg;
++		} else {
++			iscsi_buf_init_iov(&tcp_ctask->sendbuf,
++					   sc->request_buffer,
++					   sc->request_bufflen);
++			tcp_ctask->sg = NULL;
++			tcp_ctask->bad_sg = NULL;
++		}
++		debug_scsi("cmd [itt 0x%x total %d imm_data %d "
++			   "unsol count %d, unsol offset %d]\n",
++			   ctask->itt, ctask->total_length, ctask->imm_count,
++			   ctask->unsol_count, ctask->unsol_offset);
++	} else
++		tcp_ctask->xmstate = XMSTATE_R_HDR;
++
++	iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)ctask->hdr,
++			    sizeof(struct iscsi_hdr));
+ }
+ 
+ /**
+@@ -1353,130 +1323,428 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask)
+  *	The function can return -EAGAIN in which case caller must
+  *	call it again later, or recover. '0' return code means successful
+  *	xmit.
++ *
++ *	Management xmit state machine consists of two states:
++ *		IN_PROGRESS_IMM_HEAD - PDU Header xmit in progress
++ *		IN_PROGRESS_IMM_DATA - PDU Data xmit in progress
+  **/
+ static int
+ iscsi_tcp_mtask_xmit(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
+ {
++	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 	int rc;
+ 
+-	/* Flush any pending data first. */
+-	rc = iscsi_tcp_flush(conn);
+-	if (rc < 0)
+-		return rc;
++	debug_scsi("mtask deq [cid %d state %x itt 0x%x]\n",
++		conn->id, tcp_mtask->xmstate, mtask->itt);
++
++	if (tcp_mtask->xmstate & XMSTATE_IMM_HDR) {
++		tcp_mtask->xmstate &= ~XMSTATE_IMM_HDR;
++		if (mtask->data_count)
++			tcp_mtask->xmstate |= XMSTATE_IMM_DATA;
++		if (conn->c_stage != ISCSI_CONN_INITIAL_STAGE &&
++		    conn->stop_stage != STOP_CONN_RECOVER &&
++		    conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &tcp_mtask->headbuf,
++					(u8*)tcp_mtask->hdrext);
++		rc = iscsi_sendhdr(conn, &tcp_mtask->headbuf,
++				   mtask->data_count);
++		if (rc) {
++			tcp_mtask->xmstate |= XMSTATE_IMM_HDR;
++			if (mtask->data_count)
++				tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA;
++			return rc;
++		}
++	}
+ 
++	if (tcp_mtask->xmstate & XMSTATE_IMM_DATA) {
++		BUG_ON(!mtask->data_count);
++		tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA;
++		/* FIXME: implement.
++		 * Virtual buffer could be spreaded across multiple pages...
++		 */
++		do {
++			int rc;
++
++			rc = iscsi_sendpage(conn, &tcp_mtask->sendbuf,
++					&mtask->data_count, &tcp_mtask->sent);
++			if (rc) {
++				tcp_mtask->xmstate |= XMSTATE_IMM_DATA;
++				return rc;
++			}
++		} while (mtask->data_count);
++	}
++
++	BUG_ON(tcp_mtask->xmstate != XMSTATE_IDLE);
+ 	if (mtask->hdr->itt == RESERVED_ITT) {
+ 		struct iscsi_session *session = conn->session;
+ 
+ 		spin_lock_bh(&session->lock);
+-		iscsi_free_mgmt_task(conn, mtask);
++		list_del(&conn->mtask->running);
++		__kfifo_put(session->mgmtpool.queue, (void*)&conn->mtask,
++			    sizeof(void*));
+ 		spin_unlock_bh(&session->lock);
+ 	}
++	return 0;
++}
++
++static inline int
++iscsi_send_read_hdr(struct iscsi_conn *conn,
++		    struct iscsi_tcp_cmd_task *tcp_ctask)
++{
++	int rc;
++
++	tcp_ctask->xmstate &= ~XMSTATE_R_HDR;
++	if (conn->hdrdgst_en)
++		iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++				 (u8*)tcp_ctask->hdrext);
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, 0);
++	if (!rc) {
++		BUG_ON(tcp_ctask->xmstate != XMSTATE_IDLE);
++		return 0; /* wait for Data-In */
++	}
++	tcp_ctask->xmstate |= XMSTATE_R_HDR;
++	return rc;
++}
++
++static inline int
++iscsi_send_write_hdr(struct iscsi_conn *conn,
++		     struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc;
++
++	tcp_ctask->xmstate &= ~XMSTATE_W_HDR;
++	if (conn->hdrdgst_en)
++		iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++				 (u8*)tcp_ctask->hdrext);
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->imm_count);
++	if (rc) {
++		tcp_ctask->xmstate |= XMSTATE_W_HDR;
++		return rc;
++	}
++
++	if (ctask->imm_count) {
++		tcp_ctask->xmstate |= XMSTATE_IMM_DATA;
++		iscsi_set_padding(tcp_ctask, ctask->imm_count);
+ 
++		if (ctask->conn->datadgst_en) {
++			iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask);
++			tcp_ctask->immdigest = 0;
++		}
++	}
++
++	if (ctask->unsol_count)
++		tcp_ctask->xmstate |= XMSTATE_UNS_HDR | XMSTATE_UNS_INIT;
+ 	return 0;
+ }
+ 
+-/*
+- * iscsi_tcp_ctask_xmit - xmit normal PDU task
+- * @conn: iscsi connection
+- * @ctask: iscsi command task
+- *
+- * We're expected to return 0 when everything was transmitted succesfully,
+- * -EAGAIN if there's still data in the queue, or != 0 for any other kind
+- * of error.
+- */
+ static int
+-iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++iscsi_send_padding(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct scsi_data_buffer *sdb = scsi_out(sc);
+-	int rc = 0;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int sent = 0, rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_W_PAD) {
++		iscsi_buf_init_iov(&tcp_ctask->sendbuf, (char*)&tcp_ctask->pad,
++				   tcp_ctask->pad_count);
++		if (conn->datadgst_en)
++			crypto_hash_update(&tcp_conn->tx_hash,
++					   &tcp_ctask->sendbuf.sg,
++					   tcp_ctask->sendbuf.sg.length);
++	} else if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_PAD))
++		return 0;
+ 
+-flush:
+-	/* Flush any pending data first. */
+-	rc = iscsi_tcp_flush(conn);
+-	if (rc < 0)
+-		return rc;
++	tcp_ctask->xmstate &= ~XMSTATE_W_PAD;
++	tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_PAD;
++	debug_scsi("sending %d pad bytes for itt 0x%x\n",
++		   tcp_ctask->pad_count, ctask->itt);
++	rc = iscsi_sendpage(conn, &tcp_ctask->sendbuf, &tcp_ctask->pad_count,
++			   &sent);
++	if (rc) {
++		debug_scsi("padding send failed %d\n", rc);
++		tcp_ctask->xmstate |= XMSTATE_W_RESEND_PAD;
++	}
++	return rc;
++}
+ 
+-	/* Are we done already? */
+-	if (sc->sc_data_direction != DMA_TO_DEVICE)
++static int
++iscsi_send_digest(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
++			struct iscsi_buf *buf, uint32_t *digest)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask;
++	struct iscsi_tcp_conn *tcp_conn;
++	int rc, sent = 0;
++
++	if (!conn->datadgst_en)
+ 		return 0;
+ 
+-	if (ctask->unsol_count != 0) {
+-		struct iscsi_data *hdr = &tcp_ctask->unsol_dtask.hdr;
++	tcp_ctask = ctask->dd_data;
++	tcp_conn = conn->dd_data;
+ 
+-		/* Prepare a header for the unsolicited PDU.
+-		 * The amount of data we want to send will be
+-		 * in ctask->data_count.
+-		 * FIXME: return the data count instead.
+-		 */
+-		iscsi_prep_unsolicit_data_pdu(ctask, hdr);
++	if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_DATA_DIGEST)) {
++		crypto_hash_final(&tcp_conn->tx_hash, (u8*)digest);
++		iscsi_buf_init_iov(buf, (char*)digest, 4);
++	}
++	tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_DATA_DIGEST;
++
++	rc = iscsi_sendpage(conn, buf, &tcp_ctask->digest_count, &sent);
++	if (!rc)
++		debug_scsi("sent digest 0x%x for itt 0x%x\n", *digest,
++			  ctask->itt);
++	else {
++		debug_scsi("sending digest 0x%x failed for itt 0x%x!\n",
++			  *digest, ctask->itt);
++		tcp_ctask->xmstate |= XMSTATE_W_RESEND_DATA_DIGEST;
++	}
++	return rc;
++}
+ 
+-		debug_tcp("unsol dout [itt 0x%x doff %d dlen %d]\n",
+-				ctask->itt, tcp_ctask->sent, ctask->data_count);
++static int
++iscsi_send_data(struct iscsi_cmd_task *ctask, struct iscsi_buf *sendbuf,
++		struct scatterlist **sg, int *sent, int *count,
++		struct iscsi_buf *digestbuf, uint32_t *digest)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_conn *conn = ctask->conn;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	int rc, buf_sent, offset;
++
++	while (*count) {
++		buf_sent = 0;
++		offset = sendbuf->sent;
++
++		rc = iscsi_sendpage(conn, sendbuf, count, &buf_sent);
++		*sent = *sent + buf_sent;
++		if (buf_sent && conn->datadgst_en)
++			partial_sg_digest_update(&tcp_conn->tx_hash,
++				&sendbuf->sg, sendbuf->sg.offset + offset,
++				buf_sent);
++		if (!iscsi_buf_left(sendbuf) && *sg != tcp_ctask->bad_sg) {
++			iscsi_buf_init_sg(sendbuf, *sg);
++			*sg = *sg + 1;
++		}
+ 
+-		iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr));
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents, tcp_ctask->sent,
+-					      ctask->data_count);
+ 		if (rc)
+-			goto fail;
+-		tcp_ctask->sent += ctask->data_count;
+-		ctask->unsol_count -= ctask->data_count;
+-		goto flush;
+-	} else {
+-		struct iscsi_session *session = conn->session;
+-		struct iscsi_r2t_info *r2t;
++			return rc;
++	}
++
++	rc = iscsi_send_padding(conn, ctask);
++	if (rc)
++		return rc;
++
++	return iscsi_send_digest(conn, ctask, digestbuf, digest);
++}
++
++static int
++iscsi_send_unsol_hdr(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_data_task *dtask;
++	int rc;
++
++	tcp_ctask->xmstate |= XMSTATE_UNS_DATA;
++	if (tcp_ctask->xmstate & XMSTATE_UNS_INIT) {
++		dtask = &tcp_ctask->unsol_dtask;
++
++		iscsi_prep_unsolicit_data_pdu(ctask, &dtask->hdr);
++		iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)&dtask->hdr,
++				   sizeof(struct iscsi_hdr));
++		if (conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
++					(u8*)dtask->hdrext);
++
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_INIT;
++		iscsi_set_padding(tcp_ctask, ctask->data_count);
++	}
++
++	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->data_count);
++	if (rc) {
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA;
++		tcp_ctask->xmstate |= XMSTATE_UNS_HDR;
++		return rc;
++	}
++
++	if (conn->datadgst_en) {
++		dtask = &tcp_ctask->unsol_dtask;
++		iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask);
++		dtask->digest = 0;
++	}
++
++	debug_scsi("uns dout [itt 0x%x dlen %d sent %d]\n",
++		   ctask->itt, ctask->unsol_count, tcp_ctask->sent);
++	return 0;
++}
+ 
+-		/* All unsolicited PDUs sent. Check for solicited PDUs.
++static int
++iscsi_send_unsol_pdu(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_UNS_HDR) {
++		BUG_ON(!ctask->unsol_count);
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_HDR;
++send_hdr:
++		rc = iscsi_send_unsol_hdr(conn, ctask);
++		if (rc)
++			return rc;
++	}
++
++	if (tcp_ctask->xmstate & XMSTATE_UNS_DATA) {
++		struct iscsi_data_task *dtask = &tcp_ctask->unsol_dtask;
++		int start = tcp_ctask->sent;
++
++		rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg,
++				     &tcp_ctask->sent, &ctask->data_count,
++				     &dtask->digestbuf, &dtask->digest);
++		ctask->unsol_count -= tcp_ctask->sent - start;
++		if (rc)
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA;
++		/*
++		 * Done with the Data-Out. Next, check if we need
++		 * to send another unsolicited Data-Out.
+ 		 */
+-		spin_lock_bh(&session->lock);
+-		r2t = tcp_ctask->r2t;
+-		if (r2t != NULL) {
+-			/* Continue with this R2T? */
+-			if (!iscsi_solicit_data_cont(conn, ctask, r2t)) {
+-				debug_scsi("  done with r2t %p\n", r2t);
+-
+-				__kfifo_put(tcp_ctask->r2tpool.queue,
+-					    (void*)&r2t, sizeof(void*));
+-				tcp_ctask->r2t = r2t = NULL;
+-			}
++		if (ctask->unsol_count) {
++			debug_scsi("sending more uns\n");
++			tcp_ctask->xmstate |= XMSTATE_UNS_INIT;
++			goto send_hdr;
+ 		}
++	}
++	return 0;
++}
+ 
+-		if (r2t == NULL) {
++static int iscsi_send_sol_pdu(struct iscsi_conn *conn,
++			      struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	struct iscsi_session *session = conn->session;
++	struct iscsi_r2t_info *r2t;
++	struct iscsi_data_task *dtask;
++	int left, rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_SOL_HDR) {
++		tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++		tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++		if (!tcp_ctask->r2t) {
++			spin_lock_bh(&session->lock);
+ 			__kfifo_get(tcp_ctask->r2tqueue, (void*)&tcp_ctask->r2t,
+ 				    sizeof(void*));
+-			r2t = tcp_ctask->r2t;
++			spin_unlock_bh(&session->lock);
++		}
++send_hdr:
++		r2t = tcp_ctask->r2t;
++		dtask = &r2t->dtask;
++
++		if (conn->hdrdgst_en)
++			iscsi_hdr_digest(conn, &r2t->headbuf,
++					(u8*)dtask->hdrext);
++		rc = iscsi_sendhdr(conn, &r2t->headbuf, r2t->data_count);
++		if (rc) {
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate |= XMSTATE_SOL_HDR;
++			return rc;
+ 		}
+-		spin_unlock_bh(&session->lock);
+ 
+-		/* Waiting for more R2Ts to arrive. */
+-		if (r2t == NULL) {
+-			debug_tcp("no R2Ts yet\n");
+-			return 0;
++		if (conn->datadgst_en) {
++			iscsi_data_digest_init(conn->dd_data, tcp_ctask);
++			dtask->digest = 0;
+ 		}
+ 
+-		debug_scsi("sol dout %p [dsn %d itt 0x%x doff %d dlen %d]\n",
+-			r2t, r2t->solicit_datasn - 1, ctask->itt,
+-			r2t->data_offset + r2t->sent, r2t->data_count);
++		iscsi_set_padding(tcp_ctask, r2t->data_count);
++		debug_scsi("sol dout [dsn %d itt 0x%x dlen %d sent %d]\n",
++			r2t->solicit_datasn - 1, ctask->itt, r2t->data_count,
++			r2t->sent);
++	}
+ 
+-		iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr,
+-					sizeof(struct iscsi_hdr));
++	if (tcp_ctask->xmstate & XMSTATE_SOL_DATA) {
++		r2t = tcp_ctask->r2t;
++		dtask = &r2t->dtask;
+ 
+-		rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl,
+-					      sdb->table.nents,
+-					      r2t->data_offset + r2t->sent,
+-					      r2t->data_count);
++		rc = iscsi_send_data(ctask, &r2t->sendbuf, &r2t->sg,
++				     &r2t->sent, &r2t->data_count,
++				     &dtask->digestbuf, &dtask->digest);
+ 		if (rc)
+-			goto fail;
+-		tcp_ctask->sent += r2t->data_count;
+-		r2t->sent += r2t->data_count;
+-		goto flush;
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA;
++
++		/*
++		 * Done with this Data-Out. Next, check if we have
++		 * to send another Data-Out for this R2T.
++		 */
++		BUG_ON(r2t->data_length - r2t->sent < 0);
++		left = r2t->data_length - r2t->sent;
++		if (left) {
++			iscsi_solicit_data_cont(conn, ctask, r2t, left);
++			tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++			goto send_hdr;
++		}
++
++		/*
++		 * Done with this R2T. Check if there are more
++		 * outstanding R2Ts ready to be processed.
++		 */
++		spin_lock_bh(&session->lock);
++		tcp_ctask->r2t = NULL;
++		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
++			    sizeof(void*));
++		if (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t,
++				sizeof(void*))) {
++			tcp_ctask->r2t = r2t;
++			tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
++			tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
++			spin_unlock_bh(&session->lock);
++			goto send_hdr;
++		}
++		spin_unlock_bh(&session->lock);
+ 	}
+ 	return 0;
+-fail:
+-	iscsi_conn_failure(conn, rc);
+-	return -EIO;
++}
++
++static int
++iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
++{
++	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
++	int rc = 0;
++
++	debug_scsi("ctask deq [cid %d xmstate %x itt 0x%x]\n",
++		conn->id, tcp_ctask->xmstate, ctask->itt);
++
++	/*
++	 * serialize with TMF AbortTask
++	 */
++	if (ctask->mtask)
++		return rc;
++
++	if (tcp_ctask->xmstate & XMSTATE_R_HDR)
++		return iscsi_send_read_hdr(conn, tcp_ctask);
++
++	if (tcp_ctask->xmstate & XMSTATE_W_HDR) {
++		rc = iscsi_send_write_hdr(conn, ctask);
++		if (rc)
++			return rc;
++	}
++
++	if (tcp_ctask->xmstate & XMSTATE_IMM_DATA) {
++		rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg,
++				     &tcp_ctask->sent, &ctask->imm_count,
++				     &tcp_ctask->immbuf, &tcp_ctask->immdigest);
++		if (rc)
++			return rc;
++		tcp_ctask->xmstate &= ~XMSTATE_IMM_DATA;
++	}
++
++	rc = iscsi_send_unsol_pdu(conn, ctask);
++	if (rc)
++		return rc;
++
++	rc = iscsi_send_sol_pdu(conn, ctask);
++	if (rc)
++		return rc;
++
++	return rc;
+ }
+ 
+ static struct iscsi_cls_conn *
+@@ -1502,29 +1770,37 @@ iscsi_tcp_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 
+ 	conn->dd_data = tcp_conn;
+ 	tcp_conn->iscsi_conn = conn;
++	tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
++	/* initial operational parameters */
++	tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
+ 
+ 	tcp_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+ 						  CRYPTO_ALG_ASYNC);
+ 	tcp_conn->tx_hash.flags = 0;
+-	if (IS_ERR(tcp_conn->tx_hash.tfm))
++	if (IS_ERR(tcp_conn->tx_hash.tfm)) {
++		printk(KERN_ERR "Could not create connection due to crc32c "
++		       "loading error %ld. Make sure the crc32c module is "
++		       "built as a module or into the kernel\n",
++			PTR_ERR(tcp_conn->tx_hash.tfm));
+ 		goto free_tcp_conn;
++	}
+ 
+ 	tcp_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+ 						  CRYPTO_ALG_ASYNC);
+ 	tcp_conn->rx_hash.flags = 0;
+-	if (IS_ERR(tcp_conn->rx_hash.tfm))
++	if (IS_ERR(tcp_conn->rx_hash.tfm)) {
++		printk(KERN_ERR "Could not create connection due to crc32c "
++		       "loading error %ld. Make sure the crc32c module is "
++		       "built as a module or into the kernel\n",
++			PTR_ERR(tcp_conn->rx_hash.tfm));
+ 		goto free_tx_tfm;
++	}
+ 
+ 	return cls_conn;
+ 
+ free_tx_tfm:
+ 	crypto_free_hash(tcp_conn->tx_hash.tfm);
+ free_tcp_conn:
+-	iscsi_conn_printk(KERN_ERR, conn,
+-			  "Could not create connection due to crc32c "
+-			  "loading error. Make sure the crc32c "
+-			  "module is built as a module or into the "
+-			  "kernel\n");
+ 	kfree(tcp_conn);
+ tcp_conn_alloc_fail:
+ 	iscsi_conn_teardown(cls_conn);
+@@ -1534,22 +1810,18 @@ tcp_conn_alloc_fail:
+ static void
+ iscsi_tcp_release_conn(struct iscsi_conn *conn)
+ {
+-	struct iscsi_session *session = conn->session;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct socket *sock = tcp_conn->sock;
+ 
+-	if (!sock)
++	if (!tcp_conn->sock)
+ 		return;
+ 
+-	sock_hold(sock->sk);
++	sock_hold(tcp_conn->sock->sk);
+ 	iscsi_conn_restore_callbacks(tcp_conn);
+-	sock_put(sock->sk);
++	sock_put(tcp_conn->sock->sk);
+ 
+-	spin_lock_bh(&session->lock);
++	sock_release(tcp_conn->sock);
+ 	tcp_conn->sock = NULL;
+ 	conn->recv_lock = NULL;
+-	spin_unlock_bh(&session->lock);
+-	sockfd_put(sock);
+ }
+ 
+ static void
+@@ -1573,49 +1845,11 @@ static void
+ iscsi_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+ 
+ 	iscsi_conn_stop(cls_conn, flag);
+ 	iscsi_tcp_release_conn(conn);
+-}
+-
+-static int iscsi_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock,
+-			      char *buf, int *port,
+-			      int (*getname)(struct socket *, struct sockaddr *,
+-					int *addrlen))
+-{
+-	struct sockaddr_storage *addr;
+-	struct sockaddr_in6 *sin6;
+-	struct sockaddr_in *sin;
+-	int rc = 0, len;
+-
+-	addr = kmalloc(sizeof(*addr), GFP_KERNEL);
+-	if (!addr)
+-		return -ENOMEM;
+-
+-	if (getname(sock, (struct sockaddr *) addr, &len)) {
+-		rc = -ENODEV;
+-		goto free_addr;
+-	}
+-
+-	switch (addr->ss_family) {
+-	case AF_INET:
+-		sin = (struct sockaddr_in *)addr;
+-		spin_lock_bh(&conn->session->lock);
+-		sprintf(buf, NIPQUAD_FMT, NIPQUAD(sin->sin_addr.s_addr));
+-		*port = be16_to_cpu(sin->sin_port);
+-		spin_unlock_bh(&conn->session->lock);
+-		break;
+-	case AF_INET6:
+-		sin6 = (struct sockaddr_in6 *)addr;
+-		spin_lock_bh(&conn->session->lock);
+-		sprintf(buf, NIP6_FMT, NIP6(sin6->sin6_addr));
+-		*port = be16_to_cpu(sin6->sin6_port);
+-		spin_unlock_bh(&conn->session->lock);
+-		break;
+-	}
+-free_addr:
+-	kfree(addr);
+-	return rc;
++	tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
+ }
+ 
+ static int
+@@ -1632,28 +1866,13 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
+ 	/* lookup for existing socket */
+ 	sock = sockfd_lookup((int)transport_eph, &err);
+ 	if (!sock) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "sockfd_lookup failed %d\n", err);
++		printk(KERN_ERR "iscsi_tcp: sockfd_lookup failed %d\n", err);
+ 		return -EEXIST;
+ 	}
+-	/*
+-	 * copy these values now because if we drop the session
+-	 * userspace may still want to query the values since we will
+-	 * be using them for the reconnect
+-	 */
+-	err = iscsi_tcp_get_addr(conn, sock, conn->portal_address,
+-				 &conn->portal_port, kernel_getpeername);
+-	if (err)
+-		goto free_socket;
+-
+-	err = iscsi_tcp_get_addr(conn, sock, conn->local_address,
+-				&conn->local_port, kernel_getsockname);
+-	if (err)
+-		goto free_socket;
+ 
+ 	err = iscsi_conn_bind(cls_session, cls_conn, is_leading);
+ 	if (err)
+-		goto free_socket;
++		return err;
+ 
+ 	/* bind iSCSI connection and socket */
+ 	tcp_conn->sock = sock;
+@@ -1676,27 +1895,26 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
+ 	/*
+ 	 * set receive state machine into initial state
+ 	 */
+-	iscsi_tcp_hdr_recv_prep(tcp_conn);
+-	return 0;
++	tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ 
+-free_socket:
+-	sockfd_put(sock);
+-	return err;
++	return 0;
+ }
+ 
+ /* called with host lock */
+ static void
+-iscsi_tcp_mtask_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
++iscsi_tcp_mgmt_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask,
++		    char *data, uint32_t data_size)
+ {
+-	debug_scsi("mtask deq [cid %d itt 0x%x]\n", conn->id, mtask->itt);
++	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 
+-	/* Prepare PDU, optionally w/ immediate data */
+-	iscsi_tcp_send_hdr_prep(conn, mtask->hdr, sizeof(*mtask->hdr));
++	iscsi_buf_init_iov(&tcp_mtask->headbuf, (char*)mtask->hdr,
++			   sizeof(struct iscsi_hdr));
++	tcp_mtask->xmstate = XMSTATE_IMM_HDR;
++	tcp_mtask->sent = 0;
+ 
+-	/* If we have immediate data, attach a payload */
+ 	if (mtask->data_count)
+-		iscsi_tcp_send_linear_data_prepare(conn, mtask->data,
+-						   mtask->data_count);
++		iscsi_buf_init_iov(&tcp_mtask->sendbuf, (char*)mtask->data,
++				    mtask->data_count);
+ }
+ 
+ static int
+@@ -1719,7 +1937,8 @@ iscsi_r2tpool_alloc(struct iscsi_session *session)
+ 		 */
+ 
+ 		/* R2T pool */
+-		if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4, NULL,
++		if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4,
++				    (void***)&tcp_ctask->r2ts,
+ 				    sizeof(struct iscsi_r2t_info))) {
+ 			goto r2t_alloc_fail;
+ 		}
+@@ -1728,7 +1947,8 @@ iscsi_r2tpool_alloc(struct iscsi_session *session)
+ 		tcp_ctask->r2tqueue = kfifo_alloc(
+ 		      session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL);
+ 		if (tcp_ctask->r2tqueue == ERR_PTR(-ENOMEM)) {
+-			iscsi_pool_free(&tcp_ctask->r2tpool);
++			iscsi_pool_free(&tcp_ctask->r2tpool,
++					(void**)tcp_ctask->r2ts);
+ 			goto r2t_alloc_fail;
+ 		}
+ 	}
+@@ -1741,7 +1961,8 @@ r2t_alloc_fail:
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 		kfifo_free(tcp_ctask->r2tqueue);
+-		iscsi_pool_free(&tcp_ctask->r2tpool);
++		iscsi_pool_free(&tcp_ctask->r2tpool,
++				(void**)tcp_ctask->r2ts);
+ 	}
+ 	return -ENOMEM;
+ }
+@@ -1756,7 +1977,8 @@ iscsi_r2tpool_free(struct iscsi_session *session)
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+ 		kfifo_free(tcp_ctask->r2tqueue);
+-		iscsi_pool_free(&tcp_ctask->r2tpool);
++		iscsi_pool_free(&tcp_ctask->r2tpool,
++				(void**)tcp_ctask->r2ts);
+ 	}
+ }
+ 
+@@ -1772,6 +1994,9 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
+ 	switch(param) {
+ 	case ISCSI_PARAM_HDRDGST_EN:
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
++		tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
++		if (conn->hdrdgst_en)
++			tcp_conn->hdr_size += sizeof(__u32);
+ 		break;
+ 	case ISCSI_PARAM_DATADGST_EN:
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
+@@ -1780,12 +2005,12 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
+ 		break;
+ 	case ISCSI_PARAM_MAX_R2T:
+ 		sscanf(buf, "%d", &value);
+-		if (value <= 0 || !is_power_of_2(value))
+-			return -EINVAL;
+-		if (session->max_r2t == value)
++		if (session->max_r2t == roundup_pow_of_two(value))
+ 			break;
+ 		iscsi_r2tpool_free(session);
+ 		iscsi_set_param(cls_conn, param, buf, buflen);
++		if (session->max_r2t & (session->max_r2t - 1))
++			session->max_r2t = roundup_pow_of_two(session->max_r2t);
+ 		if (iscsi_r2tpool_alloc(session))
+ 			return -ENOMEM;
+ 		break;
+@@ -1801,18 +2026,41 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 			 enum iscsi_param param, char *buf)
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
++	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
++	struct inet_sock *inet;
++	struct ipv6_pinfo *np;
++	struct sock *sk;
+ 	int len;
+ 
+ 	switch(param) {
+ 	case ISCSI_PARAM_CONN_PORT:
+-		spin_lock_bh(&conn->session->lock);
+-		len = sprintf(buf, "%hu\n", conn->portal_port);
+-		spin_unlock_bh(&conn->session->lock);
++		mutex_lock(&conn->xmitmutex);
++		if (!tcp_conn->sock) {
++			mutex_unlock(&conn->xmitmutex);
++			return -EINVAL;
++		}
++
++		inet = inet_sk(tcp_conn->sock->sk);
++		len = sprintf(buf, "%hu\n", be16_to_cpu(inet->dport));
++		mutex_unlock(&conn->xmitmutex);
+ 		break;
+ 	case ISCSI_PARAM_CONN_ADDRESS:
+-		spin_lock_bh(&conn->session->lock);
+-		len = sprintf(buf, "%s\n", conn->portal_address);
+-		spin_unlock_bh(&conn->session->lock);
++		mutex_lock(&conn->xmitmutex);
++		if (!tcp_conn->sock) {
++			mutex_unlock(&conn->xmitmutex);
++			return -EINVAL;
++		}
++
++		sk = tcp_conn->sock->sk;
++		if (sk->sk_family == PF_INET) {
++			inet = inet_sk(sk);
++			len = sprintf(buf, NIPQUAD_FMT "\n",
++				      NIPQUAD(inet->daddr));
++		} else {
++			np = inet6_sk(sk);
++			len = sprintf(buf, NIP6_FMT "\n", NIP6(np->daddr));
++		}
++		mutex_unlock(&conn->xmitmutex);
+ 		break;
+ 	default:
+ 		return iscsi_conn_get_param(cls_conn, param, buf);
+@@ -1821,29 +2069,6 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 	return len;
+ }
+ 
+-static int
+-iscsi_tcp_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf)
+-{
+-        struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-	int len;
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_IPADDRESS:
+-		spin_lock_bh(&session->lock);
+-		if (!session->leadconn)
+-			len = -ENODEV;
+-		else
+-			len = sprintf(buf, "%s\n",
+-				     session->leadconn->local_address);
+-		spin_unlock_bh(&session->lock);
+-		break;
+-	default:
+-		return iscsi_host_get_param(shost, param, buf);
+-	}
+-	return len;
+-}
+-
+ static void
+ iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats)
+ {
+@@ -1871,7 +2096,6 @@ iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats)
+ static struct iscsi_cls_session *
+ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 			 struct scsi_transport_template *scsit,
+-			 uint16_t cmds_max, uint16_t qdepth,
+ 			 uint32_t initial_cmdsn, uint32_t *hostno)
+ {
+ 	struct iscsi_cls_session *cls_session;
+@@ -1879,7 +2103,7 @@ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 	uint32_t hn;
+ 	int cmd_i;
+ 
+-	cls_session = iscsi_session_setup(iscsit, scsit, cmds_max, qdepth,
++	cls_session = iscsi_session_setup(iscsit, scsit,
+ 					 sizeof(struct iscsi_tcp_cmd_task),
+ 					 sizeof(struct iscsi_tcp_mgmt_task),
+ 					 initial_cmdsn, &hn);
+@@ -1892,15 +2116,14 @@ iscsi_tcp_session_create(struct iscsi_transport *iscsit,
+ 		struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
+ 		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+ 
+-		ctask->hdr = &tcp_ctask->hdr.cmd_hdr;
+-		ctask->hdr_max = sizeof(tcp_ctask->hdr) - ISCSI_DIGEST_SIZE;
++		ctask->hdr = &tcp_ctask->hdr;
+ 	}
+ 
+ 	for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) {
+ 		struct iscsi_mgmt_task *mtask = session->mgmt_cmds[cmd_i];
+ 		struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+ 
+-		mtask->hdr = (struct iscsi_hdr *) &tcp_mtask->hdr;
++		mtask->hdr = &tcp_mtask->hdr;
+ 	}
+ 
+ 	if (iscsi_r2tpool_alloc(class_to_transport_session(cls_session)))
+@@ -1919,27 +2142,17 @@ static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
+ 	iscsi_session_teardown(cls_session);
+ }
+ 
+-static int iscsi_tcp_slave_configure(struct scsi_device *sdev)
+-{
+-	blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_ANY);
+-	blk_queue_dma_alignment(sdev->request_queue, 0);
+-	return 0;
+-}
+-
+ static struct scsi_host_template iscsi_sht = {
+-	.module			= THIS_MODULE,
+ 	.name			= "iSCSI Initiator over TCP/IP",
+ 	.queuecommand           = iscsi_queuecommand,
+ 	.change_queue_depth	= iscsi_change_queue_depth,
+-	.can_queue		= ISCSI_DEF_XMIT_CMDS_MAX - 1,
+-	.sg_tablesize		= 4096,
++	.can_queue		= ISCSI_XMIT_CMDS_MAX - 1,
++	.sg_tablesize		= ISCSI_SG_TABLESIZE,
+ 	.max_sectors		= 0xFFFF,
+ 	.cmd_per_lun		= ISCSI_DEF_CMD_PER_LUN,
+ 	.eh_abort_handler       = iscsi_eh_abort,
+-	.eh_device_reset_handler= iscsi_eh_device_reset,
+ 	.eh_host_reset_handler	= iscsi_eh_host_reset,
+ 	.use_clustering         = DISABLE_CLUSTERING,
+-	.slave_configure        = iscsi_tcp_slave_configure,
+ 	.proc_name		= "iscsi_tcp",
+ 	.this_id		= -1,
+ };
+@@ -1966,19 +2179,12 @@ static struct iscsi_transport iscsi_tcp_transport = {
+ 				  ISCSI_EXP_STATSN |
+ 				  ISCSI_PERSISTENT_PORT |
+ 				  ISCSI_PERSISTENT_ADDRESS |
+-				  ISCSI_TARGET_NAME | ISCSI_TPGT |
+-				  ISCSI_USERNAME | ISCSI_PASSWORD |
+-				  ISCSI_USERNAME_IN | ISCSI_PASSWORD_IN |
+-				  ISCSI_FAST_ABORT | ISCSI_ABORT_TMO |
+-				  ISCSI_LU_RESET_TMO |
+-				  ISCSI_PING_TMO | ISCSI_RECV_TMO,
+-	.host_param_mask	= ISCSI_HOST_HWADDRESS | ISCSI_HOST_IPADDRESS |
+-				  ISCSI_HOST_INITIATOR_NAME |
+-				  ISCSI_HOST_NETDEV_NAME,
++				  ISCSI_TARGET_NAME |
++				  ISCSI_TPGT,
+ 	.host_template		= &iscsi_sht,
+ 	.conndata_size		= sizeof(struct iscsi_conn),
+ 	.max_conn		= 1,
+-	.max_cmd_len		= 16,
++	.max_cmd_len		= ISCSI_TCP_MAX_CMD_LEN,
+ 	/* session management */
+ 	.create_session		= iscsi_tcp_session_create,
+ 	.destroy_session	= iscsi_tcp_session_destroy,
+@@ -1991,14 +2197,11 @@ static struct iscsi_transport iscsi_tcp_transport = {
+ 	.get_session_param	= iscsi_session_get_param,
+ 	.start_conn		= iscsi_conn_start,
+ 	.stop_conn		= iscsi_tcp_conn_stop,
+-	/* iscsi host params */
+-	.get_host_param		= iscsi_tcp_host_get_param,
+-	.set_host_param		= iscsi_host_set_param,
+ 	/* IO */
+ 	.send_pdu		= iscsi_conn_send_pdu,
+ 	.get_stats		= iscsi_conn_get_stats,
+-	.init_cmd_task		= iscsi_tcp_ctask_init,
+-	.init_mgmt_task		= iscsi_tcp_mtask_init,
++	.init_cmd_task		= iscsi_tcp_cmd_init,
++	.init_mgmt_task		= iscsi_tcp_mgmt_init,
+ 	.xmit_cmd_task		= iscsi_tcp_ctask_xmit,
+ 	.xmit_mgmt_task		= iscsi_tcp_mtask_xmit,
+ 	.cleanup_cmd_task	= iscsi_tcp_cleanup_ctask,
+diff --git a/drivers/scsi/iscsi_tcp.h b/drivers/scsi/iscsi_tcp.h
+index ed0b991..3273683 100644
+--- a/drivers/scsi/iscsi_tcp.h
++++ b/drivers/scsi/iscsi_tcp.h
+@@ -24,61 +24,68 @@
+ 
+ #include <scsi/libiscsi.h>
+ 
++/* Socket's Receive state machine */
++#define IN_PROGRESS_WAIT_HEADER		0x0
++#define IN_PROGRESS_HEADER_GATHER	0x1
++#define IN_PROGRESS_DATA_RECV		0x2
++#define IN_PROGRESS_DDIGEST_RECV	0x3
++
++/* xmit state machine */
++#define XMSTATE_IDLE			0x0
++#define XMSTATE_R_HDR			0x1
++#define XMSTATE_W_HDR			0x2
++#define XMSTATE_IMM_HDR			0x4
++#define XMSTATE_IMM_DATA		0x8
++#define XMSTATE_UNS_INIT		0x10
++#define XMSTATE_UNS_HDR			0x20
++#define XMSTATE_UNS_DATA		0x40
++#define XMSTATE_SOL_HDR			0x80
++#define XMSTATE_SOL_DATA		0x100
++#define XMSTATE_W_PAD			0x200
++#define XMSTATE_W_RESEND_PAD		0x400
++#define XMSTATE_W_RESEND_DATA_DIGEST	0x800
++
++#define ISCSI_PAD_LEN			4
++#define ISCSI_SG_TABLESIZE		SG_ALL
++#define ISCSI_TCP_MAX_CMD_LEN		16
++
+ struct crypto_hash;
+ struct socket;
+-struct iscsi_tcp_conn;
+-struct iscsi_segment;
+-
+-typedef int iscsi_segment_done_fn_t(struct iscsi_tcp_conn *,
+-				    struct iscsi_segment *);
+-
+-struct iscsi_segment {
+-	unsigned char		*data;
+-	unsigned int		size;
+-	unsigned int		copied;
+-	unsigned int		total_size;
+-	unsigned int		total_copied;
+-
+-	struct hash_desc	*hash;
+-	unsigned char		recv_digest[ISCSI_DIGEST_SIZE];
+-	unsigned char		digest[ISCSI_DIGEST_SIZE];
+-	unsigned int		digest_len;
+-
+-	struct scatterlist	*sg;
+-	void			*sg_mapped;
+-	unsigned int		sg_offset;
+-
+-	iscsi_segment_done_fn_t	*done;
+-};
+ 
+ /* Socket connection recieve helper */
+ struct iscsi_tcp_recv {
+ 	struct iscsi_hdr	*hdr;
+-	struct iscsi_segment	segment;
+-
+-	/* Allocate buffer for BHS + AHS */
+-	uint32_t		hdr_buf[64];
++	struct sk_buff		*skb;
++	int			offset;
++	int			len;
++	int			hdr_offset;
++	int			copy;
++	int			copied;
++	int			padding;
++	struct iscsi_cmd_task	*ctask;		/* current cmd in progress */
+ 
+ 	/* copied and flipped values */
+ 	int			datalen;
+-};
+-
+-/* Socket connection send helper */
+-struct iscsi_tcp_send {
+-	struct iscsi_hdr	*hdr;
+-	struct iscsi_segment	segment;
+-	struct iscsi_segment	data_segment;
++	int			datadgst;
++	char			zero_copy_hdr;
+ };
+ 
+ struct iscsi_tcp_conn {
+ 	struct iscsi_conn	*iscsi_conn;
+ 	struct socket		*sock;
++	struct iscsi_hdr	hdr;		/* header placeholder */
++	char			hdrext[4*sizeof(__u16) +
++				    sizeof(__u32)];
++	int			data_copied;
+ 	int			stop_stage;	/* conn_stop() flag: *
+ 						 * stop to recover,  *
+ 						 * stop to terminate */
++	/* iSCSI connection-wide sequencing */
++	int			hdr_size;	/* PDU header size */
++
+ 	/* control data */
+ 	struct iscsi_tcp_recv	in;		/* TCP receive context */
+-	struct iscsi_tcp_send	out;		/* TCP send context */
++	int			in_progress;	/* connection state machine */
+ 
+ 	/* old values for socket callbacks */
+ 	void			(*old_data_ready)(struct sock *, int);
+@@ -93,19 +100,29 @@ struct iscsi_tcp_conn {
+ 	uint32_t		sendpage_failures_cnt;
+ 	uint32_t		discontiguous_hdr_cnt;
+ 
+-	int			error;
+-
+ 	ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int);
+ };
+ 
++struct iscsi_buf {
++	struct scatterlist	sg;
++	unsigned int		sent;
++	char			use_sendmsg;
++};
++
+ struct iscsi_data_task {
+ 	struct iscsi_data	hdr;			/* PDU */
+-	char			hdrext[ISCSI_DIGEST_SIZE];/* Header-Digest */
++	char			hdrext[sizeof(__u32)];	/* Header-Digest */
++	struct iscsi_buf	digestbuf;		/* digest buffer */
++	uint32_t		digest;			/* data digest */
+ };
+ 
+ struct iscsi_tcp_mgmt_task {
+ 	struct iscsi_hdr	hdr;
+-	char			hdrext[ISCSI_DIGEST_SIZE]; /* Header-Digest */
++	char			hdrext[sizeof(__u32)]; /* Header-Digest */
++	int			xmstate;	/* mgmt xmit progress */
++	struct iscsi_buf	headbuf;	/* header buffer */
++	struct iscsi_buf	sendbuf;	/* in progress buffer */
++	int			sent;
+ };
+ 
+ struct iscsi_r2t_info {
+@@ -113,26 +130,38 @@ struct iscsi_r2t_info {
+ 	__be32			exp_statsn;	/* copied from R2T */
+ 	uint32_t		data_length;	/* copied from R2T */
+ 	uint32_t		data_offset;	/* copied from R2T */
++	struct iscsi_buf	headbuf;	/* Data-Out Header Buffer */
++	struct iscsi_buf	sendbuf;	/* Data-Out in progress buffer*/
+ 	int			sent;		/* R2T sequence progress */
+ 	int			data_count;	/* DATA-Out payload progress */
++	struct scatterlist	*sg;		/* per-R2T SG list */
+ 	int			solicit_datasn;
+-	struct iscsi_data_task	dtask;		/* Data-Out header buf */
++	struct iscsi_data_task   dtask;        /* which data task */
+ };
+ 
+ struct iscsi_tcp_cmd_task {
+-	struct iscsi_hdr_buff {
+-		struct iscsi_cmd	cmd_hdr;
+-		char			hdrextbuf[ISCSI_MAX_AHS_SIZE +
+-		                                  ISCSI_DIGEST_SIZE];
+-	} hdr;
+-
++	struct iscsi_cmd	hdr;
++	char			hdrext[4*sizeof(__u16)+	/* AHS */
++				    sizeof(__u32)];	/* HeaderDigest */
++	char			pad[ISCSI_PAD_LEN];
++	int			pad_count;		/* padded bytes */
++	struct iscsi_buf	headbuf;		/* header buf (xmit) */
++	struct iscsi_buf	sendbuf;		/* in progress buffer*/
++	int			xmstate;		/* xmit xtate machine */
+ 	int			sent;
+-	uint32_t		exp_datasn;	/* expected target's R2TSN/DataSN */
++	struct scatterlist	*sg;			/* per-cmd SG list  */
++	struct scatterlist	*bad_sg;		/* assert statement */
++	int			sg_count;		/* SG's to process  */
++	uint32_t		exp_r2tsn;
+ 	int			data_offset;
+-	struct iscsi_r2t_info	*r2t;		/* in progress R2T    */
+-	struct iscsi_pool	r2tpool;
++	struct iscsi_r2t_info	*r2t;			/* in progress R2T    */
++	struct iscsi_queue	r2tpool;
+ 	struct kfifo		*r2tqueue;
+-	struct iscsi_data_task	unsol_dtask;	/* Data-Out header buf */
++	struct iscsi_r2t_info	**r2ts;
++	int			digest_count;
++	uint32_t		immdigest;		/* for imm data */
++	struct iscsi_buf	immbuf;			/* for imm data digest */
++	struct iscsi_data_task	unsol_dtask;	/* unsol data task */
+ };
+ 
+ #endif /* ISCSI_H */
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 010c1b9..3f5b9b4 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -22,9 +22,9 @@
+  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+  */
+ #include <linux/types.h>
++#include <linux/mutex.h>
+ #include <linux/kfifo.h>
+ #include <linux/delay.h>
+-#include <linux/log2.h>
+ #include <asm/unaligned.h>
+ #include <net/tcp.h>
+ #include <scsi/scsi_cmnd.h>
+@@ -46,53 +46,27 @@ class_to_transport_session(struct iscsi_cls_session *cls_session)
+ }
+ EXPORT_SYMBOL_GPL(class_to_transport_session);
+ 
+-/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */
+-#define SNA32_CHECK 2147483648UL
++#define INVALID_SN_DELTA	0xffff
+ 
+-static int iscsi_sna_lt(u32 n1, u32 n2)
+-{
+-	return n1 != n2 && ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) ||
+-			    (n1 > n2 && (n2 - n1 < SNA32_CHECK)));
+-}
+-
+-/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */
+-static int iscsi_sna_lte(u32 n1, u32 n2)
+-{
+-	return n1 == n2 || ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) ||
+-			    (n1 > n2 && (n2 - n1 < SNA32_CHECK)));
+-}
+-
+-void
+-iscsi_update_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr)
++int
++iscsi_check_assign_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr)
+ {
+ 	uint32_t max_cmdsn = be32_to_cpu(hdr->max_cmdsn);
+ 	uint32_t exp_cmdsn = be32_to_cpu(hdr->exp_cmdsn);
+ 
+-	/*
+-	 * standard specifies this check for when to update expected and
+-	 * max sequence numbers
+-	 */
+-	if (iscsi_sna_lt(max_cmdsn, exp_cmdsn - 1))
+-		return;
+-
+-	if (exp_cmdsn != session->exp_cmdsn &&
+-	    !iscsi_sna_lt(exp_cmdsn, session->exp_cmdsn))
++	if (max_cmdsn < exp_cmdsn -1 &&
++	    max_cmdsn > exp_cmdsn - INVALID_SN_DELTA)
++		return ISCSI_ERR_MAX_CMDSN;
++	if (max_cmdsn > session->max_cmdsn ||
++	    max_cmdsn < session->max_cmdsn - INVALID_SN_DELTA)
++		session->max_cmdsn = max_cmdsn;
++	if (exp_cmdsn > session->exp_cmdsn ||
++	    exp_cmdsn < session->exp_cmdsn - INVALID_SN_DELTA)
+ 		session->exp_cmdsn = exp_cmdsn;
+ 
+-	if (max_cmdsn != session->max_cmdsn &&
+-	    !iscsi_sna_lt(max_cmdsn, session->max_cmdsn)) {
+-		session->max_cmdsn = max_cmdsn;
+-		/*
+-		 * if the window closed with IO queued, then kick the
+-		 * xmit thread
+-		 */
+-		if (!list_empty(&session->leadconn->xmitqueue) ||
+-		    !list_empty(&session->leadconn->mgmtqueue))
+-			scsi_queue_work(session->host,
+-					&session->leadconn->xmitwork);
+-	}
++	return 0;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_update_cmdsn);
++EXPORT_SYMBOL_GPL(iscsi_check_assign_cmdsn);
+ 
+ void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *ctask,
+ 				   struct iscsi_data *hdr)
+@@ -123,84 +97,6 @@ void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *ctask,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_prep_unsolicit_data_pdu);
+ 
+-static int iscsi_add_hdr(struct iscsi_cmd_task *ctask, unsigned len)
+-{
+-	unsigned exp_len = ctask->hdr_len + len;
+-
+-	if (exp_len > ctask->hdr_max) {
+-		WARN_ON(1);
+-		return -EINVAL;
+-	}
+-
+-	WARN_ON(len & (ISCSI_PAD_LEN - 1)); /* caller must pad the AHS */
+-	ctask->hdr_len = exp_len;
+-	return 0;
+-}
+-
+-/*
+- * make an extended cdb AHS
+- */
+-static int iscsi_prep_ecdb_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *cmd = ctask->sc;
+-	unsigned rlen, pad_len;
+-	unsigned short ahslength;
+-	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+-	int rc;
+-
+-	ecdb_ahdr = iscsi_next_hdr(ctask);
+-	rlen = cmd->cmd_len - ISCSI_CDB_SIZE;
+-
+-	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));
+-	ahslength = rlen + sizeof(ecdb_ahdr->reserved);
+-
+-	pad_len = iscsi_padding(rlen);
+-
+-	rc = iscsi_add_hdr(ctask, sizeof(ecdb_ahdr->ahslength) +
+-	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);
+-	if (rc)
+-		return rc;
+-
+-	if (pad_len)
+-		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len);
+-
+-	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);
+-	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;
+-	ecdb_ahdr->reserved = 0;
+-	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen);
+-
+-	debug_scsi("iscsi_prep_ecdb_ahs: varlen_cdb_len %d "
+-		   "rlen %d pad_len %d ahs_length %d iscsi_headers_size %u\n",
+-		   cmd->cmd_len, rlen, pad_len, ahslength, ctask->hdr_len);
+-
+-	return 0;
+-}
+-
+-static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+-{
+-	struct scsi_cmnd *sc = ctask->sc;
+-	struct iscsi_rlength_ahdr *rlen_ahdr;
+-	int rc;
+-
+-	rlen_ahdr = iscsi_next_hdr(ctask);
+-	rc = iscsi_add_hdr(ctask, sizeof(*rlen_ahdr));
+-	if (rc)
+-		return rc;
+-
+-	rlen_ahdr->ahslength =
+-		cpu_to_be16(sizeof(rlen_ahdr->read_length) +
+-						  sizeof(rlen_ahdr->reserved));
+-	rlen_ahdr->ahstype = ISCSI_AHSTYPE_RLENGTH;
+-	rlen_ahdr->reserved = 0;
+-	rlen_ahdr->read_length = cpu_to_be32(scsi_in(sc)->length);
+-
+-	debug_scsi("bidi-in rlen_ahdr->read_length(%d) "
+-		   "rlen_ahdr->ahslength(%d)\n",
+-		   be32_to_cpu(rlen_ahdr->read_length),
+-		   be16_to_cpu(rlen_ahdr->ahslength));
+-	return 0;
+-}
+-
+ /**
+  * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu
+  * @ctask: iscsi cmd task
+@@ -208,47 +104,26 @@ static int iscsi_prep_bidi_ahs(struct iscsi_cmd_task *ctask)
+  * Prep basic iSCSI PDU fields for a scsi cmd pdu. The LLD should set
+  * fields like dlength or final based on how much data it sends
+  */
+-static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
++static void iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ {
+ 	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+ 	struct iscsi_cmd *hdr = ctask->hdr;
+ 	struct scsi_cmnd *sc = ctask->sc;
+-	unsigned hdrlength, cmd_len;
+-	int rc;
+-
+-	ctask->hdr_len = 0;
+-	rc = iscsi_add_hdr(ctask, sizeof(*hdr));
+-	if (rc)
+-		return rc;
+-	hdr->opcode = ISCSI_OP_SCSI_CMD;
+-	hdr->flags = ISCSI_ATTR_SIMPLE;
+-	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+-	hdr->itt = build_itt(ctask->itt, session->age);
+-	hdr->cmdsn = cpu_to_be32(session->cmdsn);
+-	session->cmdsn++;
+-	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	cmd_len = sc->cmd_len;
+-	if (cmd_len < ISCSI_CDB_SIZE)
+-		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);
+-	else if (cmd_len > ISCSI_CDB_SIZE) {
+-		rc = iscsi_prep_ecdb_ahs(ctask);
+-		if (rc)
+-			return rc;
+-		cmd_len = ISCSI_CDB_SIZE;
+-	}
+-	memcpy(hdr->cdb, sc->cmnd, cmd_len);
+ 
+-	ctask->imm_count = 0;
+-	if (scsi_bidi_cmnd(sc)) {
+-		hdr->flags |= ISCSI_FLAG_CMD_READ;
+-		rc = iscsi_prep_bidi_ahs(ctask);
+-		if (rc)
+-			return rc;
+-	}
++        hdr->opcode = ISCSI_OP_SCSI_CMD;
++        hdr->flags = ISCSI_ATTR_SIMPLE;
++        int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
++        hdr->itt = build_itt(ctask->itt, conn->id, session->age);
++        hdr->data_length = cpu_to_be32(sc->request_bufflen);
++        hdr->cmdsn = cpu_to_be32(session->cmdsn);
++        session->cmdsn++;
++        hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);
++        memcpy(hdr->cdb, sc->cmnd, sc->cmd_len);
++        memset(&hdr->cdb[sc->cmd_len], 0, MAX_COMMAND_SIZE - sc->cmd_len);
++
++	ctask->data_count = 0;
+ 	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+-		unsigned out_len = scsi_out(sc)->length;
+-		hdr->data_length = cpu_to_be32(out_len);
+ 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+ 		/*
+ 		 * Write counters:
+@@ -264,61 +139,43 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+ 		 *
+ 		 *      pad_count       bytes to be sent as zero-padding
+ 		 */
++		ctask->imm_count = 0;
+ 		ctask->unsol_count = 0;
+ 		ctask->unsol_offset = 0;
+ 		ctask->unsol_datasn = 0;
+ 
+ 		if (session->imm_data_en) {
+-			if (out_len >= session->first_burst)
++			if (ctask->total_length >= session->first_burst)
+ 				ctask->imm_count = min(session->first_burst,
+ 							conn->max_xmit_dlength);
+ 			else
+-				ctask->imm_count = min(out_len,
++				ctask->imm_count = min(ctask->total_length,
+ 							conn->max_xmit_dlength);
+-			hton24(hdr->dlength, ctask->imm_count);
++			hton24(ctask->hdr->dlength, ctask->imm_count);
+ 		} else
+-			zero_data(hdr->dlength);
++			zero_data(ctask->hdr->dlength);
+ 
+ 		if (!session->initial_r2t_en) {
+-			ctask->unsol_count = min(session->first_burst, out_len)
+-							     - ctask->imm_count;
++			ctask->unsol_count = min(session->first_burst,
++				ctask->total_length) - ctask->imm_count;
+ 			ctask->unsol_offset = ctask->imm_count;
+ 		}
+ 
+ 		if (!ctask->unsol_count)
+ 			/* No unsolicit Data-Out's */
+-			hdr->flags |= ISCSI_FLAG_CMD_FINAL;
++			ctask->hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 	} else {
++		ctask->datasn = 0;
+ 		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+ 		zero_data(hdr->dlength);
+-		hdr->data_length = cpu_to_be32(scsi_in(sc)->length);
+ 
+ 		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+ 			hdr->flags |= ISCSI_FLAG_CMD_READ;
+ 	}
+ 
+-	/* calculate size of additional header segments (AHSs) */
+-	hdrlength = ctask->hdr_len - sizeof(*hdr);
+-
+-	WARN_ON(hdrlength & (ISCSI_PAD_LEN-1));
+-	hdrlength /= ISCSI_PAD_LEN;
+-
+-	WARN_ON(hdrlength >= 256);
+-	hdr->hlength = hdrlength & 0xFF;
+-
+-	if (conn->session->tt->init_cmd_task(conn->ctask))
+-		return EIO;
+-
+ 	conn->scsicmd_pdus_cnt++;
+-	debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x "
+-		"len %d bidi_len %d cmdsn %d win %d]\n",
+-		scsi_bidi_cmnd(sc) ? "bidirectional" :
+-		     sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+-		conn->id, sc, sc->cmnd[0], ctask->itt,
+-		scsi_bufflen(sc), scsi_bidi_cmnd(sc) ? scsi_in(sc)->length : 0,
+-		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+-	return 0;
+ }
++EXPORT_SYMBOL_GPL(iscsi_prep_scsi_cmd_pdu);
+ 
+ /**
+  * iscsi_complete_command - return command back to scsi-ml
+@@ -330,16 +187,13 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask)
+  */
+ static void iscsi_complete_command(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_conn *conn = ctask->conn;
+-	struct iscsi_session *session = conn->session;
++	struct iscsi_session *session = ctask->conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 
+ 	ctask->state = ISCSI_TASK_COMPLETED;
+ 	ctask->sc = NULL;
+ 	/* SCSI eh reuses commands to verify us */
+ 	sc->SCp.ptr = NULL;
+-	if (conn->ctask == ctask)
+-		conn->ctask = NULL;
+ 	list_del_init(&ctask->running);
+ 	__kfifo_put(session->cmdpool.queue, (void*)&ctask, sizeof(void*));
+ 	sc->scsi_done(sc);
+@@ -350,124 +204,27 @@ static void __iscsi_get_ctask(struct iscsi_cmd_task *ctask)
+ 	atomic_inc(&ctask->refcount);
+ }
+ 
++static void iscsi_get_ctask(struct iscsi_cmd_task *ctask)
++{
++	spin_lock_bh(&ctask->conn->session->lock);
++	__iscsi_get_ctask(ctask);
++	spin_unlock_bh(&ctask->conn->session->lock);
++}
++
+ static void __iscsi_put_ctask(struct iscsi_cmd_task *ctask)
+ {
+ 	if (atomic_dec_and_test(&ctask->refcount))
+ 		iscsi_complete_command(ctask);
+ }
+ 
+-/*
+- * session lock must be held
+- */
+-static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
+-			 int err)
++static void iscsi_put_ctask(struct iscsi_cmd_task *ctask)
+ {
+-	struct scsi_cmnd *sc;
+-
+-	sc = ctask->sc;
+-	if (!sc)
+-		return;
+-
+-	if (ctask->state == ISCSI_TASK_PENDING)
+-		/*
+-		 * cmd never made it to the xmit thread, so we should not count
+-		 * the cmd in the sequencing
+-		 */
+-		conn->session->queued_cmdsn--;
+-	else
+-		conn->session->tt->cleanup_cmd_task(conn, ctask);
+-
+-	sc->result = err;
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
+-	if (conn->ctask == ctask)
+-		conn->ctask = NULL;
+-	/* release ref from queuecommand */
++	spin_lock_bh(&ctask->conn->session->lock);
+ 	__iscsi_put_ctask(ctask);
++	spin_unlock_bh(&ctask->conn->session->lock);
+ }
+ 
+ /**
+- * iscsi_free_mgmt_task - return mgmt task back to pool
+- * @conn: iscsi connection
+- * @mtask: mtask
+- *
+- * Must be called with session lock.
+- */
+-void iscsi_free_mgmt_task(struct iscsi_conn *conn,
+-			  struct iscsi_mgmt_task *mtask)
+-{
+-	list_del_init(&mtask->running);
+-	if (conn->login_mtask == mtask)
+-		return;
+-
+-	if (conn->ping_mtask == mtask)
+-		conn->ping_mtask = NULL;
+-	__kfifo_put(conn->session->mgmtpool.queue,
+-		    (void*)&mtask, sizeof(void*));
+-}
+-EXPORT_SYMBOL_GPL(iscsi_free_mgmt_task);
+-
+-static struct iscsi_mgmt_task *
+-__iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-		      char *data, uint32_t data_size)
+-{
+-	struct iscsi_session *session = conn->session;
+-	struct iscsi_mgmt_task *mtask;
+-
+-	if (session->state == ISCSI_STATE_TERMINATE)
+-		return NULL;
+-
+-	if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) ||
+-	    hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
+-		/*
+-		 * Login and Text are sent serially, in
+-		 * request-followed-by-response sequence.
+-		 * Same mtask can be used. Same ITT must be used.
+-		 * Note that login_mtask is preallocated at conn_create().
+-		 */
+-		mtask = conn->login_mtask;
+-	else {
+-		BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
+-		BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
+-
+-		if (!__kfifo_get(session->mgmtpool.queue,
+-				 (void*)&mtask, sizeof(void*)))
+-			return NULL;
+-	}
+-
+-	if (data_size) {
+-		memcpy(mtask->data, data, data_size);
+-		mtask->data_count = data_size;
+-	} else
+-		mtask->data_count = 0;
+-
+-	memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr));
+-	INIT_LIST_HEAD(&mtask->running);
+-	list_add_tail(&mtask->running, &conn->mgmtqueue);
+-	return mtask;
+-}
+-
+-int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr,
+-			char *data, uint32_t data_size)
+-{
+-	struct iscsi_conn *conn = cls_conn->dd_data;
+-	struct iscsi_session *session = conn->session;
+-	int err = 0;
+-
+-	spin_lock_bh(&session->lock);
+-	if (!__iscsi_conn_send_pdu(conn, hdr, data, data_size))
+-		err = -EPERM;
+-	spin_unlock_bh(&session->lock);
+-	scsi_queue_work(session->host, &conn->xmitwork);
+-	return err;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
+-
+-/**
+  * iscsi_cmd_rsp - SCSI Command Response processing
+  * @conn: iscsi connection
+  * @hdr: iscsi header
+@@ -478,15 +235,21 @@ EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
+  * iscsi_cmd_rsp sets up the scsi_cmnd fields based on the PDU and
+  * then completes the command and task.
+  **/
+-static void iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-			       struct iscsi_cmd_task *ctask, char *data,
+-			       int datalen)
++static int iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			      struct iscsi_cmd_task *ctask, char *data,
++			      int datalen)
+ {
++	int rc;
+ 	struct iscsi_cmd_rsp *rhdr = (struct iscsi_cmd_rsp *)hdr;
+ 	struct iscsi_session *session = conn->session;
+ 	struct scsi_cmnd *sc = ctask->sc;
+ 
+-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
++	if (rc) {
++		sc->result = DID_ERROR << 16;
++		goto out;
++	}
++
+ 	conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+ 
+ 	sc->result = (DID_OK << 16) | rhdr->cmd_status;
+@@ -501,9 +264,8 @@ static void iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 
+ 		if (datalen < 2) {
+ invalid_datalen:
+-			iscsi_conn_printk(KERN_ERR,  conn,
+-					 "Got CHECK_CONDITION but invalid data "
+-					 "buffer size of %d\n", datalen);
++			printk(KERN_ERR "iscsi: Got CHECK_CONDITION but "
++			       "invalid data buffer size of %d\n", datalen);
+ 			sc->result = DID_BAD_TARGET << 16;
+ 			goto out;
+ 		}
+@@ -518,36 +280,28 @@ invalid_datalen:
+ 			   min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+ 	}
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_BIDI_UNDERFLOW |
+-			   ISCSI_FLAG_CMD_BIDI_OVERFLOW)) {
+-		int res_count = be32_to_cpu(rhdr->bi_residual_count);
+-
+-		if (scsi_bidi_cmnd(sc) && res_count > 0 &&
+-				(rhdr->flags & ISCSI_FLAG_CMD_BIDI_OVERFLOW ||
+-				 res_count <= scsi_in(sc)->length))
+-			scsi_in(sc)->resid = res_count;
+-		else
+-			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	if (sc->sc_data_direction == DMA_TO_DEVICE)
++		goto out;
+ 
+-	if (rhdr->flags & (ISCSI_FLAG_CMD_UNDERFLOW |
+-	                   ISCSI_FLAG_CMD_OVERFLOW)) {
++	if (rhdr->flags & ISCSI_FLAG_CMD_UNDERFLOW) {
+ 		int res_count = be32_to_cpu(rhdr->residual_count);
+ 
+-		if (res_count > 0 &&
+-		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+-		     res_count <= scsi_bufflen(sc)))
+-			/* write side for bidi or uni-io set_resid */
+-			scsi_set_resid(sc, res_count);
++		if (res_count > 0 && res_count <= sc->request_bufflen)
++			sc->resid = res_count;
+ 		else
+ 			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+-	}
++	} else if (rhdr->flags & ISCSI_FLAG_CMD_BIDI_UNDERFLOW)
++		sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
++	else if (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW)
++		sc->resid = be32_to_cpu(rhdr->residual_count);
++
+ out:
+ 	debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ 		   (long)sc, sc->result, ctask->itt);
+ 	conn->scsirsp_pdus_cnt++;
+ 
+ 	__iscsi_put_ctask(ctask);
++	return rc;
+ }
+ 
+ static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+@@ -557,51 +311,18 @@ static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+ 	conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+ 	conn->tmfrsp_pdus_cnt++;
+ 
+-	if (conn->tmf_state != TMF_QUEUED)
++	if (conn->tmabort_state != TMABORT_INITIAL)
+ 		return;
+ 
+ 	if (tmf->response == ISCSI_TMF_RSP_COMPLETE)
+-		conn->tmf_state = TMF_SUCCESS;
++		conn->tmabort_state = TMABORT_SUCCESS;
+ 	else if (tmf->response == ISCSI_TMF_RSP_NO_TASK)
+-		conn->tmf_state = TMF_NOT_FOUND;
++		conn->tmabort_state = TMABORT_NOT_FOUND;
+ 	else
+-		conn->tmf_state = TMF_FAILED;
++		conn->tmabort_state = TMABORT_FAILED;
+ 	wake_up(&conn->ehwait);
+ }
+ 
+-static void iscsi_send_nopout(struct iscsi_conn *conn, struct iscsi_nopin *rhdr)
+-{
+-        struct iscsi_nopout hdr;
+-	struct iscsi_mgmt_task *mtask;
+-
+-	if (!rhdr && conn->ping_mtask)
+-		return;
+-
+-	memset(&hdr, 0, sizeof(struct iscsi_nopout));
+-	hdr.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE;
+-	hdr.flags = ISCSI_FLAG_CMD_FINAL;
+-
+-	if (rhdr) {
+-		memcpy(hdr.lun, rhdr->lun, 8);
+-		hdr.ttt = rhdr->ttt;
+-		hdr.itt = RESERVED_ITT;
+-	} else
+-		hdr.ttt = RESERVED_ITT;
+-
+-	mtask = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)&hdr, NULL, 0);
+-	if (!mtask) {
+-		iscsi_conn_printk(KERN_ERR, conn, "Could not send nopout\n");
+-		return;
+-	}
+-
+-	/* only track our nops */
+-	if (!rhdr) {
+-		conn->ping_mtask = mtask;
+-		conn->last_ping = jiffies;
+-	}
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-
+ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			       char *data, int datalen)
+ {
+@@ -618,10 +339,9 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		if (ntoh24(reject->dlength) >= sizeof(struct iscsi_hdr)) {
+ 			memcpy(&rejected_pdu, data, sizeof(struct iscsi_hdr));
+ 			itt = get_itt(rejected_pdu.itt);
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "itt 0x%x had pdu (op 0x%x) rejected "
+-					  "due to DataDigest error.\n", itt,
+-					  rejected_pdu.opcode);
++			printk(KERN_ERR "itt 0x%x had pdu (op 0x%x) rejected "
++				"due to DataDigest error.\n", itt,
++				rejected_pdu.opcode);
+ 		}
+ 	}
+ 	return 0;
+@@ -638,8 +358,8 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+  * queuecommand or send generic. session lock must be held and verify
+  * itt must have been called.
+  */
+-static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+-				char *data, int datalen)
++int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			 char *data, int datalen)
+ {
+ 	struct iscsi_session *session = conn->session;
+ 	int opcode = hdr->opcode & ISCSI_OPCODE_MASK, rc = 0;
+@@ -647,7 +367,6 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	struct iscsi_mgmt_task *mtask;
+ 	uint32_t itt;
+ 
+-	conn->last_recv = jiffies;
+ 	if (hdr->itt != RESERVED_ITT)
+ 		itt = get_itt(hdr->itt);
+ 	else
+@@ -662,8 +381,8 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		switch(opcode) {
+ 		case ISCSI_OP_SCSI_CMD_RSP:
+ 			BUG_ON((void*)ctask != ctask->sc->SCp.ptr);
+-			iscsi_scsi_cmd_rsp(conn, hdr, ctask, data,
+-					   datalen);
++			rc = iscsi_scsi_cmd_rsp(conn, hdr, ctask, data,
++						datalen);
+ 			break;
+ 		case ISCSI_OP_SCSI_DATA_IN:
+ 			BUG_ON((void*)ctask != ctask->sc->SCp.ptr);
+@@ -686,7 +405,11 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		debug_scsi("immrsp [op 0x%x cid %d itt 0x%x len %d]\n",
+ 			   opcode, conn->id, mtask->itt, datalen);
+ 
+-		iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr);
++		rc = iscsi_check_assign_cmdsn(session,
++					      (struct iscsi_nopin*)hdr);
++		if (rc)
++			goto done;
++
+ 		switch(opcode) {
+ 		case ISCSI_OP_LOGOUT_RSP:
+ 			if (datalen) {
+@@ -703,7 +426,10 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			 */
+ 			if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen))
+ 				rc = ISCSI_ERR_CONN_FAILED;
+-			iscsi_free_mgmt_task(conn, mtask);
++			list_del(&mtask->running);
++			if (conn->login_mtask != mtask)
++				__kfifo_put(session->mgmtpool.queue,
++					    (void*)&mtask, sizeof(void*));
+ 			break;
+ 		case ISCSI_OP_SCSI_TMFUNC_RSP:
+ 			if (datalen) {
+@@ -712,33 +438,30 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			}
+ 
+ 			iscsi_tmf_rsp(conn, hdr);
+-			iscsi_free_mgmt_task(conn, mtask);
+ 			break;
+ 		case ISCSI_OP_NOOP_IN:
+-			if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) ||
+-			    datalen) {
++			if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) || datalen) {
+ 				rc = ISCSI_ERR_PROTO;
+ 				break;
+ 			}
+ 			conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+ 
+-			if (conn->ping_mtask != mtask) {
+-				/*
+-				 * If this is not in response to one of our
+-				 * nops then it must be from userspace.
+-				 */
+-				if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+-						   datalen))
+-					rc = ISCSI_ERR_CONN_FAILED;
+-			}
+-			iscsi_free_mgmt_task(conn, mtask);
++			if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen))
++				rc = ISCSI_ERR_CONN_FAILED;
++			list_del(&mtask->running);
++			if (conn->login_mtask != mtask)
++				__kfifo_put(session->mgmtpool.queue,
++					    (void*)&mtask, sizeof(void*));
+ 			break;
+ 		default:
+ 			rc = ISCSI_ERR_BAD_OPCODE;
+ 			break;
+ 		}
+ 	} else if (itt == ~0U) {
+-		iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr);
++		rc = iscsi_check_assign_cmdsn(session,
++					     (struct iscsi_nopin*)hdr);
++		if (rc)
++			goto done;
+ 
+ 		switch(opcode) {
+ 		case ISCSI_OP_NOOP_IN:
+@@ -750,7 +473,8 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 			if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG))
+ 				break;
+ 
+-			iscsi_send_nopout(conn, (struct iscsi_nopin*)hdr);
++			if (iscsi_recv_pdu(conn->cls_conn, hdr, NULL, 0))
++				rc = ISCSI_ERR_CONN_FAILED;
+ 			break;
+ 		case ISCSI_OP_REJECT:
+ 			rc = iscsi_handle_reject(conn, hdr, data, datalen);
+@@ -767,8 +491,10 @@ static int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	} else
+ 		rc = ISCSI_ERR_BAD_ITT;
+ 
++done:
+ 	return rc;
+ }
++EXPORT_SYMBOL_GPL(__iscsi_complete_pdu);
+ 
+ int iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		       char *data, int datalen)
+@@ -793,13 +519,18 @@ int iscsi_verify_itt(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	if (hdr->itt != RESERVED_ITT) {
+ 		if (((__force u32)hdr->itt & ISCSI_AGE_MASK) !=
+ 		    (session->age << ISCSI_AGE_SHIFT)) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "received itt %x expected session "
+-					  "age (%x)\n", (__force u32)hdr->itt,
+-					  session->age & ISCSI_AGE_MASK);
++			printk(KERN_ERR "iscsi: received itt %x expected "
++				"session age (%x)\n", (__force u32)hdr->itt,
++				session->age & ISCSI_AGE_MASK);
+ 			return ISCSI_ERR_BAD_ITT;
+ 		}
+ 
++		if (((__force u32)hdr->itt & ISCSI_CID_MASK) !=
++		    (conn->id << ISCSI_CID_SHIFT)) {
++			printk(KERN_ERR "iscsi: received itt %x, expected "
++				"CID (%x)\n", (__force u32)hdr->itt, conn->id);
++			return ISCSI_ERR_BAD_ITT;
++		}
+ 		itt = get_itt(hdr->itt);
+ 	} else
+ 		itt = ~0U;
+@@ -808,17 +539,16 @@ int iscsi_verify_itt(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 		ctask = session->cmds[itt];
+ 
+ 		if (!ctask->sc) {
+-			iscsi_conn_printk(KERN_INFO, conn, "dropping ctask "
+-					  "with itt 0x%x\n", ctask->itt);
++			printk(KERN_INFO "iscsi: dropping ctask with "
++			       "itt 0x%x\n", ctask->itt);
+ 			/* force drop */
+ 			return ISCSI_ERR_NO_SCSI_CMD;
+ 		}
+ 
+ 		if (ctask->sc->SCp.phase != session->age) {
+-			iscsi_conn_printk(KERN_ERR, conn,
+-					  "iscsi: ctask's session age %d, "
+-					  "expected %d\n", ctask->sc->SCp.phase,
+-					  session->age);
++			printk(KERN_ERR "iscsi: ctask's session age %d, "
++				"expected %d\n", ctask->sc->SCp.phase,
++				session->age);
+ 			return ISCSI_ERR_SESSION_FAILED;
+ 		}
+ 	}
+@@ -848,110 +578,30 @@ void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_failure);
+ 
+-static void iscsi_prep_mtask(struct iscsi_conn *conn,
+-			     struct iscsi_mgmt_task *mtask)
+-{
+-	struct iscsi_session *session = conn->session;
+-	struct iscsi_hdr *hdr = mtask->hdr;
+-	struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr;
+-
+-	if (hdr->opcode != (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) &&
+-	    hdr->opcode != (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
+-		nop->exp_statsn = cpu_to_be32(conn->exp_statsn);
+-	/*
+-	 * pre-format CmdSN for outgoing PDU.
+-	 */
+-	nop->cmdsn = cpu_to_be32(session->cmdsn);
+-	if (hdr->itt != RESERVED_ITT) {
+-		hdr->itt = build_itt(mtask->itt, session->age);
+-		/*
+-		 * TODO: We always use immediate, so we never hit this.
+-		 * If we start to send tmfs or nops as non-immediate then
+-		 * we should start checking the cmdsn numbers for mgmt tasks.
+-		 */
+-		if (conn->c_stage == ISCSI_CONN_STARTED &&
+-		    !(hdr->opcode & ISCSI_OP_IMMEDIATE)) {
+-			session->queued_cmdsn++;
+-			session->cmdsn++;
+-		}
+-	}
+-
+-	if (session->tt->init_mgmt_task)
+-		session->tt->init_mgmt_task(conn, mtask);
+-
+-	debug_scsi("mgmtpdu [op 0x%x hdr->itt 0x%x datalen %d]\n",
+-		   hdr->opcode & ISCSI_OPCODE_MASK, hdr->itt,
+-		   mtask->data_count);
+-}
+-
+ static int iscsi_xmit_mtask(struct iscsi_conn *conn)
+ {
+ 	struct iscsi_hdr *hdr = conn->mtask->hdr;
+-	int rc;
+-
+-	if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT)
+-		conn->session->state = ISCSI_STATE_LOGGING_OUT;
+-	spin_unlock_bh(&conn->session->lock);
++	int rc, was_logout = 0;
+ 
++	if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT) {
++		conn->session->state = ISCSI_STATE_IN_RECOVERY;
++		iscsi_block_session(session_to_cls(conn->session));
++		was_logout = 1;
++	}
+ 	rc = conn->session->tt->xmit_mgmt_task(conn, conn->mtask);
+-	spin_lock_bh(&conn->session->lock);
+ 	if (rc)
+ 		return rc;
+ 
+ 	/* done with this in-progress mtask */
+ 	conn->mtask = NULL;
+-	return 0;
+-}
+-
+-static int iscsi_check_cmdsn_window_closed(struct iscsi_conn *conn)
+-{
+-	struct iscsi_session *session = conn->session;
+ 
+-	/*
+-	 * Check for iSCSI window and take care of CmdSN wrap-around
+-	 */
+-	if (!iscsi_sna_lte(session->queued_cmdsn, session->max_cmdsn)) {
+-		debug_scsi("iSCSI CmdSN closed. ExpCmdSn %u MaxCmdSN %u "
+-			   "CmdSN %u/%u\n", session->exp_cmdsn,
+-			   session->max_cmdsn, session->cmdsn,
+-			   session->queued_cmdsn);
+-		return -ENOSPC;
++	if (was_logout) {
++		set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
++		return -ENODATA;
+ 	}
+ 	return 0;
+ }
+ 
+-static int iscsi_xmit_ctask(struct iscsi_conn *conn)
+-{
+-	struct iscsi_cmd_task *ctask = conn->ctask;
+-	int rc;
+-
+-	__iscsi_get_ctask(ctask);
+-	spin_unlock_bh(&conn->session->lock);
+-	rc = conn->session->tt->xmit_cmd_task(conn, ctask);
+-	spin_lock_bh(&conn->session->lock);
+-	__iscsi_put_ctask(ctask);
+-	if (!rc)
+-		/* done with this ctask */
+-		conn->ctask = NULL;
+-	return rc;
+-}
+-
+-/**
+- * iscsi_requeue_ctask - requeue ctask to run from session workqueue
+- * @ctask: ctask to requeue
+- *
+- * LLDs that need to run a ctask from the session workqueue should call
+- * this. The session lock must be held.
+- */
+-void iscsi_requeue_ctask(struct iscsi_cmd_task *ctask)
+-{
+-	struct iscsi_conn *conn = ctask->conn;
+-
+-	list_move_tail(&ctask->running, &conn->requeue);
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-EXPORT_SYMBOL_GPL(iscsi_requeue_ctask);
+-
+ /**
+  * iscsi_data_xmit - xmit any command into the scheduled connection
+  * @conn: iscsi connection
+@@ -963,106 +613,106 @@ EXPORT_SYMBOL_GPL(iscsi_requeue_ctask);
+  **/
+ static int iscsi_data_xmit(struct iscsi_conn *conn)
+ {
++	struct iscsi_transport *tt;
+ 	int rc = 0;
+ 
+-	spin_lock_bh(&conn->session->lock);
+ 	if (unlikely(conn->suspend_tx)) {
+ 		debug_scsi("conn %d Tx suspended!\n", conn->id);
+-		spin_unlock_bh(&conn->session->lock);
+ 		return -ENODATA;
+ 	}
++	tt = conn->session->tt;
++
++	/*
++	 * Transmit in the following order:
++	 *
++	 * 1) un-finished xmit (ctask or mtask)
++	 * 2) immediate control PDUs
++	 * 3) write data
++	 * 4) SCSI commands
++	 * 5) non-immediate control PDUs
++	 *
++	 * No need to lock around __kfifo_get as long as
++	 * there's one producer and one consumer.
++	 */
++
++	BUG_ON(conn->ctask && conn->mtask);
+ 
+ 	if (conn->ctask) {
+-		rc = iscsi_xmit_ctask(conn);
++		iscsi_get_ctask(conn->ctask);
++		rc = tt->xmit_cmd_task(conn, conn->ctask);
++		iscsi_put_ctask(conn->ctask);
+ 		if (rc)
+ 			goto again;
++		/* done with this in-progress ctask */
++		conn->ctask = NULL;
+ 	}
+-
+ 	if (conn->mtask) {
+ 		rc = iscsi_xmit_mtask(conn);
+ 	        if (rc)
+ 		        goto again;
+ 	}
+ 
+-	/*
+-	 * process mgmt pdus like nops before commands since we should
+-	 * only have one nop-out as a ping from us and targets should not
+-	 * overflow us with nop-ins
+-	 */
+-check_mgmt:
+-	while (!list_empty(&conn->mgmtqueue)) {
+-		conn->mtask = list_entry(conn->mgmtqueue.next,
+-					 struct iscsi_mgmt_task, running);
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
+-			iscsi_free_mgmt_task(conn, conn->mtask);
+-			conn->mtask = NULL;
+-			continue;
+-		}
+-
+-		iscsi_prep_mtask(conn, conn->mtask);
+-		list_move_tail(conn->mgmtqueue.next, &conn->mgmt_run_list);
+-		rc = iscsi_xmit_mtask(conn);
+-		if (rc)
+-			goto again;
++	/* process immediate first */
++        if (unlikely(__kfifo_len(conn->immqueue))) {
++	        while (__kfifo_get(conn->immqueue, (void*)&conn->mtask,
++			           sizeof(void*))) {
++			spin_lock_bh(&conn->session->lock);
++			list_add_tail(&conn->mtask->running,
++				      &conn->mgmt_run_list);
++			spin_unlock_bh(&conn->session->lock);
++			rc = iscsi_xmit_mtask(conn);
++		        if (rc)
++			        goto again;
++	        }
+ 	}
+ 
+-	/* process pending command queue */
++	/* process command queue */
++	spin_lock_bh(&conn->session->lock);
+ 	while (!list_empty(&conn->xmitqueue)) {
+-		if (conn->tmf_state == TMF_QUEUED)
+-			break;
+-
++		/*
++		 * iscsi tcp may readd the task to the xmitqueue to send
++		 * write data
++		 */
+ 		conn->ctask = list_entry(conn->xmitqueue.next,
+ 					 struct iscsi_cmd_task, running);
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
+-			fail_command(conn, conn->ctask, DID_IMM_RETRY << 16);
+-			continue;
+-		}
+-		if (iscsi_prep_scsi_cmd_pdu(conn->ctask)) {
+-			fail_command(conn, conn->ctask, DID_ABORT << 16);
+-			continue;
+-		}
+-
+ 		conn->ctask->state = ISCSI_TASK_RUNNING;
+ 		list_move_tail(conn->xmitqueue.next, &conn->run_list);
+-		rc = iscsi_xmit_ctask(conn);
+-		if (rc)
+-			goto again;
+-		/*
+-		 * we could continuously get new ctask requests so
+-		 * we need to check the mgmt queue for nops that need to
+-		 * be sent to aviod starvation
+-		 */
+-		if (!list_empty(&conn->mgmtqueue))
+-			goto check_mgmt;
+-	}
+-
+-	while (!list_empty(&conn->requeue)) {
+-		if (conn->session->fast_abort && conn->tmf_state != TMF_INITIAL)
+-			break;
++		__iscsi_get_ctask(conn->ctask);
++		spin_unlock_bh(&conn->session->lock);
+ 
+-		/*
+-		 * we always do fastlogout - conn stop code will clean up.
+-		 */
+-		if (conn->session->state == ISCSI_STATE_LOGGING_OUT)
+-			break;
++		rc = tt->xmit_cmd_task(conn, conn->ctask);
+ 
+-		conn->ctask = list_entry(conn->requeue.next,
+-					 struct iscsi_cmd_task, running);
+-		conn->ctask->state = ISCSI_TASK_RUNNING;
+-		list_move_tail(conn->requeue.next, &conn->run_list);
+-		rc = iscsi_xmit_ctask(conn);
+-		if (rc)
++		spin_lock_bh(&conn->session->lock);
++		__iscsi_put_ctask(conn->ctask);
++		if (rc) {
++			spin_unlock_bh(&conn->session->lock);
+ 			goto again;
+-		if (!list_empty(&conn->mgmtqueue))
+-			goto check_mgmt;
++		}
+ 	}
+ 	spin_unlock_bh(&conn->session->lock);
++	/* done with this ctask */
++	conn->ctask = NULL;
++
++	/* process the rest control plane PDUs, if any */
++        if (unlikely(__kfifo_len(conn->mgmtqueue))) {
++	        while (__kfifo_get(conn->mgmtqueue, (void*)&conn->mtask,
++			           sizeof(void*))) {
++			spin_lock_bh(&conn->session->lock);
++			list_add_tail(&conn->mtask->running,
++				      &conn->mgmt_run_list);
++			spin_unlock_bh(&conn->session->lock);
++			rc = iscsi_xmit_mtask(conn);
++		        if (rc)
++			        goto again;
++	        }
++	}
++
+ 	return -ENODATA;
+ 
+ again:
+ 	if (unlikely(conn->suspend_tx))
+-		rc = -ENODATA;
+-	spin_unlock_bh(&conn->session->lock);
++		return -ENODATA;
++
+ 	return rc;
+ }
+ 
+@@ -1074,9 +724,11 @@ static void iscsi_xmitworker(struct work_struct *work)
+ 	/*
+ 	 * serialize Xmit worker on a per-connection basis.
+ 	 */
++	mutex_lock(&conn->xmitmutex);
+ 	do {
+ 		rc = iscsi_data_xmit(conn);
+ 	} while (rc >= 0 || rc == -EAGAIN);
++	mutex_unlock(&conn->xmitmutex);
+ }
+ 
+ enum {
+@@ -1088,8 +740,6 @@ enum {
+ 	FAILURE_SESSION_TERMINATE,
+ 	FAILURE_SESSION_IN_RECOVERY,
+ 	FAILURE_SESSION_RECOVERY_TIMEOUT,
+-	FAILURE_SESSION_LOGGING_OUT,
+-	FAILURE_SESSION_NOT_READY,
+ };
+ 
+ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+@@ -1105,16 +755,9 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 	sc->SCp.ptr = NULL;
+ 
+ 	host = sc->device->host;
+-	spin_unlock(host->host_lock);
+-
+ 	session = iscsi_hostdata(host->hostdata);
+-	spin_lock(&session->lock);
+ 
+-	reason = iscsi_session_chkready(session_to_cls(session));
+-	if (reason) {
+-		sc->result = reason;
+-		goto fault;
+-	}
++	spin_lock(&session->lock);
+ 
+ 	/*
+ 	 * ISCSI_STATE_FAILED is a temp. state. The recovery
+@@ -1129,82 +772,77 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 		 * be entering our queuecommand while a block is starting
+ 		 * up because the block code is not locked)
+ 		 */
+-		switch (session->state) {
+-		case ISCSI_STATE_IN_RECOVERY:
++		if (session->state == ISCSI_STATE_IN_RECOVERY) {
+ 			reason = FAILURE_SESSION_IN_RECOVERY;
+-			sc->result = DID_IMM_RETRY << 16;
+-			break;
+-		case ISCSI_STATE_LOGGING_OUT:
+-			reason = FAILURE_SESSION_LOGGING_OUT;
+-			sc->result = DID_IMM_RETRY << 16;
+-			break;
+-		case ISCSI_STATE_RECOVERY_FAILED:
++			goto reject;
++		}
++
++		if (session->state == ISCSI_STATE_RECOVERY_FAILED)
+ 			reason = FAILURE_SESSION_RECOVERY_TIMEOUT;
+-			sc->result = DID_NO_CONNECT << 16;
+-			break;
+-		case ISCSI_STATE_TERMINATE:
++		else if (session->state == ISCSI_STATE_TERMINATE)
+ 			reason = FAILURE_SESSION_TERMINATE;
+-			sc->result = DID_NO_CONNECT << 16;
+-			break;
+-		default:
++		else
+ 			reason = FAILURE_SESSION_FREED;
+-			sc->result = DID_NO_CONNECT << 16;
+-		}
+ 		goto fault;
+ 	}
+ 
++	/*
++	 * Check for iSCSI window and take care of CmdSN wrap-around
++	 */
++	if ((int)(session->max_cmdsn - session->cmdsn) < 0) {
++		reason = FAILURE_WINDOW_CLOSED;
++		goto reject;
++	}
++
+ 	conn = session->leadconn;
+ 	if (!conn) {
+ 		reason = FAILURE_SESSION_FREED;
+-		sc->result = DID_NO_CONNECT << 16;
+ 		goto fault;
+ 	}
+ 
+-	if (iscsi_check_cmdsn_window_closed(conn)) {
+-		reason = FAILURE_WINDOW_CLOSED;
+-		goto reject;
+-	}
+-
+ 	if (!__kfifo_get(session->cmdpool.queue, (void*)&ctask,
+ 			 sizeof(void*))) {
+ 		reason = FAILURE_OOM;
+ 		goto reject;
+ 	}
+-	session->queued_cmdsn++;
+-
+ 	sc->SCp.phase = session->age;
+ 	sc->SCp.ptr = (char *)ctask;
+ 
+ 	atomic_set(&ctask->refcount, 1);
+ 	ctask->state = ISCSI_TASK_PENDING;
++	ctask->mtask = NULL;
+ 	ctask->conn = conn;
+ 	ctask->sc = sc;
+ 	INIT_LIST_HEAD(&ctask->running);
++	ctask->total_length = sc->request_bufflen;
++	iscsi_prep_scsi_cmd_pdu(ctask);
++
++	session->tt->init_cmd_task(ctask);
+ 
+ 	list_add_tail(&ctask->running, &conn->xmitqueue);
++	debug_scsi(
++	       "ctask enq [%s cid %d sc %p cdb 0x%x itt 0x%x len %d cmdsn %d "
++		"win %d]\n",
++		sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
++		conn->id, sc, sc->cmnd[0], ctask->itt, sc->request_bufflen,
++		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	spin_unlock(&session->lock);
+ 
+ 	scsi_queue_work(host, &conn->xmitwork);
+-	spin_lock(host->host_lock);
+ 	return 0;
+ 
+ reject:
+ 	spin_unlock(&session->lock);
+ 	debug_scsi("cmd 0x%x rejected (%d)\n", sc->cmnd[0], reason);
+-	spin_lock(host->host_lock);
+ 	return SCSI_MLQUEUE_HOST_BUSY;
+ 
+ fault:
+ 	spin_unlock(&session->lock);
+-	debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+-	if (!scsi_bidi_cmnd(sc))
+-		scsi_set_resid(sc, scsi_bufflen(sc));
+-	else {
+-		scsi_out(sc)->resid = scsi_out(sc)->length;
+-		scsi_in(sc)->resid = scsi_in(sc)->length;
+-	}
++	printk(KERN_ERR "iscsi: cmd 0x%x is not queued (%d)\n",
++	       sc->cmnd[0], reason);
++	sc->result = (DID_NO_CONNECT << 16);
++	sc->resid = sc->request_bufflen;
+ 	sc->scsi_done(sc);
+-	spin_lock(host->host_lock);
+ 	return 0;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_queuecommand);
+@@ -1218,15 +856,106 @@ int iscsi_change_queue_depth(struct scsi_device *sdev, int depth)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_change_queue_depth);
+ 
++static int
++iscsi_conn_send_generic(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
++			char *data, uint32_t data_size)
++{
++	struct iscsi_session *session = conn->session;
++	struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr;
++	struct iscsi_mgmt_task *mtask;
++
++	spin_lock_bh(&session->lock);
++	if (session->state == ISCSI_STATE_TERMINATE) {
++		spin_unlock_bh(&session->lock);
++		return -EPERM;
++	}
++	if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) ||
++	    hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
++		/*
++		 * Login and Text are sent serially, in
++		 * request-followed-by-response sequence.
++		 * Same mtask can be used. Same ITT must be used.
++		 * Note that login_mtask is preallocated at conn_create().
++		 */
++		mtask = conn->login_mtask;
++	else {
++		BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
++		BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
++
++		nop->exp_statsn = cpu_to_be32(conn->exp_statsn);
++		if (!__kfifo_get(session->mgmtpool.queue,
++				 (void*)&mtask, sizeof(void*))) {
++			spin_unlock_bh(&session->lock);
++			return -ENOSPC;
++		}
++	}
++
++	/*
++	 * pre-format CmdSN for outgoing PDU.
++	 */
++	if (hdr->itt != RESERVED_ITT) {
++		hdr->itt = build_itt(mtask->itt, conn->id, session->age);
++		nop->cmdsn = cpu_to_be32(session->cmdsn);
++		if (conn->c_stage == ISCSI_CONN_STARTED &&
++		    !(hdr->opcode & ISCSI_OP_IMMEDIATE))
++			session->cmdsn++;
++	} else
++		/* do not advance CmdSN */
++		nop->cmdsn = cpu_to_be32(session->cmdsn);
++
++	if (data_size) {
++		memcpy(mtask->data, data, data_size);
++		mtask->data_count = data_size;
++	} else
++		mtask->data_count = 0;
++
++	INIT_LIST_HEAD(&mtask->running);
++	memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr));
++	if (session->tt->init_mgmt_task)
++		session->tt->init_mgmt_task(conn, mtask, data, data_size);
++	spin_unlock_bh(&session->lock);
++
++	debug_scsi("mgmtpdu [op 0x%x hdr->itt 0x%x datalen %d]\n",
++		   hdr->opcode, hdr->itt, data_size);
++
++	/*
++	 * since send_pdu() could be called at least from two contexts,
++	 * we need to serialize __kfifo_put, so we don't have to take
++	 * additional lock on fast data-path
++	 */
++        if (hdr->opcode & ISCSI_OP_IMMEDIATE)
++	        __kfifo_put(conn->immqueue, (void*)&mtask, sizeof(void*));
++	else
++	        __kfifo_put(conn->mgmtqueue, (void*)&mtask, sizeof(void*));
++
++	scsi_queue_work(session->host, &conn->xmitwork);
++	return 0;
++}
++
++int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr,
++			char *data, uint32_t data_size)
++{
++	struct iscsi_conn *conn = cls_conn->dd_data;
++	int rc;
++
++	mutex_lock(&conn->xmitmutex);
++	rc = iscsi_conn_send_generic(conn, hdr, data, data_size);
++	mutex_unlock(&conn->xmitmutex);
++
++	return rc;
++}
++EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
++
+ void iscsi_session_recovery_timedout(struct iscsi_cls_session *cls_session)
+ {
+ 	struct iscsi_session *session = class_to_transport_session(cls_session);
++	struct iscsi_conn *conn = session->leadconn;
+ 
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state != ISCSI_STATE_LOGGED_IN) {
+ 		session->state = ISCSI_STATE_RECOVERY_FAILED;
+-		if (session->leadconn)
+-			wake_up(&session->leadconn->ehwait);
++		if (conn)
++			wake_up(&conn->ehwait);
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ }
+@@ -1237,25 +966,30 @@ int iscsi_eh_host_reset(struct scsi_cmnd *sc)
+ 	struct Scsi_Host *host = sc->device->host;
+ 	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+ 	struct iscsi_conn *conn = session->leadconn;
++	int fail_session = 0;
+ 
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_TERMINATE) {
+ failed:
+ 		debug_scsi("failing host reset: session terminated "
+ 			   "[CID %d age %d]\n", conn->id, session->age);
+ 		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return FAILED;
+ 	}
+ 
++	if (sc->SCp.phase == session->age) {
++		debug_scsi("failing connection CID %d due to SCSI host reset\n",
++			   conn->id);
++		fail_session = 1;
++	}
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
+ 	/*
+ 	 * we drop the lock here but the leadconn cannot be destoyed while
+ 	 * we are in the scsi eh
+ 	 */
+-	iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	if (fail_session)
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+ 
+ 	debug_scsi("iscsi_eh_host_reset wait for relogin\n");
+ 	wait_event_interruptible(conn->ehwait,
+@@ -1265,497 +999,324 @@ failed:
+ 	if (signal_pending(current))
+ 		flush_signals(current);
+ 
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_LOGGED_IN)
+-		iscsi_session_printk(KERN_INFO, session,
+-				     "host reset succeeded\n");
++		printk(KERN_INFO "iscsi: host reset succeeded\n");
+ 	else
+ 		goto failed;
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
+ 	return SUCCESS;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_eh_host_reset);
+ 
+-static void iscsi_tmf_timedout(unsigned long data)
++static void iscsi_tmabort_timedout(unsigned long data)
+ {
+-	struct iscsi_conn *conn = (struct iscsi_conn *)data;
++	struct iscsi_cmd_task *ctask = (struct iscsi_cmd_task *)data;
++	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+ 
+ 	spin_lock(&session->lock);
+-	if (conn->tmf_state == TMF_QUEUED) {
+-		conn->tmf_state = TMF_TIMEDOUT;
+-		debug_scsi("tmf timedout\n");
++	if (conn->tmabort_state == TMABORT_INITIAL) {
++		conn->tmabort_state = TMABORT_TIMEDOUT;
++		debug_scsi("tmabort timedout [sc %p itt 0x%x]\n",
++			ctask->sc, ctask->itt);
+ 		/* unblock eh_abort() */
+ 		wake_up(&conn->ehwait);
+ 	}
+ 	spin_unlock(&session->lock);
+ }
+ 
+-static int iscsi_exec_task_mgmt_fn(struct iscsi_conn *conn,
+-				   struct iscsi_tm *hdr, int age,
+-				   int timeout)
++/* must be called with the mutex lock */
++static int iscsi_exec_abort_task(struct scsi_cmnd *sc,
++				 struct iscsi_cmd_task *ctask)
+ {
++	struct iscsi_conn *conn = ctask->conn;
+ 	struct iscsi_session *session = conn->session;
+-	struct iscsi_mgmt_task *mtask;
++	struct iscsi_tm *hdr = &conn->tmhdr;
++	int rc;
+ 
+-	mtask = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)hdr,
+-				      NULL, 0);
+-	if (!mtask) {
+-		spin_unlock_bh(&session->lock);
++	/*
++	 * ctask timed out but session is OK requests must be serialized.
++	 */
++	memset(hdr, 0, sizeof(struct iscsi_tm));
++	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
++	hdr->flags = ISCSI_TM_FUNC_ABORT_TASK;
++	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
++	memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun));
++	hdr->rtt = ctask->hdr->itt;
++	hdr->refcmdsn = ctask->hdr->cmdsn;
++
++	rc = iscsi_conn_send_generic(conn, (struct iscsi_hdr *)hdr,
++				     NULL, 0);
++	if (rc) {
+ 		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		spin_lock_bh(&session->lock);
+-		debug_scsi("tmf exec failure\n");
+-		return -EPERM;
++		debug_scsi("abort sent failure [itt 0x%x] %d\n", ctask->itt,
++		           rc);
++		return rc;
+ 	}
+-	conn->tmfcmd_pdus_cnt++;
+-	conn->tmf_timer.expires = timeout * HZ + jiffies;
+-	conn->tmf_timer.function = iscsi_tmf_timedout;
+-	conn->tmf_timer.data = (unsigned long)conn;
+-	add_timer(&conn->tmf_timer);
+-	debug_scsi("tmf set timeout\n");
+ 
++	debug_scsi("abort sent [itt 0x%x]\n", ctask->itt);
++
++	spin_lock_bh(&session->lock);
++	ctask->mtask = (struct iscsi_mgmt_task *)
++			session->mgmt_cmds[get_itt(hdr->itt) -
++					ISCSI_MGMT_ITT_OFFSET];
++
++	if (conn->tmabort_state == TMABORT_INITIAL) {
++		conn->tmfcmd_pdus_cnt++;
++		conn->tmabort_timer.expires = 10*HZ + jiffies;
++		conn->tmabort_timer.function = iscsi_tmabort_timedout;
++		conn->tmabort_timer.data = (unsigned long)ctask;
++		add_timer(&conn->tmabort_timer);
++		debug_scsi("abort set timeout [itt 0x%x]\n", ctask->itt);
++	}
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
+-	scsi_queue_work(session->host, &conn->xmitwork);
++	mutex_unlock(&conn->xmitmutex);
+ 
+ 	/*
+ 	 * block eh thread until:
+ 	 *
+-	 * 1) tmf response
+-	 * 2) tmf timeout
++	 * 1) abort response
++	 * 2) abort timeout
+ 	 * 3) session is terminated or restarted or userspace has
+ 	 * given up on recovery
+ 	 */
+-	wait_event_interruptible(conn->ehwait, age != session->age ||
++	wait_event_interruptible(conn->ehwait,
++				 sc->SCp.phase != session->age ||
+ 				 session->state != ISCSI_STATE_LOGGED_IN ||
+-				 conn->tmf_state != TMF_QUEUED);
++				 conn->tmabort_state != TMABORT_INITIAL);
+ 	if (signal_pending(current))
+ 		flush_signals(current);
+-	del_timer_sync(&conn->tmf_timer);
++	del_timer_sync(&conn->tmabort_timer);
+ 
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+-	/* if the session drops it will clean up the mtask */
+-	if (age != session->age ||
+-	    session->state != ISCSI_STATE_LOGGED_IN)
+-		return -ENOTCONN;
++	mutex_lock(&conn->xmitmutex);
+ 	return 0;
+ }
+ 
+ /*
+- * Fail commands. session lock held and recv side suspended and xmit
+- * thread flushed
++ * xmit mutex and session lock must be held
+  */
+-static void fail_all_commands(struct iscsi_conn *conn, unsigned lun,
+-			      int error)
++static struct iscsi_mgmt_task *
++iscsi_remove_mgmt_task(struct kfifo *fifo, uint32_t itt)
+ {
+-	struct iscsi_cmd_task *ctask, *tmp;
++	int i, nr_tasks = __kfifo_len(fifo) / sizeof(void*);
++	struct iscsi_mgmt_task *task;
+ 
+-	if (conn->ctask && (conn->ctask->sc->device->lun == lun || lun == -1))
+-		conn->ctask = NULL;
++	debug_scsi("searching %d tasks\n", nr_tasks);
+ 
+-	/* flush pending */
+-	list_for_each_entry_safe(ctask, tmp, &conn->xmitqueue, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing pending sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, error << 16);
+-		}
+-	}
++	for (i = 0; i < nr_tasks; i++) {
++		__kfifo_get(fifo, (void*)&task, sizeof(void*));
++		debug_scsi("check task %u\n", task->itt);
+ 
+-	list_for_each_entry_safe(ctask, tmp, &conn->requeue, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing requeued sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, error << 16);
++		if (task->itt == itt) {
++			debug_scsi("matched task\n");
++			return task;
+ 		}
+-	}
+ 
+-	/* fail all other running */
+-	list_for_each_entry_safe(ctask, tmp, &conn->run_list, running) {
+-		if (lun == ctask->sc->device->lun || lun == -1) {
+-			debug_scsi("failing in progress sc %p itt 0x%x\n",
+-				   ctask->sc, ctask->itt);
+-			fail_command(conn, ctask, DID_BUS_BUSY << 16);
+-		}
++		__kfifo_put(fifo, (void*)&task, sizeof(void*));
+ 	}
++	return NULL;
+ }
+ 
+-static void iscsi_suspend_tx(struct iscsi_conn *conn)
+-{
+-	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+-	scsi_flush_work(conn->session->host);
+-}
+-
+-static void iscsi_start_tx(struct iscsi_conn *conn)
+-{
+-	clear_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+-}
+-
+-static enum scsi_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd)
++static int iscsi_ctask_mtask_cleanup(struct iscsi_cmd_task *ctask)
+ {
+-	struct iscsi_cls_session *cls_session;
+-	struct iscsi_session *session;
+-	struct iscsi_conn *conn;
+-	enum scsi_eh_timer_return rc = EH_NOT_HANDLED;
+-
+-	cls_session = starget_to_session(scsi_target(scmd->device));
+-	session = class_to_transport_session(cls_session);
+-
+-	debug_scsi("scsi cmd %p timedout\n", scmd);
+-
+-	spin_lock(&session->lock);
+-	if (session->state != ISCSI_STATE_LOGGED_IN) {
+-		/*
+-		 * We are probably in the middle of iscsi recovery so let
+-		 * that complete and handle the error.
+-		 */
+-		rc = EH_RESET_TIMER;
+-		goto done;
+-	}
++	struct iscsi_conn *conn = ctask->conn;
++	struct iscsi_session *session = conn->session;
+ 
+-	conn = session->leadconn;
+-	if (!conn) {
+-		/* In the middle of shuting down */
+-		rc = EH_RESET_TIMER;
+-		goto done;
+-	}
++	if (!ctask->mtask)
++		return -EINVAL;
+ 
+-	if (!conn->recv_timeout && !conn->ping_timeout)
+-		goto done;
+-	/*
+-	 * if the ping timedout then we are in the middle of cleaning up
+-	 * and can let the iscsi eh handle it
+-	 */
+-	if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ) +
+-			    (conn->ping_timeout * HZ), jiffies))
+-		rc = EH_RESET_TIMER;
+-	/*
+-	 * if we are about to check the transport then give the command
+-	 * more time
+-	 */
+-	if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ),
+-			   jiffies))
+-		rc = EH_RESET_TIMER;
+-	/* if in the middle of checking the transport then give us more time */
+-	if (conn->ping_mtask)
+-		rc = EH_RESET_TIMER;
+-done:
+-	spin_unlock(&session->lock);
+-	debug_scsi("return %s\n", rc == EH_RESET_TIMER ? "timer reset" : "nh");
+-	return rc;
++	if (!iscsi_remove_mgmt_task(conn->immqueue, ctask->mtask->itt))
++		list_del(&ctask->mtask->running);
++	__kfifo_put(session->mgmtpool.queue, (void*)&ctask->mtask,
++		    sizeof(void*));
++	ctask->mtask = NULL;
++	return 0;
+ }
+ 
+-static void iscsi_check_transport_timeouts(unsigned long data)
++/*
++ * session lock and xmitmutex must be held
++ */
++static void fail_command(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
++			 int err)
+ {
+-	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+-	struct iscsi_session *session = conn->session;
+-	unsigned long timeout, next_timeout = 0, last_recv;
++	struct scsi_cmnd *sc;
+ 
+-	spin_lock(&session->lock);
+-	if (session->state != ISCSI_STATE_LOGGED_IN)
+-		goto done;
+-
+-	timeout = conn->recv_timeout;
+-	if (!timeout)
+-		goto done;
+-
+-	timeout *= HZ;
+-	last_recv = conn->last_recv;
+-	if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
+-			   jiffies)) {
+-		iscsi_conn_printk(KERN_ERR, conn, "ping timeout of %d secs "
+-				  "expired, last rx %lu, last ping %lu, "
+-				  "now %lu\n", conn->ping_timeout, last_recv,
+-				  conn->last_ping, jiffies);
+-		spin_unlock(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++	sc = ctask->sc;
++	if (!sc)
+ 		return;
+-	}
+-
+-	if (time_before_eq(last_recv + timeout, jiffies)) {
+-		if (time_before_eq(conn->last_ping, last_recv)) {
+-			/* send a ping to try to provoke some traffic */
+-			debug_scsi("Sending nopout as ping on conn %p\n", conn);
+-			iscsi_send_nopout(conn, NULL);
+-		}
+-		next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
+-	} else
+-		next_timeout = last_recv + timeout;
+ 
+-	debug_scsi("Setting next tmo %lu\n", next_timeout);
+-	mod_timer(&conn->transport_timer, next_timeout);
+-done:
+-	spin_unlock(&session->lock);
+-}
++	conn->session->tt->cleanup_cmd_task(conn, ctask);
++	iscsi_ctask_mtask_cleanup(ctask);
+ 
+-static void iscsi_prep_abort_task_pdu(struct iscsi_cmd_task *ctask,
+-				      struct iscsi_tm *hdr)
+-{
+-	memset(hdr, 0, sizeof(*hdr));
+-	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
+-	hdr->flags = ISCSI_TM_FUNC_ABORT_TASK & ISCSI_FLAG_TM_FUNC_MASK;
+-	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+-	memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun));
+-	hdr->rtt = ctask->hdr->itt;
+-	hdr->refcmdsn = ctask->hdr->cmdsn;
++	sc->result = err;
++	sc->resid = sc->request_bufflen;
++	/* release ref from queuecommand */
++	__iscsi_put_ctask(ctask);
+ }
+ 
+ int iscsi_eh_abort(struct scsi_cmnd *sc)
+ {
+-	struct Scsi_Host *host = sc->device->host;
+-	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+-	struct iscsi_conn *conn;
+ 	struct iscsi_cmd_task *ctask;
+-	struct iscsi_tm *hdr;
+-	int rc, age;
++	struct iscsi_conn *conn;
++	struct iscsi_session *session;
++	int rc;
+ 
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+ 	/*
+ 	 * if session was ISCSI_STATE_IN_RECOVERY then we may not have
+ 	 * got the command.
+ 	 */
+ 	if (!sc->SCp.ptr) {
+ 		debug_scsi("sc never reached iscsi layer or it completed.\n");
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return SUCCESS;
+ 	}
+ 
++	ctask = (struct iscsi_cmd_task *)sc->SCp.ptr;
++	conn = ctask->conn;
++	session = conn->session;
++
++	conn->eh_abort_cnt++;
++	debug_scsi("aborting [sc %p itt 0x%x]\n", sc, ctask->itt);
++
++	mutex_lock(&conn->xmitmutex);
++	spin_lock_bh(&session->lock);
++
+ 	/*
+ 	 * If we are not logged in or we have started a new session
+ 	 * then let the host reset code handle this
+ 	 */
+-	if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN ||
+-	    sc->SCp.phase != session->age) {
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+-		return FAILED;
+-	}
+-
+-	conn = session->leadconn;
+-	conn->eh_abort_cnt++;
+-	age = session->age;
+-
+-	ctask = (struct iscsi_cmd_task *)sc->SCp.ptr;
+-	debug_scsi("aborting [sc %p itt 0x%x]\n", sc, ctask->itt);
++	if (session->state != ISCSI_STATE_LOGGED_IN ||
++	    sc->SCp.phase != session->age)
++		goto failed;
+ 
+ 	/* ctask completed before time out */
+ 	if (!ctask->sc) {
++		spin_unlock_bh(&session->lock);
+ 		debug_scsi("sc completed while abort in progress\n");
+-		goto success;
++		goto success_rel_mutex;
+ 	}
+ 
+-	if (ctask->state == ISCSI_TASK_PENDING) {
+-		fail_command(conn, ctask, DID_ABORT << 16);
+-		goto success;
++	/* what should we do here ? */
++	if (conn->ctask == ctask) {
++		printk(KERN_INFO "iscsi: sc %p itt 0x%x partially sent. "
++		       "Failing abort\n", sc, ctask->itt);
++		goto failed;
+ 	}
+ 
+-	/* only have one tmf outstanding at a time */
+-	if (conn->tmf_state != TMF_INITIAL)
+-		goto failed;
+-	conn->tmf_state = TMF_QUEUED;
++	if (ctask->state == ISCSI_TASK_PENDING)
++		goto success_cleanup;
+ 
+-	hdr = &conn->tmhdr;
+-	iscsi_prep_abort_task_pdu(ctask, hdr);
++	conn->tmabort_state = TMABORT_INITIAL;
+ 
+-	if (iscsi_exec_task_mgmt_fn(conn, hdr, age, session->abort_timeout)) {
+-		rc = FAILED;
++	spin_unlock_bh(&session->lock);
++	rc = iscsi_exec_abort_task(sc, ctask);
++	spin_lock_bh(&session->lock);
++
++	if (rc || sc->SCp.phase != session->age ||
++	    session->state != ISCSI_STATE_LOGGED_IN)
+ 		goto failed;
+-	}
++	iscsi_ctask_mtask_cleanup(ctask);
+ 
+-	switch (conn->tmf_state) {
+-	case TMF_SUCCESS:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_suspend_tx(conn);
+-		/*
+-		 * clean up task if aborted. grab the recv lock as a writer
+-		 */
+-		write_lock_bh(conn->recv_lock);
+-		spin_lock(&session->lock);
+-		fail_command(conn, ctask, DID_ABORT << 16);
+-		conn->tmf_state = TMF_INITIAL;
+-		spin_unlock(&session->lock);
+-		write_unlock_bh(conn->recv_lock);
+-		iscsi_start_tx(conn);
+-		goto success_unlocked;
+-	case TMF_TIMEDOUT:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		goto failed_unlocked;
+-	case TMF_NOT_FOUND:
+-		if (!sc->SCp.ptr) {
+-			conn->tmf_state = TMF_INITIAL;
++	switch (conn->tmabort_state) {
++	case TMABORT_SUCCESS:
++		goto success_cleanup;
++	case TMABORT_NOT_FOUND:
++		if (!ctask->sc) {
+ 			/* ctask completed before tmf abort response */
++			spin_unlock_bh(&session->lock);
+ 			debug_scsi("sc completed while abort in progress\n");
+-			goto success;
++			goto success_rel_mutex;
+ 		}
+ 		/* fall through */
+ 	default:
+-		conn->tmf_state = TMF_INITIAL;
++		/* timedout or failed */
++		spin_unlock_bh(&session->lock);
++		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
++		spin_lock_bh(&session->lock);
+ 		goto failed;
+ 	}
+ 
+-success:
+-	spin_unlock_bh(&session->lock);
+-success_unlocked:
++success_cleanup:
+ 	debug_scsi("abort success [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
+-	mutex_unlock(&session->eh_mutex);
+-	return SUCCESS;
+-
+-failed:
+ 	spin_unlock_bh(&session->lock);
+-failed_unlocked:
+-	debug_scsi("abort failed [sc %p itt 0x%x]\n", sc,
+-		    ctask ? ctask->itt : 0);
+-	mutex_unlock(&session->eh_mutex);
+-	return FAILED;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_eh_abort);
+ 
+-static void iscsi_prep_lun_reset_pdu(struct scsi_cmnd *sc, struct iscsi_tm *hdr)
+-{
+-	memset(hdr, 0, sizeof(*hdr));
+-	hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
+-	hdr->flags = ISCSI_TM_FUNC_LOGICAL_UNIT_RESET & ISCSI_FLAG_TM_FUNC_MASK;
+-	hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+-	int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
+-	hdr->rtt = RESERVED_ITT;
+-}
+-
+-int iscsi_eh_device_reset(struct scsi_cmnd *sc)
+-{
+-	struct Scsi_Host *host = sc->device->host;
+-	struct iscsi_session *session = iscsi_hostdata(host->hostdata);
+-	struct iscsi_conn *conn;
+-	struct iscsi_tm *hdr;
+-	int rc = FAILED;
+-
+-	debug_scsi("LU Reset [sc %p lun %u]\n", sc, sc->device->lun);
+-
+-	mutex_lock(&session->eh_mutex);
+-	spin_lock_bh(&session->lock);
+ 	/*
+-	 * Just check if we are not logged in. We cannot check for
+-	 * the phase because the reset could come from a ioctl.
++	 * clean up task if aborted. we have the xmitmutex so grab
++	 * the recv lock as a writer
+ 	 */
+-	if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN)
+-		goto unlock;
+-	conn = session->leadconn;
+-
+-	/* only have one tmf outstanding at a time */
+-	if (conn->tmf_state != TMF_INITIAL)
+-		goto unlock;
+-	conn->tmf_state = TMF_QUEUED;
+-
+-	hdr = &conn->tmhdr;
+-	iscsi_prep_lun_reset_pdu(sc, hdr);
+-
+-	if (iscsi_exec_task_mgmt_fn(conn, hdr, session->age,
+-				    session->lu_reset_timeout)) {
+-		rc = FAILED;
+-		goto unlock;
+-	}
+-
+-	switch (conn->tmf_state) {
+-	case TMF_SUCCESS:
+-		break;
+-	case TMF_TIMEDOUT:
+-		spin_unlock_bh(&session->lock);
+-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+-		goto done;
+-	default:
+-		conn->tmf_state = TMF_INITIAL;
+-		goto unlock;
+-	}
+-
+-	rc = SUCCESS;
+-	spin_unlock_bh(&session->lock);
+-
+-	iscsi_suspend_tx(conn);
+-	/* need to grab the recv lock then session lock */
+ 	write_lock_bh(conn->recv_lock);
+ 	spin_lock(&session->lock);
+-	fail_all_commands(conn, sc->device->lun, DID_ERROR);
+-	conn->tmf_state = TMF_INITIAL;
++	fail_command(conn, ctask, DID_ABORT << 16);
+ 	spin_unlock(&session->lock);
+ 	write_unlock_bh(conn->recv_lock);
+ 
+-	iscsi_start_tx(conn);
+-	goto done;
++success_rel_mutex:
++	mutex_unlock(&conn->xmitmutex);
++	return SUCCESS;
+ 
+-unlock:
++failed:
+ 	spin_unlock_bh(&session->lock);
+-done:
+-	debug_scsi("iscsi_eh_device_reset %s\n",
+-		  rc == SUCCESS ? "SUCCESS" : "FAILED");
+-	mutex_unlock(&session->eh_mutex);
+-	return rc;
++	mutex_unlock(&conn->xmitmutex);
++
++	debug_scsi("abort failed [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
++	return FAILED;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_eh_device_reset);
++EXPORT_SYMBOL_GPL(iscsi_eh_abort);
+ 
+-/*
+- * Pre-allocate a pool of @max items of @item_size. By default, the pool
+- * should be accessed via kfifo_{get,put} on q->queue.
+- * Optionally, the caller can obtain the array of object pointers
+- * by passing in a non-NULL @items pointer
+- */
+ int
+-iscsi_pool_init(struct iscsi_pool *q, int max, void ***items, int item_size)
++iscsi_pool_init(struct iscsi_queue *q, int max, void ***items, int item_size)
+ {
+-	int i, num_arrays = 1;
++	int i;
+ 
+-	memset(q, 0, sizeof(*q));
++	*items = kmalloc(max * sizeof(void*), GFP_KERNEL);
++	if (*items == NULL)
++		return -ENOMEM;
+ 
+ 	q->max = max;
+-
+-	/* If the user passed an items pointer, he wants a copy of
+-	 * the array. */
+-	if (items)
+-		num_arrays++;
+-	q->pool = kzalloc(num_arrays * max * sizeof(void*), GFP_KERNEL);
+-	if (q->pool == NULL)
+-		goto enomem;
++	q->pool = kmalloc(max * sizeof(void*), GFP_KERNEL);
++	if (q->pool == NULL) {
++		kfree(*items);
++		return -ENOMEM;
++	}
+ 
+ 	q->queue = kfifo_init((void*)q->pool, max * sizeof(void*),
+ 			      GFP_KERNEL, NULL);
+-	if (q->queue == ERR_PTR(-ENOMEM))
+-		goto enomem;
++	if (q->queue == ERR_PTR(-ENOMEM)) {
++		kfree(q->pool);
++		kfree(*items);
++		return -ENOMEM;
++	}
+ 
+ 	for (i = 0; i < max; i++) {
+-		q->pool[i] = kzalloc(item_size, GFP_KERNEL);
++		q->pool[i] = kmalloc(item_size, GFP_KERNEL);
+ 		if (q->pool[i] == NULL) {
+-			q->max = i;
+-			goto enomem;
++			int j;
++
++			for (j = 0; j < i; j++)
++				kfree(q->pool[j]);
++
++			kfifo_free(q->queue);
++			kfree(q->pool);
++			kfree(*items);
++			return -ENOMEM;
+ 		}
++		memset(q->pool[i], 0, item_size);
++		(*items)[i] = q->pool[i];
+ 		__kfifo_put(q->queue, (void*)&q->pool[i], sizeof(void*));
+ 	}
+-
+-	if (items) {
+-		*items = q->pool + max;
+-		memcpy(*items, q->pool, max * sizeof(void *));
+-	}
+-
+ 	return 0;
+-
+-enomem:
+-	iscsi_pool_free(q);
+-	return -ENOMEM;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_pool_init);
+ 
+-void iscsi_pool_free(struct iscsi_pool *q)
++void iscsi_pool_free(struct iscsi_queue *q, void **items)
+ {
+ 	int i;
+ 
+ 	for (i = 0; i < q->max; i++)
+-		kfree(q->pool[i]);
+-	if (q->pool)
+-		kfree(q->pool);
++		kfree(items[i]);
++	kfree(q->pool);
++	kfree(items);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+ 
+@@ -1778,10 +1339,6 @@ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+  * iscsi_session_setup - create iscsi cls session and host and session
+  * @scsit: scsi transport template
+  * @iscsit: iscsi transport template
+- * @cmds_max: scsi host can queue
+- * @qdepth: scsi host cmds per lun
+- * @cmd_task_size: LLD ctask private data size
+- * @mgmt_task_size: LLD mtask private data size
+  * @initial_cmdsn: initial CmdSN
+  * @hostno: host no allocated
+  *
+@@ -1791,7 +1348,6 @@ EXPORT_SYMBOL_GPL(iscsi_pool_free);
+ struct iscsi_cls_session *
+ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 		    struct scsi_transport_template *scsit,
+-		    uint16_t cmds_max, uint16_t qdepth,
+ 		    int cmd_task_size, int mgmt_task_size,
+ 		    uint32_t initial_cmdsn, uint32_t *hostno)
+ {
+@@ -1800,56 +1356,30 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 	struct iscsi_cls_session *cls_session;
+ 	int cmd_i;
+ 
+-	if (qdepth > ISCSI_MAX_CMD_PER_LUN || qdepth < 1) {
+-		if (qdepth != 0)
+-			printk(KERN_ERR "iscsi: invalid queue depth of %d. "
+-			      "Queue depth must be between 1 and %d.\n",
+-			      qdepth, ISCSI_MAX_CMD_PER_LUN);
+-		qdepth = ISCSI_DEF_CMD_PER_LUN;
+-	}
+-
+-	if (!is_power_of_2(cmds_max) || cmds_max >= ISCSI_MGMT_ITT_OFFSET ||
+-	    cmds_max < 2) {
+-		if (cmds_max != 0)
+-			printk(KERN_ERR "iscsi: invalid can_queue of %d. "
+-			       "can_queue must be a power of 2 and between "
+-			       "2 and %d - setting to %d.\n", cmds_max,
+-			       ISCSI_MGMT_ITT_OFFSET, ISCSI_DEF_XMIT_CMDS_MAX);
+-		cmds_max = ISCSI_DEF_XMIT_CMDS_MAX;
+-	}
+-
+ 	shost = scsi_host_alloc(iscsit->host_template,
+ 				hostdata_privsize(sizeof(*session)));
+ 	if (!shost)
+ 		return NULL;
+ 
+-	/* the iscsi layer takes one task for reserve */
+-	shost->can_queue = cmds_max - 1;
+-	shost->cmd_per_lun = qdepth;
+ 	shost->max_id = 1;
+ 	shost->max_channel = 0;
+ 	shost->max_lun = iscsit->max_lun;
+ 	shost->max_cmd_len = iscsit->max_cmd_len;
+ 	shost->transportt = scsit;
+ 	shost->transportt->create_work_queue = 1;
+-	shost->transportt->eh_timed_out = iscsi_eh_cmd_timed_out;
+ 	*hostno = shost->host_no;
+ 
+ 	session = iscsi_hostdata(shost->hostdata);
+ 	memset(session, 0, sizeof(struct iscsi_session));
+ 	session->host = shost;
+ 	session->state = ISCSI_STATE_FREE;
+-	session->fast_abort = 1;
+-	session->lu_reset_timeout = 15;
+-	session->abort_timeout = 10;
+ 	session->mgmtpool_max = ISCSI_MGMT_CMDS_MAX;
+-	session->cmds_max = cmds_max;
+-	session->queued_cmdsn = session->cmdsn = initial_cmdsn;
++	session->cmds_max = ISCSI_XMIT_CMDS_MAX;
++	session->cmdsn = initial_cmdsn;
+ 	session->exp_cmdsn = initial_cmdsn + 1;
+ 	session->max_cmdsn = initial_cmdsn + 1;
+ 	session->max_r2t = 1;
+ 	session->tt = iscsit;
+-	mutex_init(&session->eh_mutex);
+ 
+ 	/* initialize SCSI PDU commands pool */
+ 	if (iscsi_pool_init(&session->cmdpool, session->cmds_max,
+@@ -1904,9 +1434,9 @@ module_put:
+ cls_session_fail:
+ 	scsi_remove_host(shost);
+ add_host_fail:
+-	iscsi_pool_free(&session->mgmtpool);
++	iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds);
+ mgmtpool_alloc_fail:
+-	iscsi_pool_free(&session->cmdpool);
++	iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
+ cmdpool_alloc_fail:
+ 	scsi_host_put(shost);
+ 	return NULL;
+@@ -1926,22 +1456,14 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session)
+ 	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+ 	struct module *owner = cls_session->transport->owner;
+ 
+-	iscsi_remove_session(cls_session);
+ 	scsi_remove_host(shost);
+ 
+-	iscsi_pool_free(&session->mgmtpool);
+-	iscsi_pool_free(&session->cmdpool);
++	iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds);
++	iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
+ 
+-	kfree(session->password);
+-	kfree(session->password_in);
+-	kfree(session->username);
+-	kfree(session->username_in);
+ 	kfree(session->targetname);
+-	kfree(session->netdev);
+-	kfree(session->hwaddress);
+-	kfree(session->initiatorname);
+ 
+-	iscsi_free_session(cls_session);
++	iscsi_destroy_session(cls_session);
+ 	scsi_host_put(shost);
+ 	module_put(owner);
+ }
+@@ -1971,17 +1493,22 @@ iscsi_conn_setup(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 	conn->c_stage = ISCSI_CONN_INITIAL_STAGE;
+ 	conn->id = conn_idx;
+ 	conn->exp_statsn = 0;
+-	conn->tmf_state = TMF_INITIAL;
+-
+-	init_timer(&conn->transport_timer);
+-	conn->transport_timer.data = (unsigned long)conn;
+-	conn->transport_timer.function = iscsi_check_transport_timeouts;
+-
++	conn->tmabort_state = TMABORT_INITIAL;
+ 	INIT_LIST_HEAD(&conn->run_list);
+ 	INIT_LIST_HEAD(&conn->mgmt_run_list);
+-	INIT_LIST_HEAD(&conn->mgmtqueue);
+ 	INIT_LIST_HEAD(&conn->xmitqueue);
+-	INIT_LIST_HEAD(&conn->requeue);
++
++	/* initialize general immediate & non-immediate PDU commands queue */
++	conn->immqueue = kfifo_alloc(session->mgmtpool_max * sizeof(void*),
++			                GFP_KERNEL, NULL);
++	if (conn->immqueue == ERR_PTR(-ENOMEM))
++		goto immqueue_alloc_fail;
++
++	conn->mgmtqueue = kfifo_alloc(session->mgmtpool_max * sizeof(void*),
++			                GFP_KERNEL, NULL);
++	if (conn->mgmtqueue == ERR_PTR(-ENOMEM))
++		goto mgmtqueue_alloc_fail;
++
+ 	INIT_WORK(&conn->xmitwork, iscsi_xmitworker);
+ 
+ 	/* allocate login_mtask used for the login/text sequences */
+@@ -1999,7 +1526,8 @@ iscsi_conn_setup(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+ 		goto login_mtask_data_alloc_fail;
+ 	conn->login_mtask->data = conn->data = data;
+ 
+-	init_timer(&conn->tmf_timer);
++	init_timer(&conn->tmabort_timer);
++	mutex_init(&conn->xmitmutex);
+ 	init_waitqueue_head(&conn->ehwait);
+ 
+ 	return cls_conn;
+@@ -2008,6 +1536,10 @@ login_mtask_data_alloc_fail:
+ 	__kfifo_put(session->mgmtpool.queue, (void*)&conn->login_mtask,
+ 		    sizeof(void*));
+ login_mtask_alloc_fail:
++	kfifo_free(conn->mgmtqueue);
++mgmtqueue_alloc_fail:
++	kfifo_free(conn->immqueue);
++immqueue_alloc_fail:
+ 	iscsi_destroy_conn(cls_conn);
+ 	return NULL;
+ }
+@@ -2026,7 +1558,8 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	struct iscsi_session *session = conn->session;
+ 	unsigned long flags;
+ 
+-	del_timer_sync(&conn->transport_timer);
++	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
++	mutex_lock(&conn->xmitmutex);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	conn->c_stage = ISCSI_CONN_CLEANUP_WAIT;
+@@ -2039,6 +1572,8 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ 
++	mutex_unlock(&conn->xmitmutex);
++
+ 	/*
+ 	 * Block until all in-progress commands for this connection
+ 	 * time out or fail.
+@@ -2051,10 +1586,9 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 		}
+ 		spin_unlock_irqrestore(session->host->host_lock, flags);
+ 		msleep_interruptible(500);
+-		iscsi_conn_printk(KERN_INFO, conn, "iscsi conn_destroy(): "
+-				  "host_busy %d host_failed %d\n",
+-				  session->host->host_busy,
+-				  session->host->host_failed);
++		printk(KERN_INFO "iscsi: scsi conn_destroy(): host_busy %d "
++		       "host_failed %d\n", session->host->host_busy,
++		       session->host->host_failed);
+ 		/*
+ 		 * force eh_abort() to unblock
+ 		 */
+@@ -2062,17 +1596,23 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 
+ 	/* flush queued up work because we free the connection below */
+-	iscsi_suspend_tx(conn);
++	scsi_flush_work(session->host);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	kfree(conn->data);
+ 	kfree(conn->persistent_address);
+ 	__kfifo_put(session->mgmtpool.queue, (void*)&conn->login_mtask,
+ 		    sizeof(void*));
+-	if (session->leadconn == conn)
++	if (session->leadconn == conn) {
+ 		session->leadconn = NULL;
++		/* no connections exits.. reset sequencing */
++		session->cmdsn = session->max_cmdsn = session->exp_cmdsn = 1;
++	}
+ 	spin_unlock_bh(&session->lock);
+ 
++	kfifo_free(conn->immqueue);
++	kfifo_free(conn->mgmtqueue);
++
+ 	iscsi_destroy_conn(cls_conn);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_teardown);
+@@ -2083,41 +1623,21 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 	struct iscsi_session *session = conn->session;
+ 
+ 	if (!session) {
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "can't start unbound connection\n");
++		printk(KERN_ERR "iscsi: can't start unbound connection\n");
+ 		return -EPERM;
+ 	}
+ 
+ 	if ((session->imm_data_en || !session->initial_r2t_en) &&
+ 	     session->first_burst > session->max_burst) {
+-		iscsi_conn_printk(KERN_INFO, conn, "invalid burst lengths: "
+-				  "first_burst %d max_burst %d\n",
+-				  session->first_burst, session->max_burst);
++		printk("iscsi: invalid burst lengths: "
++		       "first_burst %d max_burst %d\n",
++		       session->first_burst, session->max_burst);
+ 		return -EINVAL;
+ 	}
+ 
+-	if (conn->ping_timeout && !conn->recv_timeout) {
+-		iscsi_conn_printk(KERN_ERR, conn, "invalid recv timeout of "
+-				  "zero. Using 5 seconds\n.");
+-		conn->recv_timeout = 5;
+-	}
+-
+-	if (conn->recv_timeout && !conn->ping_timeout) {
+-		iscsi_conn_printk(KERN_ERR, conn, "invalid ping timeout of "
+-				  "zero. Using 5 seconds.\n");
+-		conn->ping_timeout = 5;
+-	}
+-
+ 	spin_lock_bh(&session->lock);
+ 	conn->c_stage = ISCSI_CONN_STARTED;
+ 	session->state = ISCSI_STATE_LOGGED_IN;
+-	session->queued_cmdsn = session->cmdsn;
+-
+-	conn->last_recv = jiffies;
+-	conn->last_ping = jiffies;
+-	if (conn->recv_timeout && conn->ping_timeout)
+-		mod_timer(&conn->transport_timer,
+-			  jiffies + (conn->recv_timeout * HZ));
+ 
+ 	switch(conn->stop_stage) {
+ 	case STOP_CONN_RECOVER:
+@@ -2126,11 +1646,13 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 		 * commands after successful recovery
+ 		 */
+ 		conn->stop_stage = 0;
+-		conn->tmf_state = TMF_INITIAL;
++		conn->tmabort_state = TMABORT_INITIAL;
+ 		session->age++;
+-		if (session->age == 16)
+-			session->age = 0;
+-		break;
++		spin_unlock_bh(&session->lock);
++
++		iscsi_unblock_session(session_to_cls(session));
++		wake_up(&conn->ehwait);
++		return 0;
+ 	case STOP_CONN_TERM:
+ 		conn->stop_stage = 0;
+ 		break;
+@@ -2139,8 +1661,6 @@ int iscsi_conn_start(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 	spin_unlock_bh(&session->lock);
+ 
+-	iscsi_unblock_session(session_to_cls(session));
+-	wake_up(&conn->ehwait);
+ 	return 0;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_start);
+@@ -2151,43 +1671,59 @@ flush_control_queues(struct iscsi_session *session, struct iscsi_conn *conn)
+ 	struct iscsi_mgmt_task *mtask, *tmp;
+ 
+ 	/* handle pending */
+-	list_for_each_entry_safe(mtask, tmp, &conn->mgmtqueue, running) {
++	while (__kfifo_get(conn->immqueue, (void*)&mtask, sizeof(void*)) ||
++	       __kfifo_get(conn->mgmtqueue, (void*)&mtask, sizeof(void*))) {
++		if (mtask == conn->login_mtask)
++			continue;
+ 		debug_scsi("flushing pending mgmt task itt 0x%x\n", mtask->itt);
+-		iscsi_free_mgmt_task(conn, mtask);
++		__kfifo_put(session->mgmtpool.queue, (void*)&mtask,
++			    sizeof(void*));
+ 	}
+ 
+ 	/* handle running */
+ 	list_for_each_entry_safe(mtask, tmp, &conn->mgmt_run_list, running) {
+ 		debug_scsi("flushing running mgmt task itt 0x%x\n", mtask->itt);
+-		iscsi_free_mgmt_task(conn, mtask);
++		list_del(&mtask->running);
++
++		if (mtask == conn->login_mtask)
++			continue;
++		__kfifo_put(session->mgmtpool.queue, (void*)&mtask,
++			   sizeof(void*));
+ 	}
+ 
+ 	conn->mtask = NULL;
+ }
+ 
++/* Fail commands. Mutex and session lock held and recv side suspended */
++static void fail_all_commands(struct iscsi_conn *conn)
++{
++	struct iscsi_cmd_task *ctask, *tmp;
++
++	/* flush pending */
++	list_for_each_entry_safe(ctask, tmp, &conn->xmitqueue, running) {
++		debug_scsi("failing pending sc %p itt 0x%x\n", ctask->sc,
++			   ctask->itt);
++		fail_command(conn, ctask, DID_BUS_BUSY << 16);
++	}
++
++	/* fail all other running */
++	list_for_each_entry_safe(ctask, tmp, &conn->run_list, running) {
++		debug_scsi("failing in progress sc %p itt 0x%x\n",
++			   ctask->sc, ctask->itt);
++		fail_command(conn, ctask, DID_BUS_BUSY << 16);
++	}
++
++	conn->ctask = NULL;
++}
++
+ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 					 struct iscsi_conn *conn, int flag)
+ {
+ 	int old_stop_stage;
+ 
+-	del_timer_sync(&conn->transport_timer);
+-
+-	mutex_lock(&session->eh_mutex);
+ 	spin_lock_bh(&session->lock);
+ 	if (conn->stop_stage == STOP_CONN_TERM) {
+ 		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+-		return;
+-	}
+-
+-	/*
+-	 * The LLD either freed/unset the lock on us, or userspace called
+-	 * stop but did not create a proper connection (connection was never
+-	 * bound or it was unbound then stop was called).
+-	 */
+-	if (!conn->recv_lock) {
+-		spin_unlock_bh(&session->lock);
+-		mutex_unlock(&session->eh_mutex);
+ 		return;
+ 	}
+ 
+@@ -2204,14 +1740,14 @@ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 	old_stop_stage = conn->stop_stage;
+ 	conn->stop_stage = flag;
+ 	conn->c_stage = ISCSI_CONN_STOPPED;
++	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+ 	spin_unlock_bh(&session->lock);
+ 
+-	iscsi_suspend_tx(conn);
+-
+ 	write_lock_bh(conn->recv_lock);
+ 	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
+ 	write_unlock_bh(conn->recv_lock);
+ 
++	mutex_lock(&conn->xmitmutex);
+ 	/*
+ 	 * for connection level recovery we should not calculate
+ 	 * header digest. conn->hdr_size used for optimization
+@@ -2232,11 +1768,11 @@ static void iscsi_start_session_recovery(struct iscsi_session *session,
+ 	 * flush queues.
+ 	 */
+ 	spin_lock_bh(&session->lock);
+-	fail_all_commands(conn, -1,
+-			STOP_CONN_RECOVER ? DID_BUS_BUSY : DID_ERROR);
++	fail_all_commands(conn);
+ 	flush_control_queues(session, conn);
+ 	spin_unlock_bh(&session->lock);
+-	mutex_unlock(&session->eh_mutex);
++
++	mutex_unlock(&conn->xmitmutex);
+ }
+ 
+ void iscsi_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+@@ -2250,8 +1786,7 @@ void iscsi_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+ 		iscsi_start_session_recovery(session, conn, flag);
+ 		break;
+ 	default:
+-		iscsi_conn_printk(KERN_ERR, conn,
+-				  "invalid stop flag %d\n", flag);
++		printk(KERN_ERR "iscsi: invalid stop flag %d\n", flag);
+ 	}
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_stop);
+@@ -2285,21 +1820,6 @@ int iscsi_set_param(struct iscsi_cls_conn *cls_conn,
+ 	uint32_t value;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_FAST_ABORT:
+-		sscanf(buf, "%d", &session->fast_abort);
+-		break;
+-	case ISCSI_PARAM_ABORT_TMO:
+-		sscanf(buf, "%d", &session->abort_timeout);
+-		break;
+-	case ISCSI_PARAM_LU_RESET_TMO:
+-		sscanf(buf, "%d", &session->lu_reset_timeout);
+-		break;
+-	case ISCSI_PARAM_PING_TMO:
+-		sscanf(buf, "%d", &conn->ping_timeout);
+-		break;
+-	case ISCSI_PARAM_RECV_TMO:
+-		sscanf(buf, "%d", &conn->recv_timeout);
+-		break;
+ 	case ISCSI_PARAM_MAX_RECV_DLENGTH:
+ 		sscanf(buf, "%d", &conn->max_recv_dlength);
+ 		break;
+@@ -2347,30 +1867,6 @@ int iscsi_set_param(struct iscsi_cls_conn *cls_conn,
+ 	case ISCSI_PARAM_EXP_STATSN:
+ 		sscanf(buf, "%u", &conn->exp_statsn);
+ 		break;
+-	case ISCSI_PARAM_USERNAME:
+-		kfree(session->username);
+-		session->username = kstrdup(buf, GFP_KERNEL);
+-		if (!session->username)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_USERNAME_IN:
+-		kfree(session->username_in);
+-		session->username_in = kstrdup(buf, GFP_KERNEL);
+-		if (!session->username_in)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_PASSWORD:
+-		kfree(session->password);
+-		session->password = kstrdup(buf, GFP_KERNEL);
+-		if (!session->password)
+-			return -ENOMEM;
+-		break;
+-	case ISCSI_PARAM_PASSWORD_IN:
+-		kfree(session->password_in);
+-		session->password_in = kstrdup(buf, GFP_KERNEL);
+-		if (!session->password_in)
+-			return -ENOMEM;
+-		break;
+ 	case ISCSI_PARAM_TARGET_NAME:
+ 		/* this should not change between logins */
+ 		if (session->targetname)
+@@ -2414,15 +1910,6 @@ int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ 	int len;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_FAST_ABORT:
+-		len = sprintf(buf, "%d\n", session->fast_abort);
+-		break;
+-	case ISCSI_PARAM_ABORT_TMO:
+-		len = sprintf(buf, "%d\n", session->abort_timeout);
+-		break;
+-	case ISCSI_PARAM_LU_RESET_TMO:
+-		len = sprintf(buf, "%d\n", session->lu_reset_timeout);
+-		break;
+ 	case ISCSI_PARAM_INITIAL_R2T_EN:
+ 		len = sprintf(buf, "%d\n", session->initial_r2t_en);
+ 		break;
+@@ -2453,18 +1940,6 @@ int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ 	case ISCSI_PARAM_TPGT:
+ 		len = sprintf(buf, "%d\n", session->tpgt);
+ 		break;
+-	case ISCSI_PARAM_USERNAME:
+-		len = sprintf(buf, "%s\n", session->username);
+-		break;
+-	case ISCSI_PARAM_USERNAME_IN:
+-		len = sprintf(buf, "%s\n", session->username_in);
+-		break;
+-	case ISCSI_PARAM_PASSWORD:
+-		len = sprintf(buf, "%s\n", session->password);
+-		break;
+-	case ISCSI_PARAM_PASSWORD_IN:
+-		len = sprintf(buf, "%s\n", session->password_in);
+-		break;
+ 	default:
+ 		return -ENOSYS;
+ 	}
+@@ -2480,12 +1955,6 @@ int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 	int len;
+ 
+ 	switch(param) {
+-	case ISCSI_PARAM_PING_TMO:
+-		len = sprintf(buf, "%u\n", conn->ping_timeout);
+-		break;
+-	case ISCSI_PARAM_RECV_TMO:
+-		len = sprintf(buf, "%u\n", conn->recv_timeout);
+-		break;
+ 	case ISCSI_PARAM_MAX_RECV_DLENGTH:
+ 		len = sprintf(buf, "%u\n", conn->max_recv_dlength);
+ 		break;
+@@ -2521,66 +1990,6 @@ int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_get_param);
+ 
+-int iscsi_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf)
+-{
+-	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-	int len;
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_NETDEV_NAME:
+-		if (!session->netdev)
+-			len = sprintf(buf, "%s\n", "default");
+-		else
+-			len = sprintf(buf, "%s\n", session->netdev);
+-		break;
+-	case ISCSI_HOST_PARAM_HWADDRESS:
+-		if (!session->hwaddress)
+-			len = sprintf(buf, "%s\n", "default");
+-		else
+-			len = sprintf(buf, "%s\n", session->hwaddress);
+-		break;
+-	case ISCSI_HOST_PARAM_INITIATOR_NAME:
+-		if (!session->initiatorname)
+-			len = sprintf(buf, "%s\n", "unknown");
+-		else
+-			len = sprintf(buf, "%s\n", session->initiatorname);
+-		break;
+-
+-	default:
+-		return -ENOSYS;
+-	}
+-
+-	return len;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_host_get_param);
+-
+-int iscsi_host_set_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+-			 char *buf, int buflen)
+-{
+-	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
+-
+-	switch (param) {
+-	case ISCSI_HOST_PARAM_NETDEV_NAME:
+-		if (!session->netdev)
+-			session->netdev = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	case ISCSI_HOST_PARAM_HWADDRESS:
+-		if (!session->hwaddress)
+-			session->hwaddress = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	case ISCSI_HOST_PARAM_INITIATOR_NAME:
+-		if (!session->initiatorname)
+-			session->initiatorname = kstrdup(buf, GFP_KERNEL);
+-		break;
+-	default:
+-		return -ENOSYS;
+-	}
+-
+-	return 0;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_host_set_param);
+-
+ MODULE_AUTHOR("Mike Christie");
+ MODULE_DESCRIPTION("iSCSI library functions");
+ MODULE_LICENSE("GPL");
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 65d1737..cdedee5 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -30,27 +30,26 @@
+ #include <scsi/scsi_transport_iscsi.h>
+ #include <scsi/iscsi_if.h>
+ 
+-#define ISCSI_SESSION_ATTRS 19
+-#define ISCSI_CONN_ATTRS 13
+-#define ISCSI_HOST_ATTRS 4
+-#define ISCSI_TRANSPORT_VERSION "2.0-869"
++#define ISCSI_SESSION_ATTRS 11
++#define ISCSI_CONN_ATTRS 11
++#define ISCSI_HOST_ATTRS 0
++#define ISCSI_TRANSPORT_VERSION "2.0-724"
+ 
+ struct iscsi_internal {
+ 	int daemon_pid;
+ 	struct scsi_transport_template t;
+ 	struct iscsi_transport *iscsi_transport;
+ 	struct list_head list;
+-	struct device dev;
++	struct class_device cdev;
+ 
+-	struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
++	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+ 	struct transport_container conn_cont;
+-	struct device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
++	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+ 	struct transport_container session_cont;
+-	struct device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
++	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+ };
+ 
+ static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+-static struct workqueue_struct *iscsi_eh_timer_workq;
+ 
+ /*
+  * list of registered transports and lock that must
+@@ -63,12 +62,12 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define to_iscsi_internal(tmpl) \
+ 	container_of(tmpl, struct iscsi_internal, t)
+ 
+-#define dev_to_iscsi_internal(_dev) \
+-	container_of(_dev, struct iscsi_internal, dev)
++#define cdev_to_iscsi_internal(_cdev) \
++	container_of(_cdev, struct iscsi_internal, cdev)
+ 
+-static void iscsi_transport_release(struct device *dev)
++static void iscsi_transport_release(struct class_device *cdev)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	kfree(priv);
+ }
+ 
+@@ -78,27 +77,25 @@ static void iscsi_transport_release(struct device *dev)
+  */
+ static struct class iscsi_transport_class = {
+ 	.name = "iscsi_transport",
+-	.dev_release = iscsi_transport_release,
++	.release = iscsi_transport_release,
+ };
+ 
+ static ssize_t
+-show_transport_handle(struct device *dev, struct device_attribute *attr,
+-		      char *buf)
++show_transport_handle(struct class_device *cdev, char *buf)
+ {
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+ 	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+ }
+-static DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
++static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+ 
+ #define show_transport_attr(name, format)				\
+ static ssize_t								\
+-show_transport_##name(struct device *dev, 				\
+-		      struct device_attribute *attr,char *buf)		\
++show_transport_##name(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);	\
++	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+ 	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+ }									\
+-static DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
++static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+ 
+ show_transport_attr(caps, "0x%x");
+ show_transport_attr(max_lun, "%d");
+@@ -106,11 +103,11 @@ show_transport_attr(max_conn, "%d");
+ show_transport_attr(max_cmd_len, "%d");
+ 
+ static struct attribute *iscsi_transport_attrs[] = {
+-	&dev_attr_handle.attr,
+-	&dev_attr_caps.attr,
+-	&dev_attr_max_lun.attr,
+-	&dev_attr_max_conn.attr,
+-	&dev_attr_max_cmd_len.attr,
++	&class_device_attr_handle.attr,
++	&class_device_attr_caps.attr,
++	&class_device_attr_max_lun.attr,
++	&class_device_attr_max_conn.attr,
++	&class_device_attr_max_cmd_len.attr,
+ 	NULL,
+ };
+ 
+@@ -118,10 +115,8 @@ static struct attribute_group iscsi_transport_group = {
+ 	.attrs = iscsi_transport_attrs,
+ };
+ 
+-
+-
+ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct device *cdev)
++			    struct class_device *cdev)
+ {
+ 	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+@@ -129,31 +124,13 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ 	memset(ihost, 0, sizeof(*ihost));
+ 	INIT_LIST_HEAD(&ihost->sessions);
+ 	mutex_init(&ihost->mutex);
+-	atomic_set(&ihost->nr_scans, 0);
+-
+-	snprintf(ihost->scan_workq_name, KOBJ_NAME_LEN, "iscsi_scan_%d",
+-		shost->host_no);
+-	ihost->scan_workq = create_singlethread_workqueue(
+-						ihost->scan_workq_name);
+-	if (!ihost->scan_workq)
+-		return -ENOMEM;
+-	return 0;
+-}
+-
+-static int iscsi_remove_host(struct transport_container *tc, struct device *dev,
+-			     struct device *cdev)
+-{
+-	struct Scsi_Host *shost = dev_to_shost(dev);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	destroy_workqueue(ihost->scan_workq);
+ 	return 0;
+ }
+ 
+ static DECLARE_TRANSPORT_CLASS(iscsi_host_class,
+ 			       "iscsi_host",
+ 			       iscsi_setup_host,
+-			       iscsi_remove_host,
++			       NULL,
+ 			       NULL);
+ 
+ static DECLARE_TRANSPORT_CLASS(iscsi_session_class,
+@@ -224,54 +201,6 @@ static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid)
+  * The following functions can be used by LLDs that allocate
+  * their own scsi_hosts or by software iscsi LLDs
+  */
+-static struct {
+-	int value;
+-	char *name;
+-} iscsi_session_state_names[] = {
+-	{ ISCSI_SESSION_LOGGED_IN,	"LOGGED_IN" },
+-	{ ISCSI_SESSION_FAILED,		"FAILED" },
+-	{ ISCSI_SESSION_FREE,		"FREE" },
+-};
+-
+-static const char *iscsi_session_state_name(int state)
+-{
+-	int i;
+-	char *name = NULL;
+-
+-	for (i = 0; i < ARRAY_SIZE(iscsi_session_state_names); i++) {
+-		if (iscsi_session_state_names[i].value == state) {
+-			name = iscsi_session_state_names[i].name;
+-			break;
+-		}
+-	}
+-	return name;
+-}
+-
+-int iscsi_session_chkready(struct iscsi_cls_session *session)
+-{
+-	unsigned long flags;
+-	int err;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	switch (session->state) {
+-	case ISCSI_SESSION_LOGGED_IN:
+-		err = 0;
+-		break;
+-	case ISCSI_SESSION_FAILED:
+-		err = DID_IMM_RETRY << 16;
+-		break;
+-	case ISCSI_SESSION_FREE:
+-		err = DID_NO_CONNECT << 16;
+-		break;
+-	default:
+-		err = DID_NO_CONNECT << 16;
+-		break;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	return err;
+-}
+-EXPORT_SYMBOL_GPL(iscsi_session_chkready);
+-
+ static void iscsi_session_release(struct device *dev)
+ {
+ 	struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
+@@ -287,25 +216,6 @@ static int iscsi_is_session_dev(const struct device *dev)
+ 	return dev->release == iscsi_session_release;
+ }
+ 
+-/**
+- * iscsi_scan_finished - helper to report when running scans are done
+- * @shost: scsi host
+- * @time: scan run time
+- *
+- * This function can be used by drives like qla4xxx to report to the scsi
+- * layer when the scans it kicked off at module load time are done.
+- */
+-int iscsi_scan_finished(struct Scsi_Host *shost, unsigned long time)
+-{
+-	struct iscsi_host *ihost = shost->shost_data;
+-	/*
+-	 * qla4xxx will have kicked off some session unblocks before calling
+-	 * scsi_scan_host, so just wait for them to complete.
+-	 */
+-	return !atomic_read(&ihost->nr_scans);
+-}
+-EXPORT_SYMBOL_GPL(iscsi_scan_finished);
+-
+ static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+ 			   uint id, uint lun)
+ {
+@@ -324,50 +234,14 @@ static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+ 	return 0;
+ }
+ 
+-static void iscsi_scan_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session, scan_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	if (session->state != ISCSI_SESSION_LOGGED_IN) {
+-		spin_unlock_irqrestore(&session->lock, flags);
+-		goto done;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
+-
+-	scsi_scan_target(&session->dev, 0, session->target_id,
+-			 SCAN_WILD_CARD, 1);
+-done:
+-	atomic_dec(&ihost->nr_scans);
+-}
+-
+ static void session_recovery_timedout(struct work_struct *work)
+ {
+ 	struct iscsi_cls_session *session =
+ 		container_of(work, struct iscsi_cls_session,
+ 			     recovery_work.work);
+-	unsigned long flags;
+-
+-	iscsi_cls_session_printk(KERN_INFO, session,
+-				 "session recovery timed out after %d secs\n",
+-				 session->recovery_tmo);
+ 
+-	spin_lock_irqsave(&session->lock, flags);
+-	switch (session->state) {
+-	case ISCSI_SESSION_FAILED:
+-		session->state = ISCSI_SESSION_FREE;
+-		break;
+-	case ISCSI_SESSION_LOGGED_IN:
+-	case ISCSI_SESSION_FREE:
+-		/* we raced with the unblock's flush */
+-		spin_unlock_irqrestore(&session->lock, flags);
+-		return;
+-	}
+-	spin_unlock_irqrestore(&session->lock, flags);
++	dev_printk(KERN_INFO, &session->dev, "iscsi: session recovery timed "
++		  "out after %d secs\n", session->recovery_tmo);
+ 
+ 	if (session->transport->session_recovery_timedout)
+ 		session->transport->session_recovery_timedout(session);
+@@ -375,103 +249,22 @@ static void session_recovery_timedout(struct work_struct *work)
+ 	scsi_target_unblock(&session->dev);
+ }
+ 
+-static void __iscsi_unblock_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     unblock_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-
+-	/*
+-	 * The recovery and unblock work get run from the same workqueue,
+-	 * so try to cancel it if it was going to run after this unblock.
+-	 */
+-	cancel_delayed_work(&session->recovery_work);
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_LOGGED_IN;
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	/* start IO */
+-	scsi_target_unblock(&session->dev);
+-	/*
+-	 * Only do kernel scanning if the driver is properly hooked into
+-	 * the async scanning code (drivers like iscsi_tcp do login and
+-	 * scanning from userspace).
+-	 */
+-	if (shost->hostt->scan_finished) {
+-		if (queue_work(ihost->scan_workq, &session->scan_work))
+-			atomic_inc(&ihost->nr_scans);
+-	}
+-}
+-
+-/**
+- * iscsi_unblock_session - set a session as logged in and start IO.
+- * @session: iscsi session
+- *
+- * Mark a session as ready to accept IO.
+- */
+ void iscsi_unblock_session(struct iscsi_cls_session *session)
+ {
+-	queue_work(iscsi_eh_timer_workq, &session->unblock_work);
+-	/*
+-	 * make sure all the events have completed before tell the driver
+-	 * it is safe
+-	 */
+-	flush_workqueue(iscsi_eh_timer_workq);
++	if (!cancel_delayed_work(&session->recovery_work))
++		flush_scheduled_work();
++	scsi_target_unblock(&session->dev);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_unblock_session);
+ 
+-static void __iscsi_block_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     block_work);
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_FAILED;
+-	spin_unlock_irqrestore(&session->lock, flags);
+-	scsi_target_block(&session->dev);
+-	queue_delayed_work(iscsi_eh_timer_workq, &session->recovery_work,
+-			   session->recovery_tmo * HZ);
+-}
+-
+ void iscsi_block_session(struct iscsi_cls_session *session)
+ {
+-	queue_work(iscsi_eh_timer_workq, &session->block_work);
++	scsi_target_block(&session->dev);
++	schedule_delayed_work(&session->recovery_work,
++			     session->recovery_tmo * HZ);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_block_session);
+ 
+-static void __iscsi_unbind_session(struct work_struct *work)
+-{
+-	struct iscsi_cls_session *session =
+-			container_of(work, struct iscsi_cls_session,
+-				     unbind_work);
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	/* Prevent new scans and make sure scanning is not in progress */
+-	mutex_lock(&ihost->mutex);
+-	if (list_empty(&session->host_list)) {
+-		mutex_unlock(&ihost->mutex);
+-		return;
+-	}
+-	list_del_init(&session->host_list);
+-	mutex_unlock(&ihost->mutex);
+-
+-	scsi_remove_target(&session->dev);
+-	iscsi_session_event(session, ISCSI_KEVENT_UNBIND_SESSION);
+-}
+-
+-static int iscsi_unbind_session(struct iscsi_cls_session *session)
+-{
+-	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+-	struct iscsi_host *ihost = shost->shost_data;
+-
+-	return queue_work(ihost->scan_workq, &session->unbind_work);
+-}
+-
+ struct iscsi_cls_session *
+ iscsi_alloc_session(struct Scsi_Host *shost,
+ 		    struct iscsi_transport *transport)
+@@ -485,15 +278,9 @@ iscsi_alloc_session(struct Scsi_Host *shost,
+ 
+ 	session->transport = transport;
+ 	session->recovery_tmo = 120;
+-	session->state = ISCSI_SESSION_FREE;
+ 	INIT_DELAYED_WORK(&session->recovery_work, session_recovery_timedout);
+ 	INIT_LIST_HEAD(&session->host_list);
+ 	INIT_LIST_HEAD(&session->sess_list);
+-	INIT_WORK(&session->unblock_work, __iscsi_unblock_session);
+-	INIT_WORK(&session->block_work, __iscsi_block_session);
+-	INIT_WORK(&session->unbind_work, __iscsi_unbind_session);
+-	INIT_WORK(&session->scan_work, iscsi_scan_session);
+-	spin_lock_init(&session->lock);
+ 
+ 	/* this is released in the dev's release function */
+ 	scsi_host_get(shost);
+@@ -510,7 +297,6 @@ int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
+ {
+ 	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+ 	struct iscsi_host *ihost;
+-	unsigned long flags;
+ 	int err;
+ 
+ 	ihost = shost->shost_data;
+@@ -521,21 +307,15 @@ int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
+ 		 session->sid);
+ 	err = device_add(&session->dev);
+ 	if (err) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "could not register session's dev\n");
++		dev_printk(KERN_ERR, &session->dev, "iscsi: could not "
++			   "register session's dev\n");
+ 		goto release_host;
+ 	}
+ 	transport_register_device(&session->dev);
+ 
+-	spin_lock_irqsave(&sesslock, flags);
+-	list_add(&session->sess_list, &sesslist);
+-	spin_unlock_irqrestore(&sesslock, flags);
+-
+ 	mutex_lock(&ihost->mutex);
+ 	list_add(&session->host_list, &ihost->sessions);
+ 	mutex_unlock(&ihost->mutex);
+-
+-	iscsi_session_event(session, ISCSI_KEVENT_CREATE_SESSION);
+ 	return 0;
+ 
+ release_host:
+@@ -548,10 +328,9 @@ EXPORT_SYMBOL_GPL(iscsi_add_session);
+  * iscsi_create_session - create iscsi class session
+  * @shost: scsi host
+  * @transport: iscsi transport
+- * @target_id: which target
+  *
+  * This can be called from a LLD or iscsi_transport.
+- */
++ **/
+ struct iscsi_cls_session *
+ iscsi_create_session(struct Scsi_Host *shost,
+ 		     struct iscsi_transport *transport,
+@@ -571,65 +350,19 @@ iscsi_create_session(struct Scsi_Host *shost,
+ }
+ EXPORT_SYMBOL_GPL(iscsi_create_session);
+ 
+-static void iscsi_conn_release(struct device *dev)
+-{
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev);
+-	struct device *parent = conn->dev.parent;
+-
+-	kfree(conn);
+-	put_device(parent);
+-}
+-
+-static int iscsi_is_conn_dev(const struct device *dev)
+-{
+-	return dev->release == iscsi_conn_release;
+-}
+-
+-static int iscsi_iter_destroy_conn_fn(struct device *dev, void *data)
+-{
+-	if (!iscsi_is_conn_dev(dev))
+-		return 0;
+-	return iscsi_destroy_conn(iscsi_dev_to_conn(dev));
+-}
+-
+ void iscsi_remove_session(struct iscsi_cls_session *session)
+ {
+ 	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+ 	struct iscsi_host *ihost = shost->shost_data;
+-	unsigned long flags;
+-	int err;
+-
+-	spin_lock_irqsave(&sesslock, flags);
+-	list_del(&session->sess_list);
+-	spin_unlock_irqrestore(&sesslock, flags);
+ 
+-	/* make sure there are no blocks/unblocks queued */
+-	flush_workqueue(iscsi_eh_timer_workq);
+-	/* make sure the timedout callout is not running */
+ 	if (!cancel_delayed_work(&session->recovery_work))
+-		flush_workqueue(iscsi_eh_timer_workq);
+-	/*
+-	 * If we are blocked let commands flow again. The lld or iscsi
+-	 * layer should set up the queuecommand to fail commands.
+-	 * We assume that LLD will not be calling block/unblock while
+-	 * removing the session.
+-	 */
+-	spin_lock_irqsave(&session->lock, flags);
+-	session->state = ISCSI_SESSION_FREE;
+-	spin_unlock_irqrestore(&session->lock, flags);
++		flush_scheduled_work();
+ 
+-	scsi_target_unblock(&session->dev);
+-	/* flush running scans then delete devices */
+-	flush_workqueue(ihost->scan_workq);
+-	__iscsi_unbind_session(&session->unbind_work);
++	mutex_lock(&ihost->mutex);
++	list_del(&session->host_list);
++	mutex_unlock(&ihost->mutex);
+ 
+-	/* hw iscsi may not have removed all connections from session */
+-	err = device_for_each_child(&session->dev, NULL,
+-				    iscsi_iter_destroy_conn_fn);
+-	if (err)
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Could not delete all connections "
+-					 "for session. Error %d.\n", err);
++	scsi_remove_target(&session->dev);
+ 
+ 	transport_unregister_device(&session->dev);
+ 	device_del(&session->dev);
+@@ -638,9 +371,9 @@ EXPORT_SYMBOL_GPL(iscsi_remove_session);
+ 
+ void iscsi_free_session(struct iscsi_cls_session *session)
+ {
+-	iscsi_session_event(session, ISCSI_KEVENT_DESTROY_SESSION);
+ 	put_device(&session->dev);
+ }
++
+ EXPORT_SYMBOL_GPL(iscsi_free_session);
+ 
+ /**
+@@ -649,7 +382,7 @@ EXPORT_SYMBOL_GPL(iscsi_free_session);
+  *
+  * Can be called by a LLD or iscsi_transport. There must not be
+  * any running connections.
+- */
++ **/
+ int iscsi_destroy_session(struct iscsi_cls_session *session)
+ {
+ 	iscsi_remove_session(session);
+@@ -658,6 +391,20 @@ int iscsi_destroy_session(struct iscsi_cls_session *session)
+ }
+ EXPORT_SYMBOL_GPL(iscsi_destroy_session);
+ 
++static void iscsi_conn_release(struct device *dev)
++{
++	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev);
++	struct device *parent = conn->dev.parent;
++
++	kfree(conn);
++	put_device(parent);
++}
++
++static int iscsi_is_conn_dev(const struct device *dev)
++{
++	return dev->release == iscsi_conn_release;
++}
++
+ /**
+  * iscsi_create_conn - create iscsi class connection
+  * @session: iscsi cls session
+@@ -671,13 +418,12 @@ EXPORT_SYMBOL_GPL(iscsi_destroy_session);
+  * for software iscsi we could be trying to preallocate a connection struct
+  * in which case there could be two connection structs and cid would be
+  * non-zero.
+- */
++ **/
+ struct iscsi_cls_conn *
+ iscsi_create_conn(struct iscsi_cls_session *session, uint32_t cid)
+ {
+ 	struct iscsi_transport *transport = session->transport;
+ 	struct iscsi_cls_conn *conn;
+-	unsigned long flags;
+ 	int err;
+ 
+ 	conn = kzalloc(sizeof(*conn) + transport->conndata_size, GFP_KERNEL);
+@@ -701,16 +447,11 @@ iscsi_create_conn(struct iscsi_cls_session *session, uint32_t cid)
+ 	conn->dev.release = iscsi_conn_release;
+ 	err = device_register(&conn->dev);
+ 	if (err) {
+-		iscsi_cls_session_printk(KERN_ERR, session, "could not "
+-					 "register connection's dev\n");
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: could not register "
++			   "connection's dev\n");
+ 		goto release_parent_ref;
+ 	}
+ 	transport_register_device(&conn->dev);
+-
+-	spin_lock_irqsave(&connlock, flags);
+-	list_add(&conn->conn_list, &connlist);
+-	conn->active = 1;
+-	spin_unlock_irqrestore(&connlock, flags);
+ 	return conn;
+ 
+ release_parent_ref:
+@@ -724,23 +465,17 @@ EXPORT_SYMBOL_GPL(iscsi_create_conn);
+ 
+ /**
+  * iscsi_destroy_conn - destroy iscsi class connection
+- * @conn: iscsi cls session
++ * @session: iscsi cls session
+  *
+  * This can be called from a LLD or iscsi_transport.
+- */
++ **/
+ int iscsi_destroy_conn(struct iscsi_cls_conn *conn)
+ {
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&connlock, flags);
+-	conn->active = 0;
+-	list_del(&conn->conn_list);
+-	spin_unlock_irqrestore(&connlock, flags);
+-
+ 	transport_unregister_device(&conn->dev);
+ 	device_unregister(&conn->dev);
+ 	return 0;
+ }
++
+ EXPORT_SYMBOL_GPL(iscsi_destroy_conn);
+ 
+ /*
+@@ -809,8 +544,8 @@ int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+ 	if (!skb) {
+ 		iscsi_conn_error(conn, ISCSI_ERR_CONN_FAILED);
+-		iscsi_cls_conn_printk(KERN_ERR, conn, "can not deliver "
+-				      "control PDU: OOM\n");
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: can not deliver "
++			   "control PDU: OOM\n");
+ 		return -ENOMEM;
+ 	}
+ 
+@@ -843,8 +578,8 @@ void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
+ 
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+ 	if (!skb) {
+-		iscsi_cls_conn_printk(KERN_ERR, conn, "gracefully ignored "
+-				      "conn error (%d)\n", error);
++		dev_printk(KERN_ERR, &conn->dev, "iscsi: gracefully ignored "
++			  "conn error (%d)\n", error);
+ 		return;
+ 	}
+ 
+@@ -858,8 +593,8 @@ void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
+ 
+ 	iscsi_broadcast_skb(skb, GFP_ATOMIC);
+ 
+-	iscsi_cls_conn_printk(KERN_INFO, conn, "detected conn error (%d)\n",
+-			      error);
++	dev_printk(KERN_INFO, &conn->dev, "iscsi: detected conn error (%d)\n",
++		   error);
+ }
+ EXPORT_SYMBOL_GPL(iscsi_conn_error);
+ 
+@@ -874,10 +609,12 @@ iscsi_if_send_reply(int pid, int seq, int type, int done, int multi,
+ 	int t = done ? NLMSG_DONE : type;
+ 
+ 	skb = alloc_skb(len, GFP_ATOMIC);
+-	if (!skb) {
+-		printk(KERN_ERR "Could not allocate skb to send reply.\n");
+-		return -ENOMEM;
+-	}
++	/*
++	 * FIXME:
++	 * user is supposed to react on iferror == -ENOMEM;
++	 * see iscsi_if_rx().
++	 */
++	BUG_ON(!skb);
+ 
+ 	nlh = __nlmsg_put(skb, pid, seq, t, (len - sizeof(*nlh)), 0);
+ 	nlh->nlmsg_flags = flags;
+@@ -914,8 +651,8 @@ iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+ 
+ 		skbstat = alloc_skb(len, GFP_ATOMIC);
+ 		if (!skbstat) {
+-			iscsi_cls_conn_printk(KERN_ERR, conn, "can not "
+-					      "deliver stats: OOM\n");
++			dev_printk(KERN_ERR, &conn->dev, "iscsi: can not "
++				   "deliver stats: OOM\n");
+ 			return -ENOMEM;
+ 		}
+ 
+@@ -950,87 +687,144 @@ iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+ }
+ 
+ /**
+- * iscsi_session_event - send session destr. completion event
+- * @session: iscsi class session
+- * @event: type of event
+- */
+-int iscsi_session_event(struct iscsi_cls_session *session,
+-			enum iscsi_uevent_e event)
++ * iscsi_if_destroy_session_done - send session destr. completion event
++ * @conn: last connection for session
++ *
++ * This is called by HW iscsi LLDs to notify userpsace that its HW has
++ * removed a session.
++ **/
++int iscsi_if_destroy_session_done(struct iscsi_cls_conn *conn)
+ {
+ 	struct iscsi_internal *priv;
++	struct iscsi_cls_session *session;
+ 	struct Scsi_Host *shost;
+ 	struct iscsi_uevent *ev;
+ 	struct sk_buff  *skb;
+ 	struct nlmsghdr *nlh;
++	unsigned long flags;
+ 	int rc, len = NLMSG_SPACE(sizeof(*ev));
+ 
+-	priv = iscsi_if_transport_lookup(session->transport);
++	priv = iscsi_if_transport_lookup(conn->transport);
+ 	if (!priv)
+ 		return -EINVAL;
++
++	session = iscsi_dev_to_session(conn->dev.parent);
+ 	shost = iscsi_session_to_shost(session);
+ 
+ 	skb = alloc_skb(len, GFP_KERNEL);
+ 	if (!skb) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Cannot notify userspace of session "
+-					 "event %u\n", event);
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event\n");
+ 		return -ENOMEM;
+ 	}
+ 
+ 	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
+ 	ev = NLMSG_DATA(nlh);
+-	ev->transport_handle = iscsi_handle(session->transport);
++	ev->transport_handle = iscsi_handle(conn->transport);
++	ev->type = ISCSI_KEVENT_DESTROY_SESSION;
++	ev->r.d_session.host_no = shost->host_no;
++	ev->r.d_session.sid = session->sid;
+ 
+-	ev->type = event;
+-	switch (event) {
+-	case ISCSI_KEVENT_DESTROY_SESSION:
+-		ev->r.d_session.host_no = shost->host_no;
+-		ev->r.d_session.sid = session->sid;
+-		break;
+-	case ISCSI_KEVENT_CREATE_SESSION:
+-		ev->r.c_session_ret.host_no = shost->host_no;
+-		ev->r.c_session_ret.sid = session->sid;
+-		break;
+-	case ISCSI_KEVENT_UNBIND_SESSION:
+-		ev->r.unbind_session.host_no = shost->host_no;
+-		ev->r.unbind_session.sid = session->sid;
+-		break;
+-	default:
+-		iscsi_cls_session_printk(KERN_ERR, session, "Invalid event "
+-					 "%u.\n", event);
+-		kfree_skb(skb);
++	/*
++	 * this will occur if the daemon is not up, so we just warn
++	 * the user and when the daemon is restarted it will handle it
++	 */
++	rc = iscsi_broadcast_skb(skb, GFP_KERNEL);
++	if (rc < 0)
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session destruction event. Check iscsi daemon\n");
++
++	spin_lock_irqsave(&sesslock, flags);
++	list_del(&session->sess_list);
++	spin_unlock_irqrestore(&sesslock, flags);
++
++	spin_lock_irqsave(&connlock, flags);
++	conn->active = 0;
++	list_del(&conn->conn_list);
++	spin_unlock_irqrestore(&connlock, flags);
++
++	return rc;
++}
++EXPORT_SYMBOL_GPL(iscsi_if_destroy_session_done);
++
++/**
++ * iscsi_if_create_session_done - send session creation completion event
++ * @conn: leading connection for session
++ *
++ * This is called by HW iscsi LLDs to notify userpsace that its HW has
++ * created a session or a existing session is back in the logged in state.
++ **/
++int iscsi_if_create_session_done(struct iscsi_cls_conn *conn)
++{
++	struct iscsi_internal *priv;
++	struct iscsi_cls_session *session;
++	struct Scsi_Host *shost;
++	struct iscsi_uevent *ev;
++	struct sk_buff  *skb;
++	struct nlmsghdr *nlh;
++	unsigned long flags;
++	int rc, len = NLMSG_SPACE(sizeof(*ev));
++
++	priv = iscsi_if_transport_lookup(conn->transport);
++	if (!priv)
+ 		return -EINVAL;
++
++	session = iscsi_dev_to_session(conn->dev.parent);
++	shost = iscsi_session_to_shost(session);
++
++	skb = alloc_skb(len, GFP_KERNEL);
++	if (!skb) {
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event\n");
++		return -ENOMEM;
+ 	}
+ 
++	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
++	ev = NLMSG_DATA(nlh);
++	ev->transport_handle = iscsi_handle(conn->transport);
++	ev->type = ISCSI_UEVENT_CREATE_SESSION;
++	ev->r.c_session_ret.host_no = shost->host_no;
++	ev->r.c_session_ret.sid = session->sid;
++
+ 	/*
+ 	 * this will occur if the daemon is not up, so we just warn
+ 	 * the user and when the daemon is restarted it will handle it
+ 	 */
+ 	rc = iscsi_broadcast_skb(skb, GFP_KERNEL);
+ 	if (rc < 0)
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "Cannot notify userspace of session "
+-					 "event %u. Check iscsi daemon\n",
+-					 event);
++		dev_printk(KERN_ERR, &conn->dev, "Cannot notify userspace of "
++			  "session creation event. Check iscsi daemon\n");
++
++	spin_lock_irqsave(&sesslock, flags);
++	list_add(&session->sess_list, &sesslist);
++	spin_unlock_irqrestore(&sesslock, flags);
++
++	spin_lock_irqsave(&connlock, flags);
++	list_add(&conn->conn_list, &connlist);
++	conn->active = 1;
++	spin_unlock_irqrestore(&connlock, flags);
+ 	return rc;
+ }
+-EXPORT_SYMBOL_GPL(iscsi_session_event);
++EXPORT_SYMBOL_GPL(iscsi_if_create_session_done);
+ 
+ static int
+ iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_uevent *ev)
+ {
+ 	struct iscsi_transport *transport = priv->iscsi_transport;
+ 	struct iscsi_cls_session *session;
++	unsigned long flags;
+ 	uint32_t hostno;
+ 
+ 	session = transport->create_session(transport, &priv->t,
+-					    ev->u.c_session.cmds_max,
+-					    ev->u.c_session.queue_depth,
+ 					    ev->u.c_session.initial_cmdsn,
+ 					    &hostno);
+ 	if (!session)
+ 		return -ENOMEM;
+ 
++	spin_lock_irqsave(&sesslock, flags);
++	list_add(&session->sess_list, &sesslist);
++	spin_unlock_irqrestore(&sesslock, flags);
++
+ 	ev->r.c_session_ret.host_no = hostno;
+ 	ev->r.c_session_ret.sid = session->sid;
+ 	return 0;
+@@ -1041,34 +835,47 @@ iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+ {
+ 	struct iscsi_cls_conn *conn;
+ 	struct iscsi_cls_session *session;
++	unsigned long flags;
+ 
+ 	session = iscsi_session_lookup(ev->u.c_conn.sid);
+ 	if (!session) {
+-		printk(KERN_ERR "iscsi: invalid session %d.\n",
++		printk(KERN_ERR "iscsi: invalid session %d\n",
+ 		       ev->u.c_conn.sid);
+ 		return -EINVAL;
+ 	}
+ 
+ 	conn = transport->create_conn(session, ev->u.c_conn.cid);
+ 	if (!conn) {
+-		iscsi_cls_session_printk(KERN_ERR, session,
+-					 "couldn't create a new connection.");
++		printk(KERN_ERR "iscsi: couldn't create a new "
++			   "connection for session %d\n",
++			   session->sid);
+ 		return -ENOMEM;
+ 	}
+ 
+ 	ev->r.c_conn_ret.sid = session->sid;
+ 	ev->r.c_conn_ret.cid = conn->cid;
++
++	spin_lock_irqsave(&connlock, flags);
++	list_add(&conn->conn_list, &connlist);
++	conn->active = 1;
++	spin_unlock_irqrestore(&connlock, flags);
++
+ 	return 0;
+ }
+ 
+ static int
+ iscsi_if_destroy_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+ {
++	unsigned long flags;
+ 	struct iscsi_cls_conn *conn;
+ 
+ 	conn = iscsi_conn_lookup(ev->u.d_conn.sid, ev->u.d_conn.cid);
+ 	if (!conn)
+ 		return -EINVAL;
++	spin_lock_irqsave(&connlock, flags);
++	conn->active = 0;
++	list_del(&conn->conn_list);
++	spin_unlock_irqrestore(&connlock, flags);
+ 
+ 	if (transport->destroy_conn)
+ 		transport->destroy_conn(conn);
+@@ -1140,50 +947,15 @@ static int
+ iscsi_tgt_dscvr(struct iscsi_transport *transport,
+ 		struct iscsi_uevent *ev)
+ {
+-	struct Scsi_Host *shost;
+ 	struct sockaddr *dst_addr;
+-	int err;
+ 
+ 	if (!transport->tgt_dscvr)
+ 		return -EINVAL;
+ 
+-	shost = scsi_host_lookup(ev->u.tgt_dscvr.host_no);
+-	if (IS_ERR(shost)) {
+-		printk(KERN_ERR "target discovery could not find host no %u\n",
+-		       ev->u.tgt_dscvr.host_no);
+-		return -ENODEV;
+-	}
+-
+-
+ 	dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
+-	err = transport->tgt_dscvr(shost, ev->u.tgt_dscvr.type,
+-				   ev->u.tgt_dscvr.enable, dst_addr);
+-	scsi_host_put(shost);
+-	return err;
+-}
+-
+-static int
+-iscsi_set_host_param(struct iscsi_transport *transport,
+-		     struct iscsi_uevent *ev)
+-{
+-	char *data = (char*)ev + sizeof(*ev);
+-	struct Scsi_Host *shost;
+-	int err;
+-
+-	if (!transport->set_host_param)
+-		return -ENOSYS;
+-
+-	shost = scsi_host_lookup(ev->u.set_host_param.host_no);
+-	if (IS_ERR(shost)) {
+-		printk(KERN_ERR "set_host_param could not find host no %u\n",
+-		       ev->u.set_host_param.host_no);
+-		return -ENODEV;
+-	}
+-
+-	err = transport->set_host_param(shost, ev->u.set_host_param.param,
+-					data, ev->u.set_host_param.len);
+-	scsi_host_put(shost);
+-	return err;
++	return transport->tgt_dscvr(ev->u.tgt_dscvr.type,
++				    ev->u.tgt_dscvr.host_no,
++				    ev->u.tgt_dscvr.enable, dst_addr);
+ }
+ 
+ static int
+@@ -1195,6 +967,7 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 	struct iscsi_internal *priv;
+ 	struct iscsi_cls_session *session;
+ 	struct iscsi_cls_conn *conn;
++	unsigned long flags;
+ 
+ 	priv = iscsi_if_transport_lookup(iscsi_ptr(ev->transport_handle));
+ 	if (!priv)
+@@ -1212,16 +985,13 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 		break;
+ 	case ISCSI_UEVENT_DESTROY_SESSION:
+ 		session = iscsi_session_lookup(ev->u.d_session.sid);
+-		if (session)
++		if (session) {
++			spin_lock_irqsave(&sesslock, flags);
++			list_del(&session->sess_list);
++			spin_unlock_irqrestore(&sesslock, flags);
++
+ 			transport->destroy_session(session);
+-		else
+-			err = -EINVAL;
+-		break;
+-	case ISCSI_UEVENT_UNBIND_SESSION:
+-		session = iscsi_session_lookup(ev->u.d_session.sid);
+-		if (session)
+-			iscsi_unbind_session(session);
+-		else
++		} else
+ 			err = -EINVAL;
+ 		break;
+ 	case ISCSI_UEVENT_CREATE_CONN:
+@@ -1279,11 +1049,8 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ 	case ISCSI_UEVENT_TGT_DSCVR:
+ 		err = iscsi_tgt_dscvr(transport, ev);
+ 		break;
+-	case ISCSI_UEVENT_SET_HOST_PARAM:
+-		err = iscsi_set_host_param(transport, ev);
+-		break;
+ 	default:
+-		err = -ENOSYS;
++		err = -EINVAL;
+ 		break;
+ 	}
+ 
+@@ -1292,55 +1059,70 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+ }
+ 
+ /*
+- * Get message from skb.  Each message is processed by iscsi_if_recv_msg.
+- * Malformed skbs with wrong lengths or invalid creds are not processed.
++ * Get message from skb (based on rtnetlink_rcv_skb).  Each message is
++ * processed by iscsi_if_recv_msg.  Malformed skbs with wrong lengths or
++ * invalid creds are discarded silently.
+  */
+ static void
+-iscsi_if_rx(struct sk_buff *skb)
++iscsi_if_rx(struct sock *sk, int len)
+ {
++	struct sk_buff *skb;
++
+ 	mutex_lock(&rx_queue_mutex);
+-	while (skb->len >= NLMSG_SPACE(0)) {
+-		int err;
+-		uint32_t rlen;
+-		struct nlmsghdr	*nlh;
+-		struct iscsi_uevent *ev;
+-
+-		nlh = nlmsg_hdr(skb);
+-		if (nlh->nlmsg_len < sizeof(*nlh) ||
+-		    skb->len < nlh->nlmsg_len) {
+-			break;
++	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
++		if (NETLINK_CREDS(skb)->uid) {
++			skb_pull(skb, skb->len);
++			goto free_skb;
+ 		}
+ 
+-		ev = NLMSG_DATA(nlh);
+-		rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+-		if (rlen > skb->len)
+-			rlen = skb->len;
++		while (skb->len >= NLMSG_SPACE(0)) {
++			int err;
++			uint32_t rlen;
++			struct nlmsghdr	*nlh;
++			struct iscsi_uevent *ev;
+ 
+-		err = iscsi_if_recv_msg(skb, nlh);
+-		if (err) {
+-			ev->type = ISCSI_KEVENT_IF_ERROR;
+-			ev->iferror = err;
+-		}
+-		do {
+-			/*
+-			 * special case for GET_STATS:
+-			 * on success - sending reply and stats from
+-			 * inside of if_recv_msg(),
+-			 * on error - fall through.
+-			 */
+-			if (ev->type == ISCSI_UEVENT_GET_STATS && !err)
++			nlh = nlmsg_hdr(skb);
++			if (nlh->nlmsg_len < sizeof(*nlh) ||
++			    skb->len < nlh->nlmsg_len) {
+ 				break;
+-			err = iscsi_if_send_reply(
+-				NETLINK_CREDS(skb)->pid, nlh->nlmsg_seq,
+-				nlh->nlmsg_type, 0, 0, ev, sizeof(*ev));
+-		} while (err < 0 && err != -ECONNREFUSED);
+-		skb_pull(skb, rlen);
++			}
++
++			ev = NLMSG_DATA(nlh);
++			rlen = NLMSG_ALIGN(nlh->nlmsg_len);
++			if (rlen > skb->len)
++				rlen = skb->len;
++
++			err = iscsi_if_recv_msg(skb, nlh);
++			if (err) {
++				ev->type = ISCSI_KEVENT_IF_ERROR;
++				ev->iferror = err;
++			}
++			do {
++				/*
++				 * special case for GET_STATS:
++				 * on success - sending reply and stats from
++				 * inside of if_recv_msg(),
++				 * on error - fall through.
++				 */
++				if (ev->type == ISCSI_UEVENT_GET_STATS && !err)
++					break;
++				err = iscsi_if_send_reply(
++					NETLINK_CREDS(skb)->pid, nlh->nlmsg_seq,
++					nlh->nlmsg_type, 0, 0, ev, sizeof(*ev));
++			} while (err < 0 && err != -ECONNREFUSED);
++			skb_pull(skb, rlen);
++		}
++free_skb:
++		kfree_skb(skb);
+ 	}
+ 	mutex_unlock(&rx_queue_mutex);
+ }
+ 
++#define iscsi_cdev_to_conn(_cdev) \
++	iscsi_dev_to_conn(_cdev->dev)
++
+ #define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
+-struct device_attribute dev_attr_##_prefix##_##_name =	\
++struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
+ 	__ATTR(_name,_mode,_show,_store)
+ 
+ /*
+@@ -1348,10 +1130,9 @@ struct device_attribute dev_attr_##_prefix##_##_name =	\
+  */
+ #define iscsi_conn_attr_show(param)					\
+ static ssize_t								\
+-show_conn_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
++show_conn_param_##param(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);	\
++	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+ 	struct iscsi_transport *t = conn->transport;			\
+ 	return t->get_conn_param(conn, param, buf);			\
+ }
+@@ -1372,66 +1153,43 @@ iscsi_conn_attr(port, ISCSI_PARAM_CONN_PORT);
+ iscsi_conn_attr(exp_statsn, ISCSI_PARAM_EXP_STATSN);
+ iscsi_conn_attr(persistent_address, ISCSI_PARAM_PERSISTENT_ADDRESS);
+ iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS);
+-iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO);
+-iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO);
++
++#define iscsi_cdev_to_session(_cdev) \
++	iscsi_dev_to_session(_cdev->dev)
+ 
+ /*
+  * iSCSI session attrs
+  */
+-#define iscsi_session_attr_show(param, perm)				\
++#define iscsi_session_attr_show(param)					\
+ static ssize_t								\
+-show_session_param_##param(struct device *dev,				\
+-			   struct device_attribute *attr, char *buf)	\
++show_session_param_##param(struct class_device *cdev, char *buf)	\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-		iscsi_dev_to_session(dev->parent);			\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \
+ 	struct iscsi_transport *t = session->transport;			\
+-									\
+-	if (perm && !capable(CAP_SYS_ADMIN))				\
+-		return -EACCES;						\
+ 	return t->get_session_param(session, param, buf);		\
+ }
+ 
+-#define iscsi_session_attr(field, param, perm)				\
+-	iscsi_session_attr_show(param, perm)				\
++#define iscsi_session_attr(field, param)				\
++	iscsi_session_attr_show(param)					\
+ static ISCSI_CLASS_ATTR(sess, field, S_IRUGO, show_session_param_##param, \
+ 			NULL);
+ 
+-iscsi_session_attr(targetname, ISCSI_PARAM_TARGET_NAME, 0);
+-iscsi_session_attr(initial_r2t, ISCSI_PARAM_INITIAL_R2T_EN, 0);
+-iscsi_session_attr(max_outstanding_r2t, ISCSI_PARAM_MAX_R2T, 0);
+-iscsi_session_attr(immediate_data, ISCSI_PARAM_IMM_DATA_EN, 0);
+-iscsi_session_attr(first_burst_len, ISCSI_PARAM_FIRST_BURST, 0);
+-iscsi_session_attr(max_burst_len, ISCSI_PARAM_MAX_BURST, 0);
+-iscsi_session_attr(data_pdu_in_order, ISCSI_PARAM_PDU_INORDER_EN, 0);
+-iscsi_session_attr(data_seq_in_order, ISCSI_PARAM_DATASEQ_INORDER_EN, 0);
+-iscsi_session_attr(erl, ISCSI_PARAM_ERL, 0);
+-iscsi_session_attr(tpgt, ISCSI_PARAM_TPGT, 0);
+-iscsi_session_attr(username, ISCSI_PARAM_USERNAME, 1);
+-iscsi_session_attr(username_in, ISCSI_PARAM_USERNAME_IN, 1);
+-iscsi_session_attr(password, ISCSI_PARAM_PASSWORD, 1);
+-iscsi_session_attr(password_in, ISCSI_PARAM_PASSWORD_IN, 1);
+-iscsi_session_attr(fast_abort, ISCSI_PARAM_FAST_ABORT, 0);
+-iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
+-iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
+-
+-static ssize_t
+-show_priv_session_state(struct device *dev, struct device_attribute *attr,
+-			char *buf)
+-{
+-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev->parent);
+-	return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
+-}
+-static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
+-			NULL);
++iscsi_session_attr(targetname, ISCSI_PARAM_TARGET_NAME);
++iscsi_session_attr(initial_r2t, ISCSI_PARAM_INITIAL_R2T_EN);
++iscsi_session_attr(max_outstanding_r2t, ISCSI_PARAM_MAX_R2T);
++iscsi_session_attr(immediate_data, ISCSI_PARAM_IMM_DATA_EN);
++iscsi_session_attr(first_burst_len, ISCSI_PARAM_FIRST_BURST);
++iscsi_session_attr(max_burst_len, ISCSI_PARAM_MAX_BURST);
++iscsi_session_attr(data_pdu_in_order, ISCSI_PARAM_PDU_INORDER_EN);
++iscsi_session_attr(data_seq_in_order, ISCSI_PARAM_DATASEQ_INORDER_EN);
++iscsi_session_attr(erl, ISCSI_PARAM_ERL);
++iscsi_session_attr(tpgt, ISCSI_PARAM_TPGT);
+ 
+ #define iscsi_priv_session_attr_show(field, format)			\
+ static ssize_t								\
+-show_priv_session_##field(struct device *dev, 				\
+-			  struct device_attribute *attr, char *buf)	\
++show_priv_session_##field(struct class_device *cdev, char *buf)		\
+ {									\
+-	struct iscsi_cls_session *session = 				\
+-			iscsi_dev_to_session(dev->parent);		\
++	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+ 	return sprintf(buf, format"\n", session->field);		\
+ }
+ 
+@@ -1441,32 +1199,9 @@ static ISCSI_CLASS_ATTR(priv_sess, field, S_IRUGO, show_priv_session_##field, \
+ 			NULL)
+ iscsi_priv_session_attr(recovery_tmo, "%d");
+ 
+-/*
+- * iSCSI host attrs
+- */
+-#define iscsi_host_attr_show(param)					\
+-static ssize_t								\
+-show_host_param_##param(struct device *dev, 				\
+-			struct device_attribute *attr, char *buf)	\
+-{									\
+-	struct Scsi_Host *shost = transport_class_to_shost(dev);	\
+-	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \
+-	return priv->iscsi_transport->get_host_param(shost, param, buf); \
+-}
+-
+-#define iscsi_host_attr(field, param)					\
+-	iscsi_host_attr_show(param)					\
+-static ISCSI_CLASS_ATTR(host, field, S_IRUGO, show_host_param_##param,	\
+-			NULL);
+-
+-iscsi_host_attr(netdev, ISCSI_HOST_PARAM_NETDEV_NAME);
+-iscsi_host_attr(hwaddress, ISCSI_HOST_PARAM_HWADDRESS);
+-iscsi_host_attr(ipaddress, ISCSI_HOST_PARAM_IPADDRESS);
+-iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
+-
+ #define SETUP_PRIV_SESSION_RD_ATTR(field)				\
+ do {									\
+-	priv->session_attrs[count] = &dev_attr_priv_sess_##field; \
++	priv->session_attrs[count] = &class_device_attr_priv_sess_##field; \
+ 	count++;							\
+ } while (0)
+ 
+@@ -1474,7 +1209,7 @@ do {									\
+ #define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->session_attrs[count] = &dev_attr_sess_##field; \
++		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1482,15 +1217,7 @@ do {									\
+ #define SETUP_CONN_RD_ATTR(field, param_flag)				\
+ do {									\
+ 	if (tt->param_mask & param_flag) {				\
+-		priv->conn_attrs[count] = &dev_attr_conn_##field; \
+-		count++;						\
+-	}								\
+-} while (0)
+-
+-#define SETUP_HOST_RD_ATTR(field, param_flag)				\
+-do {									\
+-	if (tt->host_param_mask & param_flag) {				\
+-		priv->host_attrs[count] = &dev_attr_host_##field; \
++		priv->conn_attrs[count] = &class_device_attr_conn_##field; \
+ 		count++;						\
+ 	}								\
+ } while (0)
+@@ -1581,31 +1308,24 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	priv->iscsi_transport = tt;
+ 	priv->t.user_scan = iscsi_user_scan;
+ 
+-	priv->dev.class = &iscsi_transport_class;
+-	snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name);
+-	err = device_register(&priv->dev);
++	priv->cdev.class = &iscsi_transport_class;
++	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
++	err = class_device_register(&priv->cdev);
+ 	if (err)
+ 		goto free_priv;
+ 
+-	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
++	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	if (err)
+-		goto unregister_dev;
++		goto unregister_cdev;
+ 
+ 	/* host parameters */
+ 	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+ 	priv->t.host_attrs.ac.class = &iscsi_host_class.class;
+ 	priv->t.host_attrs.ac.match = iscsi_host_match;
+ 	priv->t.host_size = sizeof(struct iscsi_host);
++	priv->host_attrs[0] = NULL;
+ 	transport_container_register(&priv->t.host_attrs);
+ 
+-	SETUP_HOST_RD_ATTR(netdev, ISCSI_HOST_NETDEV_NAME);
+-	SETUP_HOST_RD_ATTR(ipaddress, ISCSI_HOST_IPADDRESS);
+-	SETUP_HOST_RD_ATTR(hwaddress, ISCSI_HOST_HWADDRESS);
+-	SETUP_HOST_RD_ATTR(initiatorname, ISCSI_HOST_INITIATOR_NAME);
+-	BUG_ON(count > ISCSI_HOST_ATTRS);
+-	priv->host_attrs[count] = NULL;
+-	count = 0;
+-
+ 	/* connection parameters */
+ 	priv->conn_cont.ac.attrs = &priv->conn_attrs[0];
+ 	priv->conn_cont.ac.class = &iscsi_connection_class.class;
+@@ -1623,8 +1343,6 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	SETUP_CONN_RD_ATTR(exp_statsn, ISCSI_EXP_STATSN);
+ 	SETUP_CONN_RD_ATTR(persistent_address, ISCSI_PERSISTENT_ADDRESS);
+ 	SETUP_CONN_RD_ATTR(persistent_port, ISCSI_PERSISTENT_PORT);
+-	SETUP_CONN_RD_ATTR(ping_tmo, ISCSI_PING_TMO);
+-	SETUP_CONN_RD_ATTR(recv_tmo, ISCSI_RECV_TMO);
+ 
+ 	BUG_ON(count > ISCSI_CONN_ATTRS);
+ 	priv->conn_attrs[count] = NULL;
+@@ -1646,15 +1364,7 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	SETUP_SESSION_RD_ATTR(erl, ISCSI_ERL);
+ 	SETUP_SESSION_RD_ATTR(targetname, ISCSI_TARGET_NAME);
+ 	SETUP_SESSION_RD_ATTR(tpgt, ISCSI_TPGT);
+-	SETUP_SESSION_RD_ATTR(password, ISCSI_USERNAME);
+-	SETUP_SESSION_RD_ATTR(password_in, ISCSI_USERNAME_IN);
+-	SETUP_SESSION_RD_ATTR(username, ISCSI_PASSWORD);
+-	SETUP_SESSION_RD_ATTR(username_in, ISCSI_PASSWORD_IN);
+-	SETUP_SESSION_RD_ATTR(fast_abort, ISCSI_FAST_ABORT);
+-	SETUP_SESSION_RD_ATTR(abort_tmo, ISCSI_ABORT_TMO);
+-	SETUP_SESSION_RD_ATTR(lu_reset_tmo,ISCSI_LU_RESET_TMO);
+ 	SETUP_PRIV_SESSION_RD_ATTR(recovery_tmo);
+-	SETUP_PRIV_SESSION_RD_ATTR(state);
+ 
+ 	BUG_ON(count > ISCSI_SESSION_ATTRS);
+ 	priv->session_attrs[count] = NULL;
+@@ -1666,8 +1376,8 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+ 	return &priv->t;
+ 
+-unregister_dev:
+-	device_unregister(&priv->dev);
++unregister_cdev:
++	class_device_unregister(&priv->cdev);
+ free_priv:
+ 	kfree(priv);
+ 	return NULL;
+@@ -1694,8 +1404,8 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 	transport_container_unregister(&priv->session_cont);
+ 	transport_container_unregister(&priv->t.host_attrs);
+ 
+-	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
+-	device_unregister(&priv->dev);
++	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
++	class_device_unregister(&priv->cdev);
+ 	mutex_unlock(&rx_queue_mutex);
+ 
+ 	return 0;
+@@ -1734,14 +1444,8 @@ static __init int iscsi_transport_init(void)
+ 		goto unregister_session_class;
+ 	}
+ 
+-	iscsi_eh_timer_workq = create_singlethread_workqueue("iscsi_eh");
+-	if (!iscsi_eh_timer_workq)
+-		goto release_nls;
+-
+ 	return 0;
+ 
+-release_nls:
+-	netlink_kernel_release(nls);
+ unregister_session_class:
+ 	transport_class_unregister(&iscsi_session_class);
+ unregister_conn_class:
+@@ -1755,8 +1459,7 @@ unregister_transport_class:
+ 
+ static void __exit iscsi_transport_exit(void)
+ {
+-	destroy_workqueue(iscsi_eh_timer_workq);
+-	netlink_kernel_release(nls);
++	sock_release(nls->sk_socket);
+ 	transport_class_unregister(&iscsi_connection_class);
+ 	transport_class_unregister(&iscsi_session_class);
+ 	transport_class_unregister(&iscsi_host_class);
+diff --git a/include/scsi/iscsi_if.h b/include/scsi/iscsi_if.h
+index e19e584..55ebf03 100644
+--- a/include/scsi/iscsi_if.h
++++ b/include/scsi/iscsi_if.h
+@@ -48,16 +48,12 @@ enum iscsi_uevent_e {
+ 	ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT	= UEVENT_BASE + 14,
+ 
+ 	ISCSI_UEVENT_TGT_DSCVR		= UEVENT_BASE + 15,
+-	ISCSI_UEVENT_SET_HOST_PARAM	= UEVENT_BASE + 16,
+-	ISCSI_UEVENT_UNBIND_SESSION	= UEVENT_BASE + 17,
+ 
+ 	/* up events */
+ 	ISCSI_KEVENT_RECV_PDU		= KEVENT_BASE + 1,
+ 	ISCSI_KEVENT_CONN_ERROR		= KEVENT_BASE + 2,
+ 	ISCSI_KEVENT_IF_ERROR		= KEVENT_BASE + 3,
+ 	ISCSI_KEVENT_DESTROY_SESSION	= KEVENT_BASE + 4,
+-	ISCSI_KEVENT_UNBIND_SESSION	= KEVENT_BASE + 5,
+-	ISCSI_KEVENT_CREATE_SESSION	= KEVENT_BASE + 6,
+ };
+ 
+ enum iscsi_tgt_dscvr {
+@@ -75,8 +71,6 @@ struct iscsi_uevent {
+ 		/* messages u -> k */
+ 		struct msg_create_session {
+ 			uint32_t	initial_cmdsn;
+-			uint16_t	cmds_max;
+-			uint16_t	queue_depth;
+ 		} c_session;
+ 		struct msg_destroy_session {
+ 			uint32_t	sid;
+@@ -142,11 +136,6 @@ struct iscsi_uevent {
+ 			 */
+ 			uint32_t	enable;
+ 		} tgt_dscvr;
+-		struct msg_set_host_param {
+-			uint32_t	host_no;
+-			uint32_t	param; /* enum iscsi_host_param */
+-			uint32_t	len;
+-		} set_host_param;
+ 	} u;
+ 	union {
+ 		/* messages k -> u */
+@@ -159,10 +148,6 @@ struct iscsi_uevent {
+ 			uint32_t	sid;
+ 			uint32_t	cid;
+ 		} c_conn_ret;
+-		struct msg_unbind_session {
+-			uint32_t	sid;
+-			uint32_t	host_no;
+-		} unbind_session;
+ 		struct msg_recv_req {
+ 			uint32_t	sid;
+ 			uint32_t	cid;
+@@ -238,18 +223,6 @@ enum iscsi_param {
+ 	ISCSI_PARAM_CONN_PORT,
+ 	ISCSI_PARAM_CONN_ADDRESS,
+ 
+-	ISCSI_PARAM_USERNAME,
+-	ISCSI_PARAM_USERNAME_IN,
+-	ISCSI_PARAM_PASSWORD,
+-	ISCSI_PARAM_PASSWORD_IN,
+-
+-	ISCSI_PARAM_FAST_ABORT,
+-	ISCSI_PARAM_ABORT_TMO,
+-	ISCSI_PARAM_LU_RESET_TMO,
+-	ISCSI_PARAM_HOST_RESET_TMO,
+-
+-	ISCSI_PARAM_PING_TMO,
+-	ISCSI_PARAM_RECV_TMO,
+ 	/* must always be last */
+ 	ISCSI_PARAM_MAX,
+ };
+@@ -276,30 +249,6 @@ enum iscsi_param {
+ #define ISCSI_SESS_RECOVERY_TMO		(1 << ISCSI_PARAM_SESS_RECOVERY_TMO)
+ #define ISCSI_CONN_PORT			(1 << ISCSI_PARAM_CONN_PORT)
+ #define ISCSI_CONN_ADDRESS		(1 << ISCSI_PARAM_CONN_ADDRESS)
+-#define ISCSI_USERNAME			(1 << ISCSI_PARAM_USERNAME)
+-#define ISCSI_USERNAME_IN		(1 << ISCSI_PARAM_USERNAME_IN)
+-#define ISCSI_PASSWORD			(1 << ISCSI_PARAM_PASSWORD)
+-#define ISCSI_PASSWORD_IN		(1 << ISCSI_PARAM_PASSWORD_IN)
+-#define ISCSI_FAST_ABORT		(1 << ISCSI_PARAM_FAST_ABORT)
+-#define ISCSI_ABORT_TMO			(1 << ISCSI_PARAM_ABORT_TMO)
+-#define ISCSI_LU_RESET_TMO		(1 << ISCSI_PARAM_LU_RESET_TMO)
+-#define ISCSI_HOST_RESET_TMO		(1 << ISCSI_PARAM_HOST_RESET_TMO)
+-#define ISCSI_PING_TMO			(1 << ISCSI_PARAM_PING_TMO)
+-#define ISCSI_RECV_TMO			(1 << ISCSI_PARAM_RECV_TMO)
+-
+-/* iSCSI HBA params */
+-enum iscsi_host_param {
+-	ISCSI_HOST_PARAM_HWADDRESS,
+-	ISCSI_HOST_PARAM_INITIATOR_NAME,
+-	ISCSI_HOST_PARAM_NETDEV_NAME,
+-	ISCSI_HOST_PARAM_IPADDRESS,
+-	ISCSI_HOST_PARAM_MAX,
+-};
+-
+-#define ISCSI_HOST_HWADDRESS		(1 << ISCSI_HOST_PARAM_HWADDRESS)
+-#define ISCSI_HOST_INITIATOR_NAME	(1 << ISCSI_HOST_PARAM_INITIATOR_NAME)
+-#define ISCSI_HOST_NETDEV_NAME		(1 << ISCSI_HOST_PARAM_NETDEV_NAME)
+-#define ISCSI_HOST_IPADDRESS		(1 << ISCSI_HOST_PARAM_IPADDRESS)
+ 
+ #define iscsi_ptr(_handle) ((void*)(unsigned long)_handle)
+ #define iscsi_handle(_ptr) ((uint64_t)(unsigned long)_ptr)
+@@ -323,9 +272,6 @@ enum iscsi_host_param {
+ #define CAP_MULTI_CONN		0x40
+ #define CAP_TEXT_NEGO		0x80
+ #define CAP_MARKERS		0x100
+-#define CAP_FW_DB		0x200
+-#define CAP_SENDTARGETS_OFFLOAD	0x400
+-#define CAP_DATA_PATH_OFFLOAD	0x800
+ 
+ /*
+  * These flags describes reason of stop_conn() call
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index cd3ca63..ea0816d 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -48,8 +48,9 @@ struct iscsi_nopin;
+ #define debug_scsi(fmt...)
+ #endif
+ 
+-#define ISCSI_DEF_XMIT_CMDS_MAX	128	/* must be power of 2 */
+-#define ISCSI_MGMT_CMDS_MAX	16	/* must be power of 2 */
++#define ISCSI_XMIT_CMDS_MAX	128	/* must be power of 2 */
++#define ISCSI_MGMT_CMDS_MAX	32	/* must be power of 2 */
++#define ISCSI_CONN_MAX			1
+ 
+ #define ISCSI_MGMT_ITT_OFFSET	0xa00
+ 
+@@ -57,31 +58,21 @@ struct iscsi_nopin;
+ #define ISCSI_MAX_CMD_PER_LUN		128
+ 
+ /* Task Mgmt states */
+-enum {
+-	TMF_INITIAL,
+-	TMF_QUEUED,
+-	TMF_SUCCESS,
+-	TMF_FAILED,
+-	TMF_TIMEDOUT,
+-	TMF_NOT_FOUND,
+-};
++#define TMABORT_INITIAL			0x0
++#define TMABORT_SUCCESS			0x1
++#define TMABORT_FAILED			0x2
++#define TMABORT_TIMEDOUT		0x3
++#define TMABORT_NOT_FOUND		0x4
+ 
+ /* Connection suspend "bit" */
+ #define ISCSI_SUSPEND_BIT		1
+ 
+ #define ISCSI_ITT_MASK			(0xfff)
++#define ISCSI_CID_SHIFT			12
++#define ISCSI_CID_MASK			(0xffff << ISCSI_CID_SHIFT)
+ #define ISCSI_AGE_SHIFT			28
+ #define ISCSI_AGE_MASK			(0xf << ISCSI_AGE_SHIFT)
+ 
+-#define ISCSI_ADDRESS_BUF_LEN		64
+-
+-enum {
+-	/* this is the maximum possible storage for AHSs */
+-	ISCSI_MAX_AHS_SIZE = sizeof(struct iscsi_ecdb_ahdr) +
+-				sizeof(struct iscsi_rlength_ahdr),
+-	ISCSI_DIGEST_SIZE = sizeof(__u32),
+-};
+-
+ struct iscsi_mgmt_task {
+ 	/*
+ 	 * Becuae LLDs allocate their hdr differently, this is a pointer to
+@@ -89,7 +80,7 @@ struct iscsi_mgmt_task {
+ 	 */
+ 	struct iscsi_hdr	*hdr;
+ 	char			*data;		/* mgmt payload */
+-	unsigned		data_count;	/* counts data to be sent */
++	int			data_count;	/* counts data to be sent */
+ 	uint32_t		itt;		/* this ITT */
+ 	void			*dd_data;	/* driver/transport data */
+ 	struct list_head	running;
+@@ -103,23 +94,23 @@ enum {
+ 
+ struct iscsi_cmd_task {
+ 	/*
+-	 * Because LLDs allocate their hdr differently, this is a pointer
+-	 * and length to that storage. It must be setup at session
+-	 * creation time.
++	 * Becuae LLDs allocate their hdr differently, this is a pointer to
++	 * that storage. It must be setup at session creation time.
+ 	 */
+ 	struct iscsi_cmd	*hdr;
+-	unsigned short		hdr_max;
+-	unsigned short		hdr_len;	/* accumulated size of hdr used */
+ 	int			itt;		/* this ITT */
++	int			datasn;		/* DataSN */
+ 
+ 	uint32_t		unsol_datasn;
+-	unsigned		imm_count;	/* imm-data (bytes)   */
+-	unsigned		unsol_count;	/* unsolicited (bytes)*/
++	int			imm_count;	/* imm-data (bytes)   */
++	int			unsol_count;	/* unsolicited (bytes)*/
+ 	/* offset in unsolicited stream (bytes); */
+-	unsigned		unsol_offset;
+-	unsigned		data_count;	/* remaining Data-Out */
++	int			unsol_offset;
++	int			data_count;	/* remaining Data-Out */
+ 	struct scsi_cmnd	*sc;		/* associated SCSI cmd*/
++	int			total_length;
+ 	struct iscsi_conn	*conn;		/* used connection    */
++	struct iscsi_mgmt_task	*mtask;		/* tmf mtask in progr */
+ 
+ 	/* state set/tested under session->lock */
+ 	int			state;
+@@ -128,19 +119,6 @@ struct iscsi_cmd_task {
+ 	void			*dd_data;	/* driver/transport data */
+ };
+ 
+-static inline void* iscsi_next_hdr(struct iscsi_cmd_task *ctask)
+-{
+-	return (void*)ctask->hdr + ctask->hdr_len;
+-}
+-
+-/* Connection's states */
+-enum {
+-	ISCSI_CONN_INITIAL_STAGE,
+-	ISCSI_CONN_STARTED,
+-	ISCSI_CONN_STOPPED,
+-	ISCSI_CONN_CLEANUP_WAIT,
+-};
+-
+ struct iscsi_conn {
+ 	struct iscsi_cls_conn	*cls_conn;	/* ptr to class connection */
+ 	void			*dd_data;	/* iscsi_transport data */
+@@ -154,12 +132,6 @@ struct iscsi_conn {
+ 	 * conn_stop() flag: stop to recover, stop to terminate
+ 	 */
+         int			stop_stage;
+-	struct timer_list	transport_timer;
+-	unsigned long		last_recv;
+-	unsigned long		last_ping;
+-	int			ping_timeout;
+-	int			recv_timeout;
+-	struct iscsi_mgmt_task	*ping_mtask;
+ 
+ 	/* iSCSI connection-wide sequencing */
+ 	uint32_t		exp_statsn;
+@@ -180,24 +152,30 @@ struct iscsi_conn {
+ 	struct iscsi_cmd_task	*ctask;		/* xmit ctask in progress */
+ 
+ 	/* xmit */
+-	struct list_head	mgmtqueue;	/* mgmt (control) xmit queue */
++	struct kfifo		*immqueue;	/* immediate xmit queue */
++	struct kfifo		*mgmtqueue;	/* mgmt (control) xmit queue */
+ 	struct list_head	mgmt_run_list;	/* list of control tasks */
+ 	struct list_head	xmitqueue;	/* data-path cmd queue */
+ 	struct list_head	run_list;	/* list of cmds in progress */
+-	struct list_head	requeue;	/* tasks needing another run */
+ 	struct work_struct	xmitwork;	/* per-conn. xmit workqueue */
++	/*
++	 * serializes connection xmit, access to kfifos:
++	 * xmitqueue, immqueue, mgmtqueue
++	 */
++	struct mutex		xmitmutex;
++
+ 	unsigned long		suspend_tx;	/* suspend Tx */
+ 	unsigned long		suspend_rx;	/* suspend Rx */
+ 
+ 	/* abort */
+ 	wait_queue_head_t	ehwait;		/* used in eh_abort() */
+ 	struct iscsi_tm		tmhdr;
+-	struct timer_list	tmf_timer;
+-	int			tmf_state;	/* see TMF_INITIAL, etc.*/
++	struct timer_list	tmabort_timer;
++	int			tmabort_state;	/* see TMABORT_INITIAL, etc.*/
+ 
+ 	/* negotiated params */
+-	unsigned		max_recv_dlength; /* initiator_max_recv_dsl*/
+-	unsigned		max_xmit_dlength; /* target_max_recv_dsl */
++	int			max_recv_dlength; /* initiator_max_recv_dsl*/
++	int			max_xmit_dlength; /* target_max_recv_dsl */
+ 	int			hdrdgst_en;
+ 	int			datadgst_en;
+ 	int			ifmarker_en;
+@@ -205,12 +183,6 @@ struct iscsi_conn {
+ 	/* values userspace uses to id a conn */
+ 	int			persistent_port;
+ 	char			*persistent_address;
+-	/* remote portal currently connected to */
+-	int			portal_port;
+-	char			portal_address[ISCSI_ADDRESS_BUF_LEN];
+-	/* local address */
+-	int			local_port;
+-	char			local_address[ISCSI_ADDRESS_BUF_LEN];
+ 
+ 	/* MIB-statistics */
+ 	uint64_t		txdata_octets;
+@@ -225,66 +197,34 @@ struct iscsi_conn {
+ 
+ 	/* custom statistics */
+ 	uint32_t		eh_abort_cnt;
+-	uint32_t		fmr_unalign_cnt;
+ };
+ 
+-struct iscsi_pool {
++struct iscsi_queue {
+ 	struct kfifo		*queue;		/* FIFO Queue */
+ 	void			**pool;		/* Pool of elements */
+ 	int			max;		/* Max number of elements */
+ };
+ 
+-/* Session's states */
+-enum {
+-	ISCSI_STATE_FREE = 1,
+-	ISCSI_STATE_LOGGED_IN,
+-	ISCSI_STATE_FAILED,
+-	ISCSI_STATE_TERMINATE,
+-	ISCSI_STATE_IN_RECOVERY,
+-	ISCSI_STATE_RECOVERY_FAILED,
+-	ISCSI_STATE_LOGGING_OUT,
+-};
+-
+ struct iscsi_session {
+-	/*
+-	 * Syncs up the scsi eh thread with the iscsi eh thread when sending
+-	 * task management functions. This must be taken before the session
+-	 * and recv lock.
+-	 */
+-	struct mutex		eh_mutex;
+-
+ 	/* iSCSI session-wide sequencing */
+ 	uint32_t		cmdsn;
+ 	uint32_t		exp_cmdsn;
+ 	uint32_t		max_cmdsn;
+ 
+-	/* This tracks the reqs queued into the initiator */
+-	uint32_t		queued_cmdsn;
+-
+ 	/* configuration */
+-	int			abort_timeout;
+-	int			lu_reset_timeout;
+ 	int			initial_r2t_en;
+-	unsigned		max_r2t;
++	int			max_r2t;
+ 	int			imm_data_en;
+-	unsigned		first_burst;
+-	unsigned		max_burst;
++	int			first_burst;
++	int			max_burst;
+ 	int			time2wait;
+ 	int			time2retain;
+ 	int			pdu_inorder_en;
+ 	int			dataseq_inorder_en;
+ 	int			erl;
+-	int			fast_abort;
+ 	int			tpgt;
+-	char			*username;
+-	char			*username_in;
+-	char			*password;
+-	char			*password_in;
+ 	char			*targetname;
+-	char			*initiatorname;
+-	/* hw address or netdev iscsi connection is bound to */
+-	char			*hwaddress;
+-	char			*netdev;
++
+ 	/* control data */
+ 	struct iscsi_transport	*tt;
+ 	struct Scsi_Host	*host;
+@@ -300,10 +240,10 @@ struct iscsi_session {
+ 
+ 	int			cmds_max;	/* size of cmds array */
+ 	struct iscsi_cmd_task	**cmds;		/* Original Cmds arr */
+-	struct iscsi_pool	cmdpool;	/* PDU's pool */
++	struct iscsi_queue	cmdpool;	/* PDU's pool */
+ 	int			mgmtpool_max;	/* size of mgmt array */
+ 	struct iscsi_mgmt_task	**mgmt_cmds;	/* Original mgmt arr */
+-	struct iscsi_pool	mgmtpool;	/* Mgmt PDU's pool */
++	struct iscsi_queue	mgmtpool;	/* Mgmt PDU's pool */
+ };
+ 
+ /*
+@@ -312,26 +252,15 @@ struct iscsi_session {
+ extern int iscsi_change_queue_depth(struct scsi_device *sdev, int depth);
+ extern int iscsi_eh_abort(struct scsi_cmnd *sc);
+ extern int iscsi_eh_host_reset(struct scsi_cmnd *sc);
+-extern int iscsi_eh_device_reset(struct scsi_cmnd *sc);
+ extern int iscsi_queuecommand(struct scsi_cmnd *sc,
+ 			      void (*done)(struct scsi_cmnd *));
+ 
+-
+-/*
+- * iSCSI host helpers.
+- */
+-extern int iscsi_host_set_param(struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf,
+-				int buflen);
+-extern int iscsi_host_get_param(struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf);
+-
+ /*
+  * session management
+  */
+ extern struct iscsi_cls_session *
+ iscsi_session_setup(struct iscsi_transport *, struct scsi_transport_template *,
+-		    uint16_t, uint16_t, int, int, uint32_t, uint32_t *);
++		    int, int, uint32_t, uint32_t *);
+ extern void iscsi_session_teardown(struct iscsi_cls_session *);
+ extern struct iscsi_session *class_to_transport_session(struct iscsi_cls_session *);
+ extern void iscsi_session_recovery_timedout(struct iscsi_cls_session *);
+@@ -343,10 +272,6 @@ extern int iscsi_session_get_param(struct iscsi_cls_session *cls_session,
+ #define session_to_cls(_sess) \
+ 	hostdata_session(_sess->host->hostdata)
+ 
+-#define iscsi_session_printk(prefix, _sess, fmt, a...)	\
+-	iscsi_cls_session_printk(prefix,		\
+-		(struct iscsi_cls_session *)session_to_cls(_sess), fmt, ##a)
+-
+ /*
+  * connection management
+  */
+@@ -361,47 +286,26 @@ extern void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err);
+ extern int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ 				enum iscsi_param param, char *buf);
+ 
+-#define iscsi_conn_printk(prefix, _c, fmt, a...) \
+-	iscsi_cls_conn_printk(prefix, _c->cls_conn, fmt, ##a)
+-
+ /*
+  * pdu and task processing
+  */
+-extern void iscsi_update_cmdsn(struct iscsi_session *, struct iscsi_nopin *);
++extern int iscsi_check_assign_cmdsn(struct iscsi_session *,
++				    struct iscsi_nopin *);
+ extern void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *,
+ 					struct iscsi_data *hdr);
+ extern int iscsi_conn_send_pdu(struct iscsi_cls_conn *, struct iscsi_hdr *,
+ 				char *, uint32_t);
+ extern int iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *,
+ 			      char *, int);
++extern int __iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *,
++				char *, int);
+ extern int iscsi_verify_itt(struct iscsi_conn *, struct iscsi_hdr *,
+ 			    uint32_t *);
+-extern void iscsi_requeue_ctask(struct iscsi_cmd_task *ctask);
+-extern void iscsi_free_mgmt_task(struct iscsi_conn *conn,
+-				 struct iscsi_mgmt_task *mtask);
+ 
+ /*
+  * generic helpers
+  */
+-extern void iscsi_pool_free(struct iscsi_pool *);
+-extern int iscsi_pool_init(struct iscsi_pool *, int, void ***, int);
+-
+-/*
+- * inline functions to deal with padding.
+- */
+-static inline unsigned int
+-iscsi_padded(unsigned int len)
+-{
+-	return (len + ISCSI_PAD_LEN - 1) & ~(ISCSI_PAD_LEN - 1);
+-}
+-
+-static inline unsigned int
+-iscsi_padding(unsigned int len)
+-{
+-	len &= (ISCSI_PAD_LEN - 1);
+-	if (len)
+-		len = ISCSI_PAD_LEN - len;
+-	return len;
+-}
++extern void iscsi_pool_free(struct iscsi_queue *, void **);
++extern int iscsi_pool_init(struct iscsi_queue *, int, void ***, int);
+ 
+ #endif
+diff --git a/include/scsi/scsi_transport_iscsi.h b/include/scsi/scsi_transport_iscsi.h
+index aab1eae..d5c218d 100644
+--- a/include/scsi/scsi_transport_iscsi.h
++++ b/include/scsi/scsi_transport_iscsi.h
+@@ -24,8 +24,6 @@
+ #define SCSI_TRANSPORT_ISCSI_H
+ 
+ #include <linux/device.h>
+-#include <linux/list.h>
+-#include <linux/mutex.h>
+ #include <scsi/iscsi_if.h>
+ 
+ struct scsi_transport_template;
+@@ -81,8 +79,7 @@ struct iscsi_transport {
+ 	char *name;
+ 	unsigned int caps;
+ 	/* LLD sets this to indicate what values it can export to sysfs */
+-	uint64_t param_mask;
+-	uint64_t host_param_mask;
++	unsigned int param_mask;
+ 	struct scsi_host_template *host_template;
+ 	/* LLD connection data size */
+ 	int conndata_size;
+@@ -92,8 +89,7 @@ struct iscsi_transport {
+ 	unsigned int max_conn;
+ 	unsigned int max_cmd_len;
+ 	struct iscsi_cls_session *(*create_session) (struct iscsi_transport *it,
+-		struct scsi_transport_template *t, uint16_t, uint16_t,
+-		uint32_t sn, uint32_t *hn);
++		struct scsi_transport_template *t, uint32_t sn, uint32_t *hn);
+ 	void (*destroy_session) (struct iscsi_cls_session *session);
+ 	struct iscsi_cls_conn *(*create_conn) (struct iscsi_cls_session *sess,
+ 				uint32_t cid);
+@@ -109,18 +105,14 @@ struct iscsi_transport {
+ 			       enum iscsi_param param, char *buf);
+ 	int (*get_session_param) (struct iscsi_cls_session *session,
+ 				  enum iscsi_param param, char *buf);
+-	int (*get_host_param) (struct Scsi_Host *shost,
+-				enum iscsi_host_param param, char *buf);
+-	int (*set_host_param) (struct Scsi_Host *shost,
+-			       enum iscsi_host_param param, char *buf,
+-			       int buflen);
+ 	int (*send_pdu) (struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 			 char *data, uint32_t data_size);
+ 	void (*get_stats) (struct iscsi_cls_conn *conn,
+ 			   struct iscsi_stats *stats);
+-	int (*init_cmd_task) (struct iscsi_cmd_task *ctask);
++	void (*init_cmd_task) (struct iscsi_cmd_task *ctask);
+ 	void (*init_mgmt_task) (struct iscsi_conn *conn,
+-				struct iscsi_mgmt_task *mtask);
++				struct iscsi_mgmt_task *mtask,
++				char *data, uint32_t data_size);
+ 	int (*xmit_cmd_task) (struct iscsi_conn *conn,
+ 			      struct iscsi_cmd_task *ctask);
+ 	void (*cleanup_cmd_task) (struct iscsi_conn *conn,
+@@ -132,7 +124,7 @@ struct iscsi_transport {
+ 			   uint64_t *ep_handle);
+ 	int (*ep_poll) (uint64_t ep_handle, int timeout_ms);
+ 	void (*ep_disconnect) (uint64_t ep_handle);
+-	int (*tgt_dscvr) (struct Scsi_Host *shost, enum iscsi_tgt_dscvr type,
++	int (*tgt_dscvr) (enum iscsi_tgt_dscvr type, uint32_t host_no,
+ 			  uint32_t enable, struct sockaddr *dst_addr);
+ };
+ 
+@@ -149,6 +141,13 @@ extern void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error);
+ extern int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+ 			  char *data, uint32_t data_size);
+ 
++
++/* Connection's states */
++#define ISCSI_CONN_INITIAL_STAGE	0
++#define ISCSI_CONN_STARTED		1
++#define ISCSI_CONN_STOPPED		2
++#define ISCSI_CONN_CLEANUP_WAIT		3
++
+ struct iscsi_cls_conn {
+ 	struct list_head conn_list;	/* item in connlist */
+ 	void *dd_data;			/* LLD private data */
+@@ -162,25 +161,18 @@ struct iscsi_cls_conn {
+ #define iscsi_dev_to_conn(_dev) \
+ 	container_of(_dev, struct iscsi_cls_conn, dev)
+ 
+-#define iscsi_conn_to_session(_conn) \
+-	iscsi_dev_to_session(_conn->dev.parent)
+-
+-/* iscsi class session state */
+-enum {
+-	ISCSI_SESSION_LOGGED_IN,
+-	ISCSI_SESSION_FAILED,
+-	ISCSI_SESSION_FREE,
+-};
++/* Session's states */
++#define ISCSI_STATE_FREE		1
++#define ISCSI_STATE_LOGGED_IN		2
++#define ISCSI_STATE_FAILED		3
++#define ISCSI_STATE_TERMINATE		4
++#define ISCSI_STATE_IN_RECOVERY		5
++#define ISCSI_STATE_RECOVERY_FAILED	6
+ 
+ struct iscsi_cls_session {
+ 	struct list_head sess_list;		/* item in session_list */
+ 	struct list_head host_list;
+ 	struct iscsi_transport *transport;
+-	spinlock_t lock;
+-	struct work_struct block_work;
+-	struct work_struct unblock_work;
+-	struct work_struct scan_work;
+-	struct work_struct unbind_work;
+ 
+ 	/* recovery fields */
+ 	int recovery_tmo;
+@@ -188,7 +180,6 @@ struct iscsi_cls_session {
+ 
+ 	int target_id;
+ 
+-	int state;
+ 	int sid;				/* session id */
+ 	void *dd_data;				/* LLD private data */
+ 	struct device dev;	/* sysfs transport/container device */
+@@ -205,28 +196,18 @@ struct iscsi_cls_session {
+ 
+ struct iscsi_host {
+ 	struct list_head sessions;
+-	atomic_t nr_scans;
+ 	struct mutex mutex;
+-	struct workqueue_struct *scan_workq;
+-	char scan_workq_name[KOBJ_NAME_LEN];
+ };
+ 
+ /*
+  * session and connection functions that can be used by HW iSCSI LLDs
+  */
+-#define iscsi_cls_session_printk(prefix, _cls_session, fmt, a...) \
+-	dev_printk(prefix, &(_cls_session)->dev, fmt, ##a)
+-
+-#define iscsi_cls_conn_printk(prefix, _cls_conn, fmt, a...) \
+-	dev_printk(prefix, &(_cls_conn)->dev, fmt, ##a)
+-
+-extern int iscsi_session_chkready(struct iscsi_cls_session *session);
+ extern struct iscsi_cls_session *iscsi_alloc_session(struct Scsi_Host *shost,
+ 					struct iscsi_transport *transport);
+ extern int iscsi_add_session(struct iscsi_cls_session *session,
+ 			     unsigned int target_id);
+-extern int iscsi_session_event(struct iscsi_cls_session *session,
+-			       enum iscsi_uevent_e event);
++extern int iscsi_if_create_session_done(struct iscsi_cls_conn *conn);
++extern int iscsi_if_destroy_session_done(struct iscsi_cls_conn *conn);
+ extern struct iscsi_cls_session *iscsi_create_session(struct Scsi_Host *shost,
+ 						struct iscsi_transport *t,
+ 						unsigned int target_id);
+@@ -238,6 +219,6 @@ extern struct iscsi_cls_conn *iscsi_create_conn(struct iscsi_cls_session *sess,
+ extern int iscsi_destroy_conn(struct iscsi_cls_conn *conn);
+ extern void iscsi_unblock_session(struct iscsi_cls_session *session);
+ extern void iscsi_block_session(struct iscsi_cls_session *session);
+-extern int iscsi_scan_finished(struct Scsi_Host *shost, unsigned long time);
++
+ 
+ #endif
+-- 
+1.5.3.8
+
diff --git a/kernel_patches/backport/2.6.9_U6/iscsi_02_add_to_2_6_9.patch b/kernel_patches/backport/2.6.9_U6/iscsi_02_add_to_2_6_9.patch
new file mode 100644
index 0000000..1f05d95
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U6/iscsi_02_add_to_2_6_9.patch
@@ -0,0 +1,180 @@
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 4376840..11dfaf9 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -2145,7 +2145,6 @@ static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
+ static struct scsi_host_template iscsi_sht = {
+ 	.name			= "iSCSI Initiator over TCP/IP",
+ 	.queuecommand           = iscsi_queuecommand,
+-	.change_queue_depth	= iscsi_change_queue_depth,
+ 	.can_queue		= ISCSI_XMIT_CMDS_MAX - 1,
+ 	.sg_tablesize		= ISCSI_SG_TABLESIZE,
+	.max_sectors		= 0xFFFF,
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index d37048c..60f5846 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -1366,7 +1366,6 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 	shost->max_lun = iscsit->max_lun;
+ 	shost->max_cmd_len = iscsit->max_cmd_len;
+ 	shost->transportt = scsit;
+-	shost->transportt->create_work_queue = 1;
+ 	*hostno = shost->host_no;
+ 
+ 	session = iscsi_hostdata(shost->hostdata);
+diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
+index 8133c22..f1c68f7 100644
+--- a/drivers/scsi/scsi_transport_iscsi.c
++++ b/drivers/scsi/scsi_transport_iscsi.c
+@@ -65,6 +65,8 @@ static DEFINE_SPINLOCK(iscsi_transport_lock);
+ #define cdev_to_iscsi_internal(_cdev) \
+ 	container_of(_cdev, struct iscsi_internal, cdev)
+ 
++extern int attribute_container_init(void);
++
+ static void iscsi_transport_release(struct class_device *cdev)
+ {
+ 	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+@@ -80,6 +82,17 @@ static struct class iscsi_transport_class = {
+ 	.release = iscsi_transport_release,
+ };
+ 
++static void iscsi_host_class_release(struct class_device *class_dev)
++{
++	struct Scsi_Host *shost = transport_class_to_shost(class_dev);
++	put_device(&shost->shost_gendev);
++}
++
++struct class iscsi_host_class = {
++	.name = "iscsi_host",
++	.release = iscsi_host_class_release,
++};
++
+ static ssize_t
+ show_transport_handle(struct class_device *cdev, char *buf)
+ {
+@@ -115,10 +128,8 @@ static struct attribute_group iscsi_transport_group = {
+ 	.attrs = iscsi_transport_attrs,
+ };
+ 
+-static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+-			    struct class_device *cdev)
++static int iscsi_setup_host(struct Scsi_Host *shost)
+ {
+-	struct Scsi_Host *shost = dev_to_shost(dev);
+ 	struct iscsi_host *ihost = shost->shost_data;
+ 
+ 	memset(ihost, 0, sizeof(*ihost));
+@@ -127,12 +138,6 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+ 	return 0;
+ }
+ 
+-static DECLARE_TRANSPORT_CLASS(iscsi_host_class,
+-			       "iscsi_host",
+-			       iscsi_setup_host,
+-			       NULL,
+-			       NULL);
+-
+ static DECLARE_TRANSPORT_CLASS(iscsi_session_class,
+ 			       "iscsi_session",
+ 			       NULL,
+@@ -216,24 +221,6 @@ static int iscsi_is_session_dev(const struct device *dev)
+ 	return dev->release == iscsi_session_release;
+ }
+ 
+-static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+-			   uint id, uint lun)
+-{
+-	struct iscsi_host *ihost = shost->shost_data;
+-	struct iscsi_cls_session *session;
+-
+-	mutex_lock(&ihost->mutex);
+-	list_for_each_entry(session, &ihost->sessions, host_list) {
+-		if ((channel == SCAN_WILD_CARD || channel == 0) &&
+-		    (id == SCAN_WILD_CARD || id == session->target_id))
+-			scsi_scan_target(&session->dev, 0,
+-					 session->target_id, lun, 1);
+-	}
+-	mutex_unlock(&ihost->mutex);
+-
+-	return 0;
+-}
+-
+ static void session_recovery_timedout(struct work_struct *work)
+ {
+ 	struct iscsi_cls_session *session =
+@@ -362,8 +349,6 @@ void iscsi_remove_session(struct iscsi_cls_session *session)
+ 	list_del(&session->host_list);
+ 	mutex_unlock(&ihost->mutex);
+ 
+-	scsi_remove_target(&session->dev);
+-
+ 	transport_unregister_device(&session->dev);
+ 	device_del(&session->dev);
+ }
+@@ -1269,24 +1254,6 @@ static int iscsi_conn_match(struct attribute_container *cont,
+ 	return &priv->conn_cont.ac == cont;
+ }
+ 
+-static int iscsi_host_match(struct attribute_container *cont,
+-			    struct device *dev)
+-{
+-	struct Scsi_Host *shost;
+-	struct iscsi_internal *priv;
+-
+-	if (!scsi_is_host_device(dev))
+-		return 0;
+-
+-	shost = dev_to_shost(dev);
+-	if (!shost->transportt  ||
+-	    shost->transportt->host_attrs.ac.class != &iscsi_host_class.class)
+-		return 0;
+-
+-        priv = to_iscsi_internal(shost->transportt);
+-        return &priv->t.host_attrs.ac == cont;
+-}
+-
+ struct scsi_transport_template *
+ iscsi_register_transport(struct iscsi_transport *tt)
+ {
+@@ -1306,7 +1273,6 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 	INIT_LIST_HEAD(&priv->list);
+ 	priv->daemon_pid = -1;
+ 	priv->iscsi_transport = tt;
+-	priv->t.user_scan = iscsi_user_scan;
+ 
+ 	priv->cdev.class = &iscsi_transport_class;
+ 	snprintf(priv->cdev.class_id, BUS_ID_SIZE, "%s", tt->name);
+@@ -1319,12 +1285,10 @@ iscsi_register_transport(struct iscsi_transport *tt)
+ 		goto unregister_cdev;
+ 
+ 	/* host parameters */
+-	priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
+-	priv->t.host_attrs.ac.class = &iscsi_host_class.class;
+-	priv->t.host_attrs.ac.match = iscsi_host_match;
++	priv->t.host_attrs = &priv->host_attrs[0];
++	priv->t.host_class = &iscsi_host_class;
++	priv->t.host_setup = iscsi_setup_host;
+ 	priv->t.host_size = sizeof(struct iscsi_host);
+-	priv->host_attrs[0] = NULL;
+-	transport_container_register(&priv->t.host_attrs);
+ 
+ 	/* connection parameters */
+ 	priv->conn_cont.ac.attrs = &priv->conn_attrs[0];
+@@ -1402,7 +1366,6 @@ int iscsi_unregister_transport(struct iscsi_transport *tt)
+ 
+ 	transport_container_unregister(&priv->conn_cont);
+ 	transport_container_unregister(&priv->session_cont);
+-	transport_container_unregister(&priv->t.host_attrs);
+ 
+ 	sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group);
+ 	class_device_unregister(&priv->cdev);
+@@ -1420,6 +1420,7 @@ static __init int iscsi_transport_init(void)
+ 		ISCSI_TRANSPORT_VERSION);
+ 
+	atomic_set(&iscsi_session_nr, 0);
++	attribute_container_init();
+
+	err = class_register(&iscsi_transport_class);
+ 	if (err)
+ 		return err;
diff --git a/kernel_patches/backport/2.6.9_U6/iscsi_03_add_session_wq.patch b/kernel_patches/backport/2.6.9_U6/iscsi_03_add_session_wq.patch
new file mode 100644
index 0000000..55564c5
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U6/iscsi_03_add_session_wq.patch
@@ -0,0 +1,76 @@
+diff --git a/drivers/infiniband/ulp/iser/iser_initiator.c b/drivers/infiniband/ulp/iser/iser_initiator.c
+index a6f2303..5d62cc0 100644
+--- a/drivers/infiniband/ulp/iser/iser_initiator.c
++++ b/drivers/infiniband/ulp/iser/iser_initiator.c
+@@ -610,7 +610,7 @@ void iser_snd_completion(struct iser_desc *tx_desc)
+ 
+ 	if (resume_tx) {
+ 		iser_dbg("%ld resuming tx\n",jiffies);
+-		scsi_queue_work(conn->session->host, &conn->xmitwork);
++		queue_work(conn->session->wq, &conn->xmitwork);
+ 	}
+ 
+ 	if (tx_desc->type == ISCSI_TX_CONTROL) {
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index e8020a5..43e9128 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -828,7 +828,7 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+ 		session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+ 	spin_unlock(&session->lock);
+ 
+-	scsi_queue_work(host, &conn->xmitwork);
++	queue_work(session->wq, &conn->xmitwork);
+ 	return 0;
+ 
+ reject:
+@@ -928,7 +928,7 @@ iscsi_conn_send_generic(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ 	else
+ 	        __kfifo_put(conn->mgmtqueue, (void*)&mtask, sizeof(void*));
+ 
+-	scsi_queue_work(session->host, &conn->xmitwork);
++	queue_work(session->wq, &conn->xmitwork);
+ 	return 0;
+ }
+ 
+@@ -1415,6 +1415,9 @@ iscsi_session_setup(struct iscsi_transport *iscsit,
+ 		INIT_LIST_HEAD(&mtask->running);
+ 	}
+ 
++	session->wq = create_singlethread_workqueue("");
++	BUG_ON(!session->wq);
++
+ 	if (scsi_add_host(shost, NULL))
+ 		goto add_host_fail;
+ 
+@@ -1462,6 +1465,8 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session)
+ 
+ 	kfree(session->targetname);
+ 
++	destroy_workqueue(session->wq);
++
+ 	iscsi_destroy_session(cls_session);
+ 	scsi_host_put(shost);
+ 	module_put(owner);
+@@ -1595,7 +1600,7 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
+ 	}
+ 
+ 	/* flush queued up work because we free the connection below */
+-	scsi_flush_work(session->host);
++	flush_workqueue(session->wq);
+ 
+ 	spin_lock_bh(&session->lock);
+ 	kfree(conn->data);
+diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
+index ea0816d..e8a95f5 100644
+--- a/include/scsi/libiscsi.h
++++ b/include/scsi/libiscsi.h
+@@ -244,6 +244,8 @@ struct iscsi_session {
+ 	int			mgmtpool_max;	/* size of mgmt array */
+ 	struct iscsi_mgmt_task	**mgmt_cmds;	/* Original mgmt arr */
+ 	struct iscsi_queue	mgmtpool;	/* Mgmt PDU's pool */
++
++	struct workqueue_struct *wq;
+ };
+ 
+ /*
diff --git a/kernel_patches/backport/2.6.9_U6/iscsi_04_inet_sock_to_opt.patch b/kernel_patches/backport/2.6.9_U6/iscsi_04_inet_sock_to_opt.patch
new file mode 100644
index 0000000..1fb2376
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U6/iscsi_04_inet_sock_to_opt.patch
@@ -0,0 +1,13 @@
+diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
+index 905efc4..f73a743 100644
+--- a/drivers/scsi/iscsi_tcp.c
++++ b/drivers/scsi/iscsi_tcp.c
+@@ -2027,7 +2027,7 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+ {
+ 	struct iscsi_conn *conn = cls_conn->dd_data;
+ 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+-	struct inet_sock *inet;
++	struct inet_opt *inet;
+ 	struct ipv6_pinfo *np;
+ 	struct sock *sk;
+ 	int len;
diff --git a/kernel_patches/backport/2.6.9_U6/iscsi_05_release_host_lock_before_eh.patch b/kernel_patches/backport/2.6.9_U6/iscsi_05_release_host_lock_before_eh.patch
new file mode 100644
index 0000000..c994506
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U6/iscsi_05_release_host_lock_before_eh.patch
@@ -0,0 +1,60 @@
+diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
+index 7db081b..211944e 100644
+--- a/drivers/scsi/libiscsi.c
++++ b/drivers/scsi/libiscsi.c
+@@ -968,12 +968,14 @@ int iscsi_eh_host_reset(struct scsi_cmnd *sc)
+ 	struct iscsi_conn *conn = session->leadconn;
+ 	int fail_session = 0;
+ 
++	spin_unlock_irq(host->host_lock);
+ 	spin_lock_bh(&session->lock);
+ 	if (session->state == ISCSI_STATE_TERMINATE) {
+ failed:
+ 		debug_scsi("failing host reset: session terminated "
+ 			   "[CID %d age %d]\n", conn->id, session->age);
+ 		spin_unlock_bh(&session->lock);
++		spin_lock_irq(host->host_lock);
+ 		return FAILED;
+ 	}
+ 
+@@ -1005,6 +1007,7 @@ failed:
+ 	else
+ 		goto failed;
+ 	spin_unlock_bh(&session->lock);
++	spin_lock_irq(host->host_lock);
+ 
+ 	return SUCCESS;
+ }
+@@ -1162,13 +1165,16 @@ int iscsi_eh_abort(struct scsi_cmnd *sc)
+ 	struct iscsi_conn *conn;
+ 	struct iscsi_session *session;
+ 	int rc;
++	struct Scsi_Host *shost = sc->device->host;
+ 
++	spin_unlock_irq(shost->host_lock);
+ 	/*
+ 	 * if session was ISCSI_STATE_IN_RECOVERY then we may not have
+ 	 * got the command.
+ 	 */
+ 	if (!sc->SCp.ptr) {
+ 		debug_scsi("sc never reached iscsi layer or it completed.\n");
++		spin_lock_irq(shost->host_lock);
+ 		return SUCCESS;
+ 	}
+ 
+@@ -1253,6 +1259,7 @@ success_cleanup:
+ 
+ success_rel_mutex:
+ 	mutex_unlock(&conn->xmitmutex);
++	spin_lock_irq(shost->host_lock);
+ 	return SUCCESS;
+ 
+ failed:
+@@ -1260,6 +1267,7 @@ failed:
+ 	mutex_unlock(&conn->xmitmutex);
+ 
+ 	debug_scsi("abort failed [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
++	spin_lock_irq(shost->host_lock);
+ 	return FAILED;
+ }
+ EXPORT_SYMBOL_GPL(iscsi_eh_abort);
diff --git a/kernel_patches/backport/2.6.9_U6/iscsi_06_scsi_addons.patch b/kernel_patches/backport/2.6.9_U6/iscsi_06_scsi_addons.patch
new file mode 100644
index 0000000..a114696
--- /dev/null
+++ b/kernel_patches/backport/2.6.9_U6/iscsi_06_scsi_addons.patch
@@ -0,0 +1,75 @@
+diff --git a/drivers/scsi/init.c b/drivers/scsi/init.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/init.c
+@@ -0,0 +1 @@
++#include "src/init.c"
+diff --git a/drivers/scsi/attribute_container.c b/drivers/scsi/attribute_container.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/attribute_container.c
+@@ -0,0 +1 @@
++#include "../drivers/base/attribute_container.c"
+diff --git a/drivers/scsi/transport_class.c b/drivers/scsi/transport_class.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/transport_class.c
+@@ -0,0 +1 @@
++#include "../drivers/base/transport_class.c"
+diff --git a/drivers/scsi/klist.c b/drivers/scsi/klist.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/klist.c
+@@ -0,0 +1 @@
++#include "../../lib/klist.c"
+diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi.c
+@@ -0,0 +1 @@
++#include "src/scsi.c"
+diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_lib.c
+@@ -0,0 +1 @@
++#include "src/scsi_lib.c"
+diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_scan.c
+@@ -0,0 +1 @@
++#include "src/scsi_scan.c"
+diff --git a/drivers/scsi/libiscsi_f.c b/drivers/scsi/libiscsi_f.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/libiscsi_f.c
+@@ -0,0 +1 @@
++#include "libiscsi.c"
+diff --git a/drivers/scsi/scsi_transport_iscsi_f.c b/drivers/scsi/scsi_transport_iscsi_f.c
+new file mode 100644
+index 0000000..58cf933
+--- /dev/null
++++ b/drivers/scsi/scsi_transport_iscsi_f.c
+@@ -0,0 +1 @@
++#include "scsi_transport_iscsi.c"
+diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
+index e212608..3bf2015 100644
+--- a/drivers/scsi/Makefile
++++ b/drivers/scsi/Makefile
+@@ -3,2 +3,7 @@
+ obj-$(CONFIG_SCSI_ISCSI_ATTRS) += scsi_transport_iscsi.o
+ obj-$(CONFIG_ISCSI_TCP)        += libiscsi.o   iscsi_tcp.o
++
++CFLAGS_attribute_container.o =   $(BACKPORT_INCLUDES)/src/
++
++scsi_transport_iscsi-y := scsi_transport_iscsi_f.o scsi.o scsi_lib.o init.o klist.o attribute_container.o transport_class.o
++libiscsi-y             := libiscsi_f.o scsi_scan.o
-- 
1.5.3.8






More information about the ewg mailing list