All of lore.kernel.org
 help / color / mirror / Atom feed
From: Vladislav Bolkhovitin <vst@vlnb.net>
To: linux-scsi@vger.kernel.org
Cc: linux-kernel@vger.kernel.org,
	scst-devel <scst-devel@lists.sourceforge.net>,
	James Bottomley <James.Bottomley@HansenPartnership.com>,
	Andrew Morton <akpm@linux-foundation.org>,
	FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>,
	Mike Christie <michaelc@cs.wisc.edu>,
	Jeff Garzik <jeff@garzik.org>, Vu Pham <vuhuong@mellanox.com>,
	Bart Van Assche <bart.vanassche@gmail.com>,
	James Smart <James.Smart@Emulex.Com>,
	Joe Eykholt <jeykholt@cisco.com>, Andy Yan <ayan@marvell.com>,
	Chetan Loke <generationgnu@yahoo.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Hannes Reinecke <hare@suse.de>,
	Richard Sharpe <realrichardsharpe@gmail.com>
Subject: [PATCH 6/17]: SCST internal library functions
Date: Tue, 14 Sep 2010 18:42:36 +0400	[thread overview]
Message-ID: <4C8F89DC.9050406@vlnb.net> (raw)
In-Reply-To: <4C8F886E.2080407@vlnb.net>

This patch contains SCST internal library functions

Signed-off-by: Vladislav Bolkhovitin <vst@vlnb.net>
---
 scst_lib.c | 7030 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 7030 insertions(+)

diff -uprN orig/linux-2.6.35/drivers/scst/scst_lib.c linux-2.6.35/drivers/scst/scst_lib.c
--- orig/linux-2.6.35/drivers/scst/scst_lib.c
+++ linux-2.6.35/drivers/scst/scst_lib.c
@@ -0,0 +1,7030 @@
+/*
+ *  scst_lib.c
+ *
+ *  Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <vst@vlnb.net>
+ *  Copyright (C) 2004 - 2005 Leonid Stoljar
+ *  Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ *  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, version 2
+ *  of the License.
+ *
+ *  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.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/cdrom.h>
+#include <linux/unistd.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <asm/kmap_types.h>
+#include <asm/unaligned.h>
+
+#include <scst/scst.h>
+#include "scst_priv.h"
+#include "scst_mem.h"
+#include "scst_pres.h"
+
+struct scsi_io_context {
+	unsigned int full_cdb_used:1;
+	void *data;
+	void (*done)(void *data, char *sense, int result, int resid);
+	char sense[SCST_SENSE_BUFFERSIZE];
+	unsigned char full_cdb[0];
+};
+static struct kmem_cache *scsi_io_context_cache;
+
+/* get_trans_len_x extract x bytes from cdb as length starting from off */
+static int get_trans_len_1(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_1_256(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_2(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_3(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_4(struct scst_cmd *cmd, uint8_t off);
+
+static int get_bidi_trans_len_2(struct scst_cmd *cmd, uint8_t off);
+
+/* for special commands */
+static int get_trans_len_block_limit(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_read_capacity(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_serv_act_in(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_single(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_none(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_read_pos(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_cdb_len_10(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_prevent_allow_medium_removal(struct scst_cmd *cmd,
+	uint8_t off);
+static int get_trans_len_3_read_elem_stat(struct scst_cmd *cmd, uint8_t off);
+static int get_trans_len_start_stop(struct scst_cmd *cmd, uint8_t off);
+
+/*
++=====================================-============-======-
+|  Command name                       | Operation  | Type |
+|                                     |   code     |      |
+|-------------------------------------+------------+------+
+
++=========================================================+
+|Key:  M = command implementation is mandatory.           |
+|      O = command implementation is optional.            |
+|      V = Vendor-specific                                |
+|      R = Reserved                                       |
+|     ' '= DON'T use for this device                      |
++=========================================================+
+*/
+
+#define SCST_CDB_MANDATORY  'M'	/* mandatory */
+#define SCST_CDB_OPTIONAL   'O'	/* optional  */
+#define SCST_CDB_VENDOR     'V'	/* vendor    */
+#define SCST_CDB_RESERVED   'R'	/* reserved  */
+#define SCST_CDB_NOTSUPP    ' '	/* don't use */
+
+struct scst_sdbops {
+	uint8_t ops;		/* SCSI-2 op codes */
+	uint8_t devkey[16];	/* Key for every device type M,O,V,R
+				 * type_disk      devkey[0]
+				 * type_tape      devkey[1]
+				 * type_printer   devkey[2]
+				 * type_proseccor devkey[3]
+				 * type_worm      devkey[4]
+				 * type_cdrom     devkey[5]
+				 * type_scanner   devkey[6]
+				 * type_mod       devkey[7]
+				 * type_changer   devkey[8]
+				 * type_commdev   devkey[9]
+				 * type_reserv    devkey[A]
+				 * type_reserv    devkey[B]
+				 * type_raid      devkey[C]
+				 * type_enclosure devkey[D]
+				 * type_reserv    devkey[E]
+				 * type_reserv    devkey[F]
+				 */
+	const char *op_name;	/* SCSI-2 op codes full name */
+	uint8_t direction;	/* init   --> target: SCST_DATA_WRITE
+				 * target --> init:   SCST_DATA_READ
+				 */
+	uint16_t flags;		/* opcode --  various flags */
+	uint8_t off;		/* length offset in cdb */
+	int (*get_trans_len)(struct scst_cmd *cmd, uint8_t off);
+};
+
+static int scst_scsi_op_list[256];
+
+#define FLAG_NONE 0
+
+static const struct scst_sdbops scst_scsi_op_table[] = {
+	/*
+	 *      +-------------------> TYPE_IS_DISK      (0)
+	 *      |
+	 *      |+------------------> TYPE_IS_TAPE      (1)
+	 *      ||
+	 *      || +----------------> TYPE_IS_PROCESSOR (3)
+	 *      || |
+	 *      || | +--------------> TYPE_IS_CDROM     (5)
+	 *      || | |
+	 *      || | | +------------> TYPE_IS_MOD       (7)
+	 *      || | | |
+	 *      || | | |+-----------> TYPE_IS_CHANGER   (8)
+	 *      || | | ||
+	 *      || | | ||   +-------> TYPE_IS_RAID      (C)
+	 *      || | | ||   |
+	 *      || | | ||   |
+	 *      0123456789ABCDEF ---> TYPE_IS_????     */
+
+	/* 6-bytes length CDB */
+	{0x00, "MMMMMMMMMMMMMMMM", "TEST UNIT READY",
+	 /* let's be HQ to don't look dead under high load */
+	 SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ|
+			 SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			 SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 0, get_trans_len_none},
+	{0x01, " M              ", "REWIND",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0x01, "O V OO OO       ", "REZERO UNIT",
+	 SCST_DATA_NONE, SCST_WRITE_EXCL_ALLOWED,
+	 0, get_trans_len_none},
+	{0x02, "VVVVVV  V       ", "REQUEST BLOCK ADDR",
+	 SCST_DATA_NONE, SCST_SMALL_TIMEOUT, 0, get_trans_len_none},
+	{0x03, "MMMMMMMMMMMMMMMM", "REQUEST SENSE",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_SKIP_UA|SCST_LOCAL_CMD|
+			 SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 4, get_trans_len_1},
+	{0x04, "M    O O        ", "FORMAT UNIT",
+	 SCST_DATA_WRITE, SCST_LONG_TIMEOUT|SCST_UNKNOWN_LENGTH|SCST_WRITE_MEDIUM,
+	 0, get_trans_len_none},
+	{0x04, "  O             ", "FORMAT",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0x05, "VMVVVV  V       ", "READ BLOCK LIMITS",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT|
+			 SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 0, get_trans_len_block_limit},
+	{0x07, "        O       ", "INITIALIZE ELEMENT STATUS",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0x07, "OVV O  OV       ", "REASSIGN BLOCKS",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0x08, "O               ", "READ(6)",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			 SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			 SCST_WRITE_EXCL_ALLOWED,
+	 4, get_trans_len_1_256},
+	{0x08, " MV OO OV       ", "READ(6)",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 2, get_trans_len_3},
+	{0x08, "         M      ", "GET MESSAGE(6)",
+	 SCST_DATA_READ, FLAG_NONE, 2, get_trans_len_3},
+	{0x08, "    O           ", "RECEIVE",
+	 SCST_DATA_READ, FLAG_NONE, 2, get_trans_len_3},
+	{0x0A, "O               ", "WRITE(6)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			  SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			  SCST_WRITE_MEDIUM,
+	 4, get_trans_len_1_256},
+	{0x0A, " M  O  OV       ", "WRITE(6)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
+	 2, get_trans_len_3},
+	{0x0A, "  M             ", "PRINT",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x0A, "         M      ", "SEND MESSAGE(6)",
+	 SCST_DATA_WRITE, FLAG_NONE, 2, get_trans_len_3},
+	{0x0A, "    M           ", "SEND(6)",
+	 SCST_DATA_WRITE, FLAG_NONE, 2, get_trans_len_3},
+	{0x0B, "O   OO OV       ", "SEEK(6)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x0B, "                ", "TRACK SELECT",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x0B, "  O             ", "SLEW AND PRINT",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x0C, "VVVVVV  V       ", "SEEK BLOCK",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0x0D, "VVVVVV  V       ", "PARTITION",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM,
+	 0, get_trans_len_none},
+	{0x0F, "VOVVVV  V       ", "READ REVERSE",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 2, get_trans_len_3},
+	{0x10, "VM V V          ", "WRITE FILEMARKS",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0x10, "  O O           ", "SYNCHRONIZE BUFFER",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x11, "VMVVVV          ", "SPACE",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 0, get_trans_len_none},
+	{0x12, "MMMMMMMMMMMMMMMM", "INQUIRY",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ|SCST_SKIP_UA|
+			 SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
+	 4, get_trans_len_1},
+	{0x13, "VOVVVV          ", "VERIFY(6)",
+	 SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED|
+			 SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 2, get_trans_len_3},
+	{0x14, "VOOVVV          ", "RECOVER BUFFERED DATA",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 2, get_trans_len_3},
+	{0x15, "OMOOOOOOOOOOOOOO", "MODE SELECT(6)",
+	 SCST_DATA_WRITE, SCST_LOCAL_CMD, 4, get_trans_len_1},
+	{0x16, "MMMMMMMMMMMMMMMM", "RESERVE",
+	 SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|
+			 SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
+	 0, get_trans_len_none},
+	{0x17, "MMMMMMMMMMMMMMMM", "RELEASE",
+	 SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|
+			 SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
+	 0, get_trans_len_none},
+	{0x18, "OOOOOOOO        ", "COPY",
+	 SCST_DATA_WRITE, SCST_LONG_TIMEOUT, 2, get_trans_len_3},
+	{0x19, "VMVVVV          ", "ERASE",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM,
+	 0, get_trans_len_none},
+	{0x1A, "OMOOOOOOOOOOOOOO", "MODE SENSE(6)",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT, 4, get_trans_len_1},
+	{0x1B, "      O         ", "SCAN",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x1B, " O              ", "LOAD UNLOAD",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0x1B, "  O             ", "STOP PRINT",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x1B, "O   OO O    O   ", "START STOP UNIT",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_start_stop},
+	{0x1C, "OOOOOOOOOOOOOOOO", "RECEIVE DIAGNOSTIC RESULTS",
+	 SCST_DATA_READ, FLAG_NONE, 3, get_trans_len_2},
+	{0x1D, "MMMMMMMMMMMMMMMM", "SEND DIAGNOSTIC",
+	 SCST_DATA_WRITE, FLAG_NONE, 4, get_trans_len_1},
+	{0x1E, "OOOOOOOOOOOOOOOO", "PREVENT ALLOW MEDIUM REMOVAL",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0,
+	 get_trans_len_prevent_allow_medium_removal},
+	{0x1F, "            O   ", "PORT STATUS",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+
+	 /* 10-bytes length CDB */
+	{0x23, "V   VV V        ", "READ FORMAT CAPACITY",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x24, "V   VVM         ", "SET WINDOW",
+	 SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_3},
+	{0x25, "M   MM M        ", "READ CAPACITY",
+	 SCST_DATA_READ, SCST_IMPLICIT_HQ|
+			 SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 0, get_trans_len_read_capacity},
+	{0x25, "      O         ", "GET WINDOW",
+	 SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_3},
+	{0x28, "M   MMMM        ", "READ(10)",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			 SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			 SCST_WRITE_EXCL_ALLOWED,
+	 7, get_trans_len_2},
+	{0x28, "         O      ", "GET MESSAGE(10)",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x29, "V   VV O        ", "READ GENERATION",
+	 SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_1},
+	{0x2A, "O   MO M        ", "WRITE(10)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			  SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			  SCST_WRITE_MEDIUM,
+	 7, get_trans_len_2},
+	{0x2A, "         O      ", "SEND MESSAGE(10)",
+	 SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2},
+	{0x2A, "      O         ", "SEND(10)",
+	 SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2},
+	{0x2B, " O              ", "LOCATE",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 0, get_trans_len_none},
+	{0x2B, "        O       ", "POSITION TO ELEMENT",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0x2B, "O   OO O        ", "SEEK(10)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x2C, "V    O O        ", "ERASE(10)",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM,
+	 0, get_trans_len_none},
+	{0x2D, "V   O  O        ", "READ UPDATED BLOCK",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 0, get_trans_len_single},
+	{0x2E, "O   OO O        ", "WRITE AND VERIFY(10)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
+	 7, get_trans_len_2},
+	{0x2F, "O   OO O        ", "VERIFY(10)",
+	 SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED|
+			 SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 7, get_trans_len_2},
+	{0x33, "O   OO O        ", "SET LIMITS(10)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x34, " O              ", "READ POSITION",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 7, get_trans_len_read_pos},
+	{0x34, "      O         ", "GET DATA BUFFER STATUS",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x34, "O   OO O        ", "PRE-FETCH",
+	 SCST_DATA_NONE, SCST_WRITE_EXCL_ALLOWED,
+	 0, get_trans_len_none},
+	{0x35, "O   OO O        ", "SYNCHRONIZE CACHE",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x36, "O   OO O        ", "LOCK UNLOCK CACHE",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x37, "O      O        ", "READ DEFECT DATA(10)",
+	 SCST_DATA_READ, SCST_WRITE_EXCL_ALLOWED,
+	 8, get_trans_len_1},
+	{0x37, "        O       ", "INIT ELEMENT STATUS WRANGE",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0x38, "    O  O        ", "MEDIUM SCAN",
+	 SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_1},
+	{0x39, "OOOOOOOO        ", "COMPARE",
+	 SCST_DATA_WRITE, FLAG_NONE, 3, get_trans_len_3},
+	{0x3A, "OOOOOOOO        ", "COPY AND VERIFY",
+	 SCST_DATA_WRITE, FLAG_NONE, 3, get_trans_len_3},
+	{0x3B, "OOOOOOOOOOOOOOOO", "WRITE BUFFER",
+	 SCST_DATA_WRITE, SCST_SMALL_TIMEOUT, 6, get_trans_len_3},
+	{0x3C, "OOOOOOOOOOOOOOOO", "READ BUFFER",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT, 6, get_trans_len_3},
+	{0x3D, "    O  O        ", "UPDATE BLOCK",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED,
+	 0, get_trans_len_single},
+	{0x3E, "O   OO O        ", "READ LONG",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x3F, "O   O  O        ", "WRITE LONG",
+	 SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 7, get_trans_len_2},
+	{0x40, "OOOOOOOOOO      ", "CHANGE DEFINITION",
+	 SCST_DATA_WRITE, SCST_SMALL_TIMEOUT, 8, get_trans_len_1},
+	{0x41, "O    O          ", "WRITE SAME",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
+	 0, get_trans_len_single},
+	{0x42, "     O          ", "READ SUB-CHANNEL",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x43, "     O          ", "READ TOC/PMA/ATIP",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x44, " M              ", "REPORT DENSITY SUPPORT",
+	 SCST_DATA_READ, SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 7, get_trans_len_2},
+	{0x44, "     O          ", "READ HEADER",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x45, "     O          ", "PLAY AUDIO(10)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x46, "     O          ", "GET CONFIGURATION",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x47, "     O          ", "PLAY AUDIO MSF",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x48, "     O          ", "PLAY AUDIO TRACK INDEX",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x49, "     O          ", "PLAY TRACK RELATIVE(10)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x4A, "     O          ", "GET EVENT STATUS NOTIFICATION",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x4B, "     O          ", "PAUSE/RESUME",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x4C, "OOOOOOOOOOOOOOOO", "LOG SELECT",
+	 SCST_DATA_WRITE, SCST_SMALL_TIMEOUT, 7, get_trans_len_2},
+	{0x4D, "OOOOOOOOOOOOOOOO", "LOG SENSE",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT|
+			 SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 7, get_trans_len_2},
+	{0x4E, "     O          ", "STOP PLAY/SCAN",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x50, "                ", "XDWRITE",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0x51, "     O          ", "READ DISC INFORMATION",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x51, "                ", "XPWRITE",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0x52, "     O          ", "READ TRACK INFORMATION",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x53, "O               ", "XDWRITEREAD(10)",
+	 SCST_DATA_READ|SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
+					 SCST_WRITE_MEDIUM,
+	 7, get_bidi_trans_len_2},
+	{0x53, "     O          ", "RESERVE TRACK",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x54, "     O          ", "SEND OPC INFORMATION",
+	 SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2},
+	{0x55, "OOOOOOOOOOOOOOOO", "MODE SELECT(10)",
+	 SCST_DATA_WRITE, SCST_LOCAL_CMD, 7, get_trans_len_2},
+	{0x56, "OOOOOOOOOOOOOOOO", "RESERVE(10)",
+	 SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD,
+	 0, get_trans_len_none},
+	{0x57, "OOOOOOOOOOOOOOOO", "RELEASE(10)",
+	 SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|
+			 SCST_REG_RESERVE_ALLOWED,
+	 0, get_trans_len_none},
+	{0x58, "     O          ", "REPAIR TRACK",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0x5A, "OOOOOOOOOOOOOOOO", "MODE SENSE(10)",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT, 7, get_trans_len_2},
+	{0x5B, "     O          ", "CLOSE TRACK/SESSION",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x5C, "     O          ", "READ BUFFER CAPACITY",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
+	{0x5D, "     O          ", "SEND CUE SHEET",
+	 SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_3},
+	{0x5E, "OOOOO OOOO      ", "PERSISTENT RESERV IN",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT|
+			 SCST_LOCAL_CMD|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 5, get_trans_len_4},
+	{0x5F, "OOOOO OOOO      ", "PERSISTENT RESERV OUT",
+	 SCST_DATA_WRITE, SCST_SMALL_TIMEOUT|
+			 SCST_LOCAL_CMD|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 5, get_trans_len_4},
+
+	/* 16-bytes length CDB */
+	{0x80, "O   OO O        ", "XDWRITE EXTENDED",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0x80, " M              ", "WRITE FILEMARKS",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0x81, "O   OO O        ", "REBUILD",
+	 SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4},
+	{0x82, "O   OO O        ", "REGENERATE",
+	 SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4},
+	{0x83, "OOOOOOOOOOOOOOOO", "EXTENDED COPY",
+	 SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4},
+	{0x84, "OOOOOOOOOOOOOOOO", "RECEIVE COPY RESULT",
+	 SCST_DATA_WRITE, FLAG_NONE, 10, get_trans_len_4},
+	{0x86, "OOOOOOOOOO      ", "ACCESS CONTROL IN",
+	 SCST_DATA_NONE, SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 0, get_trans_len_none},
+	{0x87, "OOOOOOOOOO      ", "ACCESS CONTROL OUT",
+	 SCST_DATA_NONE, SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|
+			 SCST_EXCL_ACCESS_ALLOWED,
+	 0, get_trans_len_none},
+	{0x88, "M   MMMM        ", "READ(16)",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			 SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			 SCST_WRITE_EXCL_ALLOWED,
+	 10, get_trans_len_4},
+	{0x8A, "O   OO O        ", "WRITE(16)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			  SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			  SCST_WRITE_MEDIUM,
+	 10, get_trans_len_4},
+	{0x8C, "OOOOOOOOOO      ", "READ ATTRIBUTE",
+	 SCST_DATA_READ, FLAG_NONE, 10, get_trans_len_4},
+	{0x8D, "OOOOOOOOOO      ", "WRITE ATTRIBUTE",
+	 SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4},
+	{0x8E, "O   OO O        ", "WRITE AND VERIFY(16)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
+	 10, get_trans_len_4},
+	{0x8F, "O   OO O        ", "VERIFY(16)",
+	 SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED|
+			 SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED,
+	 10, get_trans_len_4},
+	{0x90, "O   OO O        ", "PRE-FETCH(16)",
+	 SCST_DATA_NONE, SCST_WRITE_EXCL_ALLOWED,
+	 0, get_trans_len_none},
+	{0x91, "O   OO O        ", "SYNCHRONIZE CACHE(16)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x91, " M              ", "SPACE(16)",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 0, get_trans_len_none},
+	{0x92, "O   OO O        ", "LOCK UNLOCK CACHE(16)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0x92, " O              ", "LOCATE(16)",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 0, get_trans_len_none},
+	{0x93, "O    O          ", "WRITE SAME(16)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
+	 10, get_trans_len_4},
+	{0x93, " M              ", "ERASE(16)",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM,
+	 0, get_trans_len_none},
+	{0x9E, "O               ", "SERVICE ACTION IN",
+	 SCST_DATA_READ, FLAG_NONE, 0, get_trans_len_serv_act_in},
+
+	/* 12-bytes length CDB */
+	{0xA0, "VVVVVVVVVV  M   ", "REPORT LUNS",
+	 SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ|SCST_SKIP_UA|
+			 SCST_FULLY_LOCAL_CMD|SCST_LOCAL_CMD|
+			 SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
+	 6, get_trans_len_4},
+	{0xA1, "     O          ", "BLANK",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0xA3, "     O          ", "SEND KEY",
+	 SCST_DATA_WRITE, FLAG_NONE, 8, get_trans_len_2},
+	{0xA3, "OOOOO OOOO      ", "REPORT DEVICE IDENTIDIER",
+	 SCST_DATA_READ, SCST_REG_RESERVE_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
+	 6, get_trans_len_4},
+	{0xA3, "            M   ", "MAINTENANCE(IN)",
+	 SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
+	{0xA4, "     O          ", "REPORT KEY",
+	 SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2},
+	{0xA4, "            O   ", "MAINTENANCE(OUT)",
+	 SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
+	{0xA5, "        M       ", "MOVE MEDIUM",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0xA5, "     O          ", "PLAY AUDIO(12)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0xA6, "     O  O       ", "EXCHANGE/LOAD/UNLOAD MEDIUM",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0xA7, "     O          ", "SET READ AHEAD",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0xA8, "         O      ", "GET MESSAGE(12)",
+	 SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
+	{0xA8, "O   OO O        ", "READ(12)",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			 SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			 SCST_WRITE_EXCL_ALLOWED,
+	 6, get_trans_len_4},
+	{0xA9, "     O          ", "PLAY TRACK RELATIVE(12)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0xAA, "O   OO O        ", "WRITE(12)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+			  SCST_TEST_IO_IN_SIRQ_ALLOWED|
+#endif
+			  SCST_WRITE_MEDIUM,
+	 6, get_trans_len_4},
+	{0xAA, "         O      ", "SEND MESSAGE(12)",
+	 SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
+	{0xAC, "       O        ", "ERASE(12)",
+	 SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
+	{0xAC, "     M          ", "GET PERFORMANCE",
+	 SCST_DATA_READ, SCST_UNKNOWN_LENGTH, 0, get_trans_len_none},
+	{0xAD, "     O          ", "READ DVD STRUCTURE",
+	 SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2},
+	{0xAE, "O   OO O        ", "WRITE AND VERIFY(12)",
+	 SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
+	 6, get_trans_len_4},
+	{0xAF, "O   OO O        ", "VERIFY(12)",
+	 SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED|
+			 SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED|
+			 SCST_WRITE_EXCL_ALLOWED,
+	 6, get_trans_len_4},
+#if 0 /* No need to support at all */
+	{0xB0, "    OO O        ", "SEARCH DATA HIGH(12)",
+	 SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1},
+	{0xB1, "    OO O        ", "SEARCH DATA EQUAL(12)",
+	 SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1},
+	{0xB2, "    OO O        ", "SEARCH DATA LOW(12)",
+	 SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1},
+#endif
+	{0xB3, "    OO O        ", "SET LIMITS(12)",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0xB5, "        O       ", "REQUEST VOLUME ELEMENT ADDRESS",
+	 SCST_DATA_READ, FLAG_NONE, 9, get_trans_len_1},
+	{0xB6, "        O       ", "SEND VOLUME TAG",
+	 SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1},
+	{0xB6, "     M         ", "SET STREAMING",
+	 SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_2},
+	{0xB7, "       O        ", "READ DEFECT DATA(12)",
+	 SCST_DATA_READ, SCST_WRITE_EXCL_ALLOWED,
+	 9, get_trans_len_1},
+	{0xB8, "        O       ", "READ ELEMENT STATUS",
+	 SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_3_read_elem_stat},
+	{0xB9, "     O          ", "READ CD MSF",
+	 SCST_DATA_READ, SCST_UNKNOWN_LENGTH, 0, get_trans_len_none},
+	{0xBA, "     O          ", "SCAN",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
+	{0xBA, "            O   ", "REDUNDANCY GROUP(IN)",
+	 SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
+	{0xBB, "     O          ", "SET SPEED",
+	 SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
+	{0xBB, "            O   ", "REDUNDANCY GROUP(OUT)",
+	 SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
+	{0xBC, "            O   ", "SPARE(IN)",
+	 SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
+	{0xBD, "     O          ", "MECHANISM STATUS",
+	 SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2},
+	{0xBD, "            O   ", "SPARE(OUT)",
+	 SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
+	{0xBE, "     O          ", "READ CD",
+	 SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 6, get_trans_len_3},
+	{0xBE, "            O   ", "VOLUME SET(IN)",
+	 SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
+	{0xBF, "     O          ", "SEND DVD STRUCTUE",
+	 SCST_DATA_WRITE, FLAG_NONE, 8, get_trans_len_2},
+	{0xBF, "            O   ", "VOLUME SET(OUT)",
+	 SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
+	{0xE7, "        V       ", "INIT ELEMENT STATUS WRANGE",
+	 SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_cdb_len_10}
+};
+
+#define SCST_CDB_TBL_SIZE	((int)ARRAY_SIZE(scst_scsi_op_table))
+
+static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev);
+static void scst_check_internal_sense(struct scst_device *dev, int result,
+	uint8_t *sense, int sense_len);
+static void scst_queue_report_luns_changed_UA(struct scst_session *sess,
+	int flags);
+static void __scst_check_set_UA(struct scst_tgt_dev *tgt_dev,
+	const uint8_t *sense, int sense_len, int flags);
+static void scst_alloc_set_UA(struct scst_tgt_dev *tgt_dev,
+	const uint8_t *sense, int sense_len, int flags);
+static void scst_free_all_UA(struct scst_tgt_dev *tgt_dev);
+static void scst_release_space(struct scst_cmd *cmd);
+static void scst_unblock_cmds(struct scst_device *dev);
+static void scst_clear_reservation(struct scst_tgt_dev *tgt_dev);
+static int scst_alloc_add_tgt_dev(struct scst_session *sess,
+	struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev);
+static void scst_tgt_retry_timer_fn(unsigned long arg);
+
+#ifdef CONFIG_SCST_DEBUG_TM
+static void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev);
+static void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev);
+#else
+static inline void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev) {}
+static inline void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev) {}
+#endif /* CONFIG_SCST_DEBUG_TM */
+
+/**
+ * scst_alloc_sense() - allocate sense buffer for command
+ *
+ * Allocates, if necessary, sense buffer for command. Returns 0 on success
+ * and error code othrwise. Parameter "atomic" should be non-0 if the
+ * function called in atomic context.
+ */
+int scst_alloc_sense(struct scst_cmd *cmd, int atomic)
+{
+	int res = 0;
+	gfp_t gfp_mask = atomic ? GFP_ATOMIC : (GFP_KERNEL|__GFP_NOFAIL);
+
+	if (cmd->sense != NULL)
+		goto memzero;
+
+	cmd->sense = mempool_alloc(scst_sense_mempool, gfp_mask);
+	if (cmd->sense == NULL) {
+		PRINT_CRIT_ERROR("Sense memory allocation failed (op %x). "
+			"The sense data will be lost!!", cmd->cdb[0]);
+		res = -ENOMEM;
+		goto out;
+	}
+
+	cmd->sense_buflen = SCST_SENSE_BUFFERSIZE;
+
+memzero:
+	cmd->sense_valid_len = 0;
+	memset(cmd->sense, 0, cmd->sense_buflen);
+
+out:
+	return res;
+}
+EXPORT_SYMBOL(scst_alloc_sense);
+
+/**
+ * scst_alloc_set_sense() - allocate and fill sense buffer for command
+ *
+ * Allocates, if necessary, sense buffer for command and copies in
+ * it data from the supplied sense buffer. Returns 0 on success
+ * and error code othrwise.
+ */
+int scst_alloc_set_sense(struct scst_cmd *cmd, int atomic,
+	const uint8_t *sense, unsigned int len)
+{
+	int res;
+
+	/*
+	 * We don't check here if the existing sense is valid or not, because
+	 * we suppose the caller did it based on cmd->status.
+	 */
+
+	res = scst_alloc_sense(cmd, atomic);
+	if (res != 0) {
+		PRINT_BUFFER("Lost sense", sense, len);
+		goto out;
+	}
+
+	cmd->sense_valid_len = len;
+	if (cmd->sense_buflen < len) {
+		PRINT_WARNING("Sense truncated (needed %d), shall you increase "
+			"SCST_SENSE_BUFFERSIZE? Op: %x", len, cmd->cdb[0]);
+		cmd->sense_valid_len = cmd->sense_buflen;
+	}
+
+	memcpy(cmd->sense, sense, cmd->sense_valid_len);
+	TRACE_BUFFER("Sense set", cmd->sense, cmd->sense_valid_len);
+
+out:
+	return res;
+}
+EXPORT_SYMBOL(scst_alloc_set_sense);
+
+/**
+ * scst_set_cmd_error_status() - set error SCSI status
+ * @cmd:	SCST command
+ * @status:	SCSI status to set
+ *
+ * Description:
+ *    Sets error SCSI status in the command and prepares it for returning it.
+ *    Returns 0 on success, error code otherwise.
+ */
+int scst_set_cmd_error_status(struct scst_cmd *cmd, int status)
+{
+	int res = 0;
+
+	if (cmd->status != 0) {
+		TRACE_MGMT_DBG("cmd %p already has status %x set", cmd,
+			cmd->status);
+		res = -EEXIST;
+		goto out;
+	}
+
+	cmd->status = status;
+	cmd->host_status = DID_OK;
+
+	cmd->dbl_ua_orig_resp_data_len = cmd->resp_data_len;
+	cmd->dbl_ua_orig_data_direction = cmd->data_direction;
+
+	cmd->data_direction = SCST_DATA_NONE;
+	cmd->resp_data_len = 0;
+	cmd->resid_possible = 1;
+	cmd->is_send_status = 1;
+
+	cmd->completed = 1;
+
+out:
+	return res;
+}
+EXPORT_SYMBOL(scst_set_cmd_error_status);
+
+static int scst_set_lun_not_supported_request_sense(struct scst_cmd *cmd,
+	int key, int asc, int ascq)
+{
+	int res;
+	int sense_len, len;
+	struct scatterlist *sg;
+
+	if (cmd->status != 0) {
+		TRACE_MGMT_DBG("cmd %p already has status %x set", cmd,
+			cmd->status);
+		res = -EEXIST;
+		goto out;
+	}
+
+	if ((cmd->sg != NULL) && SCST_SENSE_VALID(sg_virt(cmd->sg))) {
+		TRACE_MGMT_DBG("cmd %p already has sense set", cmd);
+		res = -EEXIST;
+		goto out;
+	}
+
+	if (cmd->sg == NULL) {
+		/*
+		 * If target driver preparing data buffer using alloc_data_buf()
+		 * callback, it is responsible to copy the sense to its buffer
+		 * in xmit_response().
+		 */
+		if (cmd->tgt_data_buf_alloced && (cmd->tgt_sg != NULL)) {
+			cmd->sg = cmd->tgt_sg;
+			cmd->sg_cnt = cmd->tgt_sg_cnt;
+			TRACE_MEM("Tgt sg used for sense for cmd %p", cmd);
+			goto go;
+		}
+
+		if (cmd->bufflen == 0)
+			cmd->bufflen = cmd->cdb[4];
+
+		cmd->sg = scst_alloc(cmd->bufflen, GFP_ATOMIC, &cmd->sg_cnt);
+		if (cmd->sg == NULL) {
+			PRINT_ERROR("Unable to alloc sg for REQUEST SENSE"
+				"(sense %x/%x/%x)", key, asc, ascq);
+			res = 1;
+			goto out;
+		}
+
+		TRACE_MEM("sg %p alloced for sense for cmd %p (cnt %d, "
+			"len %d)", cmd->sg, cmd, cmd->sg_cnt, cmd->bufflen);
+	}
+
+go:
+	sg = cmd->sg;
+	len = sg->length;
+
+	TRACE_MEM("sg %p (len %d) for sense for cmd %p", sg, len, cmd);
+
+	sense_len = scst_set_sense(sg_virt(sg), len, cmd->cdb[1] & 1,
+			key, asc, ascq);
+
+	TRACE_BUFFER("Sense set", sg_virt(sg), sense_len);
+
+	cmd->data_direction = SCST_DATA_READ;
+	scst_set_resp_data_len(cmd, sense_len);
+
+	res = 0;
+	cmd->completed = 1;
+
+out:
+	return res;
+}
+
+static int scst_set_lun_not_supported_inquiry(struct scst_cmd *cmd)
+{
+	int res;
+	uint8_t *buf;
+	struct scatterlist *sg;
+	int len;
+
+	if (cmd->status != 0) {
+		TRACE_MGMT_DBG("cmd %p already has status %x set", cmd,
+			cmd->status);
+		res = -EEXIST;
+		goto out;
+	}
+
+	if (cmd->sg == NULL) {
+		/*
+		 * If target driver preparing data buffer using alloc_data_buf()
+		 * callback, it is responsible to copy the sense to its buffer
+		 * in xmit_response().
+		 */
+		if (cmd->tgt_data_buf_alloced && (cmd->tgt_sg != NULL)) {
+			cmd->sg = cmd->tgt_sg;
+			cmd->sg_cnt = cmd->tgt_sg_cnt;
+			TRACE_MEM("Tgt used for INQUIRY for not supported "
+				"LUN for cmd %p", cmd);
+			goto go;
+		}
+
+		if (cmd->bufflen == 0)
+			cmd->bufflen = min_t(int, 36, (cmd->cdb[3] << 8) | cmd->cdb[4]);
+
+		cmd->sg = scst_alloc(cmd->bufflen, GFP_ATOMIC, &cmd->sg_cnt);
+		if (cmd->sg == NULL) {
+			PRINT_ERROR("%s", "Unable to alloc sg for INQUIRY "
+				"for not supported LUN");
+			res = 1;
+			goto out;
+		}
+
+		TRACE_MEM("sg %p alloced for INQUIRY for not supported LUN for "
+			"cmd %p (cnt %d, len %d)", cmd->sg, cmd, cmd->sg_cnt,
+			cmd->bufflen);
+	}
+
+go:
+	sg = cmd->sg;
+	len = sg->length;
+
+	TRACE_MEM("sg %p (len %d) for INQUIRY for cmd %p", sg, len, cmd);
+
+	buf = sg_virt(sg);
+	len = min_t(int, 36, len);
+
+	memset(buf, 0, len);
+	buf[0] = 0x7F; /* Peripheral qualifier 011b, Peripheral device type 1Fh */
+
+	TRACE_BUFFER("INQUIRY for not supported LUN set", buf, len);
+
+	cmd->data_direction = SCST_DATA_READ;
+	scst_set_resp_data_len(cmd, len);
+
+	res = 0;
+	cmd->completed = 1;
+
+out:
+	return res;
+}
+
+/**
+ * scst_set_cmd_error() - set error in the command and fill the sense buffer.
+ *
+ * Sets error in the command and fill the sense buffer. Returns 0 on success,
+ * error code otherwise.
+ */
+int scst_set_cmd_error(struct scst_cmd *cmd, int key, int asc, int ascq)
+{
+	int res;
+
+	/*
+	 * We need for LOGICAL UNIT NOT SUPPORTED special handling for
+	 * REQUEST SENSE and INQUIRY.
+	 */
+	if ((key == ILLEGAL_REQUEST) && (asc == 0x25) && (ascq == 0)) {
+		if (cmd->cdb[0] == REQUEST_SENSE)
+			res = scst_set_lun_not_supported_request_sense(cmd,
+				key, asc, ascq);
+		else if (cmd->cdb[0] == INQUIRY)
+			res = scst_set_lun_not_supported_inquiry(cmd);
+		else
+			goto do_sense;
+
+		if (res > 0)
+			goto do_sense;
+		else
+			goto out;
+	}
+
+do_sense:
+	res = scst_set_cmd_error_status(cmd, SAM_STAT_CHECK_CONDITION);
+	if (res != 0)
+		goto out;
+
+	res = scst_alloc_sense(cmd, 1);
+	if (res != 0) {
+		PRINT_ERROR("Lost sense data (key %x, asc %x, ascq %x)",
+			key, asc, ascq);
+		goto out;
+	}
+
+	cmd->sense_valid_len = scst_set_sense(cmd->sense, cmd->sense_buflen,
+		scst_get_cmd_dev_d_sense(cmd), key, asc, ascq);
+	TRACE_BUFFER("Sense set", cmd->sense, cmd->sense_valid_len);
+
+out:
+	return res;
+}
+EXPORT_SYMBOL(scst_set_cmd_error);
+
+/**
+ * scst_set_sense() - set sense from KEY/ASC/ASCQ numbers
+ *
+ * Sets the corresponding fields in the sense buffer taking sense type
+ * into account. Returns resulting sense length.
+ */
+int scst_set_sense(uint8_t *buffer, int len, bool d_sense,
+	int key, int asc, int ascq)
+{
+	int res;
+
+	BUG_ON(len == 0);
+
+	memset(buffer, 0, len);
+
+	if (d_sense) {
+		/* Descriptor format */
+		if (len < 8) {
+			PRINT_ERROR("Length %d of sense buffer too small to "
+				"fit sense %x:%x:%x", len, key, asc, ascq);
+		}
+
+		buffer[0] = 0x72;		/* Response Code	*/
+		if (len > 1)
+			buffer[1] = key;	/* Sense Key		*/
+		if (len > 2)
+			buffer[2] = asc;	/* ASC			*/
+		if (len > 3)
+			buffer[3] = ascq;	/* ASCQ			*/
+		res = 8;
+	} else {
+		/* Fixed format */
+		if (len < 18) {
+			PRINT_ERROR("Length %d of sense buffer too small to "
+				"fit sense %x:%x:%x", len, key, asc, ascq);
+		}
+
+		buffer[0] = 0x70;		/* Response Code	*/
+		if (len > 2)
+			buffer[2] = key;	/* Sense Key		*/
+		if (len > 7)
+			buffer[7] = 0x0a;	/* Additional Sense Length */
+		if (len > 12)
+			buffer[12] = asc;	/* ASC			*/
+		if (len > 13)
+			buffer[13] = ascq;	/* ASCQ			*/
+		res = 18;
+	}
+
+	TRACE_BUFFER("Sense set", buffer, res);
+	return res;
+}
+EXPORT_SYMBOL(scst_set_sense);
+
+/**
+ * scst_analyze_sense() - analyze sense
+ *
+ * Returns true if sense matches to (key, asc, ascq) and false otherwise.
+ * Valid_mask is one or several SCST_SENSE_*_VALID constants setting valid
+ * (key, asc, ascq) values.
+ */
+bool scst_analyze_sense(const uint8_t *sense, int len, unsigned int valid_mask,
+	int key, int asc, int ascq)
+{
+	bool res = false;
+
+	/* Response Code */
+	if ((sense[0] == 0x70) || (sense[0] == 0x71)) {
+		/* Fixed format */
+
+		/* Sense Key */
+		if (valid_mask & SCST_SENSE_KEY_VALID) {
+			if (len < 3)
+				goto out;
+			if (sense[2] != key)
+				goto out;
+		}
+
+		/* ASC */
+		if (valid_mask & SCST_SENSE_ASC_VALID) {
+			if (len < 13)
+				goto out;
+			if (sense[12] != asc)
+				goto out;
+		}
+
+		/* ASCQ */
+		if (valid_mask & SCST_SENSE_ASCQ_VALID) {
+			if (len < 14)
+				goto out;
+			if (sense[13] != ascq)
+				goto out;
+		}
+	} else if ((sense[0] == 0x72) || (sense[0] == 0x73)) {
+		/* Descriptor format */
+
+		/* Sense Key */
+		if (valid_mask & SCST_SENSE_KEY_VALID) {
+			if (len < 2)
+				goto out;
+			if (sense[1] != key)
+				goto out;
+		}
+
+		/* ASC */
+		if (valid_mask & SCST_SENSE_ASC_VALID) {
+			if (len < 3)
+				goto out;
+			if (sense[2] != asc)
+				goto out;
+		}
+
+		/* ASCQ */
+		if (valid_mask & SCST_SENSE_ASCQ_VALID) {
+			if (len < 4)
+				goto out;
+			if (sense[3] != ascq)
+				goto out;
+		}
+	} else
+		goto out;
+
+	res = true;
+
+out:
+	return res;
+}
+EXPORT_SYMBOL(scst_analyze_sense);
+
+/**
+ * scst_is_ua_sense() - determine if the sense is UA sense
+ *
+ * Returns true if the sense is valid and carrying a Unit
+ * Attention or false otherwise.
+ */
+bool scst_is_ua_sense(const uint8_t *sense, int len)
+{
+	if (SCST_SENSE_VALID(sense))
+		return scst_analyze_sense(sense, len,
+			SCST_SENSE_KEY_VALID, UNIT_ATTENTION, 0, 0);
+	else
+		return false;
+}
+EXPORT_SYMBOL(scst_is_ua_sense);
+
+bool scst_is_ua_global(const uint8_t *sense, int len)
+{
+	bool res;
+
+	/* Changing it don't forget to change scst_requeue_ua() as well!! */
+
+	if (scst_analyze_sense(sense, len, SCST_SENSE_ALL_VALID,
+			SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed)))
+		res = true;
+	else
+		res = false;
+
+	return res;
+}
+
+/**
+ * scst_check_convert_sense() - check sense type and convert it if needed
+ *
+ * Checks if sense in the sense buffer, if any, is in the correct format.
+ * If not, converts it in the correct format.
+ */
+void scst_check_convert_sense(struct scst_cmd *cmd)
+{
+	bool d_sense;
+
+	if ((cmd->sense == NULL) || (cmd->status != SAM_STAT_CHECK_CONDITION))
+		goto out;
+
+	d_sense = scst_get_cmd_dev_d_sense(cmd);
+	if (d_sense && ((cmd->sense[0] == 0x70) || (cmd->sense[0] == 0x71))) {
+		TRACE_MGMT_DBG("Converting fixed sense to descriptor (cmd %p)",
+			cmd);
+		if ((cmd->sense_valid_len < 18)) {
+			PRINT_ERROR("Sense too small to convert (%d, "
+				"type: fixed)", cmd->sense_buflen);
+			goto out;
+		}
+		cmd->sense_valid_len = scst_set_sense(cmd->sense, cmd->sense_buflen,
+			d_sense, cmd->sense[2], cmd->sense[12], cmd->sense[13]);
+	} else if (!d_sense && ((cmd->sense[0] == 0x72) ||
+				(cmd->sense[0] == 0x73))) {
+		TRACE_MGMT_DBG("Converting descriptor sense to fixed (cmd %p)",
+			cmd);
+		if ((cmd->sense_buflen < 18) || (cmd->sense_valid_len < 8)) {
+			PRINT_ERROR("Sense too small to convert (%d, "
+				"type: descryptor, valid %d)",
+				cmd->sense_buflen, cmd->sense_valid_len);
+			goto out;
+		}
+		cmd->sense_valid_len = scst_set_sense(cmd->sense,
+			cmd->sense_buflen, d_sense,
+			cmd->sense[1], cmd->sense[2], cmd->sense[3]);
+	}
+
+out:
+	return;
+}
+EXPORT_SYMBOL(scst_check_convert_sense);
+
+static int scst_set_cmd_error_sense(struct scst_cmd *cmd, uint8_t *sense,
+	unsigned int len)
+{
+	int res;
+
+	res = scst_set_cmd_error_status(cmd, SAM_STAT_CHECK_CONDITION);
+	if (res != 0)
+		goto out;
+
+	res = scst_alloc_set_sense(cmd, 1, sense, len);
+
+out:
+	return res;
+}
+
+/**
+ * scst_set_busy() - set BUSY or TASK QUEUE FULL status
+ *
+ * Sets BUSY or TASK QUEUE FULL status depending on if this session has other
+ * outstanding commands or not.
+ */
+void scst_set_busy(struct scst_cmd *cmd)
+{
+	int c = atomic_read(&cmd->sess->sess_cmd_count);
+
+	if ((c <= 1) || (cmd->sess->init_phase != SCST_SESS_IPH_READY))	{
+		scst_set_cmd_error_status(cmd, SAM_STAT_BUSY);
+		TRACE(TRACE_FLOW_CONTROL, "Sending BUSY status to initiator %s "
+			"(cmds count %d, queue_type %x, sess->init_phase %d)",
+			cmd->sess->initiator_name, c,
+			cmd->queue_type, cmd->sess->init_phase);
+	} else {
+		scst_set_cmd_error_status(cmd, SAM_STAT_TASK_SET_FULL);
+		TRACE(TRACE_FLOW_CONTROL, "Sending QUEUE_FULL status to "
+			"initiator %s (cmds count %d, queue_type %x, "
+			"sess->init_phase %d)", cmd->sess->initiator_name, c,
+			cmd->queue_type, cmd->sess->init_phase);
+	}
+	return;
+}
+EXPORT_SYMBOL(scst_set_busy);
+
+/**
+ * scst_set_initial_UA() - set initial Unit Attention
+ *
+ * Sets initial Unit Attention on all devices of the session,
+ * replacing default scst_sense_reset_UA
+ */
+void scst_set_initial_UA(struct scst_session *sess, int key, int asc, int ascq)
+{
+	int i;
+
+	TRACE_MGMT_DBG("Setting for sess %p initial UA %x/%x/%x", sess, key,
+		asc, ascq);
+
+	/* Protect sess_tgt_dev_list_hash */
+	mutex_lock(&scst_mutex);
+
+	for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+		struct list_head *sess_tgt_dev_list_head =
+			&sess->sess_tgt_dev_list_hash[i];
+		struct scst_tgt_dev *tgt_dev;
+
+		list_for_each_entry(tgt_dev, sess_tgt_dev_list_head,
+				sess_tgt_dev_list_entry) {
+			spin_lock_bh(&tgt_dev->tgt_dev_lock);
+			if (!list_empty(&tgt_dev->UA_list)) {
+				struct scst_tgt_dev_UA *ua;
+
+				ua = list_entry(tgt_dev->UA_list.next,
+					typeof(*ua), UA_list_entry);
+				if (scst_analyze_sense(ua->UA_sense_buffer,
+						ua->UA_valid_sense_len,
+						SCST_SENSE_ALL_VALID,
+						SCST_LOAD_SENSE(scst_sense_reset_UA))) {
+					ua->UA_valid_sense_len = scst_set_sense(
+						ua->UA_sense_buffer,
+						sizeof(ua->UA_sense_buffer),
+						tgt_dev->dev->d_sense,
+						key, asc, ascq);
+				} else
+					PRINT_ERROR("%s",
+						"The first UA isn't RESET UA");
+			} else
+				PRINT_ERROR("%s", "There's no RESET UA to "
+					"replace");
+			spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+		}
+	}
+
+	mutex_unlock(&scst_mutex);
+	return;
+}
+EXPORT_SYMBOL(scst_set_initial_UA);
+
+static struct scst_aen *scst_alloc_aen(struct scst_session *sess,
+	uint64_t unpacked_lun)
+{
+	struct scst_aen *aen;
+
+	aen = mempool_alloc(scst_aen_mempool, GFP_KERNEL);
+	if (aen == NULL) {
+		PRINT_ERROR("AEN memory allocation failed. Corresponding "
+			"event notification will not be performed (initiator "
+			"%s)", sess->initiator_name);
+		goto out;
+	}
+	memset(aen, 0, sizeof(*aen));
+
+	aen->sess = sess;
+	scst_sess_get(sess);
+
+	aen->lun = scst_pack_lun(unpacked_lun, sess->acg->addr_method);
+
+out:
+	return aen;
+};
+
+static void scst_free_aen(struct scst_aen *aen)
+{
+
+	scst_sess_put(aen->sess);
+	mempool_free(aen, scst_aen_mempool);
+	return;
+};
+
+/* Must be called under scst_mutex */
+void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev,
+	int key, int asc, int ascq)
+{
+	struct scst_tgt_template *tgtt = tgt_dev->sess->tgt->tgtt;
+	uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
+	int sl;
+
+	if ((tgt_dev->sess->init_phase != SCST_SESS_IPH_READY) ||
+	    (tgt_dev->sess->shut_phase != SCST_SESS_SPH_READY))
+		goto out;
+
+	if (tgtt->report_aen != NULL) {
+		struct scst_aen *aen;
+		int rc;
+
+		aen = scst_alloc_aen(tgt_dev->sess, tgt_dev->lun);
+		if (aen == NULL)
+			goto queue_ua;
+
+		aen->event_fn = SCST_AEN_SCSI;
+		aen->aen_sense_len = scst_set_sense(aen->aen_sense,
+			sizeof(aen->aen_sense), tgt_dev->dev->d_sense,
+			key, asc, ascq);
+
+		TRACE_DBG("Calling target's %s report_aen(%p)",
+			tgtt->name, aen);
+		rc = tgtt->report_aen(aen);
+		TRACE_DBG("Target's %s report_aen(%p) returned %d",
+			tgtt->name, aen, rc);
+		if (rc == SCST_AEN_RES_SUCCESS)
+			goto out;
+
+		scst_free_aen(aen);
+	}
+
+queue_ua:
+	TRACE_MGMT_DBG("AEN not supported, queuing plain UA (tgt_dev %p)",
+		tgt_dev);
+	sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
+		tgt_dev->dev->d_sense, key, asc, ascq);
+	scst_check_set_UA(tgt_dev, sense_buffer, sl, 0);
+
+out:
+	return;
+}
+
+/**
+ * scst_capacity_data_changed() - notify SCST about device capacity change
+ *
+ * Notifies SCST core that dev has changed its capacity. Called under no locks.
+ */
+void scst_capacity_data_changed(struct scst_device *dev)
+{
+	struct scst_tgt_dev *tgt_dev;
+
+	if (dev->type != TYPE_DISK) {
+		TRACE_MGMT_DBG("Device type %d isn't for CAPACITY DATA "
+			"CHANGED UA", dev->type);
+		goto out;
+	}
+
+	TRACE_MGMT_DBG("CAPACITY DATA CHANGED (dev %p)", dev);
+
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+			    dev_tgt_dev_list_entry) {
+		scst_gen_aen_or_ua(tgt_dev,
+			SCST_LOAD_SENSE(scst_sense_capacity_data_changed));
+	}
+
+	mutex_unlock(&scst_mutex);
+
+out:
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_capacity_data_changed);
+
+static inline bool scst_is_report_luns_changed_type(int type)
+{
+	switch (type) {
+	case TYPE_DISK:
+	case TYPE_TAPE:
+	case TYPE_PRINTER:
+	case TYPE_PROCESSOR:
+	case TYPE_WORM:
+	case TYPE_ROM:
+	case TYPE_SCANNER:
+	case TYPE_MOD:
+	case TYPE_MEDIUM_CHANGER:
+	case TYPE_RAID:
+	case TYPE_ENCLOSURE:
+		return true;
+	default:
+		return false;
+	}
+}
+
+/* scst_mutex supposed to be held */
+static void scst_queue_report_luns_changed_UA(struct scst_session *sess,
+					      int flags)
+{
+	uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
+	struct list_head *shead;
+	struct scst_tgt_dev *tgt_dev;
+	int i;
+
+	TRACE_MGMT_DBG("Queuing REPORTED LUNS DATA CHANGED UA "
+		"(sess %p)", sess);
+
+	local_bh_disable();
+
+	for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+		shead = &sess->sess_tgt_dev_list_hash[i];
+
+		list_for_each_entry(tgt_dev, shead,
+				sess_tgt_dev_list_entry) {
+			/* Lockdep triggers here a false positive.. */
+			spin_lock(&tgt_dev->tgt_dev_lock);
+		}
+	}
+
+	for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+		shead = &sess->sess_tgt_dev_list_hash[i];
+
+		list_for_each_entry(tgt_dev, shead,
+				sess_tgt_dev_list_entry) {
+			int sl;
+
+			if (!scst_is_report_luns_changed_type(
+					tgt_dev->dev->type))
+				continue;
+
+			sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
+				tgt_dev->dev->d_sense,
+				SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed));
+
+			__scst_check_set_UA(tgt_dev, sense_buffer,
+				sl, flags | SCST_SET_UA_FLAG_GLOBAL);
+		}
+	}
+
+	for (i = TGT_DEV_HASH_SIZE-1; i >= 0; i--) {
+		shead = &sess->sess_tgt_dev_list_hash[i];
+
+		list_for_each_entry_reverse(tgt_dev,
+				shead, sess_tgt_dev_list_entry) {
+			spin_unlock(&tgt_dev->tgt_dev_lock);
+		}
+	}
+
+	local_bh_enable();
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+static void scst_report_luns_changed_sess(struct scst_session *sess)
+{
+	int i;
+	struct scst_tgt_template *tgtt = sess->tgt->tgtt;
+	int d_sense = 0;
+	uint64_t lun = 0;
+
+	if ((sess->init_phase != SCST_SESS_IPH_READY) ||
+	    (sess->shut_phase != SCST_SESS_SPH_READY))
+		goto out;
+
+	TRACE_DBG("REPORTED LUNS DATA CHANGED (sess %p)", sess);
+
+	for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+		struct list_head *shead;
+		struct scst_tgt_dev *tgt_dev;
+
+		shead = &sess->sess_tgt_dev_list_hash[i];
+
+		list_for_each_entry(tgt_dev, shead,
+				sess_tgt_dev_list_entry) {
+			if (scst_is_report_luns_changed_type(
+					tgt_dev->dev->type)) {
+				lun = tgt_dev->lun;
+				d_sense = tgt_dev->dev->d_sense;
+				goto found;
+			}
+		}
+	}
+
+found:
+	if (tgtt->report_aen != NULL) {
+		struct scst_aen *aen;
+		int rc;
+
+		aen = scst_alloc_aen(sess, lun);
+		if (aen == NULL)
+			goto queue_ua;
+
+		aen->event_fn = SCST_AEN_SCSI;
+		aen->aen_sense_len = scst_set_sense(aen->aen_sense,
+			sizeof(aen->aen_sense), d_sense,
+			SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed));
+
+		TRACE_DBG("Calling target's %s report_aen(%p)",
+			tgtt->name, aen);
+		rc = tgtt->report_aen(aen);
+		TRACE_DBG("Target's %s report_aen(%p) returned %d",
+			tgtt->name, aen, rc);
+		if (rc == SCST_AEN_RES_SUCCESS)
+			goto out;
+
+		scst_free_aen(aen);
+	}
+
+queue_ua:
+	scst_queue_report_luns_changed_UA(sess, 0);
+
+out:
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+void scst_report_luns_changed(struct scst_acg *acg)
+{
+	struct scst_session *sess;
+
+	TRACE_MGMT_DBG("REPORTED LUNS DATA CHANGED (acg %s)", acg->acg_name);
+
+	list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) {
+		scst_report_luns_changed_sess(sess);
+	}
+	return;
+}
+
+/**
+ * scst_aen_done() - AEN processing done
+ *
+ * Notifies SCST that the driver has sent the AEN and it
+ * can be freed now. Don't forget to set the delivery status, if it
+ * isn't success, using scst_set_aen_delivery_status() before calling
+ * this function.
+ */
+void scst_aen_done(struct scst_aen *aen)
+{
+
+	TRACE_MGMT_DBG("AEN %p (fn %d) done (initiator %s)", aen,
+		aen->event_fn, aen->sess->initiator_name);
+
+	if (aen->delivery_status == SCST_AEN_RES_SUCCESS)
+		goto out_free;
+
+	if (aen->event_fn != SCST_AEN_SCSI)
+		goto out_free;
+
+	TRACE_MGMT_DBG("Delivery of SCSI AEN failed (initiator %s)",
+		aen->sess->initiator_name);
+
+	if (scst_analyze_sense(aen->aen_sense, aen->aen_sense_len,
+			SCST_SENSE_ALL_VALID, SCST_LOAD_SENSE(
+				scst_sense_reported_luns_data_changed))) {
+		mutex_lock(&scst_mutex);
+		scst_queue_report_luns_changed_UA(aen->sess,
+			SCST_SET_UA_FLAG_AT_HEAD);
+		mutex_unlock(&scst_mutex);
+	} else {
+		struct list_head *shead;
+		struct scst_tgt_dev *tgt_dev;
+		uint64_t lun;
+
+		lun = scst_unpack_lun((uint8_t *)&aen->lun, sizeof(aen->lun));
+
+		mutex_lock(&scst_mutex);
+
+		/* tgt_dev might get dead, so we need to reseek it */
+		shead = &aen->sess->sess_tgt_dev_list_hash[HASH_VAL(lun)];
+		list_for_each_entry(tgt_dev, shead,
+				sess_tgt_dev_list_entry) {
+			if (tgt_dev->lun == lun) {
+				TRACE_MGMT_DBG("Requeuing failed AEN UA for "
+					"tgt_dev %p", tgt_dev);
+				scst_check_set_UA(tgt_dev, aen->aen_sense,
+					aen->aen_sense_len,
+					SCST_SET_UA_FLAG_AT_HEAD);
+				break;
+			}
+		}
+
+		mutex_unlock(&scst_mutex);
+	}
+
+out_free:
+	scst_free_aen(aen);
+	return;
+}
+EXPORT_SYMBOL(scst_aen_done);
+
+void scst_requeue_ua(struct scst_cmd *cmd)
+{
+
+	if (scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
+			SCST_SENSE_ALL_VALID,
+			SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed))) {
+		TRACE_MGMT_DBG("Requeuing REPORTED LUNS DATA CHANGED UA "
+			"for delivery failed cmd %p", cmd);
+		mutex_lock(&scst_mutex);
+		scst_queue_report_luns_changed_UA(cmd->sess,
+			SCST_SET_UA_FLAG_AT_HEAD);
+		mutex_unlock(&scst_mutex);
+	} else {
+		TRACE_MGMT_DBG("Requeuing UA for delivery failed cmd %p", cmd);
+		scst_check_set_UA(cmd->tgt_dev, cmd->sense,
+			cmd->sense_valid_len, SCST_SET_UA_FLAG_AT_HEAD);
+	}
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+static void scst_check_reassign_sess(struct scst_session *sess)
+{
+	struct scst_acg *acg, *old_acg;
+	struct scst_acg_dev *acg_dev;
+	int i, rc;
+	struct list_head *shead;
+	struct scst_tgt_dev *tgt_dev;
+	bool luns_changed = false;
+	bool add_failed, something_freed, not_needed_freed = false;
+
+	TRACE_MGMT_DBG("Checking reassignment for sess %p (initiator %s)",
+		sess, sess->initiator_name);
+
+	acg = scst_find_acg(sess);
+	if (acg == sess->acg) {
+		TRACE_MGMT_DBG("No reassignment for sess %p", sess);
+		goto out;
+	}
+
+	TRACE_MGMT_DBG("sess %p will be reassigned from acg %s to acg %s",
+		sess, sess->acg->acg_name, acg->acg_name);
+
+	old_acg = sess->acg;
+	sess->acg = NULL; /* to catch implicit dependencies earlier */
+
+retry_add:
+	add_failed = false;
+	list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) {
+		unsigned int inq_changed_ua_needed = 0;
+
+		for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+			shead = &sess->sess_tgt_dev_list_hash[i];
+
+			list_for_each_entry(tgt_dev, shead,
+					sess_tgt_dev_list_entry) {
+				if ((tgt_dev->dev == acg_dev->dev) &&
+				    (tgt_dev->lun == acg_dev->lun) &&
+				    (tgt_dev->acg_dev->rd_only == acg_dev->rd_only)) {
+					TRACE_MGMT_DBG("sess %p: tgt_dev %p for "
+						"LUN %lld stays the same",
+						sess, tgt_dev,
+						(unsigned long long)tgt_dev->lun);
+					tgt_dev->acg_dev = acg_dev;
+					goto next;
+				} else if (tgt_dev->lun == acg_dev->lun)
+					inq_changed_ua_needed = 1;
+			}
+		}
+
+		luns_changed = true;
+
+		TRACE_MGMT_DBG("sess %p: Allocing new tgt_dev for LUN %lld",
+			sess, (unsigned long long)acg_dev->lun);
+
+		rc = scst_alloc_add_tgt_dev(sess, acg_dev, &tgt_dev);
+		if (rc == -EPERM)
+			continue;
+		else if (rc != 0) {
+			add_failed = true;
+			break;
+		}
+
+		tgt_dev->inq_changed_ua_needed = inq_changed_ua_needed ||
+						 not_needed_freed;
+next:
+		continue;
+	}
+
+	something_freed = false;
+	not_needed_freed = true;
+	for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+		struct scst_tgt_dev *t;
+		shead = &sess->sess_tgt_dev_list_hash[i];
+
+		list_for_each_entry_safe(tgt_dev, t, shead,
+					sess_tgt_dev_list_entry) {
+			if (tgt_dev->acg_dev->acg != acg) {
+				TRACE_MGMT_DBG("sess %p: Deleting not used "
+					"tgt_dev %p for LUN %lld",
+					sess, tgt_dev,
+					(unsigned long long)tgt_dev->lun);
+				luns_changed = true;
+				something_freed = true;
+				scst_free_tgt_dev(tgt_dev);
+			}
+		}
+	}
+
+	if (add_failed && something_freed) {
+		TRACE_MGMT_DBG("sess %p: Retrying adding new tgt_devs", sess);
+		goto retry_add;
+	}
+
+	sess->acg = acg;
+
+	TRACE_DBG("Moving sess %p from acg %s to acg %s", sess,
+		old_acg->acg_name, acg->acg_name);
+	list_move_tail(&sess->acg_sess_list_entry, &acg->acg_sess_list);
+
+	scst_recreate_sess_luns_link(sess);
+	/* Ignore possible error, since we can't do anything on it */
+
+	if (luns_changed) {
+		scst_report_luns_changed_sess(sess);
+
+		for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+			shead = &sess->sess_tgt_dev_list_hash[i];
+
+			list_for_each_entry(tgt_dev, shead,
+					sess_tgt_dev_list_entry) {
+				if (tgt_dev->inq_changed_ua_needed) {
+					TRACE_MGMT_DBG("sess %p: Setting "
+						"INQUIRY DATA HAS CHANGED UA "
+						"(tgt_dev %p)", sess, tgt_dev);
+
+					tgt_dev->inq_changed_ua_needed = 0;
+
+					scst_gen_aen_or_ua(tgt_dev,
+						SCST_LOAD_SENSE(scst_sense_inquery_data_changed));
+				}
+			}
+		}
+	}
+
+out:
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+void scst_check_reassign_sessions(void)
+{
+	struct scst_tgt_template *tgtt;
+
+	list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) {
+		struct scst_tgt *tgt;
+		list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
+			struct scst_session *sess;
+			list_for_each_entry(sess, &tgt->sess_list,
+						sess_list_entry) {
+				scst_check_reassign_sess(sess);
+			}
+		}
+	}
+	return;
+}
+
+static int scst_get_cmd_abnormal_done_state(const struct scst_cmd *cmd)
+{
+	int res;
+
+	switch (cmd->state) {
+	case SCST_CMD_STATE_INIT_WAIT:
+	case SCST_CMD_STATE_INIT:
+	case SCST_CMD_STATE_PRE_PARSE:
+	case SCST_CMD_STATE_DEV_PARSE:
+		if (cmd->preprocessing_only) {
+			res = SCST_CMD_STATE_PREPROCESSING_DONE;
+			break;
+		} /* else go through */
+	case SCST_CMD_STATE_DEV_DONE:
+		if (cmd->internal)
+			res = SCST_CMD_STATE_FINISHED_INTERNAL;
+		else
+			res = SCST_CMD_STATE_PRE_XMIT_RESP;
+		break;
+
+	case SCST_CMD_STATE_PRE_DEV_DONE:
+	case SCST_CMD_STATE_MODE_SELECT_CHECKS:
+		res = SCST_CMD_STATE_DEV_DONE;
+		break;
+
+	case SCST_CMD_STATE_PRE_XMIT_RESP:
+		res = SCST_CMD_STATE_XMIT_RESP;
+		break;
+
+	case SCST_CMD_STATE_PREPROCESSING_DONE:
+	case SCST_CMD_STATE_PREPROCESSING_DONE_CALLED:
+		if (cmd->tgt_dev == NULL)
+			res = SCST_CMD_STATE_PRE_XMIT_RESP;
+		else
+			res = SCST_CMD_STATE_PRE_DEV_DONE;
+		break;
+
+	case SCST_CMD_STATE_PREPARE_SPACE:
+		if (cmd->preprocessing_only) {
+			res = SCST_CMD_STATE_PREPROCESSING_DONE;
+			break;
+		} /* else go through */
+	case SCST_CMD_STATE_RDY_TO_XFER:
+	case SCST_CMD_STATE_DATA_WAIT:
+	case SCST_CMD_STATE_TGT_PRE_EXEC:
+	case SCST_CMD_STATE_SEND_FOR_EXEC:
+	case SCST_CMD_STATE_LOCAL_EXEC:
+	case SCST_CMD_STATE_REAL_EXEC:
+	case SCST_CMD_STATE_REAL_EXECUTING:
+		res = SCST_CMD_STATE_PRE_DEV_DONE;
+		break;
+
+	default:
+		PRINT_CRIT_ERROR("Wrong cmd state %d (cmd %p, op %x)",
+			cmd->state, cmd, cmd->cdb[0]);
+		BUG();
+		/* Invalid state to supress compiler's warning */
+		res = SCST_CMD_STATE_LAST_ACTIVE;
+	}
+	return res;
+}
+
+/**
+ * scst_set_cmd_abnormal_done_state() - set command's next abnormal done state
+ *
+ * Sets state of the SCSI target state machine to abnormally complete command
+ * ASAP.
+ *
+ * Returns the new state.
+ */
+int scst_set_cmd_abnormal_done_state(struct scst_cmd *cmd)
+{
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	switch (cmd->state) {
+	case SCST_CMD_STATE_XMIT_RESP:
+	case SCST_CMD_STATE_FINISHED:
+	case SCST_CMD_STATE_FINISHED_INTERNAL:
+	case SCST_CMD_STATE_XMIT_WAIT:
+		PRINT_CRIT_ERROR("Wrong cmd state %d (cmd %p, op %x)",
+			cmd->state, cmd, cmd->cdb[0]);
+		BUG();
+	}
+#endif
+
+	cmd->state = scst_get_cmd_abnormal_done_state(cmd);
+
+	switch (cmd->state) {
+	case SCST_CMD_STATE_INIT_WAIT:
+	case SCST_CMD_STATE_INIT:
+	case SCST_CMD_STATE_PRE_PARSE:
+	case SCST_CMD_STATE_PREPROCESSING_DONE:
+	case SCST_CMD_STATE_PREPROCESSING_DONE_CALLED:
+	case SCST_CMD_STATE_PREPARE_SPACE:
+	case SCST_CMD_STATE_RDY_TO_XFER:
+	case SCST_CMD_STATE_DATA_WAIT:
+		cmd->write_len = 0;
+		cmd->resid_possible = 1;
+		break;
+	case SCST_CMD_STATE_TGT_PRE_EXEC:
+	case SCST_CMD_STATE_SEND_FOR_EXEC:
+	case SCST_CMD_STATE_LOCAL_EXEC:
+	case SCST_CMD_STATE_REAL_EXEC:
+	case SCST_CMD_STATE_REAL_EXECUTING:
+	case SCST_CMD_STATE_DEV_PARSE:
+	case SCST_CMD_STATE_DEV_DONE:
+	case SCST_CMD_STATE_PRE_DEV_DONE:
+	case SCST_CMD_STATE_MODE_SELECT_CHECKS:
+	case SCST_CMD_STATE_PRE_XMIT_RESP:
+		break;
+	default:
+		PRINT_CRIT_ERROR("Wrong cmd state %d (cmd %p, op %x)",
+			cmd->state, cmd, cmd->cdb[0]);
+		BUG();
+		break;
+	}
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	if (((cmd->state != SCST_CMD_STATE_PRE_XMIT_RESP) &&
+	     (cmd->state != SCST_CMD_STATE_PREPROCESSING_DONE)) &&
+		   (cmd->tgt_dev == NULL) && !cmd->internal) {
+		PRINT_CRIT_ERROR("Wrong not inited cmd state %d (cmd %p, "
+			"op %x)", cmd->state, cmd, cmd->cdb[0]);
+		BUG();
+	}
+#endif
+	return cmd->state;
+}
+EXPORT_SYMBOL_GPL(scst_set_cmd_abnormal_done_state);
+
+void scst_zero_write_rest(struct scst_cmd *cmd)
+{
+	int len, offs = 0;
+	uint8_t *buf;
+
+	len = scst_get_sg_buf_first(cmd, &buf, *cmd->write_sg,
+		*cmd->write_sg_cnt);
+	while (len > 0) {
+		int cur_offs;
+
+		if (offs + len <= cmd->write_len)
+			goto next;
+		else if (offs >= cmd->write_len)
+			cur_offs = 0;
+		else
+			cur_offs = cmd->write_len - offs;
+
+		memset(&buf[cur_offs], 0, len - cur_offs);
+
+next:
+		offs += len;
+		scst_put_sg_buf(cmd, buf, *cmd->write_sg, *cmd->write_sg_cnt);
+		len = scst_get_sg_buf_next(cmd, &buf, *cmd->write_sg,
+					*cmd->write_sg_cnt);
+	}
+	return;
+}
+
+static void scst_adjust_sg(struct scst_cmd *cmd, struct scatterlist *sg,
+	int *sg_cnt, int adjust_len)
+{
+	int i, j, l;
+
+	l = 0;
+	for (i = 0, j = 0; i < *sg_cnt; i++, j++) {
+		TRACE_DBG("i %d, j %d, sg_cnt %d, sg %p, page_link %lx", i, j,
+			*sg_cnt, sg, sg[j].page_link);
+		if (unlikely(sg_is_chain(&sg[j]))) {
+			sg = sg_chain_ptr(&sg[j]);
+			j = 0;
+		}
+		l += sg[j].length;
+		if (l >= adjust_len) {
+			int left = adjust_len - (l - sg[j].length);
+#ifdef CONFIG_SCST_DEBUG
+			TRACE(TRACE_SG_OP|TRACE_MEMORY, "cmd %p (tag %llu), "
+				"sg %p, sg_cnt %d, adjust_len %d, i %d, j %d, "
+				"sg[j].length %d, left %d",
+				cmd, (long long unsigned int)cmd->tag,
+				sg, *sg_cnt, adjust_len, i, j,
+				sg[j].length, left);
+#endif
+			cmd->orig_sg = sg;
+			cmd->p_orig_sg_cnt = sg_cnt;
+			cmd->orig_sg_cnt = *sg_cnt;
+			cmd->orig_sg_entry = j;
+			cmd->orig_entry_len = sg[j].length;
+			*sg_cnt = (left > 0) ? j+1 : j;
+			sg[j].length = left;
+			cmd->sg_buff_modified = 1;
+			break;
+		}
+	}
+	return;
+}
+
+/**
+ * scst_restore_sg_buff() - restores modified sg buffer
+ *
+ * Restores modified sg buffer in the original state.
+ */
+void scst_restore_sg_buff(struct scst_cmd *cmd)
+{
+	TRACE_MEM("cmd %p, sg %p, orig_sg_entry %d, "
+		"orig_entry_len %d, orig_sg_cnt %d", cmd, cmd->orig_sg,
+		cmd->orig_sg_entry, cmd->orig_entry_len,
+		cmd->orig_sg_cnt);
+	cmd->orig_sg[cmd->orig_sg_entry].length = cmd->orig_entry_len;
+	*cmd->p_orig_sg_cnt = cmd->orig_sg_cnt;
+	cmd->sg_buff_modified = 0;
+}
+EXPORT_SYMBOL(scst_restore_sg_buff);
+
+/**
+ * scst_set_resp_data_len() - set response data length
+ *
+ * Sets response data length for cmd and truncates its SG vector accordingly.
+ *
+ * The cmd->resp_data_len must not be set directly, it must be set only
+ * using this function. Value of resp_data_len must be <= cmd->bufflen.
+ */
+void scst_set_resp_data_len(struct scst_cmd *cmd, int resp_data_len)
+{
+
+	scst_check_restore_sg_buff(cmd);
+	cmd->resp_data_len = resp_data_len;
+
+	if (resp_data_len == cmd->bufflen)
+		goto out;
+
+	scst_adjust_sg(cmd, cmd->sg, &cmd->sg_cnt, resp_data_len);
+
+	cmd->resid_possible = 1;
+
+out:
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_set_resp_data_len);
+
+void scst_limit_sg_write_len(struct scst_cmd *cmd)
+{
+
+	TRACE_MEM("Limiting sg write len to %d (cmd %p, sg %p, sg_cnt %d)",
+		cmd->write_len, cmd, *cmd->write_sg, *cmd->write_sg_cnt);
+
+	scst_check_restore_sg_buff(cmd);
+	scst_adjust_sg(cmd, *cmd->write_sg, cmd->write_sg_cnt, cmd->write_len);
+	return;
+}
+
+void scst_adjust_resp_data_len(struct scst_cmd *cmd)
+{
+
+	EXTRACHECKS_BUG_ON(!cmd->expected_values_set);
+
+	cmd->adjusted_resp_data_len = min(cmd->resp_data_len,
+					cmd->expected_transfer_len);
+
+	if (cmd->adjusted_resp_data_len != cmd->resp_data_len) {
+		TRACE_MEM("Abjusting resp_data_len to %d (cmd %p, sg %p, "
+			"sg_cnt %d)", cmd->adjusted_resp_data_len, cmd, cmd->sg,
+			cmd->sg_cnt);
+		scst_check_restore_sg_buff(cmd);
+		scst_adjust_sg(cmd, cmd->sg, &cmd->sg_cnt,
+				cmd->adjusted_resp_data_len);
+	}
+	return;
+}
+
+/**
+ * scst_cmd_set_write_not_received_data_len() - sets cmd's not received len
+ *
+ * Sets cmd's not received data length. Also automatically sets resid_possible.
+ */
+void scst_cmd_set_write_not_received_data_len(struct scst_cmd *cmd,
+	int not_received)
+{
+
+	BUG_ON(!cmd->expected_values_set);
+
+	cmd->resid_possible = 1;
+
+	if ((cmd->expected_data_direction & SCST_DATA_READ) &&
+	    (cmd->expected_data_direction & SCST_DATA_WRITE)) {
+		cmd->write_len = cmd->expected_out_transfer_len - not_received;
+		if (cmd->write_len == cmd->out_bufflen)
+			goto out;
+	} else if (cmd->expected_data_direction & SCST_DATA_WRITE) {
+		cmd->write_len = cmd->expected_transfer_len - not_received;
+		if (cmd->write_len == cmd->bufflen)
+			goto out;
+	}
+
+	/*
+	 * Write len now can be bigger cmd->(out_)bufflen, but that's OK,
+	 * because it will be used to only calculate write residuals.
+	 */
+
+	TRACE_DBG("cmd %p, not_received %d, write_len %d", cmd, not_received,
+		cmd->write_len);
+
+	if (cmd->data_direction & SCST_DATA_WRITE)
+		scst_limit_sg_write_len(cmd);
+
+out:
+	return;
+}
+EXPORT_SYMBOL(scst_cmd_set_write_not_received_data_len);
+
+/**
+ * __scst_get_resid() - returns residuals for cmd
+ *
+ * Returns residuals for command. Must not be called directly, use
+ * scst_get_resid() instead.
+ */
+bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid)
+{
+
+	*resid = 0;
+	if (bidi_out_resid != NULL)
+		*bidi_out_resid = 0;
+
+	BUG_ON(!cmd->expected_values_set);
+
+	if (cmd->expected_data_direction & SCST_DATA_READ) {
+		*resid = cmd->expected_transfer_len - cmd->resp_data_len;
+		if ((cmd->expected_data_direction & SCST_DATA_WRITE) && bidi_out_resid) {
+			if (cmd->write_len < cmd->expected_out_transfer_len)
+				*bidi_out_resid = cmd->expected_out_transfer_len -
+							cmd->write_len;
+			else
+				*bidi_out_resid = cmd->write_len - cmd->out_bufflen;
+		}
+	} else if (cmd->expected_data_direction & SCST_DATA_WRITE) {
+		if (cmd->write_len < cmd->expected_transfer_len)
+			*resid = cmd->expected_transfer_len - cmd->write_len;
+		else
+			*resid = cmd->write_len - cmd->bufflen;
+	}
+
+	TRACE_DBG("cmd %p, resid %d, bidi_out_resid %d (resp_data_len %d, "
+		"expected_data_direction %d, write_len %d, bufflen %d)", cmd,
+		*resid, bidi_out_resid ? *bidi_out_resid : 0, cmd->resp_data_len,
+		cmd->expected_data_direction, cmd->write_len, cmd->bufflen);
+	return true;
+}
+EXPORT_SYMBOL(__scst_get_resid);
+
+/* No locks */
+int scst_queue_retry_cmd(struct scst_cmd *cmd, int finished_cmds)
+{
+	struct scst_tgt *tgt = cmd->tgt;
+	int res = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tgt->tgt_lock, flags);
+	tgt->retry_cmds++;
+	/*
+	 * Memory barrier is needed here, because we need the exact order
+	 * between the read and write between retry_cmds and finished_cmds to
+	 * not miss the case when a command finished while we queuing it for
+	 * retry after the finished_cmds check.
+	 */
+	smp_mb();
+	TRACE_RETRY("TGT QUEUE FULL: incrementing retry_cmds %d",
+	      tgt->retry_cmds);
+	if (finished_cmds != atomic_read(&tgt->finished_cmds)) {
+		/* At least one cmd finished, so try again */
+		tgt->retry_cmds--;
+		TRACE_RETRY("Some command(s) finished, direct retry "
+		      "(finished_cmds=%d, tgt->finished_cmds=%d, "
+		      "retry_cmds=%d)", finished_cmds,
+		      atomic_read(&tgt->finished_cmds), tgt->retry_cmds);
+		res = -1;
+		goto out_unlock_tgt;
+	}
+
+	TRACE_RETRY("Adding cmd %p to retry cmd list", cmd);
+	list_add_tail(&cmd->cmd_list_entry, &tgt->retry_cmd_list);
+
+	if (!tgt->retry_timer_active) {
+		tgt->retry_timer.expires = jiffies + SCST_TGT_RETRY_TIMEOUT;
+		add_timer(&tgt->retry_timer);
+		tgt->retry_timer_active = 1;
+	}
+
+out_unlock_tgt:
+	spin_unlock_irqrestore(&tgt->tgt_lock, flags);
+	return res;
+}
+
+/**
+ * scst_update_hw_pending_start() - update commands pending start
+ *
+ * Updates the command's hw_pending_start as if it's just started hw pending.
+ * Target drivers should call it if they received reply from this pending
+ * command, but SCST core won't see it.
+ */
+void scst_update_hw_pending_start(struct scst_cmd *cmd)
+{
+	unsigned long flags;
+
+	/* To sync with scst_check_hw_pending_cmd() */
+	spin_lock_irqsave(&cmd->sess->sess_list_lock, flags);
+	cmd->hw_pending_start = jiffies;
+	TRACE_MGMT_DBG("Updated hw_pending_start to %ld (cmd %p)",
+		cmd->hw_pending_start, cmd);
+	spin_unlock_irqrestore(&cmd->sess->sess_list_lock, flags);
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_update_hw_pending_start);
+
+/*
+ * Supposed to be called under sess_list_lock, but can release/reaquire it.
+ * Returns 0 to continue, >0 to restart, <0 to break.
+ */
+static int scst_check_hw_pending_cmd(struct scst_cmd *cmd,
+	unsigned long cur_time, unsigned long max_time,
+	struct scst_session *sess, unsigned long *flags,
+	struct scst_tgt_template *tgtt)
+{
+	int res = -1; /* break */
+
+	TRACE_DBG("cmd %p, hw_pending %d, proc time %ld, "
+		"pending time %ld", cmd, cmd->cmd_hw_pending,
+		(long)(cur_time - cmd->start_time) / HZ,
+		(long)(cur_time - cmd->hw_pending_start) / HZ);
+
+	if (time_before(cur_time, cmd->start_time + max_time)) {
+		/* Cmds are ordered, so no need to check more */
+		goto out;
+	}
+
+	if (!cmd->cmd_hw_pending) {
+		res = 0; /* continue */
+		goto out;
+	}
+
+	if (time_before(cur_time, cmd->hw_pending_start + max_time)) {
+		res = 0; /* continue */
+		goto out;
+	}
+
+	TRACE_MGMT_DBG("Cmd %p HW pending for too long %ld (state %x)",
+		cmd, (cur_time - cmd->hw_pending_start) / HZ,
+		cmd->state);
+
+	cmd->cmd_hw_pending = 0;
+
+	spin_unlock_irqrestore(&sess->sess_list_lock, *flags);
+	tgtt->on_hw_pending_cmd_timeout(cmd);
+	spin_lock_irqsave(&sess->sess_list_lock, *flags);
+
+	res = 1; /* restart */
+
+out:
+	return res;
+}
+
+static void scst_hw_pending_work_fn(struct delayed_work *work)
+{
+	struct scst_session *sess = container_of(work, struct scst_session,
+					hw_pending_work);
+	struct scst_tgt_template *tgtt = sess->tgt->tgtt;
+	struct scst_cmd *cmd;
+	unsigned long cur_time = jiffies;
+	unsigned long flags;
+	unsigned long max_time = tgtt->max_hw_pending_time * HZ;
+
+	TRACE_DBG("HW pending work (sess %p, max time %ld)", sess, max_time/HZ);
+
+	clear_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags);
+
+	spin_lock_irqsave(&sess->sess_list_lock, flags);
+
+restart:
+	list_for_each_entry(cmd, &sess->sess_cmd_list, sess_cmd_list_entry) {
+		int rc;
+
+		rc = scst_check_hw_pending_cmd(cmd, cur_time, max_time, sess,
+					&flags, tgtt);
+		if (rc < 0)
+			break;
+		else if (rc == 0)
+			continue;
+		else
+			goto restart;
+	}
+
+	if (!list_empty(&sess->sess_cmd_list)) {
+		/*
+		 * For stuck cmds if there is no activity we might need to have
+		 * one more run to release them, so reschedule once again.
+		 */
+		TRACE_DBG("Sched HW pending work for sess %p (max time %d)",
+			sess, tgtt->max_hw_pending_time);
+		set_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags);
+		schedule_delayed_work(&sess->hw_pending_work,
+				tgtt->max_hw_pending_time * HZ);
+	}
+
+	spin_unlock_irqrestore(&sess->sess_list_lock, flags);
+	return;
+}
+
+static bool __scst_is_relative_target_port_id_unique(uint16_t id,
+	const struct scst_tgt *t)
+{
+	bool res = true;
+	struct scst_tgt_template *tgtt;
+
+	list_for_each_entry(tgtt, &scst_template_list,
+				scst_template_list_entry) {
+		struct scst_tgt *tgt;
+		list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
+			if (tgt == t)
+				continue;
+			if ((tgt->tgtt->is_target_enabled != NULL) &&
+			     !tgt->tgtt->is_target_enabled(tgt))
+				continue;
+			if (id == tgt->rel_tgt_id) {
+				res = false;
+				break;
+			}
+		}
+	}
+	return res;
+}
+
+/* scst_mutex supposed to be locked */
+bool scst_is_relative_target_port_id_unique(uint16_t id,
+	const struct scst_tgt *t)
+{
+	bool res;
+
+	mutex_lock(&scst_mutex);
+	res = __scst_is_relative_target_port_id_unique(id, t);
+	mutex_unlock(&scst_mutex);
+	return res;
+}
+
+int gen_relative_target_port_id(uint16_t *id)
+{
+	int res = -EOVERFLOW;
+	static unsigned long rti = SCST_MIN_REL_TGT_ID, rti_prev;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	rti_prev = rti;
+	do {
+		if (__scst_is_relative_target_port_id_unique(rti, NULL)) {
+			*id = (uint16_t)rti++;
+			res = 0;
+			goto out_unlock;
+		}
+		rti++;
+		if (rti > SCST_MAX_REL_TGT_ID)
+			rti = SCST_MIN_REL_TGT_ID;
+	} while (rti != rti_prev);
+
+	PRINT_ERROR("%s", "Unable to create unique relative target port id");
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+
+out:
+	return res;
+}
+
+/* No locks */
+int scst_alloc_tgt(struct scst_tgt_template *tgtt, struct scst_tgt **tgt)
+{
+	struct scst_tgt *t;
+	int res = 0;
+
+	t = kzalloc(sizeof(*t), GFP_KERNEL);
+	if (t == NULL) {
+		TRACE(TRACE_OUT_OF_MEM, "%s", "Allocation of tgt failed");
+		res = -ENOMEM;
+		goto out;
+	}
+
+	INIT_LIST_HEAD(&t->sess_list);
+	init_waitqueue_head(&t->unreg_waitQ);
+	t->tgtt = tgtt;
+	t->sg_tablesize = tgtt->sg_tablesize;
+	spin_lock_init(&t->tgt_lock);
+	INIT_LIST_HEAD(&t->retry_cmd_list);
+	atomic_set(&t->finished_cmds, 0);
+	init_timer(&t->retry_timer);
+	t->retry_timer.data = (unsigned long)t;
+	t->retry_timer.function = scst_tgt_retry_timer_fn;
+
+	INIT_LIST_HEAD(&t->tgt_acg_list);
+
+	*tgt = t;
+
+out:
+	return res;
+}
+
+/* No locks */
+void scst_free_tgt(struct scst_tgt *tgt)
+{
+
+	kfree(tgt->tgt_name);
+
+	kfree(tgt);
+	return;
+}
+
+/* Called under scst_mutex and suspended activity */
+int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev)
+{
+	struct scst_device *dev;
+	int res = 0;
+
+	dev = kzalloc(sizeof(*dev), gfp_mask);
+	if (dev == NULL) {
+		TRACE(TRACE_OUT_OF_MEM, "%s",
+			"Allocation of scst_device failed");
+		res = -ENOMEM;
+		goto out;
+	}
+
+	dev->handler = &scst_null_devtype;
+	atomic_set(&dev->dev_cmd_count, 0);
+	atomic_set(&dev->write_cmd_count, 0);
+	scst_init_mem_lim(&dev->dev_mem_lim);
+	spin_lock_init(&dev->dev_lock);
+	atomic_set(&dev->on_dev_count, 0);
+	INIT_LIST_HEAD(&dev->blocked_cmd_list);
+	INIT_LIST_HEAD(&dev->dev_tgt_dev_list);
+	INIT_LIST_HEAD(&dev->dev_acg_dev_list);
+	init_waitqueue_head(&dev->on_dev_waitQ);
+	dev->dev_double_ua_possible = 1;
+	dev->queue_alg = SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER;
+
+	mutex_init(&dev->dev_pr_mutex);
+	atomic_set(&dev->pr_readers_count, 0);
+	dev->pr_generation = 0;
+	dev->pr_is_set = 0;
+	dev->pr_holder = NULL;
+	dev->pr_scope = SCOPE_LU;
+	dev->pr_type = TYPE_UNSPECIFIED;
+	INIT_LIST_HEAD(&dev->dev_registrants_list);
+
+	scst_init_threads(&dev->dev_cmd_threads);
+
+	*out_dev = dev;
+
+out:
+	return res;
+}
+
+void scst_free_device(struct scst_device *dev)
+{
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	if (!list_empty(&dev->dev_tgt_dev_list) ||
+	    !list_empty(&dev->dev_acg_dev_list)) {
+		PRINT_CRIT_ERROR("%s: dev_tgt_dev_list or dev_acg_dev_list "
+			"is not empty!", __func__);
+		BUG();
+	}
+#endif
+
+	scst_deinit_threads(&dev->dev_cmd_threads);
+
+	kfree(dev->virt_name);
+	kfree(dev);
+	return;
+}
+
+/**
+ * scst_init_mem_lim - initialize memory limits structure
+ *
+ * Initializes memory limits structure mem_lim according to
+ * the current system configuration. This structure should be latter used
+ * to track and limit allocated by one or more SGV pools memory.
+ */
+void scst_init_mem_lim(struct scst_mem_lim *mem_lim)
+{
+	atomic_set(&mem_lim->alloced_pages, 0);
+	mem_lim->max_allowed_pages =
+		((uint64_t)scst_max_dev_cmd_mem << 10) >> (PAGE_SHIFT - 10);
+}
+EXPORT_SYMBOL_GPL(scst_init_mem_lim);
+
+static struct scst_acg_dev *scst_alloc_acg_dev(struct scst_acg *acg,
+					struct scst_device *dev, uint64_t lun)
+{
+	struct scst_acg_dev *res;
+
+	res = kmem_cache_zalloc(scst_acgd_cachep, GFP_KERNEL);
+	if (res == NULL) {
+		TRACE(TRACE_OUT_OF_MEM,
+		      "%s", "Allocation of scst_acg_dev failed");
+		goto out;
+	}
+
+	res->dev = dev;
+	res->acg = acg;
+	res->lun = lun;
+
+out:
+	return res;
+}
+
+/*
+ * The activity supposed to be suspended and scst_mutex held or the
+ * corresponding target supposed to be stopped.
+ */
+static void scst_del_free_acg_dev(struct scst_acg_dev *acg_dev, bool del_sysfs)
+{
+
+	TRACE_DBG("Removing acg_dev %p from acg_dev_list and dev_acg_dev_list",
+		acg_dev);
+	list_del(&acg_dev->acg_dev_list_entry);
+	list_del(&acg_dev->dev_acg_dev_list_entry);
+
+	if (del_sysfs)
+		scst_acg_dev_sysfs_del(acg_dev);
+
+	kmem_cache_free(scst_acgd_cachep, acg_dev);
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+int scst_acg_add_lun(struct scst_acg *acg, struct kobject *parent,
+	struct scst_device *dev, uint64_t lun, int read_only,
+	bool gen_scst_report_luns_changed, struct scst_acg_dev **out_acg_dev)
+{
+	int res = 0;
+	struct scst_acg_dev *acg_dev;
+	struct scst_tgt_dev *tgt_dev;
+	struct scst_session *sess;
+	LIST_HEAD(tmp_tgt_dev_list);
+	bool del_sysfs = true;
+
+	INIT_LIST_HEAD(&tmp_tgt_dev_list);
+
+	acg_dev = scst_alloc_acg_dev(acg, dev, lun);
+	if (acg_dev == NULL) {
+		res = -ENOMEM;
+		goto out;
+	}
+	acg_dev->rd_only = read_only;
+
+	TRACE_DBG("Adding acg_dev %p to acg_dev_list and dev_acg_dev_list",
+		acg_dev);
+	list_add_tail(&acg_dev->acg_dev_list_entry, &acg->acg_dev_list);
+	list_add_tail(&acg_dev->dev_acg_dev_list_entry, &dev->dev_acg_dev_list);
+
+	list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) {
+		res = scst_alloc_add_tgt_dev(sess, acg_dev, &tgt_dev);
+		if (res == -EPERM)
+			continue;
+		else if (res != 0)
+			goto out_free;
+
+		list_add_tail(&tgt_dev->extra_tgt_dev_list_entry,
+			      &tmp_tgt_dev_list);
+	}
+
+	res = scst_acg_dev_sysfs_create(acg_dev, parent);
+	if (res != 0) {
+		del_sysfs = false;
+		goto out_free;
+	}
+
+	if (gen_scst_report_luns_changed)
+		scst_report_luns_changed(acg);
+
+	PRINT_INFO("Added device %s to group %s (LUN %lld, "
+		"rd_only %d)", dev->virt_name, acg->acg_name,
+		(long long unsigned int)lun, read_only);
+
+	if (out_acg_dev != NULL)
+		*out_acg_dev = acg_dev;
+
+out:
+	return res;
+
+out_free:
+	list_for_each_entry(tgt_dev, &tmp_tgt_dev_list,
+			 extra_tgt_dev_list_entry) {
+		scst_free_tgt_dev(tgt_dev);
+	}
+	scst_del_free_acg_dev(acg_dev, del_sysfs);
+	goto out;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+int scst_acg_del_lun(struct scst_acg *acg, uint64_t lun,
+	bool gen_scst_report_luns_changed)
+{
+	int res = 0;
+	struct scst_acg_dev *acg_dev = NULL, *a;
+	struct scst_tgt_dev *tgt_dev, *tt;
+
+	list_for_each_entry(a, &acg->acg_dev_list, acg_dev_list_entry) {
+		if (a->lun == lun) {
+			acg_dev = a;
+			break;
+		}
+	}
+	if (acg_dev == NULL) {
+		PRINT_ERROR("Device is not found in group %s", acg->acg_name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	list_for_each_entry_safe(tgt_dev, tt, &acg_dev->dev->dev_tgt_dev_list,
+			 dev_tgt_dev_list_entry) {
+		if (tgt_dev->acg_dev == acg_dev)
+			scst_free_tgt_dev(tgt_dev);
+	}
+
+	scst_del_free_acg_dev(acg_dev, true);
+
+	if (gen_scst_report_luns_changed)
+		scst_report_luns_changed(acg);
+
+	PRINT_INFO("Removed LUN %lld from group %s", (unsigned long long)lun,
+		acg->acg_name);
+
+out:
+	return res;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+struct scst_acg *scst_alloc_add_acg(struct scst_tgt *tgt,
+	const char *acg_name, bool tgt_acg)
+{
+	struct scst_acg *acg;
+
+	acg = kzalloc(sizeof(*acg), GFP_KERNEL);
+	if (acg == NULL) {
+		PRINT_ERROR("%s", "Allocation of acg failed");
+		goto out;
+	}
+
+	acg->tgt = tgt;
+	INIT_LIST_HEAD(&acg->acg_dev_list);
+	INIT_LIST_HEAD(&acg->acg_sess_list);
+	INIT_LIST_HEAD(&acg->acn_list);
+	acg->acg_name = kstrdup(acg_name, GFP_KERNEL);
+	if (acg->acg_name == NULL) {
+		PRINT_ERROR("%s", "Allocation of acg_name failed");
+		goto out_free;
+	}
+
+	acg->addr_method = SCST_LUN_ADDR_METHOD_PERIPHERAL;
+
+	if (tgt_acg) {
+		int rc;
+
+		TRACE_DBG("Adding acg '%s' to device '%s' acg_list", acg_name,
+			tgt->tgt_name);
+		list_add_tail(&acg->acg_list_entry, &tgt->tgt_acg_list);
+		acg->tgt_acg = 1;
+
+		rc = scst_acg_sysfs_create(tgt, acg);
+		if (rc != 0)
+			goto out_del;
+	}
+
+out:
+	return acg;
+
+out_del:
+	list_del(&acg->acg_list_entry);
+
+out_free:
+	kfree(acg);
+	acg = NULL;
+	goto out;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+void scst_del_free_acg(struct scst_acg *acg)
+{
+	struct scst_acn *acn, *acnt;
+	struct scst_acg_dev *acg_dev, *acg_dev_tmp;
+
+	TRACE_DBG("Clearing acg %s from list", acg->acg_name);
+
+	BUG_ON(!list_empty(&acg->acg_sess_list));
+
+	/* Freeing acg_devs */
+	list_for_each_entry_safe(acg_dev, acg_dev_tmp, &acg->acg_dev_list,
+			acg_dev_list_entry) {
+		struct scst_tgt_dev *tgt_dev, *tt;
+		list_for_each_entry_safe(tgt_dev, tt,
+				 &acg_dev->dev->dev_tgt_dev_list,
+				 dev_tgt_dev_list_entry) {
+			if (tgt_dev->acg_dev == acg_dev)
+				scst_free_tgt_dev(tgt_dev);
+		}
+		scst_del_free_acg_dev(acg_dev, true);
+	}
+
+	/* Freeing names */
+	list_for_each_entry_safe(acn, acnt, &acg->acn_list, acn_list_entry) {
+		scst_del_free_acn(acn,
+			list_is_last(&acn->acn_list_entry, &acg->acn_list));
+	}
+	INIT_LIST_HEAD(&acg->acn_list);
+
+	if (acg->tgt_acg) {
+		TRACE_DBG("Removing acg %s from list", acg->acg_name);
+		list_del(&acg->acg_list_entry);
+
+		scst_acg_sysfs_del(acg);
+	} else
+		acg->tgt->default_acg = NULL;
+
+	BUG_ON(!list_empty(&acg->acg_sess_list));
+	BUG_ON(!list_empty(&acg->acg_dev_list));
+	BUG_ON(!list_empty(&acg->acn_list));
+
+	kfree(acg->acg_name);
+	kfree(acg);
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+struct scst_acg *scst_tgt_find_acg(struct scst_tgt *tgt, const char *name)
+{
+	struct scst_acg *acg, *acg_ret = NULL;
+
+	list_for_each_entry(acg, &tgt->tgt_acg_list, acg_list_entry) {
+		if (strcmp(acg->acg_name, name) == 0) {
+			acg_ret = acg;
+			break;
+		}
+	}
+	return acg_ret;
+}
+
+/* scst_mutex supposed to be held */
+static struct scst_tgt_dev *scst_find_shared_io_tgt_dev(
+	struct scst_tgt_dev *tgt_dev)
+{
+	struct scst_tgt_dev *res = NULL;
+	struct scst_acg *acg = tgt_dev->acg_dev->acg;
+	struct scst_tgt_dev *t;
+
+	TRACE_DBG("tgt_dev %s (acg %p, io_grouping_type %d)",
+		tgt_dev->sess->initiator_name, acg, acg->acg_io_grouping_type);
+
+	switch (acg->acg_io_grouping_type) {
+	case SCST_IO_GROUPING_AUTO:
+		if (tgt_dev->sess->initiator_name == NULL)
+			goto out;
+
+		list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+			if ((t == tgt_dev) ||
+			    (t->sess->initiator_name == NULL) ||
+			    (t->active_cmd_threads == NULL))
+				continue;
+
+			TRACE_DBG("t %s", t->sess->initiator_name);
+
+			/* We check other ACG's as well */
+
+			if (strcmp(t->sess->initiator_name,
+					tgt_dev->sess->initiator_name) == 0)
+				goto found;
+		}
+		break;
+
+	case SCST_IO_GROUPING_THIS_GROUP_ONLY:
+		list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+			if ((t == tgt_dev) || (t->active_cmd_threads == NULL))
+				continue;
+
+			TRACE_DBG("t %s (acg %p)", t->sess->initiator_name,
+				t->acg_dev->acg);
+
+			if (t->acg_dev->acg == acg)
+				goto found;
+		}
+		break;
+
+	case SCST_IO_GROUPING_NEVER:
+		goto out;
+
+	default:
+		list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+			if ((t == tgt_dev) || (t->active_cmd_threads == NULL))
+				continue;
+
+			TRACE_DBG("t %s (acg %p, io_grouping_type %d)",
+				t->sess->initiator_name, t->acg_dev->acg,
+				t->acg_dev->acg->acg_io_grouping_type);
+
+			if (t->acg_dev->acg->acg_io_grouping_type ==
+					acg->acg_io_grouping_type)
+				goto found;
+		}
+		break;
+	}
+
+out:
+	return res;
+
+found:
+	if (t->active_cmd_threads == &scst_main_cmd_threads) {
+		res = t;
+		TRACE_MGMT_DBG("Going to share async IO context %p (res %p, "
+			"ini %s, dev %s, grouping type %d)",
+			t->aic_keeper->aic, res, t->sess->initiator_name,
+			t->dev->virt_name,
+			t->acg_dev->acg->acg_io_grouping_type);
+	} else {
+		res = t;
+		if (!*(volatile bool*)&res->active_cmd_threads->io_context_ready) {
+			TRACE_MGMT_DBG("IO context for t %p not yet "
+				"initialized, waiting...", t);
+			msleep(100);
+			barrier();
+			goto found;
+		}
+		TRACE_MGMT_DBG("Going to share IO context %p (res %p, ini %s, "
+			"dev %s, cmd_threads %p, grouping type %d)",
+			res->active_cmd_threads->io_context, res,
+			t->sess->initiator_name, t->dev->virt_name,
+			t->active_cmd_threads,
+			t->acg_dev->acg->acg_io_grouping_type);
+	}
+	goto out;
+}
+
+enum scst_dev_type_threads_pool_type scst_parse_threads_pool_type(const char *p,
+	int len)
+{
+	enum scst_dev_type_threads_pool_type res;
+
+	if (strncasecmp(p, SCST_THREADS_POOL_PER_INITIATOR_STR,
+			min_t(int, strlen(SCST_THREADS_POOL_PER_INITIATOR_STR),
+				len)) == 0)
+		res = SCST_THREADS_POOL_PER_INITIATOR;
+	else if (strncasecmp(p, SCST_THREADS_POOL_SHARED_STR,
+			min_t(int, strlen(SCST_THREADS_POOL_SHARED_STR),
+				len)) == 0)
+		res = SCST_THREADS_POOL_SHARED;
+	else {
+		PRINT_ERROR("Unknown threads pool type %s", p);
+		res = SCST_THREADS_POOL_TYPE_INVALID;
+	}
+
+	return res;
+}
+
+static int scst_ioc_keeper_thread(void *arg)
+{
+	struct scst_async_io_context_keeper *aic_keeper =
+		(struct scst_async_io_context_keeper *)arg;
+
+	TRACE_MGMT_DBG("AIC %p keeper thread %s (PID %d) started", aic_keeper,
+		current->comm, current->pid);
+
+	current->flags |= PF_NOFREEZE;
+
+	BUG_ON(aic_keeper->aic != NULL);
+
+	aic_keeper->aic = get_io_context(GFP_KERNEL, -1);
+	TRACE_MGMT_DBG("Alloced new async IO context %p (aic %p)",
+		aic_keeper->aic, aic_keeper);
+
+	/* We have our own ref counting */
+	put_io_context(aic_keeper->aic);
+
+	/* We are ready */
+	aic_keeper->aic_ready = true;
+	wake_up_all(&aic_keeper->aic_keeper_waitQ);
+
+	wait_event_interruptible(aic_keeper->aic_keeper_waitQ,
+		kthread_should_stop());
+
+	TRACE_MGMT_DBG("AIC %p keeper thread %s (PID %d) finished", aic_keeper,
+		current->comm, current->pid);
+	return 0;
+}
+
+/* scst_mutex supposed to be held */
+int scst_tgt_dev_setup_threads(struct scst_tgt_dev *tgt_dev)
+{
+	int res = 0;
+	struct scst_device *dev = tgt_dev->dev;
+	struct scst_async_io_context_keeper *aic_keeper;
+
+	if (dev->threads_num < 0)
+		goto out;
+
+	if (dev->threads_num == 0) {
+		struct scst_tgt_dev *shared_io_tgt_dev;
+		tgt_dev->active_cmd_threads = &scst_main_cmd_threads;
+
+		shared_io_tgt_dev = scst_find_shared_io_tgt_dev(tgt_dev);
+		if (shared_io_tgt_dev != NULL) {
+			aic_keeper = shared_io_tgt_dev->aic_keeper;
+			kref_get(&aic_keeper->aic_keeper_kref);
+
+			TRACE_MGMT_DBG("Linking async io context %p "
+				"for shared tgt_dev %p (dev %s)",
+				aic_keeper->aic, tgt_dev,
+				tgt_dev->dev->virt_name);
+		} else {
+			/* Create new context */
+			aic_keeper = kzalloc(sizeof(*aic_keeper), GFP_KERNEL);
+			if (aic_keeper == NULL) {
+				PRINT_ERROR("Unable to alloc aic_keeper "
+					"(size %zd)", sizeof(*aic_keeper));
+				res = -ENOMEM;
+				goto out;
+			}
+
+			kref_init(&aic_keeper->aic_keeper_kref);
+			init_waitqueue_head(&aic_keeper->aic_keeper_waitQ);
+
+			aic_keeper->aic_keeper_thr =
+				kthread_run(scst_ioc_keeper_thread,
+					aic_keeper, "aic_keeper");
+			if (IS_ERR(aic_keeper->aic_keeper_thr)) {
+				PRINT_ERROR("Error running ioc_keeper "
+					"thread (tgt_dev %p)", tgt_dev);
+				res = PTR_ERR(aic_keeper->aic_keeper_thr);
+				goto out_free_keeper;
+			}
+
+			wait_event(aic_keeper->aic_keeper_waitQ,
+				aic_keeper->aic_ready);
+
+			TRACE_MGMT_DBG("Created async io context %p "
+				"for not shared tgt_dev %p (dev %s)",
+				aic_keeper->aic, tgt_dev,
+				tgt_dev->dev->virt_name);
+		}
+
+		tgt_dev->async_io_context = aic_keeper->aic;
+		tgt_dev->aic_keeper = aic_keeper;
+
+		res = scst_add_threads(tgt_dev->active_cmd_threads, NULL, NULL,
+			tgt_dev->sess->tgt->tgtt->threads_num);
+		goto out;
+	}
+
+	switch (dev->threads_pool_type) {
+	case SCST_THREADS_POOL_PER_INITIATOR:
+	{
+		struct scst_tgt_dev *shared_io_tgt_dev;
+
+		scst_init_threads(&tgt_dev->tgt_dev_cmd_threads);
+
+		tgt_dev->active_cmd_threads = &tgt_dev->tgt_dev_cmd_threads;
+
+		shared_io_tgt_dev = scst_find_shared_io_tgt_dev(tgt_dev);
+		if (shared_io_tgt_dev != NULL) {
+			TRACE_MGMT_DBG("Linking io context %p for "
+				"shared tgt_dev %p (cmd_threads %p)",
+				shared_io_tgt_dev->active_cmd_threads->io_context,
+				tgt_dev, tgt_dev->active_cmd_threads);
+			/* It's ref counted via threads */
+			tgt_dev->active_cmd_threads->io_context =
+				shared_io_tgt_dev->active_cmd_threads->io_context;
+		}
+
+		res = scst_add_threads(tgt_dev->active_cmd_threads, NULL,
+			tgt_dev,
+			dev->threads_num + tgt_dev->sess->tgt->tgtt->threads_num);
+		if (res != 0) {
+			/* Let's clear here, because no threads could be run */
+			tgt_dev->active_cmd_threads->io_context = NULL;
+		}
+		break;
+	}
+	case SCST_THREADS_POOL_SHARED:
+	{
+		tgt_dev->active_cmd_threads = &dev->dev_cmd_threads;
+
+		res = scst_add_threads(tgt_dev->active_cmd_threads, dev, NULL,
+			tgt_dev->sess->tgt->tgtt->threads_num);
+		break;
+	}
+	default:
+		PRINT_CRIT_ERROR("Unknown threads pool type %d (dev %s)",
+			dev->threads_pool_type, dev->virt_name);
+		BUG();
+		break;
+	}
+
+out:
+	if (res == 0)
+		tm_dbg_init_tgt_dev(tgt_dev);
+	return res;
+
+out_free_keeper:
+	kfree(aic_keeper);
+	goto out;
+}
+
+static void scst_aic_keeper_release(struct kref *kref)
+{
+	struct scst_async_io_context_keeper *aic_keeper;
+
+	aic_keeper = container_of(kref, struct scst_async_io_context_keeper,
+			aic_keeper_kref);
+
+	kthread_stop(aic_keeper->aic_keeper_thr);
+
+	kfree(aic_keeper);
+	return;
+}
+
+/* scst_mutex supposed to be held */
+void scst_tgt_dev_stop_threads(struct scst_tgt_dev *tgt_dev)
+{
+
+	if (tgt_dev->dev->threads_num < 0)
+		goto out_deinit;
+
+	if (tgt_dev->active_cmd_threads == &scst_main_cmd_threads) {
+		/* Global async threads */
+		kref_put(&tgt_dev->aic_keeper->aic_keeper_kref,
+			scst_aic_keeper_release);
+		tgt_dev->async_io_context = NULL;
+		tgt_dev->aic_keeper = NULL;
+	} else if (tgt_dev->active_cmd_threads == &tgt_dev->dev->dev_cmd_threads) {
+		/* Per device shared threads */
+		scst_del_threads(tgt_dev->active_cmd_threads,
+			tgt_dev->sess->tgt->tgtt->threads_num);
+	} else if (tgt_dev->active_cmd_threads == &tgt_dev->tgt_dev_cmd_threads) {
+		/* Per tgt_dev threads */
+		scst_del_threads(tgt_dev->active_cmd_threads, -1);
+		scst_deinit_threads(&tgt_dev->tgt_dev_cmd_threads);
+	} /* else no threads (not yet initialized, e.g.) */
+
+out_deinit:
+	tm_dbg_deinit_tgt_dev(tgt_dev);
+	tgt_dev->active_cmd_threads = NULL;
+	return;
+}
+
+/*
+ * scst_mutex supposed to be held, there must not be parallel activity in this
+ * session.
+ */
+static int scst_alloc_add_tgt_dev(struct scst_session *sess,
+	struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev)
+{
+	int res = 0;
+	int ini_sg, ini_unchecked_isa_dma, ini_use_clustering;
+	struct scst_tgt_dev *tgt_dev;
+	struct scst_device *dev = acg_dev->dev;
+	struct list_head *sess_tgt_dev_list_head;
+	int i, sl;
+	uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
+
+	tgt_dev = kmem_cache_zalloc(scst_tgtd_cachep, GFP_KERNEL);
+	if (tgt_dev == NULL) {
+		TRACE(TRACE_OUT_OF_MEM, "%s", "Allocation of scst_tgt_dev "
+			"failed");
+		res = -ENOMEM;
+		goto out;
+	}
+
+	tgt_dev->dev = dev;
+	tgt_dev->lun = acg_dev->lun;
+	tgt_dev->acg_dev = acg_dev;
+	tgt_dev->sess = sess;
+	atomic_set(&tgt_dev->tgt_dev_cmd_count, 0);
+
+	scst_sgv_pool_use_norm(tgt_dev);
+
+	if (dev->scsi_dev != NULL) {
+		ini_sg = dev->scsi_dev->host->sg_tablesize;
+		ini_unchecked_isa_dma = dev->scsi_dev->host->unchecked_isa_dma;
+		ini_use_clustering = (dev->scsi_dev->host->use_clustering ==
+				ENABLE_CLUSTERING);
+	} else {
+		ini_sg = (1 << 15) /* infinite */;
+		ini_unchecked_isa_dma = 0;
+		ini_use_clustering = 0;
+	}
+	tgt_dev->max_sg_cnt = min(ini_sg, sess->tgt->sg_tablesize);
+
+	if ((sess->tgt->tgtt->use_clustering || ini_use_clustering) &&
+	    !sess->tgt->tgtt->no_clustering)
+		scst_sgv_pool_use_norm_clust(tgt_dev);
+
+	if (sess->tgt->tgtt->unchecked_isa_dma || ini_unchecked_isa_dma)
+		scst_sgv_pool_use_dma(tgt_dev);
+
+	TRACE_MGMT_DBG("Device %s on SCST lun=%lld",
+	       dev->virt_name, (long long unsigned int)tgt_dev->lun);
+
+	spin_lock_init(&tgt_dev->tgt_dev_lock);
+	INIT_LIST_HEAD(&tgt_dev->UA_list);
+	spin_lock_init(&tgt_dev->thr_data_lock);
+	INIT_LIST_HEAD(&tgt_dev->thr_data_list);
+	spin_lock_init(&tgt_dev->sn_lock);
+	INIT_LIST_HEAD(&tgt_dev->deferred_cmd_list);
+	INIT_LIST_HEAD(&tgt_dev->skipped_sn_list);
+	tgt_dev->curr_sn = (typeof(tgt_dev->curr_sn))(-300);
+	tgt_dev->expected_sn = tgt_dev->curr_sn + 1;
+	tgt_dev->num_free_sn_slots = ARRAY_SIZE(tgt_dev->sn_slots)-1;
+	tgt_dev->cur_sn_slot = &tgt_dev->sn_slots[0];
+	for (i = 0; i < (int)ARRAY_SIZE(tgt_dev->sn_slots); i++)
+		atomic_set(&tgt_dev->sn_slots[i], 0);
+
+	if (dev->handler->parse_atomic &&
+	    dev->handler->alloc_data_buf_atomic &&
+	    (sess->tgt->tgtt->preprocessing_done == NULL)) {
+		if (sess->tgt->tgtt->rdy_to_xfer_atomic)
+			__set_bit(SCST_TGT_DEV_AFTER_INIT_WR_ATOMIC,
+				&tgt_dev->tgt_dev_flags);
+	}
+	if (dev->handler->dev_done_atomic &&
+	    sess->tgt->tgtt->xmit_response_atomic) {
+		__set_bit(SCST_TGT_DEV_AFTER_EXEC_ATOMIC,
+			&tgt_dev->tgt_dev_flags);
+	}
+
+	sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
+		dev->d_sense, SCST_LOAD_SENSE(scst_sense_reset_UA));
+	scst_alloc_set_UA(tgt_dev, sense_buffer, sl, 0);
+
+	if (sess->tgt->tgtt->get_initiator_port_transport_id == NULL) {
+		if (!list_empty(&dev->dev_registrants_list)) {
+			PRINT_WARNING("Initiators from target %s can't connect "
+				"to device %s, because the device has PR "
+				"registrants and the target doesn't support "
+				"Persistent Reservations", sess->tgt->tgtt->name,
+				dev->virt_name);
+			res = -EPERM;
+			goto out_free;
+		}
+		dev->not_pr_supporting_tgt_devs_num++;
+	}
+
+	res = scst_pr_init_tgt_dev(tgt_dev);
+	if (res != 0)
+		goto out_dec_free;
+
+	res = scst_tgt_dev_setup_threads(tgt_dev);
+	if (res != 0)
+		goto out_pr_clear;
+
+	if (dev->handler && dev->handler->attach_tgt) {
+		TRACE_DBG("Calling dev handler's attach_tgt(%p)", tgt_dev);
+		res = dev->handler->attach_tgt(tgt_dev);
+		TRACE_DBG("%s", "Dev handler's attach_tgt() returned");
+		if (res != 0) {
+			PRINT_ERROR("Device handler's %s attach_tgt() "
+			    "failed: %d", dev->handler->name, res);
+			goto out_stop_threads;
+		}
+	}
+
+	res = scst_tgt_dev_sysfs_create(tgt_dev);
+	if (res != 0)
+		goto out_detach;
+
+	spin_lock_bh(&dev->dev_lock);
+	list_add_tail(&tgt_dev->dev_tgt_dev_list_entry, &dev->dev_tgt_dev_list);
+	if (dev->dev_reserved)
+		__set_bit(SCST_TGT_DEV_RESERVED, &tgt_dev->tgt_dev_flags);
+	spin_unlock_bh(&dev->dev_lock);
+
+	sess_tgt_dev_list_head =
+		&sess->sess_tgt_dev_list_hash[HASH_VAL(tgt_dev->lun)];
+	list_add_tail(&tgt_dev->sess_tgt_dev_list_entry,
+		      sess_tgt_dev_list_head);
+
+	*out_tgt_dev = tgt_dev;
+
+out:
+	return res;
+
+out_detach:
+	if (dev->handler && dev->handler->detach_tgt) {
+		TRACE_DBG("Calling dev handler's detach_tgt(%p)",
+		      tgt_dev);
+		dev->handler->detach_tgt(tgt_dev);
+		TRACE_DBG("%s", "Dev handler's detach_tgt() returned");
+	}
+
+out_stop_threads:
+	scst_tgt_dev_stop_threads(tgt_dev);
+
+out_pr_clear:
+	scst_pr_clear_tgt_dev(tgt_dev);
+
+out_dec_free:
+	if (tgt_dev->sess->tgt->tgtt->get_initiator_port_transport_id == NULL)
+		dev->not_pr_supporting_tgt_devs_num--;
+
+out_free:
+	scst_free_all_UA(tgt_dev);
+	kmem_cache_free(scst_tgtd_cachep, tgt_dev);
+	goto out;
+}
+
+/* No locks supposed to be held, scst_mutex - held */
+void scst_nexus_loss(struct scst_tgt_dev *tgt_dev, bool queue_UA)
+{
+
+	scst_clear_reservation(tgt_dev);
+
+	/* With activity suspended the lock isn't needed, but let's be safe */
+	spin_lock_bh(&tgt_dev->tgt_dev_lock);
+	scst_free_all_UA(tgt_dev);
+	memset(tgt_dev->tgt_dev_sense, 0, sizeof(tgt_dev->tgt_dev_sense));
+	spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+
+	if (queue_UA) {
+		uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
+		int sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
+				tgt_dev->dev->d_sense,
+				SCST_LOAD_SENSE(scst_sense_nexus_loss_UA));
+		scst_check_set_UA(tgt_dev, sense_buffer, sl, 0);
+	}
+	return;
+}
+
+/*
+ * scst_mutex supposed to be held, there must not be parallel activity in this
+ * session.
+ */
+static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev)
+{
+	struct scst_device *dev = tgt_dev->dev;
+
+	spin_lock_bh(&dev->dev_lock);
+	list_del(&tgt_dev->dev_tgt_dev_list_entry);
+	spin_unlock_bh(&dev->dev_lock);
+
+	list_del(&tgt_dev->sess_tgt_dev_list_entry);
+
+	scst_tgt_dev_sysfs_del(tgt_dev);
+
+	if (tgt_dev->sess->tgt->tgtt->get_initiator_port_transport_id == NULL)
+		dev->not_pr_supporting_tgt_devs_num--;
+
+	scst_clear_reservation(tgt_dev);
+	scst_pr_clear_tgt_dev(tgt_dev);
+	scst_free_all_UA(tgt_dev);
+
+	if (dev->handler && dev->handler->detach_tgt) {
+		TRACE_DBG("Calling dev handler's detach_tgt(%p)",
+		      tgt_dev);
+		dev->handler->detach_tgt(tgt_dev);
+		TRACE_DBG("%s", "Dev handler's detach_tgt() returned");
+	}
+
+	scst_tgt_dev_stop_threads(tgt_dev);
+
+	BUG_ON(!list_empty(&tgt_dev->thr_data_list));
+
+	kmem_cache_free(scst_tgtd_cachep, tgt_dev);
+	return;
+}
+
+/* scst_mutex supposed to be held */
+int scst_sess_alloc_tgt_devs(struct scst_session *sess)
+{
+	int res = 0;
+	struct scst_acg_dev *acg_dev;
+	struct scst_tgt_dev *tgt_dev;
+
+	list_for_each_entry(acg_dev, &sess->acg->acg_dev_list,
+			acg_dev_list_entry) {
+		res = scst_alloc_add_tgt_dev(sess, acg_dev, &tgt_dev);
+		if (res == -EPERM)
+			continue;
+		else if (res != 0)
+			goto out_free;
+	}
+
+out:
+	return res;
+
+out_free:
+	scst_sess_free_tgt_devs(sess);
+	goto out;
+}
+
+/*
+ * scst_mutex supposed to be held, there must not be parallel activity in this
+ * session.
+ */
+void scst_sess_free_tgt_devs(struct scst_session *sess)
+{
+	int i;
+	struct scst_tgt_dev *tgt_dev, *t;
+
+	/* The session is going down, no users, so no locks */
+	for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+		struct list_head *sess_tgt_dev_list_head =
+			&sess->sess_tgt_dev_list_hash[i];
+		list_for_each_entry_safe(tgt_dev, t, sess_tgt_dev_list_head,
+				sess_tgt_dev_list_entry) {
+			scst_free_tgt_dev(tgt_dev);
+		}
+		INIT_LIST_HEAD(sess_tgt_dev_list_head);
+	}
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+int scst_acg_add_acn(struct scst_acg *acg, const char *name)
+{
+	int res = 0;
+	struct scst_acn *acn;
+	int len;
+	char *nm;
+
+	list_for_each_entry(acn, &acg->acn_list, acn_list_entry) {
+		if (strcmp(acn->name, name) == 0) {
+			PRINT_ERROR("Name %s already exists in group %s",
+				name, acg->acg_name);
+			res = -EEXIST;
+			goto out;
+		}
+	}
+
+	acn = kzalloc(sizeof(*acn), GFP_KERNEL);
+	if (acn == NULL) {
+		PRINT_ERROR("%s", "Unable to allocate scst_acn");
+		res = -ENOMEM;
+		goto out;
+	}
+
+	acn->acg = acg;
+
+	len = strlen(name);
+	nm = kmalloc(len + 1, GFP_KERNEL);
+	if (nm == NULL) {
+		PRINT_ERROR("%s", "Unable to allocate scst_acn->name");
+		res = -ENOMEM;
+		goto out_free;
+	}
+
+	strcpy(nm, name);
+	acn->name = nm;
+
+	res = scst_acn_sysfs_create(acn);
+	if (res != 0)
+		goto out_free_nm;
+
+	list_add_tail(&acn->acn_list_entry, &acg->acn_list);
+
+out:
+	if (res == 0) {
+		PRINT_INFO("Added name %s to group %s", name, acg->acg_name);
+		scst_check_reassign_sessions();
+	}
+	return res;
+
+out_free_nm:
+	kfree(nm);
+
+out_free:
+	kfree(acn);
+	goto out;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+void scst_del_free_acn(struct scst_acn *acn, bool reassign)
+{
+
+	list_del(&acn->acn_list_entry);
+
+	scst_acn_sysfs_del(acn);
+
+	kfree(acn->name);
+	kfree(acn);
+
+	if (reassign)
+		scst_check_reassign_sessions();
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+struct scst_acn *scst_find_acn(struct scst_acg *acg, const char *name)
+{
+	struct scst_acn *acn;
+
+	TRACE_DBG("Trying to find name '%s'", name);
+
+	list_for_each_entry(acn, &acg->acn_list, acn_list_entry) {
+		if (strcmp(acn->name, name) == 0) {
+			TRACE_DBG("%s", "Found");
+			goto out;
+		}
+	}
+	acn = NULL;
+out:
+	return acn;
+}
+
+static struct scst_cmd *scst_create_prepare_internal_cmd(
+	struct scst_cmd *orig_cmd, int bufsize)
+{
+	struct scst_cmd *res;
+	gfp_t gfp_mask = scst_cmd_atomic(orig_cmd) ? GFP_ATOMIC : GFP_KERNEL;
+
+	res = scst_alloc_cmd(gfp_mask);
+	if (res == NULL)
+		goto out;
+
+	res->cmd_threads = orig_cmd->cmd_threads;
+	res->sess = orig_cmd->sess;
+	res->atomic = scst_cmd_atomic(orig_cmd);
+	res->internal = 1;
+	res->tgtt = orig_cmd->tgtt;
+	res->tgt = orig_cmd->tgt;
+	res->dev = orig_cmd->dev;
+	res->tgt_dev = orig_cmd->tgt_dev;
+	res->lun = orig_cmd->lun;
+	res->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
+	res->data_direction = SCST_DATA_UNKNOWN;
+	res->orig_cmd = orig_cmd;
+	res->bufflen = bufsize;
+
+	scst_sess_get(res->sess);
+	if (res->tgt_dev != NULL)
+		__scst_get(0);
+
+	res->state = SCST_CMD_STATE_PRE_PARSE;
+
+out:
+	return res;
+}
+
+int scst_prepare_request_sense(struct scst_cmd *orig_cmd)
+{
+	int res = 0;
+	static const uint8_t request_sense[6] = {
+		REQUEST_SENSE, 0, 0, 0, SCST_SENSE_BUFFERSIZE, 0
+	};
+	struct scst_cmd *rs_cmd;
+
+	if (orig_cmd->sense != NULL) {
+		TRACE_MEM("Releasing sense %p (orig_cmd %p)",
+			orig_cmd->sense, orig_cmd);
+		mempool_free(orig_cmd->sense, scst_sense_mempool);
+		orig_cmd->sense = NULL;
+	}
+
+	rs_cmd = scst_create_prepare_internal_cmd(orig_cmd,
+			SCST_SENSE_BUFFERSIZE);
+	if (rs_cmd == NULL)
+		goto out_error;
+
+	memcpy(rs_cmd->cdb, request_sense, sizeof(request_sense));
+	rs_cmd->cdb[1] |= scst_get_cmd_dev_d_sense(orig_cmd);
+	rs_cmd->cdb_len = sizeof(request_sense);
+	rs_cmd->data_direction = SCST_DATA_READ;
+	rs_cmd->expected_data_direction = rs_cmd->data_direction;
+	rs_cmd->expected_transfer_len = SCST_SENSE_BUFFERSIZE;
+	rs_cmd->expected_values_set = 1;
+
+	TRACE_MGMT_DBG("Adding REQUEST SENSE cmd %p to head of active "
+		"cmd list", rs_cmd);
+	spin_lock_irq(&rs_cmd->cmd_threads->cmd_list_lock);
+	list_add(&rs_cmd->cmd_list_entry, &rs_cmd->cmd_threads->active_cmd_list);
+	wake_up(&rs_cmd->cmd_threads->cmd_list_waitQ);
+	spin_unlock_irq(&rs_cmd->cmd_threads->cmd_list_lock);
+
+out:
+	return res;
+
+out_error:
+	res = -1;
+	goto out;
+}
+
+static void scst_complete_request_sense(struct scst_cmd *req_cmd)
+{
+	struct scst_cmd *orig_cmd = req_cmd->orig_cmd;
+	uint8_t *buf;
+	int len;
+
+	BUG_ON(orig_cmd == NULL);
+
+	len = scst_get_buf_first(req_cmd, &buf);
+
+	if (scsi_status_is_good(req_cmd->status) && (len > 0) &&
+	    SCST_SENSE_VALID(buf) && (!SCST_NO_SENSE(buf))) {
+		PRINT_BUFF_FLAG(TRACE_SCSI, "REQUEST SENSE returned",
+			buf, len);
+		scst_alloc_set_sense(orig_cmd, scst_cmd_atomic(req_cmd), buf,
+			len);
+	} else {
+		PRINT_ERROR("%s", "Unable to get the sense via "
+			"REQUEST SENSE, returning HARDWARE ERROR");
+		scst_set_cmd_error(orig_cmd,
+			SCST_LOAD_SENSE(scst_sense_hardw_error));
+	}
+
+	if (len > 0)
+		scst_put_buf(req_cmd, buf);
+
+	TRACE_MGMT_DBG("Adding orig cmd %p to head of active "
+		"cmd list", orig_cmd);
+	spin_lock_irq(&orig_cmd->cmd_threads->cmd_list_lock);
+	list_add(&orig_cmd->cmd_list_entry, &orig_cmd->cmd_threads->active_cmd_list);
+	wake_up(&orig_cmd->cmd_threads->cmd_list_waitQ);
+	spin_unlock_irq(&orig_cmd->cmd_threads->cmd_list_lock);
+	return;
+}
+
+int scst_finish_internal_cmd(struct scst_cmd *cmd)
+{
+	int res;
+
+	BUG_ON(!cmd->internal);
+
+	if (cmd->cdb[0] == REQUEST_SENSE)
+		scst_complete_request_sense(cmd);
+
+	__scst_cmd_put(cmd);
+
+	res = SCST_CMD_STATE_RES_CONT_NEXT;
+	return res;
+}
+
+static void scst_send_release(struct scst_device *dev)
+{
+	struct scsi_device *scsi_dev;
+	unsigned char cdb[6];
+	uint8_t sense[SCSI_SENSE_BUFFERSIZE];
+	int rc, i;
+
+	if (dev->scsi_dev == NULL)
+		goto out;
+
+	scsi_dev = dev->scsi_dev;
+
+	for (i = 0; i < 5; i++) {
+		memset(cdb, 0, sizeof(cdb));
+		cdb[0] = RELEASE;
+		cdb[1] = (scsi_dev->scsi_level <= SCSI_2) ?
+		    ((scsi_dev->lun << 5) & 0xe0) : 0;
+
+		memset(sense, 0, sizeof(sense));
+
+		TRACE(TRACE_DEBUG | TRACE_SCSI, "%s", "Sending RELEASE req to "
+			"SCSI mid-level");
+		rc = scsi_execute(scsi_dev, cdb, SCST_DATA_NONE, NULL, 0,
+				sense, 15, 0, 0
+				, NULL
+				);
+		TRACE_DBG("MODE_SENSE done: %x", rc);
+
+		if (scsi_status_is_good(rc)) {
+			break;
+		} else {
+			PRINT_ERROR("RELEASE failed: %d", rc);
+			PRINT_BUFFER("RELEASE sense", sense, sizeof(sense));
+			scst_check_internal_sense(dev, rc, sense,
+				sizeof(sense));
+		}
+	}
+
+out:
+	return;
+}
+
+/* scst_mutex supposed to be held */
+static void scst_clear_reservation(struct scst_tgt_dev *tgt_dev)
+{
+	struct scst_device *dev = tgt_dev->dev;
+	int release = 0;
+
+	spin_lock_bh(&dev->dev_lock);
+	if (dev->dev_reserved &&
+	    !test_bit(SCST_TGT_DEV_RESERVED, &tgt_dev->tgt_dev_flags)) {
+		/* This is one who holds the reservation */
+		struct scst_tgt_dev *tgt_dev_tmp;
+		list_for_each_entry(tgt_dev_tmp, &dev->dev_tgt_dev_list,
+				    dev_tgt_dev_list_entry) {
+			clear_bit(SCST_TGT_DEV_RESERVED,
+				    &tgt_dev_tmp->tgt_dev_flags);
+		}
+		dev->dev_reserved = 0;
+		release = 1;
+	}
+	spin_unlock_bh(&dev->dev_lock);
+
+	if (release)
+		scst_send_release(dev);
+	return;
+}
+
+struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask,
+	const char *initiator_name)
+{
+	struct scst_session *sess;
+	int i;
+
+	sess = kmem_cache_zalloc(scst_sess_cachep, gfp_mask);
+	if (sess == NULL) {
+		TRACE(TRACE_OUT_OF_MEM, "%s",
+		      "Allocation of scst_session failed");
+		goto out;
+	}
+
+	sess->init_phase = SCST_SESS_IPH_INITING;
+	sess->shut_phase = SCST_SESS_SPH_READY;
+	atomic_set(&sess->refcnt, 0);
+	for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+		struct list_head *sess_tgt_dev_list_head =
+			 &sess->sess_tgt_dev_list_hash[i];
+		INIT_LIST_HEAD(sess_tgt_dev_list_head);
+	}
+	spin_lock_init(&sess->sess_list_lock);
+	INIT_LIST_HEAD(&sess->sess_cmd_list);
+	sess->tgt = tgt;
+	INIT_LIST_HEAD(&sess->init_deferred_cmd_list);
+	INIT_LIST_HEAD(&sess->init_deferred_mcmd_list);
+	INIT_DELAYED_WORK(&sess->hw_pending_work,
+		(void (*)(struct work_struct *))scst_hw_pending_work_fn);
+
+#ifdef CONFIG_SCST_MEASURE_LATENCY
+	spin_lock_init(&sess->lat_lock);
+#endif
+
+	sess->initiator_name = kstrdup(initiator_name, gfp_mask);
+	if (sess->initiator_name == NULL) {
+		PRINT_ERROR("%s", "Unable to dup sess->initiator_name");
+		goto out_free;
+	}
+
+out:
+	return sess;
+
+out_free:
+	kmem_cache_free(scst_sess_cachep, sess);
+	sess = NULL;
+	goto out;
+}
+
+void scst_free_session(struct scst_session *sess)
+{
+
+	mutex_lock(&scst_mutex);
+
+	scst_sess_free_tgt_devs(sess);
+
+	/* tgt will stay alive at least until its sysfs alive */
+	kobject_get(&sess->tgt->tgt_kobj);
+
+	mutex_unlock(&scst_mutex);
+	scst_sess_sysfs_del(sess);
+	mutex_lock(&scst_mutex);
+
+	/*
+	 * The lists delete must be after sysfs del. Otherwise it would break
+	 * logic in scst_sess_sysfs_create() to avoid duplicate sysfs names.
+	 */
+
+	TRACE_DBG("Removing sess %p from the list", sess);
+	list_del(&sess->sess_list_entry);
+	TRACE_DBG("Removing session %p from acg %s", sess, sess->acg->acg_name);
+	list_del(&sess->acg_sess_list_entry);
+
+	mutex_unlock(&scst_mutex);
+
+	wake_up_all(&sess->tgt->unreg_waitQ);
+
+	kobject_put(&sess->tgt->tgt_kobj);
+
+	kfree(sess->transport_id);
+	kfree(sess->initiator_name);
+
+	kmem_cache_free(scst_sess_cachep, sess);
+	return;
+}
+
+void scst_free_session_callback(struct scst_session *sess)
+{
+	struct completion *c;
+
+	TRACE_DBG("Freeing session %p", sess);
+
+	cancel_delayed_work_sync(&sess->hw_pending_work);
+
+	c = sess->shutdown_compl;
+
+	mutex_lock(&scst_mutex);
+	/*
+	 * Necessary to sync with other threads trying to queue AEN, which
+	 * the target driver will not be able to serve and crash, because after
+	 * unreg_done_fn() called its internal session data will be destroyed.
+	 */
+	sess->shut_phase = SCST_SESS_SPH_UNREG_DONE_CALLING;
+	mutex_unlock(&scst_mutex);
+
+	if (sess->unreg_done_fn) {
+		TRACE_DBG("Calling unreg_done_fn(%p)", sess);
+		sess->unreg_done_fn(sess);
+		TRACE_DBG("%s", "unreg_done_fn() returned");
+	}
+	scst_free_session(sess);
+
+	if (c)
+		complete_all(c);
+	return;
+}
+
+void scst_sched_session_free(struct scst_session *sess)
+{
+	unsigned long flags;
+
+	if (sess->shut_phase != SCST_SESS_SPH_SHUTDOWN) {
+		PRINT_CRIT_ERROR("session %p is going to shutdown with unknown "
+			"shut phase %lx", sess, sess->shut_phase);
+		BUG();
+	}
+
+	spin_lock_irqsave(&scst_mgmt_lock, flags);
+	TRACE_DBG("Adding sess %p to scst_sess_shut_list", sess);
+	list_add_tail(&sess->sess_shut_list_entry, &scst_sess_shut_list);
+	spin_unlock_irqrestore(&scst_mgmt_lock, flags);
+
+	wake_up(&scst_mgmt_waitQ);
+	return;
+}
+
+/**
+ * scst_cmd_get() - increase command's reference counter
+ */
+void scst_cmd_get(struct scst_cmd *cmd)
+{
+	__scst_cmd_get(cmd);
+}
+EXPORT_SYMBOL(scst_cmd_get);
+
+/**
+ * scst_cmd_put() - decrease command's reference counter
+ */
+void scst_cmd_put(struct scst_cmd *cmd)
+{
+	__scst_cmd_put(cmd);
+}
+EXPORT_SYMBOL(scst_cmd_put);
+
+struct scst_cmd *scst_alloc_cmd(gfp_t gfp_mask)
+{
+	struct scst_cmd *cmd;
+
+	cmd = kmem_cache_zalloc(scst_cmd_cachep, gfp_mask);
+	if (cmd == NULL) {
+		TRACE(TRACE_OUT_OF_MEM, "%s", "Allocation of scst_cmd failed");
+		goto out;
+	}
+
+	cmd->state = SCST_CMD_STATE_INIT_WAIT;
+	cmd->start_time = jiffies;
+	atomic_set(&cmd->cmd_ref, 1);
+	cmd->cmd_threads = &scst_main_cmd_threads;
+	INIT_LIST_HEAD(&cmd->mgmt_cmd_list);
+	cmd->queue_type = SCST_CMD_QUEUE_SIMPLE;
+	cmd->timeout = SCST_DEFAULT_TIMEOUT;
+	cmd->retries = 0;
+	cmd->data_len = -1;
+	cmd->is_send_status = 1;
+	cmd->resp_data_len = -1;
+	cmd->write_sg = &cmd->sg;
+	cmd->write_sg_cnt = &cmd->sg_cnt;
+
+	cmd->dbl_ua_orig_data_direction = SCST_DATA_UNKNOWN;
+	cmd->dbl_ua_orig_resp_data_len = -1;
+
+out:
+	return cmd;
+}
+
+static void scst_destroy_put_cmd(struct scst_cmd *cmd)
+{
+	scst_sess_put(cmd->sess);
+
+	/*
+	 * At this point tgt_dev can be dead, but the pointer remains non-NULL
+	 */
+	if (likely(cmd->tgt_dev != NULL))
+		__scst_put();
+
+	scst_destroy_cmd(cmd);
+	return;
+}
+
+/* No locks supposed to be held */
+void scst_free_cmd(struct scst_cmd *cmd)
+{
+	int destroy = 1;
+
+	TRACE_DBG("Freeing cmd %p (tag %llu)",
+		  cmd, (long long unsigned int)cmd->tag);
+
+	if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) {
+		TRACE_MGMT_DBG("Freeing aborted cmd %p (scst_cmd_count %d)",
+			cmd, atomic_read(&scst_cmd_count));
+	}
+
+	BUG_ON(cmd->inc_blocking || cmd->needs_unblocking ||
+		cmd->dec_on_dev_needed);
+
+	/*
+	 * Target driver can already free sg buffer before calling
+	 * scst_tgt_cmd_done(). E.g., scst_local has to do that.
+	 */
+	if (!cmd->tgt_data_buf_alloced)
+		scst_check_restore_sg_buff(cmd);
+
+	if ((cmd->tgtt->on_free_cmd != NULL) && likely(!cmd->internal)) {
+		TRACE_DBG("Calling target's on_free_cmd(%p)", cmd);
+		scst_set_cur_start(cmd);
+		cmd->tgtt->on_free_cmd(cmd);
+		scst_set_tgt_on_free_time(cmd);
+		TRACE_DBG("%s", "Target's on_free_cmd() returned");
+	}
+
+	if (likely(cmd->dev != NULL)) {
+		struct scst_dev_type *handler = cmd->dev->handler;
+		if (handler->on_free_cmd != NULL) {
+			TRACE_DBG("Calling dev handler %s on_free_cmd(%p)",
+				handler->name, cmd);
+			scst_set_cur_start(cmd);
+			handler->on_free_cmd(cmd);
+			scst_set_dev_on_free_time(cmd);
+			TRACE_DBG("Dev handler %s on_free_cmd() returned",
+				handler->name);
+		}
+	}
+
+	scst_release_space(cmd);
+
+	if (unlikely(cmd->sense != NULL)) {
+		TRACE_MEM("Releasing sense %p (cmd %p)", cmd->sense, cmd);
+		mempool_free(cmd->sense, scst_sense_mempool);
+		cmd->sense = NULL;
+	}
+
+	if (likely(cmd->tgt_dev != NULL)) {
+#ifdef CONFIG_SCST_EXTRACHECKS
+		if (unlikely(!cmd->sent_for_exec) && !cmd->internal) {
+			PRINT_ERROR("Finishing not executed cmd %p (opcode "
+			    "%d, target %s, LUN %lld, sn %d, expected_sn %d)",
+			    cmd, cmd->cdb[0], cmd->tgtt->name,
+			    (long long unsigned int)cmd->lun,
+			    cmd->sn, cmd->tgt_dev->expected_sn);
+			scst_unblock_deferred(cmd->tgt_dev, cmd);
+		}
+#endif
+
+		if (unlikely(cmd->out_of_sn)) {
+			TRACE_SN("Out of SN cmd %p (tag %llu, sn %d), "
+				"destroy=%d", cmd,
+				(long long unsigned int)cmd->tag,
+				cmd->sn, destroy);
+			destroy = test_and_set_bit(SCST_CMD_CAN_BE_DESTROYED,
+					&cmd->cmd_flags);
+		}
+	}
+
+	if (likely(destroy))
+		scst_destroy_put_cmd(cmd);
+	return;
+}
+
+/* No locks supposed to be held. */
+void scst_check_retries(struct scst_tgt *tgt)
+{
+	int need_wake_up = 0;
+
+	/*
+	 * We don't worry about overflow of finished_cmds, because we check
+	 * only for its change.
+	 */
+	atomic_inc(&tgt->finished_cmds);
+	/* See comment in scst_queue_retry_cmd() */
+	smp_mb__after_atomic_inc();
+	if (unlikely(tgt->retry_cmds > 0)) {
+		struct scst_cmd *c, *tc;
+		unsigned long flags;
+
+		TRACE_RETRY("Checking retry cmd list (retry_cmds %d)",
+		      tgt->retry_cmds);
+
+		spin_lock_irqsave(&tgt->tgt_lock, flags);
+		list_for_each_entry_safe(c, tc, &tgt->retry_cmd_list,
+				cmd_list_entry) {
+			tgt->retry_cmds--;
+
+			TRACE_RETRY("Moving retry cmd %p to head of active "
+				"cmd list (retry_cmds left %d)",
+				c, tgt->retry_cmds);
+			spin_lock(&c->cmd_threads->cmd_list_lock);
+			list_move(&c->cmd_list_entry,
+				  &c->cmd_threads->active_cmd_list);
+			wake_up(&c->cmd_threads->cmd_list_waitQ);
+			spin_unlock(&c->cmd_threads->cmd_list_lock);
+
+			need_wake_up++;
+			if (need_wake_up >= 2) /* "slow start" */
+				break;
+		}
+		spin_unlock_irqrestore(&tgt->tgt_lock, flags);
+	}
+	return;
+}
+
+static void scst_tgt_retry_timer_fn(unsigned long arg)
+{
+	struct scst_tgt *tgt = (struct scst_tgt *)arg;
+	unsigned long flags;
+
+	TRACE_RETRY("Retry timer expired (retry_cmds %d)", tgt->retry_cmds);
+
+	spin_lock_irqsave(&tgt->tgt_lock, flags);
+	tgt->retry_timer_active = 0;
+	spin_unlock_irqrestore(&tgt->tgt_lock, flags);
+
+	scst_check_retries(tgt);
+	return;
+}
+
+struct scst_mgmt_cmd *scst_alloc_mgmt_cmd(gfp_t gfp_mask)
+{
+	struct scst_mgmt_cmd *mcmd;
+
+	mcmd = mempool_alloc(scst_mgmt_mempool, gfp_mask);
+	if (mcmd == NULL) {
+		PRINT_CRIT_ERROR("%s", "Allocation of management command "
+			"failed, some commands and their data could leak");
+		goto out;
+	}
+	memset(mcmd, 0, sizeof(*mcmd));
+
+out:
+	return mcmd;
+}
+
+void scst_free_mgmt_cmd(struct scst_mgmt_cmd *mcmd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&mcmd->sess->sess_list_lock, flags);
+	atomic_dec(&mcmd->sess->sess_cmd_count);
+	spin_unlock_irqrestore(&mcmd->sess->sess_list_lock, flags);
+
+	scst_sess_put(mcmd->sess);
+
+	if (mcmd->mcmd_tgt_dev != NULL)
+		__scst_put();
+
+	mempool_free(mcmd, scst_mgmt_mempool);
+	return;
+}
+
+static bool scst_on_sg_tablesize_low(struct scst_cmd *cmd, bool out)
+{
+	bool res;
+	int sg_cnt = out ? cmd->out_sg_cnt : cmd->sg_cnt;
+	static int ll;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+
+	if (sg_cnt > cmd->tgt->sg_tablesize) {
+		/* It's the target's side business */
+		goto failed;
+	}
+
+	if (tgt_dev->dev->handler->on_sg_tablesize_low == NULL)
+		goto failed;
+
+	res = tgt_dev->dev->handler->on_sg_tablesize_low(cmd);
+
+	TRACE_DBG("on_sg_tablesize_low(%p) returned %d", cmd, res);
+
+out:
+	return res;
+
+failed:
+	res = false;
+	if ((ll < 10) || TRACING_MINOR()) {
+		PRINT_INFO("Unable to complete command due to SG IO count "
+			"limitation (%srequested %d, available %d, tgt lim %d)",
+			out ? "OUT buffer, " : "", cmd->sg_cnt,
+			tgt_dev->max_sg_cnt, cmd->tgt->sg_tablesize);
+		ll++;
+	}
+	goto out;
+}
+
+int scst_alloc_space(struct scst_cmd *cmd)
+{
+	gfp_t gfp_mask;
+	int res = -ENOMEM;
+	int atomic = scst_cmd_atomic(cmd);
+	int flags;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+
+	gfp_mask = tgt_dev->gfp_mask | (atomic ? GFP_ATOMIC : GFP_KERNEL);
+
+	flags = atomic ? SGV_POOL_NO_ALLOC_ON_CACHE_MISS : 0;
+	if (cmd->no_sgv)
+		flags |= SGV_POOL_ALLOC_NO_CACHED;
+
+	cmd->sg = sgv_pool_alloc(tgt_dev->pool, cmd->bufflen, gfp_mask, flags,
+			&cmd->sg_cnt, &cmd->sgv, &cmd->dev->dev_mem_lim, NULL);
+	if (cmd->sg == NULL)
+		goto out;
+
+	if (unlikely(cmd->sg_cnt > tgt_dev->max_sg_cnt))
+		if (!scst_on_sg_tablesize_low(cmd, false))
+			goto out_sg_free;
+
+	if (cmd->data_direction != SCST_DATA_BIDI)
+		goto success;
+
+	cmd->out_sg = sgv_pool_alloc(tgt_dev->pool, cmd->out_bufflen, gfp_mask,
+			 flags, &cmd->out_sg_cnt, &cmd->out_sgv,
+			 &cmd->dev->dev_mem_lim, NULL);
+	if (cmd->out_sg == NULL)
+		goto out_sg_free;
+
+	if (unlikely(cmd->out_sg_cnt > tgt_dev->max_sg_cnt))
+		if (!scst_on_sg_tablesize_low(cmd, true))
+			goto out_out_sg_free;
+
+success:
+	res = 0;
+
+out:
+	return res;
+
+out_out_sg_free:
+	sgv_pool_free(cmd->out_sgv, &cmd->dev->dev_mem_lim);
+	cmd->out_sgv = NULL;
+	cmd->out_sg = NULL;
+	cmd->out_sg_cnt = 0;
+
+out_sg_free:
+	sgv_pool_free(cmd->sgv, &cmd->dev->dev_mem_lim);
+	cmd->sgv = NULL;
+	cmd->sg = NULL;
+	cmd->sg_cnt = 0;
+	goto out;
+}
+
+static void scst_release_space(struct scst_cmd *cmd)
+{
+
+	if (cmd->sgv == NULL) {
+		if ((cmd->sg != NULL) &&
+		    !(cmd->tgt_data_buf_alloced || cmd->dh_data_buf_alloced)) {
+			TRACE_MEM("Freeing sg %p for cmd %p (cnt %d)", cmd->sg,
+				cmd, cmd->sg_cnt);
+			scst_free(cmd->sg, cmd->sg_cnt);
+			goto out_zero;
+		} else
+			goto out;
+	}
+
+	if (cmd->tgt_data_buf_alloced || cmd->dh_data_buf_alloced) {
+		TRACE_MEM("%s", "*data_buf_alloced set, returning");
+		goto out;
+	}
+
+	if (cmd->out_sgv != NULL) {
+		sgv_pool_free(cmd->out_sgv, &cmd->dev->dev_mem_lim);
+		cmd->out_sgv = NULL;
+		cmd->out_sg_cnt = 0;
+		cmd->out_sg = NULL;
+		cmd->out_bufflen = 0;
+	}
+
+	sgv_pool_free(cmd->sgv, &cmd->dev->dev_mem_lim);
+
+out_zero:
+	cmd->sgv = NULL;
+	cmd->sg_cnt = 0;
+	cmd->sg = NULL;
+	cmd->bufflen = 0;
+	cmd->data_len = 0;
+
+out:
+	return;
+}
+
+static void scsi_end_async(struct request *req, int error)
+{
+	struct scsi_io_context *sioc = req->end_io_data;
+
+	TRACE_DBG("sioc %p, cmd %p", sioc, sioc->data);
+
+	if (sioc->done)
+		sioc->done(sioc->data, sioc->sense, req->errors, req->resid_len);
+
+	if (!sioc->full_cdb_used)
+		kmem_cache_free(scsi_io_context_cache, sioc);
+	else
+		kfree(sioc);
+
+	__blk_put_request(req->q, req);
+	return;
+}
+
+/**
+ * scst_scsi_exec_async - executes a SCSI command in pass-through mode
+ * @cmd:	scst command
+ * @data:	pointer passed to done() as "data"
+ * @done:	callback function when done
+ */
+int scst_scsi_exec_async(struct scst_cmd *cmd, void *data,
+	void (*done)(void *data, char *sense, int result, int resid))
+{
+	int res = 0;
+	struct request_queue *q = cmd->dev->scsi_dev->request_queue;
+	struct request *rq;
+	struct scsi_io_context *sioc;
+	int write = (cmd->data_direction & SCST_DATA_WRITE) ? WRITE : READ;
+	gfp_t gfp = GFP_KERNEL;
+	int cmd_len = cmd->cdb_len;
+
+	if (cmd->ext_cdb_len == 0) {
+		TRACE_DBG("Simple CDB (cmd_len %d)", cmd_len);
+		sioc = kmem_cache_zalloc(scsi_io_context_cache, gfp);
+		if (sioc == NULL) {
+			res = -ENOMEM;
+			goto out;
+		}
+	} else {
+		cmd_len += cmd->ext_cdb_len;
+
+		TRACE_DBG("Extended CDB (cmd_len %d)", cmd_len);
+
+		sioc = kzalloc(sizeof(*sioc) + cmd_len, gfp);
+		if (sioc == NULL) {
+			res = -ENOMEM;
+			goto out;
+		}
+
+		sioc->full_cdb_used = 1;
+
+		memcpy(sioc->full_cdb, cmd->cdb, cmd->cdb_len);
+		memcpy(&sioc->full_cdb[cmd->cdb_len], cmd->ext_cdb,
+			cmd->ext_cdb_len);
+	}
+
+	rq = blk_get_request(q, write, gfp);
+	if (rq == NULL) {
+		res = -ENOMEM;
+		goto out_free_sioc;
+	}
+
+	rq->cmd_type = REQ_TYPE_BLOCK_PC;
+	rq->cmd_flags |= REQ_QUIET;
+
+	if (cmd->sg == NULL)
+		goto done;
+
+	if (cmd->data_direction == SCST_DATA_BIDI) {
+		struct request *next_rq;
+
+		if (!test_bit(QUEUE_FLAG_BIDI, &q->queue_flags)) {
+			res = -EOPNOTSUPP;
+			goto out_free_rq;
+		}
+
+		res = blk_rq_map_kern_sg(rq, cmd->out_sg, cmd->out_sg_cnt, gfp);
+		if (res != 0) {
+			TRACE_DBG("blk_rq_map_kern_sg() failed: %d", res);
+			goto out_free_rq;
+		}
+
+		next_rq = blk_get_request(q, READ, gfp);
+		if (next_rq == NULL) {
+			res = -ENOMEM;
+			goto out_free_unmap;
+		}
+		rq->next_rq = next_rq;
+		next_rq->cmd_type = rq->cmd_type;
+
+		res = blk_rq_map_kern_sg(next_rq, cmd->sg, cmd->sg_cnt, gfp);
+		if (res != 0) {
+			TRACE_DBG("blk_rq_map_kern_sg() failed: %d", res);
+			goto out_free_unmap;
+		}
+	} else {
+		res = blk_rq_map_kern_sg(rq, cmd->sg, cmd->sg_cnt, gfp);
+		if (res != 0) {
+			TRACE_DBG("blk_rq_map_kern_sg() failed: %d", res);
+			goto out_free_rq;
+		}
+	}
+
+done:
+	TRACE_DBG("sioc %p, cmd %p", sioc, cmd);
+
+	sioc->data = data;
+	sioc->done = done;
+
+	rq->cmd_len = cmd_len;
+	if (cmd->ext_cdb_len == 0) {
+		memset(rq->cmd, 0, BLK_MAX_CDB); /* ATAPI hates garbage after CDB */
+		memcpy(rq->cmd, cmd->cdb, cmd->cdb_len);
+	} else
+		rq->cmd = sioc->full_cdb;
+
+	rq->sense = sioc->sense;
+	rq->sense_len = sizeof(sioc->sense);
+	rq->timeout = cmd->timeout;
+	rq->retries = cmd->retries;
+	rq->end_io_data = sioc;
+
+	blk_execute_rq_nowait(rq->q, NULL, rq,
+		(cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE), scsi_end_async);
+out:
+	return res;
+
+out_free_unmap:
+	if (rq->next_rq != NULL) {
+		blk_put_request(rq->next_rq);
+		rq->next_rq = NULL;
+	}
+	blk_rq_unmap_kern_sg(rq, res);
+
+out_free_rq:
+	blk_put_request(rq);
+
+out_free_sioc:
+	if (!sioc->full_cdb_used)
+		kmem_cache_free(scsi_io_context_cache, sioc);
+	else
+		kfree(sioc);
+	goto out;
+}
+EXPORT_SYMBOL(scst_scsi_exec_async);
+
+/**
+ * scst_copy_sg() - copy data between the command's SGs
+ *
+ * Copies data between cmd->tgt_sg and cmd->sg in direction defined by
+ * copy_dir parameter.
+ */
+void scst_copy_sg(struct scst_cmd *cmd, enum scst_sg_copy_dir copy_dir)
+{
+	struct scatterlist *src_sg, *dst_sg;
+	unsigned int to_copy;
+	int atomic = scst_cmd_atomic(cmd);
+
+	if (copy_dir == SCST_SG_COPY_FROM_TARGET) {
+		if (cmd->data_direction != SCST_DATA_BIDI) {
+			src_sg = cmd->tgt_sg;
+			dst_sg = cmd->sg;
+			to_copy = cmd->bufflen;
+		} else {
+			TRACE_MEM("BIDI cmd %p", cmd);
+			src_sg = cmd->tgt_out_sg;
+			dst_sg = cmd->out_sg;
+			to_copy = cmd->out_bufflen;
+		}
+	} else {
+		src_sg = cmd->sg;
+		dst_sg = cmd->tgt_sg;
+		to_copy = cmd->resp_data_len;
+	}
+
+	TRACE_MEM("cmd %p, copy_dir %d, src_sg %p, dst_sg %p, to_copy %lld",
+		cmd, copy_dir, src_sg, dst_sg, (long long)to_copy);
+
+	if (unlikely(src_sg == NULL) || unlikely(dst_sg == NULL)) {
+		/*
+		 * It can happened, e.g., with scst_user for cmd with delay
+		 * alloc, which failed with Check Condition.
+		 */
+		goto out;
+	}
+
+	sg_copy(dst_sg, src_sg, 0, to_copy,
+		atomic ? KM_SOFTIRQ0 : KM_USER0,
+		atomic ? KM_SOFTIRQ1 : KM_USER1);
+
+out:
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_copy_sg);
+
+int scst_get_full_buf(struct scst_cmd *cmd, uint8_t **buf)
+{
+	int res = 0;
+
+	EXTRACHECKS_BUG_ON(cmd->sg_buff_vmallocated);
+
+	if (scst_get_buf_count(cmd) > 1) {
+		int len;
+		uint8_t *tmp_buf;
+		int full_size;
+
+		full_size = 0;
+		len = scst_get_buf_first(cmd, &tmp_buf);
+		while (len > 0) {
+			full_size += len;
+			scst_put_buf(cmd, tmp_buf);
+			len = scst_get_buf_next(cmd, &tmp_buf);
+		}
+
+		*buf = vmalloc(full_size);
+		if (*buf == NULL) {
+			TRACE(TRACE_OUT_OF_MEM, "vmalloc() failed for opcode "
+				"%x", cmd->cdb[0]);
+			res = -ENOMEM;
+			goto out;
+		}
+		cmd->sg_buff_vmallocated = 1;
+
+		if (scst_cmd_get_data_direction(cmd) == SCST_DATA_WRITE) {
+			uint8_t *buf_ptr;
+
+			buf_ptr = *buf;
+
+			len = scst_get_buf_first(cmd, &tmp_buf);
+			while (len > 0) {
+				memcpy(buf_ptr, tmp_buf, len);
+				buf_ptr += len;
+
+				scst_put_buf(cmd, tmp_buf);
+				len = scst_get_buf_next(cmd, &tmp_buf);
+			}
+		}
+		res = full_size;
+	} else
+		res = scst_get_buf_first(cmd, buf);
+
+out:
+	return res;
+}
+
+void scst_put_full_buf(struct scst_cmd *cmd, uint8_t *buf)
+{
+
+	if (buf == NULL)
+		goto out;
+
+	if (cmd->sg_buff_vmallocated) {
+		if (scst_cmd_get_data_direction(cmd) == SCST_DATA_READ) {
+			int len;
+			uint8_t *tmp_buf, *buf_p;
+
+			buf_p = buf;
+
+			len = scst_get_buf_first(cmd, &tmp_buf);
+			while (len > 0) {
+				memcpy(tmp_buf, buf_p, len);
+				buf_p += len;
+
+				scst_put_buf(cmd, tmp_buf);
+				len = scst_get_buf_next(cmd, &tmp_buf);
+			}
+
+		}
+
+		cmd->sg_buff_vmallocated = 0;
+
+		vfree(buf);
+	} else
+		scst_put_buf(cmd, buf);
+
+out:
+	return;
+}
+
+static const int SCST_CDB_LENGTH[8] = { 6, 10, 10, 0, 16, 12, 0, 0 };
+
+#define SCST_CDB_GROUP(opcode)   ((opcode >> 5) & 0x7)
+#define SCST_GET_CDB_LEN(opcode) SCST_CDB_LENGTH[SCST_CDB_GROUP(opcode)]
+
+/* get_trans_len_x extract x bytes from cdb as length starting from off */
+
+static int get_trans_cdb_len_10(struct scst_cmd *cmd, uint8_t off)
+{
+	cmd->cdb_len = 10;
+	cmd->bufflen = 0;
+	return 0;
+}
+
+static int get_trans_len_block_limit(struct scst_cmd *cmd, uint8_t off)
+{
+	cmd->bufflen = 6;
+	return 0;
+}
+
+static int get_trans_len_read_capacity(struct scst_cmd *cmd, uint8_t off)
+{
+	cmd->bufflen = 8;
+	return 0;
+}
+
+static int get_trans_len_serv_act_in(struct scst_cmd *cmd, uint8_t off)
+{
+	int res = 0;
+
+	if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16) {
+		cmd->op_name = "READ CAPACITY(16)";
+		cmd->bufflen = be32_to_cpu(get_unaligned((__be32 *)&cmd->cdb[10]));
+		cmd->op_flags |= SCST_IMPLICIT_HQ|SCST_REG_RESERVE_ALLOWED;
+	} else
+		cmd->op_flags |= SCST_UNKNOWN_LENGTH;
+	return res;
+}
+
+static int get_trans_len_single(struct scst_cmd *cmd, uint8_t off)
+{
+	cmd->bufflen = 1;
+	return 0;
+}
+
+static int get_trans_len_read_pos(struct scst_cmd *cmd, uint8_t off)
+{
+	uint8_t *p = (uint8_t *)cmd->cdb + off;
+	int res = 0;
+
+	cmd->bufflen = 0;
+	cmd->bufflen |= ((u32)p[0]) << 8;
+	cmd->bufflen |= ((u32)p[1]);
+
+	switch (cmd->cdb[1] & 0x1f) {
+	case 0:
+	case 1:
+	case 6:
+		if (cmd->bufflen != 0) {
+			PRINT_ERROR("READ POSITION: Invalid non-zero (%d) "
+				"allocation length for service action %x",
+				cmd->bufflen, cmd->cdb[1] & 0x1f);
+			goto out_inval;
+		}
+		break;
+	}
+
+	switch (cmd->cdb[1] & 0x1f) {
+	case 0:
+	case 1:
+		cmd->bufflen = 20;
+		break;
+	case 6:
+		cmd->bufflen = 32;
+		break;
+	case 8:
+		cmd->bufflen = max(28, cmd->bufflen);
+		break;
+	default:
+		PRINT_ERROR("READ POSITION: Invalid service action %x",
+			cmd->cdb[1] & 0x1f);
+		goto out_inval;
+	}
+
+out:
+	return res;
+
+out_inval:
+	scst_set_cmd_error(cmd,
+		SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+	res = 1;
+	goto out;
+}
+
+static int get_trans_len_prevent_allow_medium_removal(struct scst_cmd *cmd,
+	uint8_t off)
+{
+	if ((cmd->cdb[4] & 3) == 0)
+		cmd->op_flags |= SCST_REG_RESERVE_ALLOWED |
+			SCST_WRITE_EXCL_ALLOWED | SCST_EXCL_ACCESS_ALLOWED;
+	return 0;
+}
+
+static int get_trans_len_start_stop(struct scst_cmd *cmd, uint8_t off)
+{
+	if ((cmd->cdb[4] & 0xF1) == 0x1)
+		cmd->op_flags |= SCST_REG_RESERVE_ALLOWED |
+			SCST_WRITE_EXCL_ALLOWED | SCST_EXCL_ACCESS_ALLOWED;
+	return 0;
+}
+
+static int get_trans_len_3_read_elem_stat(struct scst_cmd *cmd, uint8_t off)
+{
+	const uint8_t *p = cmd->cdb + off;
+
+	cmd->bufflen = 0;
+	cmd->bufflen |= ((u32)p[0]) << 16;
+	cmd->bufflen |= ((u32)p[1]) << 8;
+	cmd->bufflen |= ((u32)p[2]);
+
+	if ((cmd->cdb[6] & 0x2) == 0x2)
+		cmd->op_flags |= SCST_REG_RESERVE_ALLOWED |
+			SCST_WRITE_EXCL_ALLOWED | SCST_EXCL_ACCESS_ALLOWED;
+	return 0;
+}
+
+static int get_trans_len_1(struct scst_cmd *cmd, uint8_t off)
+{
+	cmd->bufflen = (u32)cmd->cdb[off];
+	return 0;
+}
+
+static int get_trans_len_1_256(struct scst_cmd *cmd, uint8_t off)
+{
+	cmd->bufflen = (u32)cmd->cdb[off];
+	if (cmd->bufflen == 0)
+		cmd->bufflen = 256;
+	return 0;
+}
+
+static int get_trans_len_2(struct scst_cmd *cmd, uint8_t off)
+{
+	const uint8_t *p = cmd->cdb + off;
+
+	cmd->bufflen = 0;
+	cmd->bufflen |= ((u32)p[0]) << 8;
+	cmd->bufflen |= ((u32)p[1]);
+
+	return 0;
+}
+
+static int get_trans_len_3(struct scst_cmd *cmd, uint8_t off)
+{
+	const uint8_t *p = cmd->cdb + off;
+
+	cmd->bufflen = 0;
+	cmd->bufflen |= ((u32)p[0]) << 16;
+	cmd->bufflen |= ((u32)p[1]) << 8;
+	cmd->bufflen |= ((u32)p[2]);
+
+	return 0;
+}
+
+static int get_trans_len_4(struct scst_cmd *cmd, uint8_t off)
+{
+	const uint8_t *p = cmd->cdb + off;
+
+	cmd->bufflen = 0;
+	cmd->bufflen |= ((u32)p[0]) << 24;
+	cmd->bufflen |= ((u32)p[1]) << 16;
+	cmd->bufflen |= ((u32)p[2]) << 8;
+	cmd->bufflen |= ((u32)p[3]);
+
+	return 0;
+}
+
+static int get_trans_len_none(struct scst_cmd *cmd, uint8_t off)
+{
+	cmd->bufflen = 0;
+	return 0;
+}
+
+static int get_bidi_trans_len_2(struct scst_cmd *cmd, uint8_t off)
+{
+	const uint8_t *p = cmd->cdb + off;
+
+	cmd->bufflen = 0;
+	cmd->bufflen |= ((u32)p[0]) << 8;
+	cmd->bufflen |= ((u32)p[1]);
+
+	cmd->out_bufflen = cmd->bufflen;
+
+	return 0;
+}
+
+/**
+ * scst_get_cdb_info() - fill various info about the command's CDB
+ *
+ * Description:
+ *    Fills various info about the command's CDB in the corresponding fields
+ *    in the command.
+ *
+ *    Returns: 0 on success, <0 if command is unknown, >0 if command
+ *    is invalid.
+ */
+int scst_get_cdb_info(struct scst_cmd *cmd)
+{
+	int dev_type = cmd->dev->type;
+	int i, res = 0;
+	uint8_t op;
+	const struct scst_sdbops *ptr = NULL;
+
+	op = cmd->cdb[0];	/* get clear opcode */
+
+	TRACE_DBG("opcode=%02x, cdblen=%d bytes, tblsize=%d, "
+		"dev_type=%d", op, SCST_GET_CDB_LEN(op), SCST_CDB_TBL_SIZE,
+		dev_type);
+
+	i = scst_scsi_op_list[op];
+	while (i < SCST_CDB_TBL_SIZE && scst_scsi_op_table[i].ops == op) {
+		if (scst_scsi_op_table[i].devkey[dev_type] != SCST_CDB_NOTSUPP) {
+			ptr = &scst_scsi_op_table[i];
+			TRACE_DBG("op = 0x%02x+'%c%c%c%c%c%c%c%c%c%c'+<%s>",
+			      ptr->ops, ptr->devkey[0],	/* disk     */
+			      ptr->devkey[1],	/* tape     */
+			      ptr->devkey[2],	/* printer */
+			      ptr->devkey[3],	/* cpu      */
+			      ptr->devkey[4],	/* cdr      */
+			      ptr->devkey[5],	/* cdrom    */
+			      ptr->devkey[6],	/* scanner */
+			      ptr->devkey[7],	/* worm     */
+			      ptr->devkey[8],	/* changer */
+			      ptr->devkey[9],	/* commdev */
+			      ptr->op_name);
+			TRACE_DBG("direction=%d flags=%d off=%d",
+			      ptr->direction,
+			      ptr->flags,
+			      ptr->off);
+			break;
+		}
+		i++;
+	}
+
+	if (unlikely(ptr == NULL)) {
+		/* opcode not found or now not used */
+		TRACE(TRACE_MINOR, "Unknown opcode 0x%x for type %d", op,
+		      dev_type);
+		res = -1;
+		goto out;
+	}
+
+	cmd->cdb_len = SCST_GET_CDB_LEN(op);
+	cmd->op_name = ptr->op_name;
+	cmd->data_direction = ptr->direction;
+	cmd->op_flags = ptr->flags | SCST_INFO_VALID;
+	res = (*ptr->get_trans_len)(cmd, ptr->off);
+
+out:
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_get_cdb_info);
+
+/* Packs SCST LUN back to SCSI form */
+__be64 scst_pack_lun(const uint64_t lun, unsigned int addr_method)
+{
+	uint64_t res;
+	uint16_t *p = (uint16_t *)&res;
+
+	res = lun;
+
+	if ((addr_method == SCST_LUN_ADDR_METHOD_FLAT) && (lun != 0)) {
+		/*
+		 * Flat space: luns other than 0 should use flat space
+		 * addressing method.
+		 */
+		*p = 0x7fff & *p;
+		*p = 0x4000 | *p;
+	}
+	/* Default is to use peripheral device addressing mode */
+
+	*p = (__force u16)cpu_to_be16(*p);
+	return (__force __be64)res;
+}
+
+/*
+ * Routine to extract a lun number from an 8-byte LUN structure
+ * in network byte order (BE).
+ * (see SAM-2, Section 4.12.3 page 40)
+ * Supports 2 types of lun unpacking: peripheral and logical unit.
+ */
+uint64_t scst_unpack_lun(const uint8_t *lun, int len)
+{
+	uint64_t res = NO_SUCH_LUN;
+	int address_method;
+
+	TRACE_BUFF_FLAG(TRACE_DEBUG, "Raw LUN", lun, len);
+
+	if (unlikely(len < 2)) {
+		PRINT_ERROR("Illegal lun length %d, expected 2 bytes or "
+			"more", len);
+		goto out;
+	}
+
+	if (len > 2) {
+		switch (len) {
+		case 8:
+			if ((*((__be64 *)lun) &
+			  __constant_cpu_to_be64(0x0000FFFFFFFFFFFFLL)) != 0)
+				goto out_err;
+			break;
+		case 4:
+			if (*((__be16 *)&lun[2]) != 0)
+				goto out_err;
+			break;
+		case 6:
+			if (*((__be32 *)&lun[2]) != 0)
+				goto out_err;
+			break;
+		default:
+			goto out_err;
+		}
+	}
+
+	address_method = (*lun) >> 6;	/* high 2 bits of byte 0 */
+	switch (address_method) {
+	case 0:	/* peripheral device addressing method */
+#if 0
+		if (*lun) {
+			PRINT_ERROR("Illegal BUS INDENTIFIER in LUN "
+			     "peripheral device addressing method 0x%02x, "
+			     "expected 0", *lun);
+			break;
+		}
+		res = *(lun + 1);
+		break;
+#else
+		/*
+		 * Looks like it's legal to use it as flat space addressing
+		 * method as well
+		 */
+
+		/* go through */
+#endif
+
+	case 1:	/* flat space addressing method */
+		res = *(lun + 1) | (((*lun) & 0x3f) << 8);
+		break;
+
+	case 2:	/* logical unit addressing method */
+		if (*lun & 0x3f) {
+			PRINT_ERROR("Illegal BUS NUMBER in LUN logical unit "
+				    "addressing method 0x%02x, expected 0",
+				    *lun & 0x3f);
+			break;
+		}
+		if (*(lun + 1) & 0xe0) {
+			PRINT_ERROR("Illegal TARGET in LUN logical unit "
+				    "addressing method 0x%02x, expected 0",
+				    (*(lun + 1) & 0xf8) >> 5);
+			break;
+		}
+		res = *(lun + 1) & 0x1f;
+		break;
+
+	case 3:	/* extended logical unit addressing method */
+	default:
+		PRINT_ERROR("Unimplemented LUN addressing method %u",
+			    address_method);
+		break;
+	}
+
+out:
+	return res;
+
+out_err:
+	PRINT_ERROR("%s", "Multi-level LUN unimplemented");
+	goto out;
+}
+
+/**
+ ** Generic parse() support routines.
+ ** Done via pointer on functions to avoid unneeded dereferences on
+ ** the fast path.
+ **/
+
+/**
+ * scst_calc_block_shift() - calculate block shift
+ *
+ * Calculates and returns block shift for the given sector size
+ */
+int scst_calc_block_shift(int sector_size)
+{
+	int block_shift = 0;
+	int t;
+
+	if (sector_size == 0)
+		sector_size = 512;
+
+	t = sector_size;
+	while (1) {
+		if ((t & 1) != 0)
+			break;
+		t >>= 1;
+		block_shift++;
+	}
+	if (block_shift < 9) {
+		PRINT_ERROR("Wrong sector size %d", sector_size);
+		block_shift = -1;
+	}
+	return block_shift;
+}
+EXPORT_SYMBOL_GPL(scst_calc_block_shift);
+
+/**
+ * scst_sbc_generic_parse() - generic SBC parsing
+ *
+ * Generic parse() for SBC (disk) devices
+ */
+int scst_sbc_generic_parse(struct scst_cmd *cmd,
+	int (*get_block_shift)(struct scst_cmd *cmd))
+{
+	int res = 0;
+
+	/*
+	 * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
+	 * therefore change them only if necessary
+	 */
+
+	TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
+	      cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
+
+	switch (cmd->cdb[0]) {
+	case VERIFY_6:
+	case VERIFY:
+	case VERIFY_12:
+	case VERIFY_16:
+		if ((cmd->cdb[1] & BYTCHK) == 0) {
+			cmd->data_len = cmd->bufflen << get_block_shift(cmd);
+			cmd->bufflen = 0;
+			goto set_timeout;
+		} else
+			cmd->data_len = 0;
+		break;
+	default:
+		/* It's all good */
+		break;
+	}
+
+	if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED) {
+		int block_shift = get_block_shift(cmd);
+		/*
+		 * No need for locks here, since *_detach() can not be
+		 * called, when there are existing commands.
+		 */
+		cmd->bufflen = cmd->bufflen << block_shift;
+		cmd->out_bufflen = cmd->out_bufflen << block_shift;
+	}
+
+set_timeout:
+	if ((cmd->op_flags & (SCST_SMALL_TIMEOUT | SCST_LONG_TIMEOUT)) == 0)
+		cmd->timeout = SCST_GENERIC_DISK_REG_TIMEOUT;
+	else if (cmd->op_flags & SCST_SMALL_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_DISK_SMALL_TIMEOUT;
+	else if (cmd->op_flags & SCST_LONG_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_DISK_LONG_TIMEOUT;
+
+	TRACE_DBG("res %d, bufflen %d, data_len %d, direct %d",
+	      res, cmd->bufflen, cmd->data_len, cmd->data_direction);
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_sbc_generic_parse);
+
+/**
+ * scst_cdrom_generic_parse() - generic MMC parse
+ *
+ * Generic parse() for MMC (cdrom) devices
+ */
+int scst_cdrom_generic_parse(struct scst_cmd *cmd,
+	int (*get_block_shift)(struct scst_cmd *cmd))
+{
+	int res = 0;
+
+	/*
+	 * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
+	 * therefore change them only if necessary
+	 */
+
+	TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
+	      cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
+
+	cmd->cdb[1] &= 0x1f;
+
+	switch (cmd->cdb[0]) {
+	case VERIFY_6:
+	case VERIFY:
+	case VERIFY_12:
+	case VERIFY_16:
+		if ((cmd->cdb[1] & BYTCHK) == 0) {
+			cmd->data_len = cmd->bufflen << get_block_shift(cmd);
+			cmd->bufflen = 0;
+			goto set_timeout;
+		}
+		break;
+	default:
+		/* It's all good */
+		break;
+	}
+
+	if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED) {
+		int block_shift = get_block_shift(cmd);
+		cmd->bufflen = cmd->bufflen << block_shift;
+		cmd->out_bufflen = cmd->out_bufflen << block_shift;
+	}
+
+set_timeout:
+	if ((cmd->op_flags & (SCST_SMALL_TIMEOUT | SCST_LONG_TIMEOUT)) == 0)
+		cmd->timeout = SCST_GENERIC_CDROM_REG_TIMEOUT;
+	else if (cmd->op_flags & SCST_SMALL_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_CDROM_SMALL_TIMEOUT;
+	else if (cmd->op_flags & SCST_LONG_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_CDROM_LONG_TIMEOUT;
+
+	TRACE_DBG("res=%d, bufflen=%d, direct=%d", res, cmd->bufflen,
+		cmd->data_direction);
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_cdrom_generic_parse);
+
+/**
+ * scst_modisk_generic_parse() - generic MO parse
+ *
+ * Generic parse() for MO disk devices
+ */
+int scst_modisk_generic_parse(struct scst_cmd *cmd,
+	int (*get_block_shift)(struct scst_cmd *cmd))
+{
+	int res = 0;
+
+	/*
+	 * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
+	 * therefore change them only if necessary
+	 */
+
+	TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
+	      cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
+
+	cmd->cdb[1] &= 0x1f;
+
+	switch (cmd->cdb[0]) {
+	case VERIFY_6:
+	case VERIFY:
+	case VERIFY_12:
+	case VERIFY_16:
+		if ((cmd->cdb[1] & BYTCHK) == 0) {
+			cmd->data_len = cmd->bufflen << get_block_shift(cmd);
+			cmd->bufflen = 0;
+			goto set_timeout;
+		}
+		break;
+	default:
+		/* It's all good */
+		break;
+	}
+
+	if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED) {
+		int block_shift = get_block_shift(cmd);
+		cmd->bufflen = cmd->bufflen << block_shift;
+		cmd->out_bufflen = cmd->out_bufflen << block_shift;
+	}
+
+set_timeout:
+	if ((cmd->op_flags & (SCST_SMALL_TIMEOUT | SCST_LONG_TIMEOUT)) == 0)
+		cmd->timeout = SCST_GENERIC_MODISK_REG_TIMEOUT;
+	else if (cmd->op_flags & SCST_SMALL_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_MODISK_SMALL_TIMEOUT;
+	else if (cmd->op_flags & SCST_LONG_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_MODISK_LONG_TIMEOUT;
+
+	TRACE_DBG("res=%d, bufflen=%d, direct=%d", res, cmd->bufflen,
+		cmd->data_direction);
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_modisk_generic_parse);
+
+/**
+ * scst_tape_generic_parse() - generic tape parse
+ *
+ * Generic parse() for tape devices
+ */
+int scst_tape_generic_parse(struct scst_cmd *cmd,
+	int (*get_block_size)(struct scst_cmd *cmd))
+{
+	int res = 0;
+
+	/*
+	 * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
+	 * therefore change them only if necessary
+	 */
+
+	TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
+	      cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
+
+	if (cmd->cdb[0] == READ_POSITION) {
+		int tclp = cmd->cdb[1] & 4;
+		int long_bit = cmd->cdb[1] & 2;
+		int bt = cmd->cdb[1] & 1;
+
+		if ((tclp == long_bit) && (!bt || !long_bit)) {
+			cmd->bufflen =
+			    tclp ? POSITION_LEN_LONG : POSITION_LEN_SHORT;
+			cmd->data_direction = SCST_DATA_READ;
+		} else {
+			cmd->bufflen = 0;
+			cmd->data_direction = SCST_DATA_NONE;
+		}
+	}
+
+	if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED & cmd->cdb[1]) {
+		int block_size = get_block_size(cmd);
+		cmd->bufflen = cmd->bufflen * block_size;
+		cmd->out_bufflen = cmd->out_bufflen * block_size;
+	}
+
+	if ((cmd->op_flags & (SCST_SMALL_TIMEOUT | SCST_LONG_TIMEOUT)) == 0)
+		cmd->timeout = SCST_GENERIC_TAPE_REG_TIMEOUT;
+	else if (cmd->op_flags & SCST_SMALL_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_TAPE_SMALL_TIMEOUT;
+	else if (cmd->op_flags & SCST_LONG_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_TAPE_LONG_TIMEOUT;
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_tape_generic_parse);
+
+static int scst_null_parse(struct scst_cmd *cmd)
+{
+	int res = 0;
+
+	/*
+	 * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
+	 * therefore change them only if necessary
+	 */
+
+	TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
+	      cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
+#if 0
+	switch (cmd->cdb[0]) {
+	default:
+		/* It's all good */
+		break;
+	}
+#endif
+	TRACE_DBG("res %d bufflen %d direct %d",
+	      res, cmd->bufflen, cmd->data_direction);
+	return res;
+}
+
+/**
+ * scst_changer_generic_parse() - generic changer parse
+ *
+ * Generic parse() for changer devices
+ */
+int scst_changer_generic_parse(struct scst_cmd *cmd,
+	int (*nothing)(struct scst_cmd *cmd))
+{
+	int res = scst_null_parse(cmd);
+
+	if (cmd->op_flags & SCST_LONG_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_CHANGER_LONG_TIMEOUT;
+	else
+		cmd->timeout = SCST_GENERIC_CHANGER_TIMEOUT;
+
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_changer_generic_parse);
+
+/**
+ * scst_processor_generic_parse - generic SCSI processor parse
+ *
+ * Generic parse() for SCSI processor devices
+ */
+int scst_processor_generic_parse(struct scst_cmd *cmd,
+	int (*nothing)(struct scst_cmd *cmd))
+{
+	int res = scst_null_parse(cmd);
+
+	if (cmd->op_flags & SCST_LONG_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_PROCESSOR_LONG_TIMEOUT;
+	else
+		cmd->timeout = SCST_GENERIC_PROCESSOR_TIMEOUT;
+
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_processor_generic_parse);
+
+/**
+ * scst_raid_generic_parse() - generic RAID parse
+ *
+ * Generic parse() for RAID devices
+ */
+int scst_raid_generic_parse(struct scst_cmd *cmd,
+	int (*nothing)(struct scst_cmd *cmd))
+{
+	int res = scst_null_parse(cmd);
+
+	if (cmd->op_flags & SCST_LONG_TIMEOUT)
+		cmd->timeout = SCST_GENERIC_RAID_LONG_TIMEOUT;
+	else
+		cmd->timeout = SCST_GENERIC_RAID_TIMEOUT;
+
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_raid_generic_parse);
+
+/**
+ ** Generic dev_done() support routines.
+ ** Done via pointer on functions to avoid unneeded dereferences on
+ ** the fast path.
+ **/
+
+/**
+ * scst_block_generic_dev_done() - generic SBC dev_done
+ *
+ * Generic dev_done() for block (SBC) devices
+ */
+int scst_block_generic_dev_done(struct scst_cmd *cmd,
+	void (*set_block_shift)(struct scst_cmd *cmd, int block_shift))
+{
+	int opcode = cmd->cdb[0];
+	int status = cmd->status;
+	int res = SCST_CMD_STATE_DEFAULT;
+
+	/*
+	 * SCST sets good defaults for cmd->is_send_status and
+	 * cmd->resp_data_len based on cmd->status and cmd->data_direction,
+	 * therefore change them only if necessary
+	 */
+
+	if ((status == SAM_STAT_GOOD) || (status == SAM_STAT_CONDITION_MET)) {
+		switch (opcode) {
+		case READ_CAPACITY:
+		{
+			/* Always keep track of disk capacity */
+			int buffer_size, sector_size, sh;
+			uint8_t *buffer;
+
+			buffer_size = scst_get_buf_first(cmd, &buffer);
+			if (unlikely(buffer_size <= 0)) {
+				if (buffer_size < 0) {
+					PRINT_ERROR("%s: Unable to get the"
+					" buffer (%d)",	__func__, buffer_size);
+				}
+				goto out;
+			}
+
+			sector_size =
+			    ((buffer[4] << 24) | (buffer[5] << 16) |
+			     (buffer[6] << 8) | (buffer[7] << 0));
+			scst_put_buf(cmd, buffer);
+			if (sector_size != 0)
+				sh = scst_calc_block_shift(sector_size);
+			else
+				sh = 0;
+			set_block_shift(cmd, sh);
+			TRACE_DBG("block_shift %d", sh);
+			break;
+		}
+		default:
+			/* It's all good */
+			break;
+		}
+	}
+
+	TRACE_DBG("cmd->is_send_status=%x, cmd->resp_data_len=%d, "
+	      "res=%d", cmd->is_send_status, cmd->resp_data_len, res);
+
+out:
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_block_generic_dev_done);
+
+/**
+ * scst_tape_generic_dev_done() - generic tape dev done
+ *
+ * Generic dev_done() for tape devices
+ */
+int scst_tape_generic_dev_done(struct scst_cmd *cmd,
+	void (*set_block_size)(struct scst_cmd *cmd, int block_shift))
+{
+	int opcode = cmd->cdb[0];
+	int res = SCST_CMD_STATE_DEFAULT;
+	int buffer_size, bs;
+	uint8_t *buffer = NULL;
+
+	/*
+	 * SCST sets good defaults for cmd->is_send_status and
+	 * cmd->resp_data_len based on cmd->status and cmd->data_direction,
+	 * therefore change them only if necessary
+	 */
+
+	if (cmd->status != SAM_STAT_GOOD)
+		goto out;
+
+	switch (opcode) {
+	case MODE_SENSE:
+	case MODE_SELECT:
+		buffer_size = scst_get_buf_first(cmd, &buffer);
+		if (unlikely(buffer_size <= 0)) {
+			if (buffer_size < 0) {
+				PRINT_ERROR("%s: Unable to get the buffer (%d)",
+					__func__, buffer_size);
+			}
+			goto out;
+		}
+		break;
+	}
+
+	switch (opcode) {
+	case MODE_SENSE:
+		TRACE_DBG("%s", "MODE_SENSE");
+		if ((cmd->cdb[2] & 0xC0) == 0) {
+			if (buffer[3] == 8) {
+				bs = (buffer[9] << 16) |
+				    (buffer[10] << 8) | buffer[11];
+				set_block_size(cmd, bs);
+			}
+		}
+		break;
+	case MODE_SELECT:
+		TRACE_DBG("%s", "MODE_SELECT");
+		if (buffer[3] == 8) {
+			bs = (buffer[9] << 16) | (buffer[10] << 8) |
+			    (buffer[11]);
+			set_block_size(cmd, bs);
+		}
+		break;
+	default:
+		/* It's all good */
+		break;
+	}
+
+	switch (opcode) {
+	case MODE_SENSE:
+	case MODE_SELECT:
+		scst_put_buf(cmd, buffer);
+		break;
+	}
+
+out:
+	return res;
+}
+EXPORT_SYMBOL_GPL(scst_tape_generic_dev_done);
+
+static void scst_check_internal_sense(struct scst_device *dev, int result,
+	uint8_t *sense, int sense_len)
+{
+
+	if (host_byte(result) == DID_RESET) {
+		int sl;
+		TRACE(TRACE_MGMT, "DID_RESET received for device %s, "
+			"triggering reset UA", dev->virt_name);
+		sl = scst_set_sense(sense, sense_len, dev->d_sense,
+			SCST_LOAD_SENSE(scst_sense_reset_UA));
+		scst_dev_check_set_UA(dev, NULL, sense, sl);
+	} else if ((status_byte(result) == CHECK_CONDITION) &&
+		   scst_is_ua_sense(sense, sense_len))
+		scst_dev_check_set_UA(dev, NULL, sense, sense_len);
+	return;
+}
+
+/**
+ * scst_to_dma_dir() - translate SCST's data direction to DMA direction
+ *
+ * Translates SCST's data direction to DMA one from backend storage
+ * perspective.
+ */
+enum dma_data_direction scst_to_dma_dir(int scst_dir)
+{
+	static const enum dma_data_direction tr_tbl[] = { DMA_NONE,
+		DMA_TO_DEVICE, DMA_FROM_DEVICE, DMA_BIDIRECTIONAL, DMA_NONE };
+
+	return tr_tbl[scst_dir];
+}
+EXPORT_SYMBOL(scst_to_dma_dir);
+
+/*
+ * scst_to_tgt_dma_dir() - translate SCST data direction to DMA direction
+ *
+ * Translates SCST data direction to DMA data direction from the perspective
+ * of a target.
+ */
+enum dma_data_direction scst_to_tgt_dma_dir(int scst_dir)
+{
+	static const enum dma_data_direction tr_tbl[] = { DMA_NONE,
+		DMA_FROM_DEVICE, DMA_TO_DEVICE, DMA_BIDIRECTIONAL, DMA_NONE };
+
+	return tr_tbl[scst_dir];
+}
+EXPORT_SYMBOL(scst_to_tgt_dma_dir);
+
+/**
+ * scst_obtain_device_parameters() - obtain device control parameters
+ *
+ * Issues a MODE SENSE for control mode page data and sets the corresponding
+ * dev's parameter from it. Returns 0 on success and not 0 otherwise.
+ */
+int scst_obtain_device_parameters(struct scst_device *dev)
+{
+	int rc, i;
+	uint8_t cmd[16];
+	uint8_t buffer[4+0x0A];
+	uint8_t sense_buffer[SCSI_SENSE_BUFFERSIZE];
+
+	EXTRACHECKS_BUG_ON(dev->scsi_dev == NULL);
+
+	for (i = 0; i < 5; i++) {
+		/* Get control mode page */
+		memset(cmd, 0, sizeof(cmd));
+#if 0
+		cmd[0] = MODE_SENSE_10;
+		cmd[1] = 0;
+		cmd[2] = 0x0A;
+		cmd[8] = sizeof(buffer); /* it's < 256 */
+#else
+		cmd[0] = MODE_SENSE;
+		cmd[1] = 8; /* DBD */
+		cmd[2] = 0x0A;
+		cmd[4] = sizeof(buffer);
+#endif
+
+		memset(buffer, 0, sizeof(buffer));
+		memset(sense_buffer, 0, sizeof(sense_buffer));
+
+		TRACE(TRACE_SCSI, "%s", "Doing internal MODE_SENSE");
+		rc = scsi_execute(dev->scsi_dev, cmd, SCST_DATA_READ, buffer,
+				sizeof(buffer), sense_buffer, 15, 0, 0
+				, NULL
+				);
+
+		TRACE_DBG("MODE_SENSE done: %x", rc);
+
+		if (scsi_status_is_good(rc)) {
+			int q;
+
+			PRINT_BUFF_FLAG(TRACE_SCSI, "Returned control mode "
+				"page data", buffer, sizeof(buffer));
+
+			dev->tst = buffer[4+2] >> 5;
+			q = buffer[4+3] >> 4;
+			if (q > SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER) {
+				PRINT_ERROR("Too big QUEUE ALG %x, dev %s",
+					dev->queue_alg, dev->virt_name);
+			}
+			dev->queue_alg = q;
+			dev->swp = (buffer[4+4] & 0x8) >> 3;
+			dev->tas = (buffer[4+5] & 0x40) >> 6;
+			dev->d_sense = (buffer[4+2] & 0x4) >> 2;
+
+			/*
+			 * Unfortunately, SCSI ML doesn't provide a way to
+			 * specify commands task attribute, so we can rely on
+			 * device's restricted reordering only. Linux I/O
+			 * subsystem doesn't reorder pass-through (PC) requests.
+			 */
+			dev->has_own_order_mgmt = !dev->queue_alg;
+
+			PRINT_INFO("Device %s: TST %x, QUEUE ALG %x, SWP %x, "
+				"TAS %x, D_SENSE %d, has_own_order_mgmt %d",
+				dev->virt_name, dev->tst, dev->queue_alg,
+				dev->swp, dev->tas, dev->d_sense,
+				dev->has_own_order_mgmt);
+
+			goto out;
+		} else {
+			scst_check_internal_sense(dev, rc, sense_buffer,
+				sizeof(sense_buffer));
+#if 0
+			if ((status_byte(rc) == CHECK_CONDITION) &&
+			    SCST_SENSE_VALID(sense_buffer)) {
+#else
+			/*
+			 * 3ware controller is buggy and returns CONDITION_GOOD
+			 * instead of CHECK_CONDITION
+			 */
+			if (SCST_SENSE_VALID(sense_buffer)) {
+#endif
+				PRINT_BUFF_FLAG(TRACE_SCSI, "Returned sense "
+					"data", sense_buffer,
+					sizeof(sense_buffer));
+				if (scst_analyze_sense(sense_buffer,
+						sizeof(sense_buffer),
+						SCST_SENSE_KEY_VALID,
+						ILLEGAL_REQUEST, 0, 0)) {
+					PRINT_INFO("Device %s doesn't support "
+						"MODE SENSE", dev->virt_name);
+					break;
+				} else if (scst_analyze_sense(sense_buffer,
+						sizeof(sense_buffer),
+						SCST_SENSE_KEY_VALID,
+						NOT_READY, 0, 0)) {
+					PRINT_ERROR("Device %s not ready",
+						dev->virt_name);
+					break;
+				}
+			} else {
+				PRINT_INFO("Internal MODE SENSE to "
+					"device %s failed: %x",
+					dev->virt_name, rc);
+				PRINT_BUFF_FLAG(TRACE_SCSI, "MODE SENSE sense",
+					sense_buffer, sizeof(sense_buffer));
+				switch (host_byte(rc)) {
+				case DID_RESET:
+				case DID_ABORT:
+				case DID_SOFT_ERROR:
+					break;
+				default:
+					goto brk;
+				}
+				switch (driver_byte(rc)) {
+				case DRIVER_BUSY:
+				case DRIVER_SOFT:
+					break;
+				default:
+					goto brk;
+				}
+			}
+		}
+	}
+brk:
+	PRINT_WARNING("Unable to get device's %s control mode page, using "
+		"existing values/defaults: TST %x, QUEUE ALG %x, SWP %x, "
+		"TAS %x, D_SENSE %d, has_own_order_mgmt %d", dev->virt_name,
+		dev->tst, dev->queue_alg, dev->swp, dev->tas, dev->d_sense,
+		dev->has_own_order_mgmt);
+
+out:
+	return 0;
+}
+EXPORT_SYMBOL_GPL(scst_obtain_device_parameters);
+
+/* Called under dev_lock and BH off */
+void scst_process_reset(struct scst_device *dev,
+	struct scst_session *originator, struct scst_cmd *exclude_cmd,
+	struct scst_mgmt_cmd *mcmd, bool setUA)
+{
+	struct scst_tgt_dev *tgt_dev;
+	struct scst_cmd *cmd, *tcmd;
+
+	/* Clear RESERVE'ation, if necessary */
+	if (dev->dev_reserved) {
+		list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+				    dev_tgt_dev_list_entry) {
+			TRACE_MGMT_DBG("Clearing RESERVE'ation for "
+				"tgt_dev LUN %lld",
+				(long long unsigned int)tgt_dev->lun);
+			clear_bit(SCST_TGT_DEV_RESERVED,
+				  &tgt_dev->tgt_dev_flags);
+		}
+		dev->dev_reserved = 0;
+		/*
+		 * There is no need to send RELEASE, since the device is going
+		 * to be resetted. Actually, since we can be in RESET TM
+		 * function, it might be dangerous.
+		 */
+	}
+
+	dev->dev_double_ua_possible = 1;
+
+	list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+		dev_tgt_dev_list_entry) {
+		struct scst_session *sess = tgt_dev->sess;
+
+		spin_lock_bh(&tgt_dev->tgt_dev_lock);
+
+		scst_free_all_UA(tgt_dev);
+
+		memset(tgt_dev->tgt_dev_sense, 0,
+			sizeof(tgt_dev->tgt_dev_sense));
+
+		spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+
+		spin_lock_irq(&sess->sess_list_lock);
+
+		TRACE_DBG("Searching in sess cmd list (sess=%p)", sess);
+		list_for_each_entry(cmd, &sess->sess_cmd_list,
+					sess_cmd_list_entry) {
+			if (cmd == exclude_cmd)
+				continue;
+			if ((cmd->tgt_dev == tgt_dev) ||
+			    ((cmd->tgt_dev == NULL) &&
+			     (cmd->lun == tgt_dev->lun))) {
+				scst_abort_cmd(cmd, mcmd,
+					(tgt_dev->sess != originator), 0);
+			}
+		}
+		spin_unlock_irq(&sess->sess_list_lock);
+	}
+
+	list_for_each_entry_safe(cmd, tcmd, &dev->blocked_cmd_list,
+				blocked_cmd_list_entry) {
+		if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) {
+			list_del(&cmd->blocked_cmd_list_entry);
+			TRACE_MGMT_DBG("Adding aborted blocked cmd %p "
+				"to active cmd list", cmd);
+			spin_lock_irq(&cmd->cmd_threads->cmd_list_lock);
+			list_add_tail(&cmd->cmd_list_entry,
+				&cmd->cmd_threads->active_cmd_list);
+			wake_up(&cmd->cmd_threads->cmd_list_waitQ);
+			spin_unlock_irq(&cmd->cmd_threads->cmd_list_lock);
+		}
+	}
+
+	if (setUA) {
+		uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
+		int sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
+			dev->d_sense, SCST_LOAD_SENSE(scst_sense_reset_UA));
+		scst_dev_check_set_local_UA(dev, exclude_cmd, sense_buffer, sl);
+	}
+	return;
+}
+
+/* No locks, no IRQ or IRQ-disabled context allowed */
+int scst_set_pending_UA(struct scst_cmd *cmd)
+{
+	int res = 0, i;
+	struct scst_tgt_dev_UA *UA_entry;
+	bool first = true, global_unlock = false;
+	struct scst_session *sess = cmd->sess;
+
+	TRACE_MGMT_DBG("Setting pending UA cmd %p", cmd);
+
+	spin_lock_bh(&cmd->tgt_dev->tgt_dev_lock);
+
+again:
+	/* UA list could be cleared behind us, so retest */
+	if (list_empty(&cmd->tgt_dev->UA_list)) {
+		TRACE_DBG("%s",
+		      "SCST_TGT_DEV_UA_PENDING set, but UA_list empty");
+		res = -1;
+		goto out_unlock;
+	}
+
+	UA_entry = list_entry(cmd->tgt_dev->UA_list.next, typeof(*UA_entry),
+			      UA_list_entry);
+
+	TRACE_DBG("next %p UA_entry %p",
+	      cmd->tgt_dev->UA_list.next, UA_entry);
+
+	if (UA_entry->global_UA && first) {
+		TRACE_MGMT_DBG("Global UA %p detected", UA_entry);
+
+		spin_unlock_bh(&cmd->tgt_dev->tgt_dev_lock);
+
+		/*
+		 * cmd won't allow to suspend activities, so we can access
+		 * sess->sess_tgt_dev_list_hash without any additional
+		 * protection.
+		 */
+
+		local_bh_disable();
+
+		for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+			struct list_head *sess_tgt_dev_list_head =
+				&sess->sess_tgt_dev_list_hash[i];
+			struct scst_tgt_dev *tgt_dev;
+			list_for_each_entry(tgt_dev, sess_tgt_dev_list_head,
+					sess_tgt_dev_list_entry) {
+				/* Lockdep triggers here a false positive.. */
+				spin_lock(&tgt_dev->tgt_dev_lock);
+			}
+		}
+
+		first = false;
+		global_unlock = true;
+		goto again;
+	}
+
+	if (scst_set_cmd_error_sense(cmd, UA_entry->UA_sense_buffer,
+			UA_entry->UA_valid_sense_len) != 0)
+		goto out_unlock;
+
+	cmd->ua_ignore = 1;
+
+	list_del(&UA_entry->UA_list_entry);
+
+	if (UA_entry->global_UA) {
+		for (i = 0; i < TGT_DEV_HASH_SIZE; i++) {
+			struct list_head *sess_tgt_dev_list_head =
+				&sess->sess_tgt_dev_list_hash[i];
+			struct scst_tgt_dev *tgt_dev;
+
+			list_for_each_entry(tgt_dev, sess_tgt_dev_list_head,
+					sess_tgt_dev_list_entry) {
+				struct scst_tgt_dev_UA *ua;
+				list_for_each_entry(ua, &tgt_dev->UA_list,
+							UA_list_entry) {
+					if (ua->global_UA &&
+					    memcmp(ua->UA_sense_buffer,
+					      UA_entry->UA_sense_buffer,
+					      sizeof(ua->UA_sense_buffer)) == 0) {
+						TRACE_MGMT_DBG("Freeing not "
+							"needed global UA %p",
+							ua);
+						list_del(&ua->UA_list_entry);
+						mempool_free(ua, scst_ua_mempool);
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	mempool_free(UA_entry, scst_ua_mempool);
+
+	if (list_empty(&cmd->tgt_dev->UA_list)) {
+		clear_bit(SCST_TGT_DEV_UA_PENDING,
+			  &cmd->tgt_dev->tgt_dev_flags);
+	}
+
+out_unlock:
+	if (global_unlock) {
+		for (i = TGT_DEV_HASH_SIZE-1; i >= 0; i--) {
+			struct list_head *sess_tgt_dev_list_head =
+				&sess->sess_tgt_dev_list_hash[i];
+			struct scst_tgt_dev *tgt_dev;
+			list_for_each_entry_reverse(tgt_dev, sess_tgt_dev_list_head,
+					sess_tgt_dev_list_entry) {
+				spin_unlock(&tgt_dev->tgt_dev_lock);
+			}
+		}
+
+		local_bh_enable();
+		spin_lock_bh(&cmd->tgt_dev->tgt_dev_lock);
+	}
+
+	spin_unlock_bh(&cmd->tgt_dev->tgt_dev_lock);
+	return res;
+}
+
+/* Called under tgt_dev_lock and BH off */
+static void scst_alloc_set_UA(struct scst_tgt_dev *tgt_dev,
+	const uint8_t *sense, int sense_len, int flags)
+{
+	struct scst_tgt_dev_UA *UA_entry = NULL;
+
+	UA_entry = mempool_alloc(scst_ua_mempool, GFP_ATOMIC);
+	if (UA_entry == NULL) {
+		PRINT_CRIT_ERROR("%s", "UNIT ATTENTION memory "
+		     "allocation failed. The UNIT ATTENTION "
+		     "on some sessions will be missed");
+		PRINT_BUFFER("Lost UA", sense, sense_len);
+		goto out;
+	}
+	memset(UA_entry, 0, sizeof(*UA_entry));
+
+	UA_entry->global_UA = (flags & SCST_SET_UA_FLAG_GLOBAL) != 0;
+	if (UA_entry->global_UA)
+		TRACE_MGMT_DBG("Queuing global UA %p", UA_entry);
+
+	if (sense_len > (int)sizeof(UA_entry->UA_sense_buffer)) {
+		PRINT_WARNING("Sense truncated (needed %d), shall you increase "
+			"SCST_SENSE_BUFFERSIZE?", sense_len);
+		sense_len = sizeof(UA_entry->UA_sense_buffer);
+	}
+	memcpy(UA_entry->UA_sense_buffer, sense, sense_len);
+	UA_entry->UA_valid_sense_len = sense_len;
+
+	set_bit(SCST_TGT_DEV_UA_PENDING, &tgt_dev->tgt_dev_flags);
+
+	TRACE_MGMT_DBG("Adding new UA to tgt_dev %p", tgt_dev);
+
+	if (flags & SCST_SET_UA_FLAG_AT_HEAD)
+		list_add(&UA_entry->UA_list_entry, &tgt_dev->UA_list);
+	else
+		list_add_tail(&UA_entry->UA_list_entry, &tgt_dev->UA_list);
+
+out:
+	return;
+}
+
+/* tgt_dev_lock supposed to be held and BH off */
+static void __scst_check_set_UA(struct scst_tgt_dev *tgt_dev,
+	const uint8_t *sense, int sense_len, int flags)
+{
+	int skip_UA = 0;
+	struct scst_tgt_dev_UA *UA_entry_tmp;
+	int len = min((int)sizeof(UA_entry_tmp->UA_sense_buffer), sense_len);
+
+	list_for_each_entry(UA_entry_tmp, &tgt_dev->UA_list,
+			    UA_list_entry) {
+		if (memcmp(sense, UA_entry_tmp->UA_sense_buffer, len) == 0) {
+			TRACE_MGMT_DBG("%s", "UA already exists");
+			skip_UA = 1;
+			break;
+		}
+	}
+
+	if (skip_UA == 0)
+		scst_alloc_set_UA(tgt_dev, sense, len, flags);
+	return;
+}
+
+void scst_check_set_UA(struct scst_tgt_dev *tgt_dev,
+	const uint8_t *sense, int sense_len, int flags)
+{
+
+	spin_lock_bh(&tgt_dev->tgt_dev_lock);
+	__scst_check_set_UA(tgt_dev, sense, sense_len, flags);
+	spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+	return;
+}
+
+/* Called under dev_lock and BH off */
+void scst_dev_check_set_local_UA(struct scst_device *dev,
+	struct scst_cmd *exclude, const uint8_t *sense, int sense_len)
+{
+	struct scst_tgt_dev *tgt_dev, *exclude_tgt_dev = NULL;
+
+	if (exclude != NULL)
+		exclude_tgt_dev = exclude->tgt_dev;
+
+	list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+			dev_tgt_dev_list_entry) {
+		if (tgt_dev != exclude_tgt_dev)
+			scst_check_set_UA(tgt_dev, sense, sense_len, 0);
+	}
+	return;
+}
+
+/* Called under dev_lock and BH off */
+void __scst_dev_check_set_UA(struct scst_device *dev,
+	struct scst_cmd *exclude, const uint8_t *sense, int sense_len)
+{
+
+	TRACE_MGMT_DBG("Processing UA dev %p", dev);
+
+	/* Check for reset UA */
+	if (scst_analyze_sense(sense, sense_len, SCST_SENSE_ASC_VALID,
+				0, SCST_SENSE_ASC_UA_RESET, 0))
+		scst_process_reset(dev,
+				   (exclude != NULL) ? exclude->sess : NULL,
+				   exclude, NULL, false);
+
+	scst_dev_check_set_local_UA(dev, exclude, sense, sense_len);
+	return;
+}
+
+/* Called under tgt_dev_lock or when tgt_dev is unused */
+static void scst_free_all_UA(struct scst_tgt_dev *tgt_dev)
+{
+	struct scst_tgt_dev_UA *UA_entry, *t;
+
+	list_for_each_entry_safe(UA_entry, t,
+				 &tgt_dev->UA_list, UA_list_entry) {
+		TRACE_MGMT_DBG("Clearing UA for tgt_dev LUN %lld",
+			       (long long unsigned int)tgt_dev->lun);
+		list_del(&UA_entry->UA_list_entry);
+		mempool_free(UA_entry, scst_ua_mempool);
+	}
+	INIT_LIST_HEAD(&tgt_dev->UA_list);
+	clear_bit(SCST_TGT_DEV_UA_PENDING, &tgt_dev->tgt_dev_flags);
+	return;
+}
+
+/* No locks */
+struct scst_cmd *__scst_check_deferred_commands(struct scst_tgt_dev *tgt_dev)
+{
+	struct scst_cmd *res = NULL, *cmd, *t;
+	typeof(tgt_dev->expected_sn) expected_sn = tgt_dev->expected_sn;
+
+	spin_lock_irq(&tgt_dev->sn_lock);
+
+	if (unlikely(tgt_dev->hq_cmd_count != 0))
+		goto out_unlock;
+
+restart:
+	list_for_each_entry_safe(cmd, t, &tgt_dev->deferred_cmd_list,
+				sn_cmd_list_entry) {
+		EXTRACHECKS_BUG_ON(cmd->queue_type ==
+			SCST_CMD_QUEUE_HEAD_OF_QUEUE);
+		if (cmd->sn == expected_sn) {
+			TRACE_SN("Deferred command %p (sn %d, set %d) found",
+				cmd, cmd->sn, cmd->sn_set);
+			tgt_dev->def_cmd_count--;
+			list_del(&cmd->sn_cmd_list_entry);
+			if (res == NULL)
+				res = cmd;
+			else {
+				spin_lock(&cmd->cmd_threads->cmd_list_lock);
+				TRACE_SN("Adding cmd %p to active cmd list",
+					cmd);
+				list_add_tail(&cmd->cmd_list_entry,
+					&cmd->cmd_threads->active_cmd_list);
+				wake_up(&cmd->cmd_threads->cmd_list_waitQ);
+				spin_unlock(&cmd->cmd_threads->cmd_list_lock);
+			}
+		}
+	}
+	if (res != NULL)
+		goto out_unlock;
+
+	list_for_each_entry(cmd, &tgt_dev->skipped_sn_list,
+				sn_cmd_list_entry) {
+		EXTRACHECKS_BUG_ON(cmd->queue_type ==
+			SCST_CMD_QUEUE_HEAD_OF_QUEUE);
+		if (cmd->sn == expected_sn) {
+			atomic_t *slot = cmd->sn_slot;
+			/*
+			 * !! At this point any pointer in cmd, except !!
+			 * !! sn_slot and sn_cmd_list_entry, could be	!!
+			 * !! already destroyed				!!
+			 */
+			TRACE_SN("cmd %p (tag %llu) with skipped sn %d found",
+				 cmd,
+				 (long long unsigned int)cmd->tag,
+				 cmd->sn);
+			tgt_dev->def_cmd_count--;
+			list_del(&cmd->sn_cmd_list_entry);
+			spin_unlock_irq(&tgt_dev->sn_lock);
+			if (test_and_set_bit(SCST_CMD_CAN_BE_DESTROYED,
+					     &cmd->cmd_flags))
+				scst_destroy_put_cmd(cmd);
+			scst_inc_expected_sn(tgt_dev, slot);
+			expected_sn = tgt_dev->expected_sn;
+			spin_lock_irq(&tgt_dev->sn_lock);
+			goto restart;
+		}
+	}
+
+out_unlock:
+	spin_unlock_irq(&tgt_dev->sn_lock);
+	return res;
+}
+
+/*****************************************************************
+ ** The following thr_data functions are necessary, because the
+ ** kernel doesn't provide a better way to have threads local
+ ** storage
+ *****************************************************************/
+
+/**
+ * scst_add_thr_data() - add the current thread's local data
+ *
+ * Adds local to the current thread data to tgt_dev
+ * (they will be local for the tgt_dev and current thread).
+ */
+void scst_add_thr_data(struct scst_tgt_dev *tgt_dev,
+	struct scst_thr_data_hdr *data,
+	void (*free_fn) (struct scst_thr_data_hdr *data))
+{
+	data->owner_thr = current;
+	atomic_set(&data->ref, 1);
+	EXTRACHECKS_BUG_ON(free_fn == NULL);
+	data->free_fn = free_fn;
+	spin_lock(&tgt_dev->thr_data_lock);
+	list_add_tail(&data->thr_data_list_entry, &tgt_dev->thr_data_list);
+	spin_unlock(&tgt_dev->thr_data_lock);
+}
+EXPORT_SYMBOL_GPL(scst_add_thr_data);
+
+/**
+ * scst_del_all_thr_data() - delete all thread's local data
+ *
+ * Deletes all local to threads data from tgt_dev
+ */
+void scst_del_all_thr_data(struct scst_tgt_dev *tgt_dev)
+{
+	spin_lock(&tgt_dev->thr_data_lock);
+	while (!list_empty(&tgt_dev->thr_data_list)) {
+		struct scst_thr_data_hdr *d = list_entry(
+				tgt_dev->thr_data_list.next, typeof(*d),
+				thr_data_list_entry);
+		list_del(&d->thr_data_list_entry);
+		spin_unlock(&tgt_dev->thr_data_lock);
+		scst_thr_data_put(d);
+		spin_lock(&tgt_dev->thr_data_lock);
+	}
+	spin_unlock(&tgt_dev->thr_data_lock);
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_del_all_thr_data);
+
+/**
+ * scst_dev_del_all_thr_data() - delete all thread's local data from device
+ *
+ * Deletes all local to threads data from all tgt_dev's of the device
+ */
+void scst_dev_del_all_thr_data(struct scst_device *dev)
+{
+	struct scst_tgt_dev *tgt_dev;
+
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+		scst_del_all_thr_data(tgt_dev);
+	}
+
+	mutex_unlock(&scst_mutex);
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_dev_del_all_thr_data);
+
+/* thr_data_lock supposed to be held */
+static struct scst_thr_data_hdr *__scst_find_thr_data_locked(
+	struct scst_tgt_dev *tgt_dev, struct task_struct *tsk)
+{
+	struct scst_thr_data_hdr *res = NULL, *d;
+
+	list_for_each_entry(d, &tgt_dev->thr_data_list, thr_data_list_entry) {
+		if (d->owner_thr == tsk) {
+			res = d;
+			scst_thr_data_get(res);
+			break;
+		}
+	}
+	return res;
+}
+
+/**
+ * __scst_find_thr_data() - find local to the thread data
+ *
+ * Finds local to the thread data. Returns NULL, if they not found.
+ */
+struct scst_thr_data_hdr *__scst_find_thr_data(struct scst_tgt_dev *tgt_dev,
+	struct task_struct *tsk)
+{
+	struct scst_thr_data_hdr *res;
+
+	spin_lock(&tgt_dev->thr_data_lock);
+	res = __scst_find_thr_data_locked(tgt_dev, tsk);
+	spin_unlock(&tgt_dev->thr_data_lock);
+
+	return res;
+}
+EXPORT_SYMBOL_GPL(__scst_find_thr_data);
+
+bool scst_del_thr_data(struct scst_tgt_dev *tgt_dev, struct task_struct *tsk)
+{
+	bool res;
+	struct scst_thr_data_hdr *td;
+
+	spin_lock(&tgt_dev->thr_data_lock);
+
+	td = __scst_find_thr_data_locked(tgt_dev, tsk);
+	if (td != NULL) {
+		list_del(&td->thr_data_list_entry);
+		res = true;
+	} else
+		res = false;
+
+	spin_unlock(&tgt_dev->thr_data_lock);
+
+	if (td != NULL) {
+		/* the find() fn also gets it */
+		scst_thr_data_put(td);
+		scst_thr_data_put(td);
+	}
+
+	return res;
+}
+
+/* dev_lock supposed to be held and BH disabled */
+void __scst_block_dev(struct scst_device *dev)
+{
+	dev->block_count++;
+	TRACE_MGMT_DBG("Device BLOCK(new %d), dev %p", dev->block_count, dev);
+}
+
+/* No locks */
+static void scst_block_dev(struct scst_device *dev, int outstanding)
+{
+	spin_lock_bh(&dev->dev_lock);
+	__scst_block_dev(dev);
+	spin_unlock_bh(&dev->dev_lock);
+
+	/*
+	 * Memory barrier is necessary here, because we need to read
+	 * on_dev_count in wait_event() below after we increased block_count.
+	 * Otherwise, we can miss wake up in scst_dec_on_dev_cmd().
+	 * We use the explicit barrier, because spin_unlock_bh() doesn't
+	 * provide the necessary memory barrier functionality.
+	 */
+	smp_mb();
+
+	TRACE_MGMT_DBG("Waiting during blocking outstanding %d (on_dev_count "
+		"%d)", outstanding, atomic_read(&dev->on_dev_count));
+	wait_event(dev->on_dev_waitQ,
+		atomic_read(&dev->on_dev_count) <= outstanding);
+	TRACE_MGMT_DBG("%s", "wait_event() returned");
+}
+
+/* No locks */
+void scst_block_dev_cmd(struct scst_cmd *cmd, int outstanding)
+{
+	BUG_ON(cmd->needs_unblocking);
+
+	cmd->needs_unblocking = 1;
+	TRACE_MGMT_DBG("Needs unblocking cmd %p (tag %llu)",
+		       cmd, (long long unsigned int)cmd->tag);
+
+	scst_block_dev(cmd->dev, outstanding);
+}
+
+/* No locks */
+void scst_unblock_dev(struct scst_device *dev)
+{
+	spin_lock_bh(&dev->dev_lock);
+	TRACE_MGMT_DBG("Device UNBLOCK(new %d), dev %p",
+		dev->block_count-1, dev);
+	if (--dev->block_count == 0)
+		scst_unblock_cmds(dev);
+	spin_unlock_bh(&dev->dev_lock);
+	BUG_ON(dev->block_count < 0);
+}
+
+/* No locks */
+void scst_unblock_dev_cmd(struct scst_cmd *cmd)
+{
+	scst_unblock_dev(cmd->dev);
+	cmd->needs_unblocking = 0;
+}
+
+/* No locks */
+int scst_inc_on_dev_cmd(struct scst_cmd *cmd)
+{
+	int res = 0;
+	struct scst_device *dev = cmd->dev;
+
+	BUG_ON(cmd->inc_blocking || cmd->dec_on_dev_needed);
+
+	atomic_inc(&dev->on_dev_count);
+	cmd->dec_on_dev_needed = 1;
+	TRACE_DBG("New on_dev_count %d", atomic_read(&dev->on_dev_count));
+
+	if (unlikely(cmd->internal) && (cmd->cdb[0] == REQUEST_SENSE)) {
+		/*
+		 * The original command can already block the device, so
+		 * REQUEST SENSE command should always pass.
+		 */
+		goto out;
+	}
+
+repeat:
+	if (unlikely(dev->block_count > 0)) {
+		spin_lock_bh(&dev->dev_lock);
+		if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)))
+			goto out_unlock;
+		if (dev->block_count > 0) {
+			scst_dec_on_dev_cmd(cmd);
+			TRACE_MGMT_DBG("Delaying cmd %p due to blocking "
+				"(tag %llu, dev %p)", cmd,
+				(long long unsigned int)cmd->tag, dev);
+			list_add_tail(&cmd->blocked_cmd_list_entry,
+				      &dev->blocked_cmd_list);
+			res = 1;
+			spin_unlock_bh(&dev->dev_lock);
+			goto out;
+		} else {
+			TRACE_MGMT_DBG("%s", "Somebody unblocked the device, "
+				"continuing");
+		}
+		spin_unlock_bh(&dev->dev_lock);
+	}
+	if (unlikely(dev->dev_double_ua_possible)) {
+		spin_lock_bh(&dev->dev_lock);
+		if (dev->block_count == 0) {
+			TRACE_MGMT_DBG("cmd %p (tag %llu), blocking further "
+				"cmds due to possible double reset UA (dev %p)",
+				cmd, (long long unsigned int)cmd->tag, dev);
+			__scst_block_dev(dev);
+			cmd->inc_blocking = 1;
+		} else {
+			spin_unlock_bh(&dev->dev_lock);
+			TRACE_MGMT_DBG("Somebody blocked the device, "
+				"repeating (count %d)", dev->block_count);
+			goto repeat;
+		}
+		spin_unlock_bh(&dev->dev_lock);
+	}
+
+out:
+	return res;
+
+out_unlock:
+	spin_unlock_bh(&dev->dev_lock);
+	goto out;
+}
+
+/* Called under dev_lock */
+static void scst_unblock_cmds(struct scst_device *dev)
+{
+	struct scst_cmd *cmd, *tcmd;
+	unsigned long flags;
+
+	local_irq_save(flags);
+	list_for_each_entry_safe(cmd, tcmd, &dev->blocked_cmd_list,
+				 blocked_cmd_list_entry) {
+		list_del(&cmd->blocked_cmd_list_entry);
+		TRACE_MGMT_DBG("Adding blocked cmd %p to active cmd list", cmd);
+		spin_lock(&cmd->cmd_threads->cmd_list_lock);
+		if (unlikely(cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE))
+			list_add(&cmd->cmd_list_entry,
+				&cmd->cmd_threads->active_cmd_list);
+		else
+			list_add_tail(&cmd->cmd_list_entry,
+				&cmd->cmd_threads->active_cmd_list);
+		wake_up(&cmd->cmd_threads->cmd_list_waitQ);
+		spin_unlock(&cmd->cmd_threads->cmd_list_lock);
+	}
+	local_irq_restore(flags);
+	return;
+}
+
+static void __scst_unblock_deferred(struct scst_tgt_dev *tgt_dev,
+	struct scst_cmd *out_of_sn_cmd)
+{
+	EXTRACHECKS_BUG_ON(!out_of_sn_cmd->sn_set);
+
+	if (out_of_sn_cmd->sn == tgt_dev->expected_sn) {
+		scst_inc_expected_sn(tgt_dev, out_of_sn_cmd->sn_slot);
+		scst_make_deferred_commands_active(tgt_dev);
+	} else {
+		out_of_sn_cmd->out_of_sn = 1;
+		spin_lock_irq(&tgt_dev->sn_lock);
+		tgt_dev->def_cmd_count++;
+		list_add_tail(&out_of_sn_cmd->sn_cmd_list_entry,
+			      &tgt_dev->skipped_sn_list);
+		TRACE_SN("out_of_sn_cmd %p with sn %d added to skipped_sn_list"
+			" (expected_sn %d)", out_of_sn_cmd, out_of_sn_cmd->sn,
+			tgt_dev->expected_sn);
+		spin_unlock_irq(&tgt_dev->sn_lock);
+	}
+
+	return;
+}
+
+void scst_unblock_deferred(struct scst_tgt_dev *tgt_dev,
+	struct scst_cmd *out_of_sn_cmd)
+{
+
+	if (!out_of_sn_cmd->sn_set) {
+		TRACE_SN("cmd %p without sn", out_of_sn_cmd);
+		goto out;
+	}
+
+	__scst_unblock_deferred(tgt_dev, out_of_sn_cmd);
+
+out:
+	return;
+}
+
+void scst_on_hq_cmd_response(struct scst_cmd *cmd)
+{
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+
+	if (!cmd->hq_cmd_inced)
+		goto out;
+
+	spin_lock_irq(&tgt_dev->sn_lock);
+	tgt_dev->hq_cmd_count--;
+	spin_unlock_irq(&tgt_dev->sn_lock);
+
+	EXTRACHECKS_BUG_ON(tgt_dev->hq_cmd_count < 0);
+
+	/*
+	 * There is no problem in checking hq_cmd_count in the
+	 * non-locked state. In the worst case we will only have
+	 * unneeded run of the deferred commands.
+	 */
+	if (tgt_dev->hq_cmd_count == 0)
+		scst_make_deferred_commands_active(tgt_dev);
+
+out:
+	return;
+}
+
+void scst_store_sense(struct scst_cmd *cmd)
+{
+
+	if (SCST_SENSE_VALID(cmd->sense) &&
+	    !test_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags) &&
+	    (cmd->tgt_dev != NULL)) {
+		struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+
+		TRACE_DBG("Storing sense (cmd %p)", cmd);
+
+		spin_lock_bh(&tgt_dev->tgt_dev_lock);
+
+		if (cmd->sense_valid_len <= sizeof(tgt_dev->tgt_dev_sense))
+			tgt_dev->tgt_dev_valid_sense_len = cmd->sense_valid_len;
+		else {
+			tgt_dev->tgt_dev_valid_sense_len = sizeof(tgt_dev->tgt_dev_sense);
+			PRINT_ERROR("Stored sense truncated to size %d "
+				"(needed %d)", tgt_dev->tgt_dev_valid_sense_len,
+				cmd->sense_valid_len);
+		}
+		memcpy(tgt_dev->tgt_dev_sense, cmd->sense,
+			tgt_dev->tgt_dev_valid_sense_len);
+
+		spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+	}
+	return;
+}
+
+void scst_xmit_process_aborted_cmd(struct scst_cmd *cmd)
+{
+
+	TRACE_MGMT_DBG("Aborted cmd %p done (cmd_ref %d, "
+		"scst_cmd_count %d)", cmd, atomic_read(&cmd->cmd_ref),
+		atomic_read(&scst_cmd_count));
+
+	scst_done_cmd_mgmt(cmd);
+
+	if (test_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags)) {
+		if (cmd->completed) {
+			/* It's completed and it's OK to return its result */
+			goto out;
+		}
+
+		/* For not yet inited commands cmd->dev can be NULL here */
+		if (test_bit(SCST_CMD_DEVICE_TAS, &cmd->cmd_flags)) {
+			TRACE_MGMT_DBG("Flag ABORTED OTHER set for cmd %p "
+				"(tag %llu), returning TASK ABORTED ", cmd,
+				(long long unsigned int)cmd->tag);
+			scst_set_cmd_error_status(cmd, SAM_STAT_TASK_ABORTED);
+		} else {
+			TRACE_MGMT_DBG("Flag ABORTED OTHER set for cmd %p "
+				"(tag %llu), aborting without delivery or "
+				"notification",
+				cmd, (long long unsigned int)cmd->tag);
+			/*
+			 * There is no need to check/requeue possible UA,
+			 * because, if it exists, it will be delivered
+			 * by the "completed" branch above.
+			 */
+			clear_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags);
+		}
+	}
+
+out:
+	return;
+}
+
+/**
+ * scst_get_max_lun_commands() - return maximum supported commands count
+ *
+ * Returns maximum commands count which can be queued to this LUN in this
+ * session.
+ *
+ * If lun is NO_SUCH_LUN, returns minimum of maximum commands count which
+ * can be queued to any LUN in this session.
+ *
+ * If sess is NULL, returns minimum of maximum commands count which can be
+ * queued to any SCST device.
+ */
+int scst_get_max_lun_commands(struct scst_session *sess, uint64_t lun)
+{
+	return SCST_MAX_TGT_DEV_COMMANDS;
+}
+EXPORT_SYMBOL(scst_get_max_lun_commands);
+
+/**
+ * scst_reassign_persistent_sess_states() - reassigns persistent states
+ *
+ * Reassigns persistent states from old_sess to new_sess.
+ */
+void scst_reassign_persistent_sess_states(struct scst_session *new_sess,
+	struct scst_session *old_sess)
+{
+	struct scst_device *dev;
+
+	TRACE_PR("Reassigning persistent states from old_sess %p to "
+		"new_sess %p", old_sess, new_sess);
+
+	if ((new_sess == NULL) || (old_sess == NULL)) {
+		TRACE_DBG("%s", "new_sess or old_sess is NULL");
+		goto out;
+	}
+
+	if (new_sess == old_sess) {
+		TRACE_DBG("%s", "new_sess or old_sess are the same");
+		goto out;
+	}
+
+	if ((new_sess->transport_id == NULL) ||
+	    (old_sess->transport_id == NULL)) {
+		TRACE_DBG("%s", "new_sess or old_sess doesn't support PRs");
+		goto out;
+	}
+
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
+		struct scst_tgt_dev *tgt_dev;
+		struct scst_tgt_dev *new_tgt_dev = NULL, *old_tgt_dev = NULL;
+
+		TRACE_DBG("Processing dev %s", dev->virt_name);
+
+		list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+					dev_tgt_dev_list_entry) {
+			if (tgt_dev->sess == new_sess) {
+				new_tgt_dev = tgt_dev;
+				if (old_tgt_dev != NULL)
+					break;
+			}
+			if (tgt_dev->sess == old_sess) {
+				old_tgt_dev = tgt_dev;
+				if (new_tgt_dev != NULL)
+					break;
+			}
+		}
+
+		if ((new_tgt_dev == NULL) || (old_tgt_dev == NULL)) {
+			TRACE_DBG("new_tgt_dev %p or old_sess %p is NULL, "
+				"skipping (dev %s)", new_tgt_dev, old_tgt_dev,
+				dev->virt_name);
+			continue;
+		}
+
+		scst_pr_write_lock(dev);
+
+		if (old_tgt_dev->registrant != NULL) {
+			TRACE_PR("Reassigning reg %p from tgt_dev %p to %p",
+				old_tgt_dev->registrant, old_tgt_dev,
+				new_tgt_dev);
+
+			if (new_tgt_dev->registrant != NULL)
+				new_tgt_dev->registrant->tgt_dev = NULL;
+
+			new_tgt_dev->registrant = old_tgt_dev->registrant;
+			new_tgt_dev->registrant->tgt_dev = new_tgt_dev;
+
+			old_tgt_dev->registrant = NULL;
+		}
+
+		scst_pr_write_unlock(dev);
+	}
+
+	mutex_unlock(&scst_mutex);
+
+out:
+	return;
+}
+EXPORT_SYMBOL(scst_reassign_persistent_sess_states);
+
+/**
+ * scst_get_next_lexem() - parse and return next lexem in the string
+ *
+ * Returns pointer to the next lexem from token_str skipping
+ * spaces and '=' character and using them then as a delimeter. Content
+ * of token_str is modified by setting '\0' at the delimeter's position.
+ */
+char *scst_get_next_lexem(char **token_str)
+{
+	char *p = *token_str;
+	char *q;
+	static const char blank = '\0';
+
+	if ((token_str == NULL) || (*token_str == NULL))
+		return (char *)&blank;
+
+	for (p = *token_str; (*p != '\0') && (isspace(*p) || (*p == '=')); p++)
+		;
+
+	for (q = p; (*q != '\0') && !isspace(*q) && (*q != '='); q++)
+		;
+
+	if (*q != '\0')
+		*q++ = '\0';
+
+	*token_str = q;
+	return p;
+}
+EXPORT_SYMBOL_GPL(scst_get_next_lexem);
+
+/**
+ * scst_restore_token_str() - restore string modified by scst_get_next_lexem()
+ *
+ * Restores token_str modified by scst_get_next_lexem() to the
+ * previous value before scst_get_next_lexem() was called. Prev_lexem is
+ * a pointer to lexem returned by scst_get_next_lexem().
+ */
+void scst_restore_token_str(char *prev_lexem, char *token_str)
+{
+	if (&prev_lexem[strlen(prev_lexem)] != token_str)
+		prev_lexem[strlen(prev_lexem)] = ' ';
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_restore_token_str);
+
+/**
+ * scst_get_next_token_str() - parse and return next token
+ *
+ * This function returns pointer to the next token strings from input_str
+ * using '\n', ';' and '\0' as a delimeter. Content of input_str is
+ * modified by setting '\0' at the delimeter's position.
+ */
+char *scst_get_next_token_str(char **input_str)
+{
+	char *p = *input_str;
+	int i = 0;
+
+	while ((p[i] != '\n') && (p[i] != ';') && (p[i] != '\0'))
+		i++;
+
+	if (i == 0)
+		return NULL;
+
+	if (p[i] == '\0')
+		*input_str = &p[i];
+	else
+		*input_str = &p[i+1];
+
+	p[i] = '\0';
+
+	return p;
+}
+EXPORT_SYMBOL_GPL(scst_get_next_token_str);
+
+static void __init scst_scsi_op_list_init(void)
+{
+	int i;
+	uint8_t op = 0xff;
+
+	for (i = 0; i < 256; i++)
+		scst_scsi_op_list[i] = SCST_CDB_TBL_SIZE;
+
+	for (i = 0; i < SCST_CDB_TBL_SIZE; i++) {
+		if (scst_scsi_op_table[i].ops != op) {
+			op = scst_scsi_op_table[i].ops;
+			scst_scsi_op_list[op] = i;
+		}
+	}
+	return;
+}
+
+int __init scst_lib_init(void)
+{
+	int res = 0;
+
+	scst_scsi_op_list_init();
+
+	scsi_io_context_cache = kmem_cache_create("scst_scsi_io_context",
+					sizeof(struct scsi_io_context),
+					0, 0, NULL);
+	if (!scsi_io_context_cache) {
+		PRINT_ERROR("%s", "Can't init scsi io context cache");
+		res = -ENOMEM;
+		goto out;
+	}
+
+out:
+	return res;
+}
+
+void scst_lib_exit(void)
+{
+	BUILD_BUG_ON(SCST_MAX_CDB_SIZE != BLK_MAX_CDB);
+	BUILD_BUG_ON(SCST_SENSE_BUFFERSIZE < SCSI_SENSE_BUFFERSIZE);
+
+	kmem_cache_destroy(scsi_io_context_cache);
+}
+
+#ifdef CONFIG_SCST_DEBUG
+
+/**
+ * scst_random() - return a pseudo-random number for debugging purposes.
+ *
+ * Returns a pseudo-random number for debugging purposes. Available only in
+ * the DEBUG build.
+ *
+ * Original taken from the XFS code
+ */
+unsigned long scst_random(void)
+{
+	static int Inited;
+	static unsigned long RandomValue;
+	static DEFINE_SPINLOCK(lock);
+	/* cycles pseudo-randomly through all values between 1 and 2^31 - 2 */
+	register long rv;
+	register long lo;
+	register long hi;
+	unsigned long flags;
+
+	spin_lock_irqsave(&lock, flags);
+	if (!Inited) {
+		RandomValue = jiffies;
+		Inited = 1;
+	}
+	rv = RandomValue;
+	hi = rv / 127773;
+	lo = rv % 127773;
+	rv = 16807 * lo - 2836 * hi;
+	if (rv <= 0)
+		rv += 2147483647;
+	RandomValue = rv;
+	spin_unlock_irqrestore(&lock, flags);
+	return rv;
+}
+EXPORT_SYMBOL_GPL(scst_random);
+#endif /* CONFIG_SCST_DEBUG */
+
+#ifdef CONFIG_SCST_DEBUG_TM
+
+#define TM_DBG_STATE_ABORT		0
+#define TM_DBG_STATE_RESET		1
+#define TM_DBG_STATE_OFFLINE		2
+
+#define INIT_TM_DBG_STATE		TM_DBG_STATE_ABORT
+
+static void tm_dbg_timer_fn(unsigned long arg);
+
+static DEFINE_SPINLOCK(scst_tm_dbg_lock);
+/* All serialized by scst_tm_dbg_lock */
+static struct {
+	unsigned int tm_dbg_release:1;
+	unsigned int tm_dbg_blocked:1;
+} tm_dbg_flags;
+static LIST_HEAD(tm_dbg_delayed_cmd_list);
+static int tm_dbg_delayed_cmds_count;
+static int tm_dbg_passed_cmds_count;
+static int tm_dbg_state;
+static int tm_dbg_on_state_passes;
+static DEFINE_TIMER(tm_dbg_timer, tm_dbg_timer_fn, 0, 0);
+static struct scst_tgt_dev *tm_dbg_tgt_dev;
+
+static const int tm_dbg_on_state_num_passes[] = { 5, 1, 0x7ffffff };
+
+static void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev)
+{
+	if (tgt_dev->lun == 6) {
+		unsigned long flags;
+
+		if (tm_dbg_tgt_dev != NULL)
+			tm_dbg_deinit_tgt_dev(tm_dbg_tgt_dev);
+
+		spin_lock_irqsave(&scst_tm_dbg_lock, flags);
+		tm_dbg_state = INIT_TM_DBG_STATE;
+		tm_dbg_on_state_passes =
+			tm_dbg_on_state_num_passes[tm_dbg_state];
+		tm_dbg_tgt_dev = tgt_dev;
+		PRINT_INFO("LUN %lld connected from initiator %s is under "
+			"TM debugging (tgt_dev %p)",
+			(unsigned long long)tgt_dev->lun,
+			tgt_dev->sess->initiator_name, tgt_dev);
+		spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
+	}
+	return;
+}
+
+static void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev)
+{
+	if (tm_dbg_tgt_dev == tgt_dev) {
+		unsigned long flags;
+		TRACE_MGMT_DBG("Deinit TM debugging tgt_dev %p", tgt_dev);
+		del_timer_sync(&tm_dbg_timer);
+		spin_lock_irqsave(&scst_tm_dbg_lock, flags);
+		tm_dbg_tgt_dev = NULL;
+		spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
+	}
+	return;
+}
+
+static void tm_dbg_timer_fn(unsigned long arg)
+{
+	TRACE_MGMT_DBG("%s", "delayed cmd timer expired");
+	tm_dbg_flags.tm_dbg_release = 1;
+	/* Used to make sure that all woken up threads see the new value */
+	smp_wmb();
+	wake_up_all(&tm_dbg_tgt_dev->active_cmd_threads->cmd_list_waitQ);
+	return;
+}
+
+/* Called under scst_tm_dbg_lock and IRQs off */
+static void tm_dbg_delay_cmd(struct scst_cmd *cmd)
+{
+	switch (tm_dbg_state) {
+	case TM_DBG_STATE_ABORT:
+		if (tm_dbg_delayed_cmds_count == 0) {
+			unsigned long d = 58*HZ + (scst_random() % (4*HZ));
+			TRACE_MGMT_DBG("STATE ABORT: delaying cmd %p (tag %llu)"
+				" for %ld.%ld seconds (%ld HZ), "
+				"tm_dbg_on_state_passes=%d", cmd, cmd->tag,
+				d/HZ, (d%HZ)*100/HZ, d,	tm_dbg_on_state_passes);
+			mod_timer(&tm_dbg_timer, jiffies + d);
+#if 0
+			tm_dbg_flags.tm_dbg_blocked = 1;
+#endif
+		} else {
+			TRACE_MGMT_DBG("Delaying another timed cmd %p "
+				"(tag %llu), delayed_cmds_count=%d, "
+				"tm_dbg_on_state_passes=%d", cmd, cmd->tag,
+				tm_dbg_delayed_cmds_count,
+				tm_dbg_on_state_passes);
+			if (tm_dbg_delayed_cmds_count == 2)
+				tm_dbg_flags.tm_dbg_blocked = 0;
+		}
+		break;
+
+	case TM_DBG_STATE_RESET:
+	case TM_DBG_STATE_OFFLINE:
+		TRACE_MGMT_DBG("STATE RESET/OFFLINE: delaying cmd %p "
+			"(tag %llu), delayed_cmds_count=%d, "
+			"tm_dbg_on_state_passes=%d", cmd, cmd->tag,
+			tm_dbg_delayed_cmds_count, tm_dbg_on_state_passes);
+		tm_dbg_flags.tm_dbg_blocked = 1;
+		break;
+
+	default:
+		BUG();
+	}
+	/* IRQs already off */
+	spin_lock(&cmd->cmd_threads->cmd_list_lock);
+	list_add_tail(&cmd->cmd_list_entry, &tm_dbg_delayed_cmd_list);
+	spin_unlock(&cmd->cmd_threads->cmd_list_lock);
+	cmd->tm_dbg_delayed = 1;
+	tm_dbg_delayed_cmds_count++;
+	return;
+}
+
+/* No locks */
+void tm_dbg_check_released_cmds(void)
+{
+	if (tm_dbg_flags.tm_dbg_release) {
+		struct scst_cmd *cmd, *tc;
+		spin_lock_irq(&scst_tm_dbg_lock);
+		list_for_each_entry_safe_reverse(cmd, tc,
+				&tm_dbg_delayed_cmd_list, cmd_list_entry) {
+			TRACE_MGMT_DBG("Releasing timed cmd %p (tag %llu), "
+				"delayed_cmds_count=%d", cmd, cmd->tag,
+				tm_dbg_delayed_cmds_count);
+			spin_lock(&cmd->cmd_threads->cmd_list_lock);
+			list_move(&cmd->cmd_list_entry,
+				&cmd->cmd_threads->active_cmd_list);
+			spin_unlock(&cmd->cmd_threads->cmd_list_lock);
+		}
+		tm_dbg_flags.tm_dbg_release = 0;
+		spin_unlock_irq(&scst_tm_dbg_lock);
+	}
+}
+
+/* Called under scst_tm_dbg_lock */
+static void tm_dbg_change_state(void)
+{
+	tm_dbg_flags.tm_dbg_blocked = 0;
+	if (--tm_dbg_on_state_passes == 0) {
+		switch (tm_dbg_state) {
+		case TM_DBG_STATE_ABORT:
+			TRACE_MGMT_DBG("%s", "Changing "
+			    "tm_dbg_state to RESET");
+			tm_dbg_state = TM_DBG_STATE_RESET;
+			tm_dbg_flags.tm_dbg_blocked = 0;
+			break;
+		case TM_DBG_STATE_RESET:
+		case TM_DBG_STATE_OFFLINE:
+#ifdef CONFIG_SCST_TM_DBG_GO_OFFLINE
+			    TRACE_MGMT_DBG("%s", "Changing "
+				    "tm_dbg_state to OFFLINE");
+			    tm_dbg_state = TM_DBG_STATE_OFFLINE;
+#else
+			    TRACE_MGMT_DBG("%s", "Changing "
+				    "tm_dbg_state to ABORT");
+			    tm_dbg_state = TM_DBG_STATE_ABORT;
+#endif
+			break;
+		default:
+			BUG();
+		}
+		tm_dbg_on_state_passes =
+		    tm_dbg_on_state_num_passes[tm_dbg_state];
+	}
+
+	TRACE_MGMT_DBG("%s", "Deleting timer");
+	del_timer_sync(&tm_dbg_timer);
+	return;
+}
+
+/* No locks */
+int tm_dbg_check_cmd(struct scst_cmd *cmd)
+{
+	int res = 0;
+	unsigned long flags;
+
+	if (cmd->tm_dbg_immut)
+		goto out;
+
+	if (cmd->tm_dbg_delayed) {
+		spin_lock_irqsave(&scst_tm_dbg_lock, flags);
+		TRACE_MGMT_DBG("Processing delayed cmd %p (tag %llu), "
+			"delayed_cmds_count=%d", cmd, cmd->tag,
+			tm_dbg_delayed_cmds_count);
+
+		cmd->tm_dbg_immut = 1;
+		tm_dbg_delayed_cmds_count--;
+		if ((tm_dbg_delayed_cmds_count == 0) &&
+		    (tm_dbg_state == TM_DBG_STATE_ABORT))
+			tm_dbg_change_state();
+		spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
+	} else if (cmd->tgt_dev && (tm_dbg_tgt_dev == cmd->tgt_dev)) {
+		/* Delay 50th command */
+		spin_lock_irqsave(&scst_tm_dbg_lock, flags);
+		if (tm_dbg_flags.tm_dbg_blocked ||
+		    (++tm_dbg_passed_cmds_count % 50) == 0) {
+			tm_dbg_delay_cmd(cmd);
+			res = 1;
+		} else
+			cmd->tm_dbg_immut = 1;
+		spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
+	}
+
+out:
+	return res;
+}
+
+/* No locks */
+void tm_dbg_release_cmd(struct scst_cmd *cmd)
+{
+	struct scst_cmd *c;
+	unsigned long flags;
+
+	spin_lock_irqsave(&scst_tm_dbg_lock, flags);
+	list_for_each_entry(c, &tm_dbg_delayed_cmd_list,
+				cmd_list_entry) {
+		if (c == cmd) {
+			TRACE_MGMT_DBG("Abort request for "
+				"delayed cmd %p (tag=%llu), moving it to "
+				"active cmd list (delayed_cmds_count=%d)",
+				c, c->tag, tm_dbg_delayed_cmds_count);
+
+			if (!test_bit(SCST_CMD_ABORTED_OTHER,
+					    &cmd->cmd_flags)) {
+				/* Test how completed commands handled */
+				if (((scst_random() % 10) == 5)) {
+					scst_set_cmd_error(cmd,
+						SCST_LOAD_SENSE(
+						scst_sense_hardw_error));
+					/* It's completed now */
+				}
+			}
+
+			spin_lock(&cmd->cmd_threads->cmd_list_lock);
+			list_move(&c->cmd_list_entry,
+				&c->cmd_threads->active_cmd_list);
+			wake_up(&c->cmd_threads->cmd_list_waitQ);
+			spin_unlock(&cmd->cmd_threads->cmd_list_lock);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
+	return;
+}
+
+/* Might be called under scst_mutex */
+void tm_dbg_task_mgmt(struct scst_device *dev, const char *fn, int force)
+{
+	unsigned long flags;
+
+	if (dev != NULL) {
+		if (tm_dbg_tgt_dev == NULL)
+			goto out;
+
+		if (tm_dbg_tgt_dev->dev != dev)
+			goto out;
+	}
+
+	spin_lock_irqsave(&scst_tm_dbg_lock, flags);
+	if ((tm_dbg_state != TM_DBG_STATE_OFFLINE) || force) {
+		TRACE_MGMT_DBG("%s: freeing %d delayed cmds", fn,
+			tm_dbg_delayed_cmds_count);
+		tm_dbg_change_state();
+		tm_dbg_flags.tm_dbg_release = 1;
+		/*
+		 * Used to make sure that all woken up threads see the new
+		 * value.
+		 */
+		smp_wmb();
+		if (tm_dbg_tgt_dev != NULL)
+			wake_up_all(&tm_dbg_tgt_dev->active_cmd_threads->cmd_list_waitQ);
+	} else {
+		TRACE_MGMT_DBG("%s: while OFFLINE state, doing nothing", fn);
+	}
+	spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
+
+out:
+	return;
+}
+
+int tm_dbg_is_release(void)
+{
+	return tm_dbg_flags.tm_dbg_release;
+}
+#endif /* CONFIG_SCST_DEBUG_TM */
+
+#ifdef CONFIG_SCST_DEBUG_SN
+void scst_check_debug_sn(struct scst_cmd *cmd)
+{
+	static DEFINE_SPINLOCK(lock);
+	static int type;
+	static int cnt;
+	unsigned long flags;
+	int old = cmd->queue_type;
+
+	spin_lock_irqsave(&lock, flags);
+
+	if (cnt == 0) {
+		if ((scst_random() % 1000) == 500) {
+			if ((scst_random() % 3) == 1)
+				type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
+			else
+				type = SCST_CMD_QUEUE_ORDERED;
+			do {
+				cnt = scst_random() % 10;
+			} while (cnt == 0);
+		} else
+			goto out_unlock;
+	}
+
+	cmd->queue_type = type;
+	cnt--;
+
+	if (((scst_random() % 1000) == 750))
+		cmd->queue_type = SCST_CMD_QUEUE_ORDERED;
+	else if (((scst_random() % 1000) == 751))
+		cmd->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
+	else if (((scst_random() % 1000) == 752))
+		cmd->queue_type = SCST_CMD_QUEUE_SIMPLE;
+
+	TRACE_SN("DbgSN changed cmd %p: %d/%d (cnt %d)", cmd, old,
+		cmd->queue_type, cnt);
+
+out_unlock:
+	spin_unlock_irqrestore(&lock, flags);
+	return;
+}
+#endif /* CONFIG_SCST_DEBUG_SN */
+
+#ifdef CONFIG_SCST_MEASURE_LATENCY
+
+static uint64_t scst_get_nsec(void)
+{
+	struct timespec ts;
+	ktime_get_ts(&ts);
+	return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
+}
+
+void scst_set_start_time(struct scst_cmd *cmd)
+{
+	cmd->start = scst_get_nsec();
+	TRACE_DBG("cmd %p: start %lld", cmd, cmd->start);
+}
+
+void scst_set_cur_start(struct scst_cmd *cmd)
+{
+	cmd->curr_start = scst_get_nsec();
+	TRACE_DBG("cmd %p: cur_start %lld", cmd, cmd->curr_start);
+}
+
+void scst_set_parse_time(struct scst_cmd *cmd)
+{
+	cmd->parse_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: parse_time %lld", cmd, cmd->parse_time);
+}
+
+void scst_set_alloc_buf_time(struct scst_cmd *cmd)
+{
+	cmd->alloc_buf_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: alloc_buf_time %lld", cmd, cmd->alloc_buf_time);
+}
+
+void scst_set_restart_waiting_time(struct scst_cmd *cmd)
+{
+	cmd->restart_waiting_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: restart_waiting_time %lld", cmd,
+		cmd->restart_waiting_time);
+}
+
+void scst_set_rdy_to_xfer_time(struct scst_cmd *cmd)
+{
+	cmd->rdy_to_xfer_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: rdy_to_xfer_time %lld", cmd, cmd->rdy_to_xfer_time);
+}
+
+void scst_set_pre_exec_time(struct scst_cmd *cmd)
+{
+	cmd->pre_exec_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: pre_exec_time %lld", cmd, cmd->pre_exec_time);
+}
+
+void scst_set_exec_time(struct scst_cmd *cmd)
+{
+	cmd->exec_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: exec_time %lld", cmd, cmd->exec_time);
+}
+
+void scst_set_dev_done_time(struct scst_cmd *cmd)
+{
+	cmd->dev_done_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: dev_done_time %lld", cmd, cmd->dev_done_time);
+}
+
+void scst_set_xmit_time(struct scst_cmd *cmd)
+{
+	cmd->xmit_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: xmit_time %lld", cmd, cmd->xmit_time);
+}
+
+void scst_set_tgt_on_free_time(struct scst_cmd *cmd)
+{
+	cmd->tgt_on_free_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: tgt_on_free_time %lld", cmd, cmd->tgt_on_free_time);
+}
+
+void scst_set_dev_on_free_time(struct scst_cmd *cmd)
+{
+	cmd->dev_on_free_time += scst_get_nsec() - cmd->curr_start;
+	TRACE_DBG("cmd %p: dev_on_free_time %lld", cmd, cmd->dev_on_free_time);
+}
+
+void scst_update_lat_stats(struct scst_cmd *cmd)
+{
+	uint64_t finish, scst_time, tgt_time, dev_time;
+	struct scst_session *sess = cmd->sess;
+	int data_len;
+	int i;
+	struct scst_ext_latency_stat *latency_stat, *dev_latency_stat;
+
+	finish = scst_get_nsec();
+
+	/* Determine the IO size for extended latency statistics */
+	data_len = cmd->bufflen;
+	i = SCST_LATENCY_STAT_INDEX_OTHER;
+	if (data_len <= SCST_IO_SIZE_THRESHOLD_SMALL)
+		i = SCST_LATENCY_STAT_INDEX_SMALL;
+	else if (data_len <= SCST_IO_SIZE_THRESHOLD_MEDIUM)
+		i = SCST_LATENCY_STAT_INDEX_MEDIUM;
+	else if (data_len <= SCST_IO_SIZE_THRESHOLD_LARGE)
+		i = SCST_LATENCY_STAT_INDEX_LARGE;
+	else if (data_len <= SCST_IO_SIZE_THRESHOLD_VERY_LARGE)
+		i = SCST_LATENCY_STAT_INDEX_VERY_LARGE;
+	latency_stat = &sess->sess_latency_stat[i];
+	dev_latency_stat = &cmd->tgt_dev->dev_latency_stat[i];
+
+	/* Calculate the latencies */
+	scst_time = finish - cmd->start - (cmd->parse_time +
+		cmd->alloc_buf_time + cmd->restart_waiting_time +
+		cmd->rdy_to_xfer_time + cmd->pre_exec_time +
+		cmd->exec_time + cmd->dev_done_time + cmd->xmit_time +
+		cmd->tgt_on_free_time + cmd->dev_on_free_time);
+	tgt_time = cmd->alloc_buf_time + cmd->restart_waiting_time +
+		cmd->rdy_to_xfer_time + cmd->pre_exec_time +
+		cmd->xmit_time + cmd->tgt_on_free_time;
+	dev_time = cmd->parse_time + cmd->exec_time + cmd->dev_done_time +
+		cmd->dev_on_free_time;
+
+	spin_lock_bh(&sess->lat_lock);
+
+	/* Save the basic latency information */
+	sess->scst_time += scst_time;
+	sess->tgt_time += tgt_time;
+	sess->dev_time += dev_time;
+	sess->processed_cmds++;
+
+	if ((sess->min_scst_time == 0) ||
+	    (sess->min_scst_time > scst_time))
+		sess->min_scst_time = scst_time;
+	if ((sess->min_tgt_time == 0) ||
+	    (sess->min_tgt_time > tgt_time))
+		sess->min_tgt_time = tgt_time;
+	if ((sess->min_dev_time == 0) ||
+	    (sess->min_dev_time > dev_time))
+		sess->min_dev_time = dev_time;
+
+	if (sess->max_scst_time < scst_time)
+		sess->max_scst_time = scst_time;
+	if (sess->max_tgt_time < tgt_time)
+		sess->max_tgt_time = tgt_time;
+	if (sess->max_dev_time < dev_time)
+		sess->max_dev_time = dev_time;
+
+	/* Save the extended latency information */
+	if (cmd->data_direction & SCST_DATA_READ) {
+		latency_stat->scst_time_rd += scst_time;
+		latency_stat->tgt_time_rd += tgt_time;
+		latency_stat->dev_time_rd += dev_time;
+		latency_stat->processed_cmds_rd++;
+
+		if ((latency_stat->min_scst_time_rd == 0) ||
+		    (latency_stat->min_scst_time_rd > scst_time))
+			latency_stat->min_scst_time_rd = scst_time;
+		if ((latency_stat->min_tgt_time_rd == 0) ||
+		    (latency_stat->min_tgt_time_rd > tgt_time))
+			latency_stat->min_tgt_time_rd = tgt_time;
+		if ((latency_stat->min_dev_time_rd == 0) ||
+		    (latency_stat->min_dev_time_rd > dev_time))
+			latency_stat->min_dev_time_rd = dev_time;
+
+		if (latency_stat->max_scst_time_rd < scst_time)
+			latency_stat->max_scst_time_rd = scst_time;
+		if (latency_stat->max_tgt_time_rd < tgt_time)
+			latency_stat->max_tgt_time_rd = tgt_time;
+		if (latency_stat->max_dev_time_rd < dev_time)
+			latency_stat->max_dev_time_rd = dev_time;
+
+		dev_latency_stat->scst_time_rd += scst_time;
+		dev_latency_stat->tgt_time_rd += tgt_time;
+		dev_latency_stat->dev_time_rd += dev_time;
+		dev_latency_stat->processed_cmds_rd++;
+
+		if ((dev_latency_stat->min_scst_time_rd == 0) ||
+		    (dev_latency_stat->min_scst_time_rd > scst_time))
+			dev_latency_stat->min_scst_time_rd = scst_time;
+		if ((dev_latency_stat->min_tgt_time_rd == 0) ||
+		    (dev_latency_stat->min_tgt_time_rd > tgt_time))
+			dev_latency_stat->min_tgt_time_rd = tgt_time;
+		if ((dev_latency_stat->min_dev_time_rd == 0) ||
+		    (dev_latency_stat->min_dev_time_rd > dev_time))
+			dev_latency_stat->min_dev_time_rd = dev_time;
+
+		if (dev_latency_stat->max_scst_time_rd < scst_time)
+			dev_latency_stat->max_scst_time_rd = scst_time;
+		if (dev_latency_stat->max_tgt_time_rd < tgt_time)
+			dev_latency_stat->max_tgt_time_rd = tgt_time;
+		if (dev_latency_stat->max_dev_time_rd < dev_time)
+			dev_latency_stat->max_dev_time_rd = dev_time;
+	} else if (cmd->data_direction & SCST_DATA_WRITE) {
+		latency_stat->scst_time_wr += scst_time;
+		latency_stat->tgt_time_wr += tgt_time;
+		latency_stat->dev_time_wr += dev_time;
+		latency_stat->processed_cmds_wr++;
+
+		if ((latency_stat->min_scst_time_wr == 0) ||
+		    (latency_stat->min_scst_time_wr > scst_time))
+			latency_stat->min_scst_time_wr = scst_time;
+		if ((latency_stat->min_tgt_time_wr == 0) ||
+		    (latency_stat->min_tgt_time_wr > tgt_time))
+			latency_stat->min_tgt_time_wr = tgt_time;
+		if ((latency_stat->min_dev_time_wr == 0) ||
+		    (latency_stat->min_dev_time_wr > dev_time))
+			latency_stat->min_dev_time_wr = dev_time;
+
+		if (latency_stat->max_scst_time_wr < scst_time)
+			latency_stat->max_scst_time_wr = scst_time;
+		if (latency_stat->max_tgt_time_wr < tgt_time)
+			latency_stat->max_tgt_time_wr = tgt_time;
+		if (latency_stat->max_dev_time_wr < dev_time)
+			latency_stat->max_dev_time_wr = dev_time;
+
+		dev_latency_stat->scst_time_wr += scst_time;
+		dev_latency_stat->tgt_time_wr += tgt_time;
+		dev_latency_stat->dev_time_wr += dev_time;
+		dev_latency_stat->processed_cmds_wr++;
+
+		if ((dev_latency_stat->min_scst_time_wr == 0) ||
+		    (dev_latency_stat->min_scst_time_wr > scst_time))
+			dev_latency_stat->min_scst_time_wr = scst_time;
+		if ((dev_latency_stat->min_tgt_time_wr == 0) ||
+		    (dev_latency_stat->min_tgt_time_wr > tgt_time))
+			dev_latency_stat->min_tgt_time_wr = tgt_time;
+		if ((dev_latency_stat->min_dev_time_wr == 0) ||
+		    (dev_latency_stat->min_dev_time_wr > dev_time))
+			dev_latency_stat->min_dev_time_wr = dev_time;
+
+		if (dev_latency_stat->max_scst_time_wr < scst_time)
+			dev_latency_stat->max_scst_time_wr = scst_time;
+		if (dev_latency_stat->max_tgt_time_wr < tgt_time)
+			dev_latency_stat->max_tgt_time_wr = tgt_time;
+		if (dev_latency_stat->max_dev_time_wr < dev_time)
+			dev_latency_stat->max_dev_time_wr = dev_time;
+	}
+
+	spin_unlock_bh(&sess->lat_lock);
+
+	TRACE_DBG("cmd %p: finish %lld, scst_time %lld, "
+		"tgt_time %lld, dev_time %lld", cmd, finish, scst_time,
+		tgt_time, dev_time);
+	return;
+}
+
+#endif /* CONFIG_SCST_MEASURE_LATENCY */



  parent reply	other threads:[~2010-09-14 14:43 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-09-14 14:36 [PATCH 0/17]: New SCSI target framework (SCST) with dev handlers and 2 target drivers Vladislav Bolkhovitin
2010-09-14 14:37 ` [PATCH 1/17]: Integration of SCST into the Linux kernel tree Vladislav Bolkhovitin
2010-09-14 14:38 ` [PATCH 2/17]: SCST core's Makefile and Kconfig Vladislav Bolkhovitin
2010-09-14 14:39 ` [PATCH 3/17]: SCST public headers Vladislav Bolkhovitin
2010-09-14 14:40 ` [PATCH 4/17]: SCST main management files and private headers Vladislav Bolkhovitin
2010-09-14 14:41 ` [PATCH 5/17]: SCST implementation of the SCSI target state machine Vladislav Bolkhovitin
2010-09-14 14:42 ` Vladislav Bolkhovitin [this message]
2010-09-14 14:43 ` [PATCH 7/17]: SCST Persistent Reservations implementation Vladislav Bolkhovitin
2010-09-14 14:44 ` [PATCH 8/17]: SCST SYSFS interface implementation Vladislav Bolkhovitin
2010-09-14 14:44 ` [PATCH 9/17]: SCST debugging support routines Vladislav Bolkhovitin
2010-09-14 14:45 ` [PATCH 10/17]: SCST SGV cache Vladislav Bolkhovitin
2010-09-14 14:45 ` [PATCH 11/17]: SCST core's docs Vladislav Bolkhovitin
2010-09-14 14:46 ` [PATCH 12/17]: SCST dev handlers' Makefile Vladislav Bolkhovitin
2010-09-14 14:47 ` [PATCH 13/17]: SCST vdisk dev handler Vladislav Bolkhovitin
2010-09-14 14:48 ` [PATCH 14/17]: SCST pass-through dev handlers Vladislav Bolkhovitin
2010-09-14 14:49 ` [PATCH 15/17]: Implementation of blk_rq_map_kern_sg() Vladislav Bolkhovitin
2010-09-14 14:50 ` [PATCH 16/17]: scst_local target driver Vladislav Bolkhovitin
2010-09-14 14:51 ` [PATCH 17/17]: SCST InfiniBand SRP " Vladislav Bolkhovitin
2010-09-15  5:56 ` [PATCH 0/17]: New SCSI target framework (SCST) with dev handlers and 2 target drivers Dmitry Torokhov
2010-09-15 14:52   ` Vladislav Bolkhovitin
2010-09-15 18:07     ` Vladislav Bolkhovitin
2010-09-19  9:54   ` Bart Van Assche

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4C8F89DC.9050406@vlnb.net \
    --to=vst@vlnb.net \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=James.Smart@Emulex.Com \
    --cc=akpm@linux-foundation.org \
    --cc=ayan@marvell.com \
    --cc=bart.vanassche@gmail.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=fujita.tomonori@lab.ntt.co.jp \
    --cc=generationgnu@yahoo.com \
    --cc=hare@suse.de \
    --cc=jeff@garzik.org \
    --cc=jeykholt@cisco.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-scsi@vger.kernel.org \
    --cc=michaelc@cs.wisc.edu \
    --cc=realrichardsharpe@gmail.com \
    --cc=scst-devel@lists.sourceforge.net \
    --cc=vuhuong@mellanox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.