Patchwork [KARMIC] UBUNTU: Upgrade iscsitarget source from 0.4.17 to 1.4.19

login
register
mail settings
Submitter Manoj Iyer
Date Dec. 4, 2009, 2:35 a.m.
Message ID <alpine.DEB.2.00.0912032015250.2754@hungry>
Download mbox | patch
Permalink /patch/40290/
State Accepted
Delegated to: Andy Whitcroft
Headers show

Comments

Manoj Iyer - Dec. 4, 2009, 2:35 a.m.
upgraded iscsitarget code under kernel/ubuntu from v0.4.17 to v1.4.19, I 
had to make changes to the Makefile and patch file-io.c (submitted patch 
to iscsi target devel list) inorder to get it to build. I have a test 
kernel in http://people.canonical.com/~manjo/lucid/iscsi/


The following changes since commit 
764f8a95fa7a713916e7bf9520f2315676a6998d:
   Manoj Iyer (1):
         UBUNTU: Upgrade iscsitarget source from 0.4.17 to 1.4.19

are available in the git repository at:


ssh://zinc.canonical.com/srv/kernel.ubuntu.com/git/manjo/ubuntu-lucid.git 
iscsitarget

From 764f8a95fa7a713916e7bf9520f2315676a6998d Mon Sep 17 00:00:00 2001
From: Manoj Iyer <manoj.iyer@canonical.com>
Date: Thu, 3 Dec 2009 19:54:20 -0600
Subject: [PATCH] UBUNTU: Upgrade iscsitarget source from 0.4.17 to 1.4.19

ExternalDriver: iscsitarget
Description: iSCSI storage system on Linux
Url: svn://svn.berlios.de/iscsitarget/trunk
Mask:
Version: 1.4.19

Signed-off-by: Manoj Iyer <manoj.iyer@canonical.com>
---
  ubuntu/Makefile                    |    2 +-
  ubuntu/iscsitarget/Makefile        |    2 +-
  ubuntu/iscsitarget/config.c        |    8 +
  ubuntu/iscsitarget/conn.c          |   11 ++
  ubuntu/iscsitarget/file-io.c       |    2 +-
  ubuntu/iscsitarget/include/iet_u.h |   12 ++-
  ubuntu/iscsitarget/iscsi.c         |  326 +++++++++++++++++++++++++-----------
  ubuntu/iscsitarget/iscsi.h         |   64 +++++++-
  ubuntu/iscsitarget/iscsi_dbg.h     |    7 +
  ubuntu/iscsitarget/iscsi_hdr.h     |    4 +-
  ubuntu/iscsitarget/nthread.c       |   67 +++++++-
  ubuntu/iscsitarget/param.c         |   11 +-
  ubuntu/iscsitarget/session.c       |   56 ++++++-
  ubuntu/iscsitarget/target.c        |   61 +++++--
  ubuntu/iscsitarget/target_disk.c   |  165 +++++++++++++-----
  ubuntu/iscsitarget/ua.c            |  164 ++++++++++++++++++
  16 files changed, 780 insertions(+), 182 deletions(-)
  create mode 100644 ubuntu/iscsitarget/ua.c

Patch

diff --git a/ubuntu/Makefile b/ubuntu/Makefile
index 60242f0..a51cb77 100644
--- a/ubuntu/Makefile
+++ b/ubuntu/Makefile
@@ -8,7 +8,7 @@  obj-$(CONFIG_AUFS_FS)		+= aufs/
  obj-$(CONFIG_BLK_DEV_COMPCACHE)	+= compcache/
  obj-$(CONFIG_DM_RAID45)		+= dm-raid4-5/
  #obj-$(CONFIG_BLK_DEV_DRBD)	+= drbd/
-#obj-$(CONFIG_SCSI_ISCSITARGET)	+= iscsitarget/
+obj-$(CONFIG_SCSI_ISCSITARGET)	+= iscsitarget/
  obj-$(CONFIG_LENOVO_SL_LAPTOP)	+= lenovo-sl-laptop/
  obj-$(CONFIG_LIRC_DEV)          += lirc/
  obj-m				+= misc/
diff --git a/ubuntu/iscsitarget/Makefile b/ubuntu/iscsitarget/Makefile
index b54a26b..727c706 100644
--- a/ubuntu/iscsitarget/Makefile
+++ b/ubuntu/iscsitarget/Makefile
@@ -13,5 +13,5 @@  obj-m		+= iscsi_trgt.o
  iscsi_trgt-objs	:= tio.o iscsi.o nthread.o wthread.o config.o digest.o \
  			conn.o session.o target.o volume.o iotype.o \
  			file-io.o null-io.o target_disk.o event.o param.o \
-			block-io.o
+			block-io.o ua.o

diff --git a/ubuntu/iscsitarget/config.c b/ubuntu/iscsitarget/config.c
index 71fc6d5..51331fb 100644
--- a/ubuntu/iscsitarget/config.c
+++ b/ubuntu/iscsitarget/config.c
@@ -307,8 +307,16 @@  done:
  	return err;
  }

+static int release(struct inode *i __attribute__((unused)),
+		   struct file *f __attribute__((unused)))
+{
+	target_del_all();
+	return 0;
+}
+
  struct file_operations ctr_fops = {
  	.owner		= THIS_MODULE,
  	.unlocked_ioctl	= ioctl,
  	.compat_ioctl	= ioctl,
+	.release	= release
  };
diff --git a/ubuntu/iscsitarget/conn.c b/ubuntu/iscsitarget/conn.c
index 360a7a0..2c89304 100644
--- a/ubuntu/iscsitarget/conn.c
+++ b/ubuntu/iscsitarget/conn.c
@@ -158,6 +158,7 @@  int conn_free(struct iscsi_conn *conn)
  	list_del(&conn->list);
  	list_del(&conn->poll_list);

+	del_timer_sync(&conn->nop_timer);
  	digest_cleanup(conn);
  	kfree(conn);

@@ -192,6 +193,7 @@  static int iet_conn_alloc(struct iscsi_session *session, struct conn_info *info)
  	INIT_LIST_HEAD(&conn->pdu_list);
  	INIT_LIST_HEAD(&conn->write_list);
  	INIT_LIST_HEAD(&conn->poll_list);
+	init_timer(&conn->nop_timer);

  	list_add(&conn->list, &session->conn_list);

@@ -240,3 +242,12 @@  int conn_del(struct iscsi_session *session, struct conn_info *info)

  	return 0;
  }
+
+/* target_lock() supposed to be held */
+void conn_del_all(struct iscsi_session *session)
+{
+	struct iscsi_conn *conn;
+
+	list_for_each_entry(conn, &session->conn_list, list)
+		conn_close(conn);
+}
diff --git a/ubuntu/iscsitarget/file-io.c b/ubuntu/iscsitarget/file-io.c
index dbf7b1c..4eb2025 100644
--- a/ubuntu/iscsitarget/file-io.c
+++ b/ubuntu/iscsitarget/file-io.c
@@ -88,7 +88,7 @@  static int fileio_sync(struct iet_volume *lu, struct tio *tio)
  		count = lu->blk_cnt << lu->blk_shift;
  	}

-	res = sync_page_range(inode, mapping, ppos, count);
+	res = generic_write_sync(p->filp, ppos, count);
  	if (res) {
  		eprintk("I/O error: syncing pages failed: %d\n", res);
  		return -EIO;
diff --git a/ubuntu/iscsitarget/include/iet_u.h b/ubuntu/iscsitarget/include/iet_u.h
index 9730b10..620b3c4 100644
--- a/ubuntu/iscsitarget/include/iet_u.h
+++ b/ubuntu/iscsitarget/include/iet_u.h
@@ -1,7 +1,7 @@ 
  #ifndef _IET_U_H
  #define _IET_U_H

-#define IET_VERSION_STRING	"0.4.17"
+#define IET_VERSION_STRING	"1.4.19"

  /* The maximum length of 223 bytes in the RFC. */
  #define ISCSI_NAME_LEN	256
@@ -81,6 +81,8 @@  enum {
  	key_wthreads,
  	key_target_type,
  	key_queued_cmnds,
+	key_nop_interval,
+	key_nop_timeout,
  	target_key_last,
  };

@@ -119,6 +121,14 @@  struct iet_event {
  #define	MIN_NR_QUEUED_CMNDS	1
  #define	MAX_NR_QUEUED_CMNDS	256

+#define DEFAULT_NOP_INTERVAL 	0
+#define MIN_NOP_INTERVAL 	0
+#define MAX_NOP_INTERVAL 	90
+
+#define	DEFAULT_NOP_TIMEOUT 	0
+#define MIN_NOP_TIMEOUT 	0
+#define MAX_NOP_TIMEOUT		90
+
  #define NETLINK_IET	21

  #define ADD_TARGET _IOW('i', 0, struct target_info)
diff --git a/ubuntu/iscsitarget/iscsi.c b/ubuntu/iscsitarget/iscsi.c
index 79e73b0..4f62b8b 100644
--- a/ubuntu/iscsitarget/iscsi.c
+++ b/ubuntu/iscsitarget/iscsi.c
@@ -1,5 +1,6 @@ 
  /*
   * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ * Copyright (C) 2008 Arne Redlich <agr@powerkom-dd.de>
   *
   * Released under the terms of the GNU GPL v2.0.
   */
@@ -139,7 +140,7 @@  static void iscsi_scsi_dequeuecmnd(struct iscsi_cmnd *cmnd)
  /**
   * create a new command.
   *
- * iscsi_cmnd_create - 
+ * iscsi_cmnd_create -
   * @conn: ptr to connection (for i/o)
   *
   * @return    ptr to command or NULL
@@ -173,7 +174,7 @@  struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *conn, int req)
  /**
   * create a new command used as response.
   *
- * iscsi_cmnd_create_rsp_cmnd - 
+ * iscsi_cmnd_create_rsp_cmnd -
   * @cmnd: ptr to request command
   *
   * @return    ptr to response command or NULL
@@ -302,6 +303,7 @@  static struct iscsi_cmnd *create_scsi_rsp(struct iscsi_cmnd *req)
  	struct iscsi_cmnd *rsp;
  	struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
  	struct iscsi_scsi_rsp_hdr *rsp_hdr;
+	struct iscsi_sense_data *sense;

  	rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);

@@ -309,99 +311,93 @@  static struct iscsi_cmnd *create_scsi_rsp(struct iscsi_cmnd *req)
  	rsp_hdr->opcode = ISCSI_OP_SCSI_RSP;
  	rsp_hdr->flags = ISCSI_FLG_FINAL;
  	rsp_hdr->response = ISCSI_RESPONSE_COMMAND_COMPLETED;
-	rsp_hdr->cmd_status = SAM_STAT_GOOD;
+	rsp_hdr->cmd_status = req->status;
  	rsp_hdr->itt = req_hdr->itt;

+	if (req->status == SAM_STAT_CHECK_CONDITION) {
+		assert(!rsp->tio);
+		rsp->tio = tio_alloc(1);
+		sense = (struct iscsi_sense_data *)
+			page_address(rsp->tio->pvec[0]);
+
+		assert(sense);
+		clear_page(sense);
+		sense->length = cpu_to_be16(IET_SENSE_BUF_SIZE);
+
+		memcpy(sense->data, req->sense_buf, IET_SENSE_BUF_SIZE);
+		rsp->pdu.datasize = sizeof(struct iscsi_sense_data) +
+			IET_SENSE_BUF_SIZE;
+
+		rsp->tio->size = (rsp->pdu.datasize + 3) & -4;
+		rsp->tio->offset = 0;
+	}
+
  	return rsp;
  }

-static struct iscsi_cmnd *create_sense_rsp(struct iscsi_cmnd *req,
-					   u8 sense_key, u8 asc, u8 ascq)
+void iscsi_cmnd_set_sense(struct iscsi_cmnd *cmnd, u8 sense_key, u8 asc,
+			  u8 ascq)
  {
-	struct iscsi_cmnd *rsp;
-	struct iscsi_scsi_rsp_hdr *rsp_hdr;
-	struct tio *tio;
-	struct iscsi_sense_data *sense;
+	cmnd->status = SAM_STAT_CHECK_CONDITION;

-	rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);
-
-	rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
-	rsp_hdr->opcode = ISCSI_OP_SCSI_RSP;
-	rsp_hdr->flags = ISCSI_FLG_FINAL;
-	rsp_hdr->response = ISCSI_RESPONSE_COMMAND_COMPLETED;
-	rsp_hdr->cmd_status = SAM_STAT_CHECK_CONDITION;
-	rsp_hdr->itt = cmnd_hdr(req)->itt;
-
-	tio = rsp->tio = tio_alloc(1);
-	sense = (struct iscsi_sense_data *) page_address(tio->pvec[0]);
-	assert(sense);
-	clear_page(sense);
-	sense->length = cpu_to_be16(14);
-	sense->data[0] = 0xf0;
-	sense->data[2] = sense_key;
-	sense->data[7] = 6;	// Additional sense length
-	sense->data[12] = asc;
-	sense->data[13] = ascq;
-
-	rsp->pdu.datasize = sizeof(struct iscsi_sense_data) + 14;
-	tio->size = (rsp->pdu.datasize + 3) & -4;
-	tio->offset = 0;
+	cmnd->sense_buf[0] = 0xf0;
+	cmnd->sense_buf[2] = sense_key;
+	cmnd->sense_buf[7] = 6;	// Additional sense length
+	cmnd->sense_buf[12] = asc;
+	cmnd->sense_buf[13] = ascq;
+}

-	return rsp;
+static struct iscsi_cmnd *create_sense_rsp(struct iscsi_cmnd *req,
+					   u8 sense_key, u8 asc, u8 ascq)
+{
+	iscsi_cmnd_set_sense(req, sense_key, asc, ascq);
+	return create_scsi_rsp(req);
  }

-void send_scsi_rsp(struct iscsi_cmnd *req, int (*func)(struct iscsi_cmnd *))
+void send_scsi_rsp(struct iscsi_cmnd *req, void (*func)(struct iscsi_cmnd *))
  {
  	struct iscsi_cmnd *rsp;
  	struct iscsi_scsi_rsp_hdr *rsp_hdr;
  	u32 size;
-	int ret = func(req);

-	switch (ret) {
-	case 0:
-	case -EBUSY:
-		rsp = create_scsi_rsp(req);
+	func(req);
+	rsp = create_scsi_rsp(req);
+
+	switch (req->status) {
+	case SAM_STAT_GOOD:
+	case SAM_STAT_RESERVATION_CONFLICT:
  		rsp_hdr = (struct iscsi_scsi_rsp_hdr *) &rsp->pdu.bhs;
  		if ((size = cmnd_read_size(req)) != 0) {
  			rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
  			rsp_hdr->residual_count = cpu_to_be32(size);
  		}
-		if (ret == -EBUSY)
-			rsp_hdr->cmd_status = SAM_STAT_RESERVATION_CONFLICT;
-		break;
-	case -EIO:
-		/* Medium Error/Write Fault */
-		rsp = create_sense_rsp(req, MEDIUM_ERROR, 0x03, 0x0);
  		break;
  	default:
-		rsp = create_sense_rsp(req, ILLEGAL_REQUEST, 0x24, 0x0);
+		break;
  	}
+
  	iscsi_cmnd_init_write(rsp);
  }

-void send_data_rsp(struct iscsi_cmnd *req, int (*func)(struct iscsi_cmnd *))
+void send_data_rsp(struct iscsi_cmnd *req, void (*func)(struct iscsi_cmnd *))
  {
  	struct iscsi_cmnd *rsp;

-	switch (func(req)) {
-	case 0:
+	func(req);
+
+	if (req->status == SAM_STAT_GOOD)
  		do_send_data_rsp(req);
-		return;
-	case -EIO:
-		/* Medium Error/Unrecovered Read Error */
-		rsp = create_sense_rsp(req, MEDIUM_ERROR, 0x11, 0x0);
-		break;
-	default:
-		rsp = create_sense_rsp(req, ILLEGAL_REQUEST, 0x24, 0x0);
+	else {
+		rsp = create_scsi_rsp(req);
+		iscsi_cmnd_init_write(rsp);
  	}
-	iscsi_cmnd_init_write(rsp);
  }

  /**
   * Free a command.
   * Also frees the additional header.
   *
- * iscsi_cmnd_remove - 
+ * iscsi_cmnd_remove -
   * @cmnd: ptr to command
   */

@@ -411,6 +407,12 @@  static void iscsi_cmnd_remove(struct iscsi_cmnd *cmnd)

  	if (!cmnd)
  		return;
+
+	if (cmnd_timer_active(cmnd)) {
+		clear_cmnd_timer_active(cmnd);
+		del_timer_sync(&cmnd->timer);
+	}
+
  	dprintk(D_GENERIC, "%p\n", cmnd);
  	conn = cmnd->conn;
  	kfree(cmnd->pdu.ahs);
@@ -570,7 +572,7 @@  static struct iscsi_cmnd *cmnd_find_hash(struct iscsi_session *session, u32 itt,
  	return cmnd;
  }

-static int cmnd_insert_hash(struct iscsi_cmnd *cmnd)
+static int cmnd_insert_hash_ttt(struct iscsi_cmnd *cmnd, u32 ttt)
  {
  	struct iscsi_session *session = cmnd->conn->session;
  	struct iscsi_cmnd *tmp;
@@ -578,17 +580,11 @@  static int cmnd_insert_hash(struct iscsi_cmnd *cmnd)
  	int err = 0;
  	u32 itt = cmnd->pdu.bhs.itt;

-	dprintk(D_GENERIC, "%p:%x\n", cmnd, itt);
-	if (itt == ISCSI_RESERVED_TAG) {
-		err = -ISCSI_REASON_PROTOCOL_ERROR;
-		goto out;
-	}
-
-	head = &session->cmnd_hash[cmnd_hashfn(cmnd->pdu.bhs.itt)];
+	head = &session->cmnd_hash[cmnd_hashfn(itt)];

  	spin_lock(&session->cmnd_hash_lock);

-	tmp = __cmnd_find_hash(session, itt, ISCSI_RESERVED_TAG);
+	tmp = __cmnd_find_hash(session, itt, ttt);
  	if (!tmp) {
  		list_add_tail(&cmnd->hash_list, head);
  		set_cmnd_hashed(cmnd);
@@ -597,12 +593,24 @@  static int cmnd_insert_hash(struct iscsi_cmnd *cmnd)

  	spin_unlock(&session->cmnd_hash_lock);

+	return err;
+}
+
+static int cmnd_insert_hash(struct iscsi_cmnd *cmnd)
+{
+	int err;
+
+	dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd->pdu.bhs.itt);
+
+	if (cmnd->pdu.bhs.itt == ISCSI_RESERVED_TAG)
+		return -ISCSI_REASON_PROTOCOL_ERROR;
+
+	err = cmnd_insert_hash_ttt(cmnd, ISCSI_RESERVED_TAG);
  	if (!err) {
  		update_stat_sn(cmnd);
  		err = check_cmd_sn(cmnd);
  	}

-out:
  	return err;
  }

@@ -618,8 +626,8 @@  static void cmnd_remove_hash(struct iscsi_cmnd *cmnd)

  	spin_lock(&session->cmnd_hash_lock);

-	tmp = __cmnd_find_hash(session, cmnd->pdu.bhs.itt, ISCSI_RESERVED_TAG);
-
+	tmp = __cmnd_find_hash(session, cmnd->pdu.bhs.itt,
+			       cmnd->target_task_tag);
  	if (tmp && tmp == cmnd)
  		__cmnd_remove_hash(tmp);
  	else
@@ -830,24 +838,34 @@  static void scsi_cmnd_exec(struct iscsi_cmnd *cmnd)
  	}
  }

-static int noop_out_start(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
+static int nop_out_start(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
  {
  	u32 size, tmp;
  	int i, err = 0;

  	if (cmnd_ttt(cmnd) != cpu_to_be32(ISCSI_RESERVED_TAG)) {
-		/*
-		 * We don't request a NOP-Out by sending a NOP-In.
-		 * See 10.18.2 in the draft 20.
-		 */
-		eprintk("initiator bug %x\n", cmnd_itt(cmnd));
-		err = -ISCSI_REASON_PROTOCOL_ERROR;
-		goto out;
+		cmnd->req = cmnd_find_hash(conn->session, cmnd->pdu.bhs.itt,
+					   cmnd->pdu.bhs.ttt);
+		if (!cmnd->req) {
+			/*
+			 * We didn't request this NOP-Out (by sending a
+			 * NOP-In, see 10.18.2 of the RFC) or our fake NOP-Out
+			 * timed out.
+			 */
+			eprintk("initiator bug %x\n", cmnd_itt(cmnd));
+			err = -ISCSI_REASON_PROTOCOL_ERROR;
+			goto out;
+		}
+
+		del_timer_sync(&cmnd->req->timer);
+		clear_cmnd_timer_active(cmnd->req);
+		dprintk(D_GENERIC, "NOP-Out: %p, ttt %x, timer %p\n",
+			cmnd->req, cmnd_ttt(cmnd->req), &cmnd->req->timer);
  	}

  	if (cmnd_itt(cmnd) == cpu_to_be32(ISCSI_RESERVED_TAG)) {
  		if (!(cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE))
-			eprintk("%s\n","initiator bug!");
+			eprintk("%s\n", "initiator bug!");
  		update_stat_sn(cmnd);
  		err = check_cmd_sn(cmnd);
  		if (err)
@@ -1159,7 +1177,7 @@  static int target_reset(struct iscsi_cmnd *req, u32 lun, int all)
  	struct iscsi_session *session;
  	struct iscsi_conn *conn;
  	struct iscsi_cmnd *cmnd, *tmp;
-	struct iet_volume *volumes;
+	struct iet_volume *volume;

  	list_for_each_entry(session, &target->session_list, list) {
  		list_for_each_entry(conn, &session->conn_list, list) {
@@ -1175,10 +1193,15 @@  static int target_reset(struct iscsi_cmnd *req, u32 lun, int all)
  		}
  	}

-	list_for_each_entry(volumes, &target->volumes, list)
-		if (all || volumes->lun == lun)
+	list_for_each_entry(volume, &target->volumes, list) {
+		if (all || volume->lun == lun) {
  			/* force release */
-			volume_release(volumes, 0, 1);
+			volume_release(volume, 0, 1);
+			/* power-on, reset, or bus device reset occurred */
+			ua_establish_for_all_sessions(target, volume->lun,
+						      0x29, 0x0);
+		}
+	}

  	return 0;
  }
@@ -1211,7 +1234,7 @@  static inline char *tmf_desc(int fun)
  		"Task Reassign",
          };

-	if ((fun < ISCSI_FUNCTION_ABORT_TASK) || 
+	if ((fun < ISCSI_FUNCTION_ABORT_TASK) ||
  				(fun > ISCSI_FUNCTION_TASK_REASSIGN))
  		fun = 0;

@@ -1313,19 +1336,24 @@  out:
  	iscsi_cmnd_init_write(rsp);
  }

-static void noop_out_exec(struct iscsi_cmnd *req)
+static void nop_hdr_setup(struct iscsi_hdr *hdr, u8 opcode, __be32 itt,
+			  __be32 ttt)
+{
+	hdr->opcode = opcode;
+	hdr->flags = ISCSI_FLG_FINAL;
+	hdr->itt = itt;
+	hdr->ttt = ttt;
+}
+
+static void nop_out_exec(struct iscsi_cmnd *req)
  {
  	struct iscsi_cmnd *rsp;
-	struct iscsi_nop_in_hdr *rsp_hdr;

  	if (cmnd_itt(req) != cpu_to_be32(ISCSI_RESERVED_TAG)) {
  		rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);

-		rsp_hdr = (struct iscsi_nop_in_hdr *)&rsp->pdu.bhs;
-		rsp_hdr->opcode = ISCSI_OP_NOOP_IN;
-		rsp_hdr->flags = ISCSI_FLG_FINAL;
-		rsp_hdr->itt = req->pdu.bhs.itt;
-		rsp_hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);
+		nop_hdr_setup(&rsp->pdu.bhs, ISCSI_OP_NOP_IN, req->pdu.bhs.itt,
+			      cpu_to_be32(ISCSI_RESERVED_TAG));

  		if (req->pdu.datasize)
  			assert(req->tio);
@@ -1340,8 +1368,86 @@  static void noop_out_exec(struct iscsi_cmnd *req)
  		assert(get_pgcnt(req->pdu.datasize, 0) < ISCSI_CONN_IOV_MAX);
  		rsp->pdu.datasize = req->pdu.datasize;
  		iscsi_cmnd_init_write(rsp);
-	} else
+	} else {
+		if (req->req) {
+			dprintk(D_GENERIC, "releasing NOP-Out %p, ttt %x; "
+				"removing NOP-In %p, ttt %x\n", req->req,
+				cmnd_ttt(req->req), req, cmnd_ttt(req));
+			cmnd_release(req->req, 0);
+		}
  		iscsi_cmnd_remove(req);
+	}
+}
+
+static void nop_in_timeout(unsigned long data)
+{
+	struct iscsi_cmnd *req = (struct iscsi_cmnd *)data;
+
+	printk(KERN_INFO "NOP-In ping timed out - closing sid:cid %llu:%u\n",
+	       req->conn->session->sid, req->conn->cid);
+	clear_cmnd_timer_active(req);
+	conn_close(req->conn);
+}
+
+/* create a fake NOP-Out req and treat the NOP-In as our rsp to it */
+void send_nop_in(struct iscsi_conn *conn)
+{
+	struct iscsi_cmnd *req = cmnd_alloc(conn, 1);
+	struct iscsi_cmnd *rsp = iscsi_cmnd_create_rsp_cmnd(req, 0);
+
+	req->target_task_tag = get_next_ttt(conn->session);
+
+
+	nop_hdr_setup(&req->pdu.bhs, ISCSI_OP_NOP_OUT,
+		      cpu_to_be32(ISCSI_RESERVED_TAG), req->target_task_tag);
+	nop_hdr_setup(&rsp->pdu.bhs, ISCSI_OP_NOP_IN,
+		      cpu_to_be32(ISCSI_RESERVED_TAG), req->target_task_tag);
+
+	dprintk(D_GENERIC, "NOP-Out: %p, ttt %x, timer %p; "
+		"NOP-In: %p, ttt %x;\n", req, cmnd_ttt(req), &req->timer, rsp,
+		cmnd_ttt(rsp));
+
+	init_timer(&req->timer);
+	req->timer.data = (unsigned long)req;
+	req->timer.function = nop_in_timeout;
+
+	if (cmnd_insert_hash_ttt(req, req->target_task_tag)) {
+		eprintk("%s\n",
+			"failed to insert fake NOP-Out into hash table");
+		cmnd_release(rsp, 0);
+		cmnd_release(req, 0);
+	} else
+		iscsi_cmnd_init_write(rsp);
+}
+
+static void nop_in_tx_end(struct iscsi_cmnd *cmnd)
+{
+	struct iscsi_conn *conn = cmnd->conn;
+	u32 t;
+
+	if (cmnd->pdu.bhs.ttt == cpu_to_be32(ISCSI_RESERVED_TAG))
+		return;
+
+	/*
+	 * NOP-In ping issued by the target.
+	 * FIXME: Sanitize the NOP timeout earlier, during configuration
+	 */
+	t = conn->session->target->trgt_param.nop_timeout;
+
+	if (!t || t > conn->session->target->trgt_param.nop_interval) {
+		eprintk("Adjusting NOPTimeout of tid %u from %u to %u "
+			"(== NOPInterval)\n", conn->session->target->tid,
+			t,
+			conn->session->target->trgt_param.nop_interval);
+		t = conn->session->target->trgt_param.nop_interval;
+		conn->session->target->trgt_param.nop_timeout = t;
+	}
+
+	dprintk(D_GENERIC, "NOP-In %p, %x: timer %p\n",	cmnd, cmnd_ttt(cmnd),
+		&cmnd->req->timer);
+
+	set_cmnd_timer_active(cmnd->req);
+	mod_timer(&cmnd->req->timer, jiffies + HZ * t);
  }

  static void logout_exec(struct iscsi_cmnd *req)
@@ -1365,8 +1471,8 @@  static void iscsi_cmnd_exec(struct iscsi_cmnd *cmnd)
  	dprintk(D_GENERIC, "%p,%x,%u\n", cmnd, cmnd_opcode(cmnd), cmnd->pdu.bhs.sn);

  	switch (cmnd_opcode(cmnd)) {
-	case ISCSI_OP_NOOP_OUT:
-		noop_out_exec(cmnd);
+	case ISCSI_OP_NOP_OUT:
+		nop_out_exec(cmnd);
  		break;
  	case ISCSI_OP_SCSI_CMD:
  		scsi_cmnd_exec(cmnd);
@@ -1491,8 +1597,15 @@  void cmnd_tx_start(struct iscsi_cmnd *cmnd)
  	conn->write_size = sizeof(cmnd->pdu.bhs);

  	switch (cmnd_opcode(cmnd)) {
-	case ISCSI_OP_NOOP_IN:
-		cmnd_set_sn(cmnd, 1);
+	case ISCSI_OP_NOP_IN:
+		if (cmnd->pdu.bhs.itt == ISCSI_RESERVED_TAG) {
+			/* NOP-In ping generated by us. Don't advance StatSN. */
+			cmnd_set_sn(cmnd, 0);
+			cmnd_set_sn(cmnd->req, 0);
+			cmnd->pdu.bhs.sn = cpu_to_be32(conn->stat_sn);
+			cmnd->req->pdu.bhs.sn = cpu_to_be32(conn->stat_sn);
+		} else
+			cmnd_set_sn(cmnd, 1);
  		cmnd_send_pdu(conn, cmnd);
  		break;
  	case ISCSI_OP_SCSI_RSP:
@@ -1547,7 +1660,9 @@  void cmnd_tx_end(struct iscsi_cmnd *cmnd)

  	dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
  	switch (cmnd_opcode(cmnd)) {
-	case ISCSI_OP_NOOP_IN:
+	case ISCSI_OP_NOP_IN:
+		nop_in_tx_end(cmnd);
+		break;
  	case ISCSI_OP_SCSI_RSP:
  	case ISCSI_OP_SCSI_TASK_MGT_RSP:
  	case ISCSI_OP_TEXT_RSP:
@@ -1575,7 +1690,7 @@  void cmnd_tx_end(struct iscsi_cmnd *cmnd)
   * This functions reorders the commands.
   * Called from the read thread.
   *
- * iscsi_session_push_cmnd - 
+ * iscsi_session_push_cmnd -
   * @cmnd: ptr to command
   */

@@ -1662,8 +1777,8 @@  void cmnd_rx_start(struct iscsi_cmnd *cmnd)
  		return;

  	switch (cmnd_opcode(cmnd)) {
-	case ISCSI_OP_NOOP_OUT:
-		err = noop_out_start(conn, cmnd);
+	case ISCSI_OP_NOP_OUT:
+		err = nop_out_start(conn, cmnd);
  		break;
  	case ISCSI_OP_SCSI_CMD:
  		if (!(err = cmnd_insert_hash(cmnd)))
@@ -1705,7 +1820,7 @@  void cmnd_rx_end(struct iscsi_cmnd *cmnd)
  	dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
  	switch (cmnd_opcode(cmnd)) {
  	case ISCSI_OP_SCSI_REJECT:
-	case ISCSI_OP_NOOP_OUT:
+	case ISCSI_OP_NOP_OUT:
  	case ISCSI_OP_SCSI_CMD:
  	case ISCSI_OP_SCSI_TASK_MGT_MSG:
  	case ISCSI_OP_TEXT_CMD:
@@ -1744,6 +1859,8 @@  static void iscsi_exit(void)

  	iotype_exit();

+	ua_exit();
+
  	if (iscsi_cmnd_cache)
  		kmem_cache_destroy(iscsi_cmnd_cache);
  }
@@ -1769,6 +1886,10 @@  static int iscsi_init(void)
  	if (!iscsi_cmnd_cache)
  		goto err;

+	err = ua_init();
+	if (err < 0)
+		goto err;
+
  	if ((err = tio_init()) < 0)
  		goto err;

@@ -1797,4 +1918,7 @@  MODULE_PARM_DESC(debug_enable_flags,
  module_init(iscsi_init);
  module_exit(iscsi_exit);

+MODULE_VERSION(IET_VERSION_STRING);
  MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("iSCSI Enterprise Target");
+MODULE_AUTHOR("IET development team <iscsitarget-devel@lists.sourceforge.net>");
diff --git a/ubuntu/iscsitarget/iscsi.h b/ubuntu/iscsitarget/iscsi.h
index 89ff2c3..92ce252 100644
--- a/ubuntu/iscsitarget/iscsi.h
+++ b/ubuntu/iscsitarget/iscsi.h
@@ -1,5 +1,6 @@ 
  /*
   * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
+ * Copyright (C) 2008 Arne Redlich <agr@powerkom-dd.de>
   *
   * Released under the terms of the GNU GPL v2.0.
   */
@@ -7,6 +8,7 @@ 
  #ifndef __ISCSI_H__
  #define __ISCSI_H__

+#include <linux/completion.h>
  #include <linux/pagemap.h>
  #include <linux/seq_file.h>
  #include <linux/mm.h>
@@ -17,6 +19,8 @@ 
  #include "iscsi_hdr.h"
  #include "iet_u.h"

+#define IET_SENSE_BUF_SIZE      18
+
  struct iscsi_sess_param {
  	int initial_r2t;
  	int immediate_data;
@@ -43,6 +47,8 @@  struct iscsi_trgt_param {
  	int wthreads;
  	int target_type;
  	int queued_cmnds;
+	int nop_interval;
+	int nop_timeout;
  };

  struct tio {
@@ -113,11 +119,15 @@  struct iscsi_target {
  	struct list_head volumes;
  	struct list_head session_list;

+	/* Prevents races between add/del session and adding UAs */
+	spinlock_t session_list_lock;
+
  	struct network_thread_info nthread_info;
  	/* Points either to own list or global pool */
  	struct worker_thread_info * wthread_info;

  	struct semaphore target_sem;
+	struct completion *done;
  };

  struct iscsi_queue {
@@ -173,10 +183,12 @@  enum lu_flags {
  #define IET_HASH_ORDER		8
  #define	cmnd_hashfn(itt)	hash_long((itt), IET_HASH_ORDER)

+#define UA_HASH_LEN 8
+
  struct iscsi_session {
  	struct list_head list;
  	struct iscsi_target *target;
-
+	struct completion *done;
  	char *initiator;
  	u64 sid;

@@ -193,6 +205,9 @@  struct iscsi_session {
  	spinlock_t cmnd_hash_lock;
  	struct list_head cmnd_hash[1 << IET_HASH_ORDER];

+	spinlock_t ua_hash_lock;
+	struct list_head ua_hash[UA_HASH_LEN];
+
  	u32 next_ttt;
  };

@@ -200,6 +215,7 @@  enum connection_state_bit {
  	CONN_ACTIVE,
  	CONN_CLOSING,
  	CONN_WSPACE_WAIT,
+	CONN_NEED_NOP_IN,
  };

  #define ISCSI_CONN_IOV_MAX	(((256 << 10) >> PAGE_SHIFT) + 1)
@@ -226,6 +242,7 @@  struct iscsi_conn {
  	atomic_t nr_busy_cmnds;
  	struct list_head pdu_list;		/* in/outcoming pdus */
  	struct list_head write_list;		/* list of data pdus to be sent */
+	struct timer_list nop_timer;

  	struct iscsi_cmnd *read_cmnd;
  	struct msghdr read_msg;
@@ -270,6 +287,10 @@  struct iscsi_cmnd {

  	struct tio *tio;

+	u8 status;
+
+	struct timer_list timer;
+
  	u32 r2t_sn;
  	u32 r2t_length;
  	u32 is_unsolicited_data;
@@ -280,6 +301,16 @@  struct iscsi_cmnd {
  	u32 ddigest;

  	struct iscsi_cmnd *req;
+
+	unsigned char sense_buf[IET_SENSE_BUF_SIZE];
+};
+
+struct ua_entry {
+	struct list_head entry;
+	struct iscsi_session *session; /* only used for debugging ATM */
+	u32 lun;
+	u8 asc;
+	u8 ascq;
  };

  #define ISCSI_OP_SCSI_REJECT	ISCSI_OP_VENDOR1_CMD
@@ -295,13 +326,17 @@  extern void cmnd_rx_end(struct iscsi_cmnd *);
  extern void cmnd_tx_start(struct iscsi_cmnd *);
  extern void cmnd_tx_end(struct iscsi_cmnd *);
  extern void cmnd_release(struct iscsi_cmnd *, int);
-extern void send_data_rsp(struct iscsi_cmnd *, int (*)(struct iscsi_cmnd *));
-extern void send_scsi_rsp(struct iscsi_cmnd *, int (*)(struct iscsi_cmnd *));
+extern void send_data_rsp(struct iscsi_cmnd *, void (*)(struct iscsi_cmnd *));
+extern void send_scsi_rsp(struct iscsi_cmnd *, void (*)(struct iscsi_cmnd *));
+extern void iscsi_cmnd_set_sense(struct iscsi_cmnd *, u8 sense_key, u8 asc,
+				 u8 ascq);
+extern void send_nop_in(struct iscsi_conn *);

  /* conn.c */
  extern struct iscsi_conn *conn_lookup(struct iscsi_session *, u16);
  extern int conn_add(struct iscsi_session *, struct conn_info *);
  extern int conn_del(struct iscsi_session *, struct conn_info *);
+extern void conn_del_all(struct iscsi_session *);
  extern int conn_free(struct iscsi_conn *);
  extern void conn_close(struct iscsi_conn *);
  extern void conn_info_show(struct seq_file *, struct iscsi_session *);
@@ -329,6 +364,7 @@  extern void target_unlock(struct iscsi_target *);
  struct iscsi_target *target_lookup_by_id(u32);
  extern int target_add(struct target_info *);
  extern int target_del(u32 id);
+extern void target_del_all(void);
  extern struct seq_operations iet_seq_op;

  /* config.c */
@@ -341,6 +377,7 @@  extern struct file_operations session_seq_fops;
  extern struct iscsi_session *session_lookup(struct iscsi_target *, u64);
  extern int session_add(struct iscsi_target *, struct session_info *);
  extern int session_del(struct iscsi_target *, u64);
+extern void session_del_all(struct iscsi_target *);

  /* volume.c */
  extern struct file_operations volume_seq_fops;
@@ -380,6 +417,21 @@  extern int event_send(u32, u64, u32, u32, int);
  extern int event_init(void);
  extern void event_exit(void);

+/* ua.c */
+int ua_init(void);
+void ua_exit(void);
+struct ua_entry * ua_get_first(struct iscsi_session *, u32 lun);
+struct ua_entry * ua_get_match(struct iscsi_session *, u32 lun, u8 asc,
+			       u8 ascq);
+void ua_free(struct ua_entry *);
+int ua_pending(struct iscsi_session *, u32 lun);
+void ua_establish_for_session(struct iscsi_session *, u32 lun, u8 asc,
+			     u8 ascq);
+void ua_establish_for_other_sessions(struct iscsi_session *, u32 lun, u8 asc,
+				     u8 ascq);
+void ua_establish_for_all_sessions(struct iscsi_target *, u32 lun, u8 asc,
+				   u8 ascq);
+
  #define get_pgcnt(size, offset)	((((size) + ((offset) & ~PAGE_CACHE_MASK)) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT)

  static inline void iscsi_cmnd_get_length(struct iscsi_pdu *pdu)
@@ -425,6 +477,7 @@  enum cmnd_flags {
  	CMND_pending,
  	CMND_tmfabort,
  	CMND_rxstart,
+	CMND_timer_active,
  };

  #define set_cmnd_hashed(cmnd)	set_bit(CMND_hashed, &(cmnd)->flags)
@@ -455,6 +508,11 @@  enum cmnd_flags {
  #define set_cmnd_rxstart(cmnd)	set_bit(CMND_rxstart, &(cmnd)->flags)
  #define cmnd_rxstart(cmnd)	test_bit(CMND_rxstart, &(cmnd)->flags)

+#define set_cmnd_timer_active(cmnd)  set_bit(CMND_timer_active, &(cmnd)->flags)
+#define clear_cmnd_timer_active(cmnd) \
+	                        clear_bit(CMND_timer_active, &(cmnd)->flags)
+#define cmnd_timer_active(cmnd) test_bit(CMND_timer_active, &(cmnd)->flags)
+
  #define VENDOR_ID	"IET"
  #define PRODUCT_ID	"VIRTUAL-DISK"
  #define PRODUCT_REV	"0"
diff --git a/ubuntu/iscsitarget/iscsi_dbg.h b/ubuntu/iscsitarget/iscsi_dbg.h
index bc83b54..d8d5966 100644
--- a/ubuntu/iscsitarget/iscsi_dbg.h
+++ b/ubuntu/iscsitarget/iscsi_dbg.h
@@ -10,6 +10,7 @@ 
  #define D_THREAD	(1UL << 6)
  #define D_TASK_MGT	(1UL << 7)
  #define D_IOMODE	(1UL << 8)
+#define D_UAC           (1UL << 9)

  #define D_DATA		(D_READ | D_WRITE)

@@ -24,6 +25,12 @@  extern unsigned long debug_enable_flags;
  	}							\
  } while (0)

+#define dprintk_ua(ua, sess, lun)					\
+	dprintk(D_UAC, "sess %llu, lun %u: %p %x %x\n",			\
+		(sess)->sid, lun, ua,					\
+		(ua) ? (ua)->asc : 0,					\
+		(ua) ? (ua)->ascq : 0)
+
  #define eprintk(fmt, args...) do {				\
  	printk(KERN_ERR PFX "%s(%d) " fmt, __FUNCTION__,	\
  						__LINE__, args);\
diff --git a/ubuntu/iscsitarget/iscsi_hdr.h b/ubuntu/iscsitarget/iscsi_hdr.h
index 1233dd2..2cbcd4f 100644
--- a/ubuntu/iscsitarget/iscsi_hdr.h
+++ b/ubuntu/iscsitarget/iscsi_hdr.h
@@ -43,7 +43,7 @@  struct iscsi_hdr {
  #define ISCSI_OPCODE_MASK		0x3F

  /* Client to Server Message Opcode values */
-#define ISCSI_OP_NOOP_OUT		0x00
+#define ISCSI_OP_NOP_OUT		0x00
  #define ISCSI_OP_SCSI_CMD		0x01
  #define ISCSI_OP_SCSI_TASK_MGT_MSG	0x02
  #define ISCSI_OP_LOGIN_CMD		0x03
@@ -58,7 +58,7 @@  struct iscsi_hdr {
  #define ISCSI_OP_VENDOR4_CMD		0x1f

  /* Server to Client Message Opcode values */
-#define ISCSI_OP_NOOP_IN		0x20
+#define ISCSI_OP_NOP_IN 		0x20
  #define ISCSI_OP_SCSI_RSP		0x21
  #define ISCSI_OP_SCSI_TASK_MGT_RSP	0x22
  #define ISCSI_OP_LOGIN_RSP		0x23
diff --git a/ubuntu/iscsitarget/nthread.c b/ubuntu/iscsitarget/nthread.c
index ec0c3eb..ec54ead 100644
--- a/ubuntu/iscsitarget/nthread.c
+++ b/ubuntu/iscsitarget/nthread.c
@@ -1,6 +1,8 @@ 
  /*
   * Network thread.
   * (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
+ * (C) 2008 Arne Redlich <agr@powerkom-dd.de>
+ *
   * This code is licenced under the GPL.
   */

@@ -567,6 +569,45 @@  static int send(struct iscsi_conn *conn)
  	return 0;
  }

+static void conn_nop_timeout(unsigned long data)
+{
+	struct iscsi_conn *conn = (struct iscsi_conn *)data;
+
+	if (test_bit(CONN_ACTIVE, &conn->state))
+		set_bit(CONN_NEED_NOP_IN, &conn->state);
+
+	dprintk(D_THREAD, "conn %llu:%hu, NOP timer %p\n", conn->session->sid,
+		conn->cid, &conn->nop_timer);
+
+	nthread_wakeup(conn->session->target);
+}
+
+static void conn_reset_nop_timer(struct iscsi_conn *conn)
+{
+	struct iscsi_target *target = conn->session->target;
+
+	if (target->trgt_param.nop_interval)
+		mod_timer(&conn->nop_timer,
+			  jiffies + HZ * target->trgt_param.nop_interval);
+}
+
+static void conn_start_nop_timer(struct iscsi_conn *conn)
+{
+	struct iscsi_target *target = conn->session->target;
+
+	if (!target->trgt_param.nop_interval || timer_pending(&conn->nop_timer))
+		return;
+
+	conn->nop_timer.data = (unsigned long)conn;
+	conn->nop_timer.function = conn_nop_timeout;
+
+	dprintk(D_THREAD, "conn %llu:%hu, NOP timer %p\n", conn->session->sid,
+		conn->cid, &conn->nop_timer);
+
+	mod_timer(&conn->nop_timer,
+		  jiffies + HZ * target->trgt_param.nop_interval);
+}
+
  static void process_io(struct iscsi_conn *conn)
  {
  	struct iscsi_target *target = conn->session->target;
@@ -574,8 +615,10 @@  static void process_io(struct iscsi_conn *conn)

  	res = recv(conn);

-	if (is_data_available(conn) > 0 || res > 0)
+	if (is_data_available(conn) > 0 || res > 0) {
+		conn_reset_nop_timer(conn);
  		wakeup = 1;
+	}

  	if (!test_bit(CONN_ACTIVE, &conn->state)) {
  		wakeup = 1;
@@ -587,12 +630,19 @@  static void process_io(struct iscsi_conn *conn)

  	res = send(conn);

-	if (!list_empty(&conn->write_list) || conn->write_cmnd)
+	if (!list_empty(&conn->write_list) || conn->write_cmnd) {
+		conn_reset_nop_timer(conn);
  		wakeup = 1;
+	}

  out:
  	if (wakeup)
  		nthread_wakeup(target);
+	else if (test_and_clear_bit(CONN_NEED_NOP_IN, &conn->state)) {
+		send_nop_in(conn);
+		nthread_wakeup(target);
+	} else
+		conn_start_nop_timer(conn);

  	return;
  }
@@ -600,10 +650,11 @@  out:
  static void close_conn(struct iscsi_conn *conn)
  {
  	struct iscsi_session *session = conn->session;
-	struct iscsi_target *target = session->target;
+	struct iscsi_target *target = conn->session->target;
  	struct iscsi_cmnd *cmnd;

-	assert(conn);
+	if (target->trgt_param.nop_interval)
+		del_timer_sync(&conn->nop_timer);

  	conn->sock->ops->shutdown(conn->sock, 2);

@@ -637,8 +688,12 @@  static void close_conn(struct iscsi_conn *conn)
  	event_send(target->tid, session->sid, conn->cid, E_CONN_CLOSE, 0);
  	conn_free(conn);

-	if (list_empty(&session->conn_list))
-		session_del(target, session->sid);
+	if (list_empty(&session->conn_list)) {
+		if (session->done)
+			complete(session->done);
+		else
+			session_del(target, session->sid);
+	}
  }

  static int istd(void *arg)
diff --git a/ubuntu/iscsitarget/param.c b/ubuntu/iscsitarget/param.c
index 3292d4d..57ad301 100644
--- a/ubuntu/iscsitarget/param.c
+++ b/ubuntu/iscsitarget/param.c
@@ -116,7 +116,12 @@  static void trgt_param_check(struct iscsi_param_info *info)
  	CHECK_PARAM(info, iparam, wthreads, MIN_NR_WTHREADS, MAX_NR_WTHREADS);
  	CHECK_PARAM(info, iparam, target_type, 0,
  		    (unsigned int) ARRAY_SIZE(target_type_array) - 1);
-	CHECK_PARAM(info, iparam, queued_cmnds, MIN_NR_QUEUED_CMNDS, MAX_NR_QUEUED_CMNDS);
+	CHECK_PARAM(info, iparam, queued_cmnds, MIN_NR_QUEUED_CMNDS,
+		    MAX_NR_QUEUED_CMNDS);
+	CHECK_PARAM(info, iparam, nop_interval, MIN_NOP_INTERVAL,
+		    MAX_NOP_INTERVAL);
+	CHECK_PARAM(info, iparam, nop_timeout, MIN_NOP_TIMEOUT,
+		    MAX_NOP_TIMEOUT);
  }

  static void trgt_param_set(struct iscsi_target *target, struct iscsi_param_info *info)
@@ -130,6 +135,8 @@  static void trgt_param_set(struct iscsi_target *target, struct iscsi_param_info
  			      target->trgt_param.wthreads, target->tid);
  	SET_PARAM(param, info, iparam, target_type);
  	SET_PARAM(param, info, iparam, queued_cmnds);
+	SET_PARAM(param, info, iparam, nop_interval);
+	SET_PARAM(param, info, iparam, nop_timeout);
  }

  static void trgt_param_get(struct iscsi_trgt_param *param, struct iscsi_param_info *info)
@@ -139,6 +146,8 @@  static void trgt_param_get(struct iscsi_trgt_param *param, struct iscsi_param_in
  	GET_PARAM(param, info, iparam, wthreads);
  	GET_PARAM(param, info, iparam, target_type);
  	GET_PARAM(param, info, iparam, queued_cmnds);
+	GET_PARAM(param, info, iparam, nop_interval);
+	GET_PARAM(param, info, iparam, nop_timeout);
  }

  static int trgt_param(struct iscsi_target *target, struct iscsi_param_info *info, int set)
diff --git a/ubuntu/iscsitarget/session.c b/ubuntu/iscsitarget/session.c
index 1f1420e..6365373 100644
--- a/ubuntu/iscsitarget/session.c
+++ b/ubuntu/iscsitarget/session.c
@@ -23,6 +23,7 @@  iet_session_alloc(struct iscsi_target *target, struct session_info *info)
  {
  	int i;
  	struct iscsi_session *session;
+	struct iet_volume *vol;

  	dprintk(D_SETUP, "%p %u %#Lx\n", target, target->tid,
  		(unsigned long long) info->sid);
@@ -52,9 +53,19 @@  iet_session_alloc(struct iscsi_target *target, struct session_info *info)
  	for (i = 0; i < ARRAY_SIZE(session->cmnd_hash); i++)
  		INIT_LIST_HEAD(&session->cmnd_hash[i]);

+	spin_lock_init(&session->ua_hash_lock);
+	for (i = 0; i < ARRAY_SIZE(session->ua_hash); i++)
+		INIT_LIST_HEAD(&session->ua_hash[i]);
+
+	list_for_each_entry(vol, &target->volumes, list)
+		/* power-on, reset, or bus device reset occurred */
+		ua_establish_for_session(session, vol->lun, 0x29, 0x0);
+
  	session->next_ttt = 1;

+	spin_lock(&target->session_list_lock);
  	list_add(&session->list, &target->session_list);
+	spin_unlock(&target->session_list_lock);

  	return session;
  }
@@ -62,9 +73,14 @@  iet_session_alloc(struct iscsi_target *target, struct session_info *info)
  static int session_free(struct iscsi_session *session)
  {
  	int i;
+	struct ua_entry *ua, *tmp;
+	struct list_head *l;
+	struct iscsi_target *target = session->target;

  	dprintk(D_SETUP, "%#Lx\n", (unsigned long long) session->sid);

+	spin_lock(&target->session_list_lock);
+
  	assert(list_empty(&session->conn_list));

  	for (i = 0; i < ARRAY_SIZE(session->cmnd_hash); i++) {
@@ -72,28 +88,37 @@  static int session_free(struct iscsi_session *session)
  			BUG();
  	}

+	for (i = 0; i < ARRAY_SIZE(session->ua_hash); i++) {
+		l = &session->ua_hash[i];
+		list_for_each_entry_safe(ua, tmp, l, entry) {
+			list_del_init(&ua->entry);
+			ua_free(ua);
+		}
+	}
+
  	list_del(&session->list);

  	kfree(session->initiator);
  	kfree(session);

+	spin_unlock(&target->session_list_lock);
+
  	return 0;
  }

  int session_add(struct iscsi_target *target, struct session_info *info)
  {
  	struct iscsi_session *session;
-	int err = -EEXIST;

  	session = session_lookup(target, info->sid);
  	if (session)
-		return err;
+		return -EEXIST;

  	session = iet_session_alloc(target, info);
  	if (!session)
-		err = -ENOMEM;
+		return -ENOMEM;

-	return err;
+	return 0;
  }

  int session_del(struct iscsi_target *target, u64 sid)
@@ -112,6 +137,29 @@  int session_del(struct iscsi_target *target, u64 sid)
  	return session_free(session);
  }

+void session_del_all(struct iscsi_target *target)
+{
+	DECLARE_COMPLETION_ONSTACK(done);
+	struct iscsi_session *sess;
+
+	while (!list_empty(&target->session_list)) {
+		init_completion(&done);
+		target_lock(target, 0);
+		sess = list_entry(target->session_list.next, struct
+				  iscsi_session, list);
+		sess->done = &done;
+		conn_del_all(sess);
+		target_unlock(target);
+		wait_for_completion(&done);
+		target_lock(target, 0);
+		session_free(sess);
+		target_unlock(target);
+	}
+
+	if (target->done)
+		complete(target->done);
+}
+
  static void iet_session_info_show(struct seq_file *seq, struct iscsi_target *target)
  {
  	struct iscsi_session *session;
diff --git a/ubuntu/iscsitarget/target.c b/ubuntu/iscsitarget/target.c
index a0879b8..15c0715 100644
--- a/ubuntu/iscsitarget/target.c
+++ b/ubuntu/iscsitarget/target.c
@@ -158,6 +158,7 @@  static int iscsi_target_create(struct target_info *info, u32 tid)
  	strncpy(target->name, name, sizeof(target->name) - 1);

  	init_MUTEX(&target->target_sem);
+	spin_lock_init(&target->session_list_lock);

  	INIT_LIST_HEAD(&target->session_list);
  	INIT_LIST_HEAD(&target->volumes);
@@ -244,40 +245,66 @@  static void target_destroy(struct iscsi_target *target)
  	module_put(THIS_MODULE);
  }

-int target_del(u32 id)
+/* @locking: target_list_sem must be locked */
+int __target_del(struct iscsi_target *target)
  {
-	struct iscsi_target *target;
-	int err;
-
-	if ((err = down_interruptible(&target_list_sem)) < 0)
-		return err;
-
-	if (!(target = __target_lookup_by_id(id))) {
-		err = -ENOENT;
-		goto out;
-	}
-
  	target_lock(target, 0);

  	if (!list_empty(&target->session_list)) {
-		err = -EBUSY;
  		target_unlock(target);
-		goto out;
+		return -EBUSY;
  	}

  	list_del(&target->t_list);
  	nr_targets--;

  	target_unlock(target);
-	up(&target_list_sem);
-
  	target_destroy(target);
  	return 0;
-out:
+}
+
+int target_del(u32 id)
+{
+	struct iscsi_target *target;
+	int err = down_interruptible(&target_list_sem);
+	if (err < 0)
+		return err;
+
+	target = __target_lookup_by_id(id);
+	if (!target) {
+		err = -ENOENT;
+		goto out;
+	}
+
+	err = __target_del(target);
+ out:
  	up(&target_list_sem);
  	return err;
  }

+void target_del_all(void)
+{
+	DECLARE_COMPLETION_ONSTACK(done);
+	struct iscsi_target *target, *tmp;
+
+	down(&target_list_sem);
+
+	if (!list_empty(&target_list))
+		iprintk("Removing all connections, sessions and targets\n");
+
+	list_for_each_entry_safe(target, tmp, &target_list, t_list) {
+		init_completion(&done);
+		target->done = &done;
+		session_del_all(target);
+		wait_for_completion(&done);
+		__target_del(target);
+	}
+
+	next_target_id = 0;
+
+	up(&target_list_sem);
+}
+
  static void *iet_seq_start(struct seq_file *m, loff_t *pos)
  {
  	int err;
diff --git a/ubuntu/iscsitarget/target_disk.c b/ubuntu/iscsitarget/target_disk.c
index 4488bc5..694edb2 100644
--- a/ubuntu/iscsitarget/target_disk.c
+++ b/ubuntu/iscsitarget/target_disk.c
@@ -84,7 +84,7 @@  static int insert_geo_m_pg(u8 *ptr, u64 sec)
  	return sizeof(geo_m_pg);
  }

-static int build_mode_sense_response(struct iscsi_cmnd *cmnd)
+static void build_mode_sense_response(struct iscsi_cmnd *cmnd)
  {
  	struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
  	struct tio *tio = cmnd->tio;
@@ -94,7 +94,7 @@  static int build_mode_sense_response(struct iscsi_cmnd *cmnd)

  	/* changeable parameter mode pages are unsupported */
  	if ((scb[2] & 0xc0) >> 6 == 0x1)
-		return -1;
+		goto set_sense;

  	pcode = req->scb[2] & 0x3f;

@@ -152,14 +152,20 @@  static int build_mode_sense_response(struct iscsi_cmnd *cmnd)
  		err = -1;
  	}

-	data[0] = len - 1;
-
-	tio_set(tio, len, 0);
+	if (!err) {
+		data[0] = len - 1;
+		tio_set(tio, len, 0);
+		return;
+	}

-	return err;
+	tio_put(tio);
+	cmnd->tio = NULL;
+ set_sense:
+	/* Invalid Field In CDB */
+	iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0x0);
  }

-static int build_inquiry_response(struct iscsi_cmnd *cmnd)
+static void build_inquiry_response(struct iscsi_cmnd *cmnd)
  {
  	struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
  	struct tio *tio = cmnd->tio;
@@ -172,7 +178,7 @@  static int build_inquiry_response(struct iscsi_cmnd *cmnd)
  	 * - CmdDt set: not supported
  	 */
  	if ((scb[1] & 0x3) > 0x1 || (!(scb[1] & 0x3) && scb[2]))
-		return err;
+		goto set_sense;

  	assert(!tio);
  	tio = cmnd->tio = tio_alloc(1);
@@ -245,14 +251,21 @@  static int build_inquiry_response(struct iscsi_cmnd *cmnd)
  		}
  	}

-	tio_set(tio, min_t(u8, tio->size, scb[4]), 0);
-	if (!cmnd->lun)
-		data[0] = TYPE_NO_LUN;
+	if (!err) {
+		tio_set(tio, min_t(u8, tio->size, scb[4]), 0);
+		if (!cmnd->lun)
+			data[0] = TYPE_NO_LUN;
+		return;
+	}

-	return err;
+	tio_put(tio);
+	cmnd->tio = NULL;
+ set_sense:
+	/* Invalid Field In CDB */
+	iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0x0);
  }

-static int build_report_luns_response(struct iscsi_cmnd *cmnd)
+static void build_report_luns_response(struct iscsi_cmnd *cmnd)
  {
  	struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
  	struct tio *tio = cmnd->tio;
@@ -262,8 +275,11 @@  static int build_report_luns_response(struct iscsi_cmnd *cmnd)

  	size = (u32)req->scb[6] << 24 | (u32)req->scb[7] << 16 |
  		(u32)req->scb[8] << 8 | (u32)req->scb[9];
-	if (size < 16)
-		return -1;
+	if (size < 16) {
+		/* Invalid Field In CDB */
+		iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0x0);
+		return;
+	}

  	len = atomic_read(&cmnd->conn->session->target->nr_volumes) * 8;
  	size = min(size & ~(8 - 1), len + 8);
@@ -293,11 +309,9 @@  static int build_report_luns_response(struct iscsi_cmnd *cmnd)
  			rest = PAGE_CACHE_SIZE;
  		}
  	}
-
-	return 0;
  }

-static int build_read_capacity_response(struct iscsi_cmnd *cmnd)
+static void build_read_capacity_response(struct iscsi_cmnd *cmnd)
  {
  	struct tio *tio = cmnd->tio;
  	u32 *data;
@@ -313,10 +327,9 @@  static int build_read_capacity_response(struct iscsi_cmnd *cmnd)
  	data[1] = cpu_to_be32(1U << cmnd->lun->blk_shift);

  	tio_set(tio, 8, 0);
-	return 0;
  }

-static int build_request_sense_response(struct iscsi_cmnd *cmnd)
+static void build_request_sense_response(struct iscsi_cmnd *cmnd)
  {
  	struct tio *tio = cmnd->tio;
  	u8 *data;
@@ -331,11 +344,9 @@  static int build_request_sense_response(struct iscsi_cmnd *cmnd)
  	data[2] = NO_SENSE;
  	data[7] = 10;
  	tio_set(tio, 18, 0);
-
-	return 0;
  }

-static int build_service_action_in_response(struct iscsi_cmnd *cmnd)
+static void build_service_action_in_response(struct iscsi_cmnd *cmnd)
  {
  	struct tio *tio = cmnd->tio;
  	u32 *data;
@@ -344,8 +355,11 @@  static int build_service_action_in_response(struct iscsi_cmnd *cmnd)
  	assert(!tio);

  	/* only READ_CAPACITY_16 service action is currently supported */
-	if ((cmnd_hdr(cmnd)->scb[1] & 0x1F) != 0x10)
-		return -1;
+	if ((cmnd_hdr(cmnd)->scb[1] & 0x1F) != 0x10) {
+		/* Invalid Field In CDB */
+		iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0x0);
+		return;
+	}

  	tio = cmnd->tio = tio_alloc(1);
  	data = page_address(tio->pvec[0]);
@@ -356,20 +370,21 @@  static int build_service_action_in_response(struct iscsi_cmnd *cmnd)
  	data[2] = cpu_to_be32(1UL << cmnd->lun->blk_shift);

  	tio_set(tio, 12, 0);
-	return 0;
  }

-static int build_read_response(struct iscsi_cmnd *cmnd)
+static void build_read_response(struct iscsi_cmnd *cmnd)
  {
  	struct tio *tio = cmnd->tio;

  	assert(tio);
  	assert(cmnd->lun);

-	return tio_read(cmnd->lun, tio);
+	if (tio_read(cmnd->lun, tio))
+		/* Medium Error/Unrecovered Read Error */
+		iscsi_cmnd_set_sense(cmnd, MEDIUM_ERROR, 0x11, 0x0);
  }

-static int build_write_response(struct iscsi_cmnd *cmnd)
+static void build_write_response(struct iscsi_cmnd *cmnd)
  {
  	int err;
  	struct tio *tio = cmnd->tio;
@@ -382,41 +397,83 @@  static int build_write_response(struct iscsi_cmnd *cmnd)
  	if (!err && !LUWCache(cmnd->lun))
  		err = tio_sync(cmnd->lun, tio);

-	return err;
+	if (err)
+		/* Medium Error/Write Fault */
+		iscsi_cmnd_set_sense(cmnd, MEDIUM_ERROR, 0x03, 0x0);
  }

-static int build_sync_cache_response(struct iscsi_cmnd *cmnd)
+static void build_sync_cache_response(struct iscsi_cmnd *cmnd)
  {
  	assert(cmnd->lun);
-	return tio_sync(cmnd->lun, NULL);
+	if (tio_sync(cmnd->lun, NULL))
+		/* Medium Error/Write Fault */
+		iscsi_cmnd_set_sense(cmnd, MEDIUM_ERROR, 0x03, 0x0);
  }

-static int build_generic_response(struct iscsi_cmnd *cmnd)
+static void build_generic_response(struct iscsi_cmnd *cmnd)
  {
-	return 0;
+	return;
  }

-static int build_reserve_response(struct iscsi_cmnd *cmnd)
+static void build_reserve_response(struct iscsi_cmnd *cmnd)
  {
-	return volume_reserve(cmnd->lun, cmnd->conn->session->sid);
+	switch (volume_reserve(cmnd->lun, cmnd->conn->session->sid)) {
+	case -ENOENT:
+		/* Logical Unit Not Supported (?) */
+		iscsi_cmnd_set_sense(cmnd, ILLEGAL_REQUEST, 0x25, 0x0);
+		break;
+	case -EBUSY:
+		cmnd->status = SAM_STAT_RESERVATION_CONFLICT;
+		break;
+	default:
+		break;
+	}
  }

-static int build_release_response(struct iscsi_cmnd *cmnd)
+static void build_release_response(struct iscsi_cmnd *cmnd)
  {
-	return volume_release(cmnd->lun,
-			      cmnd->conn->session->sid, 0);
+	if (volume_release(cmnd->lun,
+			   cmnd->conn->session->sid, 0))
+		cmnd->status = SAM_STAT_RESERVATION_CONFLICT;
  }

-static int build_reservation_conflict_response(struct iscsi_cmnd *cmnd)
+static void build_reservation_conflict_response(struct iscsi_cmnd *cmnd)
  {
-	return -EBUSY;
+	cmnd->status = SAM_STAT_RESERVATION_CONFLICT;
  }

-static int disk_execute_cmnd(struct iscsi_cmnd *cmnd)
+static int disk_check_ua(struct iscsi_cmnd *cmnd)
  {
  	struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+	struct ua_entry *ua;

-	req->opcode &= ISCSI_OPCODE_MASK;
+	if (cmnd->lun && ua_pending(cmnd->conn->session, cmnd->lun->lun)) {
+		switch(req->scb[0]){
+		case INQUIRY:
+		case REQUEST_SENSE:
+			break;
+		case REPORT_LUNS:
+			ua = ua_get_match(cmnd->conn->session,
+					  cmnd->lun->lun,
+					  /* reported luns data has changed */
+					  0x3f, 0x0e);
+			ua_free(ua);
+			break;
+		default:
+			ua = ua_get_first(cmnd->conn->session, cmnd->lun->lun);
+			iscsi_cmnd_set_sense(cmnd, UNIT_ATTENTION, ua->asc,
+					     ua->ascq);
+			ua_free(ua);
+			send_scsi_rsp(cmnd, build_generic_response);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int disk_check_reservation(struct iscsi_cmnd *cmnd)
+{
+	struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);

  	if (is_volume_reserved(cmnd->lun,
  			       cmnd->conn->session->sid)) {
@@ -425,16 +482,36 @@  static int disk_execute_cmnd(struct iscsi_cmnd *cmnd)
  		case RELEASE:
  		case REPORT_LUNS:
  		case REQUEST_SENSE:
+		case READ_CAPACITY:
  			/* allowed commands when reserved */
  			break;
+		case SERVICE_ACTION_IN:
+			if ((cmnd_hdr(cmnd)->scb[1] & 0x1F) == 0x10)
+				break;
+			/* fall through */
  		default:
  			/* return reservation conflict for all others */
  			send_scsi_rsp(cmnd,
  				      build_reservation_conflict_response);
-			return 0;
+			return 1;
  		}
  	}

+	return 0;
+}
+
+static int disk_execute_cmnd(struct iscsi_cmnd *cmnd)
+{
+	struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
+
+	req->opcode &= ISCSI_OPCODE_MASK;
+
+	if (disk_check_ua(cmnd))
+		return 0;
+
+	if (disk_check_reservation(cmnd))
+		return 0;
+
  	switch (req->scb[0]) {
  	case INQUIRY:
  		send_data_rsp(cmnd, build_inquiry_response);
diff --git a/ubuntu/iscsitarget/ua.c b/ubuntu/iscsitarget/ua.c
new file mode 100644
index 0000000..db08169
--- /dev/null
+++ b/ubuntu/iscsitarget/ua.c
@@ -0,0 +1,164 @@ 
+/*
+ * IET Unit Attention support
+ *
+ * Copyright (C) 2009 Xie Gang <xiegang112@gmail.com>
+ * Copyright (C) 2009 Arne Redlich <arne.redlich@googlemail.com>
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <scsi/scsi.h>
+
+#include "iscsi.h"
+#include "iscsi_dbg.h"
+
+#define ua_hashfn(lun) ((lun % UA_HASH_LEN))
+
+static struct kmem_cache *ua_cache;
+
+int ua_init(void)
+{
+	ua_cache = KMEM_CACHE(ua_entry, 0);
+	if (!ua_cache) {
+		eprintk("%s", "Failed to create ua cache\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void ua_exit(void)
+{
+	if (ua_cache)
+		kmem_cache_destroy(ua_cache);
+}
+
+/* sess->ua_hash_lock needs to be held */
+static struct ua_entry * ua_find_hash(struct iscsi_session *sess, u32 lun,
+				      u8 asc, u8 ascq, int match)
+{
+	struct ua_entry *ua;
+	struct list_head *h = &sess->ua_hash[ua_hashfn(lun)];
+
+	list_for_each_entry(ua, h, entry) {
+		if (ua->lun == lun) {
+			if (!match)
+				return ua;
+			if (ua->asc == asc && ua->ascq == ascq)
+				return ua;
+		}
+	}
+
+	return NULL;
+}
+
+int ua_pending(struct iscsi_session *sess, u32 lun)
+{
+	struct ua_entry *ua;
+
+	spin_lock(&sess->ua_hash_lock);
+	ua = ua_find_hash(sess, lun, 0, 0, 0);
+	spin_unlock(&sess->ua_hash_lock);
+
+	dprintk_ua(ua, sess, lun);
+
+	return ua ? 1 : 0;
+}
+
+/* sess->ua_hash_lock needs to be held */
+static struct ua_entry * __ua_get_hash(struct iscsi_session *sess, u32 lun,
+				       u8 asc, u8 ascq, int match)
+{
+	struct ua_entry *ua = ua_find_hash(sess, lun, asc, ascq, match);
+
+	if (ua)
+		list_del_init(&ua->entry);
+
+	return ua;
+}
+
+struct ua_entry * ua_get_first(struct iscsi_session *sess, u32 lun)
+{
+	struct ua_entry *ua;
+
+	spin_lock(&sess->ua_hash_lock);
+	ua = __ua_get_hash(sess, lun, 0, 0, 0);
+	spin_unlock(&sess->ua_hash_lock);
+
+	dprintk_ua(ua, sess, lun);
+
+	return ua;
+}
+
+struct ua_entry * ua_get_match(struct iscsi_session *sess, u32 lun,
+			       u8 asc, u8 ascq)
+{
+	struct ua_entry *ua;
+
+	spin_lock(&sess->ua_hash_lock);
+	ua = __ua_get_hash(sess, lun, asc, ascq, 1);
+	spin_unlock(&sess->ua_hash_lock);
+
+	dprintk_ua(ua, sess, lun);
+
+	return ua;
+}
+
+void ua_establish_for_session(struct iscsi_session *sess, u32 lun,
+			      u8 asc, u8 ascq)
+{
+	struct list_head *l = &sess->ua_hash[ua_hashfn(lun)];
+	struct ua_entry *ua = kmem_cache_alloc(ua_cache, GFP_KERNEL);
+
+	if (!ua) {
+		eprintk("%s", "Failed to alloc ua");
+		return;
+	}
+
+	ua->asc = asc;
+	ua->ascq = ascq;
+	ua->lun = lun;
+	ua->session = sess;
+
+	spin_lock(&sess->ua_hash_lock);
+	list_add_tail(&ua->entry, l);
+	spin_unlock(&sess->ua_hash_lock);
+
+	dprintk_ua(ua, sess, lun);
+}
+
+void ua_establish_for_other_sessions(struct iscsi_session *sess, u32 lun,
+				     u8 asc, u8 ascq)
+{
+	struct list_head *l = &sess->target->session_list;
+	struct iscsi_session *s;
+
+	spin_lock(&sess->target->session_list_lock);
+	list_for_each_entry(s, l, list)
+		if (s->sid != sess->sid)
+			ua_establish_for_session(s, lun, asc, ascq);
+	spin_unlock(&sess->target->session_list_lock);
+}
+
+void ua_establish_for_all_sessions(struct iscsi_target *target, u32 lun,
+				   u8 asc, u8 ascq)
+{
+	struct list_head *l = &target->session_list;
+	struct iscsi_session *s;
+
+	spin_lock(&target->session_list_lock);
+	list_for_each_entry(s, l, list)
+		ua_establish_for_session(s, lun, asc, ascq);
+	spin_unlock(&target->session_list_lock);
+
+}
+
+void ua_free(struct ua_entry *ua)
+{
+	if (!ua)
+		return;
+
+	dprintk_ua(ua, ua->session, ua->lun);
+	BUG_ON(!list_empty(&ua->entry));
+	kmem_cache_free(ua_cache, ua);
+}