Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > fc11cd6e1c513a17304da94a5390f3cd > files > 3669

kernel-2.6.18-194.11.1.el5.src.rpm

From: mchristi@redhat.com <mchristi@redhat.com>
Date: Tue, 28 Apr 2009 09:24:50 -0500
Subject: [scsi] update iscsi layer and drivers for RHEL-5.4
Message-id: 1240928691-4305-1-git-send-email-mchristi@redhat.com
O-Subject: [PATCH 1/2] RHEL 5.4: update iscsi layer and drivers v2
Bugzilla: 436791 484455

From: Mike Christie <mchristi@redhat.com>

This is for BZs 436791 and 484455.

This patch syncs the iscsi layer and ib_iser and iscsi_tcp with
what is in linus's tree plus some bug fixes that were sent here:
http://marc.info/?l=linux-scsi&m=124034605613006&w=2
http://marc.info/?l=linux-scsi&m=124034605813013&w=2
http://marc.info/?l=linux-scsi&m=124034605713009&w=2
http://marc.info/?l=linux-scsi&m=124034605913021&w=2
http://marc.info/?l=linux-scsi&m=124034605913024&w=2
This patch also updates qla4xxx to use the newer API.

There is one large difference between this patch and upstream.
It is a the KABI workaround code. The libiscsi and scsi_transport_iscsi
exports were KABId, so this patch keeps the old scsi_transport_iscsi
module but renames it scsi_transport_iscsi1. The new modules are then named
scsi_transport_iscsi2 and libiscsi2 and the new functions have a
iscsi2 instead of iscsi prefix. And then there is a scsi_transport_iscsi
module that brings in the old or new module and only allows one user
because that is all the iscsi class can/could support.

There was only one known user of the KABId code and that was broadcom.
They were using some functions in scsi_transport_iscsi, and their
driver is working with the KABI workaround setup. They are also supposed
to be adding their driver to 5.4, so it probably does not matter.

I ran the check-kabi tool and just got the expected warnings about
the functions moving from scsi_transport_iscsi to scsi_transport_iscsi1.
There were no errors.

I have tested iscsi_tcp, ib_iser and qla4xxx, by running the iscsi test
scripts which run disktest and bonnie++, while restarting the target and
shutting down interfaces.

Voltaire will also be helping test ib_iser.

v2
- Fix compilation errors on s390 and add more kabi compat support.

brew build info here:
https://brewweb.devel.redhat.com/taskinfo?taskID=1779592

v1
- Initial patch.

diff --git a/drivers/infiniband/ulp/iser/Kconfig b/drivers/infiniband/ulp/iser/Kconfig
index aecbb90..88493cb 100644
--- a/drivers/infiniband/ulp/iser/Kconfig
+++ b/drivers/infiniband/ulp/iser/Kconfig
@@ -1,12 +1,12 @@
 config INFINIBAND_ISER
 	tristate "iSCSI Extensions for RDMA (iSER)"
-	depends on INFINIBAND && SCSI && INET
-	select SCSI_ISCSI_ATTRS
+	depends on SCSI && INET && INFINIBAND_ADDR_TRANS
+	select SCSI_ISCSI2_ATTRS
 	---help---
 	  Support for the iSCSI Extensions for RDMA (iSER) Protocol
           over InfiniBand. This allows you to access storage devices
           that speak iSCSI over iSER over InfiniBand.
 
 	  The iSER protocol is defined by IETF.
-	  See <http://www.ietf.org/internet-drafts/draft-ietf-ips-iser-05.txt>
-	  and <http://www.infinibandta.org/members/spec/iser_annex_060418.pdf>
+	  See <http://www.ietf.org/rfc/rfc5046.txt>
+	  and <http://www.infinibandta.org/members/spec/Annex_iSER.PDF>
diff --git a/drivers/infiniband/ulp/iser/iscsi_iser.c b/drivers/infiniband/ulp/iser/iscsi_iser.c
index 28d86ae..2bac499 100644
--- a/drivers/infiniband/ulp/iser/iscsi_iser.c
+++ b/drivers/infiniband/ulp/iser/iscsi_iser.c
@@ -42,9 +42,6 @@
  *	Zhenyu Wang
  * Modified by:
  *      Erez Zilber
- *
- *
- * $Id: iscsi_iser.c 6965 2006-05-07 11:36:20Z ogerlitz $
  */
 
 #include <linux/types.h>
@@ -70,10 +67,14 @@
 #include <scsi/scsi_tcq.h>
 #include <scsi/scsi_host.h>
 #include <scsi/scsi.h>
-#include <scsi/scsi_transport_iscsi.h>
+#include <scsi/scsi_transport_iscsi2.h>
 
 #include "iscsi_iser.h"
 
+static struct scsi_host_template iscsi_iser_sht;
+static struct iscsi_transport iscsi_iser_transport;
+static struct scsi_transport_template *iscsi_iser_scsi_transport;
+
 static unsigned int iscsi_max_lun = 512;
 module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);
 
@@ -94,7 +95,6 @@ iscsi_iser_recv(struct iscsi_conn *conn,
 		struct iscsi_hdr *hdr, char *rx_data, int rx_data_len)
 {
 	int rc = 0;
-	uint32_t ret_itt;
 	int datalen;
 	int ahslen;
 
@@ -110,40 +110,52 @@ iscsi_iser_recv(struct iscsi_conn *conn,
 	/* read AHS */
 	ahslen = hdr->hlength * 4;
 
-	/* verify itt (itt encoding: age+cid+itt) */
-	rc = iscsi_verify_itt(conn, hdr, &ret_itt);
-
-	if (!rc)
-		rc = iscsi_complete_pdu(conn, hdr, rx_data, rx_data_len);
-
+	rc = iscsi2_complete_pdu(conn, hdr, rx_data, rx_data_len);
 	if (rc && rc != ISCSI_ERR_NO_SCSI_CMD)
 		goto error;
 
 	return;
 error:
-	iscsi_conn_failure(conn, rc);
+	iscsi2_conn_failure(conn, rc);
 }
 
+static int iscsi_iser_pdu_alloc(struct iscsi_task *task, uint8_t opcode)
+{
+	struct iscsi_iser_task *iser_task = task->dd_data;
+
+	task->hdr = (struct iscsi_hdr *)&iser_task->desc.iscsi_header;
+	task->hdr_max = sizeof(iser_task->desc.iscsi_header);
+	return 0;
+}
 
 /**
- * iscsi_iser_cmd_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
+ * iscsi_iser_task_init - Initialize task
+ * @task: iscsi task
  *
- **/
-static void
-iscsi_iser_cmd_init(struct iscsi_cmd_task *ctask)
+ * Initialize the task for the scsi command or mgmt command.
+ */
+static int
+iscsi_iser_task_init(struct iscsi_task *task)
 {
-	struct iscsi_iser_conn     *iser_conn  = ctask->conn->dd_data;
-	struct iscsi_iser_cmd_task *iser_ctask = ctask->dd_data;
+	struct iscsi_iser_conn *iser_conn  = task->conn->dd_data;
+	struct iscsi_iser_task *iser_task = task->dd_data;
+
+	/* mgmt task */
+	if (!task->sc) {
+		iser_task->desc.data = task->data;
+		return 0;
+	}
 
-	iser_ctask->command_sent = 0;
-	iser_ctask->iser_conn    = iser_conn;
-	iser_ctask_rdma_init(iser_ctask);
+	iser_task->command_sent = 0;
+	iser_task->iser_conn    = iser_conn;
+	iser_task_rdma_init(iser_task);
+	return 0;
 }
 
 /**
- * iscsi_mtask_xmit - xmit management(immediate) task
+ * iscsi_iser_mtask_xmit - xmit management(immediate) task
  * @conn: iscsi connection
- * @mtask: task management task
+ * @task: task management task
  *
  * Notes:
  *	The function can return -EAGAIN in which case caller must
@@ -152,125 +164,110 @@ iscsi_iser_cmd_init(struct iscsi_cmd_task *ctask)
  *
  **/
 static int
-iscsi_iser_mtask_xmit(struct iscsi_conn *conn,
-		      struct iscsi_mgmt_task *mtask)
+iscsi_iser_mtask_xmit(struct iscsi_conn *conn, struct iscsi_task *task)
 {
 	int error = 0;
 
-	debug_scsi("mtask deq [cid %d itt 0x%x]\n", conn->id, mtask->itt);
+	iser_dbg("task deq [cid %d itt 0x%x]\n", conn->id, task->itt);
 
-	error = iser_send_control(conn, mtask);
+	error = iser_send_control(conn, task);
 
-	/* since iser xmits control with zero copy, mtasks can not be recycled
+	/* since iser xmits control with zero copy, tasks can not be recycled
 	 * right after sending them.
 	 * The recycling scheme is based on whether a response is expected
-	 * - if yes, the mtask is recycled at iscsi_complete_pdu
-	 * - if no,  the mtask is recycled at iser_snd_completion
+	 * - if yes, the task is recycled at iscsi_complete_pdu
+	 * - if no,  the task is recycled at iser_snd_completion
 	 */
 	if (error && error != -ENOBUFS)
-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+		iscsi2_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
 
 	return error;
 }
 
 static int
-iscsi_iser_ctask_xmit_unsol_data(struct iscsi_conn *conn,
-				 struct iscsi_cmd_task *ctask)
+iscsi_iser_task_xmit_unsol_data(struct iscsi_conn *conn,
+				 struct iscsi_task *task)
 {
-	struct iscsi_data  hdr;
+	struct iscsi_r2t_info *r2t = &task->unsol_r2t;
+	struct iscsi_data hdr;
 	int error = 0;
 
 	/* Send data-out PDUs while there's still unsolicited data to send */
-	while (ctask->unsol_count > 0) {
-		iscsi_prep_unsolicit_data_pdu(ctask, &hdr);
-		debug_scsi("Sending data-out: itt 0x%x, data count %d\n",
-			   hdr.itt, ctask->data_count);
+	while (iscsi_task_has_unsol_data(task)) {
+		iscsi2_prep_data_out_pdu(task, r2t, &hdr);
+		iser_dbg("Sending data-out: itt 0x%x, data count %d\n",
+			   hdr.itt, r2t->data_count);
 
 		/* the buffer description has been passed with the command */
 		/* Send the command */
-		error = iser_send_data_out(conn, ctask, &hdr);
+		error = iser_send_data_out(conn, task, &hdr);
 		if (error) {
-			ctask->unsol_datasn--;
-			goto iscsi_iser_ctask_xmit_unsol_data_exit;
+			r2t->datasn--;
+			goto iscsi_iser_task_xmit_unsol_data_exit;
 		}
-		ctask->unsol_count -= ctask->data_count;
-		debug_scsi("Need to send %d more as data-out PDUs\n",
-			   ctask->unsol_count);
+		r2t->sent += r2t->data_count;
+		iser_dbg("Need to send %d more as data-out PDUs\n",
+			   r2t->data_length - r2t->sent);
 	}
 
-iscsi_iser_ctask_xmit_unsol_data_exit:
+iscsi_iser_task_xmit_unsol_data_exit:
 	return error;
 }
 
 static int
-iscsi_iser_ctask_xmit(struct iscsi_conn *conn,
-		      struct iscsi_cmd_task *ctask)
+iscsi_iser_task_xmit(struct iscsi_task *task)
 {
-	struct iscsi_iser_cmd_task *iser_ctask = ctask->dd_data;
+	struct iscsi_conn *conn = task->conn;
+	struct iscsi_iser_task *iser_task = task->dd_data;
 	int error = 0;
 
-	if (ctask->sc->sc_data_direction == DMA_TO_DEVICE) {
-		BUG_ON(ctask->total_length == 0);
+	if (!task->sc)
+		return iscsi_iser_mtask_xmit(conn, task);
+
+	if (task->sc->sc_data_direction == DMA_TO_DEVICE) {
+		BUG_ON(scsi_bufflen(task->sc) == 0);
 
-		debug_scsi("cmd [itt %x total %d imm %d unsol_data %d\n",
-			   ctask->itt, ctask->total_length,
-			   ctask->imm_count, ctask->unsol_count);
+		iser_dbg("cmd [itt %x total %d imm %d unsol_data %d\n",
+			   task->itt, scsi_bufflen(task->sc),
+			   task->imm_count, task->unsol_r2t.data_length);
 	}
 
-	debug_scsi("ctask deq [cid %d itt 0x%x]\n",
-		   conn->id, ctask->itt);
-
-	/*
-	 * serialize with TMF AbortTask
-	 */
-	if (ctask->mtask)
-		return error;
+	iser_dbg("task deq [cid %d itt 0x%x]\n",
+		   conn->id, task->itt);
 
 	/* Send the cmd PDU */
-	if (!iser_ctask->command_sent) {
-		error = iser_send_command(conn, ctask);
+	if (!iser_task->command_sent) {
+		error = iser_send_command(conn, task);
 		if (error)
-			goto iscsi_iser_ctask_xmit_exit;
-		iser_ctask->command_sent = 1;
+			goto iscsi_iser_task_xmit_exit;
+		iser_task->command_sent = 1;
 	}
 
 	/* Send unsolicited data-out PDU(s) if necessary */
-	if (ctask->unsol_count)
-		error = iscsi_iser_ctask_xmit_unsol_data(conn, ctask);
+	if (iscsi_task_has_unsol_data(task))
+		error = iscsi_iser_task_xmit_unsol_data(conn, task);
 
- iscsi_iser_ctask_xmit_exit:
+ iscsi_iser_task_xmit_exit:
 	if (error && error != -ENOBUFS)
-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+		iscsi2_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
 	return error;
 }
 
-static void
-iscsi_iser_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+static void iscsi_iser_cleanup_task(struct iscsi_task *task)
 {
-	struct iscsi_iser_cmd_task *iser_ctask = ctask->dd_data;
-
-	if (iser_ctask->status == ISER_TASK_STATUS_STARTED) {
-		iser_ctask->status = ISER_TASK_STATUS_COMPLETED;
-		iser_ctask_rdma_finalize(iser_ctask);
-	}
-}
+	struct iscsi_iser_task *iser_task = task->dd_data;
 
-static struct iser_conn *
-iscsi_iser_ib_conn_lookup(__u64 ep_handle)
-{
-	struct iser_conn *ib_conn;
-	struct iser_conn *uib_conn = (struct iser_conn *)(unsigned long)ep_handle;
+	/*
+	 * mgmt tasks do not need special cleanup and we do not
+	 * allocate anything in the init task callout
+	 */
+	if (!task->sc || task->state == ISCSI_TASK_PENDING)
+		return;
 
-	mutex_lock(&ig.connlist_mutex);
-	list_for_each_entry(ib_conn, &ig.connlist, conn_list) {
-		if (ib_conn == uib_conn) {
-			mutex_unlock(&ig.connlist_mutex);
-			return ib_conn;
-		}
+	if (iser_task->status == ISER_TASK_STATUS_STARTED) {
+		iser_task->status = ISER_TASK_STATUS_COMPLETED;
+		iser_task_rdma_finalize(iser_task);
 	}
-	mutex_unlock(&ig.connlist_mutex);
-	iser_err("no conn exists for eph %llx\n",(unsigned long long)ep_handle);
-	return NULL;
 }
 
 static struct iscsi_cls_conn *
@@ -280,7 +277,7 @@ iscsi_iser_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
 	struct iscsi_cls_conn *cls_conn;
 	struct iscsi_iser_conn *iser_conn;
 
-	cls_conn = iscsi_conn_setup(cls_session, conn_idx);
+	cls_conn = iscsi2_conn_setup(cls_session, sizeof(*iser_conn), conn_idx);
 	if (!cls_conn)
 		return NULL;
 	conn = cls_conn->dd_data;
@@ -291,21 +288,11 @@ iscsi_iser_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
 	 */
 	conn->max_recv_dlength = 128;
 
-	iser_conn = kzalloc(sizeof(*iser_conn), GFP_KERNEL);
-	if (!iser_conn)
-		goto conn_alloc_fail;
-
-	/* currently this is the only field which need to be initiated */
-	rwlock_init(&iser_conn->lock);
-
+	iser_conn = conn->dd_data;
 	conn->dd_data = iser_conn;
 	iser_conn->iscsi_conn = conn;
 
 	return cls_conn;
-
-conn_alloc_fail:
-	iscsi_conn_teardown(cls_conn);
-	return NULL;
 }
 
 static void
@@ -313,11 +300,18 @@ iscsi_iser_conn_destroy(struct iscsi_cls_conn *cls_conn)
 {
 	struct iscsi_conn *conn = cls_conn->dd_data;
 	struct iscsi_iser_conn *iser_conn = conn->dd_data;
+	struct iser_conn *ib_conn = iser_conn->ib_conn;
 
-	iscsi_conn_teardown(cls_conn);
-	if (iser_conn->ib_conn)
-		iser_conn->ib_conn->iser_conn = NULL;
-	kfree(iser_conn);
+	iscsi2_conn_teardown(cls_conn);
+	/*
+	 * Userspace will normally call the stop callback and
+	 * already have freed the ib_conn, but if it goofed up then
+	 * we free it here.
+	 */
+	if (ib_conn) {
+		ib_conn->iser_conn = NULL;
+		iser_conn_put(ib_conn);
+	}
 }
 
 static int
@@ -328,20 +322,23 @@ iscsi_iser_conn_bind(struct iscsi_cls_session *cls_session,
 	struct iscsi_conn *conn = cls_conn->dd_data;
 	struct iscsi_iser_conn *iser_conn;
 	struct iser_conn *ib_conn;
+	struct iscsi_endpoint *ep;
 	int error;
 
-	error = iscsi_conn_bind(cls_session, cls_conn, is_leading);
+	error = iscsi2_conn_bind(cls_session, cls_conn, is_leading);
 	if (error)
 		return error;
 
 	/* the transport ep handle comes from user space so it must be
 	 * verified against the global ib connections list */
-	ib_conn = iscsi_iser_ib_conn_lookup(transport_eph);
-	if (!ib_conn) {
+	ep = iscsi2_lookup_endpoint(transport_eph);
+	if (!ep) {
 		iser_err("can't bind eph %llx\n",
 			 (unsigned long long)transport_eph);
 		return -EINVAL;
 	}
+	ib_conn = ep->dd_data;
+
 	/* binds the iSER connection retrieved from the previously
 	 * connected ep_handle to the iSCSI layer connection. exchanges
 	 * connection pointers */
@@ -349,10 +346,30 @@ iscsi_iser_conn_bind(struct iscsi_cls_session *cls_session,
 	iser_conn = conn->dd_data;
 	ib_conn->iser_conn = iser_conn;
 	iser_conn->ib_conn  = ib_conn;
+	iser_conn_get(ib_conn);
+	return 0;
+}
 
-	conn->recv_lock = &iser_conn->lock;
+static void
+iscsi_iser_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+{
+	struct iscsi_conn *conn = cls_conn->dd_data;
+	struct iscsi_iser_conn *iser_conn = conn->dd_data;
+	struct iser_conn *ib_conn = iser_conn->ib_conn;
 
-	return 0;
+	/*
+	 * Userspace may have goofed up and not bound the connection or
+	 * might have only partially setup the connection.
+	 */
+	if (ib_conn) {
+		iscsi2_conn_stop(cls_conn, flag);
+		/*
+		 * There is no unbind event so the stop callback
+		 * must release the ref from the bind.
+		 */
+		iser_conn_put(ib_conn);
+	}
+	iser_conn->ib_conn = NULL;
 }
 
 static int
@@ -365,51 +382,68 @@ iscsi_iser_conn_start(struct iscsi_cls_conn *cls_conn)
 	if (err)
 		return err;
 
-	return iscsi_conn_start(cls_conn);
+	return iscsi2_conn_start(cls_conn);
 }
 
-static struct iscsi_transport iscsi_iser_transport;
+static void iscsi_iser_session_destroy(struct iscsi_cls_session *cls_session)
+{
+	struct Scsi_Host *shost = iscsi_session_to_shost(cls_session);
+
+	iscsi2_session_teardown(cls_session);
+	iscsi2_host_remove(shost);
+	iscsi2_host_free(shost);
+}
 
 static struct iscsi_cls_session *
-iscsi_iser_session_create(struct iscsi_transport *iscsit,
-			 struct scsi_transport_template *scsit,
-			  uint32_t initial_cmdsn, uint32_t *hostno)
+iscsi_iser_session_create(struct iscsi_endpoint *ep,
+			  uint16_t cmds_max, uint16_t qdepth,
+			  uint32_t initial_cmdsn)
 {
 	struct iscsi_cls_session *cls_session;
 	struct iscsi_session *session;
-	int i;
-	uint32_t hn;
-	struct iscsi_cmd_task  *ctask;
-	struct iscsi_mgmt_task *mtask;
-	struct iscsi_iser_cmd_task *iser_ctask;
-	struct iser_desc *desc;
-
-	cls_session = iscsi_session_setup2(iscsit, scsit, ISCSI_XMIT_CMDS_MAX,
-					   ISCSI_MAX_CMD_PER_LUN,
-					   sizeof(struct iscsi_iser_cmd_task),
-					   sizeof(struct iser_desc),
-					   initial_cmdsn, &hn);
-	if (!cls_session)
+	struct Scsi_Host *shost;
+	struct iser_conn *ib_conn;
+
+	shost = iscsi2_host_alloc(&iscsi_iser_sht, 0, 1);
+	if (!shost)
 		return NULL;
+	shost->transportt = iscsi_iser_scsi_transport;
+	shost->max_lun = iscsi_max_lun;
+	shost->max_id = 0;
+	shost->max_channel = 0;
+	shost->max_cmd_len = 16;
 
-	*hostno = hn;
-	session = class_to_transport_session(cls_session);
+	/*
+	 * older userspace tools (before 2.0-870) did not pass us
+	 * the leading conn's ep so this will be NULL;
+	 */
+	if (ep)
+		ib_conn = ep->dd_data;
 
-	/* libiscsi setup itts, data and pool so just set desc fields */
-	for (i = 0; i < session->cmds_max; i++) {
-		ctask      = session->cmds[i];
-		iser_ctask = ctask->dd_data;
-		ctask->hdr = (struct iscsi_cmd *)&iser_ctask->desc.iscsi_header;
-	}
+	if (iscsi2_host_add(shost,
+			   ep ? ib_conn->device->ib_device->dma_device : NULL))
+		goto free_host;
 
-	for (i = 0; i < session->mgmtpool_max; i++) {
-		mtask      = session->mgmt_cmds[i];
-		desc       = mtask->dd_data;
-		mtask->hdr = &desc->iscsi_header;
-		desc->data = mtask->data;
-	}
+	/*
+	 * we do not support setting can_queue cmd_per_lun from userspace yet
+	 * because we preallocate so many resources
+	 */
+	cls_session = iscsi2_session_setup(&iscsi_iser_transport, shost,
+					  ISCSI_DEF_XMIT_CMDS_MAX,
+					  sizeof(struct iscsi_iser_task),
+					  initial_cmdsn, 0);
+	if (!cls_session)
+		goto remove_host;
+	session = cls_session->dd_data;
 
+	shost->can_queue = session->scsi_cmds_max;
 	return cls_session;
+
+remove_host:
+	iscsi2_host_remove(shost);
+free_host:
+	iscsi2_host_free(shost);
+	return NULL;
 }
 
 static int
@@ -451,7 +485,7 @@ iscsi_iser_set_param(struct iscsi_cls_conn *cls_conn,
 		}
 		break;
 	default:
-		return iscsi_set_param(cls_conn, param, buf, buflen);
+		return iscsi2_set_param(cls_conn, param, buf, buflen);
 	}
 
 	return 0;
@@ -471,43 +505,49 @@ iscsi_iser_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *s
 	stats->r2t_pdus = conn->r2t_pdus_cnt; /* always 0 */
 	stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt;
 	stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt;
-	stats->custom_length = 3;
+	stats->custom_length = 4;
 	strcpy(stats->custom[0].desc, "qp_tx_queue_full");
 	stats->custom[0].value = 0; /* TB iser_conn->qp_tx_queue_full; */
 	strcpy(stats->custom[1].desc, "fmr_map_not_avail");
 	stats->custom[1].value = 0; /* TB iser_conn->fmr_map_not_avail */;
 	strcpy(stats->custom[2].desc, "eh_abort_cnt");
 	stats->custom[2].value = conn->eh_abort_cnt;
+	strcpy(stats->custom[3].desc, "fmr_unalign_cnt");
+	stats->custom[3].value = conn->fmr_unalign_cnt;
 }
 
-static int
-iscsi_iser_ep_connect(struct sockaddr *dst_addr, int non_blocking,
-		      __u64 *ep_handle)
+static struct iscsi_endpoint *
+iscsi_iser_ep_connect(struct Scsi_Host *shost, struct sockaddr *dst_addr,
+		      int non_blocking)
 {
 	int err;
 	struct iser_conn *ib_conn;
+	struct iscsi_endpoint *ep;
 
-	err = iser_conn_init(&ib_conn);
-	if (err)
-		goto out;
+	ep = iscsi2_create_endpoint(sizeof(*ib_conn));
+	if (!ep)
+		return ERR_PTR(-ENOMEM);
 
-	err = iser_connect(ib_conn, NULL, (struct sockaddr_in *)dst_addr, non_blocking);
-	if (!err)
-		*ep_handle = (__u64)(unsigned long)ib_conn;
+	ib_conn = ep->dd_data;
+	ib_conn->ep = ep;
+	iser_conn_init(ib_conn);
 
-out:
-	return err;
+	err = iser_connect(ib_conn, NULL, (struct sockaddr_in *)dst_addr,
+			   non_blocking);
+	if (err) {
+		iscsi2_destroy_endpoint(ep);
+		return ERR_PTR(err);
+	}
+	return ep;
 }
 
 static int
-iscsi_iser_ep_poll(__u64 ep_handle, int timeout_ms)
+iscsi_iser_ep_poll(struct iscsi_endpoint *ep, int timeout_ms)
 {
-	struct iser_conn *ib_conn = iscsi_iser_ib_conn_lookup(ep_handle);
+	struct iser_conn *ib_conn;
 	int rc;
 
-	if (!ib_conn)
-		return -EINVAL;
-
+	ib_conn = ep->dd_data;
 	rc = wait_event_interruptible_timeout(ib_conn->wait,
 			     ib_conn->state == ISER_CONN_UP,
 			     msecs_to_jiffies(timeout_ms));
@@ -529,28 +569,37 @@ iscsi_iser_ep_poll(__u64 ep_handle, int timeout_ms)
 }
 
 static void
-iscsi_iser_ep_disconnect(__u64 ep_handle)
+iscsi_iser_ep_disconnect(struct iscsi_endpoint *ep)
 {
 	struct iser_conn *ib_conn;
 
-	ib_conn = iscsi_iser_ib_conn_lookup(ep_handle);
-	if (!ib_conn)
-		return;
+	ib_conn = ep->dd_data;
+	if (ib_conn->iser_conn)
+		/*
+		 * Must suspend xmit path if the ep is bound to the
+		 * iscsi_conn, so we know we are not accessing the ib_conn
+		 * when we free it.
+		 *
+		 * This may not be bound if the ep poll failed.
+		 */
+		iscsi2_suspend_tx(ib_conn->iser_conn->iscsi_conn);
+
 
 	iser_err("ib conn %p state %d\n",ib_conn, ib_conn->state);
 	iser_conn_terminate(ib_conn);
 }
 
 static struct scsi_host_template iscsi_iser_sht = {
-	.module			= THIS_MODULE,
+	.module                 = THIS_MODULE,
 	.name                   = "iSCSI Initiator over iSER, v." DRV_VER,
-	.queuecommand           = iscsi_queuecommand,
-	.can_queue		= ISCSI_XMIT_CMDS_MAX - 1,
+	.queuecommand           = iscsi2_queuecommand,
+	.change_queue_depth	= iscsi2_change_queue_depth,
 	.sg_tablesize           = ISCSI_ISER_SG_TABLESIZE,
 	.max_sectors		= 1024,
-	.cmd_per_lun            = ISCSI_MAX_CMD_PER_LUN,
-	.eh_abort_handler       = iscsi_eh_abort,
-	.eh_host_reset_handler	= iscsi_eh_host_reset,
+	.cmd_per_lun            = ISER_DEF_CMD_PER_LUN,
+	.eh_abort_handler       = iscsi2_eh_abort,
+	.eh_device_reset_handler= iscsi2_eh_device_reset,
+	.eh_host_reset_handler= iscsi2_eh_target_reset,
 	.use_clustering         = DISABLE_CLUSTERING,
 	.proc_name              = "iscsi_iser",
 	.this_id                = -1,
@@ -577,38 +626,36 @@ static struct iscsi_transport iscsi_iser_transport = {
 				  ISCSI_TARGET_NAME | ISCSI_TPGT |
 				  ISCSI_USERNAME | ISCSI_PASSWORD |
 				  ISCSI_USERNAME_IN | ISCSI_PASSWORD_IN |
-				  ISCSI_PING_TMO | ISCSI_RECV_TMO,
+				  ISCSI_FAST_ABORT | ISCSI_ABORT_TMO |
+				  ISCSI_PING_TMO | ISCSI_RECV_TMO |
+				  ISCSI_IFACE_NAME | ISCSI_INITIATOR_NAME,
 	.host_param_mask	= ISCSI_HOST_HWADDRESS |
 				  ISCSI_HOST_NETDEV_NAME |
 				  ISCSI_HOST_INITIATOR_NAME,
-	.host_template          = &iscsi_iser_sht,
-	.conndata_size		= sizeof(struct iscsi_conn),
-	.max_lun                = ISCSI_ISER_MAX_LUN,
-	.max_cmd_len            = ISCSI_ISER_MAX_CMD_LEN,
 	/* session management */
 	.create_session         = iscsi_iser_session_create,
-	.destroy_session        = iscsi_session_teardown,
+	.destroy_session        = iscsi_iser_session_destroy,
 	/* connection management */
 	.create_conn            = iscsi_iser_conn_create,
 	.bind_conn              = iscsi_iser_conn_bind,
 	.destroy_conn           = iscsi_iser_conn_destroy,
 	.set_param              = iscsi_iser_set_param,
-	.get_conn_param		= iscsi_conn_get_param,
-	.get_session_param	= iscsi_session_get_param,
+	.get_conn_param		= iscsi2_conn_get_param,
+	.get_session_param	= iscsi2_session_get_param,
 	.start_conn             = iscsi_iser_conn_start,
-	.stop_conn              = iscsi_conn_stop,
+	.stop_conn              = iscsi_iser_conn_stop,
 	/* iscsi host params */
-	.get_host_param		= iscsi_host_get_param,
-	.set_host_param		= iscsi_host_set_param,
+	.get_host_param		= iscsi2_host_get_param,
+	.set_host_param		= iscsi2_host_set_param,
 	/* IO */
-	.send_pdu		= iscsi_conn_send_pdu,
+	.send_pdu		= iscsi2_conn_send_pdu,
 	.get_stats		= iscsi_iser_conn_get_stats,
-	.init_cmd_task		= iscsi_iser_cmd_init,
-	.xmit_cmd_task		= iscsi_iser_ctask_xmit,
-	.xmit_mgmt_task		= iscsi_iser_mtask_xmit,
-	.cleanup_cmd_task	= iscsi_iser_cleanup_ctask,
+	.init_task		= iscsi_iser_task_init,
+	.xmit_task		= iscsi_iser_task_xmit,
+	.cleanup_task		= iscsi_iser_cleanup_task,
+	.alloc_pdu		= iscsi_iser_pdu_alloc,
 	/* recovery */
-	.session_recovery_timedout = iscsi_session_recovery_timedout,
+	.session_recovery_timedout = iscsi2_session_recovery_timedout,
 
 	.ep_connect             = iscsi_iser_ep_connect,
 	.ep_poll                = iscsi_iser_ep_poll,
@@ -626,8 +673,6 @@ static int __init iser_init(void)
 		return -EINVAL;
 	}
 
-	iscsi_iser_transport.max_lun = iscsi_max_lun;
-
 	memset(&ig, 0, sizeof(struct iser_global));
 
 	ig.desc_cache = kmem_cache_create("iser_descriptors",
@@ -643,7 +688,9 @@ static int __init iser_init(void)
 	mutex_init(&ig.connlist_mutex);
 	INIT_LIST_HEAD(&ig.connlist);
 
-	if (!iscsi_register_transport(&iscsi_iser_transport)) {
+	iscsi_iser_scsi_transport = iscsi2_register_transport(
+							&iscsi_iser_transport);
+	if (!iscsi_iser_scsi_transport) {
 		iser_err("iscsi_register_transport failed\n");
 		err = -EINVAL;
 		goto register_transport_failure;
@@ -660,7 +707,7 @@ register_transport_failure:
 static void __exit iser_exit(void)
 {
 	iser_dbg("Removing iSER datamover...\n");
-	iscsi_unregister_transport(&iscsi_iser_transport);
+	iscsi2_unregister_transport(&iscsi_iser_transport);
 	kmem_cache_destroy(ig.desc_cache);
 }
 
diff --git a/drivers/infiniband/ulp/iser/iscsi_iser.h b/drivers/infiniband/ulp/iser/iscsi_iser.h
index b759b12..139cde8 100644
--- a/drivers/infiniband/ulp/iser/iscsi_iser.h
+++ b/drivers/infiniband/ulp/iser/iscsi_iser.h
@@ -36,16 +36,14 @@
  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
- *
- * $Id: iscsi_iser.h 7051 2006-05-10 12:29:11Z ogerlitz $
  */
 #ifndef __ISCSI_ISER_H__
 #define __ISCSI_ISER_H__
 
 #include <linux/types.h>
 #include <linux/net.h>
-#include <scsi/libiscsi.h>
-#include <scsi/scsi_transport_iscsi.h>
+#include <scsi/libiscsi2.h>
+#include <scsi/scsi_transport_iscsi2.h>
 
 #include <linux/wait.h>
 #include <linux/sched.h>
@@ -71,6 +69,13 @@
 
 #define iser_dbg(fmt, arg...)				\
 	do {						\
+		if (iser_debug_level > 1)		\
+			printk(KERN_DEBUG PFX "%s:" fmt,\
+				__func__ , ## arg);	\
+	} while (0)
+
+#define iser_warn(fmt, arg...)				\
+	do {						\
 		if (iser_debug_level > 0)		\
 			printk(KERN_DEBUG PFX "%s:" fmt,\
 				__func__ , ## arg);	\
@@ -88,8 +93,7 @@
 
 					/* support upto 512KB in one RDMA */
 #define ISCSI_ISER_SG_TABLESIZE         (0x80000 >> SHIFT_4K)
-#define ISCSI_ISER_MAX_LUN		256
-#define ISCSI_ISER_MAX_CMD_LEN		16
+#define ISER_DEF_CMD_PER_LUN		128
 
 /* QP settings */
 /* Maximal bounds on received asynchronous PDUs */
@@ -98,7 +102,7 @@
 #define ISER_MAX_TX_MISC_PDUS		6 /* NOOP_OUT(2), TEXT(1),         *
 					   * SCSI_TMFUNC(2), LOGOUT(1) */
 
-#define ISER_QP_MAX_RECV_DTOS		(ISCSI_XMIT_CMDS_MAX + \
+#define ISER_QP_MAX_RECV_DTOS		(ISCSI_DEF_XMIT_CMDS_MAX + \
 					ISER_MAX_RX_MISC_PDUS    +  \
 					ISER_MAX_TX_MISC_PDUS)
 
@@ -110,7 +114,7 @@
 
 #define ISER_INFLIGHT_DATAOUTS		8
 
-#define ISER_QP_MAX_REQ_DTOS		(ISCSI_XMIT_CMDS_MAX *    \
+#define ISER_QP_MAX_REQ_DTOS		(ISCSI_DEF_XMIT_CMDS_MAX *    \
 					(1 + ISER_INFLIGHT_DATAOUTS) + \
 					ISER_MAX_TX_MISC_PDUS        + \
 					ISER_MAX_RX_MISC_PDUS)
@@ -167,7 +171,8 @@ struct iser_data_buf {
 /* fwd declarations */
 struct iser_device;
 struct iscsi_iser_conn;
-struct iscsi_iser_cmd_task;
+struct iscsi_iser_task;
+struct iscsi_endpoint;
 
 struct iser_mem_reg {
 	u32  lkey;
@@ -182,7 +187,7 @@ struct iser_regd_buf {
 	struct iser_mem_reg     reg;        /* memory registration info        */
 	void                    *virt_addr;
 	struct iser_device      *device;    /* device->device for dma_unmap    */
-	dma_addr_t              dma_addr;   /* if non zero, addr for dma_unmap */
+	u64                     dma_addr;   /* if non zero, addr for dma_unmap */
 	enum dma_data_direction direction;  /* direction for dma_unmap	       */
 	unsigned int            data_size;
 	atomic_t                ref_count;  /* refcount, freed when dec to 0   */
@@ -191,7 +196,7 @@ struct iser_regd_buf {
 #define MAX_REGD_BUF_VECTOR_LEN	2
 
 struct iser_dto {
-	struct iscsi_iser_cmd_task *ctask;
+	struct iscsi_iser_task *task;
 	struct iser_conn *ib_conn;
 	int                        notify_enable;
 
@@ -235,7 +240,9 @@ struct iser_device {
 
 struct iser_conn {
 	struct iscsi_iser_conn       *iser_conn; /* iser conn for upcalls  */
+	struct iscsi_endpoint	     *ep;
 	enum iser_ib_conn_state	     state;	    /* rdma connection state   */
+	atomic_t		     refcount;
 	spinlock_t		     lock;	    /* used for state changes  */
 	struct iser_device           *device;       /* device context          */
 	struct rdma_cm_id            *cma_id;       /* CMA ID		       */
@@ -245,6 +252,9 @@ struct iser_conn {
 	wait_queue_head_t	     wait;          /* waitq for conn/disconn  */
 	atomic_t                     post_recv_buf_count; /* posted rx count   */
 	atomic_t                     post_send_buf_count; /* posted tx count   */
+	atomic_t                     unexpected_pdu_count;/* count of received *
+							   * unexpected pdus   *
+							   * not yet retired   */
 	char 			     name[ISER_OBJECT_NAME_SIZE];
 	struct iser_page_vec         *page_vec;     /* represents SG to fmr maps*
 						     * maps serialized as tx is*/
@@ -254,11 +264,9 @@ struct iser_conn {
 struct iscsi_iser_conn {
 	struct iscsi_conn            *iscsi_conn;/* ptr to iscsi conn */
 	struct iser_conn             *ib_conn;   /* iSER IB conn      */
-
-	rwlock_t		     lock;
 };
 
-struct iscsi_iser_cmd_task {
+struct iscsi_iser_task {
 	struct iser_desc             desc;
 	struct iscsi_iser_conn	     *iser_conn;
 	enum iser_task_status 	     status;
@@ -282,7 +290,7 @@ struct iser_global {
 	struct mutex      connlist_mutex;
 	struct list_head  connlist;		/* all iSER IB connections */
 
-	kmem_cache_t *desc_cache;
+	struct kmem_cache *desc_cache;
 };
 
 extern struct iser_global ig;
@@ -291,35 +299,37 @@ extern int iser_debug_level;
 /* allocate connection resources needed for rdma functionality */
 int iser_conn_set_full_featured_mode(struct iscsi_conn *conn);
 
-int iser_send_control(struct iscsi_conn      *conn,
-		      struct iscsi_mgmt_task *mtask);
+int iser_send_control(struct iscsi_conn *conn,
+		      struct iscsi_task *task);
 
-int iser_send_command(struct iscsi_conn      *conn,
-		      struct iscsi_cmd_task  *ctask);
+int iser_send_command(struct iscsi_conn *conn,
+		      struct iscsi_task *task);
 
-int iser_send_data_out(struct iscsi_conn     *conn,
-		       struct iscsi_cmd_task *ctask,
-		       struct iscsi_data          *hdr);
+int iser_send_data_out(struct iscsi_conn *conn,
+		       struct iscsi_task *task,
+		       struct iscsi_data *hdr);
 
 void iscsi_iser_recv(struct iscsi_conn *conn,
 		     struct iscsi_hdr       *hdr,
 		     char                   *rx_data,
 		     int                    rx_data_len);
 
-int  iser_conn_init(struct iser_conn **ib_conn);
+void iser_conn_init(struct iser_conn *ib_conn);
 
-void iser_conn_terminate(struct iser_conn *ib_conn);
+void iser_conn_get(struct iser_conn *ib_conn);
 
-void iser_conn_release(struct iser_conn *ib_conn);
+void iser_conn_put(struct iser_conn *ib_conn);
+
+void iser_conn_terminate(struct iser_conn *ib_conn);
 
 void iser_rcv_completion(struct iser_desc *desc,
 			 unsigned long    dto_xfer_len);
 
 void iser_snd_completion(struct iser_desc *desc);
 
-void iser_ctask_rdma_init(struct iscsi_iser_cmd_task     *ctask);
+void iser_task_rdma_init(struct iscsi_iser_task *task);
 
-void iser_ctask_rdma_finalize(struct iscsi_iser_cmd_task *ctask);
+void iser_task_rdma_finalize(struct iscsi_iser_task *task);
 
 void iser_dto_buffs_release(struct iser_dto *dto);
 
@@ -329,13 +339,10 @@ void iser_reg_single(struct iser_device      *device,
 		     struct iser_regd_buf    *regd_buf,
 		     enum dma_data_direction direction);
 
-int  iser_start_rdma_unaligned_sg(struct iscsi_iser_cmd_task    *ctask,
-				  enum iser_data_dir            cmd_dir);
-
-void iser_finalize_rdma_unaligned_sg(struct iscsi_iser_cmd_task *ctask,
+void iser_finalize_rdma_unaligned_sg(struct iscsi_iser_task *task,
 				     enum iser_data_dir         cmd_dir);
 
-int  iser_reg_rdma_mem(struct iscsi_iser_cmd_task *ctask,
+int  iser_reg_rdma_mem(struct iscsi_iser_task *task,
 		       enum   iser_data_dir        cmd_dir);
 
 int  iser_connect(struct iser_conn   *ib_conn,
@@ -355,10 +362,10 @@ int  iser_post_send(struct iser_desc *tx_desc);
 int iser_conn_state_comp(struct iser_conn *ib_conn,
 			 enum iser_ib_conn_state comp);
 
-int iser_dma_map_task_data(struct iscsi_iser_cmd_task *iser_ctask,
+int iser_dma_map_task_data(struct iscsi_iser_task *iser_task,
 			    struct iser_data_buf       *data,
 			    enum   iser_data_dir       iser_dir,
 			    enum   dma_data_direction  dma_dir);
 
-void iser_dma_unmap_task_data(struct iscsi_iser_cmd_task *iser_ctask);
+void iser_dma_unmap_task_data(struct iscsi_iser_task *iser_task);
 #endif
diff --git a/drivers/infiniband/ulp/iser/iser_initiator.c b/drivers/infiniband/ulp/iser/iser_initiator.c
index 6727017..8ba0c3b 100644
--- a/drivers/infiniband/ulp/iser/iser_initiator.c
+++ b/drivers/infiniband/ulp/iser/iser_initiator.c
@@ -28,14 +28,10 @@
  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
- *
- * $Id: iser_initiator.c 6964 2006-05-07 11:11:43Z ogerlitz $
  */
 #include <linux/kernel.h>
 #include <linux/slab.h>
 #include <linux/mm.h>
-#include <asm/io.h>
-#include <asm/scatterlist.h>
 #include <linux/scatterlist.h>
 #include <linux/kfifo.h>
 #include <scsi/scsi_cmnd.h>
@@ -68,46 +64,46 @@ static void iser_dto_add_regd_buff(struct iser_dto *dto,
 
 /* Register user buffer memory and initialize passive rdma
  *  dto descriptor. Total data size is stored in
- *  iser_ctask->data[ISER_DIR_IN].data_len
+ *  iser_task->data[ISER_DIR_IN].data_len
  */
-static int iser_prepare_read_cmd(struct iscsi_cmd_task *ctask,
+static int iser_prepare_read_cmd(struct iscsi_task *task,
 				 unsigned int edtl)
 
 {
-	struct iscsi_iser_cmd_task *iser_ctask = ctask->dd_data;
+	struct iscsi_iser_task *iser_task = task->dd_data;
 	struct iser_regd_buf *regd_buf;
 	int err;
-	struct iser_hdr *hdr = &iser_ctask->desc.iser_header;
-	struct iser_data_buf *buf_in = &iser_ctask->data[ISER_DIR_IN];
+	struct iser_hdr *hdr = &iser_task->desc.iser_header;
+	struct iser_data_buf *buf_in = &iser_task->data[ISER_DIR_IN];
 
-	err = iser_dma_map_task_data(iser_ctask,
+	err = iser_dma_map_task_data(iser_task,
 				     buf_in,
 				     ISER_DIR_IN,
 				     DMA_FROM_DEVICE);
 	if (err)
 		return err;
 
-	if (edtl > iser_ctask->data[ISER_DIR_IN].data_len) {
+	if (edtl > iser_task->data[ISER_DIR_IN].data_len) {
 		iser_err("Total data length: %ld, less than EDTL: "
 			 "%d, in READ cmd BHS itt: %d, conn: 0x%p\n",
-			 iser_ctask->data[ISER_DIR_IN].data_len, edtl,
-			 ctask->itt, iser_ctask->iser_conn);
+			 iser_task->data[ISER_DIR_IN].data_len, edtl,
+			 task->itt, iser_task->iser_conn);
 		return -EINVAL;
 	}
 
-	err = iser_reg_rdma_mem(iser_ctask,ISER_DIR_IN);
+	err = iser_reg_rdma_mem(iser_task,ISER_DIR_IN);
 	if (err) {
 		iser_err("Failed to set up Data-IN RDMA\n");
 		return err;
 	}
-	regd_buf = &iser_ctask->rdma_regd[ISER_DIR_IN];
+	regd_buf = &iser_task->rdma_regd[ISER_DIR_IN];
 
 	hdr->flags    |= ISER_RSV;
 	hdr->read_stag = cpu_to_be32(regd_buf->reg.rkey);
 	hdr->read_va   = cpu_to_be64(regd_buf->reg.va);
 
 	iser_dbg("Cmd itt:%d READ tags RKEY:%#.4X VA:%#llX\n",
-		 ctask->itt, regd_buf->reg.rkey,
+		 task->itt, regd_buf->reg.rkey,
 		 (unsigned long long)regd_buf->reg.va);
 
 	return 0;
@@ -115,43 +111,43 @@ static int iser_prepare_read_cmd(struct iscsi_cmd_task *ctask,
 
 /* Register user buffer memory and initialize passive rdma
  *  dto descriptor. Total data size is stored in
- *  ctask->data[ISER_DIR_OUT].data_len
+ *  task->data[ISER_DIR_OUT].data_len
  */
 static int
-iser_prepare_write_cmd(struct iscsi_cmd_task *ctask,
+iser_prepare_write_cmd(struct iscsi_task *task,
 		       unsigned int imm_sz,
 		       unsigned int unsol_sz,
 		       unsigned int edtl)
 {
-	struct iscsi_iser_cmd_task *iser_ctask = ctask->dd_data;
+	struct iscsi_iser_task *iser_task = task->dd_data;
 	struct iser_regd_buf *regd_buf;
 	int err;
-	struct iser_dto *send_dto = &iser_ctask->desc.dto;
-	struct iser_hdr *hdr = &iser_ctask->desc.iser_header;
-	struct iser_data_buf *buf_out = &iser_ctask->data[ISER_DIR_OUT];
+	struct iser_dto *send_dto = &iser_task->desc.dto;
+	struct iser_hdr *hdr = &iser_task->desc.iser_header;
+	struct iser_data_buf *buf_out = &iser_task->data[ISER_DIR_OUT];
 
-	err = iser_dma_map_task_data(iser_ctask,
+	err = iser_dma_map_task_data(iser_task,
 				     buf_out,
 				     ISER_DIR_OUT,
 				     DMA_TO_DEVICE);
 	if (err)
 		return err;
 
-	if (edtl > iser_ctask->data[ISER_DIR_OUT].data_len) {
+	if (edtl > iser_task->data[ISER_DIR_OUT].data_len) {
 		iser_err("Total data length: %ld, less than EDTL: %d, "
 			 "in WRITE cmd BHS itt: %d, conn: 0x%p\n",
-			 iser_ctask->data[ISER_DIR_OUT].data_len,
-			 edtl, ctask->itt, ctask->conn);
+			 iser_task->data[ISER_DIR_OUT].data_len,
+			 edtl, task->itt, task->conn);
 		return -EINVAL;
 	}
 
-	err = iser_reg_rdma_mem(iser_ctask,ISER_DIR_OUT);
+	err = iser_reg_rdma_mem(iser_task,ISER_DIR_OUT);
 	if (err != 0) {
 		iser_err("Failed to register write cmd RDMA mem\n");
 		return err;
 	}
 
-	regd_buf = &iser_ctask->rdma_regd[ISER_DIR_OUT];
+	regd_buf = &iser_task->rdma_regd[ISER_DIR_OUT];
 
 	if (unsol_sz < edtl) {
 		hdr->flags     |= ISER_WSV;
@@ -160,13 +156,13 @@ iser_prepare_write_cmd(struct iscsi_cmd_task *ctask,
 
 		iser_dbg("Cmd itt:%d, WRITE tags, RKEY:%#.4X "
 			 "VA:%#llX + unsol:%d\n",
-			 ctask->itt, regd_buf->reg.rkey,
+			 task->itt, regd_buf->reg.rkey,
 			 (unsigned long long)regd_buf->reg.va, unsol_sz);
 	}
 
 	if (imm_sz > 0) {
 		iser_dbg("Cmd itt:%d, WRITE, adding imm.data sz: %d\n",
-			 ctask->itt, imm_sz);
+			 task->itt, imm_sz);
 		iser_dto_add_regd_buff(send_dto,
 				       regd_buf,
 				       0,
@@ -187,64 +183,92 @@ static int iser_post_receive_control(struct iscsi_conn *conn)
 	struct iser_regd_buf *regd_data;
 	struct iser_dto      *recv_dto = NULL;
 	struct iser_device  *device = iser_conn->ib_conn->device;
-	int rx_data_size, err = 0;
-
-	rx_desc = kmem_cache_alloc(ig.desc_cache, GFP_NOIO);
-	if (rx_desc == NULL) {
-		iser_err("Failed to alloc desc for post recv\n");
-		return -ENOMEM;
-	}
-	rx_desc->type = ISCSI_RX;
+	int rx_data_size, err;
+	int posts, outstanding_unexp_pdus;
 
 	/* for the login sequence we must support rx of upto 8K; login is done
 	 * after conn create/bind (connect) and conn stop/bind (reconnect),
 	 * what's common for both schemes is that the connection is not started
 	 */
 	if (conn->c_stage != ISCSI_CONN_STARTED)
-		rx_data_size = DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH;
+		rx_data_size = ISCSI_DEF_MAX_RECV_SEG_LEN;
 	else /* FIXME till user space sets conn->max_recv_dlength correctly */
 		rx_data_size = 128;
 
-	rx_desc->data = kmalloc(rx_data_size, GFP_NOIO);
-	if (rx_desc->data == NULL) {
-		iser_err("Failed to alloc data buf for post recv\n");
-		err = -ENOMEM;
-		goto post_rx_kmalloc_failure;
-	}
+	outstanding_unexp_pdus =
+		atomic_xchg(&iser_conn->ib_conn->unexpected_pdu_count, 0);
+
+	/*
+	 * in addition to the response buffer, replace those consumed by
+	 * unexpected pdus.
+	 */
+	for (posts = 0; posts < 1 + outstanding_unexp_pdus; posts++) {
+		rx_desc = kmem_cache_alloc(ig.desc_cache, GFP_NOIO);
+		if (rx_desc == NULL) {
+			iser_err("Failed to alloc desc for post recv %d\n",
+				 posts);
+			err = -ENOMEM;
+			goto post_rx_cache_alloc_failure;
+		}
+		rx_desc->type = ISCSI_RX;
+		rx_desc->data = kmalloc(rx_data_size, GFP_NOIO);
+		if (rx_desc->data == NULL) {
+			iser_err("Failed to alloc data buf for post recv %d\n",
+				 posts);
+			err = -ENOMEM;
+			goto post_rx_kmalloc_failure;
+		}
 
-	recv_dto = &rx_desc->dto;
-	recv_dto->ib_conn = iser_conn->ib_conn;
-	recv_dto->regd_vector_len = 0;
+		recv_dto = &rx_desc->dto;
+		recv_dto->ib_conn = iser_conn->ib_conn;
+		recv_dto->regd_vector_len = 0;
 
-	regd_hdr = &rx_desc->hdr_regd_buf;
-	memset(regd_hdr, 0, sizeof(struct iser_regd_buf));
-	regd_hdr->device  = device;
-	regd_hdr->virt_addr  = rx_desc; /* == &rx_desc->iser_header */
-	regd_hdr->data_size  = ISER_TOTAL_HEADERS_LEN;
+		regd_hdr = &rx_desc->hdr_regd_buf;
+		memset(regd_hdr, 0, sizeof(struct iser_regd_buf));
+		regd_hdr->device  = device;
+		regd_hdr->virt_addr  = rx_desc; /* == &rx_desc->iser_header */
+		regd_hdr->data_size  = ISER_TOTAL_HEADERS_LEN;
 
-	iser_reg_single(device, regd_hdr, DMA_FROM_DEVICE);
+		iser_reg_single(device, regd_hdr, DMA_FROM_DEVICE);
 
-	iser_dto_add_regd_buff(recv_dto, regd_hdr, 0, 0);
+		iser_dto_add_regd_buff(recv_dto, regd_hdr, 0, 0);
 
-	regd_data = &rx_desc->data_regd_buf;
-	memset(regd_data, 0, sizeof(struct iser_regd_buf));
-	regd_data->device  = device;
-	regd_data->virt_addr  = rx_desc->data;
-	regd_data->data_size  = rx_data_size;
+		regd_data = &rx_desc->data_regd_buf;
+		memset(regd_data, 0, sizeof(struct iser_regd_buf));
+		regd_data->device  = device;
+		regd_data->virt_addr  = rx_desc->data;
+		regd_data->data_size  = rx_data_size;
 
-	iser_reg_single(device, regd_data, DMA_FROM_DEVICE);
+		iser_reg_single(device, regd_data, DMA_FROM_DEVICE);
 
-	iser_dto_add_regd_buff(recv_dto, regd_data, 0, 0);
+		iser_dto_add_regd_buff(recv_dto, regd_data, 0, 0);
 
-	err = iser_post_recv(rx_desc);
-	if (!err)
-		return 0;
+		err = iser_post_recv(rx_desc);
+		if (err) {
+			iser_err("Failed iser_post_recv for post %d\n", posts);
+			goto post_rx_post_recv_failure;
+		}
+	}
+	/* all posts successful */
+	return 0;
 
-	/* iser_post_recv failed */
+post_rx_post_recv_failure:
 	iser_dto_buffs_release(recv_dto);
 	kfree(rx_desc->data);
 post_rx_kmalloc_failure:
 	kmem_cache_free(ig.desc_cache, rx_desc);
+post_rx_cache_alloc_failure:
+	if (posts > 0) {
+		/*
+		 * response buffer posted, but did not replace all unexpected
+		 * pdu recv bufs. Ignore error, retry occurs next send
+		 */
+		outstanding_unexp_pdus -= (posts - 1);
+		err = 0;
+	}
+	atomic_add(outstanding_unexp_pdus,
+		   &iser_conn->ib_conn->unexpected_pdu_count);
+
 	return err;
 }
 
@@ -278,8 +302,10 @@ int iser_conn_set_full_featured_mode(struct iscsi_conn *conn)
 	struct iscsi_iser_conn *iser_conn = conn->dd_data;
 
 	int i;
-	/* no need to keep it in a var, we are after login so if this should
-	 * be negotiated, by now the result should be available here */
+	/*
+	 * FIXME this value should be declared to the target during login with
+	 * the MaxOutstandingUnexpectedPDUs key when supported
+	 */
 	int initial_post_recv_bufs_num = ISER_MAX_RX_MISC_PDUS;
 
 	iser_dbg("Initially post: %d\n", initial_post_recv_bufs_num);
@@ -318,62 +344,55 @@ iser_check_xmit(struct iscsi_conn *conn, void *task)
 /**
  * iser_send_command - send command PDU
  */
-int iser_send_command(struct iscsi_conn     *conn,
-		      struct iscsi_cmd_task *ctask)
+int iser_send_command(struct iscsi_conn *conn,
+		      struct iscsi_task *task)
 {
 	struct iscsi_iser_conn *iser_conn = conn->dd_data;
-	struct iscsi_iser_cmd_task *iser_ctask = ctask->dd_data;
+	struct iscsi_iser_task *iser_task = task->dd_data;
 	struct iser_dto *send_dto = NULL;
 	unsigned long edtl;
 	int err = 0;
 	struct iser_data_buf *data_buf;
-
-	struct iscsi_cmd *hdr =  ctask->hdr;
-	struct scsi_cmnd *sc  =  ctask->sc;
+	struct iscsi_cmd *hdr =  (struct iscsi_cmd *)task->hdr;
+	struct scsi_cmnd *sc  =  task->sc;
 
 	if (!iser_conn_state_comp(iser_conn->ib_conn, ISER_CONN_UP)) {
 		iser_err("Failed to send, conn: 0x%p is not up\n", iser_conn->ib_conn);
 		return -EPERM;
 	}
-	if (iser_check_xmit(conn, ctask))
+	if (iser_check_xmit(conn, task))
 		return -ENOBUFS;
 
 	edtl = ntohl(hdr->data_length);
 
 	/* build the tx desc regd header and add it to the tx desc dto */
-	iser_ctask->desc.type = ISCSI_TX_SCSI_COMMAND;
-	send_dto = &iser_ctask->desc.dto;
-	send_dto->ctask = iser_ctask;
-	iser_create_send_desc(iser_conn, &iser_ctask->desc);
+	iser_task->desc.type = ISCSI_TX_SCSI_COMMAND;
+	send_dto = &iser_task->desc.dto;
+	send_dto->task = iser_task;
+	iser_create_send_desc(iser_conn, &iser_task->desc);
 
 	if (hdr->flags & ISCSI_FLAG_CMD_READ)
-		data_buf = &iser_ctask->data[ISER_DIR_IN];
+		data_buf = &iser_task->data[ISER_DIR_IN];
 	else
-		data_buf = &iser_ctask->data[ISER_DIR_OUT];
-
-	if (sc->use_sg) { /* using a scatter list */
-		data_buf->buf  = sc->request_buffer;
-		data_buf->size = sc->use_sg;
-	} else if (sc->request_bufflen) {
-		/* using a single buffer - convert it into one entry SG */
-		sg_init_one(&data_buf->sg_single,
-			    sc->request_buffer, sc->request_bufflen);
-		data_buf->buf   = &data_buf->sg_single;
-		data_buf->size  = 1;
+		data_buf = &iser_task->data[ISER_DIR_OUT];
+
+	if (scsi_sg_count(sc)) { /* using a scatter list */
+		data_buf->buf  = scsi_sglist(sc);
+		data_buf->size = scsi_sg_count(sc);
 	}
 
-	data_buf->data_len = sc->request_bufflen;
+	data_buf->data_len = scsi_bufflen(sc);
 
 	if (hdr->flags & ISCSI_FLAG_CMD_READ) {
-		err = iser_prepare_read_cmd(ctask, edtl);
+		err = iser_prepare_read_cmd(task, edtl);
 		if (err)
 			goto send_command_error;
 	}
 	if (hdr->flags & ISCSI_FLAG_CMD_WRITE) {
-		err = iser_prepare_write_cmd(ctask,
-					     ctask->imm_count,
-				             ctask->imm_count +
-					     ctask->unsol_count,
+		err = iser_prepare_write_cmd(task,
+					     task->imm_count,
+				             task->imm_count +
+					     task->unsol_r2t.data_length,
 					     edtl);
 		if (err)
 			goto send_command_error;
@@ -388,32 +407,32 @@ int iser_send_command(struct iscsi_conn     *conn,
 		goto send_command_error;
 	}
 
-	iser_ctask->status = ISER_TASK_STATUS_STARTED;
+	iser_task->status = ISER_TASK_STATUS_STARTED;
 
-	err = iser_post_send(&iser_ctask->desc);
+	err = iser_post_send(&iser_task->desc);
 	if (!err)
 		return 0;
 
 send_command_error:
 	iser_dto_buffs_release(send_dto);
-	iser_err("conn %p failed ctask->itt %d err %d\n",conn, ctask->itt, err);
+	iser_err("conn %p failed task->itt %d err %d\n",conn, task->itt, err);
 	return err;
 }
 
 /**
  * iser_send_data_out - send data out PDU
  */
-int iser_send_data_out(struct iscsi_conn     *conn,
-		       struct iscsi_cmd_task *ctask,
+int iser_send_data_out(struct iscsi_conn *conn,
+		       struct iscsi_task *task,
 		       struct iscsi_data *hdr)
 {
 	struct iscsi_iser_conn *iser_conn = conn->dd_data;
-	struct iscsi_iser_cmd_task *iser_ctask = ctask->dd_data;
+	struct iscsi_iser_task *iser_task = task->dd_data;
 	struct iser_desc *tx_desc = NULL;
 	struct iser_dto *send_dto = NULL;
 	unsigned long buf_offset;
 	unsigned long data_seg_len;
-	unsigned int itt;
+	uint32_t itt;
 	int err = 0;
 
 	if (!iser_conn_state_comp(iser_conn->ib_conn, ISER_CONN_UP)) {
@@ -421,10 +440,10 @@ int iser_send_data_out(struct iscsi_conn     *conn,
 		return -EPERM;
 	}
 
-	if (iser_check_xmit(conn, ctask))
+	if (iser_check_xmit(conn, task))
 		return -ENOBUFS;
 
-	itt = ntohl(hdr->itt);
+	itt = (__force uint32_t)hdr->itt;
 	data_seg_len = ntoh24(hdr->dlength);
 	buf_offset   = ntohl(hdr->offset);
 
@@ -442,7 +461,7 @@ int iser_send_data_out(struct iscsi_conn     *conn,
 
 	/* build the tx desc regd header and add it to the tx desc dto */
 	send_dto = &tx_desc->dto;
-	send_dto->ctask = iser_ctask;
+	send_dto->task = iser_task;
 	iser_create_send_desc(iser_conn, tx_desc);
 
 	iser_reg_single(iser_conn->ib_conn->device,
@@ -450,15 +469,15 @@ int iser_send_data_out(struct iscsi_conn     *conn,
 
 	/* all data was registered for RDMA, we can use the lkey */
 	iser_dto_add_regd_buff(send_dto,
-			       &iser_ctask->rdma_regd[ISER_DIR_OUT],
+			       &iser_task->rdma_regd[ISER_DIR_OUT],
 			       buf_offset,
 			       data_seg_len);
 
-	if (buf_offset + data_seg_len > iser_ctask->data[ISER_DIR_OUT].data_len) {
+	if (buf_offset + data_seg_len > iser_task->data[ISER_DIR_OUT].data_len) {
 		iser_err("Offset:%ld & DSL:%ld in Data-Out "
 			 "inconsistent with total len:%ld, itt:%d\n",
 			 buf_offset, data_seg_len,
-			 iser_ctask->data[ISER_DIR_OUT].data_len, itt);
+			 iser_task->data[ISER_DIR_OUT].data_len, itt);
 		err = -EINVAL;
 		goto send_data_out_error;
 	}
@@ -478,42 +497,44 @@ send_data_out_error:
 }
 
 int iser_send_control(struct iscsi_conn *conn,
-		      struct iscsi_mgmt_task *mtask)
+		      struct iscsi_task *task)
 {
 	struct iscsi_iser_conn *iser_conn = conn->dd_data;
-	struct iser_desc *mdesc = mtask->dd_data;
+	struct iscsi_iser_task *iser_task = task->dd_data;
+	struct iser_desc *mdesc = &iser_task->desc;
 	struct iser_dto *send_dto = NULL;
 	unsigned long data_seg_len;
 	int err = 0;
 	struct iser_regd_buf *regd_buf;
 	struct iser_device *device;
+	unsigned char opcode;
 
 	if (!iser_conn_state_comp(iser_conn->ib_conn, ISER_CONN_UP)) {
 		iser_err("Failed to send, conn: 0x%p is not up\n", iser_conn->ib_conn);
 		return -EPERM;
 	}
 
-	if (iser_check_xmit(conn,mtask))
+	if (iser_check_xmit(conn, task))
 		return -ENOBUFS;
 
 	/* build the tx desc regd header and add it to the tx desc dto */
 	mdesc->type = ISCSI_TX_CONTROL;
 	send_dto = &mdesc->dto;
-	send_dto->ctask = NULL;
+	send_dto->task = NULL;
 	iser_create_send_desc(iser_conn, mdesc);
 
 	device = iser_conn->ib_conn->device;
 
 	iser_reg_single(device, send_dto->regd[0], DMA_TO_DEVICE);
 
-	data_seg_len = ntoh24(mtask->hdr->dlength);
+	data_seg_len = ntoh24(task->hdr->dlength);
 
 	if (data_seg_len > 0) {
 		regd_buf = &mdesc->data_regd_buf;
 		memset(regd_buf, 0, sizeof(struct iser_regd_buf));
 		regd_buf->device = device;
-		regd_buf->virt_addr = mtask->data;
-		regd_buf->data_size = mtask->data_count;
+		regd_buf->virt_addr = task->data;
+		regd_buf->data_size = task->data_count;
 		iser_reg_single(device, regd_buf,
 				DMA_TO_DEVICE);
 		iser_dto_add_regd_buff(send_dto, regd_buf,
@@ -521,10 +542,15 @@ int iser_send_control(struct iscsi_conn *conn,
 				       data_seg_len);
 	}
 
-	if (iser_post_receive_control(conn) != 0) {
-		iser_err("post_rcv_buff failed!\n");
-		err = -ENOMEM;
-		goto send_control_error;
+	opcode = task->hdr->opcode & ISCSI_OPCODE_MASK;
+
+	/* post recv buffer for response if one is expected */
+	if (!(opcode == ISCSI_OP_NOOP_OUT && task->hdr->itt == RESERVED_ITT)) {
+		if (iser_post_receive_control(conn) != 0) {
+			iser_err("post_rcv_buff failed!\n");
+			err = -ENOMEM;
+			goto send_control_error;
+		}
 	}
 
 	err = iser_post_send(mdesc);
@@ -543,15 +569,13 @@ send_control_error:
 void iser_rcv_completion(struct iser_desc *rx_desc,
 			 unsigned long dto_xfer_len)
 {
-	struct iser_dto        *dto = &rx_desc->dto;
+	struct iser_dto *dto = &rx_desc->dto;
 	struct iscsi_iser_conn *conn = dto->ib_conn->iser_conn;
-	struct iscsi_session *session = conn->iscsi_conn->session;
-	struct iscsi_cmd_task *ctask;
-	struct iscsi_iser_cmd_task *iser_ctask;
+	struct iscsi_task *task;
+	struct iscsi_iser_task *iser_task;
 	struct iscsi_hdr *hdr;
 	char   *rx_data = NULL;
 	int     rx_data_len = 0;
-	unsigned int itt;
 	unsigned char opcode;
 
 	hdr = &rx_desc->iscsi_header;
@@ -567,19 +591,24 @@ void iser_rcv_completion(struct iser_desc *rx_desc,
 	opcode = hdr->opcode & ISCSI_OPCODE_MASK;
 
 	if (opcode == ISCSI_OP_SCSI_CMD_RSP) {
-	        itt = hdr->itt & ISCSI_ITT_MASK; /* mask out cid and age bits */
-		if (!(itt < session->cmds_max))
-			iser_err("itt can't be matched to task!!!"
-				 "conn %p opcode %d cmds_max %d itt %d\n",
-				 conn->iscsi_conn,opcode,session->cmds_max,itt);
-		/* use the mapping given with the cmds array indexed by itt */
-		ctask = (struct iscsi_cmd_task *)session->cmds[itt];
-		iser_ctask = ctask->dd_data;
-		iser_dbg("itt %d ctask %p\n",itt,ctask);
-		iser_ctask->status = ISER_TASK_STATUS_COMPLETED;
-		iser_ctask_rdma_finalize(iser_ctask);
+		spin_lock(&conn->iscsi_conn->session->lock);
+		task = iscsi2_itt_to_ctask(conn->iscsi_conn, hdr->itt);
+		if (task)
+			__iscsi2_get_task(task);
+		spin_unlock(&conn->iscsi_conn->session->lock);
+
+		if (!task)
+			iser_err("itt can't be matched to task!!! "
+				 "conn %p opcode %d itt %d\n",
+				 conn->iscsi_conn, opcode, hdr->itt);
+		else {
+			iser_task = task->dd_data;
+			iser_dbg("itt %d task %p\n",hdr->itt, task);
+			iser_task->status = ISER_TASK_STATUS_COMPLETED;
+			iser_task_rdma_finalize(iser_task);
+			iscsi2_put_task(task);
+		}
 	}
-
 	iser_dto_buffs_release(dto);
 
 	iscsi_iser_recv(conn->iscsi_conn, hdr, rx_data, rx_data_len);
@@ -592,6 +621,20 @@ void iser_rcv_completion(struct iser_desc *rx_desc,
 	 * parallel to the execution of iser_conn_term. So the code that waits *
 	 * for the posted rx bufs refcount to become zero handles everything   */
 	atomic_dec(&conn->ib_conn->post_recv_buf_count);
+
+	/*
+	 * if an unexpected PDU was received then the recv wr consumed must
+	 * be replaced, this is done in the next send of a control-type PDU
+	 */
+	if (opcode == ISCSI_OP_NOOP_IN && hdr->itt == RESERVED_ITT) {
+		/* nop-in with itt = 0xffffffff */
+		atomic_inc(&conn->ib_conn->unexpected_pdu_count);
+	}
+	else if (opcode == ISCSI_OP_ASYNC_EVENT) {
+		/* asyncronous message */
+		atomic_inc(&conn->ib_conn->unexpected_pdu_count);
+	}
+	/* a reject PDU consumes the recv buf posted for the response */
 }
 
 void iser_snd_completion(struct iser_desc *tx_desc)
@@ -600,7 +643,7 @@ void iser_snd_completion(struct iser_desc *tx_desc)
 	struct iser_conn       *ib_conn = dto->ib_conn;
 	struct iscsi_iser_conn *iser_conn = ib_conn->iser_conn;
 	struct iscsi_conn      *conn = iser_conn->iscsi_conn;
-	struct iscsi_mgmt_task *mtask;
+	struct iscsi_task *task;
 	int resume_tx = 0;
 
 	iser_dbg("Initiator, Data sent dto=0x%p\n", dto);
@@ -618,41 +661,36 @@ 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);
+		iscsi_conn_queue_work(conn);
 	}
 
 	if (tx_desc->type == ISCSI_TX_CONTROL) {
 		/* this arithmetic is legal by libiscsi dd_data allocation */
-		mtask = (void *) ((long)(void *)tx_desc -
-				  sizeof(struct iscsi_mgmt_task));
-		if (mtask->hdr->itt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
-			struct iscsi_session *session = conn->session;
-
-			spin_lock(&conn->session->lock);
-			iscsi_free_mgmt_task(conn, mtask);
-			spin_unlock(&session->lock);
-		}
+		task = (void *) ((long)(void *)tx_desc -
+				  sizeof(struct iscsi_task));
+		if (task->hdr->itt == RESERVED_ITT)
+			iscsi2_put_task(task);
 	}
 }
 
-void iser_ctask_rdma_init(struct iscsi_iser_cmd_task *iser_ctask)
+void iser_task_rdma_init(struct iscsi_iser_task *iser_task)
 
 {
-	iser_ctask->status = ISER_TASK_STATUS_INIT;
+	iser_task->status = ISER_TASK_STATUS_INIT;
 
-	iser_ctask->dir[ISER_DIR_IN] = 0;
-	iser_ctask->dir[ISER_DIR_OUT] = 0;
+	iser_task->dir[ISER_DIR_IN] = 0;
+	iser_task->dir[ISER_DIR_OUT] = 0;
 
-	iser_ctask->data[ISER_DIR_IN].data_len  = 0;
-	iser_ctask->data[ISER_DIR_OUT].data_len = 0;
+	iser_task->data[ISER_DIR_IN].data_len  = 0;
+	iser_task->data[ISER_DIR_OUT].data_len = 0;
 
-	memset(&iser_ctask->rdma_regd[ISER_DIR_IN], 0,
+	memset(&iser_task->rdma_regd[ISER_DIR_IN], 0,
 	       sizeof(struct iser_regd_buf));
-	memset(&iser_ctask->rdma_regd[ISER_DIR_OUT], 0,
+	memset(&iser_task->rdma_regd[ISER_DIR_OUT], 0,
 	       sizeof(struct iser_regd_buf));
 }
 
-void iser_ctask_rdma_finalize(struct iscsi_iser_cmd_task *iser_ctask)
+void iser_task_rdma_finalize(struct iscsi_iser_task *iser_task)
 {
 	int deferred;
 	int is_rdma_aligned = 1;
@@ -661,17 +699,17 @@ void iser_ctask_rdma_finalize(struct iscsi_iser_cmd_task *iser_ctask)
 	/* if we were reading, copy back to unaligned sglist,
 	 * anyway dma_unmap and free the copy
 	 */
-	if (iser_ctask->data_copy[ISER_DIR_IN].copy_buf != NULL) {
+	if (iser_task->data_copy[ISER_DIR_IN].copy_buf != NULL) {
 		is_rdma_aligned = 0;
-		iser_finalize_rdma_unaligned_sg(iser_ctask, ISER_DIR_IN);
+		iser_finalize_rdma_unaligned_sg(iser_task, ISER_DIR_IN);
 	}
-	if (iser_ctask->data_copy[ISER_DIR_OUT].copy_buf != NULL) {
+	if (iser_task->data_copy[ISER_DIR_OUT].copy_buf != NULL) {
 		is_rdma_aligned = 0;
-		iser_finalize_rdma_unaligned_sg(iser_ctask, ISER_DIR_OUT);
+		iser_finalize_rdma_unaligned_sg(iser_task, ISER_DIR_OUT);
 	}
 
-	if (iser_ctask->dir[ISER_DIR_IN]) {
-		regd = &iser_ctask->rdma_regd[ISER_DIR_IN];
+	if (iser_task->dir[ISER_DIR_IN]) {
+		regd = &iser_task->rdma_regd[ISER_DIR_IN];
 		deferred = iser_regd_buff_release(regd);
 		if (deferred) {
 			iser_err("%d references remain for BUF-IN rdma reg\n",
@@ -679,8 +717,8 @@ void iser_ctask_rdma_finalize(struct iscsi_iser_cmd_task *iser_ctask)
 		}
 	}
 
-	if (iser_ctask->dir[ISER_DIR_OUT]) {
-		regd = &iser_ctask->rdma_regd[ISER_DIR_OUT];
+	if (iser_task->dir[ISER_DIR_OUT]) {
+		regd = &iser_task->rdma_regd[ISER_DIR_OUT];
 		deferred = iser_regd_buff_release(regd);
 		if (deferred) {
 			iser_err("%d references remain for BUF-OUT rdma reg\n",
@@ -690,7 +728,7 @@ void iser_ctask_rdma_finalize(struct iscsi_iser_cmd_task *iser_ctask)
 
        /* if the data was unaligned, it was already unmapped and then copied */
        if (is_rdma_aligned)
-		iser_dma_unmap_task_data(iser_ctask);
+		iser_dma_unmap_task_data(iser_task);
 }
 
 void iser_dto_buffs_release(struct iser_dto *dto)
diff --git a/drivers/infiniband/ulp/iser/iser_memory.c b/drivers/infiniband/ulp/iser/iser_memory.c
index e5a1091..b9453d0 100644
--- a/drivers/infiniband/ulp/iser/iser_memory.c
+++ b/drivers/infiniband/ulp/iser/iser_memory.c
@@ -28,15 +28,12 @@
  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
- *
- * $Id: iser_memory.c 6964 2006-05-07 11:11:43Z ogerlitz $
  */
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/slab.h>
 #include <linux/mm.h>
-#include <asm/io.h>
-#include <asm/scatterlist.h>
+#include <linux/highmem.h>
 #include <linux/scatterlist.h>
 
 #include "iscsi_iser.h"
@@ -51,7 +48,7 @@
  */
 int iser_regd_buff_release(struct iser_regd_buf *regd_buf)
 {
-	struct device *dma_device;
+	struct ib_device *dev;
 
 	if ((atomic_read(&regd_buf->ref_count) == 0) ||
 	    atomic_dec_and_test(&regd_buf->ref_count)) {
@@ -60,8 +57,8 @@ int iser_regd_buff_release(struct iser_regd_buf *regd_buf)
 			iser_unreg_mem(&regd_buf->reg);
 
 		if (regd_buf->dma_addr) {
-			dma_device = regd_buf->device->ib_device->dma_device;
-			dma_unmap_single(dma_device,
+			dev = regd_buf->device->ib_device;
+			ib_dma_unmap_single(dev,
 					 regd_buf->dma_addr,
 					 regd_buf->data_size,
 					 regd_buf->direction);
@@ -83,12 +80,12 @@ void iser_reg_single(struct iser_device *device,
 		     struct iser_regd_buf *regd_buf,
 		     enum dma_data_direction direction)
 {
-	dma_addr_t dma_addr;
+	u64 dma_addr;
 
-	dma_addr  = dma_map_single(device->ib_device->dma_device,
-				   regd_buf->virt_addr,
-				   regd_buf->data_size, direction);
-	BUG_ON(dma_mapping_error(dma_addr));
+	dma_addr = ib_dma_map_single(device->ib_device,
+				     regd_buf->virt_addr,
+				     regd_buf->data_size, direction);
+	BUG_ON(ib_dma_mapping_error(device->ib_device, dma_addr));
 
 	regd_buf->reg.lkey = device->mr->lkey;
 	regd_buf->reg.len  = regd_buf->data_size;
@@ -102,18 +99,18 @@ void iser_reg_single(struct iser_device *device,
 /**
  * iser_start_rdma_unaligned_sg
  */
-int iser_start_rdma_unaligned_sg(struct iscsi_iser_cmd_task  *iser_ctask,
-				 enum iser_data_dir cmd_dir)
+static int iser_start_rdma_unaligned_sg(struct iscsi_iser_task *iser_task,
+					enum iser_data_dir cmd_dir)
 {
 	int dma_nents;
-	struct device *dma_device;
+	struct ib_device *dev;
 	char *mem = NULL;
-	struct iser_data_buf *data = &iser_ctask->data[cmd_dir];
+	struct iser_data_buf *data = &iser_task->data[cmd_dir];
 	unsigned long  cmd_data_len = data->data_len;
 
 	if (cmd_data_len > ISER_KMALLOC_THRESHOLD)
 		mem = (void *)__get_free_pages(GFP_NOIO,
-		      long_log2(roundup_pow_of_two(cmd_data_len)) - PAGE_SHIFT);
+		      ilog2(roundup_pow_of_two(cmd_data_len)) - PAGE_SHIFT);
 	else
 		mem = kmalloc(cmd_data_len, GFP_NOIO);
 
@@ -125,67 +122,61 @@ int iser_start_rdma_unaligned_sg(struct iscsi_iser_cmd_task  *iser_ctask,
 
 	if (cmd_dir == ISER_DIR_OUT) {
 		/* copy the unaligned sg the buffer which is used for RDMA */
-		struct scatterlist *sg = (struct scatterlist *)data->buf;
+		struct scatterlist *sgl = (struct scatterlist *)data->buf;
+		struct scatterlist *sg;
 		int i;
 		char *p, *from;
 
-		for (p = mem, i = 0; i < data->size; i++) {
-			from = kmap_atomic(sg[i].page, KM_USER0);
+		p = mem;
+		for_each_sg(sgl, sg, data->size, i) {
+			from = kmap_atomic(sg_page(sg), KM_USER0);
 			memcpy(p,
-			       from + sg[i].offset,
-			       sg[i].length);
+			       from + sg->offset,
+			       sg->length);
 			kunmap_atomic(from, KM_USER0);
-			p += sg[i].length;
+			p += sg->length;
 		}
 	}
 
-	sg_init_one(&iser_ctask->data_copy[cmd_dir].sg_single, mem, cmd_data_len);
-	iser_ctask->data_copy[cmd_dir].buf  =
-		&iser_ctask->data_copy[cmd_dir].sg_single;
-	iser_ctask->data_copy[cmd_dir].size = 1;
-
-	iser_ctask->data_copy[cmd_dir].copy_buf  = mem;
+	sg_init_one(&iser_task->data_copy[cmd_dir].sg_single, mem, cmd_data_len);
+	iser_task->data_copy[cmd_dir].buf  =
+		&iser_task->data_copy[cmd_dir].sg_single;
+	iser_task->data_copy[cmd_dir].size = 1;
 
-	dma_device = iser_ctask->iser_conn->ib_conn->device->ib_device->dma_device;
-
-	if (cmd_dir == ISER_DIR_OUT)
-		dma_nents = dma_map_sg(dma_device,
-				       &iser_ctask->data_copy[cmd_dir].sg_single,
-				       1, DMA_TO_DEVICE);
-	else
-		dma_nents = dma_map_sg(dma_device,
-				       &iser_ctask->data_copy[cmd_dir].sg_single,
-				       1, DMA_FROM_DEVICE);
+	iser_task->data_copy[cmd_dir].copy_buf  = mem;
 
+	dev = iser_task->iser_conn->ib_conn->device->ib_device;
+	dma_nents = ib_dma_map_sg(dev,
+				  &iser_task->data_copy[cmd_dir].sg_single,
+				  1,
+				  (cmd_dir == ISER_DIR_OUT) ?
+				  DMA_TO_DEVICE : DMA_FROM_DEVICE);
 	BUG_ON(dma_nents == 0);
 
-	iser_ctask->data_copy[cmd_dir].dma_nents = dma_nents;
+	iser_task->data_copy[cmd_dir].dma_nents = dma_nents;
 	return 0;
 }
 
 /**
  * iser_finalize_rdma_unaligned_sg
  */
-void iser_finalize_rdma_unaligned_sg(struct iscsi_iser_cmd_task *iser_ctask,
+void iser_finalize_rdma_unaligned_sg(struct iscsi_iser_task *iser_task,
 				     enum iser_data_dir         cmd_dir)
 {
-	struct device *dma_device;
+	struct ib_device *dev;
 	struct iser_data_buf *mem_copy;
 	unsigned long  cmd_data_len;
 
-	dma_device = iser_ctask->iser_conn->ib_conn->device->ib_device->dma_device;
-	mem_copy   = &iser_ctask->data_copy[cmd_dir];
+	dev = iser_task->iser_conn->ib_conn->device->ib_device;
+	mem_copy = &iser_task->data_copy[cmd_dir];
 
-	if (cmd_dir == ISER_DIR_OUT)
-		dma_unmap_sg(dma_device, &mem_copy->sg_single, 1,
-			     DMA_TO_DEVICE);
-	else
-		dma_unmap_sg(dma_device, &mem_copy->sg_single, 1,
-			     DMA_FROM_DEVICE);
+	ib_dma_unmap_sg(dev, &mem_copy->sg_single, 1,
+			(cmd_dir == ISER_DIR_OUT) ?
+			DMA_TO_DEVICE : DMA_FROM_DEVICE);
 
 	if (cmd_dir == ISER_DIR_IN) {
 		char *mem;
-		struct scatterlist *sg;
+		struct scatterlist *sgl, *sg;
 		unsigned char *p, *to;
 		unsigned int sg_size;
 		int i;
@@ -193,24 +184,25 @@ void iser_finalize_rdma_unaligned_sg(struct iscsi_iser_cmd_task *iser_ctask,
 		/* copy back read RDMA to unaligned sg */
 		mem	= mem_copy->copy_buf;
 
-		sg	= (struct scatterlist *)iser_ctask->data[ISER_DIR_IN].buf;
-		sg_size = iser_ctask->data[ISER_DIR_IN].size;
+		sgl	= (struct scatterlist *)iser_task->data[ISER_DIR_IN].buf;
+		sg_size = iser_task->data[ISER_DIR_IN].size;
 
-		for (p = mem, i = 0; i < sg_size; i++){
-			to = kmap_atomic(sg[i].page, KM_SOFTIRQ0);
-			memcpy(to + sg[i].offset,
+		p = mem;
+		for_each_sg(sgl, sg, sg_size, i) {
+			to = kmap_atomic(sg_page(sg), KM_SOFTIRQ0);
+			memcpy(to + sg->offset,
 			       p,
-			       sg[i].length);
+			       sg->length);
 			kunmap_atomic(to, KM_SOFTIRQ0);
-			p += sg[i].length;
+			p += sg->length;
 		}
 	}
 
-	cmd_data_len = iser_ctask->data[cmd_dir].data_len;
+	cmd_data_len = iser_task->data[cmd_dir].data_len;
 
 	if (cmd_data_len > ISER_KMALLOC_THRESHOLD)
 		free_pages((unsigned long)mem_copy->copy_buf,
-			   long_log2(roundup_pow_of_two(cmd_data_len)) - PAGE_SHIFT);
+			   ilog2(roundup_pow_of_two(cmd_data_len)) - PAGE_SHIFT);
 	else
 		kfree(mem_copy->copy_buf);
 
@@ -230,31 +222,37 @@ void iser_finalize_rdma_unaligned_sg(struct iscsi_iser_cmd_task *iser_ctask,
  * consecutive elements. Also, it handles one entry SG.
  */
 static int iser_sg_to_page_vec(struct iser_data_buf *data,
-			       struct iser_page_vec *page_vec)
+			       struct iser_page_vec *page_vec,
+			       struct ib_device *ibdev)
 {
-	struct scatterlist *sg = (struct scatterlist *)data->buf;
-	dma_addr_t first_addr, last_addr, page;
+	struct scatterlist *sgl = (struct scatterlist *)data->buf;
+	struct scatterlist *sg;
+	u64 first_addr, last_addr, page;
 	int end_aligned;
 	unsigned int cur_page = 0;
 	unsigned long total_sz = 0;
 	int i;
 
 	/* compute the offset of first element */
-	page_vec->offset = (u64) sg[0].offset & ~MASK_4K;
+	page_vec->offset = (u64) sgl[0].offset & ~MASK_4K;
+
+	for_each_sg(sgl, sg, data->dma_nents, i) {
+		unsigned int dma_len = ib_sg_dma_len(ibdev, sg);
 
-	for (i = 0; i < data->dma_nents; i++) {
-		total_sz += sg_dma_len(&sg[i]);
+		total_sz += dma_len;
 
-		first_addr = sg_dma_address(&sg[i]);
-		last_addr  = first_addr + sg_dma_len(&sg[i]);
+		first_addr = ib_sg_dma_address(ibdev, sg);
+		last_addr  = first_addr + dma_len;
 
 		end_aligned   = !(last_addr  & ~MASK_4K);
 
 		/* continue to collect page fragments till aligned or SG ends */
 		while (!end_aligned && (i + 1 < data->dma_nents)) {
+			sg = sg_next(sg);
 			i++;
-			total_sz += sg_dma_len(&sg[i]);
-			last_addr = sg_dma_address(&sg[i]) + sg_dma_len(&sg[i]);
+			dma_len = ib_sg_dma_len(ibdev, sg);
+			total_sz += dma_len;
+			last_addr = ib_sg_dma_address(ibdev, sg) + dma_len;
 			end_aligned = !(last_addr  & ~MASK_4K);
 		}
 
@@ -286,35 +284,39 @@ static int iser_sg_to_page_vec(struct iser_data_buf *data,
  * the number of entries which are aligned correctly. Supports the case where
  * consecutive SG elements are actually fragments of the same physcial page.
  */
-static unsigned int iser_data_buf_aligned_len(struct iser_data_buf *data)
+static unsigned int iser_data_buf_aligned_len(struct iser_data_buf *data,
+					      struct ib_device *ibdev)
 {
-	struct scatterlist *sg;
-	dma_addr_t end_addr, next_addr;
+	struct scatterlist *sgl, *sg;
+	u64 end_addr, next_addr;
 	int i, cnt;
 	unsigned int ret_len = 0;
 
-	sg = (struct scatterlist *)data->buf;
+	sgl = (struct scatterlist *)data->buf;
 
-	for (cnt = 0, i = 0; i < data->dma_nents; i++, cnt++) {
+	cnt = 0;
+	for_each_sg(sgl, sg, data->dma_nents, i) {
 		/* iser_dbg("Checking sg iobuf [%d]: phys=0x%08lX "
 		   "offset: %ld sz: %ld\n", i,
-		   (unsigned long)page_to_phys(sg[i].page),
-		   (unsigned long)sg[i].offset,
-		   (unsigned long)sg[i].length); */
-		end_addr = sg_dma_address(&sg[i]) +
-			   sg_dma_len(&sg[i]);
+		   (unsigned long)sg_phys(sg),
+		   (unsigned long)sg->offset,
+		   (unsigned long)sg->length); */
+		end_addr = ib_sg_dma_address(ibdev, sg) +
+			   ib_sg_dma_len(ibdev, sg);
 		/* iser_dbg("Checking sg iobuf end address "
 		       "0x%08lX\n", end_addr); */
 		if (i + 1 < data->dma_nents) {
-			next_addr = sg_dma_address(&sg[i+1]);
+			next_addr = ib_sg_dma_address(ibdev, sg_next(sg));
 			/* are i, i+1 fragments of the same page? */
-			if (end_addr == next_addr)
+			if (end_addr == next_addr) {
+				cnt++;
 				continue;
-			else if (!IS_4K_ALIGNED(end_addr)) {
+			} else if (!IS_4K_ALIGNED(end_addr)) {
 				ret_len = cnt + 1;
 				break;
 			}
 		}
+		cnt++;
 	}
 	if (i == data->dma_nents)
 		ret_len = cnt;	/* loop ended */
@@ -323,17 +325,22 @@ static unsigned int iser_data_buf_aligned_len(struct iser_data_buf *data)
 	return ret_len;
 }
 
-static void iser_data_buf_dump(struct iser_data_buf *data)
+static void iser_data_buf_dump(struct iser_data_buf *data,
+			       struct ib_device *ibdev)
 {
-	struct scatterlist *sg = (struct scatterlist *)data->buf;
+	struct scatterlist *sgl = (struct scatterlist *)data->buf;
+	struct scatterlist *sg;
 	int i;
 
-	for (i = 0; i < data->dma_nents; i++)
-		iser_err("sg[%d] dma_addr:0x%lX page:0x%p "
+	if (iser_debug_level == 0)
+		return;
+
+	for_each_sg(sgl, sg, data->dma_nents, i)
+		iser_warn("sg[%d] dma_addr:0x%lX page:0x%p "
 			 "off:0x%x sz:0x%x dma_len:0x%x\n",
-			 i, (unsigned long)sg_dma_address(&sg[i]),
-			 sg[i].page, sg[i].offset,
-			 sg[i].length,sg_dma_len(&sg[i]));
+			 i, (unsigned long)ib_sg_dma_address(ibdev, sg),
+			 sg_page(sg), sg->offset,
+			 sg->length, ib_sg_dma_len(ibdev, sg));
 }
 
 static void iser_dump_page_vec(struct iser_page_vec *page_vec)
@@ -347,7 +354,8 @@ static void iser_dump_page_vec(struct iser_page_vec *page_vec)
 }
 
 static void iser_page_vec_build(struct iser_data_buf *data,
-				struct iser_page_vec *page_vec)
+				struct iser_page_vec *page_vec,
+				struct ib_device *ibdev)
 {
 	int page_vec_len = 0;
 
@@ -355,31 +363,30 @@ static void iser_page_vec_build(struct iser_data_buf *data,
 	page_vec->offset = 0;
 
 	iser_dbg("Translating sg sz: %d\n", data->dma_nents);
-	page_vec_len = iser_sg_to_page_vec(data,page_vec);
+	page_vec_len = iser_sg_to_page_vec(data, page_vec, ibdev);
 	iser_dbg("sg len %d page_vec_len %d\n", data->dma_nents,page_vec_len);
 
 	page_vec->length = page_vec_len;
 
 	if (page_vec_len * SIZE_4K < page_vec->data_size) {
 		iser_err("page_vec too short to hold this SG\n");
-		iser_data_buf_dump(data);
+		iser_data_buf_dump(data, ibdev);
 		iser_dump_page_vec(page_vec);
 		BUG();
 	}
 }
 
-int iser_dma_map_task_data(struct iscsi_iser_cmd_task *iser_ctask,
-			    struct iser_data_buf       *data,
-			    enum   iser_data_dir       iser_dir,
-			    enum   dma_data_direction  dma_dir)
+int iser_dma_map_task_data(struct iscsi_iser_task *iser_task,
+			    struct iser_data_buf *data,
+			    enum iser_data_dir iser_dir,
+			    enum dma_data_direction dma_dir)
 {
-	struct device *dma_device;
+	struct ib_device *dev;
 
-	iser_ctask->dir[iser_dir] = 1;
-	dma_device =
-		iser_ctask->iser_conn->ib_conn->device->ib_device->dma_device;
+	iser_task->dir[iser_dir] = 1;
+	dev = iser_task->iser_conn->ib_conn->device->ib_device;
 
-	data->dma_nents = dma_map_sg(dma_device, data->buf, data->size, dma_dir);
+	data->dma_nents = ib_dma_map_sg(dev, data->buf, data->size, dma_dir);
 	if (data->dma_nents == 0) {
 		iser_err("dma_map_sg failed!!!\n");
 		return -EINVAL;
@@ -387,22 +394,21 @@ int iser_dma_map_task_data(struct iscsi_iser_cmd_task *iser_ctask,
 	return 0;
 }
 
-void iser_dma_unmap_task_data(struct iscsi_iser_cmd_task *iser_ctask)
+void iser_dma_unmap_task_data(struct iscsi_iser_task *iser_task)
 {
-	struct device  *dma_device;
+	struct ib_device *dev;
 	struct iser_data_buf *data;
 
-	dma_device =
-		iser_ctask->iser_conn->ib_conn->device->ib_device->dma_device;
+	dev = iser_task->iser_conn->ib_conn->device->ib_device;
 
-	if (iser_ctask->dir[ISER_DIR_IN]) {
-		data = &iser_ctask->data[ISER_DIR_IN];
-		dma_unmap_sg(dma_device, data->buf, data->size, DMA_FROM_DEVICE);
+	if (iser_task->dir[ISER_DIR_IN]) {
+		data = &iser_task->data[ISER_DIR_IN];
+		ib_dma_unmap_sg(dev, data->buf, data->size, DMA_FROM_DEVICE);
 	}
 
-	if (iser_ctask->dir[ISER_DIR_OUT]) {
-		data = &iser_ctask->data[ISER_DIR_OUT];
-		dma_unmap_sg(dma_device, data->buf, data->size, DMA_TO_DEVICE);
+	if (iser_task->dir[ISER_DIR_OUT]) {
+		data = &iser_task->data[ISER_DIR_OUT];
+		ib_dma_unmap_sg(dev, data->buf, data->size, DMA_TO_DEVICE);
 	}
 }
 
@@ -412,34 +418,37 @@ void iser_dma_unmap_task_data(struct iscsi_iser_cmd_task *iser_ctask)
  *
  * returns 0 on success, errno code on failure
  */
-int iser_reg_rdma_mem(struct iscsi_iser_cmd_task *iser_ctask,
+int iser_reg_rdma_mem(struct iscsi_iser_task *iser_task,
 		      enum   iser_data_dir        cmd_dir)
 {
-	struct iser_conn     *ib_conn = iser_ctask->iser_conn->ib_conn;
+	struct iscsi_conn    *iscsi_conn = iser_task->iser_conn->iscsi_conn;
+	struct iser_conn     *ib_conn = iser_task->iser_conn->ib_conn;
 	struct iser_device   *device = ib_conn->device;
-	struct iser_data_buf *mem = &iser_ctask->data[cmd_dir];
+	struct ib_device     *ibdev = device->ib_device;
+	struct iser_data_buf *mem = &iser_task->data[cmd_dir];
 	struct iser_regd_buf *regd_buf;
 	int aligned_len;
 	int err;
 	int i;
 	struct scatterlist *sg;
 
-	regd_buf = &iser_ctask->rdma_regd[cmd_dir];
+	regd_buf = &iser_task->rdma_regd[cmd_dir];
 
-	aligned_len = iser_data_buf_aligned_len(mem);
+	aligned_len = iser_data_buf_aligned_len(mem, ibdev);
 	if (aligned_len != mem->dma_nents) {
-		iser_err("rdma alignment violation %d/%d aligned\n",
+		iscsi_conn->fmr_unalign_cnt++;
+		iser_warn("rdma alignment violation %d/%d aligned\n",
 			 aligned_len, mem->size);
-		iser_data_buf_dump(mem);
+		iser_data_buf_dump(mem, ibdev);
 
 		/* unmap the command data before accessing it */
-		iser_dma_unmap_task_data(iser_ctask);
+		iser_dma_unmap_task_data(iser_task);
 
 		/* allocate copy buf, if we are writing, copy the */
 		/* unaligned scatterlist, dma map the copy        */
-		if (iser_start_rdma_unaligned_sg(iser_ctask, cmd_dir) != 0)
+		if (iser_start_rdma_unaligned_sg(iser_task, cmd_dir) != 0)
 				return -ENOMEM;
-		mem = &iser_ctask->data_copy[cmd_dir];
+		mem = &iser_task->data_copy[cmd_dir];
 	}
 
 	/* if there a single dma entry, FMR is not needed */
@@ -448,8 +457,8 @@ int iser_reg_rdma_mem(struct iscsi_iser_cmd_task *iser_ctask,
 
 		regd_buf->reg.lkey = device->mr->lkey;
 		regd_buf->reg.rkey = device->mr->rkey;
-		regd_buf->reg.len  = sg_dma_len(&sg[0]);
-		regd_buf->reg.va   = sg_dma_address(&sg[0]);
+		regd_buf->reg.len  = ib_sg_dma_len(ibdev, &sg[0]);
+		regd_buf->reg.va   = ib_sg_dma_address(ibdev, &sg[0]);
 		regd_buf->reg.is_fmr = 0;
 
 		iser_dbg("PHYSICAL Mem.register: lkey: 0x%08X rkey: 0x%08X  "
@@ -459,12 +468,13 @@ int iser_reg_rdma_mem(struct iscsi_iser_cmd_task *iser_ctask,
 			 (unsigned long)regd_buf->reg.va,
 			 (unsigned long)regd_buf->reg.len);
 	} else { /* use FMR for multiple dma entries */
-		iser_page_vec_build(mem, ib_conn->page_vec);
+		iser_page_vec_build(mem, ib_conn->page_vec, ibdev);
 		err = iser_reg_page_vec(ib_conn, ib_conn->page_vec, &regd_buf->reg);
 		if (err) {
-			iser_data_buf_dump(mem);
-			iser_err("mem->dma_nents = %d (dlength = 0x%x)\n", mem->dma_nents,
-				 ntoh24(iser_ctask->desc.iscsi_header.dlength));
+			iser_data_buf_dump(mem, ibdev);
+			iser_err("mem->dma_nents = %d (dlength = 0x%x)\n",
+				 mem->dma_nents,
+				 ntoh24(iser_task->desc.iscsi_header.dlength));
 			iser_err("page_vec: data_size = 0x%x, length = %d, offset = 0x%x\n",
 				 ib_conn->page_vec->data_size, ib_conn->page_vec->length,
 				 ib_conn->page_vec->offset);
diff --git a/drivers/infiniband/ulp/iser/iser_verbs.c b/drivers/infiniband/ulp/iser/iser_verbs.c
index 6587a47..8bf97ee 100644
--- a/drivers/infiniband/ulp/iser/iser_verbs.c
+++ b/drivers/infiniband/ulp/iser/iser_verbs.c
@@ -29,15 +29,10 @@
  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
- *
- * $Id: iser_verbs.c 7051 2006-05-10 12:29:11Z ogerlitz $
  */
-#include <asm/io.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
-#include <linux/smp_lock.h>
 #include <linux/delay.h>
-#include <linux/version.h>
 
 #include "iscsi_iser.h"
 
@@ -107,7 +102,7 @@ pd_err:
 }
 
 /**
- * iser_free_device_ib_res - destory/dealloc/dereg the DMA MR,
+ * iser_free_device_ib_res - destroy/dealloc/dereg the DMA MR,
  * CQ and PD created with the device associated with the adapator.
  */
 static void iser_free_device_ib_res(struct iser_device *device)
@@ -156,8 +151,8 @@ static int iser_create_ib_conn_res(struct iser_conn *ib_conn)
 	params.max_pages_per_fmr = ISCSI_ISER_SG_TABLESIZE + 1;
 	/* make the pool size twice the max number of SCSI commands *
 	 * the ML is expected to queue, watermark for unmap at 50%  */
-	params.pool_size	 = ISCSI_XMIT_CMDS_MAX * 2;
-	params.dirty_watermark	 = ISCSI_XMIT_CMDS_MAX;
+	params.pool_size	 = ISCSI_DEF_XMIT_CMDS_MAX * 2;
+	params.dirty_watermark	 = ISCSI_DEF_XMIT_CMDS_MAX;
 	params.cache		 = 0;
 	params.flush_function	 = NULL;
 	params.access		 = (IB_ACCESS_LOCAL_WRITE  |
@@ -239,36 +234,32 @@ static int iser_free_ib_conn_res(struct iser_conn *ib_conn)
 static
 struct iser_device *iser_device_find_by_ib_device(struct rdma_cm_id *cma_id)
 {
-	struct list_head    *p_list;
-	struct iser_device  *device = NULL;
+	struct iser_device *device;
 
 	mutex_lock(&ig.device_list_mutex);
 
-	p_list = ig.device_list.next;
-	while (p_list != &ig.device_list) {
-		device = list_entry(p_list, struct iser_device, ig_list);
+	list_for_each_entry(device, &ig.device_list, ig_list)
 		/* find if there's a match using the node GUID */
 		if (device->ib_device->node_guid == cma_id->device->node_guid)
-			break;
-	}
+			goto inc_refcnt;
 
-	if (device == NULL) {
-		device = kzalloc(sizeof *device, GFP_KERNEL);
-		if (device == NULL)
-			goto out;
-		/* assign this device to the device */
-		device->ib_device = cma_id->device;
-		/* init the device and link it into ig device list */
-		if (iser_create_device_ib_res(device)) {
-			kfree(device);
-			device = NULL;
-			goto out;
-		}
-		list_add(&device->ig_list, &ig.device_list);
+	device = kzalloc(sizeof *device, GFP_KERNEL);
+	if (device == NULL)
+		goto out;
+
+	/* assign this device to the device */
+	device->ib_device = cma_id->device;
+	/* init the device and link it into ig device list */
+	if (iser_create_device_ib_res(device)) {
+		kfree(device);
+		device = NULL;
+		goto out;
 	}
-out:
-	BUG_ON(device == NULL);
+	list_add(&device->ig_list, &ig.device_list);
+
+inc_refcnt:
 	device->refcount++;
+out:
 	mutex_unlock(&ig.device_list_mutex);
 	return device;
 }
@@ -312,6 +303,40 @@ static int iser_conn_state_comp_exch(struct iser_conn *ib_conn,
 }
 
 /**
+ * Frees all conn objects and deallocs conn descriptor
+ */
+static void iser_conn_release(struct iser_conn *ib_conn)
+{
+	struct iser_device  *device = ib_conn->device;
+
+	BUG_ON(ib_conn->state != ISER_CONN_DOWN);
+
+	mutex_lock(&ig.connlist_mutex);
+	list_del(&ib_conn->conn_list);
+	mutex_unlock(&ig.connlist_mutex);
+
+	iser_free_ib_conn_res(ib_conn);
+	ib_conn->device = NULL;
+	/* on EVENT_ADDR_ERROR there's no device yet for this conn */
+	if (device != NULL)
+		iser_device_try_release(device);
+	if (ib_conn->iser_conn)
+		ib_conn->iser_conn->ib_conn = NULL;
+	iscsi2_destroy_endpoint(ib_conn->ep);
+}
+
+void iser_conn_get(struct iser_conn *ib_conn)
+{
+	atomic_inc(&ib_conn->refcount);
+}
+
+void iser_conn_put(struct iser_conn *ib_conn)
+{
+	if (atomic_dec_and_test(&ib_conn->refcount))
+		iser_conn_release(ib_conn);
+}
+
+/**
  * triggers start of the disconnect procedures and wait for them to be done
  */
 void iser_conn_terminate(struct iser_conn *ib_conn)
@@ -332,7 +357,7 @@ void iser_conn_terminate(struct iser_conn *ib_conn)
 	wait_event_interruptible(ib_conn->wait,
 				 ib_conn->state == ISER_CONN_DOWN);
 
-	iser_conn_release(ib_conn);
+	iser_conn_put(ib_conn);
 }
 
 static void iser_connect_error(struct rdma_cm_id *cma_id)
@@ -351,6 +376,12 @@ static void iser_addr_handler(struct rdma_cm_id *cma_id)
 	int    ret;
 
 	device = iser_device_find_by_ib_device(cma_id);
+	if (!device) {
+		iser_err("device lookup/creation failed\n");
+		iser_connect_error(cma_id);
+		return;
+	}
+
 	ib_conn = (struct iser_conn *)cma_id->context;
 	ib_conn->device = device;
 
@@ -359,7 +390,6 @@ static void iser_addr_handler(struct rdma_cm_id *cma_id)
 		iser_err("resolve route failed: %d\n", ret);
 		iser_connect_error(cma_id);
 	}
-	return;
 }
 
 static void iser_route_handler(struct rdma_cm_id *cma_id)
@@ -371,13 +401,6 @@ static void iser_route_handler(struct rdma_cm_id *cma_id)
 	if (ret)
 		goto failure;
 
-	iser_dbg("path.mtu is %d setting it to %d\n",
-		 cma_id->route.path_rec->mtu, IB_MTU_1024);
-
-	/* we must set the MTU to 1024 as this is what the target is assuming */
-	if (cma_id->route.path_rec->mtu > IB_MTU_1024)
-		cma_id->route.path_rec->mtu = IB_MTU_1024;
-
 	memset(&conn_param, 0, sizeof conn_param);
 	conn_param.responder_resources = 4;
 	conn_param.initiator_depth     = 1;
@@ -415,7 +438,7 @@ static void iser_disconnected_handler(struct rdma_cm_id *cma_id)
 	 * terminated asynchronously from the iSCSI layer's perspective.  */
 	if (iser_conn_state_comp_exch(ib_conn, ISER_CONN_UP,
 				      ISER_CONN_TERMINATING))
-		iscsi_conn_failure(ib_conn->iser_conn->iscsi_conn,
+		iscsi2_conn_failure(ib_conn->iser_conn->iscsi_conn,
 				   ISCSI_ERR_CONN_FAILED);
 
 	/* Complete the termination process if no posts are pending */
@@ -451,39 +474,27 @@ static int iser_cma_handler(struct rdma_cm_id *cma_id, struct rdma_cm_event *eve
 		iser_connect_error(cma_id);
 		break;
 	case RDMA_CM_EVENT_DISCONNECTED:
-		iser_disconnected_handler(cma_id);
-		break;
 	case RDMA_CM_EVENT_DEVICE_REMOVAL:
-		BUG();
-		break;
-	case RDMA_CM_EVENT_CONNECT_RESPONSE:
-		BUG();
+	case RDMA_CM_EVENT_ADDR_CHANGE:
+		iser_disconnected_handler(cma_id);
 		break;
-	case RDMA_CM_EVENT_CONNECT_REQUEST:
 	default:
+		iser_err("Unexpected RDMA CM event (%d)\n", event->event);
 		break;
 	}
 	return ret;
 }
 
-int iser_conn_init(struct iser_conn **ibconn)
+void iser_conn_init(struct iser_conn *ib_conn)
 {
-	struct iser_conn *ib_conn;
-
-	ib_conn = kzalloc(sizeof *ib_conn, GFP_KERNEL);
-	if (!ib_conn) {
-		iser_err("can't alloc memory for struct iser_conn\n");
-		return -ENOMEM;
-	}
 	ib_conn->state = ISER_CONN_INIT;
 	init_waitqueue_head(&ib_conn->wait);
 	atomic_set(&ib_conn->post_recv_buf_count, 0);
 	atomic_set(&ib_conn->post_send_buf_count, 0);
+	atomic_set(&ib_conn->unexpected_pdu_count, 0);
+	atomic_set(&ib_conn->refcount, 1);
 	INIT_LIST_HEAD(&ib_conn->conn_list);
 	spin_lock_init(&ib_conn->lock);
-
-	*ibconn = ib_conn;
-	return 0;
 }
 
  /**
@@ -498,14 +509,14 @@ int iser_connect(struct iser_conn   *ib_conn,
 	struct sockaddr *src, *dst;
 	int err = 0;
 
-	sprintf(ib_conn->name,"%d.%d.%d.%d:%d",
-		NIPQUAD(dst_addr->sin_addr.s_addr), dst_addr->sin_port);
+	sprintf(ib_conn->name, "%pI4:%d",
+		&dst_addr->sin_addr.s_addr, dst_addr->sin_port);
 
 	/* the device is known only --after-- address resolution */
 	ib_conn->device = NULL;
 
-	iser_err("connecting to: %d.%d.%d.%d, port 0x%x\n",
-		 NIPQUAD(dst_addr->sin_addr), dst_addr->sin_port);
+	iser_err("connecting to: %pI4, port 0x%x\n",
+		 &dst_addr->sin_addr, dst_addr->sin_port);
 
 	ib_conn->state = ISER_CONN_PENDING;
 
@@ -551,30 +562,6 @@ connect_failure:
 }
 
 /**
- * Frees all conn objects and deallocs conn descriptor
- */
-void iser_conn_release(struct iser_conn *ib_conn)
-{
-	struct iser_device  *device = ib_conn->device;
-
-	BUG_ON(ib_conn->state != ISER_CONN_DOWN);
-
-	mutex_lock(&ig.connlist_mutex);
-	list_del(&ib_conn->conn_list);
-	mutex_unlock(&ig.connlist_mutex);
-
-	iser_free_ib_conn_res(ib_conn);
-	ib_conn->device = NULL;
-	/* on EVENT_ADDR_ERROR there's no device yet for this conn */
-	if (device != NULL)
-		iser_device_try_release(device);
-	if (ib_conn->iser_conn)
-		ib_conn->iser_conn->ib_conn = NULL;
-	kfree(ib_conn);
-}
-
-
-/**
  * iser_reg_page_vec - Register physical memory
  *
  * returns: 0 on success, errno code on failure
@@ -773,17 +760,17 @@ static void iser_handle_comp_error(struct iser_desc *desc)
 		/* getting here when the state is UP means that the conn is *
 		 * being terminated asynchronously from the iSCSI layer's   *
 		 * perspective.                                             */
-	        if (iser_conn_state_comp_exch(ib_conn, ISER_CONN_UP,
-        	                              ISER_CONN_TERMINATING))
-                	iscsi_conn_failure(ib_conn->iser_conn->iscsi_conn,
-                        	                ISCSI_ERR_CONN_FAILED);
-
-	        /* complete the termination process if disconnect event was        *
-		 * delivered. note there are no more non completed posts to the QP */
-	        if (ib_conn->disc_evt_flag) {
-        	        ib_conn->state = ISER_CONN_DOWN;
-                	wake_up_interruptible(&ib_conn->wait);
-	        }
+		if (iser_conn_state_comp_exch(ib_conn, ISER_CONN_UP,
+		    ISER_CONN_TERMINATING))
+			iscsi2_conn_failure(ib_conn->iser_conn->iscsi_conn,
+					   ISCSI_ERR_CONN_FAILED);
+
+		/* complete the termination process if disconnect event was delivered *
+		 * note there are no more non completed posts to the QP               */
+		if (ib_conn->disc_evt_flag) {
+			ib_conn->state = ISER_CONN_DOWN;
+			wake_up_interruptible(&ib_conn->wait);
+		}
 	}
 }
 
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index de3be26..62fdcf9 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -246,6 +246,29 @@ config SCSI_ISCSI_ATTRS
 	  each attached iSCSI device to sysfs, say Y.
 	  Otherwise, say N.
 
+config SCSI_ISCSI1_ATTRS
+	tristate "iSCSI Transport Attributes"
+	depends on SCSI && NET
+	select SCSI_ISCSI_ATTRS
+	help
+	  v1 of the iSCSI attrs.
+
+	  If you wish to export transport-specific information about
+	  each attached iSCSI device to sysfs, say Y.
+	  Otherwise, say N.
+
+config SCSI_ISCSI2_ATTRS
+	tristate "iSCSI Transport Attributes v2"
+	depends on SCSI && NET
+	select SCSI_ISCSI_ATTRS
+	help
+	  v2 of the iSCSI attrs supports iSCSI offload cards such
+	  as Broadcom bnx2i and Chelsio cxgb3i.
+
+	  If you wish to export transport-specific information about
+	  each attached iSCSI device to sysfs, say Y.
+	  Otherwise, say N.
+
 config SCSI_SAS_ATTRS
 	tristate "SAS Transport Attributes"
 	depends on SCSI
@@ -280,13 +303,20 @@ config FCOE
 	---help---
 	  Fibre Channel over Ethernet module
 
+config LIBISCSI1
+	tristate "iSCSI lib compat"
+	depends on SCSI && NET
+	select SCSI_ISCSI1_ATTRS
+	help
+	  v1 of the iSCSI library.
+
 config ISCSI_TCP
 	tristate "iSCSI Initiator over TCP/IP"
 	depends on SCSI && INET
 	select CRYPTO
 	select CRYPTO_MD5
 	select CRYPTO_CRC32C
-	select SCSI_ISCSI_ATTRS
+	select SCSI_ISCSI2_ATTRS
 	help
 	 The iSCSI Driver provides a host with the ability to access storage
 	 through an IP network. The driver uses the iSCSI protocol to transport
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 464d272..4404160 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -31,6 +31,8 @@ obj-$(CONFIG_RAID_ATTRS)	+= raid_class.o
 obj-$(CONFIG_SCSI_SPI_ATTRS)	+= scsi_transport_spi.o
 obj-$(CONFIG_SCSI_FC_ATTRS) 	+= scsi_transport_fc.o
 obj-$(CONFIG_SCSI_ISCSI_ATTRS)	+= scsi_transport_iscsi.o
+obj-$(CONFIG_SCSI_ISCSI1_ATTRS)	+= scsi_transport_iscsi1.o
+obj-$(CONFIG_SCSI_ISCSI2_ATTRS)	+= scsi_transport_iscsi2.o
 obj-$(CONFIG_SCSI_SAS_ATTRS)	+= scsi_transport_sas.o
 obj-$(CONFIG_SCSI_SAS_LIBSAS)	+= libsas/
 obj-y				+= device_handler/
@@ -38,8 +40,10 @@ obj-y				+= device_handler/
 obj-$(CONFIG_LIBFC)		+= libfc/
 obj-$(CONFIG_FCOE)		+= fcoe/
 obj-$(CONFIG_FCOE_FNIC)		+= fnic/
-obj-$(CONFIG_ISCSI_TCP) 	+= libiscsi.o	iscsi_tcp.o
-obj-$(CONFIG_INFINIBAND_ISER) 	+= libiscsi.o
+obj-$(CONFIG_LIBISCSI1)		+= libiscsi1.o
+libiscsi1-objs			:= libiscsi.o
+obj-$(CONFIG_ISCSI_TCP) 	+= libiscsi2.o	libiscsi_tcp.o iscsi_tcp.o
+obj-$(CONFIG_INFINIBAND_ISER) 	+= libiscsi2.o
 obj-$(CONFIG_SCSI_AMIGA7XX)	+= amiga7xx.o	53c7xx.o
 obj-$(CONFIG_A3000_SCSI)	+= a3000.o	wd33c93.o
 obj-$(CONFIG_A2091_SCSI)	+= a2091.o	wd33c93.o
diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
index e7a7978..229bffc 100644
--- a/drivers/scsi/iscsi_tcp.c
+++ b/drivers/scsi/iscsi_tcp.c
@@ -25,9 +25,9 @@
  *	Arne Redlich
  *	Zhenyu Wang
  */
-
+#include <linux/version.h>
+#include <linux/kernel.h>
 #include <linux/types.h>
-#include <linux/list.h>
 #include <linux/inet.h>
 #include <linux/file.h>
 #include <linux/blkdev.h>
@@ -40,973 +40,95 @@
 #include <scsi/scsi_device.h>
 #include <scsi/scsi_host.h>
 #include <scsi/scsi.h>
-#include <scsi/scsi_transport_iscsi.h>
+#include <scsi/scsi_transport_iscsi2.h>
 
 #include "iscsi_tcp.h"
 
-MODULE_AUTHOR("Dmitry Yusupov <dmitry_yus@yahoo.com>, "
+MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, "
+	      "Dmitry Yusupov <dmitry_yus@yahoo.com>, "
 	      "Alex Aizman <itn780@yahoo.com>");
 MODULE_DESCRIPTION("iSCSI/TCP data-path");
 MODULE_LICENSE("GPL");
-/* #define DEBUG_TCP */
-#define DEBUG_ASSERT
-
-#ifdef DEBUG_TCP
-#define debug_tcp(fmt...) printk(KERN_INFO "tcp: " fmt)
-#else
-#define debug_tcp(fmt...)
-#endif
-
-#ifndef DEBUG_ASSERT
-#ifdef BUG_ON
-#undef BUG_ON
-#endif
-#define BUG_ON(expr)
-#endif
+
+static struct scsi_transport_template *iscsi_sw_tcp_scsi_transport;
+static struct scsi_host_template iscsi_sw_tcp_sht;
+static struct iscsi_transport iscsi_sw_tcp_transport;
 
 static unsigned int iscsi_max_lun = 512;
 module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);
 
-static inline void
-iscsi_buf_init_iov(struct iscsi_buf *ibuf, char *vbuf, int size)
-{
-	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;
-}
-
-static inline void
-iscsi_buf_init_sg(struct iscsi_buf *ibuf, struct scatterlist *sg)
-{
-	ibuf->sg.page = sg->page;
-	ibuf->sg.offset = sg->offset;
-	ibuf->sg.length = sg->length;
-	/*
-	 * Fastpath: sg element fits into single page
-	 */
-	if (sg->length + sg->offset <= PAGE_SIZE && !PageSlab(sg->page))
-		ibuf->use_sendmsg = 0;
-	else
-		ibuf->use_sendmsg = 1;
-	ibuf->sent = 0;
-}
-
-static inline int
-iscsi_buf_left(struct iscsi_buf *ibuf)
-{
-	int rc;
-
-	rc = ibuf->sg.length - ibuf->sent;
-	BUG_ON(rc < 0);
-	return rc;
-}
-
-static inline void
-iscsi_hdr_digest(struct iscsi_conn *conn, struct iscsi_buf *buf,
-		 u8* crc)
-{
-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
-
-	crypto_digest_digest(tcp_conn->tx_tfm, &buf->sg, 1, crc);
-	buf->sg.length = tcp_conn->hdr_size;
-}
-
-static inline int
-iscsi_hdr_extract(struct iscsi_tcp_conn *tcp_conn)
-{
-	struct sk_buff *skb = tcp_conn->in.skb;
-
-	tcp_conn->in.zero_copy_hdr = 0;
-
-	if (tcp_conn->in.copy >= tcp_conn->hdr_size &&
-	    tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER) {
-		/*
-		 * Zero-copy PDU Header: using connection context
-		 * to store header pointer.
-		 */
-		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 {
-			/* 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;
-		}
-		tcp_conn->in.offset += tcp_conn->hdr_size;
-		tcp_conn->in.copy -= tcp_conn->hdr_size;
-	} else {
-		int hdr_remains;
-		int copylen;
-
-		/*
-		 * PDU header scattered across SKB's,
-		 * copying it... This'll happen quite rarely.
-		 */
-
-		if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER)
-			tcp_conn->in.hdr_offset = 0;
-
-		hdr_remains = tcp_conn->hdr_size - tcp_conn->in.hdr_offset;
-		BUG_ON(hdr_remains <= 0);
-
-		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);
-
-		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);
-
-		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;
-		}
-		tcp_conn->in.hdr = &tcp_conn->hdr;
-		tcp_conn->discontiguous_hdr_cnt++;
-	        tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
-	}
-
-	return 0;
-}
-
-/*
- * must be called with session lock
- */
-static void
-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*))) {
-		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
-			    sizeof(void*));
-		debug_scsi("iscsi_tcp_cleanup_ctask pending r2t dropped\n");
-	}
-
-	sc = ctask->sc;
-	if (unlikely(!sc))
-		return;
-
-	tcp_ctask->xmstate = XMSTATE_VALUE_IDLE;
-	tcp_ctask->r2t = NULL;
-}
-
-/**
- * iscsi_data_in - SCSI Data-In Response processing
- * @conn: iscsi connection
- * @ctask: scsi command task
- **/
-static int
-iscsi_data_in(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
-{
-	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;
-	int datasn = be32_to_cpu(rhdr->datasn);
-
-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
-	/*
-	 * 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);
-		return ISCSI_ERR_DATASN;
-	}
-
-	tcp_ctask->exp_datasn++;
-
-	tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
-	if (tcp_ctask->data_offset + tcp_conn->in.datalen > ctask->total_length)
-		return ISCSI_ERR_DATA_OFFSET;
-
-	conn->datain_pdus_cnt++;
-	return 0;
-}
-
-/**
- * iscsi_solicit_data_init - initialize first Data-Out
- * @conn: iscsi connection
- * @ctask: scsi command task
- * @r2t: R2T info
- *
- * Notes:
- *	Initialize first Data-Out within this R2T sequence and finds
- *	proper data_offset within this SCSI command.
- *
- *	This function is called with connection lock taken.
- **/
-static void
-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));
-	hdr->ttt = r2t->ttt;
-	hdr->datasn = cpu_to_be32(r2t->solicit_datasn);
-	r2t->solicit_datasn++;
-	hdr->opcode = ISCSI_OP_SCSI_DATA_OUT;
-	memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun));
-	hdr->itt = ctask->hdr->itt;
-	hdr->exp_statsn = r2t->exp_statsn;
-	hdr->offset = cpu_to_be32(r2t->data_offset);
-	if (r2t->data_length > conn->max_xmit_dlength) {
-		hton24(hdr->dlength, conn->max_xmit_dlength);
-		r2t->data_count = conn->max_xmit_dlength;
-		hdr->flags = 0;
-	} else {
-		hton24(hdr->dlength, r2t->data_length);
-		r2t->data_count = r2t->data_length;
-		hdr->flags = ISCSI_FLAG_CMD_FINAL;
-	}
-	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;
-	}
-}
-
-/**
- * iscsi_r2t_rsp - iSCSI R2T Response processing
- * @conn: iscsi connection
- * @ctask: scsi command task
- **/
-static int
-iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
-{
-	struct iscsi_r2t_info *r2t;
-	struct iscsi_session *session = conn->session;
-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
-	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
-	struct iscsi_r2t_rsp *rhdr = (struct iscsi_r2t_rsp *)tcp_conn->in.hdr;
-	int r2tsn = be32_to_cpu(rhdr->r2tsn);
-	int rc;
-
-	if (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);
-		return ISCSI_ERR_R2TSN;
-	}
-
-	/* fill-in new R2T associated with the task */
-	spin_lock(&session->lock);
-	iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
-
-	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;
-	}
-
-	rc = __kfifo_get(tcp_ctask->r2tpool.queue, (void*)&r2t, sizeof(void*));
-	BUG_ON(!rc);
-
-	r2t->exp_statsn = rhdr->statsn;
-	r2t->data_length = be32_to_cpu(rhdr->data_length);
-	if (r2t->data_length == 0) {
-		printk(KERN_ERR "iscsi_tcp: invalid R2T with zero data len\n");
-		spin_unlock(&session->lock);
-		return ISCSI_ERR_DATALEN;
-	}
-
-	if (r2t->data_length > session->max_burst)
-		debug_scsi("invalid R2T with data len %u and max burst %u."
-			   "Attempting to execute request.\n",
-			    r2t->data_length, session->max_burst);
-
-	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
-	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;
-	}
-
-	r2t->ttt = rhdr->ttt; /* no flip */
-	r2t->solicit_datasn = 0;
-
-	iscsi_solicit_data_init(conn, ctask, r2t);
-
-	tcp_ctask->exp_datasn = r2tsn + 1;
-	__kfifo_put(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*));
-	set_bit(XMSTATE_BIT_SOL_HDR_INIT, &tcp_ctask->xmstate);
-	list_move_tail(&ctask->running, &conn->xmitqueue);
-
-	scsi_queue_work(session->host, &conn->xmitwork);
-	conn->r2t_pdus_cnt++;
-	spin_unlock(&session->lock);
-
-	return 0;
-}
-
-static int
-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;
-	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) {
-		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;
-
-	/* 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_digest_digest(tcp_conn->rx_tfm, &sg, 1, (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 == ISCSI_ERR_NO_SCSI_CMD) {
-		tcp_conn->in.datalen = 0; /* force drop */
-		return 0;
-	} else if (rc)
-		return rc;
-
-	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:
-		tcp_conn->in.ctask = session->cmds[itt];
-		rc = iscsi_data_in(conn, tcp_conn->in.ctask);
-		if (rc)
-			return rc;
-		/* fall through */
-	case ISCSI_OP_SCSI_CMD_RSP:
-		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:
-		tcp_conn->in.ctask = session->cmds[itt];
-		if (ahslen)
-			rc = ISCSI_ERR_AHSLEN;
-		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:
-	case ISCSI_OP_TEXT_RSP:
-	case ISCSI_OP_REJECT:
-	case ISCSI_OP_ASYNC_EVENT:
-		/*
-		 * It is possible that we could get a PDU with a buffer larger
-		 * 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) {
-			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 (tcp_conn->in.datalen)
-			goto copy_hdr;
-	/* fall through */
-	case ISCSI_OP_LOGOUT_RSP:
-	case ISCSI_OP_NOOP_IN:
-	case ISCSI_OP_SCSI_TMFUNC_RSP:
-		rc = iscsi_complete_pdu(conn, hdr, NULL, 0);
-		break;
-	default:
-		rc = ISCSI_ERR_BAD_OPCODE;
-		break;
-	}
+static int iscsi_sw_tcp_dbg;
+module_param_named(debug_iscsi_tcp, iscsi_sw_tcp_dbg, int,
+		   S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug_iscsi_tcp, "Turn on debugging for iscsi_tcp module "
+		 "Set to 1 to turn on, and zero to turn off. Default is off.");
 
-	return rc;
+#define ISCSI_SW_TCP_DBG(_conn, dbg_fmt, arg...)		\
+	do {							\
+		if (iscsi_sw_tcp_dbg)				\
+			iscsi_conn_printk(KERN_INFO, _conn,	\
+					     "%s " dbg_fmt,	\
+					     __func__, ##arg);	\
+	} while (0);
 
-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;
-}
 
 /**
- * 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_copy - copy skb bits to the destanation buffer
- * @conn: iscsi tcp connection
- *
- * 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_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 crypto_tfm *tfm, struct scatterlist *sg,
-			 int offset, int length)
-{
-	struct scatterlist temp;
-
-	memcpy(&temp, sg, sizeof(struct scatterlist));
-	temp.offset = offset;
-	temp.length = length;
-	crypto_digest_update(tfm, &temp, 1);
-}
-
-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_digest_update(tcp_conn->rx_tfm, &tmp, 1);
-}
-
-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
-	 */
-	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;
-	}
-
-	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_digest_update(
-							tcp_conn->rx_tfm,
-							&sg[i], 1);
-				else
-					partial_sg_digest_update(
-							tcp_conn->rx_tfm,
-							&sg[i],
-							sg[i].offset + offset,
-							sg[i].length - offset);
-			}
-			offset = 0;
-			tcp_ctask->sg_count++;
-		}
-
-		if (!ctask->data_count) {
-			if (rc && conn->datadgst_en)
-				/*
-				 * data-in is complete, but buffer not...
-				 */
-				partial_sg_digest_update(tcp_conn->rx_tfm,
-						&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);
-	}
-
-	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_data_recv - TCP receive in sendfile fashion
+ * iscsi_sw_tcp_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_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
-		unsigned int offset, size_t len)
+ */
+static int iscsi_sw_tcp_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;
-	int processed;
-	char pad[ISCSI_PAD_LEN];
-	struct scatterlist sg;
-
-	/*
-	 * 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;
-	}
-
-	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;
-		       }
-		}
-
-		/*
-		 * Verify and process incoming PDU header.
-		 */
-		rc = iscsi_tcp_hdr_recv(conn);
-		if (!rc && tcp_conn->in.datalen) {
-			if (conn->datadgst_en)
-				crypto_digest_init(tcp_conn->rx_tfm);
-			tcp_conn->in_progress = IN_PROGRESS_DATA_RECV;
-		} else if (rc) {
-			iscsi_conn_failure(conn, rc);
-			return 0;
-		}
-	}
-
-	if (tcp_conn->in_progress == IN_PROGRESS_DDIGEST_RECV &&
-	    tcp_conn->in.copy) {
-		uint32_t recv_digest;
-
-		debug_tcp("extra data_recv offset %d copy %d\n",
-			  tcp_conn->in.offset, tcp_conn->in.copy);
-
-		if (!tcp_conn->data_copied) {
-			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_digest_update(tcp_conn->rx_tfm,
-						     &sg, 1);
-			}
-			crypto_digest_final(tcp_conn->rx_tfm,
-					  (u8 *) &tcp_conn->in.datadgst);
-			debug_tcp("rx digest 0x%x\n", tcp_conn->in.datadgst);
-		}
-
-		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;
-		}
-	}
-
-	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;
-		}
-
-		if (tcp_conn->in.padding)
-			tcp_conn->in_progress = IN_PROGRESS_PAD_RECV;
-		else if (conn->datadgst_en)
-			tcp_conn->in_progress = IN_PROGRESS_DDIGEST_RECV;
-		else
-			tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
-		tcp_conn->data_copied = 0;
-	}
-
-	if (tcp_conn->in_progress == IN_PROGRESS_PAD_RECV &&
-	    tcp_conn->in.copy) {
-		int copylen = min(tcp_conn->in.padding - tcp_conn->data_copied,
-				  tcp_conn->in.copy);
-
-		tcp_conn->in.copy -= copylen;
-		tcp_conn->in.offset += copylen;
-		tcp_conn->data_copied += copylen;
-
-		if (tcp_conn->data_copied != tcp_conn->in.padding)
-			tcp_conn->in_progress = IN_PROGRESS_PAD_RECV;
-		else if (conn->datadgst_en)
-			tcp_conn->in_progress = IN_PROGRESS_DDIGEST_RECV;
-		else
-			tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
-		tcp_conn->data_copied = 0;
-	}
-
-	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);
+	unsigned int consumed, total_consumed = 0;
+	int status;
 
-	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;
+	ISCSI_SW_TCP_DBG(conn, "in %d bytes\n", skb->len - offset);
 
-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);
+	do {
+		status = 0;
+		consumed = iscsi_tcp_recv_skb(conn, skb, offset, 0, &status);
+		offset += consumed;
+		total_consumed += consumed;
+	} while (consumed != 0 && status != ISCSI_TCP_SKB_DONE);
 
-	conn->rxdata_octets += processed;
-	return processed;
+	ISCSI_SW_TCP_DBG(conn, "read %d bytes status %d\n",
+			 skb->len - offset, status);
+	return total_consumed;
 }
 
-static void
-iscsi_tcp_data_ready(struct sock *sk, int flag)
+static void iscsi_sw_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_data_recv.
+	 * Use rd_desc to pass 'conn' to iscsi_tcp_recv.
 	 * We set count to 1 because we want the network layer to
-	 * hand us all the skbs that are available. iscsi_tcp_data_recv
+	 * hand us all the skbs that are available. iscsi_tcp_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_data_recv);
+	tcp_read_sock(sk, &rd_desc, iscsi_sw_tcp_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
-iscsi_tcp_state_change(struct sock *sk)
+static void iscsi_sw_tcp_state_change(struct sock *sk)
 {
 	struct iscsi_tcp_conn *tcp_conn;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn;
 	struct iscsi_conn *conn;
 	struct iscsi_session *session;
 	void (*old_state_change)(struct sock *);
@@ -1019,12 +141,14 @@ iscsi_tcp_state_change(struct sock *sk)
 	if ((sk->sk_state == TCP_CLOSE_WAIT ||
 	     sk->sk_state == TCP_CLOSE) &&
 	    !atomic_read(&sk->sk_rmem_alloc)) {
-		debug_tcp("iscsi_tcp_state_change: TCP_CLOSE|TCP_CLOSE_WAIT\n");
-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+		ISCSI_SW_TCP_DBG(conn, "iscsi_tcp_state_change: "
+				 "TCP_CLOSE|TCP_CLOSE_WAIT\n");
+		iscsi2_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
 	}
 
 	tcp_conn = conn->dd_data;
-	old_state_change = tcp_conn->old_state_change;
+	tcp_sw_conn = tcp_conn->dd_data;
+	old_state_change = tcp_sw_conn->old_state_change;
 
 	read_unlock(&sk->sk_callback_lock);
 
@@ -1035,851 +159,430 @@ iscsi_tcp_state_change(struct sock *sk)
  * iscsi_write_space - Called when more output buffer space is available
  * @sk: socket space is available for
  **/
-static void
-iscsi_write_space(struct sock *sk)
+static void iscsi_sw_tcp_write_space(struct sock *sk)
 {
 	struct iscsi_conn *conn = (struct iscsi_conn*)sk->sk_user_data;
 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
 
-	tcp_conn->old_write_space(sk);
-	debug_tcp("iscsi_write_space: cid %d\n", conn->id);
-	scsi_queue_work(conn->session->host, &conn->xmitwork);
+	tcp_sw_conn->old_write_space(sk);
+	ISCSI_SW_TCP_DBG(conn, "iscsi_write_space\n");
+	iscsi_conn_queue_work(conn);
 }
 
-static void
-iscsi_conn_set_callbacks(struct iscsi_conn *conn)
+static void iscsi_sw_tcp_conn_set_callbacks(struct iscsi_conn *conn)
 {
 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
-	struct sock *sk = tcp_conn->sock->sk;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
+	struct sock *sk = tcp_sw_conn->sock->sk;
 
 	/* assign new callbacks */
 	write_lock_bh(&sk->sk_callback_lock);
 	sk->sk_user_data = conn;
-	tcp_conn->old_data_ready = sk->sk_data_ready;
-	tcp_conn->old_state_change = sk->sk_state_change;
-	tcp_conn->old_write_space = sk->sk_write_space;
-	sk->sk_data_ready = iscsi_tcp_data_ready;
-	sk->sk_state_change = iscsi_tcp_state_change;
-	sk->sk_write_space = iscsi_write_space;
+	tcp_sw_conn->old_data_ready = sk->sk_data_ready;
+	tcp_sw_conn->old_state_change = sk->sk_state_change;
+	tcp_sw_conn->old_write_space = sk->sk_write_space;
+	sk->sk_data_ready = iscsi_sw_tcp_data_ready;
+	sk->sk_state_change = iscsi_sw_tcp_state_change;
+	sk->sk_write_space = iscsi_sw_tcp_write_space;
 	write_unlock_bh(&sk->sk_callback_lock);
 }
 
 static void
-iscsi_conn_restore_callbacks(struct iscsi_tcp_conn *tcp_conn)
+iscsi_sw_tcp_conn_restore_callbacks(struct iscsi_sw_tcp_conn *tcp_sw_conn)
 {
-	struct sock *sk = tcp_conn->sock->sk;
+	struct sock *sk = tcp_sw_conn->sock->sk;
 
 	/* restore socket callbacks, see also: iscsi_conn_set_callbacks() */
 	write_lock_bh(&sk->sk_callback_lock);
 	sk->sk_user_data    = NULL;
-	sk->sk_data_ready   = tcp_conn->old_data_ready;
-	sk->sk_state_change = tcp_conn->old_state_change;
-	sk->sk_write_space  = tcp_conn->old_write_space;
+	sk->sk_data_ready   = tcp_sw_conn->old_data_ready;
+	sk->sk_state_change = tcp_sw_conn->old_state_change;
+	sk->sk_write_space  = tcp_sw_conn->old_write_space;
 	sk->sk_no_check	 = 0;
 	write_unlock_bh(&sk->sk_callback_lock);
 }
 
 /**
- * iscsi_send - generic send routine
- * @sk: kernel's socket
- * @buf: buffer to write from
- * @size: actual size to write
- * @flags: socket's flags
+ * iscsi_sw_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 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 socket *sk = tcp_conn->sock;
-	int offset = buf->sg.offset + buf->sent, res;
-
-	/*
-	 * 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;
-	}
+static int iscsi_sw_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn,
+				     struct iscsi_segment *segment)
+{
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
+	struct socket *sk = tcp_sw_conn->sock;
+	unsigned int copied = 0;
+	int r = 0;
+
+	while (!iscsi_tcp_segment_done(tcp_conn, 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_sw_conn->sendpage(sk, sg_page(sg), offset,
+						  copy, flags);
+		} else {
+			struct msghdr msg = { .msg_flags = flags };
+			struct kvec iov = {
+				.iov_base = segment->data + offset,
+				.iov_len = copy
+			};
 
-	tcp_conn->sendpage_failures_cnt++;
-	if (res == -EAGAIN)
-		res = -ENOBUFS;
-	else
-		iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
-	return res;
-}
+			r = kernel_sendmsg(sk, &msg, &iov, 1, copy);
+		}
 
-/**
- * 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_sendhdr(struct iscsi_conn *conn, struct iscsi_buf *buf, int datalen)
-{
-	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;
-		return 0;
+		if (r < 0) {
+			iscsi_tcp_segment_unmap(segment);
+			if (copied || r == -EAGAIN)
+				break;
+			return r;
+		}
+		copied += r;
 	}
-
-	return res;
+	return copied;
 }
 
 /**
- * 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)
+ * iscsi_sw_tcp_xmit - TCP transmit
  **/
-static inline int
-iscsi_sendpage(struct iscsi_conn *conn, struct iscsi_buf *buf,
-	       int *count, int *sent)
+static int iscsi_sw_tcp_xmit(struct iscsi_conn *conn)
 {
-	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;
-	}
+	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
+	struct iscsi_segment *segment = &tcp_sw_conn->out.segment;
+	unsigned int consumed = 0;
+	int rc = 0;
 
-	return res;
-}
+	while (1) {
+		rc = iscsi_sw_tcp_xmit_segment(tcp_conn, segment);
+		if (rc < 0) {
+			rc = ISCSI_ERR_XMIT_FAILED;
+			goto error;
+		}
+		if (rc == 0)
+			break;
 
-static inline void
-iscsi_data_digest_init(struct iscsi_tcp_conn *tcp_conn,
-		      struct iscsi_tcp_cmd_task *tcp_ctask)
-{
-	crypto_digest_init(tcp_conn->tx_tfm);
-	tcp_ctask->digest_count = 4;
-}
+		consumed += rc;
 
-/**
- * iscsi_solicit_data_cont - initialize next Data-Out
- * @conn: iscsi connection
- * @ctask: scsi command task
- * @r2t: R2T info
- * @left: bytes left to transfer
- *
- * Notes:
- *	Initialize next Data-Out within this R2T sequence and continue
- *	to process next Scatter-Gather element(if any) of this SCSI command.
- *
- *	Called under connection lock.
- **/
-static void
-iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
-			struct iscsi_r2t_info *r2t, int left)
-{
-	struct iscsi_data *hdr;
-	struct scsi_cmnd *sc = ctask->sc;
-	int new_offset;
-
-	hdr = &r2t->dtask.hdr;
-	memset(hdr, 0, sizeof(struct iscsi_data));
-	hdr->ttt = r2t->ttt;
-	hdr->datasn = cpu_to_be32(r2t->solicit_datasn);
-	r2t->solicit_datasn++;
-	hdr->opcode = ISCSI_OP_SCSI_DATA_OUT;
-	memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun));
-	hdr->itt = ctask->hdr->itt;
-	hdr->exp_statsn = r2t->exp_statsn;
-	new_offset = r2t->data_offset + r2t->sent;
-	hdr->offset = cpu_to_be32(new_offset);
-	if (left > conn->max_xmit_dlength) {
-		hton24(hdr->dlength, conn->max_xmit_dlength);
-		r2t->data_count = conn->max_xmit_dlength;
-	} else {
-		hton24(hdr->dlength, left);
-		r2t->data_count = left;
-		hdr->flags = ISCSI_FLAG_CMD_FINAL;
+		if (segment->total_copied >= segment->total_size) {
+			if (segment->done != NULL) {
+				rc = segment->done(tcp_conn, segment);
+				if (rc != 0)
+					goto error;
+			}
+		}
 	}
-	conn->dataout_pdus_cnt++;
-
-	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;
-	}
-}
+	ISCSI_SW_TCP_DBG(conn, "xmit %d bytes\n", consumed);
 
-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;
+	conn->txdata_octets += consumed;
+	return consumed;
 
-	tcp_ctask->pad_count = ISCSI_PAD_LEN - tcp_ctask->pad_count;
-	debug_scsi("write padding %d bytes\n", tcp_ctask->pad_count);
-	set_bit(XMSTATE_BIT_W_PAD, &tcp_ctask->xmstate);
+error:
+	/* Transmit error. We could initiate error recovery
+	 * here. */
+	ISCSI_SW_TCP_DBG(conn, "Error sending PDU, errno=%d\n", rc);
+	iscsi2_conn_failure(conn, rc);
+	return -EIO;
 }
 
 /**
- * iscsi_tcp_cmd_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
- * @conn: iscsi connection
- * @ctask: scsi command task
- * @sc: scsi command
- **/
-static void
-iscsi_tcp_cmd_init(struct iscsi_cmd_task *ctask)
+ * iscsi_tcp_xmit_qlen - return the number of bytes queued for xmit
+ */
+static inline int iscsi_sw_tcp_xmit_qlen(struct iscsi_conn *conn)
 {
-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
+	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
+	struct iscsi_segment *segment = &tcp_sw_conn->out.segment;
 
-	BUG_ON(__kfifo_len(tcp_ctask->r2tqueue));
-	tcp_ctask->xmstate = 1 << XMSTATE_BIT_CMD_HDR_INIT;
+	return segment->total_copied - segment->total_size;
 }
 
-/**
- * iscsi_tcp_mtask_xmit - xmit management(immediate) task
- * @conn: iscsi connection
- * @mtask: task management task
- *
- * Notes:
- *	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 these states:
- *		XMSTATE_BIT_IMM_HDR_INIT - calculate digest of PDU Header
- *		XMSTATE_BIT_IMM_HDR      - PDU Header xmit in progress
- *		XMSTATE_BIT_IMM_DATA     - PDU Data xmit in progress
- *		XMSTATE_VALUE_IDLE       - management PDU is done
- **/
-static int
-iscsi_tcp_mtask_xmit(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
+static int iscsi_sw_tcp_pdu_xmit(struct iscsi_task *task)
 {
-	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
+	struct iscsi_conn *conn = task->conn;
 	int rc;
 
-	debug_scsi("mtask deq [cid %d state %x itt 0x%x]\n",
-		conn->id, tcp_mtask->xmstate, mtask->itt);
-
-	if (test_bit(XMSTATE_BIT_IMM_HDR_INIT, &tcp_mtask->xmstate)) {
-		iscsi_buf_init_iov(&tcp_mtask->headbuf, (char*)mtask->hdr,
-				   sizeof(struct iscsi_hdr));
-
-		if (mtask->data_count) {
-			set_bit(XMSTATE_BIT_IMM_DATA, &tcp_mtask->xmstate);
-			iscsi_buf_init_iov(&tcp_mtask->sendbuf,
-					   (char*)mtask->data,
-					   mtask->data_count);
-		}
-
-		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);
-
-		tcp_mtask->sent = 0;
-		clear_bit(XMSTATE_BIT_IMM_HDR_INIT, &tcp_mtask->xmstate);
-		set_bit(XMSTATE_BIT_IMM_HDR, &tcp_mtask->xmstate);
-	}
-
-	if (test_bit(XMSTATE_BIT_IMM_HDR, &tcp_mtask->xmstate)) {
-		rc = iscsi_sendhdr(conn, &tcp_mtask->headbuf,
-				   mtask->data_count);
-		if (rc)
+	while (iscsi_sw_tcp_xmit_qlen(conn)) {
+		rc = iscsi_sw_tcp_xmit(conn);
+		if (rc == 0)
+			return -EAGAIN;
+		if (rc < 0)
 			return rc;
-		clear_bit(XMSTATE_BIT_IMM_HDR, &tcp_mtask->xmstate);
 	}
 
-	if (test_and_clear_bit(XMSTATE_BIT_IMM_DATA, &tcp_mtask->xmstate)) {
-		BUG_ON(!mtask->data_count);
-		/* 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) {
-				set_bit(XMSTATE_BIT_IMM_DATA, &tcp_mtask->xmstate);
-				return rc;
-			}
-		} while (mtask->data_count);
-	}
-
-	BUG_ON(tcp_mtask->xmstate != XMSTATE_VALUE_IDLE);
-	if (mtask->hdr->itt == RESERVED_ITT) {
-		struct iscsi_session *session = conn->session;
-
-		spin_lock_bh(&session->lock);
-		iscsi_free_mgmt_task(conn, mtask);
-		spin_unlock_bh(&session->lock);
-	}
 	return 0;
 }
 
-static int
-iscsi_send_cmd_hdr(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+/*
+ * This is called when we're done sending the header.
+ * Simply copy the data_segment to the send segment, and return.
+ */
+static int iscsi_sw_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn,
+				      struct iscsi_segment *segment)
 {
-	struct scsi_cmnd *sc = ctask->sc;
-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
-	int rc = 0;
-
-	if (test_bit(XMSTATE_BIT_CMD_HDR_INIT, &tcp_ctask->xmstate)) {
-		tcp_ctask->sent = 0;
-		tcp_ctask->sg_count = 0;
-		tcp_ctask->exp_datasn = 0;
-
-		if (sc->sc_data_direction == DMA_TO_DEVICE) {
-			if (sc->use_sg) {
-				struct scatterlist *sg = sc->request_buffer;
-
-				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, sc->request_bufflen,
-				   ctask->imm_count, ctask->unsol_count,
-				   ctask->unsol_offset);
-		}
-
-		iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)ctask->hdr,
-				  sizeof(struct iscsi_hdr));
-
-		if (conn->hdrdgst_en)
-			iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
-					 (u8*)tcp_ctask->hdrext);
-		clear_bit(XMSTATE_BIT_CMD_HDR_INIT, &tcp_ctask->xmstate);
-		set_bit(XMSTATE_BIT_CMD_HDR_XMIT, &tcp_ctask->xmstate);
-	}
-
-	if (test_bit(XMSTATE_BIT_CMD_HDR_XMIT, &tcp_ctask->xmstate)) {
-		rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->imm_count);
-		if (rc)
-			return rc;
-		clear_bit(XMSTATE_BIT_CMD_HDR_XMIT, &tcp_ctask->xmstate);
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
 
-		if (sc->sc_data_direction != DMA_TO_DEVICE)
-			return 0;
-
-		if (ctask->imm_count) {
-			set_bit(XMSTATE_BIT_IMM_DATA, &tcp_ctask->xmstate);
-			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) {
-			set_bit(XMSTATE_BIT_UNS_HDR, &tcp_ctask->xmstate);
-			set_bit(XMSTATE_BIT_UNS_INIT, &tcp_ctask->xmstate);
-		}
-	}
-	return rc;
+	tcp_sw_conn->out.segment = tcp_sw_conn->out.data_segment;
+	ISCSI_SW_TCP_DBG(tcp_conn->iscsi_conn,
+			 "Header done. Next segment size %u total_size %u\n",
+			 tcp_sw_conn->out.segment.size,
+			 tcp_sw_conn->out.segment.total_size);
+	return 0;
 }
 
-static int
-iscsi_send_padding(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+static void iscsi_sw_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr,
+				       size_t hdrlen)
 {
-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
-	int sent = 0, rc;
-
-	if (test_bit(XMSTATE_BIT_W_PAD, &tcp_ctask->xmstate)) {
-		iscsi_buf_init_iov(&tcp_ctask->sendbuf, (char*)&tcp_ctask->pad,
-				   tcp_ctask->pad_count);
-		if (conn->datadgst_en)
-			crypto_digest_update(tcp_conn->tx_tfm,
-					     &tcp_ctask->sendbuf.sg, 1);
-	} else if (!test_bit(XMSTATE_BIT_W_RESEND_PAD, &tcp_ctask->xmstate))
-		return 0;
-
-	clear_bit(XMSTATE_BIT_W_PAD, &tcp_ctask->xmstate);
-	clear_bit(XMSTATE_BIT_W_RESEND_PAD, &tcp_ctask->xmstate);
-	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);
-		set_bit(XMSTATE_BIT_W_RESEND_PAD, &tcp_ctask->xmstate);
-	}
-	return rc;
-}
-
-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;
-
-	tcp_ctask = ctask->dd_data;
-	tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
 
-	if (!test_bit(XMSTATE_BIT_W_RESEND_DATA_DIGEST, &tcp_ctask->xmstate)) {
-		crypto_digest_final(tcp_conn->tx_tfm, (u8*)digest);
-		iscsi_buf_init_iov(buf, (char*)digest, 4);
-	}
-	clear_bit(XMSTATE_BIT_W_RESEND_DATA_DIGEST, &tcp_ctask->xmstate);
-	
-	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);
-		set_bit(XMSTATE_BIT_W_RESEND_DATA_DIGEST, &tcp_ctask->xmstate);
-	}
-	return rc;
-}
+	ISCSI_SW_TCP_DBG(conn, "%s\n", conn->hdrdgst_en ?
+			 "digest enabled" : "digest disabled");
 
-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_tfm,
-				&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;
-		}
+	/* Clear the data segment - needs to be filled in by the
+	 * caller using iscsi_tcp_send_data_prep() */
+	memset(&tcp_sw_conn->out.data_segment, 0,
+	       sizeof(struct iscsi_segment));
 
-		if (rc)
-			return rc;
+	/* If header digest is enabled, compute the CRC and
+	 * place the digest into the same buffer. We make
+	 * sure that both iscsi_tcp_task and mtask have
+	 * sufficient room.
+	 */
+	if (conn->hdrdgst_en) {
+		iscsi_tcp_dgst_header(&tcp_sw_conn->tx_hash, hdr, hdrlen,
+				      hdr + hdrlen);
+		hdrlen += ISCSI_DIGEST_SIZE;
 	}
 
-	rc = iscsi_send_padding(conn, ctask);
-	if (rc)
-		return rc;
+	/* Remember header pointer for later, when we need
+	 * to decide whether there's a payload to go along
+	 * with the header. */
+	tcp_sw_conn->out.hdr = hdr;
 
-	return iscsi_send_digest(conn, ctask, digestbuf, digest);
+	iscsi_segment_init_linear(&tcp_sw_conn->out.segment, hdr, hdrlen,
+				  iscsi_sw_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_send_unsol_hdr(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+iscsi_sw_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg,
+			    unsigned int count, unsigned int offset,
+			    unsigned int len)
 {
-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
-	struct iscsi_data_task *dtask;
-	int rc;
-
-	set_bit(XMSTATE_BIT_UNS_DATA, &tcp_ctask->xmstate);
-	if (test_bit(XMSTATE_BIT_UNS_INIT, &tcp_ctask->xmstate)) {
-		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);
+	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
+	struct hash_desc *tx_hash = NULL;
+	unsigned int hdr_spec_len;
 
-		clear_bit(XMSTATE_BIT_UNS_INIT, &tcp_ctask->xmstate);
-		iscsi_set_padding(tcp_ctask, ctask->data_count);
-	}
+	ISCSI_SW_TCP_DBG(conn, "offset=%d, datalen=%d %s\n", offset, len,
+			 conn->datadgst_en ?
+			 "digest enabled" : "digest disabled");
 
-	rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->data_count);
-	if (rc) {
-		clear_bit(XMSTATE_BIT_UNS_DATA, &tcp_ctask->xmstate);
-		set_bit(XMSTATE_BIT_UNS_HDR, &tcp_ctask->xmstate);
-		return rc;
-	}
+	/* Make sure the datalen matches what the caller
+	   said he would send. */
+	hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength);
+	WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
 
-	if (conn->datadgst_en) {
-		dtask = &tcp_ctask->unsol_dtask;
-		iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask);
-		dtask->digest = 0;
-	}
+	if (conn->datadgst_en)
+		tx_hash = &tcp_sw_conn->tx_hash;
 
-	debug_scsi("uns dout [itt 0x%x dlen %d sent %d]\n",
-		   ctask->itt, ctask->unsol_count, tcp_ctask->sent);
-	return 0;
+	return iscsi_segment_seek_sg(&tcp_sw_conn->out.data_segment,
+				     sg, count, offset, len,
+				     NULL, tx_hash);
 }
 
-static int
-iscsi_send_unsol_pdu(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+static void
+iscsi_sw_tcp_send_linear_data_prep(struct iscsi_conn *conn, void *data,
+				   size_t len)
 {
-	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
-	int rc;
+	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
+	struct hash_desc *tx_hash = NULL;
+	unsigned int hdr_spec_len;
 
-	if (test_and_clear_bit(XMSTATE_BIT_UNS_HDR, &tcp_ctask->xmstate)) {
-		BUG_ON(!ctask->unsol_count);
-send_hdr:
-		rc = iscsi_send_unsol_hdr(conn, ctask);
-		if (rc)
-			return rc;
-	}
+	ISCSI_SW_TCP_DBG(conn, "datalen=%zd %s\n", len, conn->datadgst_en ?
+			 "digest enabled" : "digest disabled");
 
-	if (test_bit(XMSTATE_BIT_UNS_DATA, &tcp_ctask->xmstate)) {
-		struct iscsi_data_task *dtask = &tcp_ctask->unsol_dtask;
-		int start = tcp_ctask->sent;
+	/* Make sure the datalen matches what the caller
+	   said he would send. */
+	hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength);
+	WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
 
-		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;
-		clear_bit(XMSTATE_BIT_UNS_DATA, &tcp_ctask->xmstate);
-		/*
-		 * Done with the Data-Out. Next, check if we need
-		 * to send another unsolicited Data-Out.
-		 */
-		if (ctask->unsol_count) {
-			debug_scsi("sending more uns\n");
-			set_bit(XMSTATE_BIT_UNS_INIT, &tcp_ctask->xmstate);
-			goto send_hdr;
-		}
-	}
-	return 0;
+	if (conn->datadgst_en)
+		tx_hash = &tcp_sw_conn->tx_hash;
+
+	iscsi_segment_init_linear(&tcp_sw_conn->out.data_segment,
+				data, len, NULL, tx_hash);
 }
 
-static int iscsi_send_sol_pdu(struct iscsi_conn *conn,
-			      struct iscsi_cmd_task *ctask)
+static int iscsi_sw_tcp_pdu_init(struct iscsi_task *task,
+				 unsigned int offset, unsigned int count)
 {
-	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 (test_bit(XMSTATE_BIT_SOL_HDR_INIT, &tcp_ctask->xmstate)) {
-		if (!tcp_ctask->r2t) {
-			spin_lock_bh(&session->lock);
-			__kfifo_get(tcp_ctask->r2tqueue, (void*)&tcp_ctask->r2t,
-				    sizeof(void*));
-			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);
-		clear_bit(XMSTATE_BIT_SOL_HDR_INIT, &tcp_ctask->xmstate);
-		set_bit(XMSTATE_BIT_SOL_HDR, &tcp_ctask->xmstate);
-	}
+	struct iscsi_conn *conn = task->conn;
+	int err = 0;
 
-	if (test_bit(XMSTATE_BIT_SOL_HDR, &tcp_ctask->xmstate)) {
-		r2t = tcp_ctask->r2t;
-		dtask = &r2t->dtask;
-
-		rc = iscsi_sendhdr(conn, &r2t->headbuf, r2t->data_count);
-		if (rc)
-			return rc;
-		clear_bit(XMSTATE_BIT_SOL_HDR, &tcp_ctask->xmstate);
-		set_bit(XMSTATE_BIT_SOL_DATA, &tcp_ctask->xmstate);
+	iscsi_sw_tcp_send_hdr_prep(conn, task->hdr, task->hdr_len);
 
-		if (conn->datadgst_en) {
-			iscsi_data_digest_init(conn->dd_data, tcp_ctask);
-			dtask->digest = 0;
-		}
+	if (!count)
+		return 0;
 
-		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);
+	if (!task->sc)
+		iscsi_sw_tcp_send_linear_data_prep(conn, task->data, count);
+	else {
+		err = iscsi_sw_tcp_send_data_prep(conn, scsi_sglist(task->sc),
+						  scsi_sg_count(task->sc),
+						  offset, count);
 	}
 
-	if (test_bit(XMSTATE_BIT_SOL_DATA, &tcp_ctask->xmstate)) {
-		r2t = tcp_ctask->r2t;
-		dtask = &r2t->dtask;
-
-		rc = iscsi_send_data(ctask, &r2t->sendbuf, &r2t->sg,
-				     &r2t->sent, &r2t->data_count,
-				     &dtask->digestbuf, &dtask->digest);
-		if (rc)
-			return rc;
-		clear_bit(XMSTATE_BIT_SOL_DATA, &tcp_ctask->xmstate);
-
-		/*
-		 * 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);
-			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;
-			spin_unlock_bh(&session->lock);
-			goto send_hdr;
-		}
-		spin_unlock_bh(&session->lock);
-	}
+	if (err)
+		/* got invalid offset/len */
+		return -EINVAL;
 	return 0;
 }
 
-/**
- * iscsi_tcp_ctask_xmit - xmit normal PDU task
- * @conn: iscsi connection
- * @ctask: iscsi command task
- *
- * Notes:
- *	The function can return -EAGAIN in which case caller must
- *	call it again later, or recover. '0' return code means successful
- *	xmit.
- *	The function is devided to logical helpers (above) for the different
- *	xmit stages.
- *
- *iscsi_send_cmd_hdr()
- *	XMSTATE_BIT_CMD_HDR_INIT - prepare Header and Data buffers Calculate
- *	                           Header Digest
- *	XMSTATE_BIT_CMD_HDR_XMIT - Transmit header in progress
- *
- *iscsi_send_padding
- *	XMSTATE_BIT_W_PAD        - Prepare and send pading
- *	XMSTATE_BIT_W_RESEND_PAD - retry send pading
- *
- *iscsi_send_digest
- *	XMSTATE_BIT_W_RESEND_DATA_DIGEST - Finalize and send Data Digest
- *	XMSTATE_BIT_W_RESEND_DATA_DIGEST - retry sending digest
- *
- *iscsi_send_unsol_hdr
- *	XMSTATE_BIT_UNS_INIT     - prepare un-solicit data header and digest
- *	XMSTATE_BIT_UNS_HDR      - send un-solicit header
- *
- *iscsi_send_unsol_pdu
- *	XMSTATE_BIT_UNS_DATA     - send un-solicit data in progress
- *
- *iscsi_send_sol_pdu
- *	XMSTATE_BIT_SOL_HDR_INIT - solicit data header and digest initialize
- *	XMSTATE_BIT_SOL_HDR      - send solicit header
- *	XMSTATE_BIT_SOL_DATA     - send solicit data
- *
- *iscsi_tcp_ctask_xmit
- *	XMSTATE_BIT_IMM_DATA     - xmit managment data (??)
- **/
-static int
-iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
+static int iscsi_sw_tcp_pdu_alloc(struct iscsi_task *task, uint8_t opcode)
 {
-	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);
-
-	rc = iscsi_send_cmd_hdr(conn, ctask);
-	if (rc)
-		return rc;
-	if (ctask->sc->sc_data_direction != DMA_TO_DEVICE)
-		return 0;
-
-	if (test_bit(XMSTATE_BIT_IMM_DATA, &tcp_ctask->xmstate)) {
-		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;
-		clear_bit(XMSTATE_BIT_IMM_DATA, &tcp_ctask->xmstate);
-	}
-
-	rc = iscsi_send_unsol_pdu(conn, ctask);
-	if (rc)
-		return rc;
+	struct iscsi_tcp_task *tcp_task = task->dd_data;
 
-	return iscsi_send_sol_pdu(conn, ctask);
+	task->hdr = task->dd_data + sizeof(*tcp_task);
+	task->hdr_max = sizeof(struct iscsi_sw_tcp_hdrbuf) - ISCSI_DIGEST_SIZE;
+	return 0;
 }
 
 static struct iscsi_cls_conn *
-iscsi_tcp_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
+iscsi_sw_tcp_conn_create(struct iscsi_cls_session *cls_session,
+			 uint32_t conn_idx)
 {
 	struct iscsi_conn *conn;
 	struct iscsi_cls_conn *cls_conn;
 	struct iscsi_tcp_conn *tcp_conn;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn;
 
-	cls_conn = iscsi_conn_setup(cls_session, conn_idx);
+	cls_conn = iscsi_tcp_conn_setup(cls_session, sizeof(*tcp_sw_conn),
+					conn_idx);
 	if (!cls_conn)
 		return NULL;
 	conn = cls_conn->dd_data;
-	/*
-	 * due to strange issues with iser these are not set
-	 * in iscsi_conn_setup
-	 */
-	conn->max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN;
-
-	tcp_conn = kzalloc(sizeof(*tcp_conn), GFP_KERNEL);
-	if (!tcp_conn)
-		goto tcp_conn_alloc_fail;
-
-	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_tfm = crypto_alloc_tfm("crc32c", 0);
-	if (!tcp_conn->tx_tfm) {
-		printk(KERN_ERR "Could not create connection due to crc32c "
-			"loading error. Make sure the crc32c module is "
-			"built as a module or into the kernel.\n");
-		goto free_tcp_conn;
-	}
-
-	tcp_conn->rx_tfm = crypto_alloc_tfm("crc32c", 0);
-	if (!tcp_conn->rx_tfm) {
-		printk(KERN_ERR "Could not create connection due to crc32c "
-			"loading error. Make sure the crc32c module is "
-			"built as a module or into the kernel.\n");
+	tcp_conn = conn->dd_data;
+	tcp_sw_conn = tcp_conn->dd_data;
+
+	tcp_sw_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+						     CRYPTO_ALG_ASYNC);
+	tcp_sw_conn->tx_hash.flags = 0;
+	if (IS_ERR(tcp_sw_conn->tx_hash.tfm))
+		goto free_conn;
+
+	tcp_sw_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0,
+						     CRYPTO_ALG_ASYNC);
+	tcp_sw_conn->rx_hash.flags = 0;
+	if (IS_ERR(tcp_sw_conn->rx_hash.tfm))
 		goto free_tx_tfm;
-	}
+	tcp_conn->rx_hash = &tcp_sw_conn->rx_hash;
 
 	return cls_conn;
 
 free_tx_tfm:
-	crypto_free_tfm(tcp_conn->tx_tfm);
-free_tcp_conn:
-	kfree(tcp_conn);
-tcp_conn_alloc_fail:
-	iscsi_conn_teardown(cls_conn);
+	crypto_free_hash(tcp_sw_conn->tx_hash.tfm);
+free_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");
+	iscsi_tcp_conn_teardown(cls_conn);
 	return NULL;
 }
 
-static void
-iscsi_tcp_release_conn(struct iscsi_conn *conn)
+static void iscsi_sw_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;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
+	struct socket *sock = tcp_sw_conn->sock;
 
 	if (!sock)
 		return;
 
 	sock_hold(sock->sk);
-	iscsi_conn_restore_callbacks(tcp_conn);
+	iscsi_sw_tcp_conn_restore_callbacks(tcp_sw_conn);
 	sock_put(sock->sk);
 
 	spin_lock_bh(&session->lock);
-	tcp_conn->sock = NULL;
-	conn->recv_lock = NULL;
+	tcp_sw_conn->sock = NULL;
 	spin_unlock_bh(&session->lock);
 	sockfd_put(sock);
 }
 
-static void
-iscsi_tcp_conn_destroy(struct iscsi_cls_conn *cls_conn)
+static void iscsi_sw_tcp_conn_destroy(struct iscsi_cls_conn *cls_conn)
 {
 	struct iscsi_conn *conn = cls_conn->dd_data;
 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
 
-	iscsi_tcp_release_conn(conn);
-	iscsi_conn_teardown(cls_conn);
+	iscsi_sw_tcp_release_conn(conn);
 
-	if (tcp_conn->tx_tfm)
-		crypto_free_tfm(tcp_conn->tx_tfm);
-	if (tcp_conn->rx_tfm)
-		crypto_free_tfm(tcp_conn->rx_tfm);
+	if (tcp_sw_conn->tx_hash.tfm)
+		crypto_free_hash(tcp_sw_conn->tx_hash.tfm);
+	if (tcp_sw_conn->rx_hash.tfm)
+		crypto_free_hash(tcp_sw_conn->rx_hash.tfm);
 
-	kfree(tcp_conn);
+	iscsi_tcp_conn_teardown(cls_conn);
 }
 
-static void
-iscsi_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+static void iscsi_sw_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;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
+
+	/* userspace may have goofed up and not bound us */
+	if (!tcp_sw_conn->sock)
+		return;
+	/*
+	 * Make sure our recv side is stopped.
+	 * Older tools called conn stop before ep_disconnect
+	 * so IO could still be coming in.
+	 */
+	write_lock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock);
+	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
+	write_unlock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock);
 
-	iscsi_conn_stop(cls_conn, flag);
-	iscsi_tcp_release_conn(conn);
-	tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
+	iscsi2_conn_stop(cls_conn, flag);
+	iscsi_sw_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))
+static int iscsi_sw_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;
@@ -1917,12 +620,15 @@ free_addr:
 }
 
 static int
-iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
-		    struct iscsi_cls_conn *cls_conn, uint64_t transport_eph,
-		    int is_leading)
+iscsi_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session,
+		       struct iscsi_cls_conn *cls_conn, uint64_t transport_eph,
+		       int is_leading)
 {
+	struct Scsi_Host *shost = iscsi_session_to_shost(cls_session);
+	struct iscsi_host *ihost = shost_priv(shost);
 	struct iscsi_conn *conn = cls_conn->dd_data;
 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
 	struct sock *sk;
 	struct socket *sock;
 	int err;
@@ -1930,7 +636,8 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
 	/* lookup for existing socket */
 	sock = sockfd_lookup((int)transport_eph, &err);
 	if (!sock) {
-		printk(KERN_ERR "iscsi_tcp: sockfd_lookup failed %d\n", err);
+		iscsi_conn_printk(KERN_ERR, conn,
+				  "sockfd_lookup failed %d\n", err);
 		return -EEXIST;
 	}
 	/*
@@ -1938,22 +645,22 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_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);
+	err = iscsi_sw_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);
+	err = iscsi_sw_tcp_get_addr(conn, sock, ihost->local_address,
+				    &ihost->local_port, kernel_getsockname);
 	if (err)
 		goto free_socket;
 
-	err = iscsi_conn_bind(cls_session, cls_conn, is_leading);
+	err = iscsi2_conn_bind(cls_session, cls_conn, is_leading);
 	if (err)
 		goto free_socket;
 
 	/* bind iSCSI connection and socket */
-	tcp_conn->sock = sock;
+	tcp_sw_conn->sock = sock;
 
 	/* setup Socket parameters */
 	sk = sock->sk;
@@ -1961,19 +668,12 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
 	sk->sk_sndtimeo = 15 * HZ; /* FIXME: make it configurable */
 	sk->sk_allocation = GFP_ATOMIC;
 
-	/* FIXME: disable Nagle's algorithm */
-
-	/*
-	 * Intercept TCP callbacks for sendfile like receive
-	 * processing.
-	 */
-	conn->recv_lock = &sk->sk_callback_lock;
-	iscsi_conn_set_callbacks(conn);
-	tcp_conn->sendpage = tcp_conn->sock->ops->sendpage;
+	iscsi_sw_tcp_conn_set_callbacks(conn);
+	tcp_sw_conn->sendpage = tcp_sw_conn->sock->ops->sendpage;
 	/*
 	 * set receive state machine into initial state
 	 */
-	tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+	iscsi_tcp_hdr_recv_prep(tcp_conn);
 	return 0;
 
 free_socket:
@@ -1981,122 +681,45 @@ free_socket:
 	return err;
 }
 
-/* called with host lock */
-static void
-iscsi_tcp_mgmt_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask,
-		    char *data, uint32_t data_size)
-{
-	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
-	tcp_mtask->xmstate = 1 << XMSTATE_BIT_IMM_HDR_INIT;
-}
-
-static int
-iscsi_r2tpool_alloc(struct iscsi_session *session)
-{
-	int i;
-	int cmd_i;
-
-	/*
-	 * initialize per-task: R2T pool and xmit queue
-	 */
-	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
-	        struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
-		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
-
-		/*
-		 * pre-allocated x4 as much r2ts to handle race when
-		 * target acks DataOut faster than we data_xmit() queues
-		 * could replenish r2tqueue.
-		 */
-
-		/* R2T pool */
-		if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4,
-				    (void***)&tcp_ctask->r2ts,
-				    sizeof(struct iscsi_r2t_info))) {
-			goto r2t_alloc_fail;
-		}
-
-		/* R2T xmit queue */
-		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,
-					(void**)tcp_ctask->r2ts);
-			goto r2t_alloc_fail;
-		}
-	}
-
-	return 0;
-
-r2t_alloc_fail:
-	for (i = 0; i < cmd_i; i++) {
-		struct iscsi_cmd_task *ctask = session->cmds[i];
-		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
-
-		kfifo_free(tcp_ctask->r2tqueue);
-		iscsi_pool_free(&tcp_ctask->r2tpool,
-				(void**)tcp_ctask->r2ts);
-	}
-	return -ENOMEM;
-}
-
-static void
-iscsi_r2tpool_free(struct iscsi_session *session)
-{
-	int i;
-
-	for (i = 0; i < session->cmds_max; i++) {
-		struct iscsi_cmd_task *ctask = session->cmds[i];
-		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
-
-		kfifo_free(tcp_ctask->r2tqueue);
-		iscsi_pool_free(&tcp_ctask->r2tpool,
-				(void**)tcp_ctask->r2ts);
-	}
-}
-
-static int
-iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
-		     char *buf, int buflen)
+static int iscsi_sw_tcp_conn_set_param(struct iscsi_cls_conn *cls_conn,
+				       enum iscsi_param param, char *buf,
+				       int buflen)
 {
 	struct iscsi_conn *conn = cls_conn->dd_data;
 	struct iscsi_session *session = conn->session;
 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
 	int value;
 
 	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);
+		iscsi2_set_param(cls_conn, param, buf, buflen);
 		break;
 	case ISCSI_PARAM_DATADGST_EN:
-		iscsi_set_param(cls_conn, param, buf, buflen);
-		tcp_conn->sendpage = conn->datadgst_en ?
-			sock_no_sendpage : tcp_conn->sock->ops->sendpage;
+		iscsi2_set_param(cls_conn, param, buf, buflen);
+		tcp_sw_conn->sendpage = conn->datadgst_en ?
+			sock_no_sendpage : tcp_sw_conn->sock->ops->sendpage;
 		break;
 	case ISCSI_PARAM_MAX_R2T:
 		sscanf(buf, "%d", &value);
-		if (session->max_r2t == roundup_pow_of_two(value))
+		if (value <= 0 || !is_power_of_2(value))
+			return -EINVAL;
+		if (session->max_r2t == 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))
+		iscsi_tcp_r2tpool_free(session);
+		iscsi2_set_param(cls_conn, param, buf, buflen);
+		if (iscsi_tcp_r2tpool_alloc(session))
 			return -ENOMEM;
 		break;
 	default:
-		return iscsi_set_param(cls_conn, param, buf, buflen);
+		return iscsi2_set_param(cls_conn, param, buf, buflen);
 	}
 
 	return 0;
 }
 
-static int
-iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
-			 enum iscsi_param param, char *buf)
+static int iscsi_sw_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
+				       enum iscsi_param param, char *buf)
 {
 	struct iscsi_conn *conn = cls_conn->dd_data;
 	int len;
@@ -2113,172 +736,117 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
 		spin_unlock_bh(&conn->session->lock);
 		break;
 	default:
-		return iscsi_conn_get_param(cls_conn, param, buf);
+		return iscsi2_conn_get_param(cls_conn, param, buf);
 	}
 
 	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)
+iscsi_sw_tcp_conn_get_stats(struct iscsi_cls_conn *cls_conn,
+			    struct iscsi_stats *stats)
 {
 	struct iscsi_conn *conn = cls_conn->dd_data;
 	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
 
-	stats->txdata_octets = conn->txdata_octets;
-	stats->rxdata_octets = conn->rxdata_octets;
-	stats->scsicmd_pdus = conn->scsicmd_pdus_cnt;
-	stats->dataout_pdus = conn->dataout_pdus_cnt;
-	stats->scsirsp_pdus = conn->scsirsp_pdus_cnt;
-	stats->datain_pdus = conn->datain_pdus_cnt;
-	stats->r2t_pdus = conn->r2t_pdus_cnt;
-	stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt;
-	stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt;
 	stats->custom_length = 3;
 	strcpy(stats->custom[0].desc, "tx_sendpage_failures");
-	stats->custom[0].value = tcp_conn->sendpage_failures_cnt;
+	stats->custom[0].value = tcp_sw_conn->sendpage_failures_cnt;
 	strcpy(stats->custom[1].desc, "rx_discontiguous_hdr");
-	stats->custom[1].value = tcp_conn->discontiguous_hdr_cnt;
+	stats->custom[1].value = tcp_sw_conn->discontiguous_hdr_cnt;
 	strcpy(stats->custom[2].desc, "eh_abort_cnt");
 	stats->custom[2].value = conn->eh_abort_cnt;
-}
-
-static int iscsi_tcp_setup_tasks(struct iscsi_session *session)
-{
-	int cmd_i;
-
-	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
-		struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
-		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
-
-		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 = &tcp_mtask->hdr;
-	}
-
-	if (iscsi_r2tpool_alloc(session))
-		return -ENOMEM;
-
-	return 0;
+	iscsi_tcp_conn_get_stats(cls_conn, stats);
 }
 
-/*
- * iscsi_tcp_session_create2 is a KABI work around. For the new version
- * we pass in the session and default device queue depth.
- */
 static struct iscsi_cls_session *
-iscsi_tcp_session_create2(struct iscsi_transport *iscsit,
-			  struct scsi_transport_template *scsit,
-			  uint16_t cmds_max, uint16_t qdepth,
-			  uint32_t initial_cmdsn, uint32_t *hostno)
+iscsi_sw_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max,
+			    uint16_t qdepth, uint32_t initial_cmdsn)
 {
 	struct iscsi_cls_session *cls_session;
-	uint32_t hn;
+	struct iscsi_session *session;
+	struct Scsi_Host *shost;
 
-	cls_session = iscsi_session_setup2(iscsit, scsit, cmds_max, qdepth,
-					  sizeof(struct iscsi_tcp_cmd_task),
-					  sizeof(struct iscsi_tcp_mgmt_task),
-					  initial_cmdsn, &hn);
-	if (!cls_session)
+	if (ep) {
+		printk(KERN_ERR "iscsi_tcp: invalid ep %p.\n", ep);
 		return NULL;
-	*hostno = hn;
+	}
 
-	if (iscsi_tcp_setup_tasks(class_to_transport_session(cls_session)))
-		goto task_setup_fail;
+	shost = iscsi2_host_alloc(&iscsi_sw_tcp_sht, 0, 1);
+	if (!shost)
+		return NULL;
+	shost->transportt = iscsi_sw_tcp_scsi_transport;
+	shost->cmd_per_lun = qdepth;
+	shost->max_lun = iscsi_max_lun;
+	shost->max_id = 0;
+	shost->max_channel = 0;
+	shost->max_cmd_len = SCSI_MAX_VARLEN_CDB_SIZE;
+
+	if (iscsi2_host_add(shost, NULL))
+		goto free_host;
+
+	cls_session = iscsi2_session_setup(&iscsi_sw_tcp_transport, shost,
+					  cmds_max,
+					  sizeof(struct iscsi_tcp_task) +
+					  sizeof(struct iscsi_sw_tcp_hdrbuf),
+					  initial_cmdsn, 0);
+	if (!cls_session)
+		goto remove_host;
+	session = cls_session->dd_data;
 
+	shost->can_queue = session->scsi_cmds_max;
+	if (iscsi_tcp_r2tpool_alloc(session))
+		goto remove_session;
 	return cls_session;
 
-task_setup_fail:
-	iscsi_session_teardown(cls_session);
+remove_session:
+	iscsi2_session_teardown(cls_session);
+remove_host:
+	iscsi2_host_remove(shost);
+free_host:
+	iscsi2_host_free(shost);
 	return NULL;
 }
 
-static struct iscsi_cls_session *
-iscsi_tcp_session_create(struct iscsi_transport *iscsit,
-			 struct scsi_transport_template *scsit,
-			 uint32_t initial_cmdsn, uint32_t *hostno)
+static void iscsi_sw_tcp_session_destroy(struct iscsi_cls_session *cls_session)
 {
-	struct iscsi_cls_session *cls_session;
-	uint32_t hn;
-
-	cls_session = iscsi_session_setup2(iscsit, scsit, ISCSI_XMIT_CMDS_MAX,
-					   ISCSI_DEF_CMD_PER_LUN,
-					   sizeof(struct iscsi_tcp_cmd_task),
-					   sizeof(struct iscsi_tcp_mgmt_task),
-					   initial_cmdsn, &hn);
-	if (!cls_session)
-		return NULL;
-	*hostno = hn;
-
-	if (iscsi_tcp_setup_tasks(class_to_transport_session(cls_session)))
-		goto task_setup_fail;
-
-	return cls_session;
+	struct Scsi_Host *shost = iscsi_session_to_shost(cls_session);
 
-task_setup_fail:
-	iscsi_session_teardown(cls_session);
-	return NULL;
-}
+	iscsi_tcp_r2tpool_free(cls_session->dd_data);
+	iscsi2_session_teardown(cls_session);
 
-static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
-{
-	iscsi_r2tpool_free(class_to_transport_session(cls_session));
-	iscsi_session_teardown(cls_session);
+	iscsi2_host_remove(shost);
+	iscsi2_host_free(shost);
 }
 
-static int iscsi_tcp_slave_configure(struct scsi_device *sdev)
+static int iscsi_sw_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 = {
+static struct scsi_host_template iscsi_sw_tcp_sht = {
 	.module			= THIS_MODULE,
 	.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,
+	.queuecommand           = iscsi2_queuecommand,
+	.change_queue_depth	= iscsi2_change_queue_depth,
+	.can_queue		= ISCSI_DEF_XMIT_CMDS_MAX - 1,
+	.sg_tablesize		= 4096,
 	.max_sectors		= 0xFFFF,
 	.cmd_per_lun		= ISCSI_DEF_CMD_PER_LUN,
-	.eh_abort_handler       = iscsi_eh_abort,
-	.eh_host_reset_handler	= iscsi_eh_host_reset,
+	.eh_abort_handler       = iscsi2_eh_abort,
+	.eh_device_reset_handler= iscsi2_eh_device_reset,
+	.eh_host_reset_handler	= iscsi2_eh_target_reset,
 	.use_clustering         = DISABLE_CLUSTERING,
-	.slave_configure	= iscsi_tcp_slave_configure,
+	.slave_configure        = iscsi_sw_tcp_slave_configure,
 	.proc_name		= "iscsi_tcp",
 	.this_id		= -1,
 };
 
-static struct iscsi_transport iscsi_tcp_transport = {
+static struct iscsi_transport iscsi_sw_tcp_transport = {
 	.owner			= THIS_MODULE,
 	.name			= "tcp",
 	.caps			= CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST
@@ -2303,63 +871,63 @@ static struct iscsi_transport iscsi_tcp_transport = {
 				  ISCSI_TARGET_NAME | ISCSI_TPGT |
 				  ISCSI_USERNAME | ISCSI_PASSWORD |
 				  ISCSI_USERNAME_IN | ISCSI_PASSWORD_IN |
-				  ISCSI_PING_TMO | ISCSI_RECV_TMO,
+				  ISCSI_FAST_ABORT | ISCSI_ABORT_TMO |
+				  ISCSI_LU_RESET_TMO |
+				  ISCSI_PING_TMO | ISCSI_RECV_TMO |
+				  ISCSI_IFACE_NAME | ISCSI_INITIATOR_NAME,
 	.host_param_mask	= ISCSI_HOST_HWADDRESS | ISCSI_HOST_IPADDRESS |
 				  ISCSI_HOST_INITIATOR_NAME |
 				  ISCSI_HOST_NETDEV_NAME,
-	.host_template		= &iscsi_sht,
-	.conndata_size		= sizeof(struct iscsi_conn),
-	.max_conn		= 1,
-	.max_cmd_len		= ISCSI_TCP_MAX_CMD_LEN,
 	/* session management */
-	.create_session		= iscsi_tcp_session_create,
-	.create_session2	= iscsi_tcp_session_create2,
-	.destroy_session	= iscsi_tcp_session_destroy,
+	.create_session		= iscsi_sw_tcp_session_create,
+	.destroy_session	= iscsi_sw_tcp_session_destroy,
 	/* connection management */
-	.create_conn		= iscsi_tcp_conn_create,
-	.bind_conn		= iscsi_tcp_conn_bind,
-	.destroy_conn		= iscsi_tcp_conn_destroy,
-	.set_param		= iscsi_conn_set_param,
-	.get_conn_param		= iscsi_tcp_conn_get_param,
-	.get_session_param	= iscsi_session_get_param,
-	.start_conn		= iscsi_conn_start,
-	.stop_conn		= iscsi_tcp_conn_stop,
+	.create_conn		= iscsi_sw_tcp_conn_create,
+	.bind_conn		= iscsi_sw_tcp_conn_bind,
+	.destroy_conn		= iscsi_sw_tcp_conn_destroy,
+	.set_param		= iscsi_sw_tcp_conn_set_param,
+	.get_conn_param		= iscsi_sw_tcp_conn_get_param,
+	.get_session_param	= iscsi2_session_get_param,
+	.start_conn		= iscsi2_conn_start,
+	.stop_conn		= iscsi_sw_tcp_conn_stop,
 	/* iscsi host params */
-	.get_host_param		= iscsi_tcp_host_get_param,
-	.set_host_param		= iscsi_host_set_param,
+	.get_host_param		= iscsi2_host_get_param,
+	.set_host_param		= iscsi2_host_set_param,
 	/* IO */
-	.send_pdu		= iscsi_conn_send_pdu,
-	.get_stats		= iscsi_conn_get_stats,
-	.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,
+	.send_pdu		= iscsi2_conn_send_pdu,
+	.get_stats		= iscsi_sw_tcp_conn_get_stats,
+	/* iscsi task/cmd helpers */
+	.init_task		= iscsi_tcp_task_init,
+	.xmit_task		= iscsi_tcp_task_xmit,
+	.cleanup_task		= iscsi_tcp_cleanup_task,
+	/* low level pdu helpers */
+	.xmit_pdu		= iscsi_sw_tcp_pdu_xmit,
+	.init_pdu		= iscsi_sw_tcp_pdu_init,
+	.alloc_pdu		= iscsi_sw_tcp_pdu_alloc,
 	/* recovery */
-	.session_recovery_timedout = iscsi_session_recovery_timedout,
+	.session_recovery_timedout = iscsi2_session_recovery_timedout,
 };
 
-static int __init
-iscsi_tcp_init(void)
+static int __init iscsi_sw_tcp_init(void)
 {
 	if (iscsi_max_lun < 1) {
 		printk(KERN_ERR "iscsi_tcp: Invalid max_lun value of %u\n",
 		       iscsi_max_lun);
 		return -EINVAL;
 	}
-	iscsi_tcp_transport.max_lun = iscsi_max_lun;
 
-	if (!iscsi_register_transport(&iscsi_tcp_transport))
+	iscsi_sw_tcp_scsi_transport = iscsi2_register_transport(
+						&iscsi_sw_tcp_transport);
+	if (!iscsi_sw_tcp_scsi_transport)
 		return -ENODEV;
 
 	return 0;
 }
 
-static void __exit
-iscsi_tcp_exit(void)
+static void __exit iscsi_sw_tcp_exit(void)
 {
-	iscsi_unregister_transport(&iscsi_tcp_transport);
+	iscsi2_unregister_transport(&iscsi_sw_tcp_transport);
 }
 
-module_init(iscsi_tcp_init);
-module_exit(iscsi_tcp_exit);
+module_init(iscsi_sw_tcp_init);
+module_exit(iscsi_sw_tcp_exit);
diff --git a/drivers/scsi/iscsi_tcp.h b/drivers/scsi/iscsi_tcp.h
index b91b486..5912ccd 100644
--- a/drivers/scsi/iscsi_tcp.h
+++ b/drivers/scsi/iscsi_tcp.h
@@ -19,84 +19,36 @@
  * See the file COPYING included with this distribution for more details.
  */
 
-#ifndef ISCSI_TCP_H
-#define ISCSI_TCP_H
+#ifndef ISCSI_SW_TCP_H
+#define ISCSI_SW_TCP_H
 
-#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
-#define IN_PROGRESS_PAD_RECV		0x4
-
-/* xmit state machine */
-#define XMSTATE_VALUE_IDLE			0
-#define XMSTATE_BIT_CMD_HDR_INIT		0
-#define XMSTATE_BIT_CMD_HDR_XMIT		1
-#define XMSTATE_BIT_IMM_HDR			2
-#define XMSTATE_BIT_IMM_DATA			3
-#define XMSTATE_BIT_UNS_INIT			4
-#define XMSTATE_BIT_UNS_HDR			5
-#define XMSTATE_BIT_UNS_DATA			6
-#define XMSTATE_BIT_SOL_HDR			7
-#define XMSTATE_BIT_SOL_DATA			8
-#define XMSTATE_BIT_W_PAD			9
-#define XMSTATE_BIT_W_RESEND_PAD		10
-#define XMSTATE_BIT_W_RESEND_DATA_DIGEST	11
-#define XMSTATE_BIT_IMM_HDR_INIT		12
-#define XMSTATE_BIT_SOL_HDR_INIT		13
-
-#define ISCSI_PAD_LEN			4
-#define ISCSI_SG_TABLESIZE		SG_ALL
-#define ISCSI_TCP_MAX_CMD_LEN		16
+#include <scsi/iscsi_compat2.h>
+#include <scsi/libiscsi2.h>
+#include <scsi/libiscsi_tcp.h>
 
 struct socket;
+struct iscsi_tcp_conn;
 
-/* Socket connection recieve helper */
-struct iscsi_tcp_recv {
+/* Socket connection send helper */
+struct iscsi_sw_tcp_send {
 	struct iscsi_hdr	*hdr;
-	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;
-	int			datadgst;
-	char			zero_copy_hdr;
+	struct iscsi_segment	segment;
+	struct iscsi_segment	data_segment;
 };
 
-struct iscsi_tcp_conn {
+struct iscsi_sw_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 */
-	int			in_progress;	/* connection state machine */
 
+	struct iscsi_sw_tcp_send out;
 	/* old values for socket callbacks */
 	void			(*old_data_ready)(struct sock *, int);
 	void			(*old_state_change)(struct sock *);
 	void			(*old_write_space)(struct sock *);
 
 	/* data and header digests */
-	struct crypto_tfm	*tx_tfm;	/* CRC32C (Tx) */
-	struct crypto_tfm	*rx_tfm;	/* CRC32C (Rx) */
+	struct hash_desc	tx_hash;	/* CRC32C (Tx) */
+	struct hash_desc	rx_hash;	/* CRC32C (Rx) */
 
 	/* MIB custom statistics */
 	uint32_t		sendpage_failures_cnt;
@@ -105,65 +57,10 @@ struct iscsi_tcp_conn {
 	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[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[sizeof(__u32)]; /* Header-Digest */
-	unsigned long		xmstate;	/* mgmt xmit progress */
-	struct iscsi_buf	headbuf;	/* header buffer */
-	struct iscsi_buf	sendbuf;	/* in progress buffer */
-	int			sent;
-};
-
-struct iscsi_r2t_info {
-	__be32			ttt;		/* copied from R2T */
-	__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;        /* which data task */
-};
-
-struct iscsi_tcp_cmd_task {
-	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*/
-	unsigned long		xmstate;		/* xmit xtate machine */
-	int			sent;
-	struct scatterlist	*sg;			/* per-cmd SG list  */
-	struct scatterlist	*bad_sg;		/* assert statement */
-	int			sg_count;		/* SG's to process  */
-	uint32_t		exp_datasn;		/* expected target's R2TSN/DataSN */
-	int			data_offset;
-	struct iscsi_r2t_info	*r2t;			/* in progress R2T    */
-	struct iscsi_queue	r2tpool;
-	struct kfifo		*r2tqueue;
-	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 */
+struct iscsi_sw_tcp_hdrbuf {
+	struct iscsi_hdr	hdrbuf;
+	char			hdrextbuf[ISCSI_MAX_AHS_SIZE +
+		                                  ISCSI_DIGEST_SIZE];
 };
 
-#endif /* ISCSI_H */
+#endif /* ISCSI_SW_TCP_H */
diff --git a/drivers/scsi/libiscsi2.c b/drivers/scsi/libiscsi2.c
new file mode 100644
index 0000000..7d909aa
--- /dev/null
+++ b/drivers/scsi/libiscsi2.c
@@ -0,0 +1,2936 @@
+/*
+ * iSCSI lib functions
+ *
+ * Copyright (C) 2006 Red Hat, Inc.  All rights reserved.
+ * Copyright (C) 2004 - 2006 Mike Christie
+ * Copyright (C) 2004 - 2005 Dmitry Yusupov
+ * Copyright (C) 2004 - 2005 Alex Aizman
+ * maintained by open-iscsi@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#include <linux/types.h>
+#include <linux/kfifo.h>
+#include <linux/delay.h>
+#include <linux/version.h>
+#include <asm/unaligned.h>
+#include <net/tcp.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_transport.h>
+#include <scsi/iscsi_compat2.h>
+#include <scsi/iscsi_proto2.h>
+#include <scsi/scsi_transport_iscsi2.h>
+#include <scsi/libiscsi2.h>
+#include <scsi/iscsi_compat2.h>
+
+static int iscsi_dbg_lib;
+module_param_named(debug_libiscsi, iscsi_dbg_lib, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug_libiscsi, "Turn on debugging for libiscsi module. "
+		 "Set to 1 to turn on, and zero to turn off. Default "
+		 "is off.");
+
+#define ISCSI_DBG_CONN(_conn, dbg_fmt, arg...)			\
+	do {							\
+		if (iscsi_dbg_lib)				\
+			iscsi_conn_printk(KERN_INFO, _conn,	\
+					     "%s " dbg_fmt,	\
+					     __func__, ##arg);	\
+	} while (0);
+
+#define ISCSI_DBG_SESSION(_session, dbg_fmt, arg...)			\
+	do {								\
+		if (iscsi_dbg_lib)					\
+			iscsi_session_printk(KERN_INFO, _session,	\
+					     "%s " dbg_fmt,		\
+					     __func__, ##arg);		\
+	} while (0);
+
+/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */
+#define SNA32_CHECK 2147483648UL
+
+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)));
+}
+
+inline void iscsi_conn_queue_work(struct iscsi_conn *conn)
+{
+	struct Scsi_Host *shost = conn->session->host;
+	struct iscsi_host *ihost = shost_priv(shost);
+
+	queue_work(ihost->workq, &conn->xmitwork);
+}
+EXPORT_SYMBOL_GPL(iscsi_conn_queue_work);
+
+void
+iscsi2_update_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))
+		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)) {
+			if (!(session->tt->caps & CAP_DATA_PATH_OFFLOAD))
+				iscsi_conn_queue_work(session->leadconn);
+		}
+	}
+}
+EXPORT_SYMBOL_GPL(iscsi2_update_cmdsn);
+
+/**
+ * iscsi_prep_data_out_pdu - initialize Data-Out
+ * @task: scsi command task
+ * @r2t: R2T info
+ * @hdr: iscsi data in pdu
+ *
+ * Notes:
+ *	Initialize Data-Out within this R2T sequence and finds
+ *	proper data_offset within this SCSI command.
+ *
+ *	This function is called with connection lock taken.
+ **/
+void iscsi2_prep_data_out_pdu(struct iscsi_task *task, struct iscsi_r2t_info *r2t,
+			   struct iscsi_data *hdr)
+{
+	struct iscsi_conn *conn = task->conn;
+	unsigned int left = r2t->data_length - r2t->sent;
+
+	task->hdr_len = sizeof(struct iscsi_data);
+
+	memset(hdr, 0, sizeof(struct iscsi_data));
+	hdr->ttt = r2t->ttt;
+	hdr->datasn = cpu_to_be32(r2t->datasn);
+	r2t->datasn++;
+	hdr->opcode = ISCSI_OP_SCSI_DATA_OUT;
+	memcpy(hdr->lun, task->lun, sizeof(hdr->lun));
+	hdr->itt = task->hdr_itt;
+	hdr->exp_statsn = r2t->exp_statsn;
+	hdr->offset = cpu_to_be32(r2t->data_offset + r2t->sent);
+	if (left > conn->max_xmit_dlength) {
+		hton24(hdr->dlength, conn->max_xmit_dlength);
+		r2t->data_count = conn->max_xmit_dlength;
+		hdr->flags = 0;
+	} else {
+		hton24(hdr->dlength, left);
+		r2t->data_count = left;
+		hdr->flags = ISCSI_FLAG_CMD_FINAL;
+	}
+	conn->dataout_pdus_cnt++;
+}
+EXPORT_SYMBOL_GPL(iscsi2_prep_data_out_pdu);
+
+static int iscsi_add_hdr(struct iscsi_task *task, unsigned len)
+{
+	unsigned exp_len = task->hdr_len + len;
+
+	if (exp_len > task->hdr_max) {
+		WARN_ON(1);
+		return -EINVAL;
+	}
+
+	WARN_ON(len & (ISCSI_PAD_LEN - 1)); /* caller must pad the AHS */
+	task->hdr_len = exp_len;
+	return 0;
+}
+
+/*
+ * make an extended cdb AHS
+ */
+static int iscsi_prep_ecdb_ahs(struct iscsi_task *task)
+{
+	struct scsi_cmnd *cmd = task->sc;
+	unsigned rlen, pad_len;
+	unsigned short ahslength;
+	struct iscsi_ecdb_ahdr *ecdb_ahdr;
+	int rc;
+
+	ecdb_ahdr = iscsi_next_hdr(task);
+	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(task, 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);
+
+	ISCSI_DBG_SESSION(task->conn->session,
+			  "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,
+		          task->hdr_len);
+	return 0;
+}
+
+static int iscsi_prep_bidi_ahs(struct iscsi_task *task)
+{
+	struct scsi_cmnd *sc = task->sc;
+	struct iscsi_rlength_ahdr *rlen_ahdr;
+	int rc;
+
+	rlen_ahdr = iscsi_next_hdr(task);
+	rc = iscsi_add_hdr(task, 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_bufflen(sc));
+
+	ISCSI_DBG_SESSION(task->conn->session,
+			  "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
+ * @task: iscsi task
+ *
+ * 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_task *task)
+{
+	struct iscsi_conn *conn = task->conn;
+	struct iscsi_session *session = conn->session;
+	struct scsi_cmnd *sc = task->sc;
+	struct iscsi_cmd *hdr;
+	unsigned hdrlength, cmd_len;
+	itt_t itt;
+	int rc;
+
+	rc = conn->session->tt->alloc_pdu(task, ISCSI_OP_SCSI_CMD);
+	if (rc)
+		return rc;
+	hdr = (struct iscsi_cmd *) task->hdr;
+	itt = hdr->itt;
+	memset(hdr, 0, sizeof(*hdr));
+
+	if (session->tt->parse_pdu_itt)
+		hdr->itt = task->hdr_itt = itt;
+	else
+		hdr->itt = task->hdr_itt = build_itt(task->itt,
+						     task->conn->session->age);
+	task->hdr_len = 0;
+	rc = iscsi_add_hdr(task, 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);
+	memcpy(task->lun, hdr->lun, sizeof(task->lun));
+	hdr->cmdsn = task->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(task);
+		if (rc)
+			return rc;
+		cmd_len = ISCSI_CDB_SIZE;
+	}
+	memcpy(hdr->cdb, sc->cmnd, cmd_len);
+
+	task->imm_count = 0;
+	if (scsi_bidi_cmnd(sc)) {
+		hdr->flags |= ISCSI_FLAG_CMD_READ;
+		rc = iscsi_prep_bidi_ahs(task);
+		if (rc)
+			return rc;
+	}
+	if (sc->sc_data_direction == DMA_TO_DEVICE) {
+		unsigned out_len = scsi_bufflen(sc);
+		struct iscsi_r2t_info *r2t = &task->unsol_r2t;
+
+		hdr->data_length = cpu_to_be32(out_len);
+		hdr->flags |= ISCSI_FLAG_CMD_WRITE;
+		/*
+		 * Write counters:
+		 *
+		 *	imm_count	bytes to be sent right after
+		 *			SCSI PDU Header
+		 *
+		 *	unsol_count	bytes(as Data-Out) to be sent
+		 *			without	R2T ack right after
+		 *			immediate data
+		 *
+		 *	r2t data_length bytes to be sent via R2T ack's
+		 *
+		 *      pad_count       bytes to be sent as zero-padding
+		 */
+		memset(r2t, 0, sizeof(*r2t));
+
+		if (session->imm_data_en) {
+			if (out_len >= session->first_burst)
+				task->imm_count = min(session->first_burst,
+							conn->max_xmit_dlength);
+			else
+				task->imm_count = min(out_len,
+							conn->max_xmit_dlength);
+			hton24(hdr->dlength, task->imm_count);
+		} else
+			zero_data(hdr->dlength);
+
+		if (!session->initial_r2t_en) {
+			r2t->data_length = min(session->first_burst, out_len) -
+					       task->imm_count;
+			r2t->data_offset = task->imm_count;
+			r2t->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);
+			r2t->exp_statsn = cpu_to_be32(conn->exp_statsn);
+		}
+
+		if (!task->unsol_r2t.data_length)
+			/* No unsolicit Data-Out's */
+			hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+	} else {
+		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
+		zero_data(hdr->dlength);
+		hdr->data_length = cpu_to_be32(scsi_bufflen(sc));
+
+		if (sc->sc_data_direction == DMA_FROM_DEVICE)
+			hdr->flags |= ISCSI_FLAG_CMD_READ;
+	}
+
+	/* calculate size of additional header segments (AHSs) */
+	hdrlength = task->hdr_len - sizeof(*hdr);
+
+	WARN_ON(hdrlength & (ISCSI_PAD_LEN-1));
+	hdrlength /= ISCSI_PAD_LEN;
+
+	WARN_ON(hdrlength >= 256);
+	hdr->hlength = hdrlength & 0xFF;
+
+	if (session->tt->init_task && session->tt->init_task(task))
+		return -EIO;
+
+	task->state = ISCSI_TASK_RUNNING;
+	list_move_tail(&task->running, &conn->run_list);
+
+	conn->scsicmd_pdus_cnt++;
+	ISCSI_DBG_SESSION(session, "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],
+			   task->itt, scsi_bufflen(sc),
+			   scsi_bidi_cmnd(sc) ? scsi_bufflen(sc) : 0,
+			   session->cmdsn,
+			   session->max_cmdsn - session->exp_cmdsn + 1);
+	return 0;
+}
+
+/**
+ * iscsi_complete_command - finish a task
+ * @task: iscsi cmd task
+ *
+ * Must be called with session lock.
+ * This function returns the scsi command to scsi-ml or cleans
+ * up mgmt tasks then returns the task to the pool.
+ */
+static void iscsi_complete_command(struct iscsi_task *task)
+{
+	struct iscsi_conn *conn = task->conn;
+	struct iscsi_session *session = conn->session;
+	struct scsi_cmnd *sc = task->sc;
+
+	session->tt->cleanup_task(task);
+	list_del_init(&task->running);
+	task->state = ISCSI_TASK_COMPLETED;
+	task->sc = NULL;
+
+	if (conn->task == task)
+		conn->task = NULL;
+	/*
+	 * login task is preallocated so do not free
+	 */
+	if (conn->login_task == task)
+		return;
+
+	__kfifo_put(session->cmdpool.queue, (void*)&task, sizeof(void*));
+
+	if (conn->ping_task == task)
+		conn->ping_task = NULL;
+
+	if (sc) {
+		task->sc = NULL;
+		/* SCSI eh reuses commands to verify us */
+		sc->SCp.ptr = NULL;
+		/*
+		 * queue command may call this to free the task, but
+		 * not have setup the sc callback
+		 */
+		if (sc->scsi_done)
+			sc->scsi_done(sc);
+	}
+}
+
+void __iscsi2_get_task(struct iscsi_task *task)
+{
+	atomic_inc(&task->refcount);
+}
+EXPORT_SYMBOL_GPL(__iscsi2_get_task);
+
+static void __iscsi_put_task(struct iscsi_task *task)
+{
+	if (atomic_dec_and_test(&task->refcount))
+		iscsi_complete_command(task);
+}
+
+void iscsi2_put_task(struct iscsi_task *task)
+{
+	struct iscsi_session *session = task->conn->session;
+
+	spin_lock_bh(&session->lock);
+	__iscsi_put_task(task);
+	spin_unlock_bh(&session->lock);
+}
+EXPORT_SYMBOL_GPL(iscsi2_put_task);
+
+/*
+ * session lock must be held
+ */
+static void fail_command(struct iscsi_conn *conn, struct iscsi_task *task,
+			 int err)
+{
+	struct scsi_cmnd *sc;
+
+	sc = task->sc;
+	if (!sc)
+		return;
+
+	if (task->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--;
+
+	sc->result = err;
+	scsi_set_resid(sc, scsi_bufflen(sc));
+
+	if (conn->task == task)
+		conn->task = NULL;
+	/* release ref from queuecommand */
+	__iscsi_put_task(task);
+}
+
+static int iscsi_prep_mgmt_task(struct iscsi_conn *conn,
+				struct iscsi_task *task)
+{
+	struct iscsi_session *session = conn->session;
+	struct iscsi_hdr *hdr = task->hdr;
+	struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr;
+
+	if (conn->session->state == ISCSI_STATE_LOGGING_OUT)
+		return -ENOTCONN;
+
+	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) {
+		/*
+		 * 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_task && session->tt->init_task(task))
+		return -EIO;
+
+	if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT)
+		session->state = ISCSI_STATE_LOGGING_OUT;
+
+	task->state = ISCSI_TASK_RUNNING;
+	list_move_tail(&task->running, &conn->mgmt_run_list);
+	ISCSI_DBG_SESSION(session, "mgmtpdu [op 0x%x hdr->itt 0x%x "
+			  "datalen %d]\n", hdr->opcode & ISCSI_OPCODE_MASK,
+			  hdr->itt, task->data_count);
+	return 0;
+}
+
+static struct iscsi_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_task *task;
+	itt_t itt;
+
+	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 task can be used. Same ITT must be used.
+		 * Note that login_task is preallocated at conn_create().
+		 */
+		task = conn->login_task;
+	else {
+		BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
+		BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
+
+		if (!__kfifo_get(session->cmdpool.queue,
+				 (void*)&task, sizeof(void*)))
+			return NULL;
+	}
+	/*
+	 * released in complete pdu for task we expect a response for, and
+	 * released by the lld when it has transmitted the task for
+	 * pdus we do not expect a response for.
+	 */
+	atomic_set(&task->refcount, 1);
+	task->conn = conn;
+	task->sc = NULL;
+
+	if (data_size) {
+		memcpy(task->data, data, data_size);
+		task->data_count = data_size;
+	} else
+		task->data_count = 0;
+
+	if (conn->session->tt->alloc_pdu(task, hdr->opcode)) {
+		iscsi_conn_printk(KERN_ERR, conn, "Could not allocate "
+				 "pdu for mgmt task.\n");
+		goto requeue_task;
+	}
+	itt = task->hdr->itt;
+	task->hdr_len = sizeof(struct iscsi_hdr);
+	memcpy(task->hdr, hdr, sizeof(struct iscsi_hdr));
+
+	if (hdr->itt != RESERVED_ITT) {
+		if (session->tt->parse_pdu_itt)
+			task->hdr->itt = itt;
+		else
+			task->hdr->itt = build_itt(task->itt,
+						   task->conn->session->age);
+	}
+
+	INIT_LIST_HEAD(&task->running);
+	list_add_tail(&task->running, &conn->mgmtqueue);
+
+	if (session->tt->caps & CAP_DATA_PATH_OFFLOAD) {
+		if (iscsi_prep_mgmt_task(conn, task))
+			goto free_task;
+
+		if (session->tt->xmit_task(task))
+			goto free_task;
+
+	} else
+		iscsi_conn_queue_work(conn);
+
+	return task;
+
+free_task:
+	__iscsi_put_task(task);
+	return NULL;
+
+requeue_task:
+	if (task != conn->login_task)
+		__kfifo_put(session->cmdpool.queue, (void*)&task,
+			    sizeof(void*));
+	return NULL;
+}
+
+int iscsi2_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);
+	return err;
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_send_pdu);
+
+/**
+ * iscsi_cmd_rsp - SCSI Command Response processing
+ * @conn: iscsi connection
+ * @hdr: iscsi header
+ * @task: scsi command task
+ * @data: cmd data buffer
+ * @datalen: len of buffer
+ *
+ * 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_task *task, char *data,
+			       int datalen)
+{
+	struct iscsi_cmd_rsp *rhdr = (struct iscsi_cmd_rsp *)hdr;
+	struct iscsi_session *session = conn->session;
+	struct scsi_cmnd *sc = task->sc;
+
+	iscsi2_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
+	conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+
+	sc->result = (DID_OK << 16) | rhdr->cmd_status;
+
+	if (rhdr->response != ISCSI_STATUS_CMD_COMPLETED) {
+		sc->result = DID_ERROR << 16;
+		goto out;
+	}
+
+	if (rhdr->cmd_status == SAM_STAT_CHECK_CONDITION) {
+		uint16_t senselen;
+
+		if (datalen < 2) {
+invalid_datalen:
+			iscsi_conn_printk(KERN_ERR,  conn,
+					 "Got CHECK_CONDITION but invalid data "
+					 "buffer size of %d\n", datalen);
+			sc->result = DID_BAD_TARGET << 16;
+			goto out;
+		}
+
+		senselen = be16_to_cpu(get_unaligned((__be16 *) data));
+		if (datalen < senselen)
+			goto invalid_datalen;
+
+		memcpy(sc->sense_buffer, data + 2,
+		       min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE));
+		ISCSI_DBG_SESSION(session, "copied %d bytes of sense\n",
+				  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_bufflen(sc)))
+			scsi_set_resid(sc, 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);
+
+		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;
+	}
+out:
+	ISCSI_DBG_SESSION(session, "done [sc %p res %d itt 0x%x]\n",
+			  sc, sc->result, task->itt);
+	conn->scsirsp_pdus_cnt++;
+
+	__iscsi_put_task(task);
+}
+
+/**
+ * iscsi_data_in_rsp - SCSI Data-In Response processing
+ * @conn: iscsi connection
+ * @hdr:  iscsi pdu
+ * @task: scsi command task
+ **/
+static void
+iscsi_data_in_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+		  struct iscsi_task *task)
+{
+	struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)hdr;
+	struct scsi_cmnd *sc = task->sc;
+
+	if (!(rhdr->flags & ISCSI_FLAG_DATA_STATUS))
+		return;
+
+	sc->result = (DID_OK << 16) | rhdr->cmd_status;
+	conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
+	if (rhdr->flags & (ISCSI_FLAG_DATA_UNDERFLOW |
+	                   ISCSI_FLAG_DATA_OVERFLOW)) {
+		int res_count = be32_to_cpu(rhdr->residual_count);
+
+		if (res_count > 0 &&
+		    (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
+		     res_count <= scsi_bufflen(sc)))
+			scsi_set_resid(sc, res_count);
+		else
+			sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status;
+	}
+
+	conn->scsirsp_pdus_cnt++;
+	__iscsi_put_task(task);
+}
+
+static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
+{
+	struct iscsi_tm_rsp *tmf = (struct iscsi_tm_rsp *)hdr;
+
+	conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+	conn->tmfrsp_pdus_cnt++;
+
+	if (conn->tmf_state != TMF_QUEUED)
+		return;
+
+	if (tmf->response == ISCSI_TMF_RSP_COMPLETE)
+		conn->tmf_state = TMF_SUCCESS;
+	else if (tmf->response == ISCSI_TMF_RSP_NO_TASK)
+		conn->tmf_state = TMF_NOT_FOUND;
+	else
+		conn->tmf_state = TMF_FAILED;
+	wake_up(&conn->ehwait);
+}
+
+static void iscsi_send_nopout(struct iscsi_conn *conn, struct iscsi_nopin *rhdr)
+{
+        struct iscsi_nopout hdr;
+	struct iscsi_task *task;
+
+	if (!rhdr && conn->ping_task)
+		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;
+
+	task = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)&hdr, NULL, 0);
+	if (!task)
+		iscsi_conn_printk(KERN_ERR, conn, "Could not send nopout\n");
+	else if (!rhdr) {
+		/* only track our nops */
+		conn->ping_task = task;
+		conn->last_ping = jiffies;
+	}
+}
+
+static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+			       char *data, int datalen)
+{
+	struct iscsi_reject *reject = (struct iscsi_reject *)hdr;
+	struct iscsi_hdr rejected_pdu;
+
+	conn->exp_statsn = be32_to_cpu(reject->statsn) + 1;
+
+	if (reject->reason == ISCSI_REASON_DATA_DIGEST_ERROR) {
+		if (ntoh24(reject->dlength) > datalen)
+			return ISCSI_ERR_PROTO;
+
+		if (ntoh24(reject->dlength) >= sizeof(struct iscsi_hdr)) {
+			memcpy(&rejected_pdu, data, sizeof(struct iscsi_hdr));
+			iscsi_conn_printk(KERN_ERR, conn,
+					  "pdu (op 0x%x) rejected "
+					  "due to DataDigest error.\n",
+					  rejected_pdu.opcode);
+		}
+	}
+	return 0;
+}
+
+/**
+ * iscsi_itt_to_task - look up task by itt
+ * @conn: iscsi connection
+ * @itt: itt
+ *
+ * This should be used for mgmt tasks like login and nops, or if
+ * the LDD's itt space does not include the session age.
+ *
+ * The session lock must be held.
+ */
+static struct iscsi_task *iscsi_itt_to_task(struct iscsi_conn *conn, itt_t itt)
+{
+	struct iscsi_session *session = conn->session;
+	int i;
+
+	if (itt == RESERVED_ITT)
+		return NULL;
+
+	if (session->tt->parse_pdu_itt)
+		session->tt->parse_pdu_itt(conn, itt, &i, NULL);
+	else
+		i = get_itt(itt);
+	if (i >= session->cmds_max)
+		return NULL;
+
+	return session->cmds[i];
+}
+
+/**
+ * __iscsi_complete_pdu - complete pdu
+ * @conn: iscsi conn
+ * @hdr: iscsi header
+ * @data: data buffer
+ * @datalen: len of data buffer
+ *
+ * Completes pdu processing by freeing any resources allocated at
+ * queuecommand or send generic. session lock must be held and verify
+ * itt must have been called.
+ */
+int __iscsi2_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;
+	struct iscsi_task *task;
+	uint32_t itt;
+
+	conn->last_recv = jiffies;
+	rc = iscsi2_verify_itt(conn, hdr->itt);
+	if (rc)
+		return rc;
+
+	if (hdr->itt != RESERVED_ITT)
+		itt = get_itt(hdr->itt);
+	else
+		itt = ~0U;
+
+	ISCSI_DBG_SESSION(session, "[op 0x%x cid %d itt 0x%x len %d]\n",
+			  opcode, conn->id, itt, datalen);
+
+	if (itt == ~0U) {
+		iscsi2_update_cmdsn(session, (struct iscsi_nopin*)hdr);
+
+		switch(opcode) {
+		case ISCSI_OP_NOOP_IN:
+			if (datalen) {
+				rc = ISCSI_ERR_PROTO;
+				break;
+			}
+
+			if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG))
+				break;
+
+			iscsi_send_nopout(conn, (struct iscsi_nopin*)hdr);
+			break;
+		case ISCSI_OP_REJECT:
+			rc = iscsi_handle_reject(conn, hdr, data, datalen);
+			break;
+		case ISCSI_OP_ASYNC_EVENT:
+			conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+			if (iscsi2_recv_pdu(conn->cls_conn, hdr, data, datalen))
+				rc = ISCSI_ERR_CONN_FAILED;
+			break;
+		default:
+			rc = ISCSI_ERR_BAD_OPCODE;
+			break;
+		}
+		goto out;
+	}
+
+	switch(opcode) {
+	case ISCSI_OP_SCSI_CMD_RSP:
+	case ISCSI_OP_SCSI_DATA_IN:
+		task = iscsi2_itt_to_ctask(conn, hdr->itt);
+		if (!task)
+			return ISCSI_ERR_BAD_ITT;
+		break;
+	case ISCSI_OP_R2T:
+		/*
+		 * LLD handles R2Ts if they need to.
+		 */
+		return 0;
+	case ISCSI_OP_LOGOUT_RSP:
+	case ISCSI_OP_LOGIN_RSP:
+	case ISCSI_OP_TEXT_RSP:
+	case ISCSI_OP_SCSI_TMFUNC_RSP:
+	case ISCSI_OP_NOOP_IN:
+		task = iscsi_itt_to_task(conn, hdr->itt);
+		if (!task)
+			return ISCSI_ERR_BAD_ITT;
+		break;
+	default:
+		return ISCSI_ERR_BAD_OPCODE;
+	}
+
+	switch(opcode) {
+	case ISCSI_OP_SCSI_CMD_RSP:
+		iscsi_scsi_cmd_rsp(conn, hdr, task, data, datalen);
+		break;
+	case ISCSI_OP_SCSI_DATA_IN:
+		iscsi_data_in_rsp(conn, hdr, task);
+		break;
+	case ISCSI_OP_LOGOUT_RSP:
+		iscsi2_update_cmdsn(session, (struct iscsi_nopin*)hdr);
+		if (datalen) {
+			rc = ISCSI_ERR_PROTO;
+			break;
+		}
+		conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
+		goto recv_pdu;
+	case ISCSI_OP_LOGIN_RSP:
+	case ISCSI_OP_TEXT_RSP:
+		iscsi2_update_cmdsn(session, (struct iscsi_nopin*)hdr);
+		/*
+		 * login related PDU's exp_statsn is handled in
+		 * userspace
+		 */
+		goto recv_pdu;
+	case ISCSI_OP_SCSI_TMFUNC_RSP:
+		iscsi2_update_cmdsn(session, (struct iscsi_nopin*)hdr);
+		if (datalen) {
+			rc = ISCSI_ERR_PROTO;
+			break;
+		}
+
+		iscsi_tmf_rsp(conn, hdr);
+		__iscsi_put_task(task);
+		break;
+	case ISCSI_OP_NOOP_IN:
+		iscsi2_update_cmdsn(session, (struct iscsi_nopin*)hdr);
+		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_task != task)
+			/*
+			 * If this is not in response to one of our
+			 * nops then it must be from userspace.
+			 */
+			goto recv_pdu;
+
+		mod_timer(&conn->transport_timer, jiffies + conn->recv_timeout);
+		__iscsi_put_task(task);
+		break;
+	default:
+		rc = ISCSI_ERR_BAD_OPCODE;
+		break;
+	}
+
+out:
+	return rc;
+recv_pdu:
+	if (iscsi2_recv_pdu(conn->cls_conn, hdr, data, datalen))
+		rc = ISCSI_ERR_CONN_FAILED;
+	__iscsi_put_task(task);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(__iscsi2_complete_pdu);
+
+int iscsi2_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+		       char *data, int datalen)
+{
+	int rc;
+
+	spin_lock(&conn->session->lock);
+	rc = __iscsi2_complete_pdu(conn, hdr, data, datalen);
+	spin_unlock(&conn->session->lock);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(iscsi2_complete_pdu);
+
+int iscsi2_verify_itt(struct iscsi_conn *conn, itt_t itt)
+{
+	struct iscsi_session *session = conn->session;
+	int age = 0, i = 0;
+
+	if (itt == RESERVED_ITT)
+		return 0;
+
+	if (session->tt->parse_pdu_itt)
+		session->tt->parse_pdu_itt(conn, itt, &i, &age);
+	else {
+		i = get_itt(itt);
+		age = ((__force u32)itt >> ISCSI_AGE_SHIFT) & ISCSI_AGE_MASK;
+	}
+
+	if (age != session->age) {
+		iscsi_conn_printk(KERN_ERR, conn,
+				  "received itt %x expected session age (%x)\n",
+				  (__force u32)itt, session->age);
+		return ISCSI_ERR_BAD_ITT;
+	}
+
+	if (i >= session->cmds_max) {
+		iscsi_conn_printk(KERN_ERR, conn,
+				  "received invalid itt index %u (max cmds "
+				   "%u.\n", i, session->cmds_max);
+		return ISCSI_ERR_BAD_ITT;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi2_verify_itt);
+
+/**
+ * iscsi_itt_to_ctask - look up ctask by itt
+ * @conn: iscsi connection
+ * @itt: itt
+ *
+ * This should be used for cmd tasks.
+ *
+ * The session lock must be held.
+ */
+struct iscsi_task *iscsi2_itt_to_ctask(struct iscsi_conn *conn, itt_t itt)
+{
+	struct iscsi_task *task;
+
+	if (iscsi2_verify_itt(conn, itt))
+		return NULL;
+
+	task = iscsi_itt_to_task(conn, itt);
+	if (!task || !task->sc)
+		return NULL;
+
+	if (task->sc->SCp.phase != conn->session->age) {
+		iscsi_session_printk(KERN_ERR, conn->session,
+				  "task's session age %d, expected %d\n",
+				  task->sc->SCp.phase, conn->session->age);
+		return NULL;
+	}
+
+	return task;
+}
+EXPORT_SYMBOL_GPL(iscsi2_itt_to_ctask);
+
+void iscsi2_session_failure(struct iscsi_session *session,
+			   enum iscsi_err err)
+{
+	struct iscsi_conn *conn;
+	struct device *dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&session->lock, flags);
+	conn = session->leadconn;
+	if (session->state == ISCSI_STATE_TERMINATE || !conn) {
+		spin_unlock_irqrestore(&session->lock, flags);
+		return;
+	}
+
+	dev = get_device(&conn->cls_conn->dev);
+	spin_unlock_irqrestore(&session->lock, flags);
+	if (!dev)
+	        return;
+	/*
+	 * if the host is being removed bypass the connection
+	 * recovery initialization because we are going to kill
+	 * the session.
+	 */
+	if (err == ISCSI_ERR_INVALID_HOST)
+		iscsi2_conn_error_event(conn->cls_conn, err);
+	else
+		iscsi2_conn_failure(conn, err);
+	put_device(dev);
+}
+EXPORT_SYMBOL_GPL(iscsi2_session_failure);
+
+void iscsi2_conn_failure(struct iscsi_conn *conn, enum iscsi_err err)
+{
+	struct iscsi_session *session = conn->session;
+	unsigned long flags;
+
+	spin_lock_irqsave(&session->lock, flags);
+	if (session->state == ISCSI_STATE_FAILED) {
+		spin_unlock_irqrestore(&session->lock, flags);
+		return;
+	}
+
+	if (conn->stop_stage == 0)
+		session->state = ISCSI_STATE_FAILED;
+	spin_unlock_irqrestore(&session->lock, flags);
+
+	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
+	iscsi2_conn_error_event(conn->cls_conn, err);
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_failure);
+
+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)) {
+		ISCSI_DBG_SESSION(session, "iSCSI CmdSN closed. ExpCmdSn "
+				  "%u MaxCmdSN %u CmdSN %u/%u\n",
+				  session->exp_cmdsn, session->max_cmdsn,
+				  session->cmdsn, session->queued_cmdsn);
+		return -ENOSPC;
+	}
+	return 0;
+}
+
+static int iscsi_xmit_task(struct iscsi_conn *conn)
+{
+	struct iscsi_task *task = conn->task;
+	int rc;
+
+	__iscsi2_get_task(task);
+	spin_unlock_bh(&conn->session->lock);
+	rc = conn->session->tt->xmit_task(task);
+	spin_lock_bh(&conn->session->lock);
+	__iscsi_put_task(task);
+	if (!rc)
+		/* done with this task */
+		conn->task = NULL;
+	return rc;
+}
+
+/**
+ * iscsi_requeue_task - requeue task to run from session workqueue
+ * @task: task to requeue
+ *
+ * LLDs that need to run a task from the session workqueue should call
+ * this. The session lock must be held. This should only be called
+ * by software drivers.
+ */
+void iscsi2_requeue_task(struct iscsi_task *task)
+{
+	struct iscsi_conn *conn = task->conn;
+
+	list_move_tail(&task->running, &conn->requeue);
+	iscsi_conn_queue_work(conn);
+}
+EXPORT_SYMBOL_GPL(iscsi2_requeue_task);
+
+/**
+ * iscsi_data_xmit - xmit any command into the scheduled connection
+ * @conn: iscsi connection
+ *
+ * Notes:
+ *	The function can return -EAGAIN in which case the caller must
+ *	re-schedule it again later or recover. '0' return code means
+ *	successful xmit.
+ **/
+static int iscsi_data_xmit(struct iscsi_conn *conn)
+{
+	int rc = 0;
+
+	spin_lock_bh(&conn->session->lock);
+	if (unlikely(conn->suspend_tx)) {
+		ISCSI_DBG_SESSION(conn->session, "Tx suspended!\n");
+		spin_unlock_bh(&conn->session->lock);
+		return -ENODATA;
+	}
+
+	if (conn->task) {
+		rc = iscsi_xmit_task(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->task = list_entry(conn->mgmtqueue.next,
+					 struct iscsi_task, running);
+		if (iscsi_prep_mgmt_task(conn, conn->task)) {
+			__iscsi_put_task(conn->task);
+			conn->task = NULL;
+			continue;
+		}
+		rc = iscsi_xmit_task(conn);
+		if (rc)
+			goto again;
+	}
+
+	/* process pending command queue */
+	while (!list_empty(&conn->xmitqueue)) {
+		if (conn->tmf_state == TMF_QUEUED)
+			break;
+
+		conn->task = list_entry(conn->xmitqueue.next,
+					 struct iscsi_task, running);
+		if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
+			fail_command(conn, conn->task, DID_IMM_RETRY << 16);
+			continue;
+		}
+		rc = iscsi_prep_scsi_cmd_pdu(conn->task);
+		if (rc) {
+			if (rc == -ENOMEM) {
+				conn->task = NULL;
+				goto again;
+			} else
+				fail_command(conn, conn->task, DID_ABORT << 16);
+			continue;
+		}
+		rc = iscsi_xmit_task(conn);
+		if (rc)
+			goto again;
+		/*
+		 * we could continuously get new task 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;
+
+		/*
+		 * we always do fastlogout - conn stop code will clean up.
+		 */
+		if (conn->session->state == ISCSI_STATE_LOGGING_OUT)
+			break;
+
+		conn->task = list_entry(conn->requeue.next,
+					 struct iscsi_task, running);
+		conn->task->state = ISCSI_TASK_RUNNING;
+		list_move_tail(conn->requeue.next, &conn->run_list);
+		rc = iscsi_xmit_task(conn);
+		if (rc)
+			goto again;
+		if (!list_empty(&conn->mgmtqueue))
+			goto check_mgmt;
+	}
+	spin_unlock_bh(&conn->session->lock);
+	return -ENODATA;
+
+again:
+	if (unlikely(conn->suspend_tx))
+		rc = -ENODATA;
+	spin_unlock_bh(&conn->session->lock);
+	return rc;
+}
+
+static void iscsi_xmitworker(struct work_struct *work)
+{
+	struct iscsi_conn *conn =
+		container_of(work, struct iscsi_conn, xmitwork);
+	int rc;
+	/*
+	 * serialize Xmit worker on a per-connection basis.
+	 */
+	do {
+		rc = iscsi_data_xmit(conn);
+	} while (rc >= 0 || rc == -EAGAIN);
+}
+
+static inline struct iscsi_task *iscsi_alloc_task(struct iscsi_conn *conn,
+						  struct scsi_cmnd *sc)
+{
+	struct iscsi_task *task;
+
+	if (!__kfifo_get(conn->session->cmdpool.queue,
+			 (void *) &task, sizeof(void *)))
+		return NULL;
+
+	sc->SCp.phase = conn->session->age;
+	sc->SCp.ptr = (char *) task;
+
+	atomic_set(&task->refcount, 1);
+	task->state = ISCSI_TASK_PENDING;
+	task->conn = conn;
+	task->sc = sc;
+	INIT_LIST_HEAD(&task->running);
+	return task;
+}
+
+enum {
+	FAILURE_BAD_HOST = 1,
+	FAILURE_SESSION_FAILED,
+	FAILURE_SESSION_FREED,
+	FAILURE_WINDOW_CLOSED,
+	FAILURE_OOM,
+	FAILURE_SESSION_TERMINATE,
+	FAILURE_SESSION_IN_RECOVERY,
+	FAILURE_SESSION_RECOVERY_TIMEOUT,
+	FAILURE_SESSION_LOGGING_OUT,
+	FAILURE_SESSION_NOT_READY,
+};
+
+int iscsi2_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+{
+	struct iscsi_cls_session *cls_session;
+	struct Scsi_Host *host;
+	int reason = 0;
+	struct iscsi_session *session;
+	struct iscsi_conn *conn;
+	struct iscsi_task *task = NULL;
+
+	sc->scsi_done = done;
+	sc->result = 0;
+	sc->SCp.ptr = NULL;
+
+	host = sc->device->host;
+	spin_unlock(host->host_lock);
+
+	cls_session = starget_to_session(scsi_target(sc->device));
+	session = cls_session->dd_data;
+	spin_lock(&session->lock);
+
+	reason = iscsi2_session_chkready(cls_session);
+	if (reason) {
+		sc->result = reason;
+		goto fault;
+	}
+
+	/*
+	 * ISCSI_STATE_FAILED is a temp. state. The recovery
+	 * code will decide what is best to do with command queued
+	 * during this time
+	 */
+	if (session->state != ISCSI_STATE_LOGGED_IN &&
+	    session->state != ISCSI_STATE_FAILED) {
+		/*
+		 * to handle the race between when we set the recovery state
+		 * and block the session we requeue here (commands could
+		 * 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:
+			reason = FAILURE_SESSION_IN_RECOVERY;
+			goto reject;
+		case ISCSI_STATE_LOGGING_OUT:
+			reason = FAILURE_SESSION_LOGGING_OUT;
+			goto reject;
+		case ISCSI_STATE_RECOVERY_FAILED:
+			reason = FAILURE_SESSION_RECOVERY_TIMEOUT;
+			sc->result = DID_TRANSPORT_FAILFAST << 16;
+			break;
+		case ISCSI_STATE_TERMINATE:
+			reason = FAILURE_SESSION_TERMINATE;
+			sc->result = DID_NO_CONNECT << 16;
+			break;
+		default:
+			reason = FAILURE_SESSION_FREED;
+			sc->result = DID_NO_CONNECT << 16;
+		}
+		goto fault;
+	}
+
+	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;
+	}
+
+	task = iscsi_alloc_task(conn, sc);
+	if (!task) {
+		reason = FAILURE_OOM;
+		goto reject;
+	}
+	list_add_tail(&task->running, &conn->xmitqueue);
+
+	if (session->tt->caps & CAP_DATA_PATH_OFFLOAD) {
+		reason = iscsi_prep_scsi_cmd_pdu(task);
+		if (reason) {
+			if (reason == -ENOMEM) {
+				reason = FAILURE_OOM;
+				goto prepd_reject;
+			} else {
+				sc->result = DID_ABORT << 16;
+				goto prepd_fault;
+			}
+		}
+		if (session->tt->xmit_task(task)) {
+			reason = FAILURE_SESSION_NOT_READY;
+			goto prepd_reject;
+		}
+	} else
+		iscsi_conn_queue_work(conn);
+
+	session->queued_cmdsn++;
+	spin_unlock(&session->lock);
+	spin_lock(host->host_lock);
+	return 0;
+
+prepd_reject:
+	sc->scsi_done = NULL;
+	iscsi_complete_command(task);
+reject:
+	spin_unlock(&session->lock);
+	ISCSI_DBG_SESSION(session, "cmd 0x%x rejected (%d)\n",
+			  sc->cmnd[0], reason);
+	spin_lock(host->host_lock);
+	/*
+	 * we want to return SCSI_MLQUEUE_TARGET_BUSY but the backport
+	 * is too risky. Instead we return device busy which will force
+	 * this to get called for each device, but host busy would stop
+	 * traffic on all sessions which would be worse.
+	 */
+	return SCSI_MLQUEUE_DEVICE_BUSY;
+
+prepd_fault:
+	sc->scsi_done = NULL;
+	iscsi_complete_command(task);
+fault:
+	spin_unlock(&session->lock);
+	ISCSI_DBG_SESSION(session, "iscsi: cmd 0x%x is not queued (%d)\n",
+			  sc->cmnd[0], reason);
+	scsi_set_resid(sc, scsi_bufflen(sc));
+	done(sc);
+	spin_lock(host->host_lock);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi2_queuecommand);
+
+int iscsi2_change_queue_depth(struct scsi_device *sdev, int depth)
+{
+	scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), depth);
+	return sdev->queue_depth;
+}
+EXPORT_SYMBOL_GPL(iscsi2_change_queue_depth);
+
+void iscsi2_session_recovery_timedout(struct iscsi_cls_session *cls_session)
+{
+	struct iscsi_session *session = cls_session->dd_data;
+
+	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);
+	}
+	spin_unlock_bh(&session->lock);
+}
+EXPORT_SYMBOL_GPL(iscsi2_session_recovery_timedout);
+
+int iscsi2_target_reset(struct iscsi_cls_session *cls_session)
+{
+	struct iscsi_session *session;
+	struct iscsi_conn *conn;
+
+	session = cls_session->dd_data;
+	conn = session->leadconn;
+
+	mutex_lock(&session->eh_mutex);
+	spin_lock_bh(&session->lock);
+	if (session->state == ISCSI_STATE_TERMINATE) {
+failed:
+		iscsi_session_printk(KERN_INFO, session,
+				     "failing target reset: Could not log "
+				     "back into target [age %d]\n",
+				     session->age);
+		spin_unlock_bh(&session->lock);
+		mutex_unlock(&session->eh_mutex);
+		return FAILED;
+	}
+
+	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
+	 */
+	iscsi2_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+
+	ISCSI_DBG_SESSION(session, "wait for relogin\n");
+	wait_event_interruptible(conn->ehwait,
+				 session->state == ISCSI_STATE_TERMINATE ||
+				 session->state == ISCSI_STATE_LOGGED_IN ||
+				 session->state == ISCSI_STATE_RECOVERY_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,
+				     "target reset succeeded\n");
+	else
+		goto failed;
+	spin_unlock_bh(&session->lock);
+	mutex_unlock(&session->eh_mutex);
+	return SUCCESS;
+}
+EXPORT_SYMBOL_GPL(iscsi2_target_reset);
+
+int iscsi2_eh_target_reset(struct scsi_cmnd *sc)
+{
+	struct iscsi_cls_session *cls_session;
+
+	cls_session = starget_to_session(scsi_target(sc->device));
+	return iscsi2_target_reset(cls_session);
+}
+EXPORT_SYMBOL_GPL(iscsi2_eh_target_reset);
+
+static void iscsi_tmf_timedout(unsigned long data)
+{
+	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+	struct iscsi_session *session = conn->session;
+
+	spin_lock(&session->lock);
+	if (conn->tmf_state == TMF_QUEUED) {
+		conn->tmf_state = TMF_TIMEDOUT;
+		ISCSI_DBG_SESSION(session, "tmf timedout\n");
+		/* 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)
+{
+	struct iscsi_session *session = conn->session;
+	struct iscsi_task *task;
+
+	task = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)hdr,
+				      NULL, 0);
+	if (!task) {
+		spin_unlock_bh(&session->lock);
+		iscsi2_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+		spin_lock_bh(&session->lock);
+		ISCSI_DBG_SESSION(session, "tmf exec failure\n");
+		return -EPERM;
+	}
+	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);
+	ISCSI_DBG_SESSION(session, "tmf set timeout\n");
+
+	spin_unlock_bh(&session->lock);
+	mutex_unlock(&session->eh_mutex);
+
+	/*
+	 * block eh thread until:
+	 *
+	 * 1) tmf response
+	 * 2) tmf timeout
+	 * 3) session is terminated or restarted or userspace has
+	 * given up on recovery
+	 */
+	wait_event_interruptible(conn->ehwait, age != session->age ||
+				 session->state != ISCSI_STATE_LOGGED_IN ||
+				 conn->tmf_state != TMF_QUEUED);
+	if (signal_pending(current))
+		flush_signals(current);
+	del_timer_sync(&conn->tmf_timer);
+
+	mutex_lock(&session->eh_mutex);
+	spin_lock_bh(&session->lock);
+	/* if the session drops it will clean up the task */
+	if (age != session->age ||
+	    session->state != ISCSI_STATE_LOGGED_IN)
+		return -ENOTCONN;
+	return 0;
+}
+
+/*
+ * Fail commands. session lock held and recv side suspended and xmit
+ * thread flushed
+ */
+static void fail_all_commands(struct iscsi_conn *conn, unsigned lun,
+			      int error)
+{
+	struct iscsi_task *task, *tmp;
+
+	if (conn->task) {
+		if (lun == -1 ||
+		    (conn->task->sc && conn->task->sc->device->lun == lun))
+			conn->task = NULL;
+	}
+
+	/* flush pending */
+	list_for_each_entry_safe(task, tmp, &conn->xmitqueue, running) {
+		if (lun == task->sc->device->lun || lun == -1) {
+			ISCSI_DBG_SESSION(conn->session,
+					  "failing pending sc %p itt 0x%x\n",
+					  task->sc, task->itt);
+			fail_command(conn, task, error << 16);
+		}
+	}
+
+	list_for_each_entry_safe(task, tmp, &conn->requeue, running) {
+		if (lun == task->sc->device->lun || lun == -1) {
+			ISCSI_DBG_SESSION(conn->session,
+					  "failing requeued sc %p itt 0x%x\n",
+					  task->sc, task->itt);
+			fail_command(conn, task, error << 16);
+		}
+	}
+
+	/* fail all other running */
+	list_for_each_entry_safe(task, tmp, &conn->run_list, running) {
+		if (lun == task->sc->device->lun || lun == -1) {
+			ISCSI_DBG_SESSION(conn->session,
+					 "failing in progress sc %p itt 0x%x\n",
+					 task->sc, task->itt);
+			fail_command(conn, task, error << 16);
+		}
+	}
+}
+
+void iscsi2_suspend_tx(struct iscsi_conn *conn)
+{
+	struct Scsi_Host *shost = conn->session->host;
+	struct iscsi_host *ihost = shost_priv(shost);
+
+	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+	if (!(conn->session->tt->caps & CAP_DATA_PATH_OFFLOAD))
+		flush_workqueue(ihost->workq);
+}
+EXPORT_SYMBOL_GPL(iscsi2_suspend_tx);
+
+static void iscsi_start_tx(struct iscsi_conn *conn)
+{
+	clear_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+	if (!(conn->session->tt->caps & CAP_DATA_PATH_OFFLOAD))
+		iscsi_conn_queue_work(conn);
+}
+
+static enum blk_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd)
+{
+	struct iscsi_cls_session *cls_session;
+	struct iscsi_session *session;
+	struct iscsi_conn *conn;
+	enum blk_eh_timer_return rc = BLK_EH_NOT_HANDLED;
+
+	cls_session = starget_to_session(scsi_target(scmd->device));
+	session = cls_session->dd_data;
+
+	ISCSI_DBG_SESSION(session, "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 = BLK_EH_RESET_TIMER;
+		goto done;
+	}
+
+	conn = session->leadconn;
+	if (!conn) {
+		/* In the middle of shuting down */
+		rc = BLK_EH_RESET_TIMER;
+		goto done;
+	}
+
+	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 = BLK_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 = BLK_EH_RESET_TIMER;
+	/* if in the middle of checking the transport then give us more time */
+	if (conn->ping_task)
+		rc = BLK_EH_RESET_TIMER;
+done:
+	spin_unlock(&session->lock);
+	ISCSI_DBG_SESSION(session, "return %s\n", rc == BLK_EH_RESET_TIMER ?
+			  "timer reset" : "nh");
+	return rc;
+}
+
+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 recv_timeout, next_timeout = 0, last_recv;
+
+	spin_lock(&session->lock);
+	if (session->state != ISCSI_STATE_LOGGED_IN)
+		goto done;
+
+	recv_timeout = conn->recv_timeout;
+	if (!recv_timeout)
+		goto done;
+
+	recv_timeout *= HZ;
+	last_recv = conn->last_recv;
+	if (conn->ping_task &&
+	    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, "
+				  "now %lu\n", conn->ping_timeout, last_recv,
+				  conn->last_ping, jiffies);
+		spin_unlock(&session->lock);
+		iscsi2_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+		return;
+	}
+
+	if (time_before_eq(last_recv + recv_timeout, jiffies)) {
+		/* send a ping to try to provoke some traffic */
+		ISCSI_DBG_CONN(conn, "Sending nopout as ping\n");
+		iscsi_send_nopout(conn, NULL);
+		next_timeout = conn->last_ping + (conn->ping_timeout * HZ);
+	} else
+		next_timeout = last_recv + recv_timeout;
+
+	ISCSI_DBG_CONN(conn, "Setting next tmo %lu\n", next_timeout);
+	mod_timer(&conn->transport_timer, next_timeout);
+done:
+	spin_unlock(&session->lock);
+}
+
+static void iscsi_prep_abort_task_pdu(struct iscsi_task *task,
+				      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, task->lun, sizeof(hdr->lun));
+	hdr->rtt = task->hdr_itt;
+	hdr->refcmdsn = task->cmdsn;
+}
+
+int iscsi2_eh_abort(struct scsi_cmnd *sc)
+{
+	struct iscsi_cls_session *cls_session;
+	struct iscsi_session *session;
+	struct iscsi_conn *conn;
+	struct iscsi_task *task;
+	struct iscsi_tm *hdr;
+	int rc, age;
+
+	cls_session = starget_to_session(scsi_target(sc->device));
+	session = cls_session->dd_data;
+
+	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) {
+		ISCSI_DBG_SESSION(session, "sc never reached iscsi layer or "
+				  "it completed.\n");
+		spin_unlock_bh(&session->lock);
+		mutex_unlock(&session->eh_mutex);
+		return SUCCESS;
+	}
+
+	/*
+	 * 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;
+
+	task = (struct iscsi_task *)sc->SCp.ptr;
+	ISCSI_DBG_SESSION(session, "aborting [sc %p itt 0x%x]\n",
+			  sc, task->itt);
+
+	/* task completed before time out */
+	if (!task->sc) {
+		ISCSI_DBG_SESSION(session, "sc completed while abort in "
+				  "progress\n");
+		goto success;
+	}
+
+	if (task->state == ISCSI_TASK_PENDING) {
+		fail_command(conn, task, DID_ABORT << 16);
+		goto success;
+	}
+
+	/* only have one tmf outstanding at a time */
+	if (conn->tmf_state != TMF_INITIAL)
+		goto failed;
+	conn->tmf_state = TMF_QUEUED;
+
+	hdr = &conn->tmhdr;
+	iscsi_prep_abort_task_pdu(task, hdr);
+
+	if (iscsi_exec_task_mgmt_fn(conn, hdr, age, session->abort_timeout)) {
+		rc = FAILED;
+		goto failed;
+	}
+
+	switch (conn->tmf_state) {
+	case TMF_SUCCESS:
+		spin_unlock_bh(&session->lock);
+		/*
+		 * stop tx side incase the target had sent a abort rsp but
+		 * the initiator was still writing out data.
+		 */
+		iscsi2_suspend_tx(conn);
+		/*
+		 * we do not stop the recv side because targets have been
+		 * good and have never sent us a successful tmf response
+		 * then sent more data for the cmd.
+		 */
+		spin_lock(&session->lock);
+		fail_command(conn, task, DID_ABORT << 16);
+		conn->tmf_state = TMF_INITIAL;
+		spin_unlock(&session->lock);
+		iscsi_start_tx(conn);
+		goto success_unlocked;
+	case TMF_TIMEDOUT:
+		spin_unlock_bh(&session->lock);
+		iscsi2_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+		goto failed_unlocked;
+	case TMF_NOT_FOUND:
+		if (!sc->SCp.ptr) {
+			conn->tmf_state = TMF_INITIAL;
+			/* task completed before tmf abort response */
+			ISCSI_DBG_SESSION(session, "sc completed while abort "
+					  "in progress\n");
+			goto success;
+		}
+		/* fall through */
+	default:
+		conn->tmf_state = TMF_INITIAL;
+		goto failed;
+	}
+
+success:
+	spin_unlock_bh(&session->lock);
+success_unlocked:
+	ISCSI_DBG_SESSION(session, "abort success [sc %p itt 0x%x]\n",
+			  sc, task->itt);
+	mutex_unlock(&session->eh_mutex);
+	return SUCCESS;
+
+failed:
+	spin_unlock_bh(&session->lock);
+failed_unlocked:
+	ISCSI_DBG_SESSION(session, "abort failed [sc %p itt 0x%x]\n", sc,
+			  task ? task->itt : 0);
+	mutex_unlock(&session->eh_mutex);
+	return FAILED;
+}
+EXPORT_SYMBOL_GPL(iscsi2_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 iscsi2_eh_device_reset(struct scsi_cmnd *sc)
+{
+	struct iscsi_cls_session *cls_session;
+	struct iscsi_session *session;
+	struct iscsi_conn *conn;
+	struct iscsi_tm *hdr;
+	int rc = FAILED;
+
+	cls_session = starget_to_session(scsi_target(sc->device));
+	session = cls_session->dd_data;
+
+	ISCSI_DBG_SESSION(session, "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.
+	 */
+	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);
+		iscsi2_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+		goto done;
+	default:
+		conn->tmf_state = TMF_INITIAL;
+		goto unlock;
+	}
+
+	rc = SUCCESS;
+	spin_unlock_bh(&session->lock);
+
+	iscsi2_suspend_tx(conn);
+
+	spin_lock_bh(&session->lock);
+	fail_all_commands(conn, sc->device->lun, DID_ERROR);
+	conn->tmf_state = TMF_INITIAL;
+	spin_unlock_bh(&session->lock);
+
+	iscsi_start_tx(conn);
+	goto done;
+
+unlock:
+	spin_unlock_bh(&session->lock);
+done:
+	ISCSI_DBG_SESSION(session, "dev reset result = %s\n",
+			 rc == SUCCESS ? "SUCCESS" : "FAILED");
+	mutex_unlock(&session->eh_mutex);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(iscsi2_eh_device_reset);
+
+/*
+ * 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
+iscsi2_pool_init(struct iscsi_pool *q, int max, void ***items, int item_size)
+{
+	int i, num_arrays = 1;
+
+	memset(q, 0, sizeof(*q));
+
+	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)
+		return -ENOMEM;
+
+	q->queue = kfifo_init((void*)q->pool, max * sizeof(void*),
+			      GFP_KERNEL, NULL);
+	if (IS_ERR(q->queue)) {
+		q->queue = NULL;
+		goto enomem;
+	}
+
+	for (i = 0; i < max; i++) {
+		q->pool[i] = kzalloc(item_size, GFP_KERNEL);
+		if (q->pool[i] == NULL) {
+			q->max = i;
+			goto enomem;
+		}
+		__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:
+	iscsi2_pool_free(q);
+	return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(iscsi2_pool_init);
+
+void iscsi2_pool_free(struct iscsi_pool *q)
+{
+	int i;
+
+	for (i = 0; i < q->max; i++)
+		kfree(q->pool[i]);
+	kfree(q->pool);
+	kfree(q->queue);
+}
+EXPORT_SYMBOL_GPL(iscsi2_pool_free);
+
+/**
+ * iscsi_host_add - add host to system
+ * @shost: scsi host
+ * @pdev: parent device
+ *
+ * This should be called by partial offload and software iscsi drivers
+ * to add a host to the system.
+ */
+int iscsi2_host_add(struct Scsi_Host *shost, struct device *pdev)
+{
+	if (!shost->can_queue)
+		shost->can_queue = ISCSI_DEF_XMIT_CMDS_MAX;
+
+	if (!shost->cmd_per_lun)
+		shost->cmd_per_lun = ISCSI_DEF_CMD_PER_LUN;
+
+	if (!shost->transportt->eh_timed_out)
+		shost->transportt->eh_timed_out = iscsi_eh_cmd_timed_out;
+	return scsi_add_host(shost, pdev);
+}
+EXPORT_SYMBOL_GPL(iscsi2_host_add);
+
+/**
+ * iscsi_host_alloc - allocate a host and driver data
+ * @sht: scsi host template
+ * @dd_data_size: driver host data size
+ * @xmit_can_sleep: bool indicating if LLD will queue IO from a work queue
+ *
+ * This should be called by partial offload and software iscsi drivers.
+ * To access the driver specific memory use the iscsi_host_priv() macro.
+ */
+struct Scsi_Host *iscsi2_host_alloc(struct scsi_host_template *sht,
+				   int dd_data_size, bool xmit_can_sleep)
+{
+	struct Scsi_Host *shost;
+	struct iscsi_host *ihost;
+
+	shost = scsi_host_alloc(sht, sizeof(struct iscsi_host) + dd_data_size);
+	if (!shost)
+		return NULL;
+	ihost = shost_priv(shost);
+
+	if (xmit_can_sleep) {
+		snprintf(ihost->workq_name, sizeof(ihost->workq_name),
+			"iscsi_q_%d", shost->host_no);
+		ihost->workq = create_singlethread_workqueue(ihost->workq_name);
+		if (!ihost->workq)
+			goto free_host;
+	}
+
+	spin_lock_init(&ihost->lock);
+	ihost->state = ISCSI_HOST_SETUP;
+	ihost->num_sessions = 0;
+	init_waitqueue_head(&ihost->session_removal_wq);
+	return shost;
+
+free_host:
+	scsi_host_put(shost);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(iscsi2_host_alloc);
+
+static void iscsi_notify_host_removed(struct iscsi_cls_session *cls_session)
+{
+	iscsi2_session_failure(cls_session->dd_data, ISCSI_ERR_INVALID_HOST);
+}
+
+/**
+ * iscsi_host_remove - remove host and sessions
+ * @shost: scsi host
+ *
+ * If there are any sessions left, this will initiate the removal and wait
+ * for the completion.
+ */
+void iscsi2_host_remove(struct Scsi_Host *shost)
+{
+	struct iscsi_host *ihost = shost_priv(shost);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ihost->lock, flags);
+	ihost->state = ISCSI_HOST_REMOVED;
+	spin_unlock_irqrestore(&ihost->lock, flags);
+
+	iscsi2_host_for_each_session(shost, iscsi_notify_host_removed);
+	wait_event_interruptible(ihost->session_removal_wq,
+				 ihost->num_sessions == 0);
+	if (signal_pending(current))
+		flush_signals(current);
+
+	scsi_remove_host(shost);
+	if (ihost->workq)
+		destroy_workqueue(ihost->workq);
+}
+EXPORT_SYMBOL_GPL(iscsi2_host_remove);
+
+void iscsi2_host_free(struct Scsi_Host *shost)
+{
+	struct iscsi_host *ihost = shost_priv(shost);
+
+	kfree(ihost->netdev);
+	kfree(ihost->hwaddress);
+	kfree(ihost->initiatorname);
+	scsi_host_put(shost);
+}
+EXPORT_SYMBOL_GPL(iscsi2_host_free);
+
+static void iscsi_host_dec_session_cnt(struct Scsi_Host *shost)
+{
+	struct iscsi_host *ihost = shost_priv(shost);
+	unsigned long flags;
+
+	shost = scsi_host_get(shost);
+	if (!shost) {
+		printk(KERN_ERR "Invalid state. Cannot notify host removal "
+		      "of session teardown event because host already "
+		      "removed.\n");
+		return;
+	}
+
+	spin_lock_irqsave(&ihost->lock, flags);
+	ihost->num_sessions--;
+	if (ihost->num_sessions == 0)
+		wake_up(&ihost->session_removal_wq);
+	spin_unlock_irqrestore(&ihost->lock, flags);
+	scsi_host_put(shost);
+}
+
+/**
+ * iscsi_session_setup - create iscsi cls session and host and session
+ * @iscsit: iscsi transport template
+ * @shost: scsi host
+ * @cmds_max: session can queue
+ * @cmd_task_size: LLD task private data size
+ * @initial_cmdsn: initial CmdSN
+ *
+ * This can be used by software iscsi_transports that allocate
+ * a session per scsi host.
+ *
+ * Callers should set cmds_max to the largest total numer (mgmt + scsi) of
+ * tasks they support. The iscsi layer reserves ISCSI_MGMT_CMDS_MAX tasks
+ * for nop handling and login/logout requests.
+ */
+struct iscsi_cls_session *
+iscsi2_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost,
+		    uint16_t cmds_max, int cmd_task_size,
+		    uint32_t initial_cmdsn, unsigned int id)
+{
+	struct iscsi_host *ihost = shost_priv(shost);
+	struct iscsi_session *session;
+	struct iscsi_cls_session *cls_session;
+	int cmd_i, scsi_cmds, total_cmds = cmds_max;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ihost->lock, flags);
+	if (ihost->state == ISCSI_HOST_REMOVED) {
+		spin_unlock_irqrestore(&ihost->lock, flags);
+		return NULL;
+	}
+	ihost->num_sessions++;
+	spin_unlock_irqrestore(&ihost->lock, flags);
+
+	if (!total_cmds)
+		total_cmds = ISCSI_DEF_XMIT_CMDS_MAX;
+	/*
+	 * The iscsi layer needs some tasks for nop handling and tmfs,
+	 * so the cmds_max must at least be greater than ISCSI_MGMT_CMDS_MAX
+	 * + 1 command for scsi IO.
+	 */
+	if (total_cmds < ISCSI_TOTAL_CMDS_MIN) {
+		printk(KERN_ERR "iscsi: invalid can_queue of %d. can_queue "
+		       "must be a power of two that is at least %d.\n",
+		       total_cmds, ISCSI_TOTAL_CMDS_MIN);
+		goto dec_session_count;
+	}
+
+	if (total_cmds > ISCSI_TOTAL_CMDS_MAX) {
+		printk(KERN_ERR "iscsi: invalid can_queue of %d. can_queue "
+		       "must be a power of 2 less than or equal to %d.\n",
+		       cmds_max, ISCSI_TOTAL_CMDS_MAX);
+		total_cmds = ISCSI_TOTAL_CMDS_MAX;
+	}
+
+	if (!is_power_of_2(total_cmds)) {
+		printk(KERN_ERR "iscsi: invalid can_queue of %d. can_queue "
+		       "must be a power of 2.\n", total_cmds);
+		total_cmds = rounddown_pow_of_two(total_cmds);
+		if (total_cmds < ISCSI_TOTAL_CMDS_MIN)
+			return NULL;
+		printk(KERN_INFO "iscsi: Rounding can_queue to %d.\n",
+		       total_cmds);
+	}
+	scsi_cmds = total_cmds - ISCSI_MGMT_CMDS_MAX;
+
+	cls_session = iscsi2_alloc_session(shost, iscsit,
+					   sizeof(struct iscsi_session));
+	if (!cls_session)
+		goto dec_session_count;
+	session = cls_session->dd_data;
+	session->cls_session = cls_session;
+	session->host = shost;
+	session->state = ISCSI_STATE_FREE;
+	session->fast_abort = 1;
+	session->lu_reset_timeout = 15;
+	session->abort_timeout = 10;
+	session->scsi_cmds_max = scsi_cmds;
+	session->cmds_max = total_cmds;
+	session->queued_cmdsn = 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);
+	spin_lock_init(&session->lock);
+
+	/* initialize SCSI PDU commands pool */
+	if (iscsi2_pool_init(&session->cmdpool, session->cmds_max,
+			    (void***)&session->cmds,
+			    cmd_task_size + sizeof(struct iscsi_task)))
+		goto cmdpool_alloc_fail;
+
+	/* pre-format cmds pool with ITT */
+	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
+		struct iscsi_task *task = session->cmds[cmd_i];
+
+		if (cmd_task_size)
+			task->dd_data = &task[1];
+		task->itt = cmd_i;
+		INIT_LIST_HEAD(&task->running);
+	}
+
+	if (!try_module_get(iscsit->owner))
+		goto module_get_fail;
+
+	if (iscsi2_add_session(cls_session, id))
+		goto cls_session_fail;
+
+	return cls_session;
+
+cls_session_fail:
+	module_put(iscsit->owner);
+module_get_fail:
+	iscsi2_pool_free(&session->cmdpool);
+cmdpool_alloc_fail:
+	iscsi2_free_session(cls_session);
+dec_session_count:
+	iscsi_host_dec_session_cnt(shost);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(iscsi2_session_setup);
+
+/**
+ * iscsi_session_teardown - destroy session, host, and cls_session
+ * @cls_session: iscsi session
+ *
+ * The driver must have called iscsi_remove_session before
+ * calling this.
+ */
+void iscsi2_session_teardown(struct iscsi_cls_session *cls_session)
+{
+	struct iscsi_session *session = cls_session->dd_data;
+	struct module *owner = cls_session->transport->owner;
+	struct Scsi_Host *shost = session->host;
+
+	iscsi2_pool_free(&session->cmdpool);
+
+	kfree(session->password);
+	kfree(session->password_in);
+	kfree(session->username);
+	kfree(session->username_in);
+	kfree(session->targetname);
+	kfree(session->initiatorname);
+	kfree(session->ifacename);
+
+	iscsi2_destroy_session(cls_session);
+	iscsi_host_dec_session_cnt(shost);
+	module_put(owner);
+}
+EXPORT_SYMBOL_GPL(iscsi2_session_teardown);
+
+/**
+ * iscsi_conn_setup - create iscsi_cls_conn and iscsi_conn
+ * @cls_session: iscsi_cls_session
+ * @dd_size: private driver data size
+ * @conn_idx: cid
+ */
+struct iscsi_cls_conn *
+iscsi2_conn_setup(struct iscsi_cls_session *cls_session, int dd_size,
+		 uint32_t conn_idx)
+{
+	struct iscsi_session *session = cls_session->dd_data;
+	struct iscsi_conn *conn;
+	struct iscsi_cls_conn *cls_conn;
+	char *data;
+
+	cls_conn = iscsi2_create_conn(cls_session, sizeof(*conn) + dd_size,
+				      conn_idx);
+	if (!cls_conn)
+		return NULL;
+	conn = cls_conn->dd_data;
+	memset(conn, 0, sizeof(*conn) + dd_size);
+
+	conn->dd_data = cls_conn->dd_data + sizeof(*conn);
+	conn->session = session;
+	conn->cls_conn = cls_conn;
+	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;
+
+	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);
+	INIT_WORK(&conn->xmitwork, iscsi_xmitworker);
+
+	/* allocate login_task used for the login/text sequences */
+	spin_lock_bh(&session->lock);
+	if (!__kfifo_get(session->cmdpool.queue,
+                         (void*)&conn->login_task,
+			 sizeof(void*))) {
+		spin_unlock_bh(&session->lock);
+		goto login_task_alloc_fail;
+	}
+	spin_unlock_bh(&session->lock);
+
+	data = (char *) __get_free_pages(GFP_KERNEL,
+					 get_order(ISCSI_DEF_MAX_RECV_SEG_LEN));
+	if (!data)
+		goto login_task_data_alloc_fail;
+	conn->login_task->data = conn->data = data;
+
+	init_timer(&conn->tmf_timer);
+	init_waitqueue_head(&conn->ehwait);
+
+	return cls_conn;
+
+login_task_data_alloc_fail:
+	__kfifo_put(session->cmdpool.queue, (void*)&conn->login_task,
+		    sizeof(void*));
+login_task_alloc_fail:
+	iscsi2_destroy_conn(cls_conn);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_setup);
+
+/**
+ * iscsi_conn_teardown - teardown iscsi connection
+ * cls_conn: iscsi class connection
+ *
+ * TODO: we may need to make this into a two step process
+ * like scsi-mls remove + put host
+ */
+void iscsi2_conn_teardown(struct iscsi_cls_conn *cls_conn)
+{
+	struct iscsi_conn *conn = cls_conn->dd_data;
+	struct iscsi_session *session = conn->session;
+	unsigned long flags;
+
+	del_timer_sync(&conn->transport_timer);
+
+	spin_lock_bh(&session->lock);
+	conn->c_stage = ISCSI_CONN_CLEANUP_WAIT;
+	if (session->leadconn == conn) {
+		/*
+		 * leading connection? then give up on recovery.
+		 */
+		session->state = ISCSI_STATE_TERMINATE;
+		wake_up(&conn->ehwait);
+	}
+	spin_unlock_bh(&session->lock);
+
+	/*
+	 * Block until all in-progress commands for this connection
+	 * time out or fail.
+	 */
+	for (;;) {
+		spin_lock_irqsave(session->host->host_lock, flags);
+		if (!session->host->host_busy) { /* OK for ERL == 0 */
+			spin_unlock_irqrestore(session->host->host_lock, flags);
+			break;
+		}
+		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);
+		/*
+		 * force eh_abort() to unblock
+		 */
+		wake_up(&conn->ehwait);
+	}
+
+	/* flush queued up work because we free the connection below */
+	iscsi2_suspend_tx(conn);
+
+	spin_lock_bh(&session->lock);
+	free_pages((unsigned long) conn->data,
+		   get_order(ISCSI_DEF_MAX_RECV_SEG_LEN));
+	kfree(conn->persistent_address);
+	__kfifo_put(session->cmdpool.queue, (void*)&conn->login_task,
+		    sizeof(void*));
+	if (session->leadconn == conn)
+		session->leadconn = NULL;
+	spin_unlock_bh(&session->lock);
+
+	iscsi2_destroy_conn(cls_conn);
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_teardown);
+
+int iscsi2_conn_start(struct iscsi_cls_conn *cls_conn)
+{
+	struct iscsi_conn *conn = cls_conn->dd_data;
+	struct iscsi_session *session = conn->session;
+
+	if (!session) {
+		iscsi_conn_printk(KERN_ERR, conn,
+				  "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);
+		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:
+		/*
+		 * unblock eh_abort() if it is blocked. re-try all
+		 * commands after successful recovery
+		 */
+		conn->stop_stage = 0;
+		conn->tmf_state = TMF_INITIAL;
+		session->age++;
+		if (session->age == 16)
+			session->age = 0;
+		break;
+	case STOP_CONN_TERM:
+		conn->stop_stage = 0;
+		break;
+	default:
+		break;
+	}
+	spin_unlock_bh(&session->lock);
+
+	iscsi2_unblock_session(session->cls_session);
+	wake_up(&conn->ehwait);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_start);
+
+static void
+flush_control_queues(struct iscsi_session *session, struct iscsi_conn *conn)
+{
+	struct iscsi_task *task, *tmp;
+
+	/* handle pending */
+	list_for_each_entry_safe(task, tmp, &conn->mgmtqueue, running) {
+		ISCSI_DBG_SESSION(session, "flushing pending mgmt task "
+				  "itt 0x%x\n", task->itt);
+		/* release ref from prep task */
+		__iscsi_put_task(task);
+	}
+
+	/* handle running */
+	list_for_each_entry_safe(task, tmp, &conn->mgmt_run_list, running) {
+		ISCSI_DBG_SESSION(session, "flushing running mgmt task "
+				  "itt 0x%x\n", task->itt);
+		/* release ref from prep task */
+		__iscsi_put_task(task);
+	}
+
+	conn->task = 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;
+	}
+
+	/*
+	 * When this is called for the in_login state, we only want to clean
+	 * up the login task and connection. We do not need to block and set
+	 * the recovery state again
+	 */
+	if (flag == STOP_CONN_TERM)
+		session->state = ISCSI_STATE_TERMINATE;
+	else if (conn->stop_stage != STOP_CONN_RECOVER)
+		session->state = ISCSI_STATE_IN_RECOVERY;
+
+	old_stop_stage = conn->stop_stage;
+	conn->stop_stage = flag;
+	conn->c_stage = ISCSI_CONN_STOPPED;
+	spin_unlock_bh(&session->lock);
+
+	iscsi2_suspend_tx(conn);
+	/*
+	 * for connection level recovery we should not calculate
+	 * header digest. conn->hdr_size used for optimization
+	 * in hdr_extract() and will be re-negotiated at
+	 * set_param() time.
+	 */
+	if (flag == STOP_CONN_RECOVER) {
+		conn->hdrdgst_en = 0;
+		conn->datadgst_en = 0;
+		if (session->state == ISCSI_STATE_IN_RECOVERY &&
+		    old_stop_stage != STOP_CONN_RECOVER) {
+			ISCSI_DBG_SESSION(session, "blocking session\n");
+			iscsi2_block_session(session->cls_session);
+		}
+	}
+
+	/*
+	 * flush queues.
+	 */
+	spin_lock_bh(&session->lock);
+	if (flag == STOP_CONN_RECOVER)
+		fail_all_commands(conn, -1, DID_TRANSPORT_DISRUPTED);
+	else
+		fail_all_commands(conn, -1, DID_ERROR);
+	flush_control_queues(session, conn);
+	spin_unlock_bh(&session->lock);
+	mutex_unlock(&session->eh_mutex);
+}
+
+void iscsi2_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
+{
+	struct iscsi_conn *conn = cls_conn->dd_data;
+	struct iscsi_session *session = conn->session;
+
+	switch (flag) {
+	case STOP_CONN_RECOVER:
+	case STOP_CONN_TERM:
+		iscsi_start_session_recovery(session, conn, flag);
+		break;
+	default:
+		iscsi_conn_printk(KERN_ERR, conn,
+				  "invalid stop flag %d\n", flag);
+	}
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_stop);
+
+int iscsi2_conn_bind(struct iscsi_cls_session *cls_session,
+		     struct iscsi_cls_conn *cls_conn, int is_leading)
+{
+	struct iscsi_session *session = cls_session->dd_data;
+	struct iscsi_conn *conn = cls_conn->dd_data;
+
+	spin_lock_bh(&session->lock);
+	if (is_leading)
+		session->leadconn = conn;
+	spin_unlock_bh(&session->lock);
+
+	/*
+	 * Unblock xmitworker(), Login Phase will pass through.
+	 */
+	clear_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
+	clear_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_bind);
+
+static int iscsi_switch_str_param(char **param, char *new_val_buf)
+{
+	char *new_val;
+
+	if (*param) {
+		if (!strcmp(*param, new_val_buf))
+			return 0;
+	}
+
+	new_val = kstrdup(new_val_buf, GFP_NOIO);
+	if (!new_val)
+		return -ENOMEM;
+
+	kfree(*param);
+	*param = new_val;
+	return 0;
+}
+
+int iscsi2_set_param(struct iscsi_cls_conn *cls_conn,
+		    enum iscsi_param param, char *buf, int buflen)
+{
+	struct iscsi_conn *conn = cls_conn->dd_data;
+	struct iscsi_session *session = conn->session;
+	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;
+	case ISCSI_PARAM_MAX_XMIT_DLENGTH:
+		sscanf(buf, "%d", &conn->max_xmit_dlength);
+		break;
+	case ISCSI_PARAM_HDRDGST_EN:
+		sscanf(buf, "%d", &conn->hdrdgst_en);
+		break;
+	case ISCSI_PARAM_DATADGST_EN:
+		sscanf(buf, "%d", &conn->datadgst_en);
+		break;
+	case ISCSI_PARAM_INITIAL_R2T_EN:
+		sscanf(buf, "%d", &session->initial_r2t_en);
+		break;
+	case ISCSI_PARAM_MAX_R2T:
+		sscanf(buf, "%d", &session->max_r2t);
+		break;
+	case ISCSI_PARAM_IMM_DATA_EN:
+		sscanf(buf, "%d", &session->imm_data_en);
+		break;
+	case ISCSI_PARAM_FIRST_BURST:
+		sscanf(buf, "%d", &session->first_burst);
+		break;
+	case ISCSI_PARAM_MAX_BURST:
+		sscanf(buf, "%d", &session->max_burst);
+		break;
+	case ISCSI_PARAM_PDU_INORDER_EN:
+		sscanf(buf, "%d", &session->pdu_inorder_en);
+		break;
+	case ISCSI_PARAM_DATASEQ_INORDER_EN:
+		sscanf(buf, "%d", &session->dataseq_inorder_en);
+		break;
+	case ISCSI_PARAM_ERL:
+		sscanf(buf, "%d", &session->erl);
+		break;
+	case ISCSI_PARAM_IFMARKER_EN:
+		sscanf(buf, "%d", &value);
+		BUG_ON(value);
+		break;
+	case ISCSI_PARAM_OFMARKER_EN:
+		sscanf(buf, "%d", &value);
+		BUG_ON(value);
+		break;
+	case ISCSI_PARAM_EXP_STATSN:
+		sscanf(buf, "%u", &conn->exp_statsn);
+		break;
+	case ISCSI_PARAM_USERNAME:
+		return iscsi_switch_str_param(&session->username, buf);
+	case ISCSI_PARAM_USERNAME_IN:
+		return iscsi_switch_str_param(&session->username_in, buf);
+	case ISCSI_PARAM_PASSWORD:
+		return iscsi_switch_str_param(&session->password, buf);
+	case ISCSI_PARAM_PASSWORD_IN:
+		return iscsi_switch_str_param(&session->password_in, buf);
+	case ISCSI_PARAM_TARGET_NAME:
+		return iscsi_switch_str_param(&session->targetname, buf);
+	case ISCSI_PARAM_TPGT:
+		sscanf(buf, "%d", &session->tpgt);
+		break;
+	case ISCSI_PARAM_PERSISTENT_PORT:
+		sscanf(buf, "%d", &conn->persistent_port);
+		break;
+	case ISCSI_PARAM_PERSISTENT_ADDRESS:
+		return iscsi_switch_str_param(&conn->persistent_address, buf);
+	case ISCSI_PARAM_IFACE_NAME:
+		return iscsi_switch_str_param(&session->ifacename, buf);
+	case ISCSI_PARAM_INITIATOR_NAME:
+		return iscsi_switch_str_param(&session->initiatorname, buf);
+	default:
+		return -ENOSYS;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi2_set_param);
+
+int iscsi2_session_get_param(struct iscsi_cls_session *cls_session,
+			    enum iscsi_param param, char *buf)
+{
+	struct iscsi_session *session = cls_session->dd_data;
+	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;
+	case ISCSI_PARAM_MAX_R2T:
+		len = sprintf(buf, "%hu\n", session->max_r2t);
+		break;
+	case ISCSI_PARAM_IMM_DATA_EN:
+		len = sprintf(buf, "%d\n", session->imm_data_en);
+		break;
+	case ISCSI_PARAM_FIRST_BURST:
+		len = sprintf(buf, "%u\n", session->first_burst);
+		break;
+	case ISCSI_PARAM_MAX_BURST:
+		len = sprintf(buf, "%u\n", session->max_burst);
+		break;
+	case ISCSI_PARAM_PDU_INORDER_EN:
+		len = sprintf(buf, "%d\n", session->pdu_inorder_en);
+		break;
+	case ISCSI_PARAM_DATASEQ_INORDER_EN:
+		len = sprintf(buf, "%d\n", session->dataseq_inorder_en);
+		break;
+	case ISCSI_PARAM_ERL:
+		len = sprintf(buf, "%d\n", session->erl);
+		break;
+	case ISCSI_PARAM_TARGET_NAME:
+		len = sprintf(buf, "%s\n", session->targetname);
+		break;
+	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;
+	case ISCSI_PARAM_IFACE_NAME:
+		len = sprintf(buf, "%s\n", session->ifacename);
+		break;
+	case ISCSI_PARAM_INITIATOR_NAME:
+		len = sprintf(buf, "%s\n", session->initiatorname);
+		break;
+	default:
+		return -ENOSYS;
+	}
+
+	return len;
+}
+EXPORT_SYMBOL_GPL(iscsi2_session_get_param);
+
+int iscsi2_conn_get_param(struct iscsi_cls_conn *cls_conn,
+			 enum iscsi_param param, char *buf)
+{
+	struct iscsi_conn *conn = cls_conn->dd_data;
+	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;
+	case ISCSI_PARAM_MAX_XMIT_DLENGTH:
+		len = sprintf(buf, "%u\n", conn->max_xmit_dlength);
+		break;
+	case ISCSI_PARAM_HDRDGST_EN:
+		len = sprintf(buf, "%d\n", conn->hdrdgst_en);
+		break;
+	case ISCSI_PARAM_DATADGST_EN:
+		len = sprintf(buf, "%d\n", conn->datadgst_en);
+		break;
+	case ISCSI_PARAM_IFMARKER_EN:
+		len = sprintf(buf, "%d\n", conn->ifmarker_en);
+		break;
+	case ISCSI_PARAM_OFMARKER_EN:
+		len = sprintf(buf, "%d\n", conn->ofmarker_en);
+		break;
+	case ISCSI_PARAM_EXP_STATSN:
+		len = sprintf(buf, "%u\n", conn->exp_statsn);
+		break;
+	case ISCSI_PARAM_PERSISTENT_PORT:
+		len = sprintf(buf, "%d\n", conn->persistent_port);
+		break;
+	case ISCSI_PARAM_PERSISTENT_ADDRESS:
+		len = sprintf(buf, "%s\n", conn->persistent_address);
+		break;
+	default:
+		return -ENOSYS;
+	}
+
+	return len;
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_get_param);
+
+int iscsi2_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+			 char *buf)
+{
+	struct iscsi_host *ihost = shost_priv(shost);
+	int len;
+
+	switch (param) {
+	case ISCSI_HOST_PARAM_NETDEV_NAME:
+		len = sprintf(buf, "%s\n", ihost->netdev);
+		break;
+	case ISCSI_HOST_PARAM_HWADDRESS:
+		len = sprintf(buf, "%s\n", ihost->hwaddress);
+		break;
+	case ISCSI_HOST_PARAM_INITIATOR_NAME:
+		len = sprintf(buf, "%s\n", ihost->initiatorname);
+		break;
+	case ISCSI_HOST_PARAM_IPADDRESS:
+		len = sprintf(buf, "%s\n", ihost->local_address);
+		break;
+	default:
+		return -ENOSYS;
+	}
+
+	return len;
+}
+EXPORT_SYMBOL_GPL(iscsi2_host_get_param);
+
+int iscsi2_host_set_param(struct Scsi_Host *shost, enum iscsi_host_param param,
+			 char *buf, int buflen)
+{
+	struct iscsi_host *ihost = shost_priv(shost);
+
+	switch (param) {
+	case ISCSI_HOST_PARAM_NETDEV_NAME:
+		return iscsi_switch_str_param(&ihost->netdev, buf);
+	case ISCSI_HOST_PARAM_HWADDRESS:
+		return iscsi_switch_str_param(&ihost->hwaddress, buf);
+	case ISCSI_HOST_PARAM_INITIATOR_NAME:
+		return iscsi_switch_str_param(&ihost->initiatorname, buf);
+	default:
+		return -ENOSYS;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi2_host_set_param);
+
+MODULE_AUTHOR("Mike Christie");
+MODULE_DESCRIPTION("iSCSI library functions");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/libiscsi_tcp.c b/drivers/scsi/libiscsi_tcp.c
new file mode 100644
index 0000000..2241f47
--- /dev/null
+++ b/drivers/scsi/libiscsi_tcp.c
@@ -0,0 +1,1176 @@
+/*
+ * iSCSI over TCP/IP Data-Path lib
+ *
+ * Copyright (C) 2004 Dmitry Yusupov
+ * Copyright (C) 2004 Alex Aizman
+ * Copyright (C) 2005 - 2006 Mike Christie
+ * Copyright (C) 2006 Red Hat, Inc.  All rights reserved.
+ * maintained by open-iscsi@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ *
+ * Credits:
+ *	Christoph Hellwig
+ *	FUJITA Tomonori
+ *	Arne Redlich
+ *	Zhenyu Wang
+ */
+
+#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 <net/tcp.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi.h>
+#include <scsi/iscsi_compat2.h>
+#include <scsi/scsi_transport_iscsi2.h>
+#include <scsi/libiscsi_tcp.h>
+
+MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, "
+	      "Dmitry Yusupov <dmitry_yus@yahoo.com>, "
+	      "Alex Aizman <itn780@yahoo.com>");
+MODULE_DESCRIPTION("iSCSI/TCP data-path");
+MODULE_LICENSE("GPL");
+
+static int iscsi_dbg_libtcp;
+module_param_named(debug_libiscsi_tcp, iscsi_dbg_libtcp, int,
+		   S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug_libiscsi_tcp, "Turn on debugging for libiscsi_tcp "
+		 "module. Set to 1 to turn on, and zero to turn off. Default "
+		 "is off.");
+
+#define ISCSI_DBG_TCP(_conn, dbg_fmt, arg...)			\
+	do {							\
+		if (iscsi_dbg_libtcp)				\
+			iscsi_conn_printk(KERN_INFO, _conn,	\
+					     "%s " dbg_fmt,	\
+					     __func__, ##arg);	\
+	} while (0);
+
+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)
+{
+	segment->sg = sg;
+	segment->sg_offset = offset;
+	segment->size = min(sg->length - offset,
+			    segment->total_size - segment->total_copied);
+	segment->data = NULL;
+}
+
+/**
+ * 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 void iscsi_tcp_segment_map(struct iscsi_segment *segment, int recv)
+{
+	struct scatterlist *sg;
+
+	if (segment->data != NULL || !segment->sg)
+		return;
+
+	sg = segment->sg;
+	BUG_ON(segment->sg_mapped);
+	BUG_ON(sg->length == 0);
+
+	/*
+	 * 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.
+	 */
+	if (page_count(sg_page(sg)) >= 1 && !recv)
+		return;
+
+	segment->sg_mapped = kmap_atomic(sg_page(sg), KM_SOFTIRQ0);
+	segment->data = segment->sg_mapped + sg->offset + segment->sg_offset;
+}
+
+void iscsi_tcp_segment_unmap(struct iscsi_segment *segment)
+{
+	if (segment->sg_mapped) {
+		kunmap_atomic(segment->sg_mapped, KM_SOFTIRQ0);
+		segment->sg_mapped = NULL;
+		segment->data = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_segment_unmap);
+
+/*
+ * Splice the digest buffer into the buffer
+ */
+static inline void
+iscsi_tcp_segment_splice_digest(struct iscsi_segment *segment, void *digest)
+{
+	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;
+}
+
+/**
+ * iscsi_tcp_segment_done - check whether the segment is complete
+ * @tcp_conn: iscsi tcp connection
+ * @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.
+ */
+int iscsi_tcp_segment_done(struct iscsi_tcp_conn *tcp_conn,
+			   struct iscsi_segment *segment, int recv,
+			   unsigned copied)
+{
+	struct scatterlist sg;
+	unsigned int pad;
+
+	ISCSI_DBG_TCP(tcp_conn->iscsi_conn, "copied %u %u size %u %s\n",
+		      segment->copied, copied, segment->size,
+		      recv ? "recv" : "xmit");
+	if (segment->hash && copied) {
+		/*
+		 * If a segment is kmapd we must unmap it before sending
+		 * to the crypto layer since that will try to kmap it again.
+		 */
+		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? */
+	ISCSI_DBG_TCP(tcp_conn->iscsi_conn, "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? */
+	if (!(tcp_conn->iscsi_conn->session->tt->caps & CAP_PADDING_OFFLOAD)) {
+		pad = iscsi_padding(segment->total_copied);
+		if (pad != 0) {
+			ISCSI_DBG_TCP(tcp_conn->iscsi_conn,
+				      "consume %d pad bytes\n", pad);
+			segment->total_size += pad;
+			segment->size = pad;
+			segment->data = segment->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;
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_segment_done);
+
+/**
+ * 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(tcp_conn, segment, 1, copy)) {
+		if (copied == len) {
+			ISCSI_DBG_TCP(tcp_conn->iscsi_conn,
+				      "copied %d bytes\n", len);
+			break;
+		}
+
+		copy = min(len - copied, segment->size - segment->copied);
+		ISCSI_DBG_TCP(tcp_conn->iscsi_conn, "copying %d\n", copy);
+		memcpy(segment->data + segment->copied, ptr + copied, copy);
+		copied += copy;
+	}
+	return copied;
+}
+
+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;
+
+	sg_init_one(&sg, hdr, hdrlen);
+	crypto_hash_digest(hash, &sg, hdrlen, digest);
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_dgst_header);
+
+static inline int
+iscsi_tcp_dgst_verify(struct iscsi_tcp_conn *tcp_conn,
+		      struct iscsi_segment *segment)
+{
+	if (!segment->digest_len)
+		return 1;
+
+	if (memcmp(segment->recv_digest, segment->digest,
+		   segment->digest_len)) {
+		ISCSI_DBG_TCP(tcp_conn->iscsi_conn, "digest mismatch\n");
+		return 0;
+	}
+
+	return 1;
+}
+
+/*
+ * 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);
+	}
+}
+
+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;
+}
+EXPORT_SYMBOL_GPL(iscsi_segment_init_linear);
+
+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;
+
+	/*
+	 * older kernels could send use_sg=0 for commands like sgio
+	 * or scsi-ml commands.
+	 */
+	if (!sg_count) {
+		iscsi_segment_init_linear(segment, (void *)sg_list + offset,
+					  size, done, hash);
+		return 0;
+	}
+
+	__iscsi_segment_init(segment, size, done, hash);
+	for_each_sg(sg_list, sg, sg_count, i) {
+		if (offset < sg->length) {
+			iscsi_tcp_segment_init_sg(segment, sg, offset);
+			return 0;
+		}
+		offset -= sg->length;
+	}
+
+	return ISCSI_ERR_DATA_OFFSET;
+}
+EXPORT_SYMBOL_GPL(iscsi_segment_seek_sg);
+
+/**
+ * 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.
+ */
+void iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn)
+{
+	ISCSI_DBG_TCP(tcp_conn->iscsi_conn,
+		      "(%s)\n", tcp_conn->iscsi_conn->hdrdgst_en ?
+		      "digest enabled" : "digest disabled");
+	iscsi_segment_init_linear(&tcp_conn->in.segment,
+				tcp_conn->in.hdr_buf, sizeof(struct iscsi_hdr),
+				iscsi_tcp_hdr_recv_done, NULL);
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_hdr_recv_prep);
+
+/*
+ * 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 = iscsi2_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 &
+	    !(conn->session->tt->caps & CAP_DIGEST_OFFLOAD))
+		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);
+}
+
+/**
+ * iscsi_tcp_cleanup_task - free tcp_task resources
+ * @task: iscsi task
+ *
+ * must be called with session lock
+ */
+void iscsi_tcp_cleanup_task(struct iscsi_task *task)
+{
+	struct iscsi_tcp_task *tcp_task = task->dd_data;
+	struct iscsi_r2t_info *r2t;
+
+	/* nothing to do for mgmt or pending tasks */
+	if (!task->sc || task->state == ISCSI_TASK_PENDING)
+		return;
+
+	/* flush task's r2t queues */
+	while (__kfifo_get(tcp_task->r2tqueue, (void*)&r2t, sizeof(void*))) {
+		__kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t,
+			    sizeof(void*));
+		ISCSI_DBG_TCP(task->conn, "pending r2t dropped\n");
+	}
+
+	r2t = tcp_task->r2t;
+	if (r2t != NULL) {
+		__kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t,
+			    sizeof(void*));
+		tcp_task->r2t = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_cleanup_task);
+
+/**
+ * iscsi_tcp_data_in - SCSI Data-In Response processing
+ * @conn: iscsi connection
+ * @task: scsi command task
+ */
+static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task)
+{
+	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_tcp_task *tcp_task = task->dd_data;
+	struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)tcp_conn->in.hdr;
+	int datasn = be32_to_cpu(rhdr->datasn);
+	unsigned total_in_length = scsi_bufflen(task->sc);
+
+	iscsi2_update_cmdsn(conn->session, (struct iscsi_nopin*)rhdr);
+	if (tcp_conn->in.datalen == 0)
+		return 0;
+
+	if (tcp_task->exp_datasn != datasn) {
+		ISCSI_DBG_TCP(conn, "task->exp_datasn(%d) != rhdr->datasn(%d)"
+			      "\n", tcp_task->exp_datasn, datasn);
+		return ISCSI_ERR_DATASN;
+	}
+
+	tcp_task->exp_datasn++;
+
+	tcp_task->data_offset = be32_to_cpu(rhdr->offset);
+	if (tcp_task->data_offset + tcp_conn->in.datalen > total_in_length) {
+		ISCSI_DBG_TCP(conn, "data_offset(%d) + data_len(%d) > "
+			      "total_length_in(%d)\n", tcp_task->data_offset,
+			      tcp_conn->in.datalen, total_in_length);
+		return ISCSI_ERR_DATA_OFFSET;
+	}
+
+	conn->datain_pdus_cnt++;
+	return 0;
+}
+
+/**
+ * iscsi_tcp_r2t_rsp - iSCSI R2T Response processing
+ * @conn: iscsi connection
+ * @task: scsi command task
+ */
+static int iscsi_tcp_r2t_rsp(struct iscsi_conn *conn, struct iscsi_task *task)
+{
+	struct iscsi_session *session = conn->session;
+	struct iscsi_tcp_task *tcp_task = task->dd_data;
+	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_r2t_rsp *rhdr = (struct iscsi_r2t_rsp *)tcp_conn->in.hdr;
+	struct iscsi_r2t_info *r2t;
+	int r2tsn = be32_to_cpu(rhdr->r2tsn);
+	int rc;
+
+	if (tcp_conn->in.datalen) {
+		iscsi_conn_printk(KERN_ERR, conn,
+				  "invalid R2t with datalen %d\n",
+				  tcp_conn->in.datalen);
+		return ISCSI_ERR_DATALEN;
+	}
+
+	if (tcp_task->exp_datasn != r2tsn){
+		ISCSI_DBG_TCP(conn, "task->exp_datasn(%d) != rhdr->r2tsn(%d)\n",
+			      tcp_task->exp_datasn, r2tsn);
+		return ISCSI_ERR_R2TSN;
+	}
+
+	/* fill-in new R2T associated with the task */
+	iscsi2_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
+
+	if (!task->sc || session->state != ISCSI_STATE_LOGGED_IN) {
+		iscsi_conn_printk(KERN_INFO, conn,
+				  "dropping R2T itt %d in recovery.\n",
+				  task->itt);
+		return 0;
+	}
+
+	rc = __kfifo_get(tcp_task->r2tpool.queue, (void*)&r2t, sizeof(void*));
+	if (!rc) {
+		iscsi_conn_printk(KERN_ERR, conn, "Could not allocate R2T. "
+				  "Target has sent more R2Ts than it "
+				  "negotiated for or driver has has leaked.\n");
+		return ISCSI_ERR_PROTO;
+	}
+
+	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_task->r2tpool.queue, (void*)&r2t,
+			    sizeof(void*));
+		return ISCSI_ERR_DATALEN;
+	}
+
+	if (r2t->data_length > session->max_burst)
+		ISCSI_DBG_TCP(conn, "invalid R2T with data len %u and max "
+			      "burst %u. Attempting to execute request.\n",
+			      r2t->data_length, session->max_burst);
+
+	r2t->data_offset = be32_to_cpu(rhdr->data_offset);
+	if (r2t->data_offset + r2t->data_length > scsi_bufflen(task->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_bufflen(task->sc));
+		__kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t,
+			    sizeof(void*));
+		return ISCSI_ERR_DATALEN;
+	}
+
+	r2t->ttt = rhdr->ttt; /* no flip */
+	r2t->datasn = 0;
+	r2t->sent = 0;
+
+	tcp_task->exp_datasn = r2tsn + 1;
+	__kfifo_put(tcp_task->r2tqueue, (void*)&r2t, sizeof(void*));
+	conn->r2t_pdus_cnt++;
+
+	iscsi2_requeue_task(task);
+	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 = iscsi2_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
+		if (rc)
+			return rc;
+	}
+
+	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)
+{
+	int rc = 0, opcode, ahslen;
+	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+	struct iscsi_task *task;
+
+	/* 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);
+		return ISCSI_ERR_DATALEN;
+	}
+
+	/* Additional header segments. So far, we don't
+	 * process additional headers.
+	 */
+	ahslen = hdr->hlength << 2;
+
+	opcode = hdr->opcode & ISCSI_OPCODE_MASK;
+	/* verify itt (itt encoding: age+cid+itt) */
+	rc = iscsi2_verify_itt(conn, hdr->itt);
+	if (rc)
+		return rc;
+
+	ISCSI_DBG_TCP(conn, "opcode 0x%x ahslen %d datalen %d\n",
+		      opcode, ahslen, tcp_conn->in.datalen);
+
+	switch(opcode) {
+	case ISCSI_OP_SCSI_DATA_IN:
+		spin_lock(&conn->session->lock);
+		task = iscsi2_itt_to_ctask(conn, hdr->itt);
+		if (!task)
+			rc = ISCSI_ERR_BAD_ITT;
+		else
+			rc = iscsi_tcp_data_in(conn, task);
+		if (rc) {
+			spin_unlock(&conn->session->lock);
+			break;
+		}
+
+		if (tcp_conn->in.datalen) {
+			struct iscsi_tcp_task *tcp_task = task->dd_data;
+			struct hash_desc *rx_hash = NULL;
+
+			/*
+			 * 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 &&
+			    !(conn->session->tt->caps & CAP_DIGEST_OFFLOAD))
+				rx_hash = tcp_conn->rx_hash;
+
+			ISCSI_DBG_TCP(conn, "iscsi_tcp_begin_data_in( "
+				     "offset=%d, datalen=%d)\n",
+				      tcp_task->data_offset,
+				      tcp_conn->in.datalen);
+			rc = iscsi_segment_seek_sg(&tcp_conn->in.segment,
+						   scsi_sglist(task->sc),
+						   scsi_sg_count(task->sc),
+						   tcp_task->data_offset,
+						   tcp_conn->in.datalen,
+						   iscsi_tcp_process_data_in,
+						   rx_hash);
+			spin_unlock(&conn->session->lock);
+			return rc;
+		}
+		rc = __iscsi2_complete_pdu(conn, hdr, NULL, 0);
+		spin_unlock(&conn->session->lock);
+		break;
+	case ISCSI_OP_SCSI_CMD_RSP:
+		if (tcp_conn->in.datalen) {
+			iscsi_tcp_data_recv_prep(tcp_conn);
+			return 0;
+		}
+		rc = iscsi2_complete_pdu(conn, hdr, NULL, 0);
+		break;
+	case ISCSI_OP_R2T:
+		spin_lock(&conn->session->lock);
+		task = iscsi2_itt_to_ctask(conn, hdr->itt);
+		if (!task)
+			rc = ISCSI_ERR_BAD_ITT;
+		else if (ahslen)
+			rc = ISCSI_ERR_AHSLEN;
+		else if (task->sc->sc_data_direction == DMA_TO_DEVICE)
+			rc = iscsi_tcp_r2t_rsp(conn, task);
+		else
+			rc = ISCSI_ERR_PROTO;
+		spin_unlock(&conn->session->lock);
+		break;
+	case ISCSI_OP_LOGIN_RSP:
+	case ISCSI_OP_TEXT_RSP:
+	case ISCSI_OP_REJECT:
+	case ISCSI_OP_ASYNC_EVENT:
+		/*
+		 * It is possible that we could get a PDU with a buffer larger
+		 * 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);
+			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;
+		}
+	/* fall through */
+	case ISCSI_OP_LOGOUT_RSP:
+	case ISCSI_OP_NOOP_IN:
+	case ISCSI_OP_SCSI_TMFUNC_RSP:
+		rc = iscsi2_complete_pdu(conn, hdr, NULL, 0);
+		break;
+	default:
+		rc = ISCSI_ERR_BAD_OPCODE;
+		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;
+}
+
+/**
+ * iscsi_tcp_hdr_recv_done - process PDU header
+ *
+ * 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)
+{
+	struct iscsi_conn *conn = tcp_conn->iscsi_conn;
+	struct iscsi_hdr *hdr;
+
+	/* Check if there are additional header segments
+	 * *prior* to computing the digest, because we
+	 * may need to go back to the caller for more.
+	 */
+	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;
+	}
+
+	/* 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 &&
+	    !(conn->session->tt->caps & CAP_DIGEST_OFFLOAD)) {
+		if (segment->digest_len == 0) {
+			/*
+			 * Even if we offload the digest processing we
+			 * splice it in so we can increment the skb/segment
+			 * counters in preparation for the data segment.
+			 */
+			iscsi_tcp_segment_splice_digest(segment,
+							segment->recv_digest);
+			return 0;
+		}
+
+		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;
+	}
+
+	tcp_conn->in.hdr = hdr;
+	return iscsi_tcp_hdr_dissect(conn, hdr);
+}
+
+/**
+ * iscsi_tcp_recv_segment_is_hdr - tests if we are reading in a header
+ * @tcp_conn: iscsi tcp conn
+ *
+ * returns non zero if we are currently processing or setup to process
+ * a header.
+ */
+inline int iscsi_tcp_recv_segment_is_hdr(struct iscsi_tcp_conn *tcp_conn)
+{
+	return tcp_conn->in.segment.done == iscsi_tcp_hdr_recv_done;
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_recv_segment_is_hdr);
+
+/**
+ * iscsi_tcp_recv_skb - Process skb
+ * @conn: iscsi connection
+ * @skb: network buffer with header and/or data segment
+ * @offset: offset in skb
+ * @offload: bool indicating if transfer was offloaded
+ *
+ * Will return status of transfer in status. And will return
+ * number of bytes copied.
+ */
+int iscsi_tcp_recv_skb(struct iscsi_conn *conn, struct sk_buff *skb,
+		       unsigned int offset, bool offloaded, int *status)
+{
+	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;
+
+	ISCSI_DBG_TCP(conn, "in %d bytes\n", skb->len - offset);
+
+	if (unlikely(conn->suspend_rx)) {
+		ISCSI_DBG_TCP(conn, "Rx suspended!\n");
+		*status = ISCSI_TCP_SUSPENDED;
+		return 0;
+	}
+
+	if (offloaded) {
+		segment->total_copied = segment->total_size;
+		goto segment_done;
+	}
+
+	skb_prepare_seq_read(skb, offset, skb->len, &seq);
+	while (1) {
+		unsigned int avail;
+		const u8 *ptr;
+
+		avail = skb_seq_read(consumed, &ptr, &seq);
+		if (avail == 0) {
+			ISCSI_DBG_TCP(conn, "no more data avail. Consumed %d\n",
+				      consumed);
+			*status = ISCSI_TCP_SKB_DONE;
+			skb_abort_seq_read(&seq);
+			goto skb_done;
+		}
+		BUG_ON(segment->copied >= segment->size);
+
+		ISCSI_DBG_TCP(conn, "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) {
+			skb_abort_seq_read(&seq);
+			goto segment_done;
+		}
+	}
+
+segment_done:
+	*status = ISCSI_TCP_SEGMENT_DONE;
+	ISCSI_DBG_TCP(conn, "segment done\n");
+	rc = segment->done(tcp_conn, segment);
+	if (rc != 0) {
+		*status = ISCSI_TCP_CONN_ERR;
+		ISCSI_DBG_TCP(conn, "Error receiving PDU, errno=%d\n", rc);
+		iscsi2_conn_failure(conn, rc);
+		return 0;
+	}
+	/* The done() functions sets up the next segment. */
+
+skb_done:
+	conn->rxdata_octets += consumed;
+	return consumed;
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_recv_skb);
+
+/**
+ * iscsi_tcp_task_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
+ * @conn: iscsi connection
+ * @task: scsi command task
+ * @sc: scsi command
+ */
+int iscsi_tcp_task_init(struct iscsi_task *task)
+{
+	struct iscsi_tcp_task *tcp_task = task->dd_data;
+	struct iscsi_conn *conn = task->conn;
+	struct scsi_cmnd *sc = task->sc;
+	int err;
+
+	if (!sc) {
+		/*
+		 * mgmt tasks do not have a scatterlist since they come
+		 * in from the iscsi interface.
+		 */
+		ISCSI_DBG_TCP(conn, "mtask deq [itt 0x%x]\n", task->itt);
+
+		return conn->session->tt->init_pdu(task, 0, task->data_count);
+	}
+
+	BUG_ON(__kfifo_len(tcp_task->r2tqueue));
+	tcp_task->exp_datasn = 0;
+
+	/* Prepare PDU, optionally w/ immediate data */
+	ISCSI_DBG_TCP(conn, "task deq [itt 0x%x imm %d unsol %d]\n",
+		      task->itt, task->imm_count, task->unsol_r2t.data_length);
+
+	err = conn->session->tt->init_pdu(task, 0, task->imm_count);
+	if (err)
+		return err;
+	task->imm_count = 0;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_task_init);
+
+static struct iscsi_r2t_info *iscsi_tcp_get_curr_r2t(struct iscsi_task *task)
+{
+	struct iscsi_session *session = task->conn->session;
+	struct iscsi_tcp_task *tcp_task = task->dd_data;
+	struct iscsi_r2t_info *r2t = NULL;
+
+	if (iscsi_task_has_unsol_data(task))
+		r2t = &task->unsol_r2t;
+	else {
+		spin_lock_bh(&session->lock);
+		if (tcp_task->r2t) {
+			r2t = tcp_task->r2t;
+			/* Continue with this R2T? */
+			if (r2t->data_length <= r2t->sent) {
+				ISCSI_DBG_TCP(task->conn,
+					      "  done with r2t %p\n", r2t);
+				__kfifo_put(tcp_task->r2tpool.queue,
+					    (void *)&tcp_task->r2t,
+					    sizeof(void *));
+				tcp_task->r2t = r2t = NULL;
+			}
+		}
+
+		if (r2t == NULL) {
+			__kfifo_get(tcp_task->r2tqueue,
+				    (void *)&tcp_task->r2t, sizeof(void *));
+			r2t = tcp_task->r2t;
+		}
+		spin_unlock_bh(&session->lock);
+	}
+
+	return r2t;
+}
+
+/**
+ * iscsi_tcp_task_xmit - xmit normal PDU task
+ * @task: 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.
+ */
+int iscsi_tcp_task_xmit(struct iscsi_task *task)
+{
+	struct iscsi_conn *conn = task->conn;
+	struct iscsi_session *session = conn->session;
+	struct iscsi_r2t_info *r2t;
+	int rc = 0;
+
+flush:
+	/* Flush any pending data first. */
+	rc = session->tt->xmit_pdu(task);
+	if (rc < 0)
+		return rc;
+
+	/* mgmt command */
+	if (!task->sc) {
+		if (task->hdr->itt == RESERVED_ITT)
+			iscsi2_put_task(task);
+		return 0;
+	}
+
+	/* Are we done already? */
+	if (task->sc->sc_data_direction != DMA_TO_DEVICE)
+		return 0;
+
+	r2t = iscsi_tcp_get_curr_r2t(task);
+	if (r2t == NULL) {
+		/* Waiting for more R2Ts to arrive. */
+		ISCSI_DBG_TCP(conn, "no R2Ts yet\n");
+		return 0;
+	}
+
+	rc = conn->session->tt->alloc_pdu(task, ISCSI_OP_SCSI_DATA_OUT);
+	if (rc)
+		return rc;
+	iscsi2_prep_data_out_pdu(task, r2t, (struct iscsi_data *) task->hdr);
+
+	ISCSI_DBG_TCP(conn, "sol dout %p [dsn %d itt 0x%x doff %d dlen %d]\n",
+		      r2t, r2t->datasn - 1, task->hdr->itt,
+		      r2t->data_offset + r2t->sent, r2t->data_count);
+
+	rc = conn->session->tt->init_pdu(task, r2t->data_offset + r2t->sent,
+					 r2t->data_count);
+	if (rc) {
+		iscsi2_conn_failure(conn, ISCSI_ERR_XMIT_FAILED);
+		return rc;
+	}
+
+	r2t->sent += r2t->data_count;
+	goto flush;
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_task_xmit);
+
+struct iscsi_cls_conn *
+iscsi_tcp_conn_setup(struct iscsi_cls_session *cls_session, int dd_data_size,
+		      uint32_t conn_idx)
+
+{
+	struct iscsi_conn *conn;
+	struct iscsi_cls_conn *cls_conn;
+	struct iscsi_tcp_conn *tcp_conn;
+
+	cls_conn = iscsi2_conn_setup(cls_session, sizeof(*tcp_conn), conn_idx);
+	if (!cls_conn)
+		return NULL;
+	conn = cls_conn->dd_data;
+	/*
+	 * due to strange issues with iser these are not set
+	 * in iscsi_conn_setup
+	 */
+	conn->max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN;
+
+	tcp_conn = conn->dd_data;
+	tcp_conn->iscsi_conn = conn;
+
+	tcp_conn->dd_data = kzalloc(dd_data_size, GFP_KERNEL);
+	if (!tcp_conn->dd_data) {
+		iscsi2_conn_teardown(cls_conn);
+		return NULL;
+	}
+	return cls_conn;
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_conn_setup);
+
+void iscsi_tcp_conn_teardown(struct iscsi_cls_conn *cls_conn)
+{
+	struct iscsi_conn *conn = cls_conn->dd_data;
+	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
+
+	kfree(tcp_conn->dd_data);
+	iscsi2_conn_teardown(cls_conn);
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_conn_teardown);
+
+int iscsi_tcp_r2tpool_alloc(struct iscsi_session *session)
+{
+	int i;
+	int cmd_i;
+
+	/*
+	 * initialize per-task: R2T pool and xmit queue
+	 */
+	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
+	        struct iscsi_task *task = session->cmds[cmd_i];
+		struct iscsi_tcp_task *tcp_task = task->dd_data;
+
+		/*
+		 * pre-allocated x2 as much r2ts to handle race when
+		 * target acks DataOut faster than we data_xmit() queues
+		 * could replenish r2tqueue.
+		 */
+
+		/* R2T pool */
+		if (iscsi2_pool_init(&tcp_task->r2tpool,
+				    session->max_r2t * 2, NULL,
+				    sizeof(struct iscsi_r2t_info))) {
+			goto r2t_alloc_fail;
+		}
+
+		/* R2T xmit queue */
+		tcp_task->r2tqueue = kfifo_alloc(
+		      session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL);
+		if (tcp_task->r2tqueue == ERR_PTR(-ENOMEM)) {
+			iscsi2_pool_free(&tcp_task->r2tpool);
+			goto r2t_alloc_fail;
+		}
+	}
+
+	return 0;
+
+r2t_alloc_fail:
+	for (i = 0; i < cmd_i; i++) {
+		struct iscsi_task *task = session->cmds[i];
+		struct iscsi_tcp_task *tcp_task = task->dd_data;
+
+		kfifo_free(tcp_task->r2tqueue);
+		iscsi2_pool_free(&tcp_task->r2tpool);
+	}
+	return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_r2tpool_alloc);
+
+void iscsi_tcp_r2tpool_free(struct iscsi_session *session)
+{
+	int i;
+
+	for (i = 0; i < session->cmds_max; i++) {
+		struct iscsi_task *task = session->cmds[i];
+		struct iscsi_tcp_task *tcp_task = task->dd_data;
+
+		kfifo_free(tcp_task->r2tqueue);
+		iscsi2_pool_free(&tcp_task->r2tpool);
+	}
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_r2tpool_free);
+
+void iscsi_tcp_conn_get_stats(struct iscsi_cls_conn *cls_conn,
+			      struct iscsi_stats *stats)
+{
+	struct iscsi_conn *conn = cls_conn->dd_data;
+
+	stats->txdata_octets = conn->txdata_octets;
+	stats->rxdata_octets = conn->rxdata_octets;
+	stats->scsicmd_pdus = conn->scsicmd_pdus_cnt;
+	stats->dataout_pdus = conn->dataout_pdus_cnt;
+	stats->scsirsp_pdus = conn->scsirsp_pdus_cnt;
+	stats->datain_pdus = conn->datain_pdus_cnt;
+	stats->r2t_pdus = conn->r2t_pdus_cnt;
+	stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt;
+	stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt;
+}
+EXPORT_SYMBOL_GPL(iscsi_tcp_conn_get_stats);
diff --git a/drivers/scsi/qla4xxx/Kconfig b/drivers/scsi/qla4xxx/Kconfig
index 4eda26a..9fa4405 100644
--- a/drivers/scsi/qla4xxx/Kconfig
+++ b/drivers/scsi/qla4xxx/Kconfig
@@ -1,7 +1,7 @@
 config SCSI_QLA_ISCSI
 	tristate "QLogic ISP4XXX host adapter family support"
 	depends on PCI && SCSI
-	select SCSI_ISCSI_ATTRS
+	select SCSI_ISCSI2_ATTRS
 	---help---
 	This driver supports the QLogic 40xx (ISP4XXX) iSCSI host 
 	adapter family.
diff --git a/drivers/scsi/qla4xxx/ql4_def.h b/drivers/scsi/qla4xxx/ql4_def.h
index 97caead..eb35076 100644
--- a/drivers/scsi/qla4xxx/ql4_def.h
+++ b/drivers/scsi/qla4xxx/ql4_def.h
@@ -31,7 +31,7 @@
 #include <scsi/scsi_device.h>
 #include <scsi/scsi_cmnd.h>
 #include <scsi/scsi_transport.h>
-#include <scsi/scsi_transport_iscsi.h>
+#include <scsi/scsi_transport_iscsi2.h>
 
 
 #ifndef PCI_DEVICE_ID_QLOGIC_ISP4010
diff --git a/drivers/scsi/qla4xxx/ql4_os.c b/drivers/scsi/qla4xxx/ql4_os.c
index 14b3fa3..95d097a 100644
--- a/drivers/scsi/qla4xxx/ql4_os.c
+++ b/drivers/scsi/qla4xxx/ql4_os.c
@@ -66,7 +66,9 @@ void qla4xxx_config_dma_addressing(struct scsi_qla_host *ha);
 /*
  * iSCSI template entry points
  */
-static int qla4xxx_tgt_dscvr(enum iscsi_tgt_dscvr type, uint32_t host_no,
+
+static int qla4xxx_tgt_dscvr(struct Scsi_Host *shost,
+			     enum iscsi_tgt_dscvr type,
 			     uint32_t enable, struct sockaddr *dst_addr);
 static int qla4xxx_conn_get_param(struct iscsi_cls_conn *conn,
 				  enum iscsi_param param, char *buf);
@@ -111,8 +113,6 @@ static struct iscsi_transport qla4xxx_iscsi_transport = {
 				  ISCSI_CONN_ADDRESS |
 				  ISCSI_TARGET_NAME |
 				  ISCSI_TPGT,
-	.sessiondata_size	= sizeof(struct ddb_entry),
-	.host_template		= &qla4xxx_driver_template,
 
 	.tgt_dscvr		= qla4xxx_tgt_dscvr,
 	.get_conn_param		= qla4xxx_conn_get_param,
@@ -150,7 +150,7 @@ int qla4xxx_conn_start(struct iscsi_cls_conn *conn)
 	DEBUG2(printk("scsi%ld: %s: index [%d] starting conn\n",
 		      ddb_entry->ha->host_no, __func__,
 		      ddb_entry->fw_ddb_index));
-	iscsi_unblock_session(session);
+	iscsi2_unblock_session(session);
 	return 0;
 }
 
@@ -166,7 +166,7 @@ static void qla4xxx_conn_stop(struct iscsi_cls_conn *conn, int flag)
 		      ddb_entry->ha->host_no, __func__,
 		      ddb_entry->fw_ddb_index));
 	if (flag == STOP_CONN_RECOVER)
-		iscsi_block_session(session);
+		iscsi2_block_session(session);
 	else
 		printk(KERN_ERR "iscsi: invalid stop flag %d\n", flag);
 }
@@ -218,21 +218,15 @@ static int qla4xxx_conn_get_param(struct iscsi_cls_conn *conn,
 	return len;
 }
 
-static int qla4xxx_tgt_dscvr(enum iscsi_tgt_dscvr type, uint32_t host_no,
+static int qla4xxx_tgt_dscvr(struct Scsi_Host *shost,
+			     enum iscsi_tgt_dscvr type,
 			     uint32_t enable, struct sockaddr *dst_addr)
 {
 	struct scsi_qla_host *ha;
-	struct Scsi_Host *shost;
 	struct sockaddr_in *addr;
 	struct sockaddr_in6 *addr6;
 	int ret = 0;
 
-	shost = scsi_host_lookup(host_no);
-	if (!shost) {
-		printk(KERN_ERR "Could not find host no %u\n", host_no);
-		return -ENODEV;
-	}
-
 	ha = (struct scsi_qla_host *) shost->hostdata;
 
 	switch (type) {
@@ -257,7 +251,6 @@ static int qla4xxx_tgt_dscvr(enum iscsi_tgt_dscvr type, uint32_t host_no,
 		ret = -ENOSYS;
 	}
 
-	scsi_host_put(shost);
 	return ret;
 }
 
@@ -267,26 +260,25 @@ void qla4xxx_destroy_sess(struct ddb_entry *ddb_entry)
 		return;
 
 	if (ddb_entry->conn) {
-		iscsi_if_destroy_session_done(ddb_entry->conn);
-		iscsi_destroy_conn(ddb_entry->conn);
-		iscsi_remove_session(ddb_entry->sess);
+		iscsi2_destroy_conn(ddb_entry->conn);
+		iscsi2_remove_session(ddb_entry->sess);
 	}
-	iscsi_free_session(ddb_entry->sess);
+	iscsi2_free_session(ddb_entry->sess);
 }
 
 int qla4xxx_add_sess(struct ddb_entry *ddb_entry, int scan)
 {
 	int err;
 
-	err = iscsi_add_session(ddb_entry->sess, ddb_entry->fw_ddb_index);
+	err = iscsi2_add_session(ddb_entry->sess, ddb_entry->fw_ddb_index);
 	if (err) {
 		DEBUG2(printk(KERN_ERR "Could not add session.\n"));
 		return err;
 	}
 
-	ddb_entry->conn = iscsi_create_conn(ddb_entry->sess, 0);
+	ddb_entry->conn = iscsi2_create_conn(ddb_entry->sess, 0, 0);
 	if (!ddb_entry->conn) {
-		iscsi_remove_session(ddb_entry->sess);
+		iscsi2_remove_session(ddb_entry->sess);
 		DEBUG2(printk(KERN_ERR "Could not add connection.\n"));
 		return -ENOMEM;
 	}
@@ -296,7 +288,7 @@ int qla4xxx_add_sess(struct ddb_entry *ddb_entry, int scan)
 		scsi_scan_target(&ddb_entry->sess->dev, 0,
 				 ddb_entry->sess->target_id,
 				 SCAN_WILD_CARD, 0);
-	iscsi_if_create_session_done(ddb_entry->conn);
+	iscsi2_unblock_session(ddb_entry->sess);
 	return 0;
 }
 
@@ -305,7 +297,8 @@ struct ddb_entry *qla4xxx_alloc_sess(struct scsi_qla_host *ha)
 	struct ddb_entry *ddb_entry;
 	struct iscsi_cls_session *sess;
 
-	sess = iscsi_alloc_session(ha->host, &qla4xxx_iscsi_transport);
+	sess = iscsi2_alloc_session(ha->host, &qla4xxx_iscsi_transport,
+				    sizeof(*ddb_entry));
 	if (!sess)
 		return NULL;
 
@@ -1699,7 +1692,7 @@ static int __init qla4xxx_module_init(void)
 		strcat(qla4xxx_version_str, "-debug");
 
 	qla4xxx_scsi_transport =
-		iscsi_register_transport(&qla4xxx_iscsi_transport);
+		iscsi2_register_transport(&qla4xxx_iscsi_transport);
 	if (!qla4xxx_scsi_transport){
 		ret = -ENODEV;
 		goto release_srb_cache;
@@ -1713,7 +1706,7 @@ static int __init qla4xxx_module_init(void)
 	printk(KERN_INFO "QLogic iSCSI HBA Driver\n");
 	return 0;
 unregister_transport:
-	iscsi_unregister_transport(&qla4xxx_iscsi_transport);
+	iscsi2_unregister_transport(&qla4xxx_iscsi_transport);
 release_srb_cache:
 	kmem_cache_destroy(srb_cachep);
 no_srp_cache:
@@ -1724,7 +1717,7 @@ static void __exit qla4xxx_module_exit(void)
 {
 	ql4_mod_unload = 1;
 	pci_unregister_driver(&qla4xxx_pci_driver);
-	iscsi_unregister_transport(&qla4xxx_iscsi_transport);
+	iscsi2_unregister_transport(&qla4xxx_iscsi_transport);
 	kmem_cache_destroy(srb_cachep);
 }
 
diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
index 2cd2396..d410da2 100644
--- a/drivers/scsi/scsi_transport_iscsi.c
+++ b/drivers/scsi/scsi_transport_iscsi.c
@@ -1,10 +1,7 @@
 /*
- * iSCSI transport class definitions
+ * Compat wrapper module for the iSCSI interface
  *
- * Copyright (C) IBM Corporation, 2004
- * Copyright (C) Mike Christie, 2004 - 2005
- * Copyright (C) Dmitry Yusupov, 2004 - 2005
- * Copyright (C) Alex Aizman, 2004 - 2005
+ * Copyright (C) 2009 Red Hat, Inc.  All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,1540 +18,42 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 #include <linux/module.h>
-#include <linux/mutex.h>
-#include <net/tcp.h>
-#include <scsi/scsi.h>
-#include <scsi/scsi_host.h>
-#include <scsi/scsi_device.h>
-#include <scsi/scsi_transport.h>
-#include <scsi/scsi_transport_iscsi.h>
-#include <scsi/iscsi_if.h>
 
-#define ISCSI_SESSION_ATTRS 15
-#define ISCSI_CONN_ATTRS 13
-#define ISCSI_HOST_ATTRS 4
-#define ISCSI_TRANSPORT_VERSION "2.0-724"
+static unsigned long iscsi_if_loaded;
 
-struct iscsi_internal {
-	int daemon_pid;
-	struct scsi_transport_template t;
-	struct iscsi_transport *iscsi_transport;
-	struct list_head list;
-	struct class_device cdev;
-
-	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
-	struct transport_container conn_cont;
-	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
-	struct transport_container session_cont;
-	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
-};
-
-static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
-
-/*
- * list of registered transports and lock that must
- * be held while accessing list. The iscsi_transport_lock must
- * be acquired after the rx_queue_mutex.
- */
-static LIST_HEAD(iscsi_transports);
-static DEFINE_SPINLOCK(iscsi_transport_lock);
-
-#define to_iscsi_internal(tmpl) \
-	container_of(tmpl, struct iscsi_internal, t)
-
-#define cdev_to_iscsi_internal(_cdev) \
-	container_of(_cdev, struct iscsi_internal, cdev)
-
-static void iscsi_transport_release(struct class_device *cdev)
-{
-	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
-	kfree(priv);
-}
-
-/*
- * iscsi_transport_class represents the iscsi_transports that are
- * registered.
- */
-static struct class iscsi_transport_class = {
-	.name = "iscsi_transport",
-	.release = iscsi_transport_release,
-};
-
-static ssize_t
-show_transport_handle(struct class_device *cdev, char *buf)
-{
-	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
-	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
-}
-static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
-
-#define show_transport_attr(name, format)				\
-static ssize_t								\
-show_transport_##name(struct class_device *cdev, char *buf)		\
-{									\
-	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
-	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
-}									\
-static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
-
-show_transport_attr(caps, "0x%x");
-show_transport_attr(max_lun, "%d");
-show_transport_attr(max_conn, "%d");
-show_transport_attr(max_cmd_len, "%d");
-
-static struct attribute *iscsi_transport_attrs[] = {
-	&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,
-};
-
-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)
-{
-	struct Scsi_Host *shost = dev_to_shost(dev);
-	struct iscsi_host *ihost = shost->shost_data;
-
-	memset(ihost, 0, sizeof(*ihost));
-	INIT_LIST_HEAD(&ihost->sessions);
-	mutex_init(&ihost->mutex);
-	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,
-			       NULL,
-			       NULL);
-
-static DECLARE_TRANSPORT_CLASS(iscsi_connection_class,
-			       "iscsi_connection",
-			       NULL,
-			       NULL,
-			       NULL);
-
-static struct sock *nls;
-static DEFINE_MUTEX(rx_queue_mutex);
-
-static LIST_HEAD(sesslist);
-static DEFINE_SPINLOCK(sesslock);
-static LIST_HEAD(connlist);
-static DEFINE_SPINLOCK(connlock);
-
-static uint32_t iscsi_conn_get_sid(struct iscsi_cls_conn *conn)
-{
-	struct iscsi_cls_session *sess = iscsi_dev_to_session(conn->dev.parent);
-	return sess->sid;
-}
-
-/*
- * Returns the matching session to a given sid
- */
-static struct iscsi_cls_session *iscsi_session_lookup(uint32_t sid)
-{
-	unsigned long flags;
-	struct iscsi_cls_session *sess;
-
-	spin_lock_irqsave(&sesslock, flags);
-	list_for_each_entry(sess, &sesslist, sess_list) {
-		if (sess->sid == sid) {
-			spin_unlock_irqrestore(&sesslock, flags);
-			return sess;
-		}
-	}
-	spin_unlock_irqrestore(&sesslock, flags);
-	return NULL;
-}
-
-/*
- * Returns the matching connection to a given sid / cid tuple
- */
-static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid)
-{
-	unsigned long flags;
-	struct iscsi_cls_conn *conn;
-
-	spin_lock_irqsave(&connlock, flags);
-	list_for_each_entry(conn, &connlist, conn_list) {
-		if ((conn->cid == cid) && (iscsi_conn_get_sid(conn) == sid)) {
-			spin_unlock_irqrestore(&connlock, flags);
-			return conn;
-		}
-	}
-	spin_unlock_irqrestore(&connlock, flags);
-	return NULL;
-}
-
-/*
- * The following functions can be used by LLDs that allocate
- * their own scsi_hosts or by software iscsi LLDs
- */
-static void iscsi_session_release(struct device *dev)
-{
-	struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
-	struct Scsi_Host *shost;
-
-	shost = iscsi_session_to_shost(session);
-	scsi_host_put(shost);
-	kfree(session);
-}
-
-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(void *data)
-{
-	struct iscsi_cls_session *session = data;
-
-	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);
-
-	scsi_target_unblock(&session->dev);
-}
-
-void iscsi_unblock_session(struct iscsi_cls_session *session)
-{
-	if (!cancel_delayed_work(&session->recovery_work))
-		flush_scheduled_work();
-	scsi_target_unblock(&session->dev);
-}
-EXPORT_SYMBOL_GPL(iscsi_unblock_session);
-
-void iscsi_block_session(struct iscsi_cls_session *session)
-{
-	scsi_target_block(&session->dev);
-	schedule_delayed_work(&session->recovery_work,
-			     session->recovery_tmo * HZ);
-}
-EXPORT_SYMBOL_GPL(iscsi_block_session);
-
-struct iscsi_cls_session *
-iscsi_alloc_session(struct Scsi_Host *shost,
-		    struct iscsi_transport *transport)
-{
-	struct iscsi_cls_session *session;
-
-	session = kzalloc(sizeof(*session) + transport->sessiondata_size,
-			  GFP_KERNEL);
-	if (!session)
-		return NULL;
-
-	session->transport = transport;
-	session->recovery_tmo = 120;
-	INIT_WORK(&session->recovery_work, session_recovery_timedout, session);
-	INIT_LIST_HEAD(&session->host_list);
-	INIT_LIST_HEAD(&session->sess_list);
-
-	/* this is released in the dev's release function */
-	scsi_host_get(shost);
-	session->dev.parent = &shost->shost_gendev;
-	session->dev.release = iscsi_session_release;
-	device_initialize(&session->dev);
-	if (transport->sessiondata_size)
-		session->dd_data = &session[1];
-	return session;
-}
-EXPORT_SYMBOL_GPL(iscsi_alloc_session);
-
-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;
-	int err;
-
-	ihost = shost->shost_data;
-	session->sid = atomic_add_return(1, &iscsi_session_nr);
-	session->target_id = target_id;
-
-	snprintf(session->dev.bus_id, BUS_ID_SIZE, "session%u",
-		 session->sid);
-	err = device_add(&session->dev);
-	if (err) {
-		dev_printk(KERN_ERR, &session->dev, "iscsi: could not "
-			   "register session's dev\n");
-		goto release_host;
-	}
-	transport_register_device(&session->dev);
-
-	mutex_lock(&ihost->mutex);
-	list_add(&session->host_list, &ihost->sessions);
-	mutex_unlock(&ihost->mutex);
-	return 0;
-
-release_host:
-	scsi_host_put(shost);
-	return err;
-}
-EXPORT_SYMBOL_GPL(iscsi_add_session);
+#define ISCSI_IF_LOADED 1
 
 /**
- * iscsi_create_session - create iscsi class session
- * @shost: scsi host
- * @transport: iscsi transport
+ * iscsi_if_claim - load iscsi interface.
  *
- * 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,
-		     unsigned int target_id)
-{
-	struct iscsi_cls_session *session;
-
-	session = iscsi_alloc_session(shost, transport);
-	if (!session)
-		return NULL;
-
-	if (iscsi_add_session(session, target_id)) {
-		iscsi_free_session(session);
-		return NULL;
-	}
-	return session;
-}
-EXPORT_SYMBOL_GPL(iscsi_create_session);
-
-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;
-
-	if (!cancel_delayed_work(&session->recovery_work))
-		flush_scheduled_work();
-
-	mutex_lock(&ihost->mutex);
-	list_del(&session->host_list);
-	mutex_unlock(&ihost->mutex);
-
-	scsi_remove_target(&session->dev);
-
-	transport_unregister_device(&session->dev);
-	device_del(&session->dev);
-}
-EXPORT_SYMBOL_GPL(iscsi_remove_session);
-
-void iscsi_free_session(struct iscsi_cls_session *session)
-{
-	put_device(&session->dev);
-}
-
-EXPORT_SYMBOL_GPL(iscsi_free_session);
-
-/**
- * iscsi_destroy_session - destroy iscsi session
- * @session: iscsi_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);
-	iscsi_free_session(session);
-	return 0;
-}
-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
- * @cid: connection id
- *
- * This can be called from a LLD or iscsi_transport. The connection
- * is child of the session so cid must be unique for all connections
- * on the session.
- *
- * Since we do not support MCS, cid will normally be zero. In some cases
- * 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;
-	int err;
-
-	conn = kzalloc(sizeof(*conn) + transport->conndata_size, GFP_KERNEL);
-	if (!conn)
-		return NULL;
-
-	if (transport->conndata_size)
-		conn->dd_data = &conn[1];
-
-	INIT_LIST_HEAD(&conn->conn_list);
-	conn->transport = transport;
-	conn->cid = cid;
-
-	/* this is released in the dev's release function */
-	if (!get_device(&session->dev))
-		goto free_conn;
-
-	snprintf(conn->dev.bus_id, BUS_ID_SIZE, "connection%d:%u",
-		 session->sid, cid);
-	conn->dev.parent = &session->dev;
-	conn->dev.release = iscsi_conn_release;
-	err = device_register(&conn->dev);
-	if (err) {
-		dev_printk(KERN_ERR, &conn->dev, "iscsi: could not register "
-			   "connection's dev\n");
-		goto release_parent_ref;
-	}
-	transport_register_device(&conn->dev);
-	return conn;
-
-release_parent_ref:
-	put_device(&session->dev);
-free_conn:
-	kfree(conn);
-	return NULL;
-}
-
-EXPORT_SYMBOL_GPL(iscsi_create_conn);
-
-/**
- * iscsi_destroy_conn - destroy iscsi class connection
- * @session: iscsi cls session
- *
- * This can be called from a LLD or iscsi_transport.
- **/
-int iscsi_destroy_conn(struct iscsi_cls_conn *conn)
-{
-	transport_unregister_device(&conn->dev);
-	device_unregister(&conn->dev);
-	return 0;
-}
-
-EXPORT_SYMBOL_GPL(iscsi_destroy_conn);
-
-/*
- * iscsi interface functions
+ * Only iscsi interfaces should call this. Because they all
+ * use the same netlink number and export the same sysfs tree
+ * only one can be allowed at a time.
  */
-static struct iscsi_internal *
-iscsi_if_transport_lookup(struct iscsi_transport *tt)
-{
-	struct iscsi_internal *priv;
-	unsigned long flags;
-
-	spin_lock_irqsave(&iscsi_transport_lock, flags);
-	list_for_each_entry(priv, &iscsi_transports, list) {
-		if (tt == priv->iscsi_transport) {
-			spin_unlock_irqrestore(&iscsi_transport_lock, flags);
-			return priv;
-		}
-	}
-	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
-	return NULL;
-}
-
-static int
-iscsi_broadcast_skb(struct sk_buff *skb, gfp_t gfp)
-{
-	int rc;
-
-	rc = netlink_broadcast(nls, skb, 0, 1, gfp);
-	if (rc < 0) {
-		printk(KERN_ERR "iscsi: can not broadcast skb (%d)\n", rc);
-		return rc;
-	}
-
-	return 0;
-}
-
-static int
-iscsi_unicast_skb(struct sk_buff *skb, int pid)
-{
-	int rc;
-
-	rc = netlink_unicast(nls, skb, pid, MSG_DONTWAIT);
-	if (rc < 0) {
-		printk(KERN_ERR "iscsi: can not unicast skb (%d)\n", rc);
-		return rc;
-	}
-
-	return 0;
-}
-
-int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
-		   char *data, uint32_t data_size)
-{
-	struct nlmsghdr	*nlh;
-	struct sk_buff *skb;
-	struct iscsi_uevent *ev;
-	char *pdu;
-	struct iscsi_internal *priv;
-	int len = NLMSG_SPACE(sizeof(*ev) + sizeof(struct iscsi_hdr) +
-			      data_size);
-
-	priv = iscsi_if_transport_lookup(conn->transport);
-	if (!priv)
-		return -EINVAL;
-
-	skb = alloc_skb(len, GFP_ATOMIC);
-	if (!skb) {
-		iscsi_conn_error(conn, ISCSI_ERR_CONN_FAILED);
-		dev_printk(KERN_ERR, &conn->dev, "iscsi: can not deliver "
-			   "control PDU: OOM\n");
-		return -ENOMEM;
-	}
-
-	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
-	ev = NLMSG_DATA(nlh);
-	memset(ev, 0, sizeof(*ev));
-	ev->transport_handle = iscsi_handle(conn->transport);
-	ev->type = ISCSI_KEVENT_RECV_PDU;
-	ev->r.recv_req.cid = conn->cid;
-	ev->r.recv_req.sid = iscsi_conn_get_sid(conn);
-	pdu = (char*)ev + sizeof(*ev);
-	memcpy(pdu, hdr, sizeof(struct iscsi_hdr));
-	memcpy(pdu + sizeof(struct iscsi_hdr), data, data_size);
-
-	return iscsi_unicast_skb(skb, priv->daemon_pid);
-}
-EXPORT_SYMBOL_GPL(iscsi_recv_pdu);
-
-void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
-{
-	struct nlmsghdr	*nlh;
-	struct sk_buff	*skb;
-	struct iscsi_uevent *ev;
-	struct iscsi_internal *priv;
-	int len = NLMSG_SPACE(sizeof(*ev));
-
-	priv = iscsi_if_transport_lookup(conn->transport);
-	if (!priv)
-		return;
-
-	skb = alloc_skb(len, GFP_ATOMIC);
-	if (!skb) {
-		dev_printk(KERN_ERR, &conn->dev, "iscsi: gracefully ignored "
-			  "conn error (%d)\n", error);
-		return;
-	}
-
-	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_KEVENT_CONN_ERROR;
-	ev->r.connerror.error = error;
-	ev->r.connerror.cid = conn->cid;
-	ev->r.connerror.sid = iscsi_conn_get_sid(conn);
-
-	iscsi_broadcast_skb(skb, GFP_ATOMIC);
-
-	dev_printk(KERN_INFO, &conn->dev, "iscsi: detected conn error (%d)\n",
-		   error);
-}
-EXPORT_SYMBOL_GPL(iscsi_conn_error);
-
-static int
-iscsi_if_send_reply(int pid, int seq, int type, int done, int multi,
-		      void *payload, int size)
-{
-	struct sk_buff	*skb;
-	struct nlmsghdr	*nlh;
-	int len = NLMSG_SPACE(size);
-	int flags = multi ? NLM_F_MULTI : 0;
-	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;
-	}
-
-	nlh = __nlmsg_put(skb, pid, seq, t, (len - sizeof(*nlh)), 0);
-	nlh->nlmsg_flags = flags;
-	memcpy(NLMSG_DATA(nlh), payload, size);
-	return iscsi_unicast_skb(skb, pid);
-}
-
-static int
-iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
-{
-	struct iscsi_uevent *ev = NLMSG_DATA(nlh);
-	struct iscsi_stats *stats;
-	struct sk_buff *skbstat;
-	struct iscsi_cls_conn *conn;
-	struct nlmsghdr	*nlhstat;
-	struct iscsi_uevent *evstat;
-	struct iscsi_internal *priv;
-	int len = NLMSG_SPACE(sizeof(*ev) +
-			      sizeof(struct iscsi_stats) +
-			      sizeof(struct iscsi_stats_custom) *
-			      ISCSI_STATS_CUSTOM_MAX);
-	int err = 0;
-
-	priv = iscsi_if_transport_lookup(transport);
-	if (!priv)
-		return -EINVAL;
-
-	conn = iscsi_conn_lookup(ev->u.get_stats.sid, ev->u.get_stats.cid);
-	if (!conn)
-		return -EEXIST;
-
-	do {
-		int actual_size;
-
-		skbstat = alloc_skb(len, GFP_ATOMIC);
-		if (!skbstat) {
-			dev_printk(KERN_ERR, &conn->dev, "iscsi: can not "
-				   "deliver stats: OOM\n");
-			return -ENOMEM;
-		}
-
-		nlhstat = __nlmsg_put(skbstat, priv->daemon_pid, 0, 0,
-				      (len - sizeof(*nlhstat)), 0);
-		evstat = NLMSG_DATA(nlhstat);
-		memset(evstat, 0, sizeof(*evstat));
-		evstat->transport_handle = iscsi_handle(conn->transport);
-		evstat->type = nlh->nlmsg_type;
-		evstat->u.get_stats.cid =
-			ev->u.get_stats.cid;
-		evstat->u.get_stats.sid =
-			ev->u.get_stats.sid;
-		stats = (struct iscsi_stats *)
-			((char*)evstat + sizeof(*evstat));
-		memset(stats, 0, sizeof(*stats));
-
-		transport->get_stats(conn, stats);
-		actual_size = NLMSG_SPACE(sizeof(struct iscsi_uevent) +
-					  sizeof(struct iscsi_stats) +
-					  sizeof(struct iscsi_stats_custom) *
-					  stats->custom_length);
-		actual_size -= sizeof(*nlhstat);
-		actual_size = NLMSG_LENGTH(actual_size);
-		skb_trim(skbstat, NLMSG_ALIGN(actual_size));
-		nlhstat->nlmsg_len = actual_size;
-
-		err = iscsi_unicast_skb(skbstat, priv->daemon_pid);
-	} while (err < 0 && err != -ECONNREFUSED);
-
-	return err;
-}
-
-/**
- * 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(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_KEVENT_DESTROY_SESSION;
-	ev->r.d_session.host_no = shost->host_no;
-	ev->r.d_session.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)
-		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)
-		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_if_create_session_done);
-
-static int
-iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_uevent *ev)
+int iscsi_if_load(void)
 {
-	struct iscsi_transport *transport = priv->iscsi_transport;
-	struct iscsi_cls_session *session;
-	unsigned long flags;
-	uint32_t hostno;
-
-	if ((!ev->u.c_session.cmds_max && !ev->u.c_session.queue_depth) ||
-	     !transport->create_session2)
-		session = transport->create_session(transport, &priv->t,
-						ev->u.c_session.initial_cmdsn,
-						&hostno);
-	else
-		session = transport->create_session2(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;
-}
-
-static int
-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",
-		       ev->u.c_conn.sid);
+	if (test_and_set_bit(ISCSI_IF_LOADED, &iscsi_if_loaded)) {
+		printk(KERN_ERR "iSCSI transport class interface already "
+		       "loaded.\n");
 		return -EINVAL;
 	}
-
-	conn = transport->create_conn(session, ev->u.c_conn.cid);
-	if (!conn) {
-		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);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(iscsi_if_load);
 
-static int
-iscsi_set_param(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+void iscsi_if_release(void)
 {
-	char *data = (char*)ev + sizeof(*ev);
-	struct iscsi_cls_conn *conn;
-	struct iscsi_cls_session *session;
-	int err = 0, value = 0;
-
-	session = iscsi_session_lookup(ev->u.set_param.sid);
-	conn = iscsi_conn_lookup(ev->u.set_param.sid, ev->u.set_param.cid);
-	if (!conn || !session)
-		return -EINVAL;
-
-	switch (ev->u.set_param.param) {
-	case ISCSI_PARAM_SESS_RECOVERY_TMO:
-		sscanf(data, "%d", &value);
-		if (value != 0)
-			session->recovery_tmo = value;
-		break;
-	default:
-		err = transport->set_param(conn, ev->u.set_param.param,
-					   data, ev->u.set_param.len);
-	}
-
-	return err;
-}
-
-static int
-iscsi_if_transport_ep(struct iscsi_transport *transport,
-		      struct iscsi_uevent *ev, int msg_type)
-{
-	struct sockaddr *dst_addr;
-	int rc = 0;
-
-	switch (msg_type) {
-	case ISCSI_UEVENT_TRANSPORT_EP_CONNECT:
-		if (!transport->ep_connect)
-			return -EINVAL;
-
-		dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
-		rc = transport->ep_connect(dst_addr,
-					   ev->u.ep_connect.non_blocking,
-					   &ev->r.ep_connect_ret.handle);
-		break;
-	case ISCSI_UEVENT_TRANSPORT_EP_POLL:
-		if (!transport->ep_poll)
-			return -EINVAL;
-
-		ev->r.retcode = transport->ep_poll(ev->u.ep_poll.ep_handle,
-						   ev->u.ep_poll.timeout_ms);
-		break;
-	case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT:
-		if (!transport->ep_disconnect)
-			return -EINVAL;
-
-		transport->ep_disconnect(ev->u.ep_disconnect.ep_handle);
-		break;
-	}
-	return rc;
-}
-
-static int
-iscsi_tgt_dscvr(struct iscsi_transport *transport,
-		struct iscsi_uevent *ev)
-{
-	struct sockaddr *dst_addr;
-
-	if (!transport->tgt_dscvr)
-		return -EINVAL;
-
-	dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
-	return transport->tgt_dscvr(ev->u.tgt_dscvr.type,
-				    ev->u.tgt_dscvr.host_no,
-				    ev->u.tgt_dscvr.enable, dst_addr);
-}
-
-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 (!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;
-}
-
-static int
-iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
-{
-	int err = 0;
-	struct iscsi_uevent *ev = NLMSG_DATA(nlh);
-	struct iscsi_transport *transport = NULL;
-	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)
-		return -EINVAL;
-	transport = priv->iscsi_transport;
-
-	if (!try_module_get(transport->owner))
-		return -EINVAL;
-
-	priv->daemon_pid = NETLINK_CREDS(skb)->pid;
-
-	switch (nlh->nlmsg_type) {
-	case ISCSI_UEVENT_CREATE_SESSION:
-		err = iscsi_if_create_session(priv, ev);
-		break;
-	case ISCSI_UEVENT_DESTROY_SESSION:
-		session = iscsi_session_lookup(ev->u.d_session.sid);
-		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_CREATE_CONN:
-		err = iscsi_if_create_conn(transport, ev);
-		break;
-	case ISCSI_UEVENT_DESTROY_CONN:
-		err = iscsi_if_destroy_conn(transport, ev);
-		break;
-	case ISCSI_UEVENT_BIND_CONN:
-		session = iscsi_session_lookup(ev->u.b_conn.sid);
-		conn = iscsi_conn_lookup(ev->u.b_conn.sid, ev->u.b_conn.cid);
-
-		if (session && conn)
-			ev->r.retcode =	transport->bind_conn(session, conn,
-					ev->u.b_conn.transport_eph,
-					ev->u.b_conn.is_leading);
-		else
-			err = -EINVAL;
-		break;
-	case ISCSI_UEVENT_SET_PARAM:
-		err = iscsi_set_param(transport, ev);
-		break;
-	case ISCSI_UEVENT_START_CONN:
-		conn = iscsi_conn_lookup(ev->u.start_conn.sid, ev->u.start_conn.cid);
-		if (conn)
-			ev->r.retcode = transport->start_conn(conn);
-		else
-			err = -EINVAL;
-		break;
-	case ISCSI_UEVENT_STOP_CONN:
-		conn = iscsi_conn_lookup(ev->u.stop_conn.sid, ev->u.stop_conn.cid);
-		if (conn)
-			transport->stop_conn(conn, ev->u.stop_conn.flag);
-		else
-			err = -EINVAL;
-		break;
-	case ISCSI_UEVENT_SEND_PDU:
-		conn = iscsi_conn_lookup(ev->u.send_pdu.sid, ev->u.send_pdu.cid);
-		if (conn)
-			ev->r.retcode =	transport->send_pdu(conn,
-				(struct iscsi_hdr*)((char*)ev + sizeof(*ev)),
-				(char*)ev + sizeof(*ev) + ev->u.send_pdu.hdr_size,
-				ev->u.send_pdu.data_size);
-		else
-			err = -EINVAL;
-		break;
-	case ISCSI_UEVENT_GET_STATS:
-		err = iscsi_if_get_stats(transport, nlh);
-		break;
-	case ISCSI_UEVENT_TRANSPORT_EP_CONNECT:
-	case ISCSI_UEVENT_TRANSPORT_EP_POLL:
-	case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT:
-		err = iscsi_if_transport_ep(transport, ev, nlh->nlmsg_type);
-		break;
-	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 = -EINVAL;
-		break;
-	}
-
-	module_put(transport->owner);
-	return err;
-}
-
-/*
- * 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 sock *sk, int len)
-{
-	struct sk_buff *skb;
-
-	mutex_lock(&rx_queue_mutex);
-	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
-		if (NETLINK_CREDS(skb)->uid) {
-			skb_pull(skb, skb->len);
-			goto free_skb;
-		}
-
-		while (skb->len >= NLMSG_SPACE(0)) {
-			int err;
-			uint32_t rlen;
-			struct nlmsghdr	*nlh;
-			struct iscsi_uevent *ev;
-
-			nlh = (struct nlmsghdr *)skb->data;
-			if (nlh->nlmsg_len < sizeof(*nlh) ||
-			    skb->len < nlh->nlmsg_len) {
-				break;
-			}
-
-			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);
+	clear_bit(ISCSI_IF_LOADED, &iscsi_if_loaded);
 }
+EXPORT_SYMBOL_GPL(iscsi_if_release);
 
-#define iscsi_cdev_to_conn(_cdev) \
-	iscsi_dev_to_conn(_cdev->dev)
-
-#define ISCSI_CLASS_ATTR(_prefix,_name,_mode,_show,_store)		\
-struct class_device_attribute class_device_attr_##_prefix##_##_name =	\
-	__ATTR(_name,_mode,_show,_store)
-
-/*
- * iSCSI connection attrs
- */
-#define iscsi_conn_attr_show(param)					\
-static ssize_t								\
-show_conn_param_##param(struct class_device *cdev, char *buf)		\
-{									\
-	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
-	struct iscsi_transport *t = conn->transport;			\
-	return t->get_conn_param(conn, param, buf);			\
-}
-
-#define iscsi_conn_attr(field, param)					\
-	iscsi_conn_attr_show(param)					\
-static ISCSI_CLASS_ATTR(conn, field, S_IRUGO, show_conn_param_##param,	\
-			NULL);
-
-iscsi_conn_attr(max_recv_dlength, ISCSI_PARAM_MAX_RECV_DLENGTH);
-iscsi_conn_attr(max_xmit_dlength, ISCSI_PARAM_MAX_XMIT_DLENGTH);
-iscsi_conn_attr(header_digest, ISCSI_PARAM_HDRDGST_EN);
-iscsi_conn_attr(data_digest, ISCSI_PARAM_DATADGST_EN);
-iscsi_conn_attr(ifmarker, ISCSI_PARAM_IFMARKER_EN);
-iscsi_conn_attr(ofmarker, ISCSI_PARAM_OFMARKER_EN);
-iscsi_conn_attr(persistent_port, ISCSI_PARAM_PERSISTENT_PORT);
-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)				\
-static ssize_t								\
-show_session_param_##param(struct class_device *cdev, char *buf)	\
-{									\
-	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)				\
-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);
-
-#define iscsi_priv_session_attr_show(field, format)			\
-static ssize_t								\
-show_priv_session_##field(struct class_device *cdev, char *buf)		\
-{									\
-	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
-	return sprintf(buf, format"\n", session->field);		\
-}
-
-#define iscsi_priv_session_attr(field, format)				\
-	iscsi_priv_session_attr_show(field, format)			\
-static ISCSI_CLASS_ATTR(priv_sess, field, S_IRUGO, show_priv_session_##field, \
-			NULL)
-iscsi_priv_session_attr(recovery_tmo, "%d");
-
+MODULE_DESCRIPTION("Compat wrapper for iSCSI Transport Interface");
+MODULE_LICENSE("GPL");
 /*
- * iSCSI host attrs
+ * The purpose of this module is to only allow one interface to be used
+ * at a time and to make sure that the module version is displayed in a
+ * common place. The -871 part is ignored by usersapce tools. They are
+ * concerned about the major and minor which indicates the
+ * interface versioning. For RHEL tihs will always be 2.0.
  */
-#define iscsi_host_attr_show(param)					\
-static ssize_t								\
-show_host_param_##param(struct class_device *cdev, char *buf)		\
-{									\
-	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); \
-}
-
-#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] = &class_device_attr_priv_sess_##field; \
-	count++;							\
-} while (0)
-
-#define SETUP_SESSION_RD_ATTR(field, param_flag)			\
-do {									\
-	if (tt->param_mask & param_flag) {				\
-		priv->session_attrs[count] = &class_device_attr_sess_##field; \
-		count++;						\
-	}								\
-} while (0)
-
-#define SETUP_CONN_RD_ATTR(field, param_flag)				\
-do {									\
-	if (tt->param_mask & param_flag) {				\
-		priv->conn_attrs[count] = &class_device_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] = &class_device_attr_host_##field; \
-		count++;						\
-	}								\
-} while (0)
-
-static int iscsi_session_match(struct attribute_container *cont,
-			   struct device *dev)
-{
-	struct iscsi_cls_session *session;
-	struct Scsi_Host *shost;
-	struct iscsi_internal *priv;
-
-	if (!iscsi_is_session_dev(dev))
-		return 0;
-
-	session = iscsi_dev_to_session(dev);
-	shost = iscsi_session_to_shost(session);
-	if (!shost->transportt)
-		return 0;
-
-	priv = to_iscsi_internal(shost->transportt);
-	if (priv->session_cont.ac.class != &iscsi_session_class.class)
-		return 0;
-
-	return &priv->session_cont.ac == cont;
-}
-
-static int iscsi_conn_match(struct attribute_container *cont,
-			   struct device *dev)
-{
-	struct iscsi_cls_session *session;
-	struct iscsi_cls_conn *conn;
-	struct Scsi_Host *shost;
-	struct iscsi_internal *priv;
-
-	if (!iscsi_is_conn_dev(dev))
-		return 0;
-
-	conn = iscsi_dev_to_conn(dev);
-	session = iscsi_dev_to_session(conn->dev.parent);
-	shost = iscsi_session_to_shost(session);
-
-	if (!shost->transportt)
-		return 0;
-
-	priv = to_iscsi_internal(shost->transportt);
-	if (priv->conn_cont.ac.class != &iscsi_connection_class.class)
-		return 0;
-
-	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)
-{
-	struct iscsi_internal *priv;
-	unsigned long flags;
-	int count = 0, err;
-
-	BUG_ON(!tt);
-
-	priv = iscsi_if_transport_lookup(tt);
-	if (priv)
-		return NULL;
-
-	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
-	if (!priv)
-		return NULL;
-	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);
-	err = class_device_register(&priv->cdev);
-	if (err)
-		goto free_priv;
-
-	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
-	if (err)
-		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);
-	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;
-	priv->conn_cont.ac.match = iscsi_conn_match;
-	transport_container_register(&priv->conn_cont);
-
-	SETUP_CONN_RD_ATTR(max_recv_dlength, ISCSI_MAX_RECV_DLENGTH);
-	SETUP_CONN_RD_ATTR(max_xmit_dlength, ISCSI_MAX_XMIT_DLENGTH);
-	SETUP_CONN_RD_ATTR(header_digest, ISCSI_HDRDGST_EN);
-	SETUP_CONN_RD_ATTR(data_digest, ISCSI_DATADGST_EN);
-	SETUP_CONN_RD_ATTR(ifmarker, ISCSI_IFMARKER_EN);
-	SETUP_CONN_RD_ATTR(ofmarker, ISCSI_OFMARKER_EN);
-	SETUP_CONN_RD_ATTR(address, ISCSI_CONN_ADDRESS);
-	SETUP_CONN_RD_ATTR(port, ISCSI_CONN_PORT);
-	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;
-	count = 0;
-
-	/* session parameters */
-	priv->session_cont.ac.attrs = &priv->session_attrs[0];
-	priv->session_cont.ac.class = &iscsi_session_class.class;
-	priv->session_cont.ac.match = iscsi_session_match;
-	transport_container_register(&priv->session_cont);
-
-	SETUP_SESSION_RD_ATTR(initial_r2t, ISCSI_INITIAL_R2T_EN);
-	SETUP_SESSION_RD_ATTR(max_outstanding_r2t, ISCSI_MAX_R2T);
-	SETUP_SESSION_RD_ATTR(immediate_data, ISCSI_IMM_DATA_EN);
-	SETUP_SESSION_RD_ATTR(first_burst_len, ISCSI_FIRST_BURST);
-	SETUP_SESSION_RD_ATTR(max_burst_len, ISCSI_MAX_BURST);
-	SETUP_SESSION_RD_ATTR(data_pdu_in_order, ISCSI_PDU_INORDER_EN);
-	SETUP_SESSION_RD_ATTR(data_seq_in_order, ISCSI_DATASEQ_INORDER_EN);
-	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_PRIV_SESSION_RD_ATTR(recovery_tmo);
-
-	BUG_ON(count > ISCSI_SESSION_ATTRS);
-	priv->session_attrs[count] = NULL;
-
-	spin_lock_irqsave(&iscsi_transport_lock, flags);
-	list_add(&priv->list, &iscsi_transports);
-	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
-
-	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
-	return &priv->t;
-
-unregister_cdev:
-	class_device_unregister(&priv->cdev);
-free_priv:
-	kfree(priv);
-	return NULL;
-}
-EXPORT_SYMBOL_GPL(iscsi_register_transport);
-
-int iscsi_unregister_transport(struct iscsi_transport *tt)
-{
-	struct iscsi_internal *priv;
-	unsigned long flags;
-
-	BUG_ON(!tt);
-
-	mutex_lock(&rx_queue_mutex);
-
-	priv = iscsi_if_transport_lookup(tt);
-	BUG_ON (!priv);
-
-	spin_lock_irqsave(&iscsi_transport_lock, flags);
-	list_del(&priv->list);
-	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
-
-	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);
-	mutex_unlock(&rx_queue_mutex);
-
-	return 0;
-}
-EXPORT_SYMBOL_GPL(iscsi_unregister_transport);
-
-static __init int iscsi_transport_init(void)
-{
-	int err;
-
-	printk(KERN_INFO "Loading iSCSI transport class v%s.\n",
-		ISCSI_TRANSPORT_VERSION);
-
-	atomic_set(&iscsi_session_nr, 0);
-
-	err = class_register(&iscsi_transport_class);
-	if (err)
-		return err;
-
-	err = transport_class_register(&iscsi_host_class);
-	if (err)
-		goto unregister_transport_class;
-
-	err = transport_class_register(&iscsi_connection_class);
-	if (err)
-		goto unregister_host_class;
-
-	err = transport_class_register(&iscsi_session_class);
-	if (err)
-		goto unregister_conn_class;
-
-	nls = netlink_kernel_create(NETLINK_ISCSI, 1, iscsi_if_rx,
-			THIS_MODULE);
-	if (!nls) {
-		err = -ENOBUFS;
-		goto unregister_session_class;
-	}
-
-	return 0;
-
-unregister_session_class:
-	transport_class_unregister(&iscsi_session_class);
-unregister_conn_class:
-	transport_class_unregister(&iscsi_connection_class);
-unregister_host_class:
-	transport_class_unregister(&iscsi_host_class);
-unregister_transport_class:
-	class_unregister(&iscsi_transport_class);
-	return err;
-}
-
-static void __exit iscsi_transport_exit(void)
-{
-	sock_release(nls->sk_socket);
-	transport_class_unregister(&iscsi_connection_class);
-	transport_class_unregister(&iscsi_session_class);
-	transport_class_unregister(&iscsi_host_class);
-	class_unregister(&iscsi_transport_class);
-}
-
-module_init(iscsi_transport_init);
-module_exit(iscsi_transport_exit);
-
-MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, "
-	      "Dmitry Yusupov <dmitry_yus@yahoo.com>, "
-	      "Alex Aizman <itn780@yahoo.com>");
-MODULE_DESCRIPTION("iSCSI Transport Interface");
-MODULE_LICENSE("GPL");
-MODULE_VERSION(ISCSI_TRANSPORT_VERSION);
+MODULE_VERSION("2.0-871");
diff --git a/drivers/scsi/scsi_transport_iscsi1.c b/drivers/scsi/scsi_transport_iscsi1.c
new file mode 100644
index 0000000..dd8b2c7
--- /dev/null
+++ b/drivers/scsi/scsi_transport_iscsi1.c
@@ -0,0 +1,1568 @@
+/*
+ * iSCSI transport class definitions
+ *
+ * Copyright (C) IBM Corporation, 2004
+ * Copyright (C) Mike Christie, 2004 - 2005
+ * Copyright (C) Dmitry Yusupov, 2004 - 2005
+ * Copyright (C) Alex Aizman, 2004 - 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <net/tcp.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_transport.h>
+#include <scsi/scsi_transport_iscsi.h>
+#include <scsi/iscsi_if.h>
+#include <scsi/iscsi_if_compat.h>
+
+#define ISCSI_SESSION_ATTRS 15
+#define ISCSI_CONN_ATTRS 13
+#define ISCSI_HOST_ATTRS 4
+#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 class_device cdev;
+
+	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+	struct transport_container conn_cont;
+	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+	struct transport_container session_cont;
+	struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+};
+
+static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
+
+/*
+ * list of registered transports and lock that must
+ * be held while accessing list. The iscsi_transport_lock must
+ * be acquired after the rx_queue_mutex.
+ */
+static LIST_HEAD(iscsi_transports);
+static DEFINE_SPINLOCK(iscsi_transport_lock);
+
+#define to_iscsi_internal(tmpl) \
+	container_of(tmpl, struct iscsi_internal, t)
+
+#define cdev_to_iscsi_internal(_cdev) \
+	container_of(_cdev, struct iscsi_internal, cdev)
+
+static void iscsi_transport_release(struct class_device *cdev)
+{
+	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+	kfree(priv);
+}
+
+/*
+ * iscsi_transport_class represents the iscsi_transports that are
+ * registered.
+ */
+static struct class iscsi_transport_class = {
+	.name = "iscsi_transport",
+	.release = iscsi_transport_release,
+};
+
+static ssize_t
+show_transport_handle(struct class_device *cdev, char *buf)
+{
+	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+}
+static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+
+#define show_transport_attr(name, format)				\
+static ssize_t								\
+show_transport_##name(struct class_device *cdev, char *buf)		\
+{									\
+	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+}									\
+static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+
+show_transport_attr(caps, "0x%x");
+show_transport_attr(max_lun, "%d");
+show_transport_attr(max_conn, "%d");
+show_transport_attr(max_cmd_len, "%d");
+
+static struct attribute *iscsi_transport_attrs[] = {
+	&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,
+};
+
+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)
+{
+	struct Scsi_Host *shost = dev_to_shost(dev);
+	struct iscsi_host *ihost = shost->shost_data;
+
+	memset(ihost, 0, sizeof(*ihost));
+	INIT_LIST_HEAD(&ihost->sessions);
+	mutex_init(&ihost->mutex);
+	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,
+			       NULL,
+			       NULL);
+
+static DECLARE_TRANSPORT_CLASS(iscsi_connection_class,
+			       "iscsi_connection",
+			       NULL,
+			       NULL,
+			       NULL);
+
+static struct sock *nls;
+static DEFINE_MUTEX(rx_queue_mutex);
+
+static LIST_HEAD(sesslist);
+static DEFINE_SPINLOCK(sesslock);
+static LIST_HEAD(connlist);
+static DEFINE_SPINLOCK(connlock);
+
+static uint32_t iscsi_conn_get_sid(struct iscsi_cls_conn *conn)
+{
+	struct iscsi_cls_session *sess = iscsi_dev_to_session(conn->dev.parent);
+	return sess->sid;
+}
+
+/*
+ * Returns the matching session to a given sid
+ */
+static struct iscsi_cls_session *iscsi_session_lookup(uint32_t sid)
+{
+	unsigned long flags;
+	struct iscsi_cls_session *sess;
+
+	spin_lock_irqsave(&sesslock, flags);
+	list_for_each_entry(sess, &sesslist, sess_list) {
+		if (sess->sid == sid) {
+			spin_unlock_irqrestore(&sesslock, flags);
+			return sess;
+		}
+	}
+	spin_unlock_irqrestore(&sesslock, flags);
+	return NULL;
+}
+
+/*
+ * Returns the matching connection to a given sid / cid tuple
+ */
+static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid)
+{
+	unsigned long flags;
+	struct iscsi_cls_conn *conn;
+
+	spin_lock_irqsave(&connlock, flags);
+	list_for_each_entry(conn, &connlist, conn_list) {
+		if ((conn->cid == cid) && (iscsi_conn_get_sid(conn) == sid)) {
+			spin_unlock_irqrestore(&connlock, flags);
+			return conn;
+		}
+	}
+	spin_unlock_irqrestore(&connlock, flags);
+	return NULL;
+}
+
+/*
+ * The following functions can be used by LLDs that allocate
+ * their own scsi_hosts or by software iscsi LLDs
+ */
+static void iscsi_session_release(struct device *dev)
+{
+	struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
+	struct Scsi_Host *shost;
+
+	shost = iscsi_session_to_shost(session);
+	scsi_host_put(shost);
+	kfree(session);
+}
+
+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(void *data)
+{
+	struct iscsi_cls_session *session = data;
+
+	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);
+
+	scsi_target_unblock(&session->dev);
+}
+
+void iscsi_unblock_session(struct iscsi_cls_session *session)
+{
+	if (!cancel_delayed_work(&session->recovery_work))
+		flush_scheduled_work();
+	scsi_target_unblock(&session->dev);
+}
+EXPORT_SYMBOL_GPL(iscsi_unblock_session);
+
+void iscsi_block_session(struct iscsi_cls_session *session)
+{
+	scsi_target_block(&session->dev);
+	schedule_delayed_work(&session->recovery_work,
+			     session->recovery_tmo * HZ);
+}
+EXPORT_SYMBOL_GPL(iscsi_block_session);
+
+struct iscsi_cls_session *
+iscsi_alloc_session(struct Scsi_Host *shost,
+		    struct iscsi_transport *transport)
+{
+	struct iscsi_cls_session *session;
+
+	session = kzalloc(sizeof(*session) + transport->sessiondata_size,
+			  GFP_KERNEL);
+	if (!session)
+		return NULL;
+
+	session->transport = transport;
+	session->recovery_tmo = 120;
+	INIT_WORK(&session->recovery_work, session_recovery_timedout, session);
+	INIT_LIST_HEAD(&session->host_list);
+	INIT_LIST_HEAD(&session->sess_list);
+
+	/* this is released in the dev's release function */
+	scsi_host_get(shost);
+	session->dev.parent = &shost->shost_gendev;
+	session->dev.release = iscsi_session_release;
+	device_initialize(&session->dev);
+	if (transport->sessiondata_size)
+		session->dd_data = &session[1];
+	return session;
+}
+EXPORT_SYMBOL_GPL(iscsi_alloc_session);
+
+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;
+	int err;
+
+	ihost = shost->shost_data;
+	session->sid = atomic_add_return(1, &iscsi_session_nr);
+	session->target_id = target_id;
+
+	snprintf(session->dev.bus_id, BUS_ID_SIZE, "session%u",
+		 session->sid);
+	err = device_add(&session->dev);
+	if (err) {
+		dev_printk(KERN_ERR, &session->dev, "iscsi: could not "
+			   "register session's dev\n");
+		goto release_host;
+	}
+	transport_register_device(&session->dev);
+
+	mutex_lock(&ihost->mutex);
+	list_add(&session->host_list, &ihost->sessions);
+	mutex_unlock(&ihost->mutex);
+	return 0;
+
+release_host:
+	scsi_host_put(shost);
+	return err;
+}
+EXPORT_SYMBOL_GPL(iscsi_add_session);
+
+/**
+ * iscsi_create_session - create iscsi class session
+ * @shost: scsi host
+ * @transport: iscsi transport
+ *
+ * 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,
+		     unsigned int target_id)
+{
+	struct iscsi_cls_session *session;
+
+	session = iscsi_alloc_session(shost, transport);
+	if (!session)
+		return NULL;
+
+	if (iscsi_add_session(session, target_id)) {
+		iscsi_free_session(session);
+		return NULL;
+	}
+	return session;
+}
+EXPORT_SYMBOL_GPL(iscsi_create_session);
+
+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;
+
+	if (!cancel_delayed_work(&session->recovery_work))
+		flush_scheduled_work();
+
+	mutex_lock(&ihost->mutex);
+	list_del(&session->host_list);
+	mutex_unlock(&ihost->mutex);
+
+	scsi_remove_target(&session->dev);
+
+	transport_unregister_device(&session->dev);
+	device_del(&session->dev);
+}
+EXPORT_SYMBOL_GPL(iscsi_remove_session);
+
+void iscsi_free_session(struct iscsi_cls_session *session)
+{
+	put_device(&session->dev);
+}
+
+EXPORT_SYMBOL_GPL(iscsi_free_session);
+
+/**
+ * iscsi_destroy_session - destroy iscsi session
+ * @session: iscsi_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);
+	iscsi_free_session(session);
+	return 0;
+}
+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
+ * @cid: connection id
+ *
+ * This can be called from a LLD or iscsi_transport. The connection
+ * is child of the session so cid must be unique for all connections
+ * on the session.
+ *
+ * Since we do not support MCS, cid will normally be zero. In some cases
+ * 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;
+	int err;
+
+	conn = kzalloc(sizeof(*conn) + transport->conndata_size, GFP_KERNEL);
+	if (!conn)
+		return NULL;
+
+	if (transport->conndata_size)
+		conn->dd_data = &conn[1];
+
+	INIT_LIST_HEAD(&conn->conn_list);
+	conn->transport = transport;
+	conn->cid = cid;
+
+	/* this is released in the dev's release function */
+	if (!get_device(&session->dev))
+		goto free_conn;
+
+	snprintf(conn->dev.bus_id, BUS_ID_SIZE, "connection%d:%u",
+		 session->sid, cid);
+	conn->dev.parent = &session->dev;
+	conn->dev.release = iscsi_conn_release;
+	err = device_register(&conn->dev);
+	if (err) {
+		dev_printk(KERN_ERR, &conn->dev, "iscsi: could not register "
+			   "connection's dev\n");
+		goto release_parent_ref;
+	}
+	transport_register_device(&conn->dev);
+	return conn;
+
+release_parent_ref:
+	put_device(&session->dev);
+free_conn:
+	kfree(conn);
+	return NULL;
+}
+
+EXPORT_SYMBOL_GPL(iscsi_create_conn);
+
+/**
+ * iscsi_destroy_conn - destroy iscsi class connection
+ * @session: iscsi cls session
+ *
+ * This can be called from a LLD or iscsi_transport.
+ **/
+int iscsi_destroy_conn(struct iscsi_cls_conn *conn)
+{
+	transport_unregister_device(&conn->dev);
+	device_unregister(&conn->dev);
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(iscsi_destroy_conn);
+
+/*
+ * iscsi interface functions
+ */
+static struct iscsi_internal *
+iscsi_if_transport_lookup(struct iscsi_transport *tt)
+{
+	struct iscsi_internal *priv;
+	unsigned long flags;
+
+	spin_lock_irqsave(&iscsi_transport_lock, flags);
+	list_for_each_entry(priv, &iscsi_transports, list) {
+		if (tt == priv->iscsi_transport) {
+			spin_unlock_irqrestore(&iscsi_transport_lock, flags);
+			return priv;
+		}
+	}
+	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
+	return NULL;
+}
+
+static int
+iscsi_broadcast_skb(struct sk_buff *skb, gfp_t gfp)
+{
+	int rc;
+
+	rc = netlink_broadcast(nls, skb, 0, 1, gfp);
+	if (rc < 0) {
+		printk(KERN_ERR "iscsi: can not broadcast skb (%d)\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int
+iscsi_unicast_skb(struct sk_buff *skb, int pid)
+{
+	int rc;
+
+	rc = netlink_unicast(nls, skb, pid, MSG_DONTWAIT);
+	if (rc < 0) {
+		printk(KERN_ERR "iscsi: can not unicast skb (%d)\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+		   char *data, uint32_t data_size)
+{
+	struct nlmsghdr	*nlh;
+	struct sk_buff *skb;
+	struct iscsi_uevent *ev;
+	char *pdu;
+	struct iscsi_internal *priv;
+	int len = NLMSG_SPACE(sizeof(*ev) + sizeof(struct iscsi_hdr) +
+			      data_size);
+
+	priv = iscsi_if_transport_lookup(conn->transport);
+	if (!priv)
+		return -EINVAL;
+
+	skb = alloc_skb(len, GFP_ATOMIC);
+	if (!skb) {
+		iscsi_conn_error(conn, ISCSI_ERR_CONN_FAILED);
+		dev_printk(KERN_ERR, &conn->dev, "iscsi: can not deliver "
+			   "control PDU: OOM\n");
+		return -ENOMEM;
+	}
+
+	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
+	ev = NLMSG_DATA(nlh);
+	memset(ev, 0, sizeof(*ev));
+	ev->transport_handle = iscsi_handle(conn->transport);
+	ev->type = ISCSI_KEVENT_RECV_PDU;
+	ev->r.recv_req.cid = conn->cid;
+	ev->r.recv_req.sid = iscsi_conn_get_sid(conn);
+	pdu = (char*)ev + sizeof(*ev);
+	memcpy(pdu, hdr, sizeof(struct iscsi_hdr));
+	memcpy(pdu + sizeof(struct iscsi_hdr), data, data_size);
+
+	return iscsi_unicast_skb(skb, priv->daemon_pid);
+}
+EXPORT_SYMBOL_GPL(iscsi_recv_pdu);
+
+void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
+{
+	struct nlmsghdr	*nlh;
+	struct sk_buff	*skb;
+	struct iscsi_uevent *ev;
+	struct iscsi_internal *priv;
+	int len = NLMSG_SPACE(sizeof(*ev));
+
+	priv = iscsi_if_transport_lookup(conn->transport);
+	if (!priv)
+		return;
+
+	skb = alloc_skb(len, GFP_ATOMIC);
+	if (!skb) {
+		dev_printk(KERN_ERR, &conn->dev, "iscsi: gracefully ignored "
+			  "conn error (%d)\n", error);
+		return;
+	}
+
+	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_KEVENT_CONN_ERROR;
+	ev->r.connerror.error = error;
+	ev->r.connerror.cid = conn->cid;
+	ev->r.connerror.sid = iscsi_conn_get_sid(conn);
+
+	iscsi_broadcast_skb(skb, GFP_ATOMIC);
+
+	dev_printk(KERN_INFO, &conn->dev, "iscsi: detected conn error (%d)\n",
+		   error);
+}
+EXPORT_SYMBOL_GPL(iscsi_conn_error);
+
+static int
+iscsi_if_send_reply(int pid, int seq, int type, int done, int multi,
+		      void *payload, int size)
+{
+	struct sk_buff	*skb;
+	struct nlmsghdr	*nlh;
+	int len = NLMSG_SPACE(size);
+	int flags = multi ? NLM_F_MULTI : 0;
+	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;
+	}
+
+	nlh = __nlmsg_put(skb, pid, seq, t, (len - sizeof(*nlh)), 0);
+	nlh->nlmsg_flags = flags;
+	memcpy(NLMSG_DATA(nlh), payload, size);
+	return iscsi_unicast_skb(skb, pid);
+}
+
+static int
+iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+{
+	struct iscsi_uevent *ev = NLMSG_DATA(nlh);
+	struct iscsi_stats *stats;
+	struct sk_buff *skbstat;
+	struct iscsi_cls_conn *conn;
+	struct nlmsghdr	*nlhstat;
+	struct iscsi_uevent *evstat;
+	struct iscsi_internal *priv;
+	int len = NLMSG_SPACE(sizeof(*ev) +
+			      sizeof(struct iscsi_stats) +
+			      sizeof(struct iscsi_stats_custom) *
+			      ISCSI_STATS_CUSTOM_MAX);
+	int err = 0;
+
+	priv = iscsi_if_transport_lookup(transport);
+	if (!priv)
+		return -EINVAL;
+
+	conn = iscsi_conn_lookup(ev->u.get_stats.sid, ev->u.get_stats.cid);
+	if (!conn)
+		return -EEXIST;
+
+	do {
+		int actual_size;
+
+		skbstat = alloc_skb(len, GFP_ATOMIC);
+		if (!skbstat) {
+			dev_printk(KERN_ERR, &conn->dev, "iscsi: can not "
+				   "deliver stats: OOM\n");
+			return -ENOMEM;
+		}
+
+		nlhstat = __nlmsg_put(skbstat, priv->daemon_pid, 0, 0,
+				      (len - sizeof(*nlhstat)), 0);
+		evstat = NLMSG_DATA(nlhstat);
+		memset(evstat, 0, sizeof(*evstat));
+		evstat->transport_handle = iscsi_handle(conn->transport);
+		evstat->type = nlh->nlmsg_type;
+		evstat->u.get_stats.cid =
+			ev->u.get_stats.cid;
+		evstat->u.get_stats.sid =
+			ev->u.get_stats.sid;
+		stats = (struct iscsi_stats *)
+			((char*)evstat + sizeof(*evstat));
+		memset(stats, 0, sizeof(*stats));
+
+		transport->get_stats(conn, stats);
+		actual_size = NLMSG_SPACE(sizeof(struct iscsi_uevent) +
+					  sizeof(struct iscsi_stats) +
+					  sizeof(struct iscsi_stats_custom) *
+					  stats->custom_length);
+		actual_size -= sizeof(*nlhstat);
+		actual_size = NLMSG_LENGTH(actual_size);
+		skb_trim(skbstat, NLMSG_ALIGN(actual_size));
+		nlhstat->nlmsg_len = actual_size;
+
+		err = iscsi_unicast_skb(skbstat, priv->daemon_pid);
+	} while (err < 0 && err != -ECONNREFUSED);
+
+	return err;
+}
+
+/**
+ * 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(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_KEVENT_DESTROY_SESSION;
+	ev->r.d_session.host_no = shost->host_no;
+	ev->r.d_session.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)
+		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)
+		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_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;
+
+	if ((!ev->u.c_session.cmds_max && !ev->u.c_session.queue_depth) ||
+	     !transport->create_session2)
+		session = transport->create_session(transport, &priv->t,
+						ev->u.c_session.initial_cmdsn,
+						&hostno);
+	else
+		session = transport->create_session2(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;
+}
+
+static int
+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",
+		       ev->u.c_conn.sid);
+		return -EINVAL;
+	}
+
+	conn = transport->create_conn(session, ev->u.c_conn.cid);
+	if (!conn) {
+		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);
+	return 0;
+}
+
+static int
+iscsi_set_param(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+{
+	char *data = (char*)ev + sizeof(*ev);
+	struct iscsi_cls_conn *conn;
+	struct iscsi_cls_session *session;
+	int err = 0, value = 0;
+
+	session = iscsi_session_lookup(ev->u.set_param.sid);
+	conn = iscsi_conn_lookup(ev->u.set_param.sid, ev->u.set_param.cid);
+	if (!conn || !session)
+		return -EINVAL;
+
+	switch (ev->u.set_param.param) {
+	case ISCSI_PARAM_SESS_RECOVERY_TMO:
+		sscanf(data, "%d", &value);
+		if (value != 0)
+			session->recovery_tmo = value;
+		break;
+	default:
+		err = transport->set_param(conn, ev->u.set_param.param,
+					   data, ev->u.set_param.len);
+	}
+
+	return err;
+}
+
+static int
+iscsi_if_transport_ep(struct iscsi_transport *transport,
+		      struct iscsi_uevent *ev, int msg_type)
+{
+	struct sockaddr *dst_addr;
+	int rc = 0;
+
+	switch (msg_type) {
+	case ISCSI_UEVENT_TRANSPORT_EP_CONNECT:
+		if (!transport->ep_connect)
+			return -EINVAL;
+
+		dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
+		rc = transport->ep_connect(dst_addr,
+					   ev->u.ep_connect.non_blocking,
+					   &ev->r.ep_connect_ret.handle);
+		break;
+	case ISCSI_UEVENT_TRANSPORT_EP_POLL:
+		if (!transport->ep_poll)
+			return -EINVAL;
+
+		ev->r.retcode = transport->ep_poll(ev->u.ep_poll.ep_handle,
+						   ev->u.ep_poll.timeout_ms);
+		break;
+	case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT:
+		if (!transport->ep_disconnect)
+			return -EINVAL;
+
+		transport->ep_disconnect(ev->u.ep_disconnect.ep_handle);
+		break;
+	}
+	return rc;
+}
+
+static int
+iscsi_tgt_dscvr(struct iscsi_transport *transport,
+		struct iscsi_uevent *ev)
+{
+	struct sockaddr *dst_addr;
+
+	if (!transport->tgt_dscvr)
+		return -EINVAL;
+
+	dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
+	return transport->tgt_dscvr(ev->u.tgt_dscvr.type,
+				    ev->u.tgt_dscvr.host_no,
+				    ev->u.tgt_dscvr.enable, dst_addr);
+}
+
+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 (!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;
+}
+
+static int
+iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+	int err = 0;
+	struct iscsi_uevent *ev = NLMSG_DATA(nlh);
+	struct iscsi_transport *transport = NULL;
+	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)
+		return -EINVAL;
+	transport = priv->iscsi_transport;
+
+	if (!try_module_get(transport->owner))
+		return -EINVAL;
+
+	priv->daemon_pid = NETLINK_CREDS(skb)->pid;
+
+	switch (nlh->nlmsg_type) {
+	case ISCSI_UEVENT_CREATE_SESSION:
+		err = iscsi_if_create_session(priv, ev);
+		break;
+	case ISCSI_UEVENT_DESTROY_SESSION:
+		session = iscsi_session_lookup(ev->u.d_session.sid);
+		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_CREATE_CONN:
+		err = iscsi_if_create_conn(transport, ev);
+		break;
+	case ISCSI_UEVENT_DESTROY_CONN:
+		err = iscsi_if_destroy_conn(transport, ev);
+		break;
+	case ISCSI_UEVENT_BIND_CONN:
+		session = iscsi_session_lookup(ev->u.b_conn.sid);
+		conn = iscsi_conn_lookup(ev->u.b_conn.sid, ev->u.b_conn.cid);
+
+		if (session && conn)
+			ev->r.retcode =	transport->bind_conn(session, conn,
+					ev->u.b_conn.transport_eph,
+					ev->u.b_conn.is_leading);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_SET_PARAM:
+		err = iscsi_set_param(transport, ev);
+		break;
+	case ISCSI_UEVENT_START_CONN:
+		conn = iscsi_conn_lookup(ev->u.start_conn.sid, ev->u.start_conn.cid);
+		if (conn)
+			ev->r.retcode = transport->start_conn(conn);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_STOP_CONN:
+		conn = iscsi_conn_lookup(ev->u.stop_conn.sid, ev->u.stop_conn.cid);
+		if (conn)
+			transport->stop_conn(conn, ev->u.stop_conn.flag);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_SEND_PDU:
+		conn = iscsi_conn_lookup(ev->u.send_pdu.sid, ev->u.send_pdu.cid);
+		if (conn)
+			ev->r.retcode =	transport->send_pdu(conn,
+				(struct iscsi_hdr*)((char*)ev + sizeof(*ev)),
+				(char*)ev + sizeof(*ev) + ev->u.send_pdu.hdr_size,
+				ev->u.send_pdu.data_size);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_GET_STATS:
+		err = iscsi_if_get_stats(transport, nlh);
+		break;
+	case ISCSI_UEVENT_TRANSPORT_EP_CONNECT:
+	case ISCSI_UEVENT_TRANSPORT_EP_POLL:
+	case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT:
+		err = iscsi_if_transport_ep(transport, ev, nlh->nlmsg_type);
+		break;
+	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 = -EINVAL;
+		break;
+	}
+
+	module_put(transport->owner);
+	return err;
+}
+
+/*
+ * 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 sock *sk, int len)
+{
+	struct sk_buff *skb;
+
+	mutex_lock(&rx_queue_mutex);
+	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
+		if (NETLINK_CREDS(skb)->uid) {
+			skb_pull(skb, skb->len);
+			goto free_skb;
+		}
+
+		while (skb->len >= NLMSG_SPACE(0)) {
+			int err;
+			uint32_t rlen;
+			struct nlmsghdr	*nlh;
+			struct iscsi_uevent *ev;
+
+			nlh = (struct nlmsghdr *)skb->data;
+			if (nlh->nlmsg_len < sizeof(*nlh) ||
+			    skb->len < nlh->nlmsg_len) {
+				break;
+			}
+
+			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 class_device_attribute class_device_attr_##_prefix##_##_name =	\
+	__ATTR(_name,_mode,_show,_store)
+
+/*
+ * iSCSI connection attrs
+ */
+#define iscsi_conn_attr_show(param)					\
+static ssize_t								\
+show_conn_param_##param(struct class_device *cdev, char *buf)		\
+{									\
+	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+	struct iscsi_transport *t = conn->transport;			\
+	return t->get_conn_param(conn, param, buf);			\
+}
+
+#define iscsi_conn_attr(field, param)					\
+	iscsi_conn_attr_show(param)					\
+static ISCSI_CLASS_ATTR(conn, field, S_IRUGO, show_conn_param_##param,	\
+			NULL);
+
+iscsi_conn_attr(max_recv_dlength, ISCSI_PARAM_MAX_RECV_DLENGTH);
+iscsi_conn_attr(max_xmit_dlength, ISCSI_PARAM_MAX_XMIT_DLENGTH);
+iscsi_conn_attr(header_digest, ISCSI_PARAM_HDRDGST_EN);
+iscsi_conn_attr(data_digest, ISCSI_PARAM_DATADGST_EN);
+iscsi_conn_attr(ifmarker, ISCSI_PARAM_IFMARKER_EN);
+iscsi_conn_attr(ofmarker, ISCSI_PARAM_OFMARKER_EN);
+iscsi_conn_attr(persistent_port, ISCSI_PARAM_PERSISTENT_PORT);
+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)				\
+static ssize_t								\
+show_session_param_##param(struct class_device *cdev, char *buf)	\
+{									\
+	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)				\
+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);
+
+#define iscsi_priv_session_attr_show(field, format)			\
+static ssize_t								\
+show_priv_session_##field(struct class_device *cdev, char *buf)		\
+{									\
+	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+	return sprintf(buf, format"\n", session->field);		\
+}
+
+#define iscsi_priv_session_attr(field, format)				\
+	iscsi_priv_session_attr_show(field, format)			\
+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 class_device *cdev, char *buf)		\
+{									\
+	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); \
+}
+
+#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] = &class_device_attr_priv_sess_##field; \
+	count++;							\
+} while (0)
+
+#define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+do {									\
+	if (tt->param_mask & param_flag) {				\
+		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+		count++;						\
+	}								\
+} while (0)
+
+#define SETUP_CONN_RD_ATTR(field, param_flag)				\
+do {									\
+	if (tt->param_mask & param_flag) {				\
+		priv->conn_attrs[count] = &class_device_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] = &class_device_attr_host_##field; \
+		count++;						\
+	}								\
+} while (0)
+
+static int iscsi_session_match(struct attribute_container *cont,
+			   struct device *dev)
+{
+	struct iscsi_cls_session *session;
+	struct Scsi_Host *shost;
+	struct iscsi_internal *priv;
+
+	if (!iscsi_is_session_dev(dev))
+		return 0;
+
+	session = iscsi_dev_to_session(dev);
+	shost = iscsi_session_to_shost(session);
+	if (!shost->transportt)
+		return 0;
+
+	priv = to_iscsi_internal(shost->transportt);
+	if (priv->session_cont.ac.class != &iscsi_session_class.class)
+		return 0;
+
+	return &priv->session_cont.ac == cont;
+}
+
+static int iscsi_conn_match(struct attribute_container *cont,
+			   struct device *dev)
+{
+	struct iscsi_cls_session *session;
+	struct iscsi_cls_conn *conn;
+	struct Scsi_Host *shost;
+	struct iscsi_internal *priv;
+
+	if (!iscsi_is_conn_dev(dev))
+		return 0;
+
+	conn = iscsi_dev_to_conn(dev);
+	session = iscsi_dev_to_session(conn->dev.parent);
+	shost = iscsi_session_to_shost(session);
+
+	if (!shost->transportt)
+		return 0;
+
+	priv = to_iscsi_internal(shost->transportt);
+	if (priv->conn_cont.ac.class != &iscsi_connection_class.class)
+		return 0;
+
+	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)
+{
+	struct iscsi_internal *priv;
+	unsigned long flags;
+	int count = 0, err;
+
+	BUG_ON(!tt);
+
+	priv = iscsi_if_transport_lookup(tt);
+	if (priv)
+		return NULL;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return NULL;
+	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);
+	err = class_device_register(&priv->cdev);
+	if (err)
+		goto free_priv;
+
+	err = sysfs_create_group(&priv->cdev.kobj, &iscsi_transport_group);
+	if (err)
+		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);
+	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;
+	priv->conn_cont.ac.match = iscsi_conn_match;
+	transport_container_register(&priv->conn_cont);
+
+	SETUP_CONN_RD_ATTR(max_recv_dlength, ISCSI_MAX_RECV_DLENGTH);
+	SETUP_CONN_RD_ATTR(max_xmit_dlength, ISCSI_MAX_XMIT_DLENGTH);
+	SETUP_CONN_RD_ATTR(header_digest, ISCSI_HDRDGST_EN);
+	SETUP_CONN_RD_ATTR(data_digest, ISCSI_DATADGST_EN);
+	SETUP_CONN_RD_ATTR(ifmarker, ISCSI_IFMARKER_EN);
+	SETUP_CONN_RD_ATTR(ofmarker, ISCSI_OFMARKER_EN);
+	SETUP_CONN_RD_ATTR(address, ISCSI_CONN_ADDRESS);
+	SETUP_CONN_RD_ATTR(port, ISCSI_CONN_PORT);
+	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;
+	count = 0;
+
+	/* session parameters */
+	priv->session_cont.ac.attrs = &priv->session_attrs[0];
+	priv->session_cont.ac.class = &iscsi_session_class.class;
+	priv->session_cont.ac.match = iscsi_session_match;
+	transport_container_register(&priv->session_cont);
+
+	SETUP_SESSION_RD_ATTR(initial_r2t, ISCSI_INITIAL_R2T_EN);
+	SETUP_SESSION_RD_ATTR(max_outstanding_r2t, ISCSI_MAX_R2T);
+	SETUP_SESSION_RD_ATTR(immediate_data, ISCSI_IMM_DATA_EN);
+	SETUP_SESSION_RD_ATTR(first_burst_len, ISCSI_FIRST_BURST);
+	SETUP_SESSION_RD_ATTR(max_burst_len, ISCSI_MAX_BURST);
+	SETUP_SESSION_RD_ATTR(data_pdu_in_order, ISCSI_PDU_INORDER_EN);
+	SETUP_SESSION_RD_ATTR(data_seq_in_order, ISCSI_DATASEQ_INORDER_EN);
+	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_PRIV_SESSION_RD_ATTR(recovery_tmo);
+
+	BUG_ON(count > ISCSI_SESSION_ATTRS);
+	priv->session_attrs[count] = NULL;
+
+	spin_lock_irqsave(&iscsi_transport_lock, flags);
+	list_add(&priv->list, &iscsi_transports);
+	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
+
+	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+	return &priv->t;
+
+unregister_cdev:
+	class_device_unregister(&priv->cdev);
+free_priv:
+	kfree(priv);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(iscsi_register_transport);
+
+int iscsi_unregister_transport(struct iscsi_transport *tt)
+{
+	struct iscsi_internal *priv;
+	unsigned long flags;
+
+	BUG_ON(!tt);
+
+	mutex_lock(&rx_queue_mutex);
+
+	priv = iscsi_if_transport_lookup(tt);
+	BUG_ON (!priv);
+
+	spin_lock_irqsave(&iscsi_transport_lock, flags);
+	list_del(&priv->list);
+	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
+
+	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);
+	mutex_unlock(&rx_queue_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi_unregister_transport);
+
+static __init int iscsi_transport_init(void)
+{
+	int err;
+
+	printk(KERN_INFO "Loading iSCSI transport class v%s.\n",
+		ISCSI_TRANSPORT_VERSION);
+
+	err = iscsi_if_load();
+	if (err)
+		return err;
+
+	atomic_set(&iscsi_session_nr, 0);
+
+	err = class_register(&iscsi_transport_class);
+	if (err)
+		goto release_if;
+
+	err = transport_class_register(&iscsi_host_class);
+	if (err)
+		goto unregister_transport_class;
+
+	err = transport_class_register(&iscsi_connection_class);
+	if (err)
+		goto unregister_host_class;
+
+	err = transport_class_register(&iscsi_session_class);
+	if (err)
+		goto unregister_conn_class;
+
+	nls = netlink_kernel_create(NETLINK_ISCSI, 1, iscsi_if_rx,
+			THIS_MODULE);
+	if (!nls) {
+		err = -ENOBUFS;
+		goto unregister_session_class;
+	}
+
+	return 0;
+
+unregister_session_class:
+	transport_class_unregister(&iscsi_session_class);
+unregister_conn_class:
+	transport_class_unregister(&iscsi_connection_class);
+unregister_host_class:
+	transport_class_unregister(&iscsi_host_class);
+unregister_transport_class:
+	class_unregister(&iscsi_transport_class);
+release_if:
+	iscsi_if_release();
+	return err;
+}
+
+static void __exit iscsi_transport_exit(void)
+{
+	sock_release(nls->sk_socket);
+	transport_class_unregister(&iscsi_connection_class);
+	transport_class_unregister(&iscsi_session_class);
+	transport_class_unregister(&iscsi_host_class);
+	class_unregister(&iscsi_transport_class);
+	iscsi_if_release();
+}
+
+module_init(iscsi_transport_init);
+module_exit(iscsi_transport_exit);
+
+MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, "
+	      "Dmitry Yusupov <dmitry_yus@yahoo.com>, "
+	      "Alex Aizman <itn780@yahoo.com>");
+MODULE_DESCRIPTION("iSCSI Transport Interface");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(ISCSI_TRANSPORT_VERSION);
diff --git a/drivers/scsi/scsi_transport_iscsi2.c b/drivers/scsi/scsi_transport_iscsi2.c
new file mode 100644
index 0000000..33f5da4
--- /dev/null
+++ b/drivers/scsi/scsi_transport_iscsi2.c
@@ -0,0 +1,1989 @@
+/*
+ * iSCSI transport class definitions
+ *
+ * Copyright (C) IBM Corporation, 2004
+ * Copyright (C) Mike Christie, 2004 - 2005
+ * Copyright (C) Dmitry Yusupov, 2004 - 2005
+ * Copyright (C) Alex Aizman, 2004 - 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/mutex.h>
+#include <net/tcp.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_transport.h>
+#include <scsi/iscsi_compat2.h>
+#include <scsi/scsi_transport_iscsi2.h>
+#include <scsi/iscsi_if2.h>
+#include <scsi/iscsi_if_compat.h>
+
+#define ISCSI_SESSION_ATTRS 21
+#define ISCSI_CONN_ATTRS 13
+#define ISCSI_HOST_ATTRS 4
+
+#define ISCSI_TRANSPORT_VERSION "2.0-871"
+
+struct iscsi_internal {
+	int daemon_pid;
+	struct scsi_transport_template t;
+	struct iscsi_transport *iscsi_transport;
+	struct list_head list;
+	struct class_device cdev;
+
+	struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+	struct transport_container conn_cont;
+	struct class_device_attribute *conn_attrs[ISCSI_CONN_ATTRS + 1];
+	struct transport_container session_cont;
+	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
+ * be held while accessing list. The iscsi_transport_lock must
+ * be acquired after the rx_queue_mutex.
+ */
+static LIST_HEAD(iscsi_transports);
+static DEFINE_SPINLOCK(iscsi_transport_lock);
+
+#define to_iscsi_internal(tmpl) \
+	container_of(tmpl, struct iscsi_internal, t)
+
+#define cdev_to_iscsi_internal(_cdev) \
+	container_of(_cdev, struct iscsi_internal, cdev)
+
+static void iscsi_transport_release(struct class_device *cdev)
+{
+	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+	kfree(priv);
+}
+
+/*
+ * iscsi_transport_class represents the iscsi_transports that are
+ * registered.
+ */
+static struct class iscsi_transport_class = {
+	.name = "iscsi_transport",
+	.release = iscsi_transport_release,
+};
+
+static ssize_t
+show_transport_handle(struct class_device *cdev, char *buf)
+{
+	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);
+	return sprintf(buf, "%llu\n", (unsigned long long)iscsi_handle(priv->iscsi_transport));
+}
+static CLASS_DEVICE_ATTR(handle, S_IRUGO, show_transport_handle, NULL);
+
+#define show_transport_attr(name, format)				\
+static ssize_t								\
+show_transport_##name(struct class_device *cdev, char *buf)		\
+{									\
+	struct iscsi_internal *priv = cdev_to_iscsi_internal(cdev);	\
+	return sprintf(buf, format"\n", priv->iscsi_transport->name);	\
+}									\
+static CLASS_DEVICE_ATTR(name, S_IRUGO, show_transport_##name, NULL);
+
+show_transport_attr(caps, "0x%x");
+
+static struct attribute *iscsi_transport_attrs[] = {
+	&class_device_attr_handle.attr,
+	&class_device_attr_caps.attr,
+	NULL,
+};
+
+static struct attribute_group iscsi_transport_group = {
+	.attrs = iscsi_transport_attrs,
+};
+
+/*
+ * iSCSI endpoint attrs
+ */
+#define iscsi_dev_to_endpoint(_dev) \
+	container_of(_dev, struct iscsi_endpoint, dev)
+
+#define ISCSI_ATTR(_prefix,_name,_mode,_show,_store)	\
+struct device_attribute dev_attr_##_prefix##_##_name =	\
+        __ATTR(_name,_mode,_show,_store)
+
+static void iscsi_endpoint_release(struct class_device *cdev)
+{
+	struct iscsi_endpoint *ep = iscsi_dev_to_endpoint(cdev);
+	kfree(ep);
+}
+
+static struct class iscsi_endpoint_class = {
+	.name = "iscsi_endpoint",
+	.release = iscsi_endpoint_release,
+};
+
+static ssize_t
+show_ep_handle(struct class_device *cdev, char *buf)
+{
+	struct iscsi_endpoint *ep = iscsi_dev_to_endpoint(cdev);
+	return sprintf(buf, "%llu\n", (unsigned long long) ep->id);
+}
+static struct class_device_attribute class_device_attr_ep_handle =
+	__ATTR(handle, S_IRUGO, show_ep_handle, NULL);
+
+static struct attribute *iscsi_endpoint_attrs[] = {
+	&class_device_attr_ep_handle.attr,
+	NULL,
+};
+
+static struct attribute_group iscsi_endpoint_group = {
+	.attrs = iscsi_endpoint_attrs,
+};
+
+#define ISCSI_MAX_EPID -1
+
+struct iscsi_endpoint *iscsi2_lookup_endpoint(u64 handle)
+{
+	struct iscsi_endpoint *ep = NULL;
+	struct class_device *cdev;
+
+	down_read(&iscsi_endpoint_class.subsys.rwsem);
+	list_for_each_entry(cdev, &iscsi_endpoint_class.children, node) {
+		ep = iscsi_dev_to_endpoint(cdev);
+		if (ep->id == handle)
+			break;
+		ep = NULL;
+	}
+	up_read(&iscsi_endpoint_class.subsys.rwsem);
+	return ep;
+}
+EXPORT_SYMBOL_GPL(iscsi2_lookup_endpoint);
+
+struct iscsi_endpoint *
+iscsi2_create_endpoint(int dd_size)
+{
+	struct iscsi_endpoint *ep;
+	uint64_t id;
+	int err;
+
+	for (id = 1; id < ISCSI_MAX_EPID; id++) {
+		if (!iscsi2_lookup_endpoint(id))
+			break;
+	}
+	if (id == ISCSI_MAX_EPID) {
+		printk(KERN_ERR "Too many connections. Max supported %u\n",
+		       ISCSI_MAX_EPID - 1);
+		return NULL;
+	}
+
+	ep = kzalloc(sizeof(*ep) + dd_size, GFP_KERNEL);
+	if (!ep)
+		return NULL;
+
+	ep->id = id;
+	ep->dev.class = &iscsi_endpoint_class;
+	snprintf(ep->dev.class_id, BUS_ID_SIZE, "ep-%llu",
+		 (unsigned long long) id);
+	err = class_device_register(&ep->dev);
+        if (err)
+                goto free_ep;
+
+	err = sysfs_create_group(&ep->dev.kobj, &iscsi_endpoint_group);
+	if (err)
+		goto unregister_dev;
+
+	if (dd_size)
+		ep->dd_data = &ep[1];
+	return ep;
+
+unregister_dev:
+	class_device_unregister(&ep->dev);
+	return NULL;
+
+free_ep:
+	kfree(ep);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(iscsi2_create_endpoint);
+
+void iscsi2_destroy_endpoint(struct iscsi_endpoint *ep)
+{
+	sysfs_remove_group(&ep->dev.kobj, &iscsi_endpoint_group);
+	class_device_unregister(&ep->dev);
+}
+EXPORT_SYMBOL_GPL(iscsi2_destroy_endpoint);
+
+static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
+			    struct class_device *cdev)
+{
+	struct Scsi_Host *shost = dev_to_shost(dev);
+	struct iscsi_cls_host *ihost = shost->shost_data;
+
+	memset(ihost, 0, sizeof(*ihost));
+	atomic_set(&ihost->nr_scans, 0);
+	mutex_init(&ihost->mutex);
+	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,
+			       NULL,
+			       NULL);
+
+static DECLARE_TRANSPORT_CLASS(iscsi_connection_class,
+			       "iscsi_connection",
+			       NULL,
+			       NULL,
+			       NULL);
+
+static struct sock *nls;
+static DEFINE_MUTEX(rx_queue_mutex);
+
+static LIST_HEAD(sesslist);
+static DEFINE_SPINLOCK(sesslock);
+static LIST_HEAD(connlist);
+static DEFINE_SPINLOCK(connlock);
+
+static uint32_t iscsi_conn_get_sid(struct iscsi_cls_conn *conn)
+{
+	struct iscsi_cls_session *sess = iscsi_dev_to_session(conn->dev.parent);
+	return sess->sid;
+}
+
+/*
+ * Returns the matching session to a given sid
+ */
+static struct iscsi_cls_session *iscsi_session_lookup(uint32_t sid)
+{
+	unsigned long flags;
+	struct iscsi_cls_session *sess;
+
+	spin_lock_irqsave(&sesslock, flags);
+	list_for_each_entry(sess, &sesslist, sess_list) {
+		if (sess->sid == sid) {
+			spin_unlock_irqrestore(&sesslock, flags);
+			return sess;
+		}
+	}
+	spin_unlock_irqrestore(&sesslock, flags);
+	return NULL;
+}
+
+/*
+ * Returns the matching connection to a given sid / cid tuple
+ */
+static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid)
+{
+	unsigned long flags;
+	struct iscsi_cls_conn *conn;
+
+	spin_lock_irqsave(&connlock, flags);
+	list_for_each_entry(conn, &connlist, conn_list) {
+		if ((conn->cid == cid) && (iscsi_conn_get_sid(conn) == sid)) {
+			spin_unlock_irqrestore(&connlock, flags);
+			return conn;
+		}
+	}
+	spin_unlock_irqrestore(&connlock, flags);
+	return NULL;
+}
+
+/*
+ * 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 iscsi2_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_TRANSPORT_DISRUPTED << 16;
+		break;
+	case ISCSI_SESSION_FREE:
+		err = DID_TRANSPORT_FAILFAST << 16;
+		break;
+	default:
+		err = DID_NO_CONNECT << 16;
+		break;
+	}
+	spin_unlock_irqrestore(&session->lock, flags);
+	return err;
+}
+EXPORT_SYMBOL_GPL(iscsi2_session_chkready);
+
+static void iscsi_session_release(struct device *dev)
+{
+	struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
+	struct Scsi_Host *shost;
+
+	shost = iscsi_session_to_shost(session);
+	scsi_host_put(shost);
+	kfree(session);
+}
+
+static int iscsi_is_session_dev(const struct device *dev)
+{
+	return dev->release == iscsi_session_release;
+}
+
+static int iscsi_iter_session_fn(struct device *dev, void *data)
+{
+	void (* fn) (struct iscsi_cls_session *) = data;
+
+	if (!iscsi_is_session_dev(dev))
+		return 0;
+	fn(iscsi_dev_to_session(dev));
+	return 0;
+}
+
+void iscsi2_host_for_each_session(struct Scsi_Host *shost,
+				  void (*fn)(struct iscsi_cls_session *))
+{
+	device_for_each_child(&shost->shost_gendev, fn,
+			      iscsi_iter_session_fn);
+}
+EXPORT_SYMBOL_GPL(iscsi2_host_for_each_session);
+
+struct iscsi_scan_data {
+	unsigned int channel;
+	unsigned int id;
+	unsigned int lun;
+};
+
+static int iscsi_user_scan_session(struct device *dev, void *data)
+{
+	struct iscsi_scan_data *scan_data = data;
+	struct iscsi_cls_session *session;
+	struct Scsi_Host *shost;
+	struct iscsi_cls_host *ihost;
+	unsigned long flags;
+	unsigned int id;
+
+	if (!iscsi_is_session_dev(dev))
+		return 0;
+
+	session = iscsi_dev_to_session(dev);
+	shost = iscsi_session_to_shost(session);
+	ihost = shost->shost_data;
+
+	mutex_lock(&ihost->mutex);
+	spin_lock_irqsave(&session->lock, flags);
+	if (session->state != ISCSI_SESSION_LOGGED_IN) {
+		spin_unlock_irqrestore(&session->lock, flags);
+		mutex_unlock(&ihost->mutex);
+		return 0;
+	}
+	id = session->target_id;
+	spin_unlock_irqrestore(&session->lock, flags);
+
+	if (id != ISCSI_MAX_TARGET) {
+		if ((scan_data->channel == SCAN_WILD_CARD ||
+		     scan_data->channel == 0) &&
+		    (scan_data->id == SCAN_WILD_CARD ||
+		     scan_data->id == id))
+			scsi_scan_target(&session->dev, 0, id,
+					 scan_data->lun, 1);
+	}
+	mutex_unlock(&ihost->mutex);
+	return 0;
+}
+
+static int iscsi_user_scan(struct Scsi_Host *shost, uint channel,
+			   uint id, uint lun)
+{
+	struct iscsi_scan_data scan_data;
+
+	scan_data.channel = channel;
+	scan_data.id = id;
+	scan_data.lun = lun;
+
+	return device_for_each_child(&shost->shost_gendev, &scan_data,
+				     iscsi_user_scan_session);
+}
+
+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_cls_host *ihost = shost->shost_data;
+	struct iscsi_scan_data scan_data;
+
+	scan_data.channel = 0;
+	scan_data.id = SCAN_WILD_CARD;
+	scan_data.lun = SCAN_WILD_CARD;
+
+	iscsi_user_scan_session(&session->dev, &scan_data);
+	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);
+	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);
+
+	if (session->transport->session_recovery_timedout)
+		session->transport->session_recovery_timedout(session);
+
+	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);
+	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);
+}
+
+/**
+ * iscsi_unblock_session - set a session as logged in and start IO.
+ * @session: iscsi session
+ *
+ * Mark a session as ready to accept IO.
+ */
+void iscsi2_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);
+}
+EXPORT_SYMBOL_GPL(iscsi2_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 iscsi2_block_session(struct iscsi_cls_session *session)
+{
+	queue_work(iscsi_eh_timer_workq, &session->block_work);
+}
+EXPORT_SYMBOL_GPL(iscsi2_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_cls_host *ihost = shost->shost_data;
+	unsigned long flags;
+
+	/* Prevent new scans and make sure scanning is not in progress */
+	mutex_lock(&ihost->mutex);
+	spin_lock_irqsave(&session->lock, flags);
+	if (session->target_id == ISCSI_MAX_TARGET) {
+		spin_unlock_irqrestore(&session->lock, flags);
+		mutex_unlock(&ihost->mutex);
+		return;
+	}
+	session->target_id = ISCSI_MAX_TARGET;
+	spin_unlock_irqrestore(&session->lock, flags);
+	mutex_unlock(&ihost->mutex);
+
+	scsi_remove_target(&session->dev);
+	iscsi2_session_event(session, ISCSI_KEVENT_UNBIND_SESSION);
+}
+
+struct iscsi_cls_session *
+iscsi2_alloc_session(struct Scsi_Host *shost, struct iscsi_transport *transport,
+		    int dd_size)
+{
+	struct iscsi_cls_session *session;
+
+	session = kzalloc(sizeof(*session) + dd_size,
+			  GFP_KERNEL);
+	if (!session)
+		return NULL;
+
+	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->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);
+	session->dev.parent = &shost->shost_gendev;
+	session->dev.release = iscsi_session_release;
+	device_initialize(&session->dev);
+	if (dd_size)
+		session->dd_data = &session[1];
+	return session;
+}
+EXPORT_SYMBOL_GPL(iscsi2_alloc_session);
+
+static int iscsi_get_next_target_id(struct device *dev, void *data)
+{
+	struct iscsi_cls_session *session;
+	unsigned long flags;
+	int err = 0;
+
+	if (!iscsi_is_session_dev(dev))
+		return 0;
+
+	session = iscsi_dev_to_session(dev);
+	spin_lock_irqsave(&session->lock, flags);
+	if (*((unsigned int *) data) == session->target_id)
+		err = -EEXIST;
+	spin_unlock_irqrestore(&session->lock, flags);
+	return err;
+}
+
+int iscsi2_add_session(struct iscsi_cls_session *session, unsigned int target_id)
+{
+	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+	struct iscsi_cls_host *ihost;
+	unsigned long flags;
+	unsigned int id = target_id;
+	int err;
+
+	ihost = shost->shost_data;
+	session->sid = atomic_add_return(1, &iscsi_session_nr);
+
+	if (id == ISCSI_MAX_TARGET) {
+		for (id = 0; id < ISCSI_MAX_TARGET; id++) {
+			err = device_for_each_child(&shost->shost_gendev, &id,
+						    iscsi_get_next_target_id);
+			if (!err)
+				break;
+		}
+
+		if (id == ISCSI_MAX_TARGET) {
+			iscsi_cls_session_printk(KERN_ERR, session,
+						 "Too many iscsi targets. Max "
+						 "number of targets is %d.\n",
+						 ISCSI_MAX_TARGET - 1);
+			goto release_host;
+		}
+	}
+	session->target_id = id;
+
+	snprintf(session->dev.bus_id, BUS_ID_SIZE, "session%u",
+		 session->sid);
+	err = device_add(&session->dev);
+	if (err) {
+		iscsi_cls_session_printk(KERN_ERR, session,
+					 "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);
+
+	iscsi2_session_event(session, ISCSI_KEVENT_CREATE_SESSION);
+	return 0;
+
+release_host:
+	scsi_host_put(shost);
+	return err;
+}
+EXPORT_SYMBOL_GPL(iscsi2_add_session);
+
+/**
+ * iscsi_create_session - create iscsi class session
+ * @shost: scsi host
+ * @transport: iscsi transport
+ * @dd_size: private driver data size
+ * @target_id: which target
+ *
+ * This can be called from a LLD or iscsi_transport.
+ */
+struct iscsi_cls_session *
+iscsi2_create_session(struct Scsi_Host *shost, struct iscsi_transport *transport,
+		     int dd_size, unsigned int target_id)
+{
+	struct iscsi_cls_session *session;
+
+	session = iscsi2_alloc_session(shost, transport, dd_size);
+	if (!session)
+		return NULL;
+
+	if (iscsi2_add_session(session, target_id)) {
+		iscsi2_free_session(session);
+		return NULL;
+	}
+	return session;
+}
+EXPORT_SYMBOL_GPL(iscsi2_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 iscsi2_destroy_conn(iscsi_dev_to_conn(dev));
+}
+
+void iscsi2_remove_session(struct iscsi_cls_session *session)
+{
+	struct Scsi_Host *shost = iscsi_session_to_shost(session);
+	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);
+
+	scsi_target_unblock(&session->dev);
+	/* flush running scans then delete devices */
+	scsi_flush_work(shost);
+	__iscsi_unbind_session(&session->unbind_work);
+
+	/* 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);
+
+	transport_unregister_device(&session->dev);
+	device_del(&session->dev);
+}
+EXPORT_SYMBOL_GPL(iscsi2_remove_session);
+
+void iscsi2_free_session(struct iscsi_cls_session *session)
+{
+	iscsi2_session_event(session, ISCSI_KEVENT_DESTROY_SESSION);
+	put_device(&session->dev);
+}
+EXPORT_SYMBOL_GPL(iscsi2_free_session);
+
+/**
+ * iscsi_destroy_session - destroy iscsi session
+ * @session: iscsi_session
+ *
+ * Can be called by a LLD or iscsi_transport. There must not be
+ * any running connections.
+ */
+int iscsi2_destroy_session(struct iscsi_cls_session *session)
+{
+	iscsi2_remove_session(session);
+	iscsi2_free_session(session);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi2_destroy_session);
+
+/**
+ * iscsi_create_conn - create iscsi class connection
+ * @session: iscsi cls session
+ * @dd_size: private driver data size
+ * @cid: connection id
+ *
+ * This can be called from a LLD or iscsi_transport. The connection
+ * is child of the session so cid must be unique for all connections
+ * on the session.
+ *
+ * Since we do not support MCS, cid will normally be zero. In some cases
+ * 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 *
+iscsi2_create_conn(struct iscsi_cls_session *session, int dd_size, uint32_t cid)
+{
+	struct iscsi_transport *transport = session->transport;
+	struct iscsi_cls_conn *conn;
+	unsigned long flags;
+	int err;
+
+	conn = kzalloc(sizeof(*conn) + dd_size, GFP_KERNEL);
+	if (!conn)
+		return NULL;
+	if (dd_size)
+		conn->dd_data = &conn[1];
+
+	INIT_LIST_HEAD(&conn->conn_list);
+	conn->transport = transport;
+	conn->cid = cid;
+
+	/* this is released in the dev's release function */
+	if (!get_device(&session->dev))
+		goto free_conn;
+
+	snprintf(conn->dev.bus_id, BUS_ID_SIZE, "connection%d:%u",
+		 session->sid, cid);
+	conn->dev.parent = &session->dev;
+	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");
+		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:
+	put_device(&session->dev);
+free_conn:
+	kfree(conn);
+	return NULL;
+}
+
+EXPORT_SYMBOL_GPL(iscsi2_create_conn);
+
+/**
+ * iscsi_destroy_conn - destroy iscsi class connection
+ * @conn: iscsi cls session
+ *
+ * This can be called from a LLD or iscsi_transport.
+ */
+int iscsi2_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(iscsi2_destroy_conn);
+
+/*
+ * iscsi interface functions
+ */
+static struct iscsi_internal *
+iscsi_if_transport_lookup(struct iscsi_transport *tt)
+{
+	struct iscsi_internal *priv;
+	unsigned long flags;
+
+	spin_lock_irqsave(&iscsi_transport_lock, flags);
+	list_for_each_entry(priv, &iscsi_transports, list) {
+		if (tt == priv->iscsi_transport) {
+			spin_unlock_irqrestore(&iscsi_transport_lock, flags);
+			return priv;
+		}
+	}
+	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
+	return NULL;
+}
+
+static int
+iscsi_broadcast_skb(struct sk_buff *skb, gfp_t gfp)
+{
+	return netlink_broadcast(nls, skb, 0, 1, gfp);
+}
+
+static int
+iscsi_unicast_skb(struct sk_buff *skb, int pid)
+{
+	int rc;
+
+	rc = netlink_unicast(nls, skb, pid, MSG_DONTWAIT);
+	if (rc < 0) {
+		printk(KERN_ERR "iscsi: can not unicast skb (%d)\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+int iscsi2_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+		    char *data, uint32_t data_size)
+{
+	struct nlmsghdr	*nlh;
+	struct sk_buff *skb;
+	struct iscsi_uevent *ev;
+	char *pdu;
+	struct iscsi_internal *priv;
+	int len = NLMSG_SPACE(sizeof(*ev) + sizeof(struct iscsi_hdr) +
+			      data_size);
+
+	priv = iscsi_if_transport_lookup(conn->transport);
+	if (!priv)
+		return -EINVAL;
+
+	skb = alloc_skb(len, GFP_ATOMIC);
+	if (!skb) {
+		iscsi2_conn_error_event(conn, ISCSI_ERR_CONN_FAILED);
+		iscsi_cls_conn_printk(KERN_ERR, conn, "can not deliver "
+				      "control PDU: OOM\n");
+		return -ENOMEM;
+	}
+
+	nlh = __nlmsg_put(skb, priv->daemon_pid, 0, 0, (len - sizeof(*nlh)), 0);
+	ev = NLMSG_DATA(nlh);
+	memset(ev, 0, sizeof(*ev));
+	ev->transport_handle = iscsi_handle(conn->transport);
+	ev->type = ISCSI_KEVENT_RECV_PDU;
+	ev->r.recv_req.cid = conn->cid;
+	ev->r.recv_req.sid = iscsi_conn_get_sid(conn);
+	pdu = (char*)ev + sizeof(*ev);
+	memcpy(pdu, hdr, sizeof(struct iscsi_hdr));
+	memcpy(pdu + sizeof(struct iscsi_hdr), data, data_size);
+
+	return iscsi_unicast_skb(skb, priv->daemon_pid);
+}
+EXPORT_SYMBOL_GPL(iscsi2_recv_pdu);
+
+void iscsi2_conn_error_event(struct iscsi_cls_conn *conn, enum iscsi_err error)
+{
+	struct nlmsghdr	*nlh;
+	struct sk_buff	*skb;
+	struct iscsi_uevent *ev;
+	struct iscsi_internal *priv;
+	int len = NLMSG_SPACE(sizeof(*ev));
+
+	priv = iscsi_if_transport_lookup(conn->transport);
+	if (!priv)
+		return;
+
+	skb = alloc_skb(len, GFP_ATOMIC);
+	if (!skb) {
+		iscsi_cls_conn_printk(KERN_ERR, conn, "gracefully ignored "
+				      "conn error (%d)\n", error);
+		return;
+	}
+
+	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_KEVENT_CONN_ERROR;
+	ev->r.connerror.error = error;
+	ev->r.connerror.cid = conn->cid;
+	ev->r.connerror.sid = iscsi_conn_get_sid(conn);
+
+	iscsi_broadcast_skb(skb, GFP_ATOMIC);
+
+	iscsi_cls_conn_printk(KERN_INFO, conn, "detected conn error (%d)\n",
+			      error);
+}
+EXPORT_SYMBOL_GPL(iscsi2_conn_error_event);
+
+static int
+iscsi_if_send_reply(int pid, int seq, int type, int done, int multi,
+		      void *payload, int size)
+{
+	struct sk_buff	*skb;
+	struct nlmsghdr	*nlh;
+	int len = NLMSG_SPACE(size);
+	int flags = multi ? NLM_F_MULTI : 0;
+	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;
+	}
+
+	nlh = __nlmsg_put(skb, pid, seq, t, (len - sizeof(*nlh)), 0);
+	nlh->nlmsg_flags = flags;
+	memcpy(NLMSG_DATA(nlh), payload, size);
+	return iscsi_unicast_skb(skb, pid);
+}
+
+static int
+iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+{
+	struct iscsi_uevent *ev = NLMSG_DATA(nlh);
+	struct iscsi_stats *stats;
+	struct sk_buff *skbstat;
+	struct iscsi_cls_conn *conn;
+	struct nlmsghdr	*nlhstat;
+	struct iscsi_uevent *evstat;
+	struct iscsi_internal *priv;
+	int len = NLMSG_SPACE(sizeof(*ev) +
+			      sizeof(struct iscsi_stats) +
+			      sizeof(struct iscsi_stats_custom) *
+			      ISCSI_STATS_CUSTOM_MAX);
+	int err = 0;
+
+	priv = iscsi_if_transport_lookup(transport);
+	if (!priv)
+		return -EINVAL;
+
+	conn = iscsi_conn_lookup(ev->u.get_stats.sid, ev->u.get_stats.cid);
+	if (!conn)
+		return -EEXIST;
+
+	do {
+		int actual_size;
+
+		skbstat = alloc_skb(len, GFP_ATOMIC);
+		if (!skbstat) {
+			iscsi_cls_conn_printk(KERN_ERR, conn, "can not "
+					      "deliver stats: OOM\n");
+			return -ENOMEM;
+		}
+
+		nlhstat = __nlmsg_put(skbstat, priv->daemon_pid, 0, 0,
+				      (len - sizeof(*nlhstat)), 0);
+		evstat = NLMSG_DATA(nlhstat);
+		memset(evstat, 0, sizeof(*evstat));
+		evstat->transport_handle = iscsi_handle(conn->transport);
+		evstat->type = nlh->nlmsg_type;
+		evstat->u.get_stats.cid =
+			ev->u.get_stats.cid;
+		evstat->u.get_stats.sid =
+			ev->u.get_stats.sid;
+		stats = (struct iscsi_stats *)
+			((char*)evstat + sizeof(*evstat));
+		memset(stats, 0, sizeof(*stats));
+
+		transport->get_stats(conn, stats);
+		actual_size = NLMSG_SPACE(sizeof(struct iscsi_uevent) +
+					  sizeof(struct iscsi_stats) +
+					  sizeof(struct iscsi_stats_custom) *
+					  stats->custom_length);
+		actual_size -= sizeof(*nlhstat);
+		actual_size = NLMSG_LENGTH(actual_size);
+		skb_trim(skbstat, NLMSG_ALIGN(actual_size));
+		nlhstat->nlmsg_len = actual_size;
+
+		err = iscsi_unicast_skb(skbstat, priv->daemon_pid);
+	} while (err < 0 && err != -ECONNREFUSED);
+
+	return err;
+}
+
+/**
+ * iscsi_session_event - send session destr. completion event
+ * @session: iscsi class session
+ * @event: type of event
+ */
+int iscsi2_session_event(struct iscsi_cls_session *session,
+			 enum iscsi_uevent_e event)
+{
+	struct iscsi_internal *priv;
+	struct Scsi_Host *shost;
+	struct iscsi_uevent *ev;
+	struct sk_buff  *skb;
+	struct nlmsghdr *nlh;
+	int rc, len = NLMSG_SPACE(sizeof(*ev));
+
+	priv = iscsi_if_transport_lookup(session->transport);
+	if (!priv)
+		return -EINVAL;
+	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);
+		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->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);
+		return -EINVAL;
+	}
+
+	/*
+	 * 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);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(iscsi2_session_event);
+
+static int
+iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_endpoint *ep,
+			struct iscsi_uevent *ev, uint32_t initial_cmdsn,
+			uint16_t cmds_max, uint16_t queue_depth)
+{
+	struct iscsi_transport *transport = priv->iscsi_transport;
+	struct iscsi_cls_session *session;
+	struct Scsi_Host *shost;
+
+	session = transport->create_session(ep, cmds_max, queue_depth,
+					    initial_cmdsn);
+	if (!session)
+		return -ENOMEM;
+
+	shost = iscsi_session_to_shost(session);
+	ev->r.c_session_ret.host_no = shost->host_no;
+	ev->r.c_session_ret.sid = session->sid;
+	return 0;
+}
+
+static int
+iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+{
+	struct iscsi_cls_conn *conn;
+	struct iscsi_cls_session *session;
+
+	session = iscsi_session_lookup(ev->u.c_conn.sid);
+	if (!session) {
+		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.");
+		return -ENOMEM;
+	}
+
+	ev->r.c_conn_ret.sid = session->sid;
+	ev->r.c_conn_ret.cid = conn->cid;
+	return 0;
+}
+
+static int
+iscsi_if_destroy_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+{
+	struct iscsi_cls_conn *conn;
+
+	conn = iscsi_conn_lookup(ev->u.d_conn.sid, ev->u.d_conn.cid);
+	if (!conn)
+		return -EINVAL;
+
+	if (transport->destroy_conn)
+		transport->destroy_conn(conn);
+	return 0;
+}
+
+static int
+iscsi_set_param(struct iscsi_transport *transport, struct iscsi_uevent *ev)
+{
+	char *data = (char*)ev + sizeof(*ev);
+	struct iscsi_cls_conn *conn;
+	struct iscsi_cls_session *session;
+	int err = 0, value = 0;
+
+	session = iscsi_session_lookup(ev->u.set_param.sid);
+	conn = iscsi_conn_lookup(ev->u.set_param.sid, ev->u.set_param.cid);
+	if (!conn || !session)
+		return -EINVAL;
+
+	switch (ev->u.set_param.param) {
+	case ISCSI_PARAM_SESS_RECOVERY_TMO:
+		sscanf(data, "%d", &value);
+		if (value != 0)
+			session->recovery_tmo = value;
+		break;
+	default:
+		err = transport->set_param(conn, ev->u.set_param.param,
+					   data, ev->u.set_param.len);
+	}
+
+	return err;
+}
+
+static int iscsi_if_ep_connect(struct iscsi_transport *transport,
+			       struct iscsi_uevent *ev, int msg_type)
+{
+	struct iscsi_endpoint *ep;
+	struct sockaddr *dst_addr;
+	struct Scsi_Host *shost = NULL;
+	int non_blocking, err = 0;
+
+	if (!transport->ep_connect)
+		return -EINVAL;
+
+	if (msg_type == ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST) {
+		shost = scsi_host_lookup(ev->u.ep_connect_through_host.host_no);
+		if (!shost) {
+			printk(KERN_ERR "ep connect failed. Could not find "
+			       "host no %u\n",
+			       ev->u.ep_connect_through_host.host_no);
+			return -ENODEV;
+		}
+		non_blocking = ev->u.ep_connect_through_host.non_blocking;
+	} else
+		non_blocking = ev->u.ep_connect.non_blocking;
+
+	dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
+	ep = transport->ep_connect(shost, dst_addr, non_blocking);
+	if (IS_ERR(ep)) {
+		err = PTR_ERR(ep);
+		goto release_host;
+	}
+
+	ev->r.ep_connect_ret.handle = ep->id;
+release_host:
+	if (shost)
+		scsi_host_put(shost);
+	return err;
+}
+
+static int
+iscsi_if_transport_ep(struct iscsi_transport *transport,
+		      struct iscsi_uevent *ev, int msg_type)
+{
+	struct iscsi_endpoint *ep;
+	int rc = 0;
+
+	switch (msg_type) {
+	case ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST:
+	case ISCSI_UEVENT_TRANSPORT_EP_CONNECT:
+		rc = iscsi_if_ep_connect(transport, ev, msg_type);
+		break;
+	case ISCSI_UEVENT_TRANSPORT_EP_POLL:
+		if (!transport->ep_poll)
+			return -EINVAL;
+
+		ep = iscsi2_lookup_endpoint(ev->u.ep_poll.ep_handle);
+		if (!ep)
+			return -EINVAL;
+
+		ev->r.retcode = transport->ep_poll(ep,
+						   ev->u.ep_poll.timeout_ms);
+		break;
+	case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT:
+		if (!transport->ep_disconnect)
+			return -EINVAL;
+
+		ep = iscsi2_lookup_endpoint(ev->u.ep_disconnect.ep_handle);
+		if (!ep)
+			return -EINVAL;
+
+		transport->ep_disconnect(ep);
+		break;
+	}
+	return rc;
+}
+
+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 (!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 (!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;
+}
+
+static int
+iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+	int err = 0;
+	struct iscsi_uevent *ev = NLMSG_DATA(nlh);
+	struct iscsi_transport *transport = NULL;
+	struct iscsi_internal *priv;
+	struct iscsi_cls_session *session;
+	struct iscsi_cls_conn *conn;
+	struct iscsi_endpoint *ep = NULL;
+
+	priv = iscsi_if_transport_lookup(iscsi_ptr(ev->transport_handle));
+	if (!priv)
+		return -EINVAL;
+	transport = priv->iscsi_transport;
+
+	if (!try_module_get(transport->owner))
+		return -EINVAL;
+
+	priv->daemon_pid = NETLINK_CREDS(skb)->pid;
+
+	switch (nlh->nlmsg_type) {
+	case ISCSI_UEVENT_CREATE_SESSION:
+		err = iscsi_if_create_session(priv, ep, ev,
+					      ev->u.c_session.initial_cmdsn,
+					      ev->u.c_session.cmds_max,
+					      ev->u.c_session.queue_depth);
+		break;
+	case ISCSI_UEVENT_CREATE_BOUND_SESSION:
+		ep = iscsi2_lookup_endpoint(ev->u.c_bound_session.ep_handle);
+		if (!ep) {
+			err = -EINVAL;
+			break;
+		}
+
+		err = iscsi_if_create_session(priv, ep, ev,
+					ev->u.c_bound_session.initial_cmdsn,
+					ev->u.c_bound_session.cmds_max,
+					ev->u.c_bound_session.queue_depth);
+		break;
+	case ISCSI_UEVENT_DESTROY_SESSION:
+		session = iscsi_session_lookup(ev->u.d_session.sid);
+		if (session)
+			transport->destroy_session(session);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_UNBIND_SESSION:
+		session = iscsi_session_lookup(ev->u.d_session.sid);
+		if (session)
+			scsi_queue_work(iscsi_session_to_shost(session),
+					&session->unbind_work);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_CREATE_CONN:
+		err = iscsi_if_create_conn(transport, ev);
+		break;
+	case ISCSI_UEVENT_DESTROY_CONN:
+		err = iscsi_if_destroy_conn(transport, ev);
+		break;
+	case ISCSI_UEVENT_BIND_CONN:
+		session = iscsi_session_lookup(ev->u.b_conn.sid);
+		conn = iscsi_conn_lookup(ev->u.b_conn.sid, ev->u.b_conn.cid);
+
+		if (session && conn)
+			ev->r.retcode =	transport->bind_conn(session, conn,
+					ev->u.b_conn.transport_eph,
+					ev->u.b_conn.is_leading);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_SET_PARAM:
+		err = iscsi_set_param(transport, ev);
+		break;
+	case ISCSI_UEVENT_START_CONN:
+		conn = iscsi_conn_lookup(ev->u.start_conn.sid, ev->u.start_conn.cid);
+		if (conn)
+			ev->r.retcode = transport->start_conn(conn);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_STOP_CONN:
+		conn = iscsi_conn_lookup(ev->u.stop_conn.sid, ev->u.stop_conn.cid);
+		if (conn)
+			transport->stop_conn(conn, ev->u.stop_conn.flag);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_SEND_PDU:
+		conn = iscsi_conn_lookup(ev->u.send_pdu.sid, ev->u.send_pdu.cid);
+		if (conn)
+			ev->r.retcode =	transport->send_pdu(conn,
+				(struct iscsi_hdr*)((char*)ev + sizeof(*ev)),
+				(char*)ev + sizeof(*ev) + ev->u.send_pdu.hdr_size,
+				ev->u.send_pdu.data_size);
+		else
+			err = -EINVAL;
+		break;
+	case ISCSI_UEVENT_GET_STATS:
+		err = iscsi_if_get_stats(transport, nlh);
+		break;
+	case ISCSI_UEVENT_TRANSPORT_EP_CONNECT:
+	case ISCSI_UEVENT_TRANSPORT_EP_POLL:
+	case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT:
+	case ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST:
+		err = iscsi_if_transport_ep(transport, ev, nlh->nlmsg_type);
+		break;
+	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;
+		break;
+	}
+
+	module_put(transport->owner);
+	return err;
+}
+
+/*
+ * 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 sock *sk, int len)
+{
+	struct sk_buff *skb;
+
+	mutex_lock(&rx_queue_mutex);
+	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
+		if (NETLINK_CREDS(skb)->uid) {
+			skb_pull(skb, skb->len);
+			goto free_skb;
+		}
+
+		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;
+			}
+
+			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 class_device_attribute class_device_attr_##_prefix##_##_name =	\
+	__ATTR(_name,_mode,_show,_store)
+
+/*
+ * iSCSI connection attrs
+ */
+#define iscsi_conn_attr_show(param)					\
+static ssize_t								\
+show_conn_param_##param(struct class_device *cdev, char *buf)		\
+{									\
+	struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev);		\
+	struct iscsi_transport *t = conn->transport;			\
+	return t->get_conn_param(conn, param, buf);			\
+}
+
+#define iscsi_conn_attr(field, param)					\
+	iscsi_conn_attr_show(param)					\
+static ISCSI_CLASS_ATTR(conn, field, S_IRUGO, show_conn_param_##param,	\
+			NULL);
+
+iscsi_conn_attr(max_recv_dlength, ISCSI_PARAM_MAX_RECV_DLENGTH);
+iscsi_conn_attr(max_xmit_dlength, ISCSI_PARAM_MAX_XMIT_DLENGTH);
+iscsi_conn_attr(header_digest, ISCSI_PARAM_HDRDGST_EN);
+iscsi_conn_attr(data_digest, ISCSI_PARAM_DATADGST_EN);
+iscsi_conn_attr(ifmarker, ISCSI_PARAM_IFMARKER_EN);
+iscsi_conn_attr(ofmarker, ISCSI_PARAM_OFMARKER_EN);
+iscsi_conn_attr(persistent_port, ISCSI_PARAM_PERSISTENT_PORT);
+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)				\
+static ssize_t								\
+show_session_param_##param(struct class_device *cdev, char *buf)	\
+{									\
+	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)				\
+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);
+iscsi_session_attr(ifacename, ISCSI_PARAM_IFACE_NAME, 0);
+iscsi_session_attr(initiatorname, ISCSI_PARAM_INITIATOR_NAME, 0)
+
+static ssize_t
+show_priv_session_state(struct class_device *cdev, char *buf)
+{
+	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,
+			NULL);
+
+#define iscsi_priv_session_attr_show(field, format)			\
+static ssize_t								\
+show_priv_session_##field(struct class_device *cdev, char *buf)		\
+{									\
+	struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);\
+	return sprintf(buf, format"\n", session->field);		\
+}
+
+#define iscsi_priv_session_attr(field, format)				\
+	iscsi_priv_session_attr_show(field, format)			\
+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 class_device *cdev, char *buf)		\
+{									\
+	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); \
+}
+
+#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] = &class_device_attr_priv_sess_##field; \
+	count++;							\
+} while (0)
+
+
+#define SETUP_SESSION_RD_ATTR(field, param_flag)			\
+do {									\
+	if (tt->param_mask & param_flag) {				\
+		priv->session_attrs[count] = &class_device_attr_sess_##field; \
+		count++;						\
+	}								\
+} while (0)
+
+#define SETUP_CONN_RD_ATTR(field, param_flag)				\
+do {									\
+	if (tt->param_mask & param_flag) {				\
+		priv->conn_attrs[count] = &class_device_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] = &class_device_attr_host_##field; \
+		count++;						\
+	}								\
+} while (0)
+
+static int iscsi_session_match(struct attribute_container *cont,
+			   struct device *dev)
+{
+	struct iscsi_cls_session *session;
+	struct Scsi_Host *shost;
+	struct iscsi_internal *priv;
+
+	if (!iscsi_is_session_dev(dev))
+		return 0;
+
+	session = iscsi_dev_to_session(dev);
+	shost = iscsi_session_to_shost(session);
+	if (!shost->transportt)
+		return 0;
+
+	priv = to_iscsi_internal(shost->transportt);
+	if (priv->session_cont.ac.class != &iscsi_session_class.class)
+		return 0;
+
+	return &priv->session_cont.ac == cont;
+}
+
+static int iscsi_conn_match(struct attribute_container *cont,
+			   struct device *dev)
+{
+	struct iscsi_cls_session *session;
+	struct iscsi_cls_conn *conn;
+	struct Scsi_Host *shost;
+	struct iscsi_internal *priv;
+
+	if (!iscsi_is_conn_dev(dev))
+		return 0;
+
+	conn = iscsi_dev_to_conn(dev);
+	session = iscsi_dev_to_session(conn->dev.parent);
+	shost = iscsi_session_to_shost(session);
+
+	if (!shost->transportt)
+		return 0;
+
+	priv = to_iscsi_internal(shost->transportt);
+	if (priv->conn_cont.ac.class != &iscsi_connection_class.class)
+		return 0;
+
+	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 *
+iscsi2_register_transport(struct iscsi_transport *tt)
+{
+	struct iscsi_internal *priv;
+	unsigned long flags;
+	int count = 0, err;
+
+	BUG_ON(!tt);
+
+	priv = iscsi_if_transport_lookup(tt);
+	if (priv)
+		return NULL;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return NULL;
+	INIT_LIST_HEAD(&priv->list);
+	priv->daemon_pid = -1;
+	priv->iscsi_transport = tt;
+	priv->t.user_scan = iscsi_user_scan;
+	priv->t.create_work_queue = 1;
+
+	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->cdev.kobj, &iscsi_transport_group);
+	if (err)
+		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_cls_host);
+	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;
+	priv->conn_cont.ac.match = iscsi_conn_match;
+	transport_container_register(&priv->conn_cont);
+
+	SETUP_CONN_RD_ATTR(max_recv_dlength, ISCSI_MAX_RECV_DLENGTH);
+	SETUP_CONN_RD_ATTR(max_xmit_dlength, ISCSI_MAX_XMIT_DLENGTH);
+	SETUP_CONN_RD_ATTR(header_digest, ISCSI_HDRDGST_EN);
+	SETUP_CONN_RD_ATTR(data_digest, ISCSI_DATADGST_EN);
+	SETUP_CONN_RD_ATTR(ifmarker, ISCSI_IFMARKER_EN);
+	SETUP_CONN_RD_ATTR(ofmarker, ISCSI_OFMARKER_EN);
+	SETUP_CONN_RD_ATTR(address, ISCSI_CONN_ADDRESS);
+	SETUP_CONN_RD_ATTR(port, ISCSI_CONN_PORT);
+	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;
+	count = 0;
+
+	/* session parameters */
+	priv->session_cont.ac.attrs = &priv->session_attrs[0];
+	priv->session_cont.ac.class = &iscsi_session_class.class;
+	priv->session_cont.ac.match = iscsi_session_match;
+	transport_container_register(&priv->session_cont);
+
+	SETUP_SESSION_RD_ATTR(initial_r2t, ISCSI_INITIAL_R2T_EN);
+	SETUP_SESSION_RD_ATTR(max_outstanding_r2t, ISCSI_MAX_R2T);
+	SETUP_SESSION_RD_ATTR(immediate_data, ISCSI_IMM_DATA_EN);
+	SETUP_SESSION_RD_ATTR(first_burst_len, ISCSI_FIRST_BURST);
+	SETUP_SESSION_RD_ATTR(max_burst_len, ISCSI_MAX_BURST);
+	SETUP_SESSION_RD_ATTR(data_pdu_in_order, ISCSI_PDU_INORDER_EN);
+	SETUP_SESSION_RD_ATTR(data_seq_in_order, ISCSI_DATASEQ_INORDER_EN);
+	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_SESSION_RD_ATTR(ifacename, ISCSI_IFACE_NAME);
+	SETUP_SESSION_RD_ATTR(initiatorname, ISCSI_INITIATOR_NAME);
+	SETUP_PRIV_SESSION_RD_ATTR(recovery_tmo);
+	SETUP_PRIV_SESSION_RD_ATTR(state);
+
+	BUG_ON(count > ISCSI_SESSION_ATTRS);
+	priv->session_attrs[count] = NULL;
+
+	spin_lock_irqsave(&iscsi_transport_lock, flags);
+	list_add(&priv->list, &iscsi_transports);
+	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
+
+	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
+	return &priv->t;
+
+unregister_cdev:
+	class_device_unregister(&priv->cdev);
+free_priv:
+	kfree(priv);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(iscsi2_register_transport);
+
+int iscsi2_unregister_transport(struct iscsi_transport *tt)
+{
+	struct iscsi_internal *priv;
+	unsigned long flags;
+
+	BUG_ON(!tt);
+
+	mutex_lock(&rx_queue_mutex);
+
+	priv = iscsi_if_transport_lookup(tt);
+	BUG_ON (!priv);
+
+	spin_lock_irqsave(&iscsi_transport_lock, flags);
+	list_del(&priv->list);
+	spin_unlock_irqrestore(&iscsi_transport_lock, flags);
+
+	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);
+	mutex_unlock(&rx_queue_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(iscsi2_unregister_transport);
+
+static __init int iscsi_transport_init(void)
+{
+	int err;
+
+	printk(KERN_INFO "Loading iSCSI transport class v%s.\n",
+		ISCSI_TRANSPORT_VERSION);
+
+	err = iscsi_if_load();
+	if (err)
+		return err;
+
+	atomic_set(&iscsi_session_nr, 0);
+
+	err = class_register(&iscsi_transport_class);
+	if (err)
+		goto release_if;
+
+	err = class_register(&iscsi_endpoint_class);
+	if (err)
+		goto unregister_transport_class;
+
+	err = transport_class_register(&iscsi_host_class);
+	if (err)
+		goto unregister_endpoint_class;
+
+	err = transport_class_register(&iscsi_connection_class);
+	if (err)
+		goto unregister_host_class;
+
+	err = transport_class_register(&iscsi_session_class);
+	if (err)
+		goto unregister_conn_class;
+
+	nls = netlink_kernel_create(&init_net, NETLINK_ISCSI, 1, iscsi_if_rx,
+				    NULL, THIS_MODULE);
+	if (!nls) {
+		err = -ENOBUFS;
+		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:
+	transport_class_unregister(&iscsi_connection_class);
+unregister_host_class:
+	transport_class_unregister(&iscsi_host_class);
+unregister_endpoint_class:
+	class_unregister(&iscsi_endpoint_class);
+unregister_transport_class:
+	class_unregister(&iscsi_transport_class);
+release_if:
+	iscsi_if_release();
+	return err;
+}
+
+static void __exit iscsi_transport_exit(void)
+{
+	destroy_workqueue(iscsi_eh_timer_workq);
+	netlink_kernel_release(nls);
+	transport_class_unregister(&iscsi_connection_class);
+	transport_class_unregister(&iscsi_session_class);
+	transport_class_unregister(&iscsi_host_class);
+	class_unregister(&iscsi_endpoint_class);
+	class_unregister(&iscsi_transport_class);
+	iscsi_if_release();
+}
+
+module_init(iscsi_transport_init);
+module_exit(iscsi_transport_exit);
+
+MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, "
+	      "Dmitry Yusupov <dmitry_yus@yahoo.com>, "
+	      "Alex Aizman <itn780@yahoo.com>");
+MODULE_DESCRIPTION("iSCSI Transport Interface");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(ISCSI_TRANSPORT_VERSION);
diff --git a/include/scsi/iscsi_compat2.h b/include/scsi/iscsi_compat2.h
new file mode 100644
index 0000000..11d287a
--- /dev/null
+++ b/include/scsi/iscsi_compat2.h
@@ -0,0 +1,123 @@
+#ifndef OPEN_ISCSI_COMPAT
+#define OPEN_ISCSI_COMPAT
+
+#include <asm/scatterlist.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/workqueue.h>
+#include <linux/crypto.h>
+#include <linux/net.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+
+#define CRYPTO_ALG_ASYNC		0x00000080
+struct hash_desc
+{
+	struct crypto_tfm *tfm;
+	u32 flags;
+};
+
+static inline int crypto_hash_init(struct hash_desc *desc)
+{
+	crypto_digest_init(desc->tfm);
+	return 0;
+}
+
+static inline int crypto_hash_digest(struct hash_desc *desc,
+				     struct scatterlist *sg,
+				     unsigned int nbytes, u8 *out)
+{
+	crypto_digest_digest(desc->tfm, sg, 1, out);
+	return nbytes;
+}
+
+static inline int crypto_hash_update(struct hash_desc *desc,
+				     struct scatterlist *sg,
+				     unsigned int nbytes)
+{
+	crypto_digest_update(desc->tfm, sg, 1);
+	return nbytes;
+}
+
+static inline int crypto_hash_final(struct hash_desc *desc, u8 *out)
+{
+	crypto_digest_final(desc->tfm, out);
+	return 0;
+}
+
+static inline struct crypto_tfm *crypto_alloc_hash(const char *alg_name,
+						    u32 type, u32 mask)
+{
+	struct crypto_tfm *ret = crypto_alloc_tfm(alg_name ,type);
+	return ret ? ret : ERR_PTR(-ENOMEM);
+}
+
+static inline void crypto_free_hash(struct crypto_tfm *tfm)
+{
+	crypto_free_tfm(tfm);
+}
+
+static inline unsigned long rounddown_pow_of_two(unsigned long n)
+{
+	return 1UL << (fls_long(n) - 1);
+}
+
+#define SCSI_MAX_VARLEN_CDB_SIZE 16
+
+static inline struct scatterlist *sg_next(struct scatterlist *sg)
+{
+	if (!sg) {
+		BUG();
+		return NULL;
+	}
+	return sg + 1;
+}
+
+#define for_each_sg(sglist, sg, nr, __i)        \
+	for (__i = 0, sg = (sglist); __i < (nr); __i++, sg = sg_next(sg))
+
+#define sg_page(_sg) _sg->page
+
+static inline void sg_set_page(struct scatterlist *sg, struct page *page,
+				unsigned int len, unsigned int offset)
+{
+	sg->page = page;
+	sg->offset = offset;
+	sg->length = len;
+}
+
+static inline void sg_init_table(struct scatterlist *sgl, unsigned int nents)
+{
+	memset(sgl, 0, sizeof(*sgl) * nents);
+}
+
+static inline int scsi_bidi_cmnd(struct scsi_cmnd *cmd)
+{
+	return 0;
+}
+
+static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
+{
+	return (struct nlmsghdr *)skb->data;
+}
+
+#define netlink_kernel_release(_nls) \
+	sock_release(_nls->sk_socket)
+
+#define netlink_kernel_create(net, uint, groups, input, cb_mutex, mod) \
+	netlink_kernel_create(uint, groups, input, mod)
+
+#define BLK_EH_NOT_HANDLED EH_NOT_HANDLED
+#define BLK_EH_RESET_TIMER EH_RESET_TIMER
+#define blk_eh_timer_return scsi_eh_timer_return
+
+static inline void INIT_WORK_compat(struct work_struct *work, void *func)
+{
+	INIT_WORK(work, func, work);
+}
+
+#undef INIT_WORK
+#define INIT_WORK(_work, _func) INIT_WORK_compat(_work, _func)
+#define INIT_DELAYED_WORK(_work,_func) INIT_WORK_compat(_work, _func)
+
+#endif
diff --git a/include/scsi/iscsi_if2.h b/include/scsi/iscsi_if2.h
new file mode 100644
index 0000000..8721bc5
--- /dev/null
+++ b/include/scsi/iscsi_if2.h
@@ -0,0 +1,406 @@
+/*
+ * iSCSI User/Kernel Shares (Defines, Constants, Protocol definitions, etc)
+ *
+ * Copyright (C) 2005 Dmitry Yusupov
+ * Copyright (C) 2005 Alex Aizman
+ * maintained by open-iscsi@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#ifndef ISCSI_IF_H
+#define ISCSI_IF_H
+
+#include <scsi/iscsi_proto2.h>
+
+#define UEVENT_BASE			10
+#define KEVENT_BASE			100
+#define ISCSI_ERR_BASE			1000
+
+enum iscsi_uevent_e {
+	ISCSI_UEVENT_UNKNOWN		= 0,
+
+	/* down events */
+	ISCSI_UEVENT_CREATE_SESSION	= UEVENT_BASE + 1,
+	ISCSI_UEVENT_DESTROY_SESSION	= UEVENT_BASE + 2,
+	ISCSI_UEVENT_CREATE_CONN	= UEVENT_BASE + 3,
+	ISCSI_UEVENT_DESTROY_CONN	= UEVENT_BASE + 4,
+	ISCSI_UEVENT_BIND_CONN		= UEVENT_BASE + 5,
+	ISCSI_UEVENT_SET_PARAM		= UEVENT_BASE + 6,
+	ISCSI_UEVENT_START_CONN		= UEVENT_BASE + 7,
+	ISCSI_UEVENT_STOP_CONN		= UEVENT_BASE + 8,
+	ISCSI_UEVENT_SEND_PDU		= UEVENT_BASE + 9,
+	ISCSI_UEVENT_GET_STATS		= UEVENT_BASE + 10,
+	ISCSI_UEVENT_GET_PARAM		= UEVENT_BASE + 11,
+
+	ISCSI_UEVENT_TRANSPORT_EP_CONNECT	= UEVENT_BASE + 12,
+	ISCSI_UEVENT_TRANSPORT_EP_POLL		= UEVENT_BASE + 13,
+	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,
+	ISCSI_UEVENT_CREATE_BOUND_SESSION		= UEVENT_BASE + 18,
+	ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST	= UEVENT_BASE + 19,
+
+	/* 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 {
+	ISCSI_TGT_DSCVR_SEND_TARGETS	= 1,
+	ISCSI_TGT_DSCVR_ISNS		= 2,
+	ISCSI_TGT_DSCVR_SLP		= 3,
+};
+
+struct iscsi_uevent {
+	uint32_t type; /* k/u events type */
+	uint32_t iferror; /* carries interface or resource errors */
+	uint64_t transport_handle;
+
+	union {
+		/* messages u -> k */
+		struct msg_create_session {
+			uint32_t	initial_cmdsn;
+			uint16_t	cmds_max;
+			uint16_t	queue_depth;
+		} c_session;
+		struct msg_create_bound_session {
+			uint64_t	ep_handle;
+			uint32_t	initial_cmdsn;
+			uint16_t	cmds_max;
+			uint16_t	queue_depth;
+		} c_bound_session;
+		struct msg_destroy_session {
+			uint32_t	sid;
+		} d_session;
+		struct msg_create_conn {
+			uint32_t	sid;
+			uint32_t	cid;
+		} c_conn;
+		struct msg_bind_conn {
+			uint32_t	sid;
+			uint32_t	cid;
+			uint64_t	transport_eph;
+			uint32_t	is_leading;
+		} b_conn;
+		struct msg_destroy_conn {
+			uint32_t	sid;
+			uint32_t	cid;
+		} d_conn;
+		struct msg_send_pdu {
+			uint32_t	sid;
+			uint32_t	cid;
+			uint32_t	hdr_size;
+			uint32_t	data_size;
+		} send_pdu;
+		struct msg_set_param {
+			uint32_t	sid;
+			uint32_t	cid;
+			uint32_t	param; /* enum iscsi_param */
+			uint32_t	len;
+		} set_param;
+		struct msg_start_conn {
+			uint32_t	sid;
+			uint32_t	cid;
+		} start_conn;
+		struct msg_stop_conn {
+			uint32_t	sid;
+			uint32_t	cid;
+			uint64_t	conn_handle;
+			uint32_t	flag;
+		} stop_conn;
+		struct msg_get_stats {
+			uint32_t	sid;
+			uint32_t	cid;
+		} get_stats;
+		struct msg_transport_connect {
+			uint32_t	non_blocking;
+		} ep_connect;
+		struct msg_transport_connect_through_host {
+			uint32_t	host_no;
+			uint32_t	non_blocking;
+		} ep_connect_through_host;
+		struct msg_transport_poll {
+			uint64_t	ep_handle;
+			uint32_t	timeout_ms;
+		} ep_poll;
+		struct msg_transport_disconnect {
+			uint64_t	ep_handle;
+		} ep_disconnect;
+		struct msg_tgt_dscvr {
+			enum iscsi_tgt_dscvr	type;
+			uint32_t	host_no;
+			/*
+ 			 * enable = 1 to establish a new connection
+			 * with the server. enable = 0 to disconnect
+			 * from the server. Used primarily to switch
+			 * from one iSNS server to another.
+			 */
+			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 */
+		int			retcode;
+		struct msg_create_session_ret {
+			uint32_t	sid;
+			uint32_t	host_no;
+		} c_session_ret;
+		struct msg_create_conn_ret {
+			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;
+			uint64_t	recv_handle;
+		} recv_req;
+		struct msg_conn_error {
+			uint32_t	sid;
+			uint32_t	cid;
+			uint32_t	error; /* enum iscsi_err */
+		} connerror;
+		struct msg_session_destroyed {
+			uint32_t	host_no;
+			uint32_t	sid;
+		} d_session;
+		struct msg_transport_connect_ret {
+			uint64_t	handle;
+		} ep_connect_ret;
+	} r;
+} __attribute__ ((aligned (sizeof(uint64_t))));
+
+/*
+ * Common error codes
+ */
+enum iscsi_err {
+	ISCSI_OK			= 0,
+
+	ISCSI_ERR_DATASN		= ISCSI_ERR_BASE + 1,
+	ISCSI_ERR_DATA_OFFSET		= ISCSI_ERR_BASE + 2,
+	ISCSI_ERR_MAX_CMDSN		= ISCSI_ERR_BASE + 3,
+	ISCSI_ERR_EXP_CMDSN		= ISCSI_ERR_BASE + 4,
+	ISCSI_ERR_BAD_OPCODE		= ISCSI_ERR_BASE + 5,
+	ISCSI_ERR_DATALEN		= ISCSI_ERR_BASE + 6,
+	ISCSI_ERR_AHSLEN		= ISCSI_ERR_BASE + 7,
+	ISCSI_ERR_PROTO			= ISCSI_ERR_BASE + 8,
+	ISCSI_ERR_LUN			= ISCSI_ERR_BASE + 9,
+	ISCSI_ERR_BAD_ITT		= ISCSI_ERR_BASE + 10,
+	ISCSI_ERR_CONN_FAILED		= ISCSI_ERR_BASE + 11,
+	ISCSI_ERR_R2TSN			= ISCSI_ERR_BASE + 12,
+	ISCSI_ERR_SESSION_FAILED	= ISCSI_ERR_BASE + 13,
+	ISCSI_ERR_HDR_DGST		= ISCSI_ERR_BASE + 14,
+	ISCSI_ERR_DATA_DGST		= ISCSI_ERR_BASE + 15,
+	ISCSI_ERR_PARAM_NOT_FOUND	= ISCSI_ERR_BASE + 16,
+	ISCSI_ERR_NO_SCSI_CMD		= ISCSI_ERR_BASE + 17,
+	ISCSI_ERR_INVALID_HOST		= ISCSI_ERR_BASE + 18,
+	ISCSI_ERR_XMIT_FAILED		= ISCSI_ERR_BASE + 19,
+};
+
+/*
+ * iSCSI Parameters (RFC3720)
+ */
+enum iscsi_param {
+	/* passed in using netlink set param */
+	ISCSI_PARAM_MAX_RECV_DLENGTH,
+	ISCSI_PARAM_MAX_XMIT_DLENGTH,
+	ISCSI_PARAM_HDRDGST_EN,
+	ISCSI_PARAM_DATADGST_EN,
+	ISCSI_PARAM_INITIAL_R2T_EN,
+	ISCSI_PARAM_MAX_R2T,
+	ISCSI_PARAM_IMM_DATA_EN,
+	ISCSI_PARAM_FIRST_BURST,
+	ISCSI_PARAM_MAX_BURST,
+	ISCSI_PARAM_PDU_INORDER_EN,
+	ISCSI_PARAM_DATASEQ_INORDER_EN,
+	ISCSI_PARAM_ERL,
+	ISCSI_PARAM_IFMARKER_EN,
+	ISCSI_PARAM_OFMARKER_EN,
+	ISCSI_PARAM_EXP_STATSN,
+	ISCSI_PARAM_TARGET_NAME,
+	ISCSI_PARAM_TPGT,
+	ISCSI_PARAM_PERSISTENT_ADDRESS,
+	ISCSI_PARAM_PERSISTENT_PORT,
+	ISCSI_PARAM_SESS_RECOVERY_TMO,
+
+	/* pased in through bind conn using transport_fd */
+	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,
+
+	ISCSI_PARAM_IFACE_NAME,
+	ISCSI_PARAM_ISID,
+	ISCSI_PARAM_INITIATOR_NAME,
+	/* must always be last */
+	ISCSI_PARAM_MAX,
+};
+
+#define ISCSI_MAX_RECV_DLENGTH		(1ULL << ISCSI_PARAM_MAX_RECV_DLENGTH)
+#define ISCSI_MAX_XMIT_DLENGTH		(1ULL << ISCSI_PARAM_MAX_XMIT_DLENGTH)
+#define ISCSI_HDRDGST_EN		(1ULL << ISCSI_PARAM_HDRDGST_EN)
+#define ISCSI_DATADGST_EN		(1ULL << ISCSI_PARAM_DATADGST_EN)
+#define ISCSI_INITIAL_R2T_EN		(1ULL << ISCSI_PARAM_INITIAL_R2T_EN)
+#define ISCSI_MAX_R2T			(1ULL << ISCSI_PARAM_MAX_R2T)
+#define ISCSI_IMM_DATA_EN		(1ULL << ISCSI_PARAM_IMM_DATA_EN)
+#define ISCSI_FIRST_BURST		(1ULL << ISCSI_PARAM_FIRST_BURST)
+#define ISCSI_MAX_BURST			(1ULL << ISCSI_PARAM_MAX_BURST)
+#define ISCSI_PDU_INORDER_EN		(1ULL << ISCSI_PARAM_PDU_INORDER_EN)
+#define ISCSI_DATASEQ_INORDER_EN	(1ULL << ISCSI_PARAM_DATASEQ_INORDER_EN)
+#define ISCSI_ERL			(1ULL << ISCSI_PARAM_ERL)
+#define ISCSI_IFMARKER_EN		(1ULL << ISCSI_PARAM_IFMARKER_EN)
+#define ISCSI_OFMARKER_EN		(1ULL << ISCSI_PARAM_OFMARKER_EN)
+#define ISCSI_EXP_STATSN		(1ULL << ISCSI_PARAM_EXP_STATSN)
+#define ISCSI_TARGET_NAME		(1ULL << ISCSI_PARAM_TARGET_NAME)
+#define ISCSI_TPGT			(1ULL << ISCSI_PARAM_TPGT)
+#define ISCSI_PERSISTENT_ADDRESS	(1ULL << ISCSI_PARAM_PERSISTENT_ADDRESS)
+#define ISCSI_PERSISTENT_PORT		(1ULL << ISCSI_PARAM_PERSISTENT_PORT)
+#define ISCSI_SESS_RECOVERY_TMO		(1ULL << ISCSI_PARAM_SESS_RECOVERY_TMO)
+#define ISCSI_CONN_PORT			(1ULL << ISCSI_PARAM_CONN_PORT)
+#define ISCSI_CONN_ADDRESS		(1ULL << ISCSI_PARAM_CONN_ADDRESS)
+#define ISCSI_USERNAME			(1ULL << ISCSI_PARAM_USERNAME)
+#define ISCSI_USERNAME_IN		(1ULL << ISCSI_PARAM_USERNAME_IN)
+#define ISCSI_PASSWORD			(1ULL << ISCSI_PARAM_PASSWORD)
+#define ISCSI_PASSWORD_IN		(1ULL << ISCSI_PARAM_PASSWORD_IN)
+#define ISCSI_FAST_ABORT		(1ULL << ISCSI_PARAM_FAST_ABORT)
+#define ISCSI_ABORT_TMO			(1ULL << ISCSI_PARAM_ABORT_TMO)
+#define ISCSI_LU_RESET_TMO		(1ULL << ISCSI_PARAM_LU_RESET_TMO)
+#define ISCSI_HOST_RESET_TMO		(1ULL << ISCSI_PARAM_HOST_RESET_TMO)
+#define ISCSI_PING_TMO			(1ULL << ISCSI_PARAM_PING_TMO)
+#define ISCSI_RECV_TMO			(1ULL << ISCSI_PARAM_RECV_TMO)
+#define ISCSI_IFACE_NAME		(1ULL << ISCSI_PARAM_IFACE_NAME)
+#define ISCSI_ISID			(1ULL << ISCSI_PARAM_ISID)
+#define ISCSI_INITIATOR_NAME		(1ULL << ISCSI_PARAM_INITIATOR_NAME)
+
+/* 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		(1ULL << ISCSI_HOST_PARAM_HWADDRESS)
+#define ISCSI_HOST_INITIATOR_NAME	(1ULL << ISCSI_HOST_PARAM_INITIATOR_NAME)
+#define ISCSI_HOST_NETDEV_NAME		(1ULL << ISCSI_HOST_PARAM_NETDEV_NAME)
+#define ISCSI_HOST_IPADDRESS		(1ULL << ISCSI_HOST_PARAM_IPADDRESS)
+
+#define iscsi_ptr(_handle) ((void*)(unsigned long)_handle)
+#define iscsi_handle(_ptr) ((uint64_t)(unsigned long)_ptr)
+
+/*
+ * These flags presents iSCSI Data-Path capabilities.
+ */
+#define CAP_RECOVERY_L0		0x1
+#define CAP_RECOVERY_L1		0x2
+#define CAP_RECOVERY_L2		0x4
+#define CAP_MULTI_R2T		0x8
+#define CAP_HDRDGST		0x10
+#define CAP_DATADGST		0x20
+#define CAP_MULTI_CONN		0x40
+#define CAP_TEXT_NEGO		0x80
+#define CAP_MARKERS		0x100
+#define CAP_FW_DB		0x200
+#define CAP_SENDTARGETS_OFFLOAD	0x400	/* offload discovery process */
+#define CAP_DATA_PATH_OFFLOAD	0x800	/* offload entire IO path */
+#define CAP_DIGEST_OFFLOAD	0x1000	/* offload hdr and data digests */
+#define CAP_PADDING_OFFLOAD	0x2000	/* offload padding insertion, removal,
+					 and verification */
+
+/*
+ * These flags describes reason of stop_conn() call
+ */
+#define STOP_CONN_TERM		0x1
+#define STOP_CONN_SUSPEND	0x2
+#define STOP_CONN_RECOVER	0x3
+
+#define ISCSI_STATS_CUSTOM_MAX		32
+#define ISCSI_STATS_CUSTOM_DESC_MAX	64
+struct iscsi_stats_custom {
+	char desc[ISCSI_STATS_CUSTOM_DESC_MAX];
+	uint64_t value;
+};
+
+/*
+ * struct iscsi_stats - iSCSI Statistics (iSCSI MIB)
+ *
+ * Note: this structure contains counters collected on per-connection basis.
+ */
+struct iscsi_stats {
+	/* octets */
+	uint64_t txdata_octets;
+	uint64_t rxdata_octets;
+
+	/* xmit pdus */
+	uint32_t noptx_pdus;
+	uint32_t scsicmd_pdus;
+	uint32_t tmfcmd_pdus;
+	uint32_t login_pdus;
+	uint32_t text_pdus;
+	uint32_t dataout_pdus;
+	uint32_t logout_pdus;
+	uint32_t snack_pdus;
+
+	/* recv pdus */
+	uint32_t noprx_pdus;
+	uint32_t scsirsp_pdus;
+	uint32_t tmfrsp_pdus;
+	uint32_t textrsp_pdus;
+	uint32_t datain_pdus;
+	uint32_t logoutrsp_pdus;
+	uint32_t r2t_pdus;
+	uint32_t async_pdus;
+	uint32_t rjt_pdus;
+
+	/* errors */
+	uint32_t digest_err;
+	uint32_t timeout_err;
+
+	/*
+	 * iSCSI Custom Statistics support, i.e. Transport could
+	 * extend existing MIB statistics with its own specific statistics
+	 * up to ISCSI_STATS_CUSTOM_MAX
+	 */
+	uint32_t custom_length;
+	struct iscsi_stats_custom custom[0]
+		__attribute__ ((aligned (sizeof(uint64_t))));
+};
+
+#endif
diff --git a/include/scsi/iscsi_if_compat.h b/include/scsi/iscsi_if_compat.h
new file mode 100644
index 0000000..bde08eb
--- /dev/null
+++ b/include/scsi/iscsi_if_compat.h
@@ -0,0 +1,26 @@
+/*
+ * Compat wrapper module for the iSCSI interface
+ *
+ * Copyright (C) 2009 Red Hat, Inc.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#ifndef ISCSI_IF_COMPAT
+#define ISCSI_IF_COMPAT
+
+int iscsi_if_load(void);
+void iscsi_if_release(void);
+
+#endif
diff --git a/include/scsi/iscsi_proto2.h b/include/scsi/iscsi_proto2.h
new file mode 100644
index 0000000..c257381
--- /dev/null
+++ b/include/scsi/iscsi_proto2.h
@@ -0,0 +1,617 @@
+/*
+ * RFC 3720 (iSCSI) protocol data types
+ *
+ * Copyright (C) 2005 Dmitry Yusupov
+ * Copyright (C) 2005 Alex Aizman
+ * maintained by open-iscsi@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#ifndef ISCSI_PROTO_H
+#define ISCSI_PROTO_H
+
+#define ISCSI_DRAFT20_VERSION	0x00
+
+/* default iSCSI listen port for incoming connections */
+#define ISCSI_LISTEN_PORT	3260
+
+/* Padding word length */
+#define ISCSI_PAD_LEN		4
+
+/*
+ * useful common(control and data pathes) macro
+ */
+#define ntoh24(p) (((p)[0] << 16) | ((p)[1] << 8) | ((p)[2]))
+#define hton24(p, v) { \
+        p[0] = (((v) >> 16) & 0xFF); \
+        p[1] = (((v) >> 8) & 0xFF); \
+        p[2] = ((v) & 0xFF); \
+}
+#define zero_data(p) {p[0]=0;p[1]=0;p[2]=0;}
+
+/* initiator tags; opaque for target */
+typedef uint32_t __bitwise__ itt_t;
+/* below makes sense only for initiator that created this tag */
+#define build_itt(itt, age) ((__force itt_t)\
+	((itt) | ((age) << ISCSI_AGE_SHIFT)))
+#define get_itt(itt) ((__force uint32_t)(itt_t)(itt) & ISCSI_ITT_MASK)
+#define RESERVED_ITT ((__force itt_t)0xffffffff)
+
+/*
+ * iSCSI Template Message Header
+ */
+struct iscsi_hdr {
+	uint8_t		opcode;
+	uint8_t		flags;		/* Final bit */
+	uint8_t		rsvd2[2];
+	uint8_t		hlength;	/* AHSs total length */
+	uint8_t		dlength[3];	/* Data length */
+	uint8_t		lun[8];
+	itt_t		itt;		/* Initiator Task Tag, opaque for target */
+	__be32		ttt;		/* Target Task Tag */
+	__be32		statsn;
+	__be32		exp_statsn;
+	__be32		max_statsn;
+	uint8_t		other[12];
+};
+
+/************************* RFC 3720 Begin *****************************/
+
+#define ISCSI_RESERVED_TAG		0xffffffff
+
+/* Opcode encoding bits */
+#define ISCSI_OP_RETRY			0x80
+#define ISCSI_OP_IMMEDIATE		0x40
+#define ISCSI_OPCODE_MASK		0x3F
+
+/* Initiator Opcode values */
+#define ISCSI_OP_NOOP_OUT		0x00
+#define ISCSI_OP_SCSI_CMD		0x01
+#define ISCSI_OP_SCSI_TMFUNC		0x02
+#define ISCSI_OP_LOGIN			0x03
+#define ISCSI_OP_TEXT			0x04
+#define ISCSI_OP_SCSI_DATA_OUT		0x05
+#define ISCSI_OP_LOGOUT			0x06
+#define ISCSI_OP_SNACK			0x10
+
+#define ISCSI_OP_VENDOR1_CMD		0x1c
+#define ISCSI_OP_VENDOR2_CMD		0x1d
+#define ISCSI_OP_VENDOR3_CMD		0x1e
+#define ISCSI_OP_VENDOR4_CMD		0x1f
+
+/* Target Opcode values */
+#define ISCSI_OP_NOOP_IN		0x20
+#define ISCSI_OP_SCSI_CMD_RSP		0x21
+#define ISCSI_OP_SCSI_TMFUNC_RSP	0x22
+#define ISCSI_OP_LOGIN_RSP		0x23
+#define ISCSI_OP_TEXT_RSP		0x24
+#define ISCSI_OP_SCSI_DATA_IN		0x25
+#define ISCSI_OP_LOGOUT_RSP		0x26
+#define ISCSI_OP_R2T			0x31
+#define ISCSI_OP_ASYNC_EVENT		0x32
+#define ISCSI_OP_REJECT			0x3f
+
+struct iscsi_ahs_hdr {
+	__be16 ahslength;
+	uint8_t ahstype;
+	uint8_t ahspec[5];
+};
+
+#define ISCSI_AHSTYPE_CDB		1
+#define ISCSI_AHSTYPE_RLENGTH		2
+#define ISCSI_CDB_SIZE			16
+
+/* iSCSI PDU Header */
+struct iscsi_cmd {
+	uint8_t opcode;
+	uint8_t flags;
+	__be16 rsvd2;
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t lun[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be32 data_length;
+	__be32 cmdsn;
+	__be32 exp_statsn;
+	uint8_t cdb[ISCSI_CDB_SIZE];	/* SCSI Command Block */
+	/* Additional Data (Command Dependent) */
+};
+
+/* Command PDU flags */
+#define ISCSI_FLAG_CMD_FINAL		0x80
+#define ISCSI_FLAG_CMD_READ		0x40
+#define ISCSI_FLAG_CMD_WRITE		0x20
+#define ISCSI_FLAG_CMD_ATTR_MASK	0x07	/* 3 bits */
+
+/* SCSI Command Attribute values */
+#define ISCSI_ATTR_UNTAGGED		0
+#define ISCSI_ATTR_SIMPLE		1
+#define ISCSI_ATTR_ORDERED		2
+#define ISCSI_ATTR_HEAD_OF_QUEUE	3
+#define ISCSI_ATTR_ACA			4
+
+struct iscsi_rlength_ahdr {
+	__be16 ahslength;
+	uint8_t ahstype;
+	uint8_t reserved;
+	__be32 read_length;
+};
+
+/* Extended CDB AHS */
+struct iscsi_ecdb_ahdr {
+	__be16 ahslength;	/* CDB length - 15, including reserved byte */
+	uint8_t ahstype;
+	uint8_t reserved;
+	/* 4-byte aligned extended CDB spillover */
+	uint8_t ecdb[260 - ISCSI_CDB_SIZE];
+};
+
+/* SCSI Response Header */
+struct iscsi_cmd_rsp {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t response;
+	uint8_t cmd_status;
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t rsvd[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be32	rsvd1;
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	__be32	exp_datasn;
+	__be32	bi_residual_count;
+	__be32	residual_count;
+	/* Response or Sense Data (optional) */
+};
+
+/* Command Response PDU flags */
+#define ISCSI_FLAG_CMD_BIDI_OVERFLOW	0x10
+#define ISCSI_FLAG_CMD_BIDI_UNDERFLOW	0x08
+#define ISCSI_FLAG_CMD_OVERFLOW		0x04
+#define ISCSI_FLAG_CMD_UNDERFLOW	0x02
+
+/* iSCSI Status values. Valid if Rsp Selector bit is not set */
+#define ISCSI_STATUS_CMD_COMPLETED	0
+#define ISCSI_STATUS_TARGET_FAILURE	1
+#define ISCSI_STATUS_SUBSYS_FAILURE	2
+
+/* Asynchronous Event Header */
+struct iscsi_async {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd2[2];
+	uint8_t rsvd3;
+	uint8_t dlength[3];
+	uint8_t lun[8];
+	uint8_t rsvd4[8];
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	uint8_t async_event;
+	uint8_t async_vcode;
+	__be16	param1;
+	__be16	param2;
+	__be16	param3;
+	uint8_t rsvd5[4];
+};
+
+/* iSCSI Event Codes */
+#define ISCSI_ASYNC_MSG_SCSI_EVENT			0
+#define ISCSI_ASYNC_MSG_REQUEST_LOGOUT			1
+#define ISCSI_ASYNC_MSG_DROPPING_CONNECTION		2
+#define ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS	3
+#define ISCSI_ASYNC_MSG_PARAM_NEGOTIATION		4
+#define ISCSI_ASYNC_MSG_VENDOR_SPECIFIC			255
+
+/* NOP-Out Message */
+struct iscsi_nopout {
+	uint8_t opcode;
+	uint8_t flags;
+	__be16	rsvd2;
+	uint8_t rsvd3;
+	uint8_t dlength[3];
+	uint8_t lun[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be32	ttt;	/* Target Transfer Tag */
+	__be32	cmdsn;
+	__be32	exp_statsn;
+	uint8_t rsvd4[16];
+};
+
+/* NOP-In Message */
+struct iscsi_nopin {
+	uint8_t opcode;
+	uint8_t flags;
+	__be16	rsvd2;
+	uint8_t rsvd3;
+	uint8_t dlength[3];
+	uint8_t lun[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be32	ttt;	/* Target Transfer Tag */
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	uint8_t rsvd4[12];
+};
+
+/* SCSI Task Management Message Header */
+struct iscsi_tm {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd1[2];
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t lun[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	itt_t	 rtt;	/* Reference Task Tag */
+	__be32	cmdsn;
+	__be32	exp_statsn;
+	__be32	refcmdsn;
+	__be32	exp_datasn;
+	uint8_t rsvd2[8];
+};
+
+#define ISCSI_FLAG_TM_FUNC_MASK			0x7F
+
+/* Function values */
+#define ISCSI_TM_FUNC_ABORT_TASK		1
+#define ISCSI_TM_FUNC_ABORT_TASK_SET		2
+#define ISCSI_TM_FUNC_CLEAR_ACA			3
+#define ISCSI_TM_FUNC_CLEAR_TASK_SET		4
+#define ISCSI_TM_FUNC_LOGICAL_UNIT_RESET	5
+#define ISCSI_TM_FUNC_TARGET_WARM_RESET		6
+#define ISCSI_TM_FUNC_TARGET_COLD_RESET		7
+#define ISCSI_TM_FUNC_TASK_REASSIGN		8
+
+/* SCSI Task Management Response Header */
+struct iscsi_tm_rsp {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t response;	/* see Response values below */
+	uint8_t qualifier;
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t rsvd2[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	itt_t	 rtt;	/* Reference Task Tag */
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	uint8_t rsvd3[12];
+};
+
+/* Response values */
+#define ISCSI_TMF_RSP_COMPLETE		0x00
+#define ISCSI_TMF_RSP_NO_TASK		0x01
+#define ISCSI_TMF_RSP_NO_LUN		0x02
+#define ISCSI_TMF_RSP_TASK_ALLEGIANT	0x03
+#define ISCSI_TMF_RSP_NO_FAILOVER	0x04
+#define ISCSI_TMF_RSP_NOT_SUPPORTED	0x05
+#define ISCSI_TMF_RSP_AUTH_FAILED	0x06
+#define ISCSI_TMF_RSP_REJECTED		0xff
+
+/* Ready To Transfer Header */
+struct iscsi_r2t_rsp {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd2[2];
+	uint8_t	hlength;
+	uint8_t	dlength[3];
+	uint8_t lun[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be32	ttt;	/* Target Transfer Tag */
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	__be32	r2tsn;
+	__be32	data_offset;
+	__be32	data_length;
+};
+
+/* SCSI Data Hdr */
+struct iscsi_data {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd2[2];
+	uint8_t rsvd3;
+	uint8_t dlength[3];
+	uint8_t lun[8];
+	itt_t	 itt;
+	__be32	ttt;
+	__be32	rsvd4;
+	__be32	exp_statsn;
+	__be32	rsvd5;
+	__be32	datasn;
+	__be32	offset;
+	__be32	rsvd6;
+	/* Payload */
+};
+
+/* SCSI Data Response Hdr */
+struct iscsi_data_rsp {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd2;
+	uint8_t cmd_status;
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t lun[8];
+	itt_t	 itt;
+	__be32	ttt;
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	__be32	datasn;
+	__be32	offset;
+	__be32	residual_count;
+};
+
+/* Data Response PDU flags */
+#define ISCSI_FLAG_DATA_ACK		0x40
+#define ISCSI_FLAG_DATA_OVERFLOW	0x04
+#define ISCSI_FLAG_DATA_UNDERFLOW	0x02
+#define ISCSI_FLAG_DATA_STATUS		0x01
+
+/* Text Header */
+struct iscsi_text {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd2[2];
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t rsvd4[8];
+	itt_t	 itt;
+	__be32	ttt;
+	__be32	cmdsn;
+	__be32	exp_statsn;
+	uint8_t rsvd5[16];
+	/* Text - key=value pairs */
+};
+
+#define ISCSI_FLAG_TEXT_CONTINUE	0x40
+
+/* Text Response Header */
+struct iscsi_text_rsp {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd2[2];
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t rsvd4[8];
+	itt_t	 itt;
+	__be32	ttt;
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	uint8_t rsvd5[12];
+	/* Text Response - key:value pairs */
+};
+
+/* Login Header */
+struct iscsi_login {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t max_version;	/* Max. version supported */
+	uint8_t min_version;	/* Min. version supported */
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t isid[6];	/* Initiator Session ID */
+	__be16	tsih;	/* Target Session Handle */
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be16	cid;
+	__be16	rsvd3;
+	__be32	cmdsn;
+	__be32	exp_statsn;
+	uint8_t rsvd5[16];
+};
+
+/* Login PDU flags */
+#define ISCSI_FLAG_LOGIN_TRANSIT		0x80
+#define ISCSI_FLAG_LOGIN_CONTINUE		0x40
+#define ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK	0x0C	/* 2 bits */
+#define ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK	0x03	/* 2 bits */
+
+#define ISCSI_LOGIN_CURRENT_STAGE(flags) \
+	((flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2)
+#define ISCSI_LOGIN_NEXT_STAGE(flags) \
+	(flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK)
+
+/* Login Response Header */
+struct iscsi_login_rsp {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t max_version;	/* Max. version supported */
+	uint8_t active_version;	/* Active version */
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t isid[6];	/* Initiator Session ID */
+	__be16	tsih;	/* Target Session Handle */
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be32	rsvd3;
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	uint8_t status_class;	/* see Login RSP ststus classes below */
+	uint8_t status_detail;	/* see Login RSP Status details below */
+	uint8_t rsvd4[10];
+};
+
+/* Login stage (phase) codes for CSG, NSG */
+#define ISCSI_INITIAL_LOGIN_STAGE		-1
+#define ISCSI_SECURITY_NEGOTIATION_STAGE	0
+#define ISCSI_OP_PARMS_NEGOTIATION_STAGE	1
+#define ISCSI_FULL_FEATURE_PHASE		3
+
+/* Login Status response classes */
+#define ISCSI_STATUS_CLS_SUCCESS		0x00
+#define ISCSI_STATUS_CLS_REDIRECT		0x01
+#define ISCSI_STATUS_CLS_INITIATOR_ERR		0x02
+#define ISCSI_STATUS_CLS_TARGET_ERR		0x03
+
+/* Login Status response detail codes */
+/* Class-0 (Success) */
+#define ISCSI_LOGIN_STATUS_ACCEPT		0x00
+
+/* Class-1 (Redirection) */
+#define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP	0x01
+#define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM	0x02
+
+/* Class-2 (Initiator Error) */
+#define ISCSI_LOGIN_STATUS_INIT_ERR		0x00
+#define ISCSI_LOGIN_STATUS_AUTH_FAILED		0x01
+#define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN	0x02
+#define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND	0x03
+#define ISCSI_LOGIN_STATUS_TGT_REMOVED		0x04
+#define ISCSI_LOGIN_STATUS_NO_VERSION		0x05
+#define ISCSI_LOGIN_STATUS_ISID_ERROR		0x06
+#define ISCSI_LOGIN_STATUS_MISSING_FIELDS	0x07
+#define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED	0x08
+#define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE	0x09
+#define ISCSI_LOGIN_STATUS_NO_SESSION		0x0a
+#define ISCSI_LOGIN_STATUS_INVALID_REQUEST	0x0b
+
+/* Class-3 (Target Error) */
+#define ISCSI_LOGIN_STATUS_TARGET_ERROR		0x00
+#define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE	0x01
+#define ISCSI_LOGIN_STATUS_NO_RESOURCES		0x02
+
+/* Logout Header */
+struct iscsi_logout {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd1[2];
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t rsvd2[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be16	cid;
+	uint8_t rsvd3[2];
+	__be32	cmdsn;
+	__be32	exp_statsn;
+	uint8_t rsvd4[16];
+};
+
+/* Logout PDU flags */
+#define ISCSI_FLAG_LOGOUT_REASON_MASK	0x7F
+
+/* logout reason_code values */
+
+#define ISCSI_LOGOUT_REASON_CLOSE_SESSION	0
+#define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION	1
+#define ISCSI_LOGOUT_REASON_RECOVERY		2
+#define ISCSI_LOGOUT_REASON_AEN_REQUEST		3
+
+/* Logout Response Header */
+struct iscsi_logout_rsp {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t response;	/* see Logout response values below */
+	uint8_t rsvd2;
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t rsvd3[8];
+	itt_t	 itt;	/* Initiator Task Tag */
+	__be32	rsvd4;
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	__be32	rsvd5;
+	__be16	t2wait;
+	__be16	t2retain;
+	__be32	rsvd6;
+};
+
+/* logout response status values */
+
+#define ISCSI_LOGOUT_SUCCESS			0
+#define ISCSI_LOGOUT_CID_NOT_FOUND		1
+#define ISCSI_LOGOUT_RECOVERY_UNSUPPORTED	2
+#define ISCSI_LOGOUT_CLEANUP_FAILED		3
+
+/* SNACK Header */
+struct iscsi_snack {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t rsvd2[14];
+	itt_t	 itt;
+	__be32	begrun;
+	__be32	runlength;
+	__be32	exp_statsn;
+	__be32	rsvd3;
+	__be32	exp_datasn;
+	uint8_t rsvd6[8];
+};
+
+/* SNACK PDU flags */
+#define ISCSI_FLAG_SNACK_TYPE_MASK	0x0F	/* 4 bits */
+
+/* Reject Message Header */
+struct iscsi_reject {
+	uint8_t opcode;
+	uint8_t flags;
+	uint8_t reason;
+	uint8_t rsvd2;
+	uint8_t hlength;
+	uint8_t dlength[3];
+	uint8_t rsvd3[8];
+	__be32  ffffffff;
+	uint8_t rsvd4[4];
+	__be32	statsn;
+	__be32	exp_cmdsn;
+	__be32	max_cmdsn;
+	__be32	datasn;
+	uint8_t rsvd5[8];
+	/* Text - Rejected hdr */
+};
+
+/* Reason for Reject */
+#define ISCSI_REASON_CMD_BEFORE_LOGIN	1
+#define ISCSI_REASON_DATA_DIGEST_ERROR	2
+#define ISCSI_REASON_DATA_SNACK_REJECT	3
+#define ISCSI_REASON_PROTOCOL_ERROR	4
+#define ISCSI_REASON_CMD_NOT_SUPPORTED	5
+#define ISCSI_REASON_IMM_CMD_REJECT		6
+#define ISCSI_REASON_TASK_IN_PROGRESS	7
+#define ISCSI_REASON_INVALID_SNACK		8
+#define ISCSI_REASON_BOOKMARK_INVALID	9
+#define ISCSI_REASON_BOOKMARK_NO_RESOURCES	10
+#define ISCSI_REASON_NEGOTIATION_RESET	11
+
+/* Max. number of Key=Value pairs in a text message */
+#define MAX_KEY_VALUE_PAIRS	8192
+
+/* maximum length for text keys/values */
+#define KEY_MAXLEN		64
+#define VALUE_MAXLEN		255
+#define TARGET_NAME_MAXLEN	VALUE_MAXLEN
+
+#define ISCSI_DEF_MAX_RECV_SEG_LEN		8192
+#define ISCSI_MIN_MAX_RECV_SEG_LEN		512
+#define ISCSI_MAX_MAX_RECV_SEG_LEN		16777215
+
+#define ISCSI_DEF_FIRST_BURST_LEN		65536
+#define ISCSI_MIN_FIRST_BURST_LEN		512
+#define ISCSI_MAX_FIRST_BURST_LEN		16777215
+
+#define ISCSI_DEF_MAX_BURST_LEN			262144
+#define ISCSI_MIN_MAX_BURST_LEN			512
+#define ISCSI_MAX_MAX_BURST_LEN			16777215
+
+#define ISCSI_DEF_TIME2WAIT			2
+
+/************************* RFC 3720 End *****************************/
+
+#endif /* ISCSI_PROTO_H */
diff --git a/include/scsi/libiscsi2.h b/include/scsi/libiscsi2.h
new file mode 100644
index 0000000..44ac169
--- /dev/null
+++ b/include/scsi/libiscsi2.h
@@ -0,0 +1,434 @@
+/*
+ * iSCSI lib definitions
+ *
+ * Copyright (C) 2006 Red Hat, Inc.  All rights reserved.
+ * Copyright (C) 2004 - 2006 Mike Christie
+ * Copyright (C) 2004 - 2005 Dmitry Yusupov
+ * Copyright (C) 2004 - 2005 Alex Aizman
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#ifndef LIBISCSI_H
+#define LIBISCSI_H
+
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/mutex.h>
+#include <scsi/iscsi_proto2.h>
+#include <scsi/iscsi_if2.h>
+#include <scsi/scsi_transport_iscsi2.h>
+
+struct scsi_transport_template;
+struct scsi_host_template;
+struct scsi_device;
+struct Scsi_Host;
+struct scsi_cmnd;
+struct socket;
+struct iscsi_transport;
+struct iscsi_cls_session;
+struct iscsi_cls_conn;
+struct iscsi_session;
+struct iscsi_nopin;
+struct device;
+
+#define ISCSI_DEF_XMIT_CMDS_MAX	128	/* must be power of 2 */
+#define ISCSI_MGMT_CMDS_MAX	15
+
+#define ISCSI_DEF_CMD_PER_LUN	32
+
+/* Task Mgmt states */
+enum {
+	TMF_INITIAL,
+	TMF_QUEUED,
+	TMF_SUCCESS,
+	TMF_FAILED,
+	TMF_TIMEDOUT,
+	TMF_NOT_FOUND,
+};
+
+/* Connection suspend "bit" */
+#define ISCSI_SUSPEND_BIT		1
+
+#define ISCSI_ITT_MASK			0x1fff
+#define ISCSI_TOTAL_CMDS_MAX		4096
+/* this must be a power of two greater than ISCSI_MGMT_CMDS_MAX */
+#define ISCSI_TOTAL_CMDS_MIN		16
+#define ISCSI_AGE_SHIFT			28
+#define ISCSI_AGE_MASK			0xf
+
+#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),
+};
+
+
+enum {
+	ISCSI_TASK_COMPLETED,
+	ISCSI_TASK_PENDING,
+	ISCSI_TASK_RUNNING,
+};
+
+struct iscsi_r2t_info {
+	__be32			ttt;		/* copied from R2T */
+	__be32			exp_statsn;	/* copied from R2T */
+	uint32_t		data_length;	/* copied from R2T */
+	uint32_t		data_offset;	/* copied from R2T */
+	int			data_count;	/* DATA-Out payload progress */
+	int			datasn;
+	/* LLDs should set/update these values */
+	int			sent;		/* R2T sequence progress */
+};
+
+struct iscsi_task {
+	/*
+	 * Because LLDs allocate their hdr differently, this is a pointer
+	 * and length to that storage. It must be setup at session
+	 * creation time.
+	 */
+	struct iscsi_hdr	*hdr;
+	unsigned short		hdr_max;
+	unsigned short		hdr_len;	/* accumulated size of hdr used */
+	/* copied values in case we need to send tmfs */
+	itt_t			hdr_itt;
+	__be32			cmdsn;
+	uint8_t			lun[8];
+
+	int			itt;		/* this ITT */
+
+	unsigned		imm_count;	/* imm-data (bytes)   */
+	/* offset in unsolicited stream (bytes); */
+	struct iscsi_r2t_info	unsol_r2t;
+	char			*data;		/* mgmt payload */
+	unsigned		data_count;
+	struct scsi_cmnd	*sc;		/* associated SCSI cmd*/
+	struct iscsi_conn	*conn;		/* used connection    */
+
+	/* state set/tested under session->lock */
+	int			state;
+	atomic_t		refcount;
+	struct list_head	running;	/* running cmd list */
+	void			*dd_data;	/* driver/transport data */
+};
+
+static inline int iscsi_task_has_unsol_data(struct iscsi_task *task)
+{
+	return task->unsol_r2t.data_length > task->unsol_r2t.sent;
+}
+
+static inline void* iscsi_next_hdr(struct iscsi_task *task)
+{
+	return (void*)task->hdr + task->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 */
+	struct iscsi_session	*session;	/* parent session */
+	/*
+	 * 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_task 	*ping_task;
+
+	/* iSCSI connection-wide sequencing */
+	uint32_t		exp_statsn;
+
+	/* control data */
+	int			id;		/* CID */
+	int			c_stage;	/* connection state */
+	/*
+	 * Preallocated buffer for pdus that have data but do not
+	 * originate from scsi-ml. We never have two pdus using the
+	 * buffer at the same time. It is only allocated to
+	 * the default max recv size because the pdus we support
+	 * should always fit in this buffer
+	 */
+	char			*data;
+	struct iscsi_task 	*login_task;	/* mtask used for login/text */
+	struct iscsi_task	*task;		/* xmit task in progress */
+
+	/* xmit */
+	struct list_head	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 */
+	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.*/
+
+	/* negotiated params */
+	unsigned		max_recv_dlength; /* initiator_max_recv_dsl*/
+	unsigned		max_xmit_dlength; /* target_max_recv_dsl */
+	int			hdrdgst_en;
+	int			datadgst_en;
+	int			ifmarker_en;
+	int			ofmarker_en;
+	/* 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];
+
+	/* MIB-statistics */
+	uint64_t		txdata_octets;
+	uint64_t		rxdata_octets;
+	uint32_t		scsicmd_pdus_cnt;
+	uint32_t		dataout_pdus_cnt;
+	uint32_t		scsirsp_pdus_cnt;
+	uint32_t		datain_pdus_cnt;
+	uint32_t		r2t_pdus_cnt;
+	uint32_t		tmfcmd_pdus_cnt;
+	int32_t			tmfrsp_pdus_cnt;
+
+	/* custom statistics */
+	uint32_t		eh_abort_cnt;
+	uint32_t		fmr_unalign_cnt;
+};
+
+struct iscsi_pool {
+	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 {
+	struct iscsi_cls_session *cls_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			imm_data_en;
+	unsigned		first_burst;
+	unsigned		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			*ifacename;
+	char			*initiatorname;
+	/* control data */
+	struct iscsi_transport	*tt;
+	struct Scsi_Host	*host;
+	struct iscsi_conn	*leadconn;	/* leading connection */
+	spinlock_t		lock;		/* protects session state, *
+						 * sequence numbers,       *
+						 * session resources:      *
+						 * - cmdpool,		   *
+						 * - mgmtpool,		   *
+						 * - r2tpool		   */
+	int			state;		/* session state           */
+	int			age;		/* counts session re-opens */
+
+	int			scsi_cmds_max; 	/* max scsi commands */
+	int			cmds_max;	/* size of cmds array */
+	struct iscsi_task	**cmds;		/* Original Cmds arr */
+	struct iscsi_pool	cmdpool;	/* PDU's pool */
+};
+
+enum {
+	ISCSI_HOST_SETUP,
+	ISCSI_HOST_REMOVED,
+};
+
+struct iscsi_host {
+	char			*initiatorname;
+	/* hw address or netdev iscsi connection is bound to */
+	char			*hwaddress;
+	char			*netdev;
+	/* local address */
+	int			local_port;
+	char			local_address[ISCSI_ADDRESS_BUF_LEN];
+
+	wait_queue_head_t	session_removal_wq;
+	/* protects sessions and state */
+	spinlock_t		lock;
+	int			num_sessions;
+	int			state;
+
+	struct workqueue_struct	*workq;
+	char			workq_name[20];
+};
+
+/*
+ * scsi host template
+ */
+extern int iscsi2_change_queue_depth(struct scsi_device *sdev, int depth);
+extern int iscsi2_eh_abort(struct scsi_cmnd *sc);
+extern int iscsi2_eh_target_reset(struct scsi_cmnd *sc);
+extern int iscsi2_target_reset(struct iscsi_cls_session *cls_session);
+extern int iscsi2_eh_device_reset(struct scsi_cmnd *sc);
+extern int iscsi2_queuecommand(struct scsi_cmnd *sc,
+			      void (*done)(struct scsi_cmnd *));
+
+/*
+ * iSCSI host helpers.
+ */
+#define iscsi_host_priv(_shost) \
+	(shost_priv(_shost) + sizeof(struct iscsi_host))
+
+extern int iscsi2_host_set_param(struct Scsi_Host *shost,
+				enum iscsi_host_param param, char *buf,
+				int buflen);
+extern int iscsi2_host_get_param(struct Scsi_Host *shost,
+				enum iscsi_host_param param, char *buf);
+extern int iscsi2_host_add(struct Scsi_Host *shost, struct device *pdev);
+extern struct Scsi_Host *iscsi2_host_alloc(struct scsi_host_template *sht,
+					  int dd_data_size,
+					  bool xmit_can_sleep);
+extern void iscsi2_host_remove(struct Scsi_Host *shost);
+extern void iscsi2_host_free(struct Scsi_Host *shost);
+
+/*
+ * session management
+ */
+extern struct iscsi_cls_session *
+iscsi2_session_setup(struct iscsi_transport *, struct Scsi_Host *shost,
+		    uint16_t, int, uint32_t, unsigned int);
+extern void iscsi2_session_teardown(struct iscsi_cls_session *);
+extern void iscsi2_session_recovery_timedout(struct iscsi_cls_session *);
+extern int iscsi2_set_param(struct iscsi_cls_conn *cls_conn,
+			   enum iscsi_param param, char *buf, int buflen);
+extern int iscsi2_session_get_param(struct iscsi_cls_session *cls_session,
+				   enum iscsi_param param, char *buf);
+
+#define iscsi_session_printk(prefix, _sess, fmt, a...)	\
+	iscsi_cls_session_printk(prefix, _sess->cls_session, fmt, ##a)
+
+/*
+ * connection management
+ */
+extern struct iscsi_cls_conn *iscsi2_conn_setup(struct iscsi_cls_session *,
+					       int, uint32_t);
+extern void iscsi2_conn_teardown(struct iscsi_cls_conn *);
+extern int iscsi2_conn_start(struct iscsi_cls_conn *);
+extern void iscsi2_conn_stop(struct iscsi_cls_conn *, int);
+extern int iscsi2_conn_bind(struct iscsi_cls_session *, struct iscsi_cls_conn *,
+			   int);
+extern void iscsi2_conn_failure(struct iscsi_conn *conn, enum iscsi_err err);
+extern void iscsi2_session_failure(struct iscsi_session *session,
+				  enum iscsi_err err);
+extern int iscsi2_conn_get_param(struct iscsi_cls_conn *cls_conn,
+				enum iscsi_param param, char *buf);
+extern void iscsi2_suspend_tx(struct iscsi_conn *conn);
+extern void iscsi_conn_queue_work(struct iscsi_conn *conn);
+
+#define iscsi_conn_printk(prefix, _c, fmt, a...) \
+	iscsi_cls_conn_printk(prefix, ((struct iscsi_conn *)_c)->cls_conn, \
+			      fmt, ##a)
+
+/*
+ * pdu and task processing
+ */
+extern void iscsi2_update_cmdsn(struct iscsi_session *, struct iscsi_nopin *);
+extern void iscsi2_prep_data_out_pdu(struct iscsi_task *task,
+				    struct iscsi_r2t_info *r2t,
+				    struct iscsi_data *hdr);
+extern int iscsi2_conn_send_pdu(struct iscsi_cls_conn *, struct iscsi_hdr *,
+				char *, uint32_t);
+extern int iscsi2_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *,
+			      char *, int);
+extern int __iscsi2_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *,
+				char *, int);
+extern int iscsi2_verify_itt(struct iscsi_conn *, itt_t);
+extern struct iscsi_task *iscsi2_itt_to_ctask(struct iscsi_conn *, itt_t);
+extern void iscsi2_requeue_task(struct iscsi_task *task);
+extern void iscsi2_put_task(struct iscsi_task *task);
+extern void __iscsi2_get_task(struct iscsi_task *task);
+
+/*
+ * generic helpers
+ */
+extern void iscsi2_pool_free(struct iscsi_pool *);
+extern int iscsi2_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;
+}
+
+#endif
diff --git a/include/scsi/libiscsi_tcp.h b/include/scsi/libiscsi_tcp.h
new file mode 100644
index 0000000..f90e65b
--- /dev/null
+++ b/include/scsi/libiscsi_tcp.h
@@ -0,0 +1,133 @@
+/*
+ * iSCSI over TCP/IP Data-Path lib
+ *
+ * Copyright (C) 2008 Mike Christie
+ * Copyright (C) 2008 Red Hat, Inc.  All rights reserved.
+ * maintained by open-iscsi@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * See the file COPYING included with this distribution for more details.
+ */
+
+#ifndef LIBISCSI_TCP_H
+#define LIBISCSI_TCP_H
+
+#include <scsi/libiscsi2.h>
+
+struct iscsi_tcp_conn;
+struct iscsi_segment;
+struct sk_buff;
+struct hash_desc;
+
+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		padbuf[ISCSI_PAD_LEN];
+	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];
+
+	/* copied and flipped values */
+	int			datalen;
+};
+
+struct iscsi_tcp_conn {
+	struct iscsi_conn	*iscsi_conn;
+	void			*dd_data;
+	int			stop_stage;	/* conn_stop() flag: *
+						 * stop to recover,  *
+						 * stop to terminate */
+	/* control data */
+	struct iscsi_tcp_recv	in;		/* TCP receive context */
+	/* CRC32C (Rx) LLD should set this is they do not offload */
+	struct hash_desc	*rx_hash;
+};
+
+struct iscsi_tcp_task {
+	uint32_t		exp_datasn;	/* expected target's R2TSN/DataSN */
+	int			data_offset;
+	struct iscsi_r2t_info	*r2t;		/* in progress solict R2T */
+	struct iscsi_pool	r2tpool;
+	struct kfifo		*r2tqueue;
+	void			*dd_data;
+};
+
+enum {
+	ISCSI_TCP_SEGMENT_DONE,		/* curr seg has been processed */
+	ISCSI_TCP_SKB_DONE,		/* skb is out of data */
+	ISCSI_TCP_CONN_ERR,		/* iscsi layer has fired a conn err */
+	ISCSI_TCP_SUSPENDED,		/* conn is suspended */
+};
+
+extern void iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn);
+extern int iscsi_tcp_recv_skb(struct iscsi_conn *conn, struct sk_buff *skb,
+			      unsigned int offset, bool offloaded, int *status);
+extern void iscsi_tcp_cleanup_task(struct iscsi_task *task);
+extern int iscsi_tcp_task_init(struct iscsi_task *task);
+extern int iscsi_tcp_task_xmit(struct iscsi_task *task);
+
+/* segment helpers */
+extern int iscsi_tcp_recv_segment_is_hdr(struct iscsi_tcp_conn *tcp_conn);
+extern int iscsi_tcp_segment_done(struct iscsi_tcp_conn *tcp_conn,
+				  struct iscsi_segment *segment, int recv,
+				  unsigned copied);
+extern void iscsi_tcp_segment_unmap(struct iscsi_segment *segment);
+
+extern void iscsi_segment_init_linear(struct iscsi_segment *segment,
+				      void *data, size_t size,
+				      iscsi_segment_done_fn_t *done,
+				      struct hash_desc *hash);
+extern 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);
+
+/* digest helpers */
+extern void iscsi_tcp_dgst_header(struct hash_desc *hash, const void *hdr,
+				  size_t hdrlen,
+				  unsigned char digest[ISCSI_DIGEST_SIZE]);
+extern struct iscsi_cls_conn *
+iscsi_tcp_conn_setup(struct iscsi_cls_session *cls_session, int dd_data_size,
+		     uint32_t conn_idx);
+extern void iscsi_tcp_conn_teardown(struct iscsi_cls_conn *cls_conn);
+
+/* misc helpers */
+extern int iscsi_tcp_r2tpool_alloc(struct iscsi_session *session);
+extern void iscsi_tcp_r2tpool_free(struct iscsi_session *session);
+
+extern void iscsi_tcp_conn_get_stats(struct iscsi_cls_conn *cls_conn,
+				     struct iscsi_stats *stats);
+#endif /* LIBISCSI_TCP_H */
diff --git a/include/scsi/scsi_transport_iscsi2.h b/include/scsi/scsi_transport_iscsi2.h
new file mode 100644
index 0000000..42b5e64
--- /dev/null
+++ b/include/scsi/scsi_transport_iscsi2.h
@@ -0,0 +1,254 @@
+/*
+ * iSCSI transport class definitions
+ *
+ * Copyright (C) IBM Corporation, 2004
+ * Copyright (C) Mike Christie, 2004 - 2006
+ * Copyright (C) Dmitry Yusupov, 2004 - 2005
+ * Copyright (C) Alex Aizman, 2004 - 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#ifndef SCSI_TRANSPORT_ISCSI_H
+#define SCSI_TRANSPORT_ISCSI_H
+
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/version.h>
+#include <linux/mutex.h>
+#include <scsi/iscsi_if2.h>
+
+struct scsi_transport_template;
+struct iscsi_transport;
+struct iscsi_endpoint;
+struct Scsi_Host;
+struct iscsi_cls_conn;
+struct iscsi_conn;
+struct iscsi_task;
+struct sockaddr;
+
+/**
+ * struct iscsi_transport - iSCSI Transport template
+ *
+ * @name:		transport name
+ * @caps:		iSCSI Data-Path capabilities
+ * @create_session:	create new iSCSI session object
+ * @destroy_session:	destroy existing iSCSI session object
+ * @create_conn:	create new iSCSI connection
+ * @bind_conn:		associate this connection with existing iSCSI session
+ *			and specified transport descriptor
+ * @destroy_conn:	destroy inactive iSCSI connection
+ * @set_param:		set iSCSI parameter. Return 0 on success, -ENODATA
+ *			when param is not supported, and a -Exx value on other
+ *			error.
+ * @get_param		get iSCSI parameter. Must return number of bytes
+ *			copied to buffer on success, -ENODATA when param
+ *			is not supported, and a -Exx value on other error
+ * @start_conn:		set connection to be operational
+ * @stop_conn:		suspend/recover/terminate connection
+ * @send_pdu:		send iSCSI PDU, Login, Logout, NOP-Out, Reject, Text.
+ * @session_recovery_timedout: notify LLD a block during recovery timed out
+ * @init_task:		Initialize a iscsi_task and any internal structs.
+ *			When offloading the data path, this is called from
+ *			queuecommand with the session lock, or from the
+ *			iscsi_conn_send_pdu context with the session lock.
+ *			When not offloading the data path, this is called
+ *			from the scsi work queue without the session lock.
+ * @xmit_task		Requests LLD to transfer cmd task. Returns 0 or the
+ *			the number of bytes transferred on success, and -Exyz
+ *			value on error. When offloading the data path, this
+ *			is called from queuecommand with the session lock, or
+ *			from the iscsi_conn_send_pdu context with the session
+ *			lock. When not offloading the data path, this is called
+ *			from the scsi work queue without the session lock.
+ * @cleanup_task:	requests LLD to fail task. Called with session lock
+ *			and after the connection has been suspended and
+ *			terminated during recovery. If called
+ *			from abort task then connection is not suspended
+ *			or terminated but sk_callback_lock is held
+ *
+ * Template API provided by iSCSI Transport
+ */
+struct iscsi_transport {
+	struct module *owner;
+	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;
+	struct iscsi_cls_session *(*create_session) (struct iscsi_endpoint *ep,
+					uint16_t cmds_max, uint16_t qdepth,
+					uint32_t sn);
+	void (*destroy_session) (struct iscsi_cls_session *session);
+	struct iscsi_cls_conn *(*create_conn) (struct iscsi_cls_session *sess,
+				uint32_t cid);
+	int (*bind_conn) (struct iscsi_cls_session *session,
+			  struct iscsi_cls_conn *cls_conn,
+			  uint64_t transport_eph, int is_leading);
+	int (*start_conn) (struct iscsi_cls_conn *conn);
+	void (*stop_conn) (struct iscsi_cls_conn *conn, int flag);
+	void (*destroy_conn) (struct iscsi_cls_conn *conn);
+	int (*set_param) (struct iscsi_cls_conn *conn, enum iscsi_param param,
+			  char *buf, int buflen);
+	int (*get_conn_param) (struct iscsi_cls_conn *conn,
+			       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_task) (struct iscsi_task *task);
+	int (*xmit_task) (struct iscsi_task *task);
+	void (*cleanup_task) (struct iscsi_task *task);
+
+	int (*alloc_pdu) (struct iscsi_task *task, uint8_t opcode);
+	int (*xmit_pdu) (struct iscsi_task *task);
+	int (*init_pdu) (struct iscsi_task *task, unsigned int offset,
+			 unsigned int count);
+	void (*parse_pdu_itt) (struct iscsi_conn *conn, itt_t itt,
+			       int *index, int *age);
+
+	void (*session_recovery_timedout) (struct iscsi_cls_session *session);
+	struct iscsi_endpoint *(*ep_connect) (struct Scsi_Host *shost,
+					      struct sockaddr *dst_addr,
+					      int non_blocking);
+	int (*ep_poll) (struct iscsi_endpoint *ep, int timeout_ms);
+	void (*ep_disconnect) (struct iscsi_endpoint *ep);
+	int (*tgt_dscvr) (struct Scsi_Host *shost, enum iscsi_tgt_dscvr type,
+			  uint32_t enable, struct sockaddr *dst_addr);
+};
+
+/*
+ * transport registration upcalls
+ */
+extern struct scsi_transport_template *iscsi2_register_transport(struct iscsi_transport *tt);
+extern int iscsi2_unregister_transport(struct iscsi_transport *tt);
+
+/*
+ * control plane upcalls
+ */
+extern void iscsi2_conn_error_event(struct iscsi_cls_conn *conn,
+				    enum iscsi_err error);
+extern int iscsi2_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr,
+			   char *data, uint32_t data_size);
+
+struct iscsi_cls_conn {
+	struct list_head conn_list;	/* item in connlist */
+	void *dd_data;			/* LLD private data */
+	struct iscsi_transport *transport;
+	uint32_t cid;			/* connection id */
+
+	int active;			/* must be accessed with the connlock */
+	struct device dev;		/* sysfs transport/container device */
+};
+
+#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,
+};
+
+#define ISCSI_MAX_TARGET -1
+
+struct iscsi_cls_session {
+	struct list_head sess_list;		/* item in session_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;
+	struct work_struct recovery_work;
+
+	unsigned int target_id;
+
+	int state;
+	int sid;				/* session id */
+	void *dd_data;				/* LLD private data */
+	struct device dev;	/* sysfs transport/container device */
+};
+
+#define iscsi_dev_to_session(_dev) \
+	container_of(_dev, struct iscsi_cls_session, dev)
+
+#define iscsi_session_to_shost(_session) \
+	dev_to_shost(_session->dev.parent)
+
+#define starget_to_session(_stgt) \
+	iscsi_dev_to_session(_stgt->dev.parent)
+
+struct iscsi_cls_host {
+	atomic_t nr_scans;
+	struct mutex mutex;
+};
+
+extern void iscsi2_host_for_each_session(struct Scsi_Host *shost,
+				void (*fn)(struct iscsi_cls_session *));
+
+struct iscsi_endpoint {
+	void *dd_data;			/* LLD private data */
+	struct class_device dev;
+	uint64_t id;
+};
+
+/*
+ * 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 iscsi2_session_chkready(struct iscsi_cls_session *session);
+extern struct iscsi_cls_session *iscsi2_alloc_session(struct Scsi_Host *shost,
+				struct iscsi_transport *transport, int dd_size);
+extern int iscsi2_add_session(struct iscsi_cls_session *session,
+			      unsigned int target_id);
+extern int iscsi2_session_event(struct iscsi_cls_session *session,
+			        enum iscsi_uevent_e event);
+extern struct iscsi_cls_session *iscsi2_create_session(struct Scsi_Host *shost,
+						struct iscsi_transport *t,
+						int dd_size,
+						unsigned int target_id);
+extern void iscsi2_remove_session(struct iscsi_cls_session *session);
+extern void iscsi2_free_session(struct iscsi_cls_session *session);
+extern int iscsi2_destroy_session(struct iscsi_cls_session *session);
+extern struct iscsi_cls_conn *iscsi2_create_conn(struct iscsi_cls_session *sess,
+						int dd_size, uint32_t cid);
+extern int iscsi2_destroy_conn(struct iscsi_cls_conn *conn);
+extern void iscsi2_unblock_session(struct iscsi_cls_session *session);
+extern void iscsi2_block_session(struct iscsi_cls_session *session);
+extern struct iscsi_endpoint *iscsi2_create_endpoint(int dd_size);
+extern void iscsi2_destroy_endpoint(struct iscsi_endpoint *ep);
+extern struct iscsi_endpoint *iscsi2_lookup_endpoint(u64 handle);
+
+#endif