Skip to contentSkip to conversation

: New SCSI target framework (SCST) with dev handlers and 2 target drivers

PATCH In review Vladislav Bolkhovitin 11 participants 93 messages Oct 1, 2010 – Dec 14, 2010 Page 1 of 2

Subsystems (8)
lore
Share & tools
lore
#0 lore raw

v4 cover : New SCSI target framework (SCST) with dev handlers and 2 target drivers

Hi All,

Please review the next iteration of the patch set of the new (although, in fact, the oldest) SCSI target framework for Linux SCST with a set of dev handlers and 3 target drivers: for local access (scst_local), for Infiniband SRP (srpt) and IBM POWER Virtual SCSI .

SCST is the most advanced and features rich SCSI target subsystem for Linux. It allows to build the fastest devices and clusters delivering millions of IOPS. Many companies are using it as a foundation for their storage products and solutions. List of some of them you can find on
http://scst.sourceforge.net/users.html. There are also many other who not yet added there or prefer for some reasons to not be listed on this page.

The previous iterations of the SCST patch set you can find on
http://lkml.org/lkml/2008 and http://lkml.org/lkml/2010/4/13/146.

We believe that code is fully mainline ready (considering that ibmvstgt driver for SCST not yet tested on hardware).

This iteration for simplicity contains only 2 target drivers: for local access (scst_local) and SRP (ib_srpt). If SCST accepted, we will prepare and submit patches for other target drivers: for iSCSI, FCoE, QLogic Fibre Channel QLA 2xxx, Emulex Fibre Channel/FCoE and Marvell 88SE63xx/64xx/68xx/94xx SAS hardware.

Also this patchset contains port of ibmvstgt driver for SCST, many thanks to Bart Van Assche for performing it!

This patchset is for kernel 2.6.35. On request we will rebase it for any other kernel tree. Since SCST is quite self-contained and almost doesn't touch outside kernel code, there are no problems in it.

As Dmitry Torokhov requested (thanks for the review!), in this iteration we added more detail descriptions of each patch. Since the space for description is too limited, we can't describe full SCST internals (it's worth a book). But we hope that those description will be a good starting point.

More detail description of SCST, its drivers and utilities you can find on SCST home page http://scst.sourceforge.net. Detail features list and comparison with other Linux target subsystems you can find on
http://scst.sourceforge.net/comparison.html. Description of the SCST processing flow you can find in http://scst.sourceforge.net/scst_pg.html
(especially see picture "The commands processing flow").

SCST is complete self-contained subsystem. It shares only few with the Linux SCSI (initiator) subsystem: constants and some low level utility functions. This is a deliberate decision, because the task of implementing a SCSI target (i.e. server) is orthogonal to the task of implementing a SCSI initiator (i.e. client). Such orthogonality between client and server implementations is quite common. In fact, I can't recall anything, where client and server are coupled together. You can see how few NFS client and server are sharing in the Linux kernel (<300 LOC). For me to build SCSI target around initiator is similar as to build Sendmail around Mutt, or Apache around Firefox.

I'm writing about it so much, because STGT's in-kernel interface and data fields embedded into the Linux SCSI (initiator) subsystem, i.e. STGT is built around the Linux SCSI (initiator) subsystem, and this approach considered the best by many kernel developers. But, in fact, this approach is wrong, because of orthogonality between initiator and target subsystems. A good design is to keep orthogonal things separately, not coupled together.

Consequences of this bad move is predictable and common for cases when separate things coupled together:

1. Internal: sometimes it isn't obvious if a function or data type is for target or initiator mode. Hence, it is easy fixing initiator mode break target and vice versa.

2. User visible. At the moment, supposed to be initiator-side scsi_transport_* modules for FC and SRP depends from the target side module module scsi_tgt, like:

# lsmod
Module                  Size  Used by
qla2xxx               130844  0
firmware_class          8064  1 qla2xxx
scsi_transport_fc      40900  1 qla2xxx
scsi_tgt               12196  1 scsi_transport_fc
...

This means that all FC and SRP users must have scsi_tgt loaded although they never going to run a SCSI target and have never heard about the only module in the kernel which actually needs scsi_tgt.ko: ibmvstgt.ko.

SCSI target and initiator devices generally live under different lifetime rules starting from initialization: initiator devices created by target scanning, while target devices created by external action on the target side.

SCSI target and initiator devices doing opposite processing: initiator is initiating SCSI commands, while target is processing them. The target side initiating notifications, the initiator side - processing them, for instance, by rescanning the corresponding target port after REPORT LUNS DATA HAS CHANGED Unit Attention. Even DMA transfer direction for the same commands is opposite. For instance for a READ command: TO_DEVICE for target device and FROM_DEVICE for initiator device.

SCSI target and initiator subsystems need completely different user interface, because the initiator subsystem creates devices internally, but for the target subsystem devices created by external actions via the user interface.

Yes, sure, there are cases when a HBA can serve both target and initiator modes, but this is rather a special case. For instance, SCST has 9 target drivers, from which 7 are target mode only drivers. For such cases, the best is (and this approach is used by mvsas_tgt and qla2x00t drivers):

- To make the target mode part a separate add-on to the main initiator
mode driver. The initiator mode driver will be responsible for initializing hardware and managing ports for both modes.

- To add to the initiator module a set of hooks to allow it to interact
with target mode add-on as soon as it is loaded and send to it target commands from initiators.

As an illustration, consider STGT ibmvstgt driver and see how many it actually shares with the initiator mode. It is defined as:

static struct scsi_host_template ibmvstgt_sht = {
.name = TGT_NAME,
.module = THIS_MODULE,
.can_queue = INITIAL_SRP_LIMIT,
.sg_tablesize = SG_ALL,
.use_clustering = DISABLE_CLUSTERING,
.max_sectors = DEFAULT_MAX_SECTORS,
.transfer_response = ibmvstgt_cmd_done,
.eh_abort_handler = ibmvstgt_eh_abort_handler,
.shost_attrs = ibmvstgt_attrs,
.proc_name = TGT_NAME,
.supported_mode = MODE_TARGET,
};

1. For "can_queue" we see in scsi_tgt_alloc_queue() commented code:

/*
* this is a silly hack. We should probably just queue as many
* command as is recvd to userspace. uspace can then make
* sure we do not overload the HBA
*/
q->nr_requests = shost->can_queue;

The comment is correct, can_queue is nonsense on the target side, because initiator is queuing commands and a target driver generally can't do anything with it, so can_queue isn't property of a target driver, but property of common flow control managed by the target mid-layer.

2. Similarly, "max_sectors" is also nonsense for a target driver, because it doesn't queuing commands. In SCSI such limit is negotiated by Block Limits VPD page on per LUN, not per target driver basis.

3. eh_abort_handler(). Such callback in the way how it is used by STGT can only be used with a single threaded processing, because abort can happen fully asynchronously to the processing of the affected command, so the processing would need complicated locking to allow such async abort. On practice with multithreaded processing it is much simpler to have all commands processing fully synchronous and check for aborts only in specially defined places. In this approach, such callback isn't needed.

4. transfer_response() - fully target mode callback

5. shost_attrs and proc_name - it's better to have target mode user interface in different place with initiator mode.

Thus, we have shared between target and initiator modes only "name", "module", "sg_tablesize", "use_clustering" fields. Not many, yes?

In the descriptions for specific patches I'll evaluate more about SCST architecture.

Thanks,
Vlad

#1 lore raw

PATCH v4 1/19 : Integration of SCST into the Linux kernel tree

This patch contains changes in drivers/Kconfig and drivers/Makefile to integrate SCST into the Linux kernel tree.

---
 Kconfig  |    2 ++
 Makefile |    1 +
 2 files changed, 3 insertions(+)
drivers/Kconfig 14:14:46.000000000 +0400 renamed from linux-2.6.35/drivers/Kconfig 01:51:29.000000000 +0400 +2 -0
diff -upkr -X linux-2.6.35/Documentation/dontdiff linux-2.6.35/drivers/Kconfig linux-2.6.35/drivers/Kconfig
@@ -22,6 +22,8 @@ source "drivers/ide/Kconfig"
 
 source "drivers/scsi/Kconfig"
 
+source "drivers/scst/Kconfig"
+
 source "drivers/ata/Kconfig"
 
 source "drivers/md/Kconfig"
drivers/Makefile 14:15:29.000000000 +0400 renamed from linux-2.6.35/drivers/Makefile 01:51:29.000000000 +0400 +1 -0
diff -upkr -X linux-2.6.35/Documentation/dontdiff linux-2.6.35/drivers/Makefile linux-2.6.35/drivers/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_ATM)		+= atm/
 obj-y				+= macintosh/
 obj-$(CONFIG_IDE)		+= ide/
 obj-$(CONFIG_SCSI)		+= scsi/
+obj-$(CONFIG_SCST)		+= scst/
 obj-$(CONFIG_ATA)		+= ata/
 obj-y				+= net/
 obj-$(CONFIG_ATM)		+= atm/
#2 lore raw

PATCH v4 2/19 : SCST core's Makefile and Kconfig

In reply to Vladislav Bolkhovitin

This patch contains SCST core's Makefile and Kconfig.

---
 Kconfig  |  244 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 Makefile |   11 ++
 2 files changed, 255 insertions(+)
drivers/scst/Kconfig renamed from linux-2.6.35/drivers/scst/Kconfig +244 -0
diff -uprN orig/linux-2.6.35/drivers/scst/Kconfig linux-2.6.35/drivers/scst/Kconfig
@@ -0,0 +1,244 @@
+menu "SCSI target (SCST) support"
+
+config SCST
+	tristate "SCSI target (SCST) support"
+	depends on SCSI
+	help
+	  SCSI target (SCST) is designed to provide unified, consistent
+	  interface between SCSI target drivers and Linux kernel and
+	  simplify target drivers development as much as possible. Visit
+	  http://scst.sourceforge.net for more info about it.
+
+config SCST_DISK
+	tristate "SCSI target disk support"
+	default SCST
+	depends on SCSI && SCST
+	help
+	  SCST pass-through device handler for disk device.
+
+config SCST_TAPE
+	tristate "SCSI target tape support"
+	default SCST
+	depends on SCSI && SCST
+	help
+	  SCST pass-through device handler for tape device.
+
+config SCST_CDROM
+	tristate "SCSI target CDROM support"
+	default SCST
+	depends on SCSI && SCST
+	help
+	  SCST pass-through device handler for CDROM device.
+
+config SCST_MODISK
+	tristate "SCSI target MO disk support"
+	default SCST
+	depends on SCSI && SCST
+	help
+	  SCST pass-through device handler for MO disk device.
+
+config SCST_CHANGER
+	tristate "SCSI target changer support"
+	default SCST
+	depends on SCSI && SCST
+	help
+	  SCST pass-through device handler for changer device.
+
+config SCST_PROCESSOR
+	tristate "SCSI target processor support"
+	default SCST
+	depends on SCSI && SCST
+	help
+	  SCST pass-through device handler for processor device.
+
+config SCST_RAID
+	tristate "SCSI target storage array controller (RAID) support"
+	default SCST
+	depends on SCSI && SCST
+	help
+	  SCST pass-through device handler for raid storage array controller (RAID) device.
+
+config SCST_VDISK
+	tristate "SCSI target virtual disk and/or CDROM support"
+	default SCST
+	depends on SCSI && SCST
+	help
+	  SCST device handler for virtual disk and/or CDROM device.
+
+config SCST_STRICT_SERIALIZING
+	bool "Strict serialization"
+	depends on SCST
+	help
+	  Enable strict SCSI command serialization. When enabled, SCST sends
+	  all SCSI commands to the underlying SCSI device synchronously, one
+	  after one. This makes task management more reliable, at the cost of
+	  a performance penalty. This is most useful for stateful SCSI devices
+	  like tapes, where the result of the execution of a command
+	  depends on the device settings configured by previous commands. Disk
+	  and RAID devices are stateless in most cases. The current SCSI core
+	  in Linux doesn't allow to abort all commands reliably if they have
+	  been sent asynchronously to a stateful device.
+	  Enable this option if you use stateful device(s) and need as much
+	  error recovery reliability as possible.
+
+	  If unsure, say "N".
+
+config SCST_STRICT_SECURITY
+	bool "Strict security"
+	depends on SCST
+	help
+	  Makes SCST clear (zero-fill) allocated data buffers. Note: this has a
+	  significant performance penalty.
+
+	  If unsure, say "N".
+
+config SCST_TEST_IO_IN_SIRQ
+	bool "Allow test I/O from soft-IRQ context"
+	depends on SCST
+	help
+	  Allows SCST to submit selected SCSI commands (TUR and
+	  READ/WRITE) from soft-IRQ context (tasklets). Enabling it will
+	  decrease amount of context switches and slightly improve
+	  performance. The goal of this option is to be able to measure
+	  overhead of the context switches. See more info about it in
+	  README.scst.
+
+	  WARNING! Improperly used, this option can lead you to a kernel crash!
+
+	  If unsure, say "N".
+
+config SCST_ABORT_CONSIDER_FINISHED_TASKS_AS_NOT_EXISTING
+	bool "Send back UNKNOWN TASK when an already finished task is aborted"
+	depends on SCST
+	help
+	  Controls which response is sent by SCST to the initiator in case
+	  the initiator attempts to abort (ABORT TASK) an already finished
+	  request. If this option is enabled, the response UNKNOWN TASK is
+	  sent back to the initiator. However, some initiators, particularly
+	  the VMware iSCSI initiator, interpret the UNKNOWN TASK response as
+	  if the target got crazy and try to RESET it. Then sometimes the
+	  initiator gets crazy itself.
+
+	  If unsure, say "N".
+
+config SCST_USE_EXPECTED_VALUES
+	bool "Prefer initiator-supplied SCSI command attributes"
+	depends on SCST
+	help
+	  When SCST receives a SCSI command from an initiator, such a SCSI
+	  command has both data transfer length and direction attributes.
+	  There are two possible sources for these attributes: either the
+	  values computed by SCST from its internal command translation table
+	  or the values supplied by the initiator. The former are used by
+	  default because of security reasons. Invalid initiator-supplied
+	  attributes can crash the target, especially in pass-through mode.
+	  Only consider enabling this option when SCST logs the following
+	  message: "Unknown opcode XX for YY. Should you update
+	  scst_scsi_op_table?" and when the initiator complains. Please
+	  report any unrecognized commands to [email protected].
+
+	  If unsure, say "N".
+
+config SCST_EXTRACHECKS
+	bool "Extra consistency checks"
+	depends on SCST
+	help
+	  Enable additional consistency checks in the SCSI middle level target
+	  code. This may be helpful for SCST developers. Enable it if you have
+	  any problems.
+
+	  If unsure, say "N".
+
+config SCST_TRACING
+	bool "Tracing support"
+	depends on SCST
+	default y
+	help
+	  Enable SCSI middle level tracing support. Tracing can be controlled
+	  dynamically via sysfs interface. The traced information
+	  is sent to the kernel log and may be very helpful when analyzing
+	  the cause of a communication problem between initiator and target.
+
+	  If unsure, say "Y".
+
+config SCST_DEBUG
+	bool "Debugging support"
+	depends on SCST
+	select DEBUG_BUGVERBOSE
+	help
+	  Enables support for debugging SCST. This may be helpful for SCST
+	  developers.
+
+	  If unsure, say "N".
+
+config SCST_DEBUG_OOM
+	bool "Out-of-memory debugging support"
+	depends on SCST
+	help
+	  Let SCST's internal memory allocation function
+	  (scst_alloc_sg_entries()) fail about once in every 10000 calls, at
+	  least if the flag __GFP_NOFAIL has not been set. This allows SCST
+	  developers to test the behavior of SCST in out-of-memory conditions.
+	  This may be helpful for SCST developers.
+
+	  If unsure, say "N".
+
+config SCST_DEBUG_RETRY
+	bool "SCSI command retry debugging support"
+	depends on SCST
+	help
+	  Let SCST's internal SCSI command transfer function
+	  (scst_rdy_to_xfer()) fail about once in every 100 calls. This allows
+	  SCST developers to test the behavior of SCST when SCSI queues fill
+	  up. This may be helpful for SCST developers.
+
+	  If unsure, say "N".
+
+config SCST_DEBUG_SN
+	bool "SCSI sequence number debugging support"
+	depends on SCST
+	help
+	  Allows to test SCSI command ordering via sequence numbers by
+	  randomly changing the type of SCSI commands into
+	  SCST_CMD_QUEUE_ORDERED, SCST_CMD_QUEUE_HEAD_OF_QUEUE or
+	  SCST_CMD_QUEUE_SIMPLE for about one in 300 SCSI commands.
+	  This may be helpful for SCST developers.
+
+	  If unsure, say "N".
+
+config SCST_DEBUG_TM
+	bool "Task management debugging support"
+	depends on SCST_DEBUG
+	help
+	  Enables support for debugging of SCST's task management functions.
+	  When enabled, some of the commands on LUN 0 in the default access
+	  control group will be delayed for about 60 seconds. This will
+	  cause the remote initiator send SCSI task management functions,
+	  e.g. ABORT TASK and TARGET RESET.
+
+	  If unsure, say "N".
+
+config SCST_TM_DBG_GO_OFFLINE
+	bool "Let devices become completely unresponsive"
+	depends on SCST_DEBUG_TM
+	help
+	  Enable this option if you want that the device eventually becomes
+	  completely unresponsive. When disabled, the device will receive
+	  ABORT and RESET commands.
+
+config SCST_MEASURE_LATENCY
+	bool "Commands processing latency measurement facility"
+	depends on SCST
+	help
+	  This option enables commands processing latency measurement
+	  facility in SCST. It will provide in the sysfs interface
+	  average commands processing latency statistics. You can clear
+	  already measured results by writing 0 in the corresponding sysfs file.
+	  Note, you need a non-preemtible kernel to have correct results.
+
+	  If unsure, say "N".
+
+source "drivers/scst/scst_local/Kconfig"
+source "drivers/scst/srpt/Kconfig"
+
+endmenu
drivers/scst/Makefile renamed from linux-2.6.35/drivers/scst/Makefile +11 -0
diff -uprN orig/linux-2.6.35/drivers/scst/Makefile linux-2.6.35/drivers/scst/Makefile
@@ -0,0 +1,11 @@
+ccflags-y += -Wno-unused-parameter
+
+scst-y        += scst_main.o
+scst-y        += scst_pres.o
+scst-y        += scst_targ.o
+scst-y        += scst_lib.o
+scst-y        += scst_sysfs.o
+scst-y        += scst_mem.o
+scst-y        += scst_debug.o
+
+obj-$(CONFIG_SCST)   += scst.o dev_handlers/ srpt/ scst_local/
#3 lore raw

PATCH v4 3/19 : SCST public headers

In reply to Vladislav Bolkhovitin

This patch contains declarations of all externally visible SCST constants, types and functions prototypes.

Particularly, it has definitions of all core SCST structures:

- scst_tgt_template - defines SCST target driver, its callbacks and SCST core behavior dealing with it.

- scst_dev_type - defines SCST backend driver, its callbacks and SCST core behavior dealing with it.

- scst_tgt - defines SCST target (SCSI target port). Analog of Scsi_Host on the initiator side.

- scst_session - defines SCST session (SCSI I_T nexus)

- scst_cmd - defines SCST command (SCSI I_T_L_Q nexus)

- scst_device - defines SCST device (SCSI device)

- scst_tgt_dev - defines SCSI I_T_L nexus

- scst_acg - defines SCST access control group. Such groups define which initiators see which LUNs.

as well as prototypes for all the functions to manage those objects.

---
 scst.h       | 3642 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scst_const.h |  376 ++++++
 2 files changed, 4018 insertions(+)
include/scst/scst_const.h renamed from linux-2.6.35/include/scst/scst_const.h +376 -0
diff -uprN orig/linux-2.6.35/include/scst/scst_const.h linux-2.6.35/include/scst/scst_const.h
@@ -0,0 +1,376 @@
+/*
+ *  include/scst_const.h
+ *
+ *  Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <[email protected]>
+ *  Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ *  Contains common SCST constants.
+ *
+ *  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.
+ */
+
+#ifndef __SCST_CONST_H
+#define __SCST_CONST_H
+
+#include <scsi/scsi.h>
+
+#define SCST_CONST_VERSION "$Revision: 2272 $"
+
+/*** Shared constants between user and kernel spaces ***/
+
+/* Max size of CDB */
+#define SCST_MAX_CDB_SIZE            16
+
+/* Max size of various names */
+#define SCST_MAX_NAME		     50
+
+/* Max size of external names, like initiator name */
+#define SCST_MAX_EXTERNAL_NAME	     256
+
+/*
+ * Size of sense sufficient to carry standard sense data.
+ * Warning! It's allocated on stack!
+ */
+#define SCST_STANDARD_SENSE_LEN      18
+
+/* Max size of sense */
+#define SCST_SENSE_BUFFERSIZE        96
+
+/*************************************************************
+ ** Allowed delivery statuses for cmd's delivery_status
+ *************************************************************/
+
+#define SCST_CMD_DELIVERY_SUCCESS	0
+#define SCST_CMD_DELIVERY_FAILED	-1
+#define SCST_CMD_DELIVERY_ABORTED	-2
+
+/*************************************************************
+ ** Values for task management functions
+ *************************************************************/
+#define SCST_ABORT_TASK			0
+#define SCST_ABORT_TASK_SET		1
+#define SCST_CLEAR_ACA			2
+#define SCST_CLEAR_TASK_SET		3
+#define SCST_LUN_RESET			4
+#define SCST_TARGET_RESET		5
+
+/** SCST extensions **/
+
+/*
+ * Notifies about I_T nexus loss event in the corresponding session.
+ * Aborts all tasks there, resets the reservation, if any, and sets
+ * up the I_T Nexus loss UA.
+ */
+#define SCST_NEXUS_LOSS_SESS		6
+
+/* Aborts all tasks in the corresponding session */
+#define SCST_ABORT_ALL_TASKS_SESS	7
+
+/*
+ * Notifies about I_T nexus loss event. Aborts all tasks in all sessions
+ * of the tgt, resets the reservations, if any,  and sets up the I_T Nexus
+ * loss UA.
+ */
+#define SCST_NEXUS_LOSS			8
+
+/* Aborts all tasks in all sessions of the tgt */
+#define SCST_ABORT_ALL_TASKS		9
+
+/*
+ * Internal TM command issued by SCST in scst_unregister_session(). It is the
+ * same as SCST_NEXUS_LOSS_SESS, except:
+ *  - it doesn't call task_mgmt_affected_cmds_done()
+ *  - it doesn't call task_mgmt_fn_done()
+ *  - it doesn't queue NEXUS LOSS UA.
+ *
+ * Target drivers must NEVER use it!!
+ */
+#define SCST_UNREG_SESS_TM		10
+
+/*
+ * Internal TM command issued by SCST in scst_pr_abort_reg(). It aborts all
+ * tasks from mcmd->origin_pr_cmd->tgt_dev, except mcmd->origin_pr_cmd.
+ * Additionally:
+ *  - it signals pr_aborting_cmpl completion when all affected
+ *    commands marked as aborted.
+ *  - it doesn't call task_mgmt_affected_cmds_done()
+ *  - it doesn't call task_mgmt_fn_done()
+ *  - it calls mcmd->origin_pr_cmd->scst_cmd_done() when all affected
+ *    commands aborted.
+ *
+ * Target drivers must NEVER use it!!
+ */
+#define SCST_PR_ABORT_ALL		11
+
+/*************************************************************
+ ** Values for mgmt cmd's status field. Codes taken from iSCSI
+ *************************************************************/
+#define SCST_MGMT_STATUS_SUCCESS		0
+#define SCST_MGMT_STATUS_TASK_NOT_EXIST		-1
+#define SCST_MGMT_STATUS_LUN_NOT_EXIST		-2
+#define SCST_MGMT_STATUS_FN_NOT_SUPPORTED	-5
+#define SCST_MGMT_STATUS_REJECTED		-255
+#define SCST_MGMT_STATUS_FAILED			-129
+
+/*************************************************************
+ ** SCSI task attribute queue types
+ *************************************************************/
+enum scst_cmd_queue_type {
+	SCST_CMD_QUEUE_UNTAGGED = 0,
+	SCST_CMD_QUEUE_SIMPLE,
+	SCST_CMD_QUEUE_ORDERED,
+	SCST_CMD_QUEUE_HEAD_OF_QUEUE,
+	SCST_CMD_QUEUE_ACA
+};
+
+/*************************************************************
+ ** CDB flags
+ **
+ ** Implicit ordered used for commands which need calm environment
+ ** without any simultaneous activities. For instance, for MODE
+ ** SELECT it is needed to correctly generate its UA.
+ *************************************************************/
+enum scst_cdb_flags {
+	SCST_TRANSFER_LEN_TYPE_FIXED =		0x0001,
+	SCST_SMALL_TIMEOUT =			0x0002,
+	SCST_LONG_TIMEOUT =			0x0004,
+	SCST_UNKNOWN_LENGTH =			0x0008,
+	SCST_INFO_VALID =			0x0010, /* must be single bit */
+	SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED =	0x0020,
+	SCST_IMPLICIT_HQ =			0x0040,
+	SCST_IMPLICIT_ORDERED =			0x0080,
+	SCST_SKIP_UA =				0x0100,
+	SCST_WRITE_MEDIUM =			0x0200,
+	SCST_LOCAL_CMD =			0x0400,
+	SCST_FULLY_LOCAL_CMD =			0x0800,
+	SCST_REG_RESERVE_ALLOWED =		0x1000,
+	SCST_WRITE_EXCL_ALLOWED =		0x2000,
+	SCST_EXCL_ACCESS_ALLOWED =		0x4000,
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+	SCST_TEST_IO_IN_SIRQ_ALLOWED =		0x8000,
+#endif
+};
+
+/*************************************************************
+ ** Data direction aliases. Changing it don't forget to change
+ ** scst_to_tgt_dma_dir as well!!
+ *************************************************************/
+#define SCST_DATA_UNKNOWN		0
+#define SCST_DATA_WRITE			1
+#define SCST_DATA_READ			2
+#define SCST_DATA_BIDI			(SCST_DATA_WRITE | SCST_DATA_READ)
+#define SCST_DATA_NONE			4
+
+/*************************************************************
+ ** Default suffix for targets with NULL names
+ *************************************************************/
+#define SCST_DEFAULT_TGT_NAME_SUFFIX		"_target_"
+
+/*************************************************************
+ ** Sense manipulation and examination
+ *************************************************************/
+#define SCST_LOAD_SENSE(key_asc_ascq) key_asc_ascq
+
+#define SCST_SENSE_VALID(sense)  ((sense != NULL) && \
+				  ((((const uint8_t *)(sense))[0] & 0x70) == 0x70))
+
+#define SCST_NO_SENSE(sense)     ((sense != NULL) && \
+				  (((const uint8_t *)(sense))[2] == 0))
+
+/*************************************************************
+ ** Sense data for the appropriate errors. Can be used with
+ ** scst_set_cmd_error()
+ *************************************************************/
+#define scst_sense_no_sense			NO_SENSE,        0x00, 0
+#define scst_sense_hardw_error			HARDWARE_ERROR,  0x44, 0
+#define scst_sense_aborted_command		ABORTED_COMMAND, 0x00, 0
+#define scst_sense_invalid_opcode		ILLEGAL_REQUEST, 0x20, 0
+#define scst_sense_invalid_field_in_cdb		ILLEGAL_REQUEST, 0x24, 0
+#define scst_sense_invalid_field_in_parm_list	ILLEGAL_REQUEST, 0x26, 0
+#define scst_sense_parameter_value_invalid	ILLEGAL_REQUEST, 0x26, 2
+#define scst_sense_invalid_release		ILLEGAL_REQUEST, 0x26, 4
+#define scst_sense_parameter_list_length_invalid \
+						ILLEGAL_REQUEST, 0x1A, 0
+#define scst_sense_reset_UA			UNIT_ATTENTION,  0x29, 0
+#define scst_sense_nexus_loss_UA		UNIT_ATTENTION,  0x29, 0x7
+#define scst_sense_saving_params_unsup		ILLEGAL_REQUEST, 0x39, 0
+#define scst_sense_lun_not_supported		ILLEGAL_REQUEST, 0x25, 0
+#define scst_sense_data_protect			DATA_PROTECT,    0x00, 0
+#define scst_sense_miscompare_error		MISCOMPARE,      0x1D, 0
+#define scst_sense_block_out_range_error	ILLEGAL_REQUEST, 0x21, 0
+#define scst_sense_medium_changed_UA		UNIT_ATTENTION,  0x28, 0
+#define scst_sense_read_error			MEDIUM_ERROR,    0x11, 0
+#define scst_sense_write_error			MEDIUM_ERROR,    0x03, 0
+#define scst_sense_not_ready			NOT_READY,       0x04, 0x10
+#define scst_sense_invalid_message		ILLEGAL_REQUEST, 0x49, 0
+#define scst_sense_cleared_by_another_ini_UA	UNIT_ATTENTION,  0x2F, 0
+#define scst_sense_capacity_data_changed	UNIT_ATTENTION,  0x2A, 0x9
+#define scst_sense_reservation_preempted	UNIT_ATTENTION,  0x2A, 0x03
+#define scst_sense_reservation_released		UNIT_ATTENTION,  0x2A, 0x04
+#define scst_sense_registrations_preempted	UNIT_ATTENTION,  0x2A, 0x05
+#define scst_sense_reported_luns_data_changed	UNIT_ATTENTION,  0x3F, 0xE
+#define scst_sense_inquery_data_changed		UNIT_ATTENTION,  0x3F, 0x3
+
+/*************************************************************
+ * SCSI opcodes not listed anywhere else
+ *************************************************************/
+#define REPORT_DEVICE_IDENTIFIER    0xA3
+#define INIT_ELEMENT_STATUS         0x07
+#define INIT_ELEMENT_STATUS_RANGE   0x37
+#define PREVENT_ALLOW_MEDIUM        0x1E
+#define READ_ATTRIBUTE              0x8C
+#define REQUEST_VOLUME_ADDRESS      0xB5
+#define WRITE_ATTRIBUTE             0x8D
+#define WRITE_VERIFY_16             0x8E
+#define VERIFY_6                    0x13
+#ifndef VERIFY_12
+#define VERIFY_12                   0xAF
+#endif
+
+
+/*************************************************************
+ **  SCSI Architecture Model (SAM) Status codes. Taken from SAM-3 draft
+ **  T10/1561-D Revision 4 Draft dated 7th November 2002.
+ *************************************************************/
+#define SAM_STAT_GOOD            0x00
+#define SAM_STAT_CHECK_CONDITION 0x02
+#define SAM_STAT_CONDITION_MET   0x04
+#define SAM_STAT_BUSY            0x08
+#define SAM_STAT_INTERMEDIATE    0x10
+#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14
+#define SAM_STAT_RESERVATION_CONFLICT 0x18
+#define SAM_STAT_COMMAND_TERMINATED 0x22	/* obsolete in SAM-3 */
+#define SAM_STAT_TASK_SET_FULL   0x28
+#define SAM_STAT_ACA_ACTIVE      0x30
+#define SAM_STAT_TASK_ABORTED    0x40
+
+/*************************************************************
+ ** Control byte field in CDB
+ *************************************************************/
+#define CONTROL_BYTE_LINK_BIT       0x01
+#define CONTROL_BYTE_NACA_BIT       0x04
+
+/*************************************************************
+ ** Byte 1 in INQUIRY CDB
+ *************************************************************/
+#define SCST_INQ_EVPD                0x01
+
+/*************************************************************
+ ** Byte 3 in Standard INQUIRY data
+ *************************************************************/
+#define SCST_INQ_BYTE3               3
+
+#define SCST_INQ_NORMACA_BIT         0x20
+
+/*************************************************************
+ ** Byte 2 in RESERVE_10 CDB
+ *************************************************************/
+#define SCST_RES_3RDPTY              0x10
+#define SCST_RES_LONGID              0x02
+
+/*************************************************************
+ ** Values for the control mode page TST field
+ *************************************************************/
+#define SCST_CONTR_MODE_ONE_TASK_SET  0
+#define SCST_CONTR_MODE_SEP_TASK_SETS 1
+
+/*******************************************************************
+ ** Values for the control mode page QUEUE ALGORITHM MODIFIER field
+ *******************************************************************/
+#define SCST_CONTR_MODE_QUEUE_ALG_RESTRICTED_REORDER   0
+#define SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER 1
+
+/*************************************************************
+ ** Values for the control mode page D_SENSE field
+ *************************************************************/
+#define SCST_CONTR_MODE_FIXED_SENSE  0
+#define SCST_CONTR_MODE_DESCR_SENSE 1
+
+/*************************************************************
+ ** TransportID protocol identifiers
+ *************************************************************/
+
+#define SCSI_TRANSPORTID_PROTOCOLID_FCP2	0
+#define SCSI_TRANSPORTID_PROTOCOLID_SPI5	1
+#define SCSI_TRANSPORTID_PROTOCOLID_SRP		4
+#define SCSI_TRANSPORTID_PROTOCOLID_ISCSI	5
+#define SCSI_TRANSPORTID_PROTOCOLID_SAS		6
+
+/*************************************************************
+ ** Misc SCSI constants
+ *************************************************************/
+#define SCST_SENSE_ASC_UA_RESET      0x29
+#define BYTCHK			     0x02
+#define POSITION_LEN_SHORT           20
+#define POSITION_LEN_LONG            32
+
+/*************************************************************
+ ** Various timeouts
+ *************************************************************/
+#define SCST_DEFAULT_TIMEOUT			(60 * HZ)
+
+#define SCST_GENERIC_CHANGER_TIMEOUT		(3 * HZ)
+#define SCST_GENERIC_CHANGER_LONG_TIMEOUT	(14000 * HZ)
+
+#define SCST_GENERIC_PROCESSOR_TIMEOUT		(3 * HZ)
+#define SCST_GENERIC_PROCESSOR_LONG_TIMEOUT	(14000 * HZ)
+
+#define SCST_GENERIC_TAPE_SMALL_TIMEOUT		(3 * HZ)
+#define SCST_GENERIC_TAPE_REG_TIMEOUT		(900 * HZ)
+#define SCST_GENERIC_TAPE_LONG_TIMEOUT		(14000 * HZ)
+
+#define SCST_GENERIC_MODISK_SMALL_TIMEOUT	(3 * HZ)
+#define SCST_GENERIC_MODISK_REG_TIMEOUT		(900 * HZ)
+#define SCST_GENERIC_MODISK_LONG_TIMEOUT	(14000 * HZ)
+
+#define SCST_GENERIC_DISK_SMALL_TIMEOUT		(3 * HZ)
+#define SCST_GENERIC_DISK_REG_TIMEOUT		(60 * HZ)
+#define SCST_GENERIC_DISK_LONG_TIMEOUT		(3600 * HZ)
+
+#define SCST_GENERIC_RAID_TIMEOUT		(3 * HZ)
+#define SCST_GENERIC_RAID_LONG_TIMEOUT		(14000 * HZ)
+
+#define SCST_GENERIC_CDROM_SMALL_TIMEOUT	(3 * HZ)
+#define SCST_GENERIC_CDROM_REG_TIMEOUT		(900 * HZ)
+#define SCST_GENERIC_CDROM_LONG_TIMEOUT		(14000 * HZ)
+
+#define SCST_MAX_OTHER_TIMEOUT			(14000 * HZ)
+
+/*************************************************************
+ ** I/O grouping attribute string values. Must match constants
+ ** w/o '_STR' suffix!
+ *************************************************************/
+#define SCST_IO_GROUPING_AUTO_STR		"auto"
+#define SCST_IO_GROUPING_THIS_GROUP_ONLY_STR	"this_group_only"
+#define SCST_IO_GROUPING_NEVER_STR		"never"
+
+/*************************************************************
+ ** Threads pool type attribute string values.
+ ** Must match scst_dev_type_threads_pool_type!
+ *************************************************************/
+#define SCST_THREADS_POOL_PER_INITIATOR_STR	"per_initiator"
+#define SCST_THREADS_POOL_SHARED_STR		"shared"
+
+/*************************************************************
+ ** Misc constants
+ *************************************************************/
+#define SCST_SYSFS_BLOCK_SIZE			PAGE_SIZE
+
+#define SCST_PR_DIR				"/var/lib/scst/pr"
+
+#define TID_COMMON_SIZE				24
+
+#define SCST_SYSFS_KEY_MARK			"[key]"
+
+#define SCST_MIN_REL_TGT_ID			1
+#define SCST_MAX_REL_TGT_ID			65535
+
+#endif /* __SCST_CONST_H */
include/scst/scst.h renamed from linux-2.6.35/include/scst/scst.h +3642 -0

2,624 lines not on this page · raw message

The rest of this diff is not shown. View the raw message.

Part of the diff is not shown here. View the raw message.

#4 lore raw

PATCH v4 4/19 : SCST main management files and private headers

In reply to Vladislav Bolkhovitin

This patch contains main management files and private headers.

File scst_main.c contains management functions to create and destroy target drivers, targets, sessions, device drivers, devices and threads.

A typical life-circle of a target driver:

1. scst_register_target_template()

2. scst_register_target() for each target

3. scst_register_session() for each incoming session.

4. Serving commands:

4.1. scst_rx_cmd() on a new command

4.2. scst_cmd_init_done() after the command's initialization finished

4.3. For WRITE-direction commands only:

4.3.1. rdy_to_xfer() callback to receive data into prepared buffer

4.3.2. scst_rx_data() to notify SCST core that all the data received

4.4. xmit_response() callback to transfer response with (possibly) data to the initiator

4.5. scst_tgt_cmd_done() - to notify SCST core that the response has sent

4.6. on_free_cmd() callback to notify that the command is about to be freed

5. scst_unregister_session() when initiator disconnects

6. scst_unregister_target()

7. scst_unregister_target_template()

A typical lifecircle of a virtual dev handler:

1. scst_register_virtual_dev_driver()

2. scst_register_virtual_device() for each virtual device as requested user space configurator

3. Serving commands:

3.1. parse() callback to ensure that command initialized correctly and initialize what's not initialized yet. Particularly, data transfer direction and data transfer size.

3.2. exec() callback to execute the command

3.3. Calling cmd->scst_cmd_done() to notify SCST core that the command finished.

3.4. dev_done() callback to notify that SCST core finished post processing of the command

3.5. on_free_cmd() callback to notify that the command is about to be freed

4. scst_unregister_virtual_device()

5. scst_register_virtual_dev_driver()

---
 scst_main.c   | 2105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scst_module.c |   63 +
 scst_priv.h   |  606 ++++++++++++++++
 3 files changed, 2774 insertions(+)
drivers/scst/scst_main.c renamed from linux-2.6.35/drivers/scst/scst_main.c +2105 -0
diff -uprN orig/linux-2.6.35/drivers/scst/scst_main.c linux-2.6.35/drivers/scst/scst_main.c
@@ -0,0 +1,2105 @@
+/*
+ *  scst_main.c
+ *
+ *  Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <[email protected]>
+ *  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/module.h>
+
+#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/unistd.h>
+#include <linux/string.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+
+#include <scst/scst.h>
+#include "scst_priv.h"
+#include "scst_mem.h"
+#include "scst_pres.h"
+
+#if defined(CONFIG_HIGHMEM4G) || defined(CONFIG_HIGHMEM64G)
+#warning "HIGHMEM kernel configurations are fully supported, but not\
+ recommended for performance reasons. Consider changing VMSPLIT\
+ option or use a 64-bit configuration instead. See README file for\
+ details."
+#endif
+
+/**
+ ** SCST global variables. They are all uninitialized to have their layout in
+ ** memory be exactly as specified. Otherwise compiler puts zero-initialized
+ ** variable separately from nonzero-initialized ones.
+ **/
+
+/*
+ * Main SCST mutex. All targets, devices and dev_types management is done
+ * under this mutex.
+ *
+ * It must NOT be used in any works (schedule_work(), etc.), because
+ * otherwise a deadlock (double lock, actually) is possible, e.g., with
+ * scst_user detach_tgt(), which is called under scst_mutex and calls
+ * flush_scheduled_work().
+ */
+struct mutex scst_mutex;
+EXPORT_SYMBOL_GPL(scst_mutex);
+
+/*
+ * Secondary level main mutex, inner for scst_mutex. Needed for
+ * __scst_pr_register_all_tg_pt(), since we can't use scst_mutex there,
+ * because of the circular locking dependency with dev_pr_mutex.
+ */
+struct mutex scst_mutex2;
+
+/* Both protected by scst_mutex or scst_mutex2 on read and both on write */
+struct list_head scst_template_list;
+struct list_head scst_dev_list;
+
+/* Protected by scst_mutex */
+struct list_head scst_dev_type_list;
+struct list_head scst_virtual_dev_type_list;
+
+spinlock_t scst_main_lock;
+
+static struct kmem_cache *scst_mgmt_cachep;
+mempool_t *scst_mgmt_mempool;
+static struct kmem_cache *scst_mgmt_stub_cachep;
+mempool_t *scst_mgmt_stub_mempool;
+static struct kmem_cache *scst_ua_cachep;
+mempool_t *scst_ua_mempool;
+static struct kmem_cache *scst_sense_cachep;
+mempool_t *scst_sense_mempool;
+static struct kmem_cache *scst_aen_cachep;
+mempool_t *scst_aen_mempool;
+struct kmem_cache *scst_tgtd_cachep;
+struct kmem_cache *scst_sess_cachep;
+struct kmem_cache *scst_acgd_cachep;
+
+unsigned int scst_setup_id;
+
+spinlock_t scst_init_lock;
+wait_queue_head_t scst_init_cmd_list_waitQ;
+struct list_head scst_init_cmd_list;
+unsigned int scst_init_poll_cnt;
+
+struct kmem_cache *scst_cmd_cachep;
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+unsigned long scst_trace_flag;
+#endif
+
+unsigned long scst_flags;
+atomic_t scst_cmd_count;
+
+struct scst_cmd_threads scst_main_cmd_threads;
+
+struct scst_tasklet scst_tasklets[NR_CPUS];
+
+spinlock_t scst_mcmd_lock;
+struct list_head scst_active_mgmt_cmd_list;
+struct list_head scst_delayed_mgmt_cmd_list;
+wait_queue_head_t scst_mgmt_cmd_list_waitQ;
+
+wait_queue_head_t scst_mgmt_waitQ;
+spinlock_t scst_mgmt_lock;
+struct list_head scst_sess_init_list;
+struct list_head scst_sess_shut_list;
+
+wait_queue_head_t scst_dev_cmd_waitQ;
+
+static struct mutex scst_suspend_mutex;
+/* protected by scst_suspend_mutex */
+static struct list_head scst_cmd_threads_list;
+
+int scst_threads;
+static struct task_struct *scst_init_cmd_thread;
+static struct task_struct *scst_mgmt_thread;
+static struct task_struct *scst_mgmt_cmd_thread;
+
+static int suspend_count;
+
+static int scst_virt_dev_last_id; /* protected by scst_mutex */
+
+cpumask_t default_cpu_mask;
+
+static unsigned int scst_max_cmd_mem;
+unsigned int scst_max_dev_cmd_mem;
+
+module_param_named(scst_threads, scst_threads, int, 0);
+MODULE_PARM_DESC(scst_threads, "SCSI target threads count");
+
+module_param_named(scst_max_cmd_mem, scst_max_cmd_mem, int, S_IRUGO);
+MODULE_PARM_DESC(scst_max_cmd_mem, "Maximum memory allowed to be consumed by "
+	"all SCSI commands of all devices at any given time in MB");
+
+module_param_named(scst_max_dev_cmd_mem, scst_max_dev_cmd_mem, int, S_IRUGO);
+MODULE_PARM_DESC(scst_max_dev_cmd_mem, "Maximum memory allowed to be consumed "
+	"by all SCSI commands of a device at any given time in MB");
+
+struct scst_dev_type scst_null_devtype = {
+	.name = "none",
+	.threads_num = -1,
+};
+
+static void __scst_resume_activity(void);
+
+/**
+ * __scst_register_target_template() - register target template.
+ * @vtt:	target template
+ * @version:	SCST_INTERFACE_VERSION version string to ensure that
+ *		SCST core and the target driver use the same version of
+ *		the SCST interface
+ *
+ * Description:
+ *    Registers a target template and returns 0 on success or appropriate
+ *    error code otherwise.
+ *
+ *    Target drivers supposed to behave sanely and not call register()
+ *    and unregister() randomly sinultaneously.
+ */
+int __scst_register_target_template(struct scst_tgt_template *vtt,
+	const char *version)
+{
+	int res = 0;
+	struct scst_tgt_template *t;
+
+	INIT_LIST_HEAD(&vtt->tgt_list);
+
+	if (strcmp(version, SCST_INTERFACE_VERSION) != 0) {
+		PRINT_ERROR("Incorrect version of target %s", vtt->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if (!vtt->detect) {
+		PRINT_ERROR("Target driver %s must have "
+			"detect() method.", vtt->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if (!vtt->release) {
+		PRINT_ERROR("Target driver %s must have "
+			"release() method.", vtt->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if (!vtt->xmit_response) {
+		PRINT_ERROR("Target driver %s must have "
+			"xmit_response() method.", vtt->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if (vtt->get_initiator_port_transport_id == NULL)
+		PRINT_WARNING("Target driver %s doesn't support Persistent "
+			"Reservations", vtt->name);
+
+	if (vtt->threads_num < 0) {
+		PRINT_ERROR("Wrong threads_num value %d for "
+			"target \"%s\"", vtt->threads_num,
+			vtt->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if ((!vtt->enable_target || !vtt->is_target_enabled) &&
+	    !vtt->enabled_attr_not_needed)
+		PRINT_WARNING("Target driver %s doesn't have enable_target() "
+			"and/or is_target_enabled() method(s). This is unsafe "
+			"and can lead that initiators connected on the "
+			"initialization time can see an unexpected set of "
+			"devices or no devices at all!", vtt->name);
+
+	if (((vtt->add_target != NULL) && (vtt->del_target == NULL)) ||
+	    ((vtt->add_target == NULL) && (vtt->del_target != NULL))) {
+		PRINT_ERROR("Target driver %s must either define both "
+			"add_target() and del_target(), or none.", vtt->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if (vtt->rdy_to_xfer == NULL)
+		vtt->rdy_to_xfer_atomic = 1;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0)
+		goto out;
+	list_for_each_entry(t, &scst_template_list, scst_template_list_entry) {
+		if (strcmp(t->name, vtt->name) == 0) {
+			PRINT_ERROR("Target driver %s already registered",
+				vtt->name);
+			mutex_unlock(&scst_mutex);
+			goto out_unlock;
+		}
+	}
+	mutex_unlock(&scst_mutex);
+
+	res = scst_tgtt_sysfs_create(vtt);
+	if (res)
+		goto out;
+
+	mutex_lock(&scst_mutex);
+	mutex_lock(&scst_mutex2);
+	list_add_tail(&vtt->scst_template_list_entry, &scst_template_list);
+	mutex_unlock(&scst_mutex2);
+	mutex_unlock(&scst_mutex);
+
+	TRACE_DBG("%s", "Calling target driver's detect()");
+	res = vtt->detect(vtt);
+	TRACE_DBG("Target driver's detect() returned %d", res);
+	if (res < 0) {
+		PRINT_ERROR("%s", "The detect() routine failed");
+		res = -EINVAL;
+		goto out_del;
+	}
+
+	PRINT_INFO("Target template %s registered successfully", vtt->name);
+
+out:
+	return res;
+
+out_del:
+	scst_tgtt_sysfs_del(vtt);
+
+	mutex_lock(&scst_mutex);
+
+	mutex_lock(&scst_mutex2);
+	list_del(&vtt->scst_template_list_entry);
+	mutex_unlock(&scst_mutex2);
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	goto out;
+}
+EXPORT_SYMBOL_GPL(__scst_register_target_template);
+
+static int scst_check_non_gpl_target_template(struct scst_tgt_template *vtt)
+{
+	int res;
+
+	if (vtt->task_mgmt_affected_cmds_done || vtt->threads_num ||
+	    vtt->on_hw_pending_cmd_timeout) {
+		PRINT_ERROR("Not allowed functionality in non-GPL version for "
+			"target template %s", vtt->name);
+		res = -EPERM;
+		goto out;
+	}
+
+	res = 0;
+
+out:
+	return res;
+}
+
+/**
+ * __scst_register_target_template_non_gpl() - register target template,
+ *					      non-GPL version
+ * @vtt:	target template
+ * @version:	SCST_INTERFACE_VERSION version string to ensure that
+ *		SCST core and the target driver use the same version of
+ *		the SCST interface
+ *
+ * Description:
+ *    Registers a target template and returns 0 on success or appropriate
+ *    error code otherwise.
+ *
+ *    Note: *vtt must be static!
+ */
+int __scst_register_target_template_non_gpl(struct scst_tgt_template *vtt,
+	const char *version)
+{
+	int res;
+
+	res = scst_check_non_gpl_target_template(vtt);
+	if (res != 0)
+		goto out;
+
+	res = __scst_register_target_template(vtt, version);
+
+out:
+	return res;
+}
+EXPORT_SYMBOL(__scst_register_target_template_non_gpl);
+
+/**
+ * scst_unregister_target_template() - unregister target template
+ *
+ * Target drivers supposed to behave sanely and not call register()
+ * and unregister() randomly sinultaneously. Also it is supposed that
+ * no attepts to create new targets for this vtt will be done in a race
+ * with this function.
+ */
+void scst_unregister_target_template(struct scst_tgt_template *vtt)
+{
+	struct scst_tgt *tgt;
+	struct scst_tgt_template *t;
+	int found = 0;
+
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(t, &scst_template_list, scst_template_list_entry) {
+		if (strcmp(t->name, vtt->name) == 0) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		PRINT_ERROR("Target driver %s isn't registered", vtt->name);
+		goto out_err_up;
+	}
+
+	mutex_lock(&scst_mutex2);
+	list_del(&vtt->scst_template_list_entry);
+	mutex_unlock(&scst_mutex2);
+
+	/* Wait for outstanding sysfs mgmt calls completed */
+	while (vtt->tgtt_active_sysfs_works_count > 0) {
+		mutex_unlock(&scst_mutex);
+		msleep(100);
+		mutex_lock(&scst_mutex);
+	}
+
+restart:
+	list_for_each_entry(tgt, &vtt->tgt_list, tgt_list_entry) {
+		mutex_unlock(&scst_mutex);
+		scst_unregister_target(tgt);
+		mutex_lock(&scst_mutex);
+		goto restart;
+	}
+
+	mutex_unlock(&scst_mutex);
+
+	scst_tgtt_sysfs_del(vtt);
+
+	PRINT_INFO("Target template %s unregistered successfully", vtt->name);
+
+out:
+	return;
+
+out_err_up:
+	mutex_unlock(&scst_mutex);
+	goto out;
+}
+EXPORT_SYMBOL(scst_unregister_target_template);
+
+/**
+ * scst_register_target() - register target
+ *
+ * Registers a target for template vtt and returns new target structure on
+ * success or NULL otherwise.
+ */
+struct scst_tgt *scst_register_target(struct scst_tgt_template *vtt,
+	const char *target_name)
+{
+	struct scst_tgt *tgt;
+	int rc = 0;
+
+	rc = scst_alloc_tgt(vtt, &tgt);
+	if (rc != 0)
+		goto out;
+
+	if (target_name != NULL) {
+
+		tgt->tgt_name = kmalloc(strlen(target_name) + 1, GFP_KERNEL);
+		if (tgt->tgt_name == NULL) {
+			TRACE(TRACE_OUT_OF_MEM, "Allocation of tgt name %s failed",
+				target_name);
+			rc = -ENOMEM;
+			goto out_free_tgt;
+		}
+		strcpy(tgt->tgt_name, target_name);
+	} else {
+		static int tgt_num; /* protected by scst_mutex */
+		int len = strlen(vtt->name) +
+			strlen(SCST_DEFAULT_TGT_NAME_SUFFIX) + 11 + 1;
+
+		tgt->tgt_name = kmalloc(len, GFP_KERNEL);
+		if (tgt->tgt_name == NULL) {
+			TRACE(TRACE_OUT_OF_MEM, "Allocation of tgt name failed "
+				"(template name %s)", vtt->name);
+			rc = -ENOMEM;
+			goto out_free_tgt;
+		}
+		sprintf(tgt->tgt_name, "%s%s%d", vtt->name,
+			SCST_DEFAULT_TGT_NAME_SUFFIX, tgt_num++);
+	}
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		rc = -EINTR;
+		goto out_free_tgt;
+	}
+
+	rc = scst_tgt_sysfs_create(tgt);
+	if (rc < 0)
+		goto out_unlock;
+
+	tgt->default_acg = scst_alloc_add_acg(tgt, tgt->tgt_name, false);
+	if (tgt->default_acg == NULL)
+		goto out_sysfs_del;
+
+	mutex_lock(&scst_mutex2);
+	list_add_tail(&tgt->tgt_list_entry, &vtt->tgt_list);
+	mutex_unlock(&scst_mutex2);
+
+	mutex_unlock(&scst_mutex);
+
+	PRINT_INFO("Target %s for template %s registered successfully",
+		tgt->tgt_name, vtt->name);
+
+	TRACE_DBG("tgt %p", tgt);
+
+out:
+	return tgt;
+
+out_sysfs_del:
+	mutex_unlock(&scst_mutex);
+	scst_tgt_sysfs_del(tgt);
+	goto out_free_tgt;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+
+out_free_tgt:
+	/* In case of error tgt_name will be freed in scst_free_tgt() */
+	scst_free_tgt(tgt);
+	tgt = NULL;
+	goto out;
+}
+EXPORT_SYMBOL(scst_register_target);
+
+static inline int test_sess_list(struct scst_tgt *tgt)
+{
+	int res;
+	mutex_lock(&scst_mutex);
+	res = list_empty(&tgt->sess_list);
+	mutex_unlock(&scst_mutex);
+	return res;
+}
+
+/**
+ * scst_unregister_target() - unregister target.
+ *
+ * It is supposed that no attepts to create new sessions for this
+ * target will be done in a race with this function.
+ */
+void scst_unregister_target(struct scst_tgt *tgt)
+{
+	struct scst_session *sess;
+	struct scst_tgt_template *vtt = tgt->tgtt;
+	struct scst_acg *acg, *acg_tmp;
+
+	TRACE_DBG("%s", "Calling target driver's release()");
+	tgt->tgtt->release(tgt);
+	TRACE_DBG("%s", "Target driver's release() returned");
+
+	mutex_lock(&scst_mutex);
+again:
+	list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
+		if (sess->shut_phase == SCST_SESS_SPH_READY) {
+			/*
+			 * Sometimes it's hard for target driver to track all
+			 * its sessions (see scst_local, eg), so let's help it.
+			 */
+			mutex_unlock(&scst_mutex);
+			scst_unregister_session(sess, 0, NULL);
+			mutex_lock(&scst_mutex);
+			goto again;
+		}
+	}
+	mutex_unlock(&scst_mutex);
+
+	TRACE_DBG("%s", "Waiting for sessions shutdown");
+	wait_event(tgt->unreg_waitQ, test_sess_list(tgt));
+	TRACE_DBG("%s", "wait_event() returned");
+
+	scst_suspend_activity(false);
+	mutex_lock(&scst_mutex);
+
+	mutex_lock(&scst_mutex2);
+	list_del(&tgt->tgt_list_entry);
+	mutex_unlock(&scst_mutex2);
+
+	del_timer_sync(&tgt->retry_timer);
+
+	scst_del_free_acg(tgt->default_acg);
+
+	list_for_each_entry_safe(acg, acg_tmp, &tgt->tgt_acg_list,
+					acg_list_entry) {
+		scst_del_free_acg(acg);
+	}
+
+	mutex_unlock(&scst_mutex);
+	scst_resume_activity();
+
+	scst_tgt_sysfs_del(tgt);
+
+	PRINT_INFO("Target %s for template %s unregistered successfully",
+		tgt->tgt_name, vtt->name);
+
+	scst_free_tgt(tgt);
+
+	TRACE_DBG("Unregistering tgt %p finished", tgt);
+	return;
+}
+EXPORT_SYMBOL(scst_unregister_target);
+
+static int scst_susp_wait(bool interruptible)
+{
+	int res = 0;
+
+	if (interruptible) {
+		res = wait_event_interruptible_timeout(scst_dev_cmd_waitQ,
+			(atomic_read(&scst_cmd_count) == 0),
+			SCST_SUSPENDING_TIMEOUT);
+		if (res <= 0) {
+			__scst_resume_activity();
+			if (res == 0)
+				res = -EBUSY;
+		} else
+			res = 0;
+	} else
+		wait_event(scst_dev_cmd_waitQ,
+			   atomic_read(&scst_cmd_count) == 0);
+
+	TRACE_MGMT_DBG("wait_event() returned %d", res);
+	return res;
+}
+
+/**
+ * scst_suspend_activity() - globally suspend any activity
+ *
+ * Description:
+ *    Globally suspends any activity and doesn't return, until there are any
+ *    active commands (state after SCST_CMD_STATE_INIT). If "interruptible"
+ *    is true, it returns after SCST_SUSPENDING_TIMEOUT or if it was interrupted
+ *    by a signal with the corresponding error status < 0. If "interruptible"
+ *    is false, it will wait virtually forever. On success returns 0.
+ *
+ *    New arriving commands stay in the suspended state until
+ *    scst_resume_activity() is called.
+ */
+int scst_suspend_activity(bool interruptible)
+{
+	int res = 0;
+	bool rep = false;
+
+	if (interruptible) {
+		if (mutex_lock_interruptible(&scst_suspend_mutex) != 0) {
+			res = -EINTR;
+			goto out;
+		}
+	} else
+		mutex_lock(&scst_suspend_mutex);
+
+	TRACE_MGMT_DBG("suspend_count %d", suspend_count);
+	suspend_count++;
+	if (suspend_count > 1)
+		goto out_up;
+
+	set_bit(SCST_FLAG_SUSPENDING, &scst_flags);
+	set_bit(SCST_FLAG_SUSPENDED, &scst_flags);
+	/*
+	 * Assignment of SCST_FLAG_SUSPENDING and SCST_FLAG_SUSPENDED must be
+	 * ordered with scst_cmd_count. Otherwise lockless logic in
+	 * scst_translate_lun() and scst_mgmt_translate_lun() won't work.
+	 */
+	smp_mb__after_set_bit();
+
+	/*
+	 * See comment in scst_user.c::dev_user_task_mgmt_fn() for more
+	 * information about scst_user behavior.
+	 *
+	 * ToDo: make the global suspending unneeded (switch to per-device
+	 * reference counting? That would mean to switch off from lockless
+	 * implementation of scst_translate_lun().. )
+	 */
+
+	if (atomic_read(&scst_cmd_count) != 0) {
+		PRINT_INFO("Waiting for %d active commands to complete... This "
+			"might take few minutes for disks or few hours for "
+			"tapes, if you use long executed commands, like "
+			"REWIND or FORMAT. In case, if you have a hung user "
+			"space device (i.e. made using scst_user module) not "
+			"responding to any commands, if might take virtually "
+			"forever until the corresponding user space "
+			"program recovers and starts responding or gets "
+			"killed.", atomic_read(&scst_cmd_count));
+		rep = true;
+	}
+
+	res = scst_susp_wait(interruptible);
+	if (res != 0)
+		goto out_clear;
+
+	clear_bit(SCST_FLAG_SUSPENDING, &scst_flags);
+	/* See comment about smp_mb() above */
+	smp_mb__after_clear_bit();
+
+	TRACE_MGMT_DBG("Waiting for %d active commands finally to complete",
+		atomic_read(&scst_cmd_count));
+
+	res = scst_susp_wait(interruptible);
+	if (res != 0)
+		goto out_clear;
+
+	if (rep)
+		PRINT_INFO("%s", "All active commands completed");
+
+out_up:
+	mutex_unlock(&scst_suspend_mutex);
+
+out:
+	return res;
+
+out_clear:
+	clear_bit(SCST_FLAG_SUSPENDING, &scst_flags);
+	/* See comment about smp_mb() above */
+	smp_mb__after_clear_bit();
+	goto out_up;
+}
+EXPORT_SYMBOL_GPL(scst_suspend_activity);
+
+static void __scst_resume_activity(void)
+{
+	struct scst_cmd_threads *l;
+
+	suspend_count--;
+	TRACE_MGMT_DBG("suspend_count %d left", suspend_count);
+	if (suspend_count > 0)
+		goto out;
+
+	clear_bit(SCST_FLAG_SUSPENDED, &scst_flags);
+	/*
+	 * The barrier is needed to make sure all woken up threads see the
+	 * cleared flag. Not sure if it's really needed, but let's be safe.
+	 */
+	smp_mb__after_clear_bit();
+
+	list_for_each_entry(l, &scst_cmd_threads_list, lists_list_entry) {
+		wake_up_all(&l->cmd_list_waitQ);
+	}
+	wake_up_all(&scst_init_cmd_list_waitQ);
+
+	spin_lock_irq(&scst_mcmd_lock);
+	if (!list_empty(&scst_delayed_mgmt_cmd_list)) {
+		struct scst_mgmt_cmd *m;
+		m = list_entry(scst_delayed_mgmt_cmd_list.next, typeof(*m),
+				mgmt_cmd_list_entry);
+		TRACE_MGMT_DBG("Moving delayed mgmt cmd %p to head of active "
+			"mgmt cmd list", m);
+		list_move(&m->mgmt_cmd_list_entry, &scst_active_mgmt_cmd_list);
+	}
+	spin_unlock_irq(&scst_mcmd_lock);
+	wake_up_all(&scst_mgmt_cmd_list_waitQ);
+
+out:
+	return;
+}
+
+/**
+ * scst_resume_activity() - globally resume all activities
+ *
+ * Resumes suspended by scst_suspend_activity() activities.
+ */
+void scst_resume_activity(void)
+{
+
+	mutex_lock(&scst_suspend_mutex);
+	__scst_resume_activity();
+	mutex_unlock(&scst_suspend_mutex);
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_resume_activity);
+
+static int scst_register_device(struct scsi_device *scsidp)
+{
+	int res = 0;
+	struct scst_device *dev, *d;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	res = scst_alloc_device(GFP_KERNEL, &dev);
+	if (res != 0)
+		goto out_unlock;
+
+	dev->type = scsidp->type;
+
+	dev->virt_name = kmalloc(50, GFP_KERNEL);
+	if (dev->virt_name == NULL) {
+		PRINT_ERROR("%s", "Unable to alloc device name");
+		res = -ENOMEM;
+		goto out_free_dev;
+	}
+	snprintf(dev->virt_name, 50, "%d:%d:%d:%d", scsidp->host->host_no,
+		scsidp->channel, scsidp->id, scsidp->lun);
+
+	list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
+		if (strcmp(d->virt_name, dev->virt_name) == 0) {
+			PRINT_ERROR("Device %s already exists", dev->virt_name);
+			res = -EEXIST;
+			goto out_free_dev;
+		}
+	}
+
+	dev->scsi_dev = scsidp;
+
+	list_add_tail(&dev->dev_list_entry, &scst_dev_list);
+
+	mutex_unlock(&scst_mutex);
+
+	res = scst_dev_sysfs_create(dev);
+	if (res != 0)
+		goto out_del;
+
+	PRINT_INFO("Attached to scsi%d, channel %d, id %d, lun %d, "
+		"type %d", scsidp->host->host_no, scsidp->channel,
+		scsidp->id, scsidp->lun, scsidp->type);
+
+out:
+	return res;
+
+out_del:
+	list_del(&dev->dev_list_entry);
+
+out_free_dev:
+	scst_free_device(dev);
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	goto out;
+}
+
+static void scst_unregister_device(struct scsi_device *scsidp)
+{
+	struct scst_device *d, *dev = NULL;
+	struct scst_acg_dev *acg_dev, *aa;
+
+	scst_suspend_activity(false);
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
+		if (d->scsi_dev == scsidp) {
+			dev = d;
+			TRACE_DBG("Device %p found", dev);
+			break;
+		}
+	}
+	if (dev == NULL) {
+		PRINT_ERROR("SCST device for SCSI device %d:%d:%d:%d not found",
+			scsidp->host->host_no, scsidp->channel, scsidp->id,
+			scsidp->lun);
+		goto out_unlock;
+	}
+
+	list_del(&dev->dev_list_entry);
+
+	scst_assign_dev_handler(dev, &scst_null_devtype);
+
+	list_for_each_entry_safe(acg_dev, aa, &dev->dev_acg_dev_list,
+				 dev_acg_dev_list_entry) {
+		scst_acg_del_lun(acg_dev->acg, acg_dev->lun, true);
+	}
+
+	mutex_unlock(&scst_mutex);
+
+	scst_resume_activity();
+
+	scst_dev_sysfs_del(dev);
+
+	PRINT_INFO("Detached from scsi%d, channel %d, id %d, lun %d, type %d",
+		scsidp->host->host_no, scsidp->channel, scsidp->id,
+		scsidp->lun, scsidp->type);
+
+	scst_free_device(dev);
+
+out:
+	return;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	scst_resume_activity();
+	goto out;
+}
+
+static int scst_dev_handler_check(struct scst_dev_type *dev_handler)
+{
+	int res = 0;
+
+	if (dev_handler->parse == NULL) {
+		PRINT_ERROR("scst dev handler %s must have "
+			"parse() method.", dev_handler->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if (((dev_handler->add_device != NULL) &&
+	     (dev_handler->del_device == NULL)) ||
+	    ((dev_handler->add_device == NULL) &&
+	     (dev_handler->del_device != NULL))) {
+		PRINT_ERROR("Dev handler %s must either define both "
+			"add_device() and del_device(), or none.",
+			dev_handler->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if (dev_handler->alloc_data_buf == NULL)
+		dev_handler->alloc_data_buf_atomic = 1;
+
+	if (dev_handler->dev_done == NULL)
+		dev_handler->dev_done_atomic = 1;
+
+out:
+	return res;
+}
+
+static int scst_check_device_name(const char *dev_name)
+{
+	int res = 0;
+
+	if (strchr(dev_name, '/') != NULL) {
+		PRINT_ERROR("Dev name %s contains illegal character '/'",
+			dev_name);
+		res = -EINVAL;
+	}
+	return res;
+}
+
+/**
+ * scst_register_virtual_device() - register a virtual device.
+ * @dev_handler: the device's device handler
+ * @dev_name:	the new device name, NULL-terminated string. Must be uniq
+ *              among all virtual devices in the system.
+ *
+ * Registers a virtual device and returns assinged to the device ID on
+ * success, or negative value otherwise
+ */
+int scst_register_virtual_device(struct scst_dev_type *dev_handler,
+	const char *dev_name)
+{
+	int res, rc;
+	struct scst_device *dev, *d;
+	bool sysfs_del = false;
+
+	if (dev_handler == NULL) {
+		PRINT_ERROR("%s: valid device handler must be supplied",
+			    __func__);
+		res = -EINVAL;
+		goto out;
+	}
+
+	if (dev_name == NULL) {
+		PRINT_ERROR("%s: device name must be non-NULL", __func__);
+		res = -EINVAL;
+		goto out;
+	}
+
+	res = scst_check_device_name(dev_name);
+	if (res != 0)
+		goto out;
+
+	res = scst_dev_handler_check(dev_handler);
+	if (res != 0)
+		goto out;
+
+	res = scst_suspend_activity(true);
+	if (res != 0)
+		goto out;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out_resume;
+	}
+
+	res = scst_alloc_device(GFP_KERNEL, &dev);
+	if (res != 0)
+		goto out_unlock;
+
+	dev->type = dev_handler->type;
+	dev->scsi_dev = NULL;
+	dev->virt_name = kstrdup(dev_name, GFP_KERNEL);
+	if (dev->virt_name == NULL) {
+		PRINT_ERROR("Unable to allocate virt_name for dev %s",
+			dev_name);
+		res = -ENOMEM;
+		goto out_free_dev;
+	}
+
+	while (1) {
+		dev->virt_id = scst_virt_dev_last_id++;
+		if (dev->virt_id > 0)
+			break;
+		scst_virt_dev_last_id = 1;
+	}
+
+	res = dev->virt_id;
+
+	rc = scst_pr_init_dev(dev);
+	if (rc != 0) {
+		res = rc;
+		goto out_free_dev;
+	}
+
+	/*
+	 * We can drop scst_mutex, because we have not yet added the dev in
+	 * scst_dev_list, so it "doesn't exist" yet.
+	 */
+	mutex_unlock(&scst_mutex);
+
+	res = scst_dev_sysfs_create(dev);
+	if (res != 0)
+		goto out_lock_pr_clear_dev;
+
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
+		if (strcmp(d->virt_name, dev_name) == 0) {
+			PRINT_ERROR("Device %s already exists", dev_name);
+			res = -EEXIST;
+			sysfs_del = true;
+			goto out_pr_clear_dev;
+		}
+	}
+
+	rc = scst_assign_dev_handler(dev, dev_handler);
+	if (rc != 0) {
+		res = rc;
+		sysfs_del = true;
+		goto out_pr_clear_dev;
+	}
+
+	list_add_tail(&dev->dev_list_entry, &scst_dev_list);
+
+	mutex_unlock(&scst_mutex);
+	scst_resume_activity();
+
+	res = dev->virt_id;
+
+	PRINT_INFO("Attached to virtual device %s (id %d)",
+		dev_name, res);
+
+out:
+	return res;
+
+out_lock_pr_clear_dev:
+	mutex_lock(&scst_mutex);
+
+out_pr_clear_dev:
+	scst_pr_clear_dev(dev);
+
+out_free_dev:
+	mutex_unlock(&scst_mutex);
+	if (sysfs_del)
+		scst_dev_sysfs_del(dev);
+	scst_free_device(dev);
+	goto out_resume;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+
+out_resume:
+	scst_resume_activity();
+	goto out;
+}
+EXPORT_SYMBOL_GPL(scst_register_virtual_device);
+
+/**
+ * scst_unregister_virtual_device() - unegister a virtual device.
+ * @id:		the device's ID, returned by the registration function
+ */
+void scst_unregister_virtual_device(int id)
+{
+	struct scst_device *d, *dev = NULL;
+	struct scst_acg_dev *acg_dev, *aa;
+
+	scst_suspend_activity(false);
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
+		if (d->virt_id == id) {
+			dev = d;
+			TRACE_DBG("Virtual device %p (id %d) found", dev, id);
+			break;
+		}
+	}
+	if (dev == NULL) {
+		PRINT_ERROR("Virtual device (id %d) not found", id);
+		goto out_unlock;
+	}
+
+	list_del(&dev->dev_list_entry);
+
+	scst_pr_clear_dev(dev);
+
+	scst_assign_dev_handler(dev, &scst_null_devtype);
+
+	list_for_each_entry_safe(acg_dev, aa, &dev->dev_acg_dev_list,
+				 dev_acg_dev_list_entry) {
+		scst_acg_del_lun(acg_dev->acg, acg_dev->lun, true);
+	}
+
+	mutex_unlock(&scst_mutex);
+	scst_resume_activity();
+
+	scst_dev_sysfs_del(dev);
+
+	PRINT_INFO("Detached from virtual device %s (id %d)",
+		dev->virt_name, dev->virt_id);
+
+	scst_free_device(dev);
+
+out:
+	return;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	scst_resume_activity();
+	goto out;
+}
+EXPORT_SYMBOL_GPL(scst_unregister_virtual_device);
+
+/**
+ * __scst_register_dev_driver() - register pass-through dev handler driver
+ * @dev_type:	dev handler template
+ * @version:	SCST_INTERFACE_VERSION version string to ensure that
+ *		SCST core and the dev handler use the same version of
+ *		the SCST interface
+ *
+ * Description:
+ *    Registers a pass-through dev handler driver. Returns 0 on success
+ *    or appropriate error code otherwise.
+ */
+int __scst_register_dev_driver(struct scst_dev_type *dev_type,
+	const char *version)
+{
+	int res, exist;
+	struct scst_dev_type *dt;
+
+	if (strcmp(version, SCST_INTERFACE_VERSION) != 0) {
+		PRINT_ERROR("Incorrect version of dev handler %s",
+			dev_type->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	res = scst_dev_handler_check(dev_type);
+	if (res != 0)
+		goto out;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	exist = 0;
+	list_for_each_entry(dt, &scst_dev_type_list, dev_type_list_entry) {
+		if (strcmp(dt->name, dev_type->name) == 0) {
+			PRINT_ERROR("Device type handler \"%s\" already "
+				"exist", dt->name);
+			exist = 1;
+			break;
+		}
+	}
+	if (exist)
+		goto out_unlock;
+
+	list_add_tail(&dev_type->dev_type_list_entry, &scst_dev_type_list);
+
+	mutex_unlock(&scst_mutex);
+
+	res = scst_devt_sysfs_create(dev_type);
+	if (res < 0)
+		goto out;
+
+	PRINT_INFO("Device handler \"%s\" for type %d registered "
+		"successfully", dev_type->name, dev_type->type);
+
+out:
+	return res;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	goto out;
+}
+EXPORT_SYMBOL_GPL(__scst_register_dev_driver);
+
+/**
+ * scst_unregister_dev_driver() - unregister pass-through dev handler driver
+ */
+void scst_unregister_dev_driver(struct scst_dev_type *dev_type)
+{
+	struct scst_device *dev;
+	struct scst_dev_type *dt;
+	int found = 0;
+
+	scst_suspend_activity(false);
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(dt, &scst_dev_type_list, dev_type_list_entry) {
+		if (strcmp(dt->name, dev_type->name) == 0) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		PRINT_ERROR("Dev handler \"%s\" isn't registered",
+			dev_type->name);
+		goto out_up;
+	}
+
+	list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
+		if (dev->handler == dev_type) {
+			scst_assign_dev_handler(dev, &scst_null_devtype);
+			TRACE_DBG("Dev handler removed from device %p", dev);
+		}
+	}
+
+	list_del(&dev_type->dev_type_list_entry);
+
+	mutex_unlock(&scst_mutex);
+	scst_resume_activity();
+
+	scst_devt_sysfs_del(dev_type);
+
+	PRINT_INFO("Device handler \"%s\" for type %d unloaded",
+		   dev_type->name, dev_type->type);
+
+out:
+	return;
+
+out_up:
+	mutex_unlock(&scst_mutex);
+	scst_resume_activity();
+	goto out;
+}
+EXPORT_SYMBOL_GPL(scst_unregister_dev_driver);
+
+/**
+ * __scst_register_virtual_dev_driver() - register virtual dev handler driver
+ * @dev_type:	dev handler template
+ * @version:	SCST_INTERFACE_VERSION version string to ensure that
+ *		SCST core and the dev handler use the same version of
+ *		the SCST interface
+ *
+ * Description:
+ *    Registers a virtual dev handler driver. Returns 0 on success or
+ *    appropriate error code otherwise.
+ */
+int __scst_register_virtual_dev_driver(struct scst_dev_type *dev_type,
+	const char *version)
+{
+	int res;
+
+	if (strcmp(version, SCST_INTERFACE_VERSION) != 0) {
+		PRINT_ERROR("Incorrect version of virtual dev handler %s",
+			dev_type->name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	res = scst_dev_handler_check(dev_type);
+	if (res != 0)
+		goto out;
+
+	mutex_lock(&scst_mutex);
+	list_add_tail(&dev_type->dev_type_list_entry, &scst_virtual_dev_type_list);
+	mutex_unlock(&scst_mutex);
+
+	res = scst_devt_sysfs_create(dev_type);
+	if (res < 0)
+		goto out;
+
+	if (dev_type->type != -1) {
+		PRINT_INFO("Virtual device handler %s for type %d "
+			"registered successfully", dev_type->name,
+			dev_type->type);
+	} else {
+		PRINT_INFO("Virtual device handler \"%s\" registered "
+			"successfully", dev_type->name);
+	}
+
+out:
+	return res;
+}
+EXPORT_SYMBOL_GPL(__scst_register_virtual_dev_driver);
+
+/**
+ * scst_unregister_virtual_dev_driver() - unregister virtual dev driver
+ */
+void scst_unregister_virtual_dev_driver(struct scst_dev_type *dev_type)
+{
+
+	mutex_lock(&scst_mutex);
+
+	/* Disable sysfs mgmt calls (e.g. addition of new devices) */
+	list_del(&dev_type->dev_type_list_entry);
+
+	/* Wait for outstanding sysfs mgmt calls completed */
+	while (dev_type->devt_active_sysfs_works_count > 0) {
+		mutex_unlock(&scst_mutex);
+		msleep(100);
+		mutex_lock(&scst_mutex);
+	}
+
+	mutex_unlock(&scst_mutex);
+
+	scst_devt_sysfs_del(dev_type);
+
+	PRINT_INFO("Device handler \"%s\" unloaded", dev_type->name);
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_unregister_virtual_dev_driver);
+
+/* scst_mutex supposed to be held */
+int scst_add_threads(struct scst_cmd_threads *cmd_threads,
+	struct scst_device *dev, struct scst_tgt_dev *tgt_dev, int num)
+{
+	int res = 0, i;
+	struct scst_cmd_thread_t *thr;
+	int n = 0, tgt_dev_num = 0;
+
+	if (num == 0) {
+		res = 0;
+		goto out;
+	}
+
+	list_for_each_entry(thr, &cmd_threads->threads_list, thread_list_entry) {
+		n++;
+	}
+
+	TRACE_DBG("cmd_threads %p, dev %p, tgt_dev %p, num %d, n %d",
+		cmd_threads, dev, tgt_dev, num, n);
+
+	if (tgt_dev != NULL) {
+		struct scst_tgt_dev *t;
+		list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+			if (t == tgt_dev)
+				break;
+			tgt_dev_num++;
+		}
+	}
+
+	for (i = 0; i < num; i++) {
+		thr = kmalloc(sizeof(*thr), GFP_KERNEL);
+		if (!thr) {
+			res = -ENOMEM;
+			PRINT_ERROR("Fail to allocate thr %d", res);
+			goto out_wait;
+		}
+
+		if (dev != NULL) {
+			char nm[14]; /* to limit the name's len */
+			strlcpy(nm, dev->virt_name, ARRAY_SIZE(nm));
+			thr->cmd_thread = kthread_create(scst_cmd_thread,
+				cmd_threads, "%s%d", nm, n++);
+		} else if (tgt_dev != NULL) {
+			char nm[11]; /* to limit the name's len */
+			int rc;
+			strlcpy(nm, tgt_dev->dev->virt_name, ARRAY_SIZE(nm));
+			thr->cmd_thread = kthread_create(scst_cmd_thread,
+				cmd_threads, "%s%d_%d", nm, tgt_dev_num, n++);
+			rc = set_cpus_allowed_ptr(thr->cmd_thread,
+				&tgt_dev->sess->acg->acg_cpu_mask);
+			if (rc != 0)
+				PRINT_ERROR("Setting CPU affinity failed: "
+					"%d", rc);
+		} else
+			thr->cmd_thread = kthread_create(scst_cmd_thread,
+				cmd_threads, "scstd%d", n++);
+
+		if (IS_ERR(thr->cmd_thread)) {
+			res = PTR_ERR(thr->cmd_thread);
+			PRINT_ERROR("kthread_create() failed: %d", res);
+			kfree(thr);
+			goto out_wait;
+		}
+
+		list_add(&thr->thread_list_entry, &cmd_threads->threads_list);
+		cmd_threads->nr_threads++;
+
+		TRACE_DBG("Added thr %p to threads list (nr_threads %d, n %d)",
+			thr, cmd_threads->nr_threads, n);
+
+		wake_up_process(thr->cmd_thread);
+	}
+
+out_wait:
+	if (cmd_threads != &scst_main_cmd_threads) {
+		/*
+		 * Wait for io_context gets initialized to avoid possible races
+		 * for it from the sharing it tgt_devs.
+		 */
+		while (!*(volatile bool*)&cmd_threads->io_context_ready) {
+			TRACE_DBG("Waiting for io_context for cmd_threads %p "
+				"initialized", cmd_threads);
+			msleep(50);
+		}
+	}
+
+	if (res != 0)
+		scst_del_threads(cmd_threads, i);
+
+out:
+	return res;
+}
+
+/* scst_mutex supposed to be held */
+void scst_del_threads(struct scst_cmd_threads *cmd_threads, int num)
+{
+	struct scst_cmd_thread_t *ct, *tmp;
+
+	if (num == 0)
+		goto out;
+
+	list_for_each_entry_safe_reverse(ct, tmp, &cmd_threads->threads_list,
+				thread_list_entry) {
+		int rc;
+		struct scst_device *dev;
+
+		rc = kthread_stop(ct->cmd_thread);
+		if (rc < 0)
+			TRACE_MGMT_DBG("kthread_stop() failed: %d", rc);
+
+		list_del(&ct->thread_list_entry);
+
+		list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
+			struct scst_tgt_dev *tgt_dev;
+			list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+					dev_tgt_dev_list_entry) {
+				scst_del_thr_data(tgt_dev, ct->cmd_thread);
+			}
+		}
+
+		kfree(ct);
+
+		cmd_threads->nr_threads--;
+
+		--num;
+		if (num == 0)
+			break;
+	}
+
+	EXTRACHECKS_BUG_ON((cmd_threads->nr_threads == 0) &&
+		(cmd_threads->io_context != NULL));
+
+out:
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+void scst_stop_dev_threads(struct scst_device *dev)
+{
+	struct scst_tgt_dev *tgt_dev;
+
+	list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+		scst_tgt_dev_stop_threads(tgt_dev);
+	}
+
+	if ((dev->threads_num > 0) &&
+	    (dev->threads_pool_type == SCST_THREADS_POOL_SHARED))
+		scst_del_threads(&dev->dev_cmd_threads, -1);
+	return;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+int scst_create_dev_threads(struct scst_device *dev)
+{
+	int res = 0;
+	struct scst_tgt_dev *tgt_dev;
+
+	list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+			dev_tgt_dev_list_entry) {
+		res = scst_tgt_dev_setup_threads(tgt_dev);
+		if (res != 0)
+			goto out_err;
+	}
+
+	if ((dev->threads_num > 0) &&
+	    (dev->threads_pool_type == SCST_THREADS_POOL_SHARED)) {
+		res = scst_add_threads(&dev->dev_cmd_threads, dev, NULL,
+			dev->threads_num);
+		if (res != 0)
+			goto out_err;
+	}
+
+out:
+	return res;
+
+out_err:
+	scst_stop_dev_threads(dev);
+	goto out;
+}
+
+/* The activity supposed to be suspended and scst_mutex held */
+int scst_assign_dev_handler(struct scst_device *dev,
+	struct scst_dev_type *handler)
+{
+	int res = 0;
+	struct scst_tgt_dev *tgt_dev;
+	LIST_HEAD(attached_tgt_devs);
+
+	BUG_ON(handler == NULL);
+
+	if (dev->handler == handler)
+		goto out;
+
+	if (dev->handler == NULL)
+		goto assign;
+
+	if (dev->handler->detach_tgt) {
+		list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+			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");
+		}
+	}
+
+	/*
+	 * devt_dev sysfs must be created AFTER attach() and deleted BEFORE
+	 * detach() to avoid calls from sysfs for not yet ready or already dead
+	 * objects.
+	 */
+	scst_devt_dev_sysfs_del(dev);
+
+	if (dev->handler->detach) {
+		TRACE_DBG("%s", "Calling dev handler's detach()");
+		dev->handler->detach(dev);
+		TRACE_DBG("%s", "Old handler's detach() returned");
+	}
+
+	scst_stop_dev_threads(dev);
+
+assign:
+	dev->handler = handler;
+
+	if (handler == NULL)
+		goto out;
+
+	dev->threads_num = handler->threads_num;
+	dev->threads_pool_type = handler->threads_pool_type;
+
+	if (handler->attach) {
+		TRACE_DBG("Calling new dev handler's attach(%p)", dev);
+		res = handler->attach(dev);
+		TRACE_DBG("New dev handler's attach() returned %d", res);
+		if (res != 0) {
+			PRINT_ERROR("New device handler's %s attach() "
+				"failed: %d", handler->name, res);
+			goto out;
+		}
+	}
+
+	res = scst_devt_dev_sysfs_create(dev);
+	if (res != 0)
+		goto out_detach;
+
+	if (handler->attach_tgt) {
+		list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+			TRACE_DBG("Calling dev handler's attach_tgt(%p)",
+				tgt_dev);
+			res = 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", handler->name, res);
+				goto out_err_remove_sysfs;
+			}
+			list_add_tail(&tgt_dev->extra_tgt_dev_list_entry,
+				&attached_tgt_devs);
+		}
+	}
+
+	res = scst_create_dev_threads(dev);
+	if (res != 0)
+		goto out_err_detach_tgt;
+
+out:
+	return res;
+
+out_err_detach_tgt:
+	if (handler && handler->detach_tgt) {
+		list_for_each_entry(tgt_dev, &attached_tgt_devs,
+				 extra_tgt_dev_list_entry) {
+			TRACE_DBG("Calling handler's detach_tgt(%p)",
+				tgt_dev);
+			handler->detach_tgt(tgt_dev);
+			TRACE_DBG("%s", "Handler's detach_tgt() returned");
+		}
+	}
+
+out_err_remove_sysfs:
+	scst_devt_dev_sysfs_del(dev);
+
+out_detach:
+	if (handler && handler->detach) {
+		TRACE_DBG("%s", "Calling handler's detach()");
+		handler->detach(dev);
+		TRACE_DBG("%s", "Handler's detach() returned");
+	}
+
+	dev->handler = &scst_null_devtype;
+	dev->threads_num = scst_null_devtype.threads_num;
+	dev->threads_pool_type = scst_null_devtype.threads_pool_type;
+	goto out;
+}
+
+/**
+ * scst_init_threads() - initialize SCST processing threads pool
+ *
+ * Initializes scst_cmd_threads structure
+ */
+void scst_init_threads(struct scst_cmd_threads *cmd_threads)
+{
+
+	spin_lock_init(&cmd_threads->cmd_list_lock);
+	INIT_LIST_HEAD(&cmd_threads->active_cmd_list);
+	init_waitqueue_head(&cmd_threads->cmd_list_waitQ);
+	INIT_LIST_HEAD(&cmd_threads->threads_list);
+
+	mutex_lock(&scst_suspend_mutex);
+	list_add_tail(&cmd_threads->lists_list_entry,
+		&scst_cmd_threads_list);
+	mutex_unlock(&scst_suspend_mutex);
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_init_threads);
+
+/**
+ * scst_deinit_threads() - deinitialize SCST processing threads pool
+ *
+ * Deinitializes scst_cmd_threads structure
+ */
+void scst_deinit_threads(struct scst_cmd_threads *cmd_threads)
+{
+
+	mutex_lock(&scst_suspend_mutex);
+	list_del(&cmd_threads->lists_list_entry);
+	mutex_unlock(&scst_suspend_mutex);
+
+	BUG_ON(cmd_threads->io_context);
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_deinit_threads);
+
+static void scst_stop_global_threads(void)
+{
+
+	mutex_lock(&scst_mutex);
+
+	scst_del_threads(&scst_main_cmd_threads, -1);
+
+	if (scst_mgmt_cmd_thread)
+		kthread_stop(scst_mgmt_cmd_thread);
+	if (scst_mgmt_thread)
+		kthread_stop(scst_mgmt_thread);
+	if (scst_init_cmd_thread)
+		kthread_stop(scst_init_cmd_thread);
+
+	mutex_unlock(&scst_mutex);
+	return;
+}
+
+/* It does NOT stop ran threads on error! */
+static int scst_start_global_threads(int num)
+{
+	int res;
+
+	mutex_lock(&scst_mutex);
+
+	res = scst_add_threads(&scst_main_cmd_threads, NULL, NULL, num);
+	if (res < 0)
+		goto out_unlock;
+
+	scst_init_cmd_thread = kthread_run(scst_init_thread,
+		NULL, "scst_initd");
+	if (IS_ERR(scst_init_cmd_thread)) {
+		res = PTR_ERR(scst_init_cmd_thread);
+		PRINT_ERROR("kthread_create() for init cmd failed: %d", res);
+		scst_init_cmd_thread = NULL;
+		goto out_unlock;
+	}
+
+	scst_mgmt_cmd_thread = kthread_run(scst_tm_thread,
+		NULL, "scsi_tm");
+	if (IS_ERR(scst_mgmt_cmd_thread)) {
+		res = PTR_ERR(scst_mgmt_cmd_thread);
+		PRINT_ERROR("kthread_create() for TM failed: %d", res);
+		scst_mgmt_cmd_thread = NULL;
+		goto out_unlock;
+	}
+
+	scst_mgmt_thread = kthread_run(scst_global_mgmt_thread,
+		NULL, "scst_mgmtd");
+	if (IS_ERR(scst_mgmt_thread)) {
+		res = PTR_ERR(scst_mgmt_thread);
+		PRINT_ERROR("kthread_create() for mgmt failed: %d", res);
+		scst_mgmt_thread = NULL;
+		goto out_unlock;
+	}
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	return res;
+}
+
+/**
+ * scst_get() - increase global SCST ref counter
+ *
+ * Increases global SCST ref counter that prevents from entering into suspended
+ * activities stage, so protects from any global management operations.
+ */
+void scst_get(void)
+{
+	__scst_get(0);
+}
+EXPORT_SYMBOL(scst_get);
+
+/**
+ * scst_put() - decrease global SCST ref counter
+ *
+ * Decreses global SCST ref counter that prevents from entering into suspended
+ * activities stage, so protects from any global management operations. On
+ * zero, if suspending activities is waiting, they will be suspended.
+ */
+void scst_put(void)
+{
+	__scst_put();
+}
+EXPORT_SYMBOL(scst_put);
+
+/**
+ * scst_get_setup_id() - return SCST setup ID
+ *
+ * Returns SCST setup ID. This ID can be used for multiple
+ * setups with the same configuration.
+ */
+unsigned int scst_get_setup_id(void)
+{
+	return scst_setup_id;
+}
+EXPORT_SYMBOL_GPL(scst_get_setup_id);
+
+static int scst_add(struct device *cdev, struct class_interface *intf)
+{
+	struct scsi_device *scsidp;
+	int res = 0;
+
+	scsidp = to_scsi_device(cdev->parent);
+
+	if ((scsidp->host->hostt->name == NULL) ||
+	    (strcmp(scsidp->host->hostt->name, SCST_LOCAL_NAME) != 0))
+		res = scst_register_device(scsidp);
+	return res;
+}
+
+static void scst_remove(struct device *cdev, struct class_interface *intf)
+{
+	struct scsi_device *scsidp;
+
+	scsidp = to_scsi_device(cdev->parent);
+
+	if ((scsidp->host->hostt->name == NULL) ||
+	    (strcmp(scsidp->host->hostt->name, SCST_LOCAL_NAME) != 0))
+		scst_unregister_device(scsidp);
+	return;
+}
+
+static struct class_interface scst_interface = {
+	.add_dev = scst_add,
+	.remove_dev = scst_remove,
+};
+
+static void __init scst_print_config(void)
+{
+	char buf[128];
+	int i, j;
+
+	i = snprintf(buf, sizeof(buf), "Enabled features: ");
+	j = i;
+
+#ifdef CONFIG_SCST_STRICT_SERIALIZING
+	i += snprintf(&buf[i], sizeof(buf) - i, "STRICT_SERIALIZING");
+#endif
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sEXTRACHECKS",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_TRACING
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sTRACING",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_DEBUG
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_DEBUG_TM
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG_TM",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_DEBUG_RETRY
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG_RETRY",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_DEBUG_OOM
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG_OOM",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_DEBUG_SN
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG_SN",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_USE_EXPECTED_VALUES
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sUSE_EXPECTED_VALUES",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+	i += snprintf(&buf[i], sizeof(buf) - i,
+		"%sTEST_IO_IN_SIRQ",
+		(j == i) ? "" : ", ");
+#endif
+
+#ifdef CONFIG_SCST_STRICT_SECURITY
+	i += snprintf(&buf[i], sizeof(buf) - i, "%sSTRICT_SECURITY",
+		(j == i) ? "" : ", ");
+#endif
+
+	if (j != i)
+		PRINT_INFO("%s", buf);
+}
+
+static int __init init_scst(void)
+{
+	int res, i;
+	int scst_num_cpus;
+
+	{
+		struct scsi_sense_hdr *shdr;
+		BUILD_BUG_ON(SCST_SENSE_BUFFERSIZE < sizeof(*shdr));
+	}
+	{
+		struct scst_tgt_dev *t;
+		struct scst_cmd *c;
+		BUILD_BUG_ON(sizeof(t->curr_sn) != sizeof(t->expected_sn));
+		BUILD_BUG_ON(sizeof(c->sn) != sizeof(t->expected_sn));
+	}
+
+	mutex_init(&scst_mutex);
+	mutex_init(&scst_mutex2);
+	INIT_LIST_HEAD(&scst_template_list);
+	INIT_LIST_HEAD(&scst_dev_list);
+	INIT_LIST_HEAD(&scst_dev_type_list);
+	INIT_LIST_HEAD(&scst_virtual_dev_type_list);
+	spin_lock_init(&scst_main_lock);
+	spin_lock_init(&scst_init_lock);
+	init_waitqueue_head(&scst_init_cmd_list_waitQ);
+	INIT_LIST_HEAD(&scst_init_cmd_list);
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+	scst_trace_flag = SCST_DEFAULT_LOG_FLAGS;
+#endif
+	atomic_set(&scst_cmd_count, 0);
+	spin_lock_init(&scst_mcmd_lock);
+	INIT_LIST_HEAD(&scst_active_mgmt_cmd_list);
+	INIT_LIST_HEAD(&scst_delayed_mgmt_cmd_list);
+	init_waitqueue_head(&scst_mgmt_cmd_list_waitQ);
+	init_waitqueue_head(&scst_mgmt_waitQ);
+	spin_lock_init(&scst_mgmt_lock);
+	INIT_LIST_HEAD(&scst_sess_init_list);
+	INIT_LIST_HEAD(&scst_sess_shut_list);
+	init_waitqueue_head(&scst_dev_cmd_waitQ);
+	mutex_init(&scst_suspend_mutex);
+	INIT_LIST_HEAD(&scst_cmd_threads_list);
+	cpus_setall(default_cpu_mask);
+
+	scst_init_threads(&scst_main_cmd_threads);
+
+	res = scst_lib_init();
+	if (res != 0)
+		goto out_deinit_threads;
+
+	scst_num_cpus = num_online_cpus();
+
+	/* ToDo: register_cpu_notifier() */
+
+	if (scst_threads == 0)
+		scst_threads = scst_num_cpus;
+
+	if (scst_threads < 1) {
+		PRINT_ERROR("%s", "scst_threads can not be less than 1");
+		scst_threads = scst_num_cpus;
+	}
+
+#define INIT_CACHEP(p, s, o) do {					\
+		p = KMEM_CACHE(s, SCST_SLAB_FLAGS);			\
+		TRACE_MEM("Slab create: %s at %p size %zd", #s, p,	\
+			  sizeof(struct s));				\
+		if (p == NULL) {					\
+			res = -ENOMEM;					\
+			goto o;						\
+		}							\
+	} while (0)
+
+	INIT_CACHEP(scst_mgmt_cachep, scst_mgmt_cmd, out_lib_exit);
+	INIT_CACHEP(scst_mgmt_stub_cachep, scst_mgmt_cmd_stub,
+			out_destroy_mgmt_cache);
+	INIT_CACHEP(scst_ua_cachep, scst_tgt_dev_UA,
+			out_destroy_mgmt_stub_cache);
+	{
+		struct scst_sense { uint8_t s[SCST_SENSE_BUFFERSIZE]; };
+		INIT_CACHEP(scst_sense_cachep, scst_sense,
+			    out_destroy_ua_cache);
+	}
+	INIT_CACHEP(scst_aen_cachep, scst_aen, out_destroy_sense_cache);
+	INIT_CACHEP(scst_cmd_cachep, scst_cmd, out_destroy_aen_cache);
+	INIT_CACHEP(scst_sess_cachep, scst_session, out_destroy_cmd_cache);
+	INIT_CACHEP(scst_tgtd_cachep, scst_tgt_dev, out_destroy_sess_cache);
+	INIT_CACHEP(scst_acgd_cachep, scst_acg_dev, out_destroy_tgt_cache);
+
+	scst_mgmt_mempool = mempool_create(64, mempool_alloc_slab,
+		mempool_free_slab, scst_mgmt_cachep);
+	if (scst_mgmt_mempool == NULL) {
+		res = -ENOMEM;
+		goto out_destroy_acg_cache;
+	}
+
+	/*
+	 * All mgmt stubs, UAs and sense buffers are bursty and loosing them
+	 * may have fatal consequences, so let's have big pools for them.
+	 */
+
+	scst_mgmt_stub_mempool = mempool_create(1024, mempool_alloc_slab,
+		mempool_free_slab, scst_mgmt_stub_cachep);
+	if (scst_mgmt_stub_mempool == NULL) {
+		res = -ENOMEM;
+		goto out_destroy_mgmt_mempool;
+	}
+
+	scst_ua_mempool = mempool_create(512, mempool_alloc_slab,
+		mempool_free_slab, scst_ua_cachep);
+	if (scst_ua_mempool == NULL) {
+		res = -ENOMEM;
+		goto out_destroy_mgmt_stub_mempool;
+	}
+
+	scst_sense_mempool = mempool_create(1024, mempool_alloc_slab,
+		mempool_free_slab, scst_sense_cachep);
+	if (scst_sense_mempool == NULL) {
+		res = -ENOMEM;
+		goto out_destroy_ua_mempool;
+	}
+
+	scst_aen_mempool = mempool_create(100, mempool_alloc_slab,
+		mempool_free_slab, scst_aen_cachep);
+	if (scst_aen_mempool == NULL) {
+		res = -ENOMEM;
+		goto out_destroy_sense_mempool;
+	}
+
+	res = scst_sysfs_init();
+	if (res != 0)
+		goto out_destroy_aen_mempool;
+
+	if (scst_max_cmd_mem == 0) {
+		struct sysinfo si;
+		si_meminfo(&si);
+#if BITS_PER_LONG == 32
+		scst_max_cmd_mem = min(
+			(((uint64_t)(si.totalram - si.totalhigh) << PAGE_SHIFT)
+				>> 20) >> 2, (uint64_t)1 << 30);
+#else
+		scst_max_cmd_mem = (((si.totalram - si.totalhigh) << PAGE_SHIFT)
+					>> 20) >> 2;
+#endif
+	}
+
+	if (scst_max_dev_cmd_mem != 0) {
+		if (scst_max_dev_cmd_mem > scst_max_cmd_mem) {
+			PRINT_ERROR("scst_max_dev_cmd_mem (%d) > "
+				"scst_max_cmd_mem (%d)",
+				scst_max_dev_cmd_mem,
+				scst_max_cmd_mem);
+			scst_max_dev_cmd_mem = scst_max_cmd_mem;
+		}
+	} else
+		scst_max_dev_cmd_mem = scst_max_cmd_mem * 2 / 5;
+
+	res = scst_sgv_pools_init(
+		((uint64_t)scst_max_cmd_mem << 10) >> (PAGE_SHIFT - 10), 0);
+	if (res != 0)
+		goto out_sysfs_cleanup;
+
+	res = scsi_register_interface(&scst_interface);
+	if (res != 0)
+		goto out_destroy_sgv_pool;
+
+	for (i = 0; i < (int)ARRAY_SIZE(scst_tasklets); i++) {
+		spin_lock_init(&scst_tasklets[i].tasklet_lock);
+		INIT_LIST_HEAD(&scst_tasklets[i].tasklet_cmd_list);
+		tasklet_init(&scst_tasklets[i].tasklet,
+			     (void *)scst_cmd_tasklet,
+			     (unsigned long)&scst_tasklets[i]);
+	}
+
+	TRACE_DBG("%d CPUs found, starting %d threads", scst_num_cpus,
+		scst_threads);
+
+	res = scst_start_global_threads(scst_threads);
+	if (res < 0)
+		goto out_thread_free;
+
+	res = scst_pr_check_pr_path();
+	if (res != 0)
+		goto out_thread_free;
+
+	PRINT_INFO("SCST version %s loaded successfully (max mem for "
+		"commands %dMB, per device %dMB)", SCST_VERSION_STRING,
+		scst_max_cmd_mem, scst_max_dev_cmd_mem);
+
+	scst_print_config();
+
+out:
+	return res;
+
+out_thread_free:
+	scst_stop_global_threads();
+
+	scsi_unregister_interface(&scst_interface);
+
+out_destroy_sgv_pool:
+	scst_sgv_pools_deinit();
+
+out_sysfs_cleanup:
+	scst_sysfs_cleanup();
+
+out_destroy_aen_mempool:
+	mempool_destroy(scst_aen_mempool);
+
+out_destroy_sense_mempool:
+	mempool_destroy(scst_sense_mempool);
+
+out_destroy_ua_mempool:
+	mempool_destroy(scst_ua_mempool);
+
+out_destroy_mgmt_stub_mempool:
+	mempool_destroy(scst_mgmt_stub_mempool);
+
+out_destroy_mgmt_mempool:
+	mempool_destroy(scst_mgmt_mempool);
+
+out_destroy_acg_cache:
+	kmem_cache_destroy(scst_acgd_cachep);
+
+out_destroy_tgt_cache:
+	kmem_cache_destroy(scst_tgtd_cachep);
+
+out_destroy_sess_cache:
+	kmem_cache_destroy(scst_sess_cachep);
+
+out_destroy_cmd_cache:
+	kmem_cache_destroy(scst_cmd_cachep);
+
+out_destroy_aen_cache:
+	kmem_cache_destroy(scst_aen_cachep);
+
+out_destroy_sense_cache:
+	kmem_cache_destroy(scst_sense_cachep);
+
+out_destroy_ua_cache:
+	kmem_cache_destroy(scst_ua_cachep);
+
+out_destroy_mgmt_stub_cache:
+	kmem_cache_destroy(scst_mgmt_stub_cachep);
+
+out_destroy_mgmt_cache:
+	kmem_cache_destroy(scst_mgmt_cachep);
+
+out_lib_exit:
+	scst_lib_exit();
+
+out_deinit_threads:
+	scst_deinit_threads(&scst_main_cmd_threads);
+	goto out;
+}
+
+static void __exit exit_scst(void)
+{
+
+	/* ToDo: unregister_cpu_notifier() */
+
+	scst_stop_global_threads();
+
+	scst_deinit_threads(&scst_main_cmd_threads);
+
+	scsi_unregister_interface(&scst_interface);
+
+	scst_sgv_pools_deinit();
+
+	scst_sysfs_cleanup();
+
+#define DEINIT_CACHEP(p) do {		\
+		kmem_cache_destroy(p);	\
+		p = NULL;		\
+	} while (0)
+
+	mempool_destroy(scst_mgmt_mempool);
+	mempool_destroy(scst_mgmt_stub_mempool);
+	mempool_destroy(scst_ua_mempool);
+	mempool_destroy(scst_sense_mempool);
+	mempool_destroy(scst_aen_mempool);
+
+	DEINIT_CACHEP(scst_mgmt_cachep);
+	DEINIT_CACHEP(scst_mgmt_stub_cachep);
+	DEINIT_CACHEP(scst_ua_cachep);
+	DEINIT_CACHEP(scst_sense_cachep);
+	DEINIT_CACHEP(scst_aen_cachep);
+	DEINIT_CACHEP(scst_cmd_cachep);
+	DEINIT_CACHEP(scst_sess_cachep);
+	DEINIT_CACHEP(scst_tgtd_cachep);
+	DEINIT_CACHEP(scst_acgd_cachep);
+
+	scst_lib_exit();
+
+	PRINT_INFO("%s", "SCST unloaded");
+	return;
+}
+
+module_init(init_scst);
+module_exit(exit_scst);
+
+MODULE_AUTHOR("Vladislav Bolkhovitin");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SCSI target core");
+MODULE_VERSION(SCST_VERSION_STRING);
drivers/scst/scst_module.c renamed from linux-2.6.35/drivers/scst/scst_module.c +63 -0

63 lines not on this page · raw message

drivers/scst/scst_priv.h renamed from linux-2.6.35/drivers/scst/scst_priv.h +606 -0

606 lines not on this page · raw message

#5 lore raw

PATCH v4 5/19 : SCST implementation of the SCSI target state machine

In reply to Vladislav Bolkhovitin

This patch contains implementation of the SCSI target state machine as well as functions to interact with target drivers and dev handlers during commands processing.

The core of SCSI target state machine is scst_process_active_cmd(), which depending on the current processing state (cmd->state) schedules the next processing function.

All the processing supposed to be asynchronous, i.e. with no sleeping states. Each command can be processed only by a single thread at time.

There are the following processing states:

1. SCST_CMD_STATE_INIT_WAIT - waiting for the target driver to call scst_cmd_init_done()

2. SCST_CMD_STATE_INIT - LUN is being translated to the corresponding SCST device

3. SCST_CMD_STATE_PRE_PARSE - internal parsing of the command to do the most common work of it in scst_pre_parse()

4. SCST_CMD_STATE_DEV_PARSE - the dev handler's parse() is going to be called

5. SCST_CMD_STATE_PREPARE_SPACE - allocation of the cmd's data buffer

6. SCST_CMD_STATE_PREPROCESSING_DONE - calling the target driver's preprocessing_done(), if requested by it.

7. SCST_CMD_STATE_PREPROCESSING_DONE_CALLED - waiting for the target driver to call scst_restart_cmd()

8. SCST_CMD_STATE_RDY_TO_XFER - the target driver's rdy_to_xfer() is going to be called

9. SCST_CMD_STATE_DATA_WAIT - waiting for the target driver to call scst_rx_data()

10. SCST_CMD_STATE_TGT_PRE_EXEC - the target driver's pre_exec() is going to be called

11. SCST_CMD_STATE_SEND_FOR_EXEC - cmd is going to be sent for execution

12. SCST_CMD_STATE_LOCAL_EXEC - cmd is being checked if it should be executed locally (e.g. REPORT LUNS or reservation commands) and, if yes, execute them

13. SCST_CMD_STATE_REAL_EXEC - cmd is being sent for execution

14. SCST_CMD_STATE_REAL_EXECUTING - waiting for the dev handler to call cmd->scst_cmd_done()

15. SCST_CMD_STATE_PRE_DEV_DONE - internal post-exec checks

16. SCST_CMD_STATE_MODE_SELECT_CHECKS - internal MODE SELECT pages related checks for MODE SELECT command to emulate required behavior in 1:many pass-through case.

17. SCST_CMD_STATE_DEV_DONE - the dev handler's dev_done() is going to be called

18. SCST_CMD_STATE_PRE_XMIT_RESP - checks before the target driver's xmit_response() is called

19. SCST_CMD_STATE_XMIT_RESP - the target driver's xmit_response() is going to be called

20. SCST_CMD_STATE_XMIT_WAIT - waiting for the target driver to call scst_tgt_cmd_done()

15. SCST_CMD_STATE_FINISHED - cmd finished

16. SCST_CMD_STATE_FINISHED_INTERNAL - internal cmd (e.g. REQUEST SENSE) finished

---
 scst_targ.c | 6290 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 6290 insertions(+)
drivers/scst/scst_targ.c renamed from linux-2.6.35/drivers/scst/scst_targ.c +6290 -0
diff -uprN orig/linux-2.6.35/drivers/scst/scst_targ.c linux-2.6.35/drivers/scst/scst_targ.c
@@ -0,0 +1,6290 @@
+/*
+ *  scst_targ.c
+ *
+ *  Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <[email protected]>
+ *  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/smp_lock.h>
+#include <linux/unistd.h>
+#include <linux/string.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+
+#include <scst/scst.h>
+#include "scst_priv.h"
+#include "scst_pres.h"
+
+#if 0 /* Temporary left for future performance investigations */
+/* Deleting it don't forget to delete write_cmd_count */
+#define CONFIG_SCST_ORDERED_READS
+#endif
+
+#if 0 /* Let's disable it for now to see if users will complain about it */
+/* Deleting it don't forget to delete write_cmd_count */
+#define CONFIG_SCST_PER_DEVICE_CMD_COUNT_LIMIT
+#endif
+
+static void scst_cmd_set_sn(struct scst_cmd *cmd);
+static int __scst_init_cmd(struct scst_cmd *cmd);
+static void scst_finish_cmd_mgmt(struct scst_cmd *cmd);
+static struct scst_cmd *__scst_find_cmd_by_tag(struct scst_session *sess,
+	uint64_t tag, bool to_abort);
+static void scst_process_redirect_cmd(struct scst_cmd *cmd,
+	enum scst_exec_context context, int check_retries);
+
+/**
+ * scst_post_parse() - do post parse actions
+ *
+ * This function must be called by dev handler after its parse() callback
+ * returned SCST_CMD_STATE_STOP before calling scst_process_active_cmd().
+ */
+void scst_post_parse(struct scst_cmd *cmd)
+{
+	scst_set_parse_time(cmd);
+}
+EXPORT_SYMBOL_GPL(scst_post_parse);
+
+/**
+ * scst_post_alloc_data_buf() - do post alloc_data_buf actions
+ *
+ * This function must be called by dev handler after its alloc_data_buf()
+ * callback returned SCST_CMD_STATE_STOP before calling
+ * scst_process_active_cmd().
+ */
+void scst_post_alloc_data_buf(struct scst_cmd *cmd)
+{
+	scst_set_alloc_buf_time(cmd);
+}
+EXPORT_SYMBOL_GPL(scst_post_alloc_data_buf);
+
+static inline void scst_schedule_tasklet(struct scst_cmd *cmd)
+{
+	struct scst_tasklet *t = &scst_tasklets[smp_processor_id()];
+	unsigned long flags;
+
+	spin_lock_irqsave(&t->tasklet_lock, flags);
+	TRACE_DBG("Adding cmd %p to tasklet %d cmd list", cmd,
+		smp_processor_id());
+	list_add_tail(&cmd->cmd_list_entry, &t->tasklet_cmd_list);
+	spin_unlock_irqrestore(&t->tasklet_lock, flags);
+
+	tasklet_schedule(&t->tasklet);
+}
+
+/**
+ * scst_rx_cmd() - create new command
+ * @sess:	SCST session
+ * @lun:	LUN for the command
+ * @lun_len:	length of the LUN in bytes
+ * @cdb:	CDB of the command
+ * @cdb_len:	length of the CDB in bytes
+ * @atomic:	true, if current context is atomic
+ *
+ * Description:
+ *    Creates new SCST command. Returns new command on success or
+ *    NULL otherwise.
+ *
+ *    Must not be called in parallel with scst_unregister_session() for the
+ *    same session.
+ */
+struct scst_cmd *scst_rx_cmd(struct scst_session *sess,
+	const uint8_t *lun, int lun_len, const uint8_t *cdb,
+	unsigned int cdb_len, int atomic)
+{
+	struct scst_cmd *cmd;
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	if (unlikely(sess->shut_phase != SCST_SESS_SPH_READY)) {
+		PRINT_CRIT_ERROR("%s",
+			"New cmd while shutting down the session");
+		BUG();
+	}
+#endif
+
+	cmd = scst_alloc_cmd(atomic ? GFP_ATOMIC : GFP_KERNEL);
+	if (cmd == NULL)
+		goto out;
+
+	cmd->sess = sess;
+	cmd->tgt = sess->tgt;
+	cmd->tgtt = sess->tgt->tgtt;
+
+	cmd->lun = scst_unpack_lun(lun, lun_len);
+	if (unlikely(cmd->lun == NO_SUCH_LUN)) {
+		PRINT_ERROR("Wrong LUN %d, finishing cmd", -1);
+		scst_set_cmd_error(cmd,
+			   SCST_LOAD_SENSE(scst_sense_lun_not_supported));
+	}
+
+	/*
+	 * For cdb_len 0 defer the error reporting until scst_cmd_init_done(),
+	 * scst_set_cmd_error() supports nested calls.
+	 */
+	if (unlikely(cdb_len > SCST_MAX_CDB_SIZE)) {
+		PRINT_ERROR("Too big CDB len %d, finishing cmd", cdb_len);
+		cdb_len = SCST_MAX_CDB_SIZE;
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_message));
+	}
+
+	memcpy(cmd->cdb, cdb, cdb_len);
+	cmd->cdb_len = cdb_len;
+
+	TRACE_DBG("cmd %p, sess %p", cmd, sess);
+	scst_sess_get(sess);
+
+out:
+	return cmd;
+}
+EXPORT_SYMBOL(scst_rx_cmd);
+
+/*
+ * No locks, but might be on IRQ. Returns 0 on success, <0 if processing of
+ * this command should be stopped.
+ */
+static int scst_init_cmd(struct scst_cmd *cmd, enum scst_exec_context *context)
+{
+	int rc, res = 0;
+
+	/* See the comment in scst_do_job_init() */
+	if (unlikely(!list_empty(&scst_init_cmd_list))) {
+		TRACE_MGMT_DBG("%s", "init cmd list busy");
+		goto out_redirect;
+	}
+	/*
+	 * Memory barrier isn't necessary here, because CPU appears to
+	 * be self-consistent and we don't care about the race, described
+	 * in comment in scst_do_job_init().
+	 */
+
+	rc = __scst_init_cmd(cmd);
+	if (unlikely(rc > 0))
+		goto out_redirect;
+	else if (unlikely(rc != 0)) {
+		res = 1;
+		goto out;
+	}
+
+	EXTRACHECKS_BUG_ON(*context == SCST_CONTEXT_SAME);
+
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+	scst_get_cdb_info(cmd);
+	if (cmd->op_flags & SCST_TEST_IO_IN_SIRQ_ALLOWED)
+		goto out;
+#endif
+
+	/* Small context optimization */
+	if (((*context == SCST_CONTEXT_TASKLET) ||
+	     (*context == SCST_CONTEXT_DIRECT_ATOMIC)) &&
+	      scst_cmd_is_expected_set(cmd)) {
+		if (cmd->expected_data_direction & SCST_DATA_WRITE) {
+			if (!test_bit(SCST_TGT_DEV_AFTER_INIT_WR_ATOMIC,
+					&cmd->tgt_dev->tgt_dev_flags))
+				*context = SCST_CONTEXT_THREAD;
+		} else
+			*context = SCST_CONTEXT_THREAD;
+	}
+
+out:
+	return res;
+
+out_redirect:
+	if (cmd->preprocessing_only) {
+		/*
+		 * Poor man solution for single threaded targets, where
+		 * blocking receiver at least sometimes means blocking all.
+		 * For instance, iSCSI target won't be able to receive
+		 * Data-Out PDUs.
+		 */
+		BUG_ON(*context != SCST_CONTEXT_DIRECT);
+		scst_set_busy(cmd);
+		scst_set_cmd_abnormal_done_state(cmd);
+		res = 1;
+		/* Keep initiator away from too many BUSY commands */
+		msleep(50);
+	} else {
+		unsigned long flags;
+		spin_lock_irqsave(&scst_init_lock, flags);
+		TRACE_MGMT_DBG("Adding cmd %p to init cmd list (scst_cmd_count "
+			"%d)", cmd, atomic_read(&scst_cmd_count));
+		list_add_tail(&cmd->cmd_list_entry, &scst_init_cmd_list);
+		if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))
+			scst_init_poll_cnt++;
+		spin_unlock_irqrestore(&scst_init_lock, flags);
+		wake_up(&scst_init_cmd_list_waitQ);
+		res = -1;
+	}
+	goto out;
+}
+
+/**
+ * scst_cmd_init_done() - the command's initialization done
+ * @cmd:	SCST command
+ * @pref_context: preferred command execution context
+ *
+ * Description:
+ *    Notifies SCST that the driver finished its part of the command
+ *    initialization, and the command is ready for execution.
+ *    The second argument sets preferred command execition context.
+ *    See SCST_CONTEXT_* constants for details.
+ *
+ *    !!IMPORTANT!!
+ *
+ *    If cmd->set_sn_on_restart_cmd not set, this function, as well as
+ *    scst_cmd_init_stage1_done() and scst_restart_cmd(), must not be
+ *    called simultaneously for the same session (more precisely,
+ *    for the same session/LUN, i.e. tgt_dev), i.e. they must be
+ *    somehow externally serialized. This is needed to have lock free fast
+ *    path in scst_cmd_set_sn(). For majority of targets those functions are
+ *    naturally serialized by the single source of commands. Only iSCSI
+ *    immediate commands with multiple connections per session seems to be an
+ *    exception. For it, some mutex/lock shall be used for the serialization.
+ */
+void scst_cmd_init_done(struct scst_cmd *cmd,
+	enum scst_exec_context pref_context)
+{
+	unsigned long flags;
+	struct scst_session *sess = cmd->sess;
+	int rc;
+
+	scst_set_start_time(cmd);
+
+	TRACE_DBG("Preferred context: %d (cmd %p)", pref_context, cmd);
+	TRACE(TRACE_SCSI, "tag=%llu, lun=%lld, CDB len=%d, queue_type=%x "
+		"(cmd %p)", (long long unsigned int)cmd->tag,
+		(long long unsigned int)cmd->lun, cmd->cdb_len,
+		cmd->queue_type, cmd);
+	PRINT_BUFF_FLAG(TRACE_SCSI|TRACE_RCV_BOT, "Recieving CDB",
+		cmd->cdb, cmd->cdb_len);
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	if (unlikely((in_irq() || irqs_disabled())) &&
+	    ((pref_context == SCST_CONTEXT_DIRECT) ||
+	     (pref_context == SCST_CONTEXT_DIRECT_ATOMIC))) {
+		PRINT_ERROR("Wrong context %d in IRQ from target %s, use "
+			"SCST_CONTEXT_THREAD instead", pref_context,
+			cmd->tgtt->name);
+		pref_context = SCST_CONTEXT_THREAD;
+	}
+#endif
+
+	atomic_inc(&sess->sess_cmd_count);
+
+	spin_lock_irqsave(&sess->sess_list_lock, flags);
+
+	if (unlikely(sess->init_phase != SCST_SESS_IPH_READY)) {
+		/*
+		 * We must always keep commands in the sess list from the
+		 * very beginning, because otherwise they can be missed during
+		 * TM processing. This check is needed because there might be
+		 * old, i.e. deferred, commands and new, i.e. just coming, ones.
+		 */
+		if (cmd->sess_cmd_list_entry.next == NULL)
+			list_add_tail(&cmd->sess_cmd_list_entry,
+				&sess->sess_cmd_list);
+		switch (sess->init_phase) {
+		case SCST_SESS_IPH_SUCCESS:
+			break;
+		case SCST_SESS_IPH_INITING:
+			TRACE_DBG("Adding cmd %p to init deferred cmd list",
+				  cmd);
+			list_add_tail(&cmd->cmd_list_entry,
+				&sess->init_deferred_cmd_list);
+			spin_unlock_irqrestore(&sess->sess_list_lock, flags);
+			goto out;
+		case SCST_SESS_IPH_FAILED:
+			spin_unlock_irqrestore(&sess->sess_list_lock, flags);
+			scst_set_busy(cmd);
+			goto set_state;
+		default:
+			BUG();
+		}
+	} else
+		list_add_tail(&cmd->sess_cmd_list_entry,
+			      &sess->sess_cmd_list);
+
+	spin_unlock_irqrestore(&sess->sess_list_lock, flags);
+
+	if (unlikely(cmd->cdb_len == 0)) {
+		PRINT_ERROR("%s", "Wrong CDB len 0, finishing cmd");
+		scst_set_cmd_error(cmd,
+			   SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+	}
+
+	if (unlikely(cmd->queue_type >= SCST_CMD_QUEUE_ACA)) {
+		PRINT_ERROR("Unsupported queue type %d", cmd->queue_type);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_message));
+	}
+
+set_state:
+	if (unlikely(cmd->status != SAM_STAT_GOOD)) {
+		scst_set_cmd_abnormal_done_state(cmd);
+		goto active;
+	}
+
+	/*
+	 * Cmd must be inited here to preserve the order. In case if cmd
+	 * already preliminary completed by target driver we need to init
+	 * cmd anyway to find out in which format we should return sense.
+	 */
+	cmd->state = SCST_CMD_STATE_INIT;
+	rc = scst_init_cmd(cmd, &pref_context);
+	if (unlikely(rc < 0))
+		goto out;
+
+active:
+	/* Here cmd must not be in any cmd list, no locks */
+	switch (pref_context) {
+	case SCST_CONTEXT_TASKLET:
+		scst_schedule_tasklet(cmd);
+		break;
+
+	case SCST_CONTEXT_DIRECT:
+		scst_process_active_cmd(cmd, false);
+		break;
+
+	case SCST_CONTEXT_DIRECT_ATOMIC:
+		scst_process_active_cmd(cmd, true);
+		break;
+
+	default:
+		PRINT_ERROR("Context %x is undefined, using the thread one",
+			pref_context);
+		/* go through */
+	case SCST_CONTEXT_THREAD:
+		spin_lock_irqsave(&cmd->cmd_threads->cmd_list_lock, flags);
+		TRACE_DBG("Adding cmd %p to active cmd list", cmd);
+		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_irqrestore(&cmd->cmd_threads->cmd_list_lock, flags);
+		break;
+	}
+
+out:
+	return;
+}
+EXPORT_SYMBOL(scst_cmd_init_done);
+
+static int scst_pre_parse(struct scst_cmd *cmd)
+{
+	int res = SCST_CMD_STATE_RES_CONT_SAME;
+	struct scst_device *dev = cmd->dev;
+	int rc;
+
+#ifdef CONFIG_SCST_STRICT_SERIALIZING
+	cmd->inc_expected_sn_on_done = 1;
+#else
+	cmd->inc_expected_sn_on_done = dev->handler->exec_sync ||
+		scst_is_implicit_ordered(cmd) ||
+		(!dev->has_own_order_mgmt &&
+		 (dev->queue_alg == SCST_CONTR_MODE_QUEUE_ALG_RESTRICTED_REORDER ||
+		  cmd->queue_type == SCST_CMD_QUEUE_ORDERED));
+#endif
+
+	/*
+	 * Expected transfer data supplied by the SCSI transport via the
+	 * target driver are untrusted, so we prefer to fetch them from CDB.
+	 * Additionally, not all transports support supplying the expected
+	 * transfer data.
+	 */
+
+	rc = scst_get_cdb_info(cmd);
+	if (unlikely(rc != 0)) {
+		if (rc > 0) {
+			PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
+			goto out_err;
+		}
+
+		EXTRACHECKS_BUG_ON(cmd->op_flags & SCST_INFO_VALID);
+
+		TRACE(TRACE_MINOR, "Unknown opcode 0x%02x for %s. "
+			"Should you update scst_scsi_op_table?",
+			cmd->cdb[0], dev->handler->name);
+		PRINT_BUFF_FLAG(TRACE_MINOR, "Failed CDB", cmd->cdb,
+			cmd->cdb_len);
+	} else {
+		EXTRACHECKS_BUG_ON(!(cmd->op_flags & SCST_INFO_VALID));
+	}
+
+	cmd->state = SCST_CMD_STATE_DEV_PARSE;
+
+	TRACE_DBG("op_name <%s> (cmd %p), direction=%d "
+		"(expected %d, set %s), bufflen=%d, out_bufflen=%d (expected "
+		"len %d, out expected len %d), flags=%d", cmd->op_name, cmd,
+		cmd->data_direction, cmd->expected_data_direction,
+		scst_cmd_is_expected_set(cmd) ? "yes" : "no",
+		cmd->bufflen, cmd->out_bufflen, cmd->expected_transfer_len,
+		cmd->expected_out_transfer_len, cmd->op_flags);
+
+out:
+	return res;
+
+out_err:
+	scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+	scst_set_cmd_abnormal_done_state(cmd);
+	res = SCST_CMD_STATE_RES_CONT_SAME;
+	goto out;
+}
+
+#ifndef CONFIG_SCST_USE_EXPECTED_VALUES
+static bool scst_is_allowed_to_mismatch_cmd(struct scst_cmd *cmd)
+{
+	bool res = false;
+
+	/* VERIFY commands with BYTCHK unset shouldn't fail here */
+	if ((cmd->op_flags & SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED) &&
+	    (cmd->cdb[1] & BYTCHK) == 0) {
+		res = true;
+		goto out;
+	}
+
+	switch (cmd->cdb[0]) {
+	case TEST_UNIT_READY:
+		/* Crazy VMware people sometimes do TUR with READ direction */
+		if ((cmd->expected_data_direction == SCST_DATA_READ) ||
+		    (cmd->expected_data_direction == SCST_DATA_NONE))
+			res = true;
+		break;
+	}
+
+out:
+	return res;
+}
+#endif
+
+static int scst_parse_cmd(struct scst_cmd *cmd)
+{
+	int res = SCST_CMD_STATE_RES_CONT_SAME;
+	int state;
+	struct scst_device *dev = cmd->dev;
+	int orig_bufflen = cmd->bufflen;
+
+	if (likely(!scst_is_cmd_fully_local(cmd))) {
+		if (unlikely(!dev->handler->parse_atomic &&
+			     scst_cmd_atomic(cmd))) {
+			/*
+			 * It shouldn't be because of the SCST_TGT_DEV_AFTER_*
+			 * optimization.
+			 */
+			TRACE_DBG("Dev handler %s parse() needs thread "
+				"context, rescheduling", dev->handler->name);
+			res = SCST_CMD_STATE_RES_NEED_THREAD;
+			goto out;
+		}
+
+		TRACE_DBG("Calling dev handler %s parse(%p)",
+		      dev->handler->name, cmd);
+		TRACE_BUFF_FLAG(TRACE_SND_BOT, "Parsing: ",
+				cmd->cdb, cmd->cdb_len);
+		scst_set_cur_start(cmd);
+		state = dev->handler->parse(cmd);
+		/* Caution: cmd can be already dead here */
+		TRACE_DBG("Dev handler %s parse() returned %d",
+			dev->handler->name, state);
+
+		switch (state) {
+		case SCST_CMD_STATE_NEED_THREAD_CTX:
+			scst_set_parse_time(cmd);
+			TRACE_DBG("Dev handler %s parse() requested thread "
+			      "context, rescheduling", dev->handler->name);
+			res = SCST_CMD_STATE_RES_NEED_THREAD;
+			goto out;
+
+		case SCST_CMD_STATE_STOP:
+			TRACE_DBG("Dev handler %s parse() requested stop "
+				"processing", dev->handler->name);
+			res = SCST_CMD_STATE_RES_CONT_NEXT;
+			goto out;
+		}
+
+		scst_set_parse_time(cmd);
+
+		if (state == SCST_CMD_STATE_DEFAULT)
+			state = SCST_CMD_STATE_PREPARE_SPACE;
+	} else
+		state = SCST_CMD_STATE_PREPARE_SPACE;
+
+	if (unlikely(state == SCST_CMD_STATE_PRE_XMIT_RESP))
+		goto set_res;
+
+	if (unlikely(!(cmd->op_flags & SCST_INFO_VALID))) {
+#ifdef CONFIG_SCST_USE_EXPECTED_VALUES
+		if (scst_cmd_is_expected_set(cmd)) {
+			TRACE(TRACE_MINOR, "Using initiator supplied values: "
+				"direction %d, transfer_len %d/%d",
+				cmd->expected_data_direction,
+				cmd->expected_transfer_len,
+				cmd->expected_out_transfer_len);
+			cmd->data_direction = cmd->expected_data_direction;
+			cmd->bufflen = cmd->expected_transfer_len;
+			cmd->out_bufflen = cmd->expected_out_transfer_len;
+		} else {
+			PRINT_ERROR("Unknown opcode 0x%02x for %s and "
+			     "target %s not supplied expected values",
+			     cmd->cdb[0], dev->handler->name, cmd->tgtt->name);
+			scst_set_cmd_error(cmd,
+				   SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+			goto out_done;
+		}
+#else
+		/*
+		 * Let's ignore reporting T10/04-262r7 16-byte and 12-byte ATA
+		 * pass-thru commands to not pollute logs (udev(?) checks them
+		 * for some reason). If somebody has their description, please,
+		 * update scst_scsi_op_table.
+		 */
+		if ((cmd->cdb[0] != 0x85) && (cmd->cdb[0] != 0xa1))
+			PRINT_ERROR("Refusing unknown opcode %x", cmd->cdb[0]);
+		else
+			TRACE(TRACE_MINOR, "Refusing unknown opcode %x",
+				cmd->cdb[0]);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+		goto out_done;
+#endif
+	}
+
+	if (unlikely(cmd->cdb_len == 0)) {
+		PRINT_ERROR("Unable to get CDB length for "
+			"opcode 0x%02x. Returning INVALID "
+			"OPCODE", cmd->cdb[0]);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+		goto out_done;
+	}
+
+	EXTRACHECKS_BUG_ON(cmd->cdb_len == 0);
+
+	TRACE(TRACE_SCSI, "op_name <%s> (cmd %p), direction=%d "
+		"(expected %d, set %s), bufflen=%d, out_bufflen=%d, (expected "
+		"len %d, out expected len %d), flags=%x", cmd->op_name, cmd,
+		cmd->data_direction, cmd->expected_data_direction,
+		scst_cmd_is_expected_set(cmd) ? "yes" : "no",
+		cmd->bufflen, cmd->out_bufflen, cmd->expected_transfer_len,
+		cmd->expected_out_transfer_len, cmd->op_flags);
+
+	if (unlikely((cmd->op_flags & SCST_UNKNOWN_LENGTH) != 0)) {
+		if (scst_cmd_is_expected_set(cmd)) {
+			/*
+			 * Command data length can't be easily
+			 * determined from the CDB. ToDo, all such
+			 * commands processing should be fixed. Until
+			 * it's done, get the length from the supplied
+			 * expected value, but limit it to some
+			 * reasonable value (15MB).
+			 */
+			cmd->bufflen = min(cmd->expected_transfer_len,
+						15*1024*1024);
+			if (cmd->data_direction == SCST_DATA_BIDI)
+				cmd->out_bufflen = min(cmd->expected_out_transfer_len,
+							15*1024*1024);
+			cmd->op_flags &= ~SCST_UNKNOWN_LENGTH;
+		} else {
+			PRINT_ERROR("Unknown data transfer length for opcode "
+				"0x%x (handler %s, target %s)", cmd->cdb[0],
+				dev->handler->name, cmd->tgtt->name);
+			PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_invalid_message));
+			goto out_done;
+		}
+	}
+
+	if (unlikely(cmd->cdb[cmd->cdb_len - 1] & CONTROL_BYTE_NACA_BIT)) {
+		PRINT_ERROR("NACA bit in control byte CDB is not supported "
+			    "(opcode 0x%02x)", cmd->cdb[0]);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_done;
+	}
+
+	if (unlikely(cmd->cdb[cmd->cdb_len - 1] & CONTROL_BYTE_LINK_BIT)) {
+		PRINT_ERROR("Linked commands are not supported "
+			    "(opcode 0x%02x)", cmd->cdb[0]);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_done;
+	}
+
+	if (cmd->dh_data_buf_alloced &&
+	    unlikely((orig_bufflen > cmd->bufflen))) {
+		PRINT_ERROR("Dev handler supplied data buffer (size %d), "
+			"is less, than required (size %d)", cmd->bufflen,
+			orig_bufflen);
+		PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
+		goto out_hw_error;
+	}
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	if ((cmd->bufflen != 0) &&
+	    ((cmd->data_direction == SCST_DATA_NONE) ||
+	     ((cmd->sg == NULL) && (state > SCST_CMD_STATE_PREPARE_SPACE)))) {
+		PRINT_ERROR("Dev handler %s parse() returned "
+			"invalid cmd data_direction %d, bufflen %d, state %d "
+			"or sg %p (opcode 0x%x)", dev->handler->name,
+			cmd->data_direction, cmd->bufflen, state, cmd->sg,
+			cmd->cdb[0]);
+		PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
+		goto out_hw_error;
+	}
+#endif
+
+	if (scst_cmd_is_expected_set(cmd)) {
+#ifdef CONFIG_SCST_USE_EXPECTED_VALUES
+		if (unlikely((cmd->data_direction != cmd->expected_data_direction) ||
+			     (cmd->bufflen != cmd->expected_transfer_len) ||
+			     (cmd->out_bufflen != cmd->expected_out_transfer_len))) {
+			TRACE(TRACE_MINOR, "Expected values don't match "
+				"decoded ones: data_direction %d, "
+				"expected_data_direction %d, "
+				"bufflen %d, expected_transfer_len %d, "
+				"out_bufflen %d, expected_out_transfer_len %d",
+				cmd->data_direction,
+				cmd->expected_data_direction,
+				cmd->bufflen, cmd->expected_transfer_len,
+				cmd->out_bufflen, cmd->expected_out_transfer_len);
+			PRINT_BUFF_FLAG(TRACE_MINOR, "Suspicious CDB",
+				cmd->cdb, cmd->cdb_len);
+			cmd->data_direction = cmd->expected_data_direction;
+			cmd->bufflen = cmd->expected_transfer_len;
+			cmd->out_bufflen = cmd->expected_out_transfer_len;
+			cmd->resid_possible = 1;
+		}
+#else
+		if (unlikely(cmd->data_direction !=
+				cmd->expected_data_direction)) {
+			if (((cmd->expected_data_direction != SCST_DATA_NONE) ||
+			     (cmd->bufflen != 0)) &&
+			    !scst_is_allowed_to_mismatch_cmd(cmd)) {
+				PRINT_ERROR("Expected data direction %d for "
+					"opcode 0x%02x (handler %s, target %s) "
+					"doesn't match decoded value %d",
+					cmd->expected_data_direction,
+					cmd->cdb[0], dev->handler->name,
+					cmd->tgtt->name, cmd->data_direction);
+				PRINT_BUFFER("Failed CDB", cmd->cdb,
+					cmd->cdb_len);
+				scst_set_cmd_error(cmd,
+				   SCST_LOAD_SENSE(scst_sense_invalid_message));
+				goto out_done;
+			}
+		}
+		if (unlikely(cmd->bufflen != cmd->expected_transfer_len)) {
+			TRACE(TRACE_MINOR, "Warning: expected "
+				"transfer length %d for opcode 0x%02x "
+				"(handler %s, target %s) doesn't match "
+				"decoded value %d. Faulty initiator "
+				"(e.g. VMware is known to be such) or "
+				"scst_scsi_op_table should be updated?",
+				cmd->expected_transfer_len, cmd->cdb[0],
+				dev->handler->name, cmd->tgtt->name,
+				cmd->bufflen);
+			PRINT_BUFF_FLAG(TRACE_MINOR, "Suspicious CDB",
+				cmd->cdb, cmd->cdb_len);
+			if ((cmd->data_direction & SCST_DATA_READ) ||
+			    (cmd->data_direction & SCST_DATA_WRITE))
+				cmd->resid_possible = 1;
+		}
+		if (unlikely(cmd->out_bufflen != cmd->expected_out_transfer_len)) {
+			TRACE(TRACE_MINOR, "Warning: expected bidirectional OUT "
+				"transfer length %d for opcode 0x%02x "
+				"(handler %s, target %s) doesn't match "
+				"decoded value %d. Faulty initiator "
+				"(e.g. VMware is known to be such) or "
+				"scst_scsi_op_table should be updated?",
+				cmd->expected_out_transfer_len, cmd->cdb[0],
+				dev->handler->name, cmd->tgtt->name,
+				cmd->out_bufflen);
+			PRINT_BUFF_FLAG(TRACE_MINOR, "Suspicious CDB",
+				cmd->cdb, cmd->cdb_len);
+			cmd->resid_possible = 1;
+		}
+#endif
+	}
+
+	if (unlikely(cmd->data_direction == SCST_DATA_UNKNOWN)) {
+		PRINT_ERROR("Unknown data direction. Opcode 0x%x, handler %s, "
+			"target %s", cmd->cdb[0], dev->handler->name,
+			cmd->tgtt->name);
+		PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
+		goto out_hw_error;
+	}
+
+set_res:
+	if (cmd->data_len == -1)
+		cmd->data_len = cmd->bufflen;
+
+	if (cmd->bufflen == 0) {
+		/*
+		 * According to SPC bufflen 0 for data transfer commands isn't
+		 * an error, so we need to fix the transfer direction.
+		 */
+		cmd->data_direction = SCST_DATA_NONE;
+	}
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	switch (state) {
+	case SCST_CMD_STATE_PREPARE_SPACE:
+	case SCST_CMD_STATE_PRE_PARSE:
+	case SCST_CMD_STATE_DEV_PARSE:
+	case SCST_CMD_STATE_RDY_TO_XFER:
+	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_PRE_DEV_DONE:
+	case SCST_CMD_STATE_DEV_DONE:
+	case SCST_CMD_STATE_PRE_XMIT_RESP:
+	case SCST_CMD_STATE_XMIT_RESP:
+	case SCST_CMD_STATE_FINISHED:
+	case SCST_CMD_STATE_FINISHED_INTERNAL:
+#endif
+		cmd->state = state;
+		res = SCST_CMD_STATE_RES_CONT_SAME;
+#ifdef CONFIG_SCST_EXTRACHECKS
+		break;
+
+	default:
+		if (state >= 0) {
+			PRINT_ERROR("Dev handler %s parse() returned "
+			     "invalid cmd state %d (opcode %d)",
+			     dev->handler->name, state, cmd->cdb[0]);
+		} else {
+			PRINT_ERROR("Dev handler %s parse() returned "
+				"error %d (opcode %d)", dev->handler->name,
+				state, cmd->cdb[0]);
+		}
+		goto out_hw_error;
+	}
+#endif
+
+	if (cmd->resp_data_len == -1) {
+		if (cmd->data_direction & SCST_DATA_READ)
+			cmd->resp_data_len = cmd->bufflen;
+		else
+			 cmd->resp_data_len = 0;
+	}
+
+	/* We already completed (with an error) */
+	if (unlikely(cmd->completed))
+		goto out_done;
+
+out:
+	return res;
+
+out_hw_error:
+	/* dev_done() will be called as part of the regular cmd's finish */
+	scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+
+out_done:
+	scst_set_cmd_abnormal_done_state(cmd);
+	res = SCST_CMD_STATE_RES_CONT_SAME;
+	goto out;
+}
+
+static void scst_set_write_len(struct scst_cmd *cmd)
+{
+
+	EXTRACHECKS_BUG_ON(!(cmd->data_direction & SCST_DATA_WRITE));
+
+	if (cmd->data_direction & SCST_DATA_READ) {
+		cmd->write_len = cmd->out_bufflen;
+		cmd->write_sg = &cmd->out_sg;
+		cmd->write_sg_cnt = &cmd->out_sg_cnt;
+	} else {
+		cmd->write_len = cmd->bufflen;
+		/* write_sg and write_sg_cnt already initialized correctly */
+	}
+
+	TRACE_MEM("cmd %p, write_len %d, write_sg %p, write_sg_cnt %d, "
+		"resid_possible %d", cmd, cmd->write_len, *cmd->write_sg,
+		*cmd->write_sg_cnt, cmd->resid_possible);
+
+	if (unlikely(cmd->resid_possible)) {
+		if (cmd->data_direction & SCST_DATA_READ) {
+			cmd->write_len = min(cmd->out_bufflen,
+				cmd->expected_out_transfer_len);
+			if (cmd->write_len == cmd->out_bufflen)
+				goto out;
+		} else {
+			cmd->write_len = min(cmd->bufflen,
+				cmd->expected_transfer_len);
+			if (cmd->write_len == cmd->bufflen)
+				goto out;
+		}
+		scst_limit_sg_write_len(cmd);
+	}
+
+out:
+	return;
+}
+
+static int scst_prepare_space(struct scst_cmd *cmd)
+{
+	int r = 0, res = SCST_CMD_STATE_RES_CONT_SAME;
+	struct scst_device *dev = cmd->dev;
+
+	if (cmd->data_direction == SCST_DATA_NONE)
+		goto done;
+
+	if (likely(!scst_is_cmd_fully_local(cmd)) &&
+	    (dev->handler->alloc_data_buf != NULL)) {
+		int state;
+
+		if (unlikely(!dev->handler->alloc_data_buf_atomic &&
+			     scst_cmd_atomic(cmd))) {
+			/*
+			 * It shouldn't be because of the SCST_TGT_DEV_AFTER_*
+			 * optimization.
+			 */
+			TRACE_DBG("Dev handler %s alloc_data_buf() needs "
+				"thread context, rescheduling",
+				dev->handler->name);
+			res = SCST_CMD_STATE_RES_NEED_THREAD;
+			goto out;
+		}
+
+		TRACE_DBG("Calling dev handler %s alloc_data_buf(%p)",
+		      dev->handler->name, cmd);
+		scst_set_cur_start(cmd);
+		state = dev->handler->alloc_data_buf(cmd);
+		/* Caution: cmd can be already dead here */
+		TRACE_DBG("Dev handler %s alloc_data_buf() returned %d",
+			dev->handler->name, state);
+
+		switch (state) {
+		case SCST_CMD_STATE_NEED_THREAD_CTX:
+			scst_set_alloc_buf_time(cmd);
+			TRACE_DBG("Dev handler %s alloc_data_buf() requested "
+				"thread context, rescheduling",
+				dev->handler->name);
+			res = SCST_CMD_STATE_RES_NEED_THREAD;
+			goto out;
+
+		case SCST_CMD_STATE_STOP:
+			TRACE_DBG("Dev handler %s alloc_data_buf() requested "
+				"stop processing", dev->handler->name);
+			res = SCST_CMD_STATE_RES_CONT_NEXT;
+			goto out;
+		}
+
+		scst_set_alloc_buf_time(cmd);
+
+		if (unlikely(state != SCST_CMD_STATE_DEFAULT)) {
+			cmd->state = state;
+			goto out;
+		}
+	}
+
+	if (cmd->tgt_need_alloc_data_buf) {
+		int orig_bufflen = cmd->bufflen;
+
+		TRACE_MEM("Custom tgt data buf allocation requested (cmd %p)",
+			cmd);
+
+		scst_set_cur_start(cmd);
+		r = cmd->tgtt->alloc_data_buf(cmd);
+		scst_set_alloc_buf_time(cmd);
+
+		if (r > 0)
+			goto alloc;
+		else if (r == 0) {
+			if (unlikely(cmd->bufflen == 0)) {
+				/* See comment in scst_alloc_space() */
+				if (cmd->sg == NULL)
+					goto alloc;
+			}
+
+			cmd->tgt_data_buf_alloced = 1;
+
+			if (unlikely(orig_bufflen < cmd->bufflen)) {
+				PRINT_ERROR("Target driver allocated data "
+					"buffer (size %d), is less, than "
+					"required (size %d)", orig_bufflen,
+					cmd->bufflen);
+				goto out_error;
+			}
+			TRACE_MEM("tgt_data_buf_alloced (cmd %p)", cmd);
+		} else
+			goto check;
+	}
+
+alloc:
+	if (!cmd->tgt_data_buf_alloced && !cmd->dh_data_buf_alloced) {
+		r = scst_alloc_space(cmd);
+	} else if (cmd->dh_data_buf_alloced && !cmd->tgt_data_buf_alloced) {
+		TRACE_MEM("dh_data_buf_alloced set (cmd %p)", cmd);
+		r = 0;
+	} else if (cmd->tgt_data_buf_alloced && !cmd->dh_data_buf_alloced) {
+		TRACE_MEM("tgt_data_buf_alloced set (cmd %p)", cmd);
+		cmd->sg = cmd->tgt_sg;
+		cmd->sg_cnt = cmd->tgt_sg_cnt;
+		cmd->out_sg = cmd->tgt_out_sg;
+		cmd->out_sg_cnt = cmd->tgt_out_sg_cnt;
+		r = 0;
+	} else {
+		TRACE_MEM("Both *_data_buf_alloced set (cmd %p, sg %p, "
+			"sg_cnt %d, tgt_sg %p, tgt_sg_cnt %d)", cmd, cmd->sg,
+			cmd->sg_cnt, cmd->tgt_sg, cmd->tgt_sg_cnt);
+		r = 0;
+	}
+
+check:
+	if (r != 0) {
+		if (scst_cmd_atomic(cmd)) {
+			TRACE_MEM("%s", "Atomic memory allocation failed, "
+			      "rescheduling to the thread");
+			res = SCST_CMD_STATE_RES_NEED_THREAD;
+			goto out;
+		} else
+			goto out_no_space;
+	}
+
+done:
+	if (cmd->preprocessing_only) {
+		cmd->state = SCST_CMD_STATE_PREPROCESSING_DONE;
+		if (cmd->data_direction & SCST_DATA_WRITE)
+			scst_set_write_len(cmd);
+	} else if (cmd->data_direction & SCST_DATA_WRITE) {
+		cmd->state = SCST_CMD_STATE_RDY_TO_XFER;
+		scst_set_write_len(cmd);
+	} else
+		cmd->state = SCST_CMD_STATE_TGT_PRE_EXEC;
+
+out:
+	return res;
+
+out_no_space:
+	TRACE(TRACE_OUT_OF_MEM, "Unable to allocate or build requested buffer "
+		"(size %d), sending BUSY or QUEUE FULL status", cmd->bufflen);
+	scst_set_busy(cmd);
+	scst_set_cmd_abnormal_done_state(cmd);
+	res = SCST_CMD_STATE_RES_CONT_SAME;
+	goto out;
+
+out_error:
+	scst_set_cmd_error(cmd,	SCST_LOAD_SENSE(scst_sense_hardw_error));
+	scst_set_cmd_abnormal_done_state(cmd);
+	res = SCST_CMD_STATE_RES_CONT_SAME;
+	goto out;
+}
+
+static int scst_preprocessing_done(struct scst_cmd *cmd)
+{
+	int res;
+
+	EXTRACHECKS_BUG_ON(!cmd->preprocessing_only);
+
+	cmd->preprocessing_only = 0;
+
+	res = SCST_CMD_STATE_RES_CONT_NEXT;
+	cmd->state = SCST_CMD_STATE_PREPROCESSING_DONE_CALLED;
+
+	TRACE_DBG("Calling preprocessing_done(cmd %p)", cmd);
+	scst_set_cur_start(cmd);
+	cmd->tgtt->preprocessing_done(cmd);
+	TRACE_DBG("%s", "preprocessing_done() returned");
+	return res;
+}
+
+/**
+ * scst_restart_cmd() - restart execution of the command
+ * @cmd:	SCST commands
+ * @status:	completion status
+ * @pref_context: preferred command execition context
+ *
+ * Description:
+ *    Notifies SCST that the driver finished its part of the command's
+ *    preprocessing and it is ready for further processing.
+ *
+ *    The second argument sets completion status
+ *    (see SCST_PREPROCESS_STATUS_* constants for details)
+ *
+ *    See also comment for scst_cmd_init_done() for the serialization
+ *    requirements.
+ */
+void scst_restart_cmd(struct scst_cmd *cmd, int status,
+	enum scst_exec_context pref_context)
+{
+
+	scst_set_restart_waiting_time(cmd);
+
+	TRACE_DBG("Preferred context: %d", pref_context);
+	TRACE_DBG("tag=%llu, status=%#x",
+		  (long long unsigned int)scst_cmd_get_tag(cmd),
+		  status);
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	if ((in_irq() || irqs_disabled()) &&
+	    ((pref_context == SCST_CONTEXT_DIRECT) ||
+	     (pref_context == SCST_CONTEXT_DIRECT_ATOMIC))) {
+		PRINT_ERROR("Wrong context %d in IRQ from target %s, use "
+			"SCST_CONTEXT_THREAD instead", pref_context,
+			cmd->tgtt->name);
+		pref_context = SCST_CONTEXT_THREAD;
+	}
+#endif
+
+	switch (status) {
+	case SCST_PREPROCESS_STATUS_SUCCESS:
+		if (cmd->data_direction & SCST_DATA_WRITE)
+			cmd->state = SCST_CMD_STATE_RDY_TO_XFER;
+		else
+			cmd->state = SCST_CMD_STATE_TGT_PRE_EXEC;
+		if (cmd->set_sn_on_restart_cmd)
+			scst_cmd_set_sn(cmd);
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+		if (cmd->op_flags & SCST_TEST_IO_IN_SIRQ_ALLOWED)
+			break;
+#endif
+		/* Small context optimization */
+		if ((pref_context == SCST_CONTEXT_TASKLET) ||
+		    (pref_context == SCST_CONTEXT_DIRECT_ATOMIC) ||
+		    ((pref_context == SCST_CONTEXT_SAME) &&
+		     scst_cmd_atomic(cmd)))
+			pref_context = SCST_CONTEXT_THREAD;
+		break;
+
+	case SCST_PREPROCESS_STATUS_ERROR_SENSE_SET:
+		scst_set_cmd_abnormal_done_state(cmd);
+		break;
+
+	case SCST_PREPROCESS_STATUS_ERROR_FATAL:
+		set_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags);
+		/* go through */
+	case SCST_PREPROCESS_STATUS_ERROR:
+		if (cmd->sense != NULL)
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+		scst_set_cmd_abnormal_done_state(cmd);
+		break;
+
+	default:
+		PRINT_ERROR("%s() received unknown status %x", __func__,
+			status);
+		scst_set_cmd_abnormal_done_state(cmd);
+		break;
+	}
+
+	scst_process_redirect_cmd(cmd, pref_context, 1);
+	return;
+}
+EXPORT_SYMBOL(scst_restart_cmd);
+
+static int scst_rdy_to_xfer(struct scst_cmd *cmd)
+{
+	int res, rc;
+	struct scst_tgt_template *tgtt = cmd->tgtt;
+
+	if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) {
+		TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd);
+		goto out_dev_done;
+	}
+
+	if ((tgtt->rdy_to_xfer == NULL) || unlikely(cmd->internal)) {
+		cmd->state = SCST_CMD_STATE_TGT_PRE_EXEC;
+		res = SCST_CMD_STATE_RES_CONT_SAME;
+		goto out;
+	}
+
+	if (unlikely(!tgtt->rdy_to_xfer_atomic && scst_cmd_atomic(cmd))) {
+		/*
+		 * It shouldn't be because of the SCST_TGT_DEV_AFTER_*
+		 * optimization.
+		 */
+		TRACE_DBG("Target driver %s rdy_to_xfer() needs thread "
+			      "context, rescheduling", tgtt->name);
+		res = SCST_CMD_STATE_RES_NEED_THREAD;
+		goto out;
+	}
+
+	while (1) {
+		int finished_cmds = atomic_read(&cmd->tgt->finished_cmds);
+
+		res = SCST_CMD_STATE_RES_CONT_NEXT;
+		cmd->state = SCST_CMD_STATE_DATA_WAIT;
+
+		if (tgtt->on_hw_pending_cmd_timeout != NULL) {
+			struct scst_session *sess = cmd->sess;
+			cmd->hw_pending_start = jiffies;
+			cmd->cmd_hw_pending = 1;
+			if (!test_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags)) {
+				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);
+			}
+		}
+
+		scst_set_cur_start(cmd);
+
+		TRACE_DBG("Calling rdy_to_xfer(%p)", cmd);
+#ifdef CONFIG_SCST_DEBUG_RETRY
+		if (((scst_random() % 100) == 75))
+			rc = SCST_TGT_RES_QUEUE_FULL;
+		else
+#endif
+			rc = tgtt->rdy_to_xfer(cmd);
+		TRACE_DBG("rdy_to_xfer() returned %d", rc);
+
+		if (likely(rc == SCST_TGT_RES_SUCCESS))
+			goto out;
+
+		scst_set_rdy_to_xfer_time(cmd);
+
+		cmd->cmd_hw_pending = 0;
+
+		/* Restore the previous state */
+		cmd->state = SCST_CMD_STATE_RDY_TO_XFER;
+
+		switch (rc) {
+		case SCST_TGT_RES_QUEUE_FULL:
+			if (scst_queue_retry_cmd(cmd, finished_cmds) == 0)
+				break;
+			else
+				continue;
+
+		case SCST_TGT_RES_NEED_THREAD_CTX:
+			TRACE_DBG("Target driver %s "
+			      "rdy_to_xfer() requested thread "
+			      "context, rescheduling", tgtt->name);
+			res = SCST_CMD_STATE_RES_NEED_THREAD;
+			break;
+
+		default:
+			goto out_error_rc;
+		}
+		break;
+	}
+
+out:
+	return res;
+
+out_error_rc:
+	if (rc == SCST_TGT_RES_FATAL_ERROR) {
+		PRINT_ERROR("Target driver %s rdy_to_xfer() returned "
+		     "fatal error", tgtt->name);
+	} else {
+		PRINT_ERROR("Target driver %s rdy_to_xfer() returned invalid "
+			    "value %d", tgtt->name, rc);
+	}
+	scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+
+out_dev_done:
+	scst_set_cmd_abnormal_done_state(cmd);
+	res = SCST_CMD_STATE_RES_CONT_SAME;
+	goto out;
+}
+
+/* No locks, but might be in IRQ */
+static void scst_process_redirect_cmd(struct scst_cmd *cmd,
+	enum scst_exec_context context, int check_retries)
+{
+	struct scst_tgt *tgt = cmd->tgt;
+	unsigned long flags;
+
+	TRACE_DBG("Context: %x", context);
+
+	if (context == SCST_CONTEXT_SAME)
+		context = scst_cmd_atomic(cmd) ? SCST_CONTEXT_DIRECT_ATOMIC :
+						 SCST_CONTEXT_DIRECT;
+
+	switch (context) {
+	case SCST_CONTEXT_DIRECT_ATOMIC:
+		scst_process_active_cmd(cmd, true);
+		break;
+
+	case SCST_CONTEXT_DIRECT:
+		if (check_retries)
+			scst_check_retries(tgt);
+		scst_process_active_cmd(cmd, false);
+		break;
+
+	default:
+		PRINT_ERROR("Context %x is unknown, using the thread one",
+			    context);
+		/* go through */
+	case SCST_CONTEXT_THREAD:
+		if (check_retries)
+			scst_check_retries(tgt);
+		spin_lock_irqsave(&cmd->cmd_threads->cmd_list_lock, flags);
+		TRACE_DBG("Adding cmd %p to active cmd list", cmd);
+		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_irqrestore(&cmd->cmd_threads->cmd_list_lock, flags);
+		break;
+
+	case SCST_CONTEXT_TASKLET:
+		if (check_retries)
+			scst_check_retries(tgt);
+		scst_schedule_tasklet(cmd);
+		break;
+	}
+	return;
+}
+
+/**
+ * scst_rx_data() - the command's data received
+ * @cmd:	SCST commands
+ * @status:	data receiving completion status
+ * @pref_context: preferred command execution context
+ *
+ * Description:
+ *    Notifies SCST that the driver received all the necessary data
+ *    and the command is ready for further processing.
+ *
+ *    The second argument sets data receiving completion status
+ *    (see SCST_RX_STATUS_* constants for details)
+ */
+void scst_rx_data(struct scst_cmd *cmd, int status,
+	enum scst_exec_context pref_context)
+{
+
+	scst_set_rdy_to_xfer_time(cmd);
+
+	TRACE_DBG("Preferred context: %d", pref_context);
+	TRACE(TRACE_SCSI, "cmd %p, status %#x", cmd, status);
+
+	cmd->cmd_hw_pending = 0;
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	if ((in_irq() || irqs_disabled()) &&
+	    ((pref_context == SCST_CONTEXT_DIRECT) ||
+	     (pref_context == SCST_CONTEXT_DIRECT_ATOMIC))) {
+		PRINT_ERROR("Wrong context %d in IRQ from target %s, use "
+			"SCST_CONTEXT_THREAD instead", pref_context,
+			cmd->tgtt->name);
+		pref_context = SCST_CONTEXT_THREAD;
+	}
+#endif
+
+	switch (status) {
+	case SCST_RX_STATUS_SUCCESS:
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+		if (trace_flag & TRACE_RCV_BOT) {
+			int i, j;
+			struct scatterlist *sg;
+			if (cmd->out_sg != NULL)
+				sg = cmd->out_sg;
+			else if (cmd->tgt_out_sg != NULL)
+				sg = cmd->tgt_out_sg;
+			else if (cmd->tgt_sg != NULL)
+				sg = cmd->tgt_sg;
+			else
+				sg = cmd->sg;
+			if (sg != NULL) {
+				TRACE_RECV_BOT("RX data for cmd %p "
+					"(sg_cnt %d, sg %p, sg[0].page %p)",
+					cmd, cmd->tgt_sg_cnt, sg,
+					(void *)sg_page(&sg[0]));
+				for (i = 0, j = 0; i < cmd->tgt_sg_cnt; ++i, ++j) {
+					if (unlikely(sg_is_chain(&sg[j]))) {
+						sg = sg_chain_ptr(&sg[j]);
+						j = 0;
+					}
+					PRINT_BUFF_FLAG(TRACE_RCV_BOT, "RX sg",
+						sg_virt(&sg[j]), sg[j].length);
+				}
+			}
+		}
+#endif
+		cmd->state = SCST_CMD_STATE_TGT_PRE_EXEC;
+
+#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
+		if (cmd->op_flags & SCST_TEST_IO_IN_SIRQ_ALLOWED)
+			break;
+#endif
+
+		/* Small context optimization */
+		if ((pref_context == SCST_CONTEXT_TASKLET) ||
+		    (pref_context == SCST_CONTEXT_DIRECT_ATOMIC) ||
+		    ((pref_context == SCST_CONTEXT_SAME) &&
+		     scst_cmd_atomic(cmd)))
+			pref_context = SCST_CONTEXT_THREAD;
+		break;
+
+	case SCST_RX_STATUS_ERROR_SENSE_SET:
+		scst_set_cmd_abnormal_done_state(cmd);
+		break;
+
+	case SCST_RX_STATUS_ERROR_FATAL:
+		set_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags);
+		/* go through */
+	case SCST_RX_STATUS_ERROR:
+		scst_set_cmd_error(cmd,
+			   SCST_LOAD_SENSE(scst_sense_hardw_error));
+		scst_set_cmd_abnormal_done_state(cmd);
+		break;
+
+	default:
+		PRINT_ERROR("scst_rx_data() received unknown status %x",
+			status);
+		scst_set_cmd_abnormal_done_state(cmd);
+		break;
+	}
+
+	scst_process_redirect_cmd(cmd, pref_context, 1);
+	return;
+}
+EXPORT_SYMBOL(scst_rx_data);
+
+static int scst_tgt_pre_exec(struct scst_cmd *cmd)
+{
+	int res = SCST_CMD_STATE_RES_CONT_SAME, rc;
+
+	if (unlikely(cmd->resid_possible)) {
+		if (cmd->data_direction & SCST_DATA_WRITE) {
+			bool do_zero = false;
+			if (cmd->data_direction & SCST_DATA_READ) {
+				if (cmd->write_len != cmd->out_bufflen)
+					do_zero = true;
+			} else {
+				if (cmd->write_len != cmd->bufflen)
+					do_zero = true;
+			}
+			if (do_zero) {
+				scst_check_restore_sg_buff(cmd);
+				scst_zero_write_rest(cmd);
+			}
+		}
+	}
+
+	cmd->state = SCST_CMD_STATE_SEND_FOR_EXEC;
+
+	if ((cmd->tgtt->pre_exec == NULL) || unlikely(cmd->internal))
+		goto out;
+
+	TRACE_DBG("Calling pre_exec(%p)", cmd);
+	scst_set_cur_start(cmd);
+	rc = cmd->tgtt->pre_exec(cmd);
+	scst_set_pre_exec_time(cmd);
+	TRACE_DBG("pre_exec() returned %d", rc);
+
+	if (unlikely(rc != SCST_PREPROCESS_STATUS_SUCCESS)) {
+		switch (rc) {
+		case SCST_PREPROCESS_STATUS_ERROR_SENSE_SET:
+			scst_set_cmd_abnormal_done_state(cmd);
+			break;
+		case SCST_PREPROCESS_STATUS_ERROR_FATAL:
+			set_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags);
+			/* go through */
+		case SCST_PREPROCESS_STATUS_ERROR:
+			scst_set_cmd_error(cmd,
+				   SCST_LOAD_SENSE(scst_sense_hardw_error));
+			scst_set_cmd_abnormal_done_state(cmd);
+			break;
+		default:
+			BUG();
+			break;
+		}
+	}
+
+out:
+	return res;
+}
+
+static void scst_do_cmd_done(struct scst_cmd *cmd, int result,
+	const uint8_t *rq_sense, int rq_sense_len, int resid)
+{
+
+	scst_set_exec_time(cmd);
+
+	cmd->status = result & 0xff;
+	cmd->msg_status = msg_byte(result);
+	cmd->host_status = host_byte(result);
+	cmd->driver_status = driver_byte(result);
+	if (unlikely(resid != 0)) {
+		if ((cmd->data_direction & SCST_DATA_READ) &&
+		    (resid > 0) && (resid < cmd->resp_data_len))
+			scst_set_resp_data_len(cmd, cmd->resp_data_len - resid);
+		/*
+		 * We ignore write direction residue, because from the
+		 * initiator's POV we already transferred all the data.
+		 */
+	}
+
+	if (unlikely(cmd->status == SAM_STAT_CHECK_CONDITION)) {
+		/* We might have double reset UA here */
+		cmd->dbl_ua_orig_resp_data_len = cmd->resp_data_len;
+		cmd->dbl_ua_orig_data_direction = cmd->data_direction;
+
+		scst_alloc_set_sense(cmd, 1, rq_sense, rq_sense_len);
+	}
+
+	TRACE(TRACE_SCSI, "cmd %p, result %x, cmd->status %x, resid %d, "
+	      "cmd->msg_status %x, cmd->host_status %x, "
+	      "cmd->driver_status %x", cmd, result, cmd->status, resid,
+	      cmd->msg_status, cmd->host_status, cmd->driver_status);
+
+	cmd->completed = 1;
+	return;
+}
+
+/* For small context optimization */
+static inline enum scst_exec_context scst_optimize_post_exec_context(
+	struct scst_cmd *cmd, enum scst_exec_context context)
+{
+	if (((context == SCST_CONTEXT_SAME) && scst_cmd_atomic(cmd)) ||
+	    (context == SCST_CONTEXT_TASKLET) ||
+	    (context == SCST_CONTEXT_DIRECT_ATOMIC)) {
+		if (!test_bit(SCST_TGT_DEV_AFTER_EXEC_ATOMIC,
+				&cmd->tgt_dev->tgt_dev_flags))
+			context = SCST_CONTEXT_THREAD;
+	}
+	return context;
+}
+
+/**
+ * scst_pass_through_cmd_done - done callback for pass-through commands
+ * @data:	private opaque data
+ * @sense:	pointer to the sense data, if any
+ * @result:	command's execution result
+ * @resid:	residual, if any
+ */
+void scst_pass_through_cmd_done(void *data, char *sense, int result, int resid)
+{
+	struct scst_cmd *cmd;
+
+	cmd = (struct scst_cmd *)data;
+	if (cmd == NULL)
+		goto out;
+
+	scst_do_cmd_done(cmd, result, sense, SCSI_SENSE_BUFFERSIZE, resid);
+
+	cmd->state = SCST_CMD_STATE_PRE_DEV_DONE;
+
+	scst_process_redirect_cmd(cmd,
+	    scst_optimize_post_exec_context(cmd, scst_estimate_context()), 0);
+
+out:
+	return;
+}
+EXPORT_SYMBOL_GPL(scst_pass_through_cmd_done);
+
+static void scst_cmd_done_local(struct scst_cmd *cmd, int next_state,
+	enum scst_exec_context pref_context)
+{
+
+	EXTRACHECKS_BUG_ON(cmd->pr_abort_counter != NULL);
+
+	scst_set_exec_time(cmd);
+
+	TRACE(TRACE_SCSI, "cmd %p, status %x, msg_status %x, host_status %x, "
+	      "driver_status %x, resp_data_len %d", cmd, cmd->status,
+	      cmd->msg_status, cmd->host_status, cmd->driver_status,
+	      cmd->resp_data_len);
+
+	if (next_state == SCST_CMD_STATE_DEFAULT)
+		next_state = SCST_CMD_STATE_PRE_DEV_DONE;
+
+#if defined(CONFIG_SCST_DEBUG)
+	if (next_state == SCST_CMD_STATE_PRE_DEV_DONE) {
+		if ((trace_flag & TRACE_RCV_TOP) && (cmd->sg != NULL)) {
+			int i, j;
+			struct scatterlist *sg = cmd->sg;
+			TRACE_RECV_TOP("Exec'd %d S/G(s) at %p sg[0].page at "
+				"%p", cmd->sg_cnt, sg, (void *)sg_page(&sg[0]));
+			for (i = 0, j = 0; i < cmd->sg_cnt; ++i, ++j) {
+				if (unlikely(sg_is_chain(&sg[j]))) {
+					sg = sg_chain_ptr(&sg[j]);
+					j = 0;
+				}
+				TRACE_BUFF_FLAG(TRACE_RCV_TOP,
+					"Exec'd sg", sg_virt(&sg[j]),
+					sg[j].length);
+			}
+		}
+	}
+#endif
+
+	cmd->state = next_state;
+
+#ifdef CONFIG_SCST_EXTRACHECKS
+	if ((next_state != SCST_CMD_STATE_PRE_DEV_DONE) &&
+	    (next_state != SCST_CMD_STATE_PRE_XMIT_RESP) &&
+	    (next_state != SCST_CMD_STATE_FINISHED) &&
+	    (next_state != SCST_CMD_STATE_FINISHED_INTERNAL)) {
+		PRINT_ERROR("%s() received invalid cmd state %d (opcode %d)",
+			__func__, next_state, cmd->cdb[0]);
+		scst_set_cmd_error(cmd,
+				   SCST_LOAD_SENSE(scst_sense_hardw_error));
+		scst_set_cmd_abnormal_done_state(cmd);
+	}
+#endif
+	pref_context = scst_optimize_post_exec_context(cmd, pref_context);
+	scst_process_redirect_cmd(cmd, pref_context, 0);
+	return;
+}
+
+static int scst_report_luns_local(struct scst_cmd *cmd)
+{
+	int res = SCST_EXEC_COMPLETED, rc;
+	int dev_cnt = 0;
+	int buffer_size;
+	int i;
+	struct scst_tgt_dev *tgt_dev = NULL;
+	uint8_t *buffer;
+	int offs, overflow = 0;
+
+	rc = scst_check_local_events(cmd);
+	if (unlikely(rc != 0))
+		goto out_done;
+
+	cmd->status = 0;
+	cmd->msg_status = 0;
+	cmd->host_status = DID_OK;
+	cmd->driver_status = 0;
+
+	if ((cmd->cdb[2] != 0) && (cmd->cdb[2] != 2)) {
+		PRINT_ERROR("Unsupported SELECT REPORT value %x in REPORT "
+			"LUNS command", cmd->cdb[2]);
+		goto out_err;
+	}
+
+	buffer_size = scst_get_buf_first(cmd, &buffer);
+	if (unlikely(buffer_size == 0))
+		goto out_compl;
+	else if (unlikely(buffer_size < 0))
+		goto out_hw_err;
+
+	if (buffer_size < 16)
+		goto out_put_err;
+
+	memset(buffer, 0, buffer_size);
+	offs = 8;
+
+	/*
+	 * cmd won't allow to suspend activities, so we can access
+	 * sess->sess_tgt_dev_list without any additional protection.
+	 */
+	for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+		struct list_head *head = &cmd->sess->sess_tgt_dev_list[i];
+		list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
+			if (!overflow) {
+				if (offs >= buffer_size) {
+					scst_put_buf(cmd, buffer);
+					buffer_size = scst_get_buf_next(cmd,
+								       &buffer);
+					if (buffer_size > 0) {
+						memset(buffer, 0, buffer_size);
+						offs = 0;
+					} else {
+						overflow = 1;
+						goto inc_dev_cnt;
+					}
+				}
+				if ((buffer_size - offs) < 8) {
+					PRINT_ERROR("Buffer allocated for "
+						"REPORT LUNS command doesn't "
+						"allow to fit 8 byte entry "
+						"(buffer_size=%d)",
+						buffer_size);
+					goto out_put_hw_err;
+				}
+				if ((cmd->sess->acg->addr_method == SCST_LUN_ADDR_METHOD_FLAT) &&
+				    (tgt_dev->lun != 0)) {
+					buffer[offs] = (tgt_dev->lun >> 8) & 0x3f;
+					buffer[offs] = buffer[offs] | 0x40;
+					buffer[offs+1] = tgt_dev->lun & 0xff;
+				} else {
+					buffer[offs] = (tgt_dev->lun >> 8) & 0xff;
+					buffer[offs+1] = tgt_dev->lun & 0xff;
+				}
+				offs += 8;
+			}
+inc_dev_cnt:
+			dev_cnt++;
+		}
+	}
+	if (!overflow)
+		scst_put_buf(cmd, buffer);
+
+	/* Set the response header */
+	buffer_size = scst_get_buf_first(cmd, &buffer);
+	if (unlikely(buffer_size == 0))
+		goto out_compl;
+	else if (unlikely(buffer_size < 0))
+		goto out_hw_err;
+
+	dev_cnt *= 8;
+	buffer[0] = (dev_cnt >> 24) & 0xff;
+	buffer[1] = (dev_cnt >> 16) & 0xff;
+	buffer[2] = (dev_cnt >> 8) & 0xff;
+	buffer[3] = dev_cnt & 0xff;
+
+	scst_put_buf(cmd, buffer);
+
+	dev_cnt += 8;
+	if (dev_cnt < cmd->resp_data_len)
+		scst_set_resp_data_len(cmd, dev_cnt);
+
+out_compl:
+	cmd->completed = 1;
+
+	/* Clear left sense_reported_luns_data_changed UA, if any. */
+
+	/*
+	 * cmd won't allow to suspend activities, so we can access
+	 * sess->sess_tgt_dev_list without any additional protection.
+	 */
+	for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+		struct list_head *head = &cmd->sess->sess_tgt_dev_list[i];
+
+		list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
+			struct scst_tgt_dev_UA *ua;
+
+			spin_lock_bh(&tgt_dev->tgt_dev_lock);
+			list_for_each_entry(ua, &tgt_dev->UA_list,
+						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_reported_luns_data_changed))) {
+					TRACE_MGMT_DBG("Freeing not needed "
+						"REPORTED LUNS DATA CHANGED UA "
+						"%p", ua);
+					list_del(&ua->UA_list_entry);
+					mempool_free(ua, scst_ua_mempool);
+					break;
+				}
+			}
+			spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+		}
+	}
+
+out_done:
+	/* Report the result */
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+	return res;
+
+out_put_err:
+	scst_put_buf(cmd, buffer);
+
+out_err:
+	scst_set_cmd_error(cmd,
+		   SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+	goto out_compl;
+
+out_put_hw_err:
+	scst_put_buf(cmd, buffer);
+
+out_hw_err:
+	scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+	goto out_compl;
+}
+
+static int scst_request_sense_local(struct scst_cmd *cmd)
+{
+	int res = SCST_EXEC_COMPLETED, rc;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	uint8_t *buffer;
+	int buffer_size = 0, sl = 0;
+
+	rc = scst_check_local_events(cmd);
+	if (unlikely(rc != 0))
+		goto out_done;
+
+	cmd->status = 0;
+	cmd->msg_status = 0;
+	cmd->host_status = DID_OK;
+	cmd->driver_status = 0;
+
+	spin_lock_bh(&tgt_dev->tgt_dev_lock);
+
+	if (tgt_dev->tgt_dev_valid_sense_len == 0)
+		goto out_unlock_not_completed;
+
+	TRACE(TRACE_SCSI, "%s: Returning stored sense", cmd->op_name);
+
+	buffer_size = scst_get_buf_first(cmd, &buffer);
+	if (unlikely(buffer_size == 0))
+		goto out_unlock_compl;
+	else if (unlikely(buffer_size < 0))
+		goto out_unlock_hw_err;
+
+	memset(buffer, 0, buffer_size);
+
+	if (((tgt_dev->tgt_dev_sense[0] == 0x70) ||
+	     (tgt_dev->tgt_dev_sense[0] == 0x71)) && (cmd->cdb[1] & 1)) {
+		PRINT_WARNING("%s: Fixed format of the saved sense, but "
+			"descriptor format requested. Convertion will "
+			"truncated data", cmd->op_name);
+		PRINT_BUFFER("Original sense", tgt_dev->tgt_dev_sense,
+			tgt_dev->tgt_dev_valid_sense_len);
+
+		buffer_size = min(SCST_STANDARD_SENSE_LEN, buffer_size);
+		sl = scst_set_sense(buffer, buffer_size, true,
+			tgt_dev->tgt_dev_sense[2], tgt_dev->tgt_dev_sense[12],
+			tgt_dev->tgt_dev_sense[13]);
+	} else if (((tgt_dev->tgt_dev_sense[0] == 0x72) ||
+		    (tgt_dev->tgt_dev_sense[0] == 0x73)) && !(cmd->cdb[1] & 1)) {
+		PRINT_WARNING("%s: Descriptor format of the "
+			"saved sense, but fixed format requested. Convertion "
+			"will truncated data", cmd->op_name);
+		PRINT_BUFFER("Original sense", tgt_dev->tgt_dev_sense,
+			tgt_dev->tgt_dev_valid_sense_len);
+
+		buffer_size = min(SCST_STANDARD_SENSE_LEN, buffer_size);
+		sl = scst_set_sense(buffer, buffer_size, false,
+			tgt_dev->tgt_dev_sense[1], tgt_dev->tgt_dev_sense[2],
+			tgt_dev->tgt_dev_sense[3]);
+	} else {
+		if (buffer_size >= tgt_dev->tgt_dev_valid_sense_len)
+			sl = tgt_dev->tgt_dev_valid_sense_len;
+		else {
+			sl = buffer_size;
+			TRACE(TRACE_MINOR, "%s: Being returned sense truncated "
+				"to size %d (needed %d)", cmd->op_name,
+				buffer_size, tgt_dev->tgt_dev_valid_sense_len);
+		}
+		memcpy(buffer, tgt_dev->tgt_dev_sense, sl);
+	}
+
+	scst_put_buf(cmd, buffer);
+
+	tgt_dev->tgt_dev_valid_sense_len = 0;
+
+	spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+
+	scst_set_resp_data_len(cmd, sl);
+
+out_compl:
+	cmd->completed = 1;
+
+out_done:
+	/* Report the result */
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+
+out:
+	return res;
+
+out_unlock_hw_err:
+	spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+	scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+	goto out_compl;
+
+out_unlock_not_completed:
+	spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+	res = SCST_EXEC_NOT_COMPLETED;
+	goto out;
+
+out_unlock_compl:
+	spin_unlock_bh(&tgt_dev->tgt_dev_lock);
+	goto out_compl;
+}
+
+static int scst_reserve_local(struct scst_cmd *cmd)
+{
+	int res = SCST_EXEC_NOT_COMPLETED, rc;
+	struct scst_device *dev;
+	struct scst_tgt_dev *tgt_dev_tmp;
+
+	if ((cmd->cdb[0] == RESERVE_10) && (cmd->cdb[2] & SCST_RES_3RDPTY)) {
+		PRINT_ERROR("RESERVE_10: 3rdPty RESERVE not implemented "
+		     "(lun=%lld)", (long long unsigned int)cmd->lun);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_done;
+	}
+
+	dev = cmd->dev;
+
+	/*
+	 * There's no need to block this device, even for
+	 * SCST_CONTR_MODE_ONE_TASK_SET, or anyhow else protect reservations
+	 * changes, because:
+	 *
+	 * 1. The reservation changes are (rather) atomic, i.e., in contrast
+	 *    to persistent reservations, don't have any invalid intermediate
+	 *    states during being changed.
+	 *
+	 * 2. It's a duty of initiators to ensure order of regular commands
+	 *    around the reservation command either by ORDERED attribute, or by
+	 *    queue draining, or etc. For case of SCST_CONTR_MODE_ONE_TASK_SET
+	 *    there are no target drivers which can ensure even for ORDERED
+	 *    comamnds order of their delivery, so, because initiators know
+	 *    it, also there's no point to do any extra protection actions.
+	 */
+
+	rc = scst_check_local_events(cmd);
+	if (unlikely(rc != 0))
+		goto out_done;
+
+	if (!list_empty(&dev->dev_registrants_list)) {
+		if (scst_pr_crh_case(cmd))
+			goto out_completed;
+		else {
+			scst_set_cmd_error_status(cmd,
+				SAM_STAT_RESERVATION_CONFLICT);
+			goto out_done;
+		}
+	}
+
+	spin_lock_bh(&dev->dev_lock);
+
+	if (test_bit(SCST_TGT_DEV_RESERVED, &cmd->tgt_dev->tgt_dev_flags)) {
+		spin_unlock_bh(&dev->dev_lock);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out_done;
+	}
+
+	list_for_each_entry(tgt_dev_tmp, &dev->dev_tgt_dev_list,
+			    dev_tgt_dev_list_entry) {
+		if (cmd->tgt_dev != tgt_dev_tmp)
+			set_bit(SCST_TGT_DEV_RESERVED,
+				&tgt_dev_tmp->tgt_dev_flags);
+	}
+	dev->dev_reserved = 1;
+
+	spin_unlock_bh(&dev->dev_lock);
+
+out:
+	return res;
+
+out_completed:
+	cmd->completed = 1;
+
+out_done:
+	/* Report the result */
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+	res = SCST_EXEC_COMPLETED;
+	goto out;
+}
+
+static int scst_release_local(struct scst_cmd *cmd)
+{
+	int res = SCST_EXEC_NOT_COMPLETED, rc;
+	struct scst_tgt_dev *tgt_dev_tmp;
+	struct scst_device *dev;
+
+	dev = cmd->dev;
+
+	/*
+	 * See comment in scst_reserve_local() why no dev blocking or any
+	 * other protection is needed here.
+	 */
+
+	rc = scst_check_local_events(cmd);
+	if (unlikely(rc != 0))
+		goto out_done;
+
+	if (!list_empty(&dev->dev_registrants_list)) {
+		if (scst_pr_crh_case(cmd))
+			goto out_completed;
+		else {
+			scst_set_cmd_error_status(cmd,
+				SAM_STAT_RESERVATION_CONFLICT);
+			goto out_done;
+		}
+	}
+
+	spin_lock_bh(&dev->dev_lock);
+
+	/*
+	 * The device could be RELEASED behind us, if RESERVING session
+	 * is closed (see scst_free_tgt_dev()), but this actually doesn't
+	 * matter, so use lock and no retest for DEV_RESERVED bits again
+	 */
+	if (test_bit(SCST_TGT_DEV_RESERVED, &cmd->tgt_dev->tgt_dev_flags)) {
+		res = SCST_EXEC_COMPLETED;
+		cmd->status = 0;
+		cmd->msg_status = 0;
+		cmd->host_status = DID_OK;
+		cmd->driver_status = 0;
+		cmd->completed = 1;
+	} else {
+		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;
+	}
+
+	spin_unlock_bh(&dev->dev_lock);
+
+	if (res == SCST_EXEC_COMPLETED)
+		goto out_done;
+
+out:
+	return res;
+
+out_completed:
+	cmd->completed = 1;
+
+out_done:
+	res = SCST_EXEC_COMPLETED;
+	/* Report the result */
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+	goto out;
+}
+
+/**
+ * scst_check_local_events() - check if there are any local SCSI events
+ *
+ * Description:
+ *    Checks if the command can be executed or there are local events,
+ *    like reservatons, pending UAs, etc. Returns < 0 if command must be
+ *    aborted, > 0 if there is an event and command should be immediately
+ *    completed, or 0 otherwise.
+ *
+ * !! Dev handlers implementing exec() callback must call this function there
+ * !! just before the actual command's execution!
+ *
+ *    On call no locks, no IRQ or IRQ-disabled context allowed.
+ */
+static int scst_persistent_reserve_in_local(struct scst_cmd *cmd)
+{
+	int rc;
+	struct scst_device *dev;
+	struct scst_tgt_dev *tgt_dev;
+	struct scst_session *session;
+	int action;
+	uint8_t *buffer;
+	int buffer_size;
+
+	EXTRACHECKS_BUG_ON(scst_cmd_atomic(cmd));
+
+	dev = cmd->dev;
+	tgt_dev = cmd->tgt_dev;
+	session = cmd->sess;
+
+	rc = scst_check_local_events(cmd);
+	if (unlikely(rc != 0))
+		goto out_done;
+
+	if (unlikely(dev->not_pr_supporting_tgt_devs_num != 0)) {
+		PRINT_WARNING("Persistent Reservation command %x refused for "
+			"device %s, because the device has not supporting PR "
+			"transports connected", cmd->cdb[0], dev->virt_name);
+		scst_set_cmd_error(cmd,
+				   SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+		goto out_done;
+	}
+
+	if (dev->dev_reserved) {
+		TRACE_PR("PR command rejected, because device %s holds regular "
+			"reservation", dev->virt_name);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out_done;
+	}
+
+	if (dev->scsi_dev != NULL) {
+		PRINT_WARNING("PR commands for pass-through devices not "
+			"supported (device %s)", dev->virt_name);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+		goto out_done;
+	}
+
+	buffer_size = scst_get_full_buf(cmd, &buffer);
+	if (unlikely(buffer_size <= 0)) {
+		if (buffer_size < 0)
+			scst_set_busy(cmd);
+		goto out_done;
+	}
+
+	scst_pr_write_lock(dev);
+
+	/* We can be aborted by another PR command while waiting for the lock */
+	if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) {
+		TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd);
+		goto out_unlock;
+	}
+
+	action = cmd->cdb[1] & 0x1f;
+
+	TRACE(TRACE_SCSI, "PR action %x for '%s' (LUN %llx) from '%s'", action,
+	    dev->virt_name, tgt_dev->lun, session->initiator_name);
+
+	switch (action) {
+	case PR_READ_KEYS:
+		scst_pr_read_keys(cmd, buffer, buffer_size);
+		break;
+	case PR_READ_RESERVATION:
+		scst_pr_read_reservation(cmd, buffer, buffer_size);
+		break;
+	case PR_REPORT_CAPS:
+		scst_pr_report_caps(cmd, buffer, buffer_size);
+		break;
+	case PR_READ_FULL_STATUS:
+		scst_pr_read_full_status(cmd, buffer, buffer_size);
+		break;
+	default:
+		PRINT_ERROR("Unsupported action %x", action);
+		scst_pr_write_unlock(dev);
+		goto out_err;
+	}
+
+out_complete:
+	cmd->completed = 1;
+
+out_unlock:
+	scst_pr_write_unlock(dev);
+
+	scst_put_full_buf(cmd, buffer);
+
+out_done:
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+	return SCST_EXEC_COMPLETED;
+
+out_err:
+	scst_set_cmd_error(cmd,
+		   SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+	goto out_complete;
+}
+
+/* No locks, no IRQ or IRQ-disabled context allowed */
+static int scst_persistent_reserve_out_local(struct scst_cmd *cmd)
+{
+	int res = SCST_EXEC_COMPLETED;
+	int rc;
+	struct scst_device *dev;
+	struct scst_tgt_dev *tgt_dev;
+	struct scst_session *session;
+	int action;
+	uint8_t *buffer;
+	int buffer_size;
+	bool aborted = false;
+
+	EXTRACHECKS_BUG_ON(scst_cmd_atomic(cmd));
+
+	dev = cmd->dev;
+	tgt_dev = cmd->tgt_dev;
+	session = cmd->sess;
+
+	rc = scst_check_local_events(cmd);
+	if (unlikely(rc != 0))
+		goto out_done;
+
+	if (unlikely(dev->not_pr_supporting_tgt_devs_num != 0)) {
+		PRINT_WARNING("Persistent Reservation command %x refused for "
+			"device %s, because the device has not supporting PR "
+			"transports connected", cmd->cdb[0], dev->virt_name);
+		scst_set_cmd_error(cmd,
+				   SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+		goto out_done;
+	}
+
+	action = cmd->cdb[1] & 0x1f;
+
+	TRACE(TRACE_SCSI, "PR action %x for '%s' (LUN %llx) from '%s'", action,
+	    dev->virt_name, tgt_dev->lun, session->initiator_name);
+
+	if (dev->dev_reserved) {
+		TRACE_PR("PR command rejected, because device %s holds regular "
+			"reservation", dev->virt_name);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out_done;
+	}
+
+	/*
+	 * Check if tgt_dev already registered. Also by this check we make
+	 * sure that table "PERSISTENT RESERVE OUT service actions that are
+	 * allowed in the presence of various reservations" is honored.
+	 * REGISTER AND MOVE and RESERVE will be additionally checked for
+	 * conflicts later.
+	 */
+	if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) &&
+	    (tgt_dev->registrant == NULL)) {
+		TRACE_PR("'%s' not registered", cmd->sess->initiator_name);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out_done;
+	}
+
+	buffer_size = scst_get_full_buf(cmd, &buffer);
+	if (unlikely(buffer_size <= 0)) {
+		if (buffer_size < 0)
+			scst_set_busy(cmd);
+		goto out_done;
+	}
+
+	/* Check scope */
+	if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) &&
+	    (action != PR_CLEAR) && ((cmd->cdb[2] & 0x0f) >> 4) != SCOPE_LU) {
+		TRACE_PR("Scope must be SCOPE_LU for action %x", action);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_put_full_buf;
+	}
+
+	/* Check SPEC_I_PT (PR_REGISTER_AND_MOVE has another format) */
+	if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_MOVE) &&
+	    ((buffer[20] >> 3) & 0x01)) {
+		TRACE_PR("SPEC_I_PT must be zero for action %x", action);
+		scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+					scst_sense_invalid_field_in_cdb));
+		goto out_put_full_buf;
+	}
+
+	/* Check ALL_TG_PT (PR_REGISTER_AND_MOVE has another format) */
+	if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) &&
+	    (action != PR_REGISTER_AND_MOVE) && ((buffer[20] >> 2) & 0x01)) {
+		TRACE_PR("ALL_TG_PT must be zero for action %x", action);
+		scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+					scst_sense_invalid_field_in_cdb));
+		goto out_put_full_buf;
+	}
+
+	scst_pr_write_lock(dev);
+
+	/* We can be aborted by another PR command while waiting for the lock */
+	aborted = test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags);
+	if (unlikely(aborted)) {
+		TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd);
+		goto out_unlock;
+	}
+
+	switch (action) {
+	case PR_REGISTER:
+		scst_pr_register(cmd, buffer, buffer_size);
+		break;
+	case PR_RESERVE:
+		scst_pr_reserve(cmd, buffer, buffer_size);
+		break;
+	case PR_RELEASE:
+		scst_pr_release(cmd, buffer, buffer_size);
+		break;
+	case PR_CLEAR:
+		scst_pr_clear(cmd, buffer, buffer_size);
+		break;
+	case PR_PREEMPT:
+		scst_pr_preempt(cmd, buffer, buffer_size);
+		break;
+	case PR_PREEMPT_AND_ABORT:
+		scst_pr_preempt_and_abort(cmd, buffer, buffer_size);
+		break;
+	case PR_REGISTER_AND_IGNORE:
+		scst_pr_register_and_ignore(cmd, buffer, buffer_size);
+		break;
+	case PR_REGISTER_AND_MOVE:
+		scst_pr_register_and_move(cmd, buffer, buffer_size);
+		break;
+	default:
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_unlock;
+	}
+
+	if (cmd->status == SAM_STAT_GOOD)
+		scst_pr_sync_device_file(tgt_dev, cmd);
+
+	if ((dev->handler->pr_cmds_notifications) &&
+	    (cmd->status == SAM_STAT_GOOD)) /* sync file may change status */
+		res = SCST_EXEC_NOT_COMPLETED;
+
+out_unlock:
+	scst_pr_write_unlock(dev);
+
+out_put_full_buf:
+	scst_put_full_buf(cmd, buffer);
+
+out_done:
+	if (SCST_EXEC_COMPLETED == res) {
+		if (!aborted)
+			cmd->completed = 1;
+		cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT,
+				SCST_CONTEXT_SAME);
+	}
+	return res;
+}
+
+/* No locks, no IRQ or IRQ-disabled context allowed */
+int scst_check_local_events(struct scst_cmd *cmd)
+{
+	int res, rc;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_device *dev = cmd->dev;
+
+	/*
+	 * There's no race here, because we need to trace commands sent
+	 * *after* dev_double_ua_possible flag was set.
+	 */
+	if (unlikely(dev->dev_double_ua_possible))
+		cmd->double_ua_possible = 1;
+
+	/* Reserve check before Unit Attention */
+	if (unlikely(test_bit(SCST_TGT_DEV_RESERVED,
+			      &tgt_dev->tgt_dev_flags))) {
+		if ((cmd->op_flags & SCST_REG_RESERVE_ALLOWED) == 0) {
+			scst_set_cmd_error_status(cmd,
+				SAM_STAT_RESERVATION_CONFLICT);
+			goto out_complete;
+		}
+	}
+
+	if (dev->pr_is_set) {
+		if (unlikely(!scst_pr_is_cmd_allowed(cmd))) {
+			scst_set_cmd_error_status(cmd,
+				SAM_STAT_RESERVATION_CONFLICT);
+			goto out_complete;
+		}
+	}
+
+	/*
+	 * Let's check for ABORTED after scst_pr_is_cmd_allowed(), because
+	 * we might sleep for a while there.
+	 */
+	if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) {
+		TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd);
+		goto out_uncomplete;
+	}
+
+	/* If we had internal bus reset, set the command error unit attention */
+	if ((dev->scsi_dev != NULL) &&
+	    unlikely(dev->scsi_dev->was_reset)) {
+		if (scst_is_ua_command(cmd)) {
+			int done = 0;
+			/*
+			 * Prevent more than 1 cmd to be triggered by
+			 * was_reset.
+			 */
+			spin_lock_bh(&dev->dev_lock);
+			if (dev->scsi_dev->was_reset) {
+				TRACE(TRACE_MGMT, "was_reset is %d", 1);
+				scst_set_cmd_error(cmd,
+					  SCST_LOAD_SENSE(scst_sense_reset_UA));
+				/*
+				 * It looks like it is safe to clear was_reset
+				 * here.
+				 */
+				dev->scsi_dev->was_reset = 0;
+				done = 1;
+			}
+			spin_unlock_bh(&dev->dev_lock);
+
+			if (done)
+				goto out_complete;
+		}
+	}
+
+	if (unlikely(test_bit(SCST_TGT_DEV_UA_PENDING,
+			&cmd->tgt_dev->tgt_dev_flags))) {
+		if (scst_is_ua_command(cmd)) {
+			rc = scst_set_pending_UA(cmd);
+			if (rc == 0)
+				goto out_complete;
+		}
+	}
+
+	res = 0;
+
+out:
+	return res;
+
+out_complete:
+	res = 1;
+	BUG_ON(!cmd->completed);
+	goto out;
+
+out_uncomplete:
+	res = -1;
+	goto out;
+}
+EXPORT_SYMBOL_GPL(scst_check_local_events);
+
+/* No locks */
+void scst_inc_expected_sn(struct scst_tgt_dev *tgt_dev, atomic_t *slot)
+{
+	if (slot == NULL)
+		goto inc;
+
+	/* Optimized for lockless fast path */
+
+	TRACE_SN("Slot %zd, *cur_sn_slot %d", slot - tgt_dev->sn_slots,
+		atomic_read(slot));
+
+	if (!atomic_dec_and_test(slot))
+		goto out;
+
+	TRACE_SN("Slot is 0 (num_free_sn_slots=%d)",
+		tgt_dev->num_free_sn_slots);
+	if (tgt_dev->num_free_sn_slots < (int)ARRAY_SIZE(tgt_dev->sn_slots)-1) {
+		spin_lock_irq(&tgt_dev->sn_lock);
+		if (likely(tgt_dev->num_free_sn_slots < (int)ARRAY_SIZE(tgt_dev->sn_slots)-1)) {
+			if (tgt_dev->num_free_sn_slots < 0)
+				tgt_dev->cur_sn_slot = slot;
+			/*
+			 * To be in-sync with SIMPLE case in scst_cmd_set_sn()
+			 */
+			smp_mb();
+			tgt_dev->num_free_sn_slots++;
+			TRACE_SN("Incremented num_free_sn_slots (%d)",
+				tgt_dev->num_free_sn_slots);
+
+		}
+		spin_unlock_irq(&tgt_dev->sn_lock);
+	}
+
+inc:
+	/*
+	 * No protection of expected_sn is needed, because only one thread
+	 * at time can be here (serialized by sn). Also it is supposed that
+	 * there could not be half-incremented halves.
+	 */
+	tgt_dev->expected_sn++;
+	/*
+	 * Write must be before def_cmd_count read to be in sync. with
+	 * scst_post_exec_sn(). See comment in scst_send_for_exec().
+	 */
+	smp_mb();
+	TRACE_SN("Next expected_sn: %d", tgt_dev->expected_sn);
+
+out:
+	return;
+}
+
+/* No locks */
+static struct scst_cmd *scst_post_exec_sn(struct scst_cmd *cmd,
+	bool make_active)
+{
+	/* For HQ commands SN is not set */
+	bool inc_expected_sn = !cmd->inc_expected_sn_on_done &&
+			       cmd->sn_set && !cmd->retry;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_cmd *res;
+
+	if (inc_expected_sn)
+		scst_inc_expected_sn(tgt_dev, cmd->sn_slot);
+
+	if (make_active) {
+		scst_make_deferred_commands_active(tgt_dev);
+		res = NULL;
+	} else
+		res = scst_check_deferred_commands(tgt_dev);
+	return res;
+}
+
+/* cmd must be additionally referenced to not die inside */
+static int scst_do_real_exec(struct scst_cmd *cmd)
+{
+	int res = SCST_EXEC_NOT_COMPLETED;
+	int rc;
+	struct scst_device *dev = cmd->dev;
+	struct scst_dev_type *handler = dev->handler;
+	struct io_context *old_ctx = NULL;
+	bool ctx_changed = false;
+
+	ctx_changed = scst_set_io_context(cmd, &old_ctx);
+
+	cmd->state = SCST_CMD_STATE_REAL_EXECUTING;
+
+	if (handler->exec) {
+		TRACE_DBG("Calling dev handler %s exec(%p)",
+		      handler->name, cmd);
+		TRACE_BUFF_FLAG(TRACE_SND_TOP, "Execing: ", cmd->cdb,
+			cmd->cdb_len);
+		scst_set_cur_start(cmd);
+		res = handler->exec(cmd);
+		TRACE_DBG("Dev handler %s exec() returned %d",
+		      handler->name, res);
+
+		if (res == SCST_EXEC_COMPLETED)
+			goto out_complete;
+
+		scst_set_exec_time(cmd);
+
+		BUG_ON(res != SCST_EXEC_NOT_COMPLETED);
+	}
+
+	TRACE_DBG("Sending cmd %p to SCSI mid-level", cmd);
+
+	if (unlikely(dev->scsi_dev == NULL)) {
+		PRINT_ERROR("Command for virtual device must be "
+			"processed by device handler (LUN %lld)!",
+			(long long unsigned int)cmd->lun);
+		goto out_error;
+	}
+
+	res = scst_check_local_events(cmd);
+	if (unlikely(res != 0))
+		goto out_done;
+
+	scst_set_cur_start(cmd);
+
+	rc = scst_scsi_exec_async(cmd, cmd, scst_pass_through_cmd_done);
+	if (unlikely(rc != 0)) {
+		PRINT_ERROR("scst pass-through exec failed: %x", rc);
+		goto out_error;
+	}
+
+out_complete:
+	res = SCST_EXEC_COMPLETED;
+
+	if (ctx_changed)
+		scst_reset_io_context(cmd->tgt_dev, old_ctx);
+	return res;
+
+out_error:
+	scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+	goto out_done;
+
+out_done:
+	res = SCST_EXEC_COMPLETED;
+	/* Report the result */
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+	goto out_complete;
+}
+
+static inline int scst_real_exec(struct scst_cmd *cmd)
+{
+	int res;
+
+	BUILD_BUG_ON(SCST_CMD_STATE_RES_CONT_SAME != SCST_EXEC_NOT_COMPLETED);
+	BUILD_BUG_ON(SCST_CMD_STATE_RES_CONT_NEXT != SCST_EXEC_COMPLETED);
+
+	__scst_cmd_get(cmd);
+
+	res = scst_do_real_exec(cmd);
+
+	if (likely(res == SCST_EXEC_COMPLETED)) {
+		scst_post_exec_sn(cmd, true);
+		if (cmd->dev->scsi_dev != NULL)
+			generic_unplug_device(
+				cmd->dev->scsi_dev->request_queue);
+	} else
+		BUG();
+
+	__scst_cmd_put(cmd);
+
+	/* SCST_EXEC_* match SCST_CMD_STATE_RES_* */
+	return res;
+}
+
+static int scst_do_local_exec(struct scst_cmd *cmd)
+{
+	int res;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+
+	/* Check READ_ONLY device status */
+	if ((cmd->op_flags & SCST_WRITE_MEDIUM) &&
+	    (tgt_dev->acg_dev->rd_only || cmd->dev->swp ||
+	     cmd->dev->rd_only)) {
+		PRINT_WARNING("Attempt of write access to read-only device: "
+			"initiator %s, LUN %lld, op %x",
+			cmd->sess->initiator_name, cmd->lun, cmd->cdb[0]);
+		scst_set_cmd_error(cmd,
+			   SCST_LOAD_SENSE(scst_sense_data_protect));
+		goto out_done;
+	}
+
+	if (!scst_is_cmd_local(cmd)) {
+		res = SCST_EXEC_NOT_COMPLETED;
+		goto out;
+	}
+
+	switch (cmd->cdb[0]) {
+	case RESERVE:
+	case RESERVE_10:
+		res = scst_reserve_local(cmd);
+		break;
+	case RELEASE:
+	case RELEASE_10:
+		res = scst_release_local(cmd);
+		break;
+	case PERSISTENT_RESERVE_IN:
+		res = scst_persistent_reserve_in_local(cmd);
+		break;
+	case PERSISTENT_RESERVE_OUT:
+		res = scst_persistent_reserve_out_local(cmd);
+		break;
+	case REPORT_LUNS:
+		res = scst_report_luns_local(cmd);
+		break;
+	case REQUEST_SENSE:
+		res = scst_request_sense_local(cmd);
+		break;
+	default:
+		res = SCST_EXEC_NOT_COMPLETED;
+		break;
+	}
+
+out:
+	return res;
+
+out_done:
+	/* Report the result */
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+	res = SCST_EXEC_COMPLETED;
+	goto out;
+}
+
+static int scst_local_exec(struct scst_cmd *cmd)
+{
+	int res;
+
+	BUILD_BUG_ON(SCST_CMD_STATE_RES_CONT_SAME != SCST_EXEC_NOT_COMPLETED);
+	BUILD_BUG_ON(SCST_CMD_STATE_RES_CONT_NEXT != SCST_EXEC_COMPLETED);
+
+	__scst_cmd_get(cmd);
+
+	res = scst_do_local_exec(cmd);
+	if (likely(res == SCST_EXEC_NOT_COMPLETED))
+		cmd->state = SCST_CMD_STATE_REAL_EXEC;
+	else if (res == SCST_EXEC_COMPLETED)
+		scst_post_exec_sn(cmd, true);
+	else
+		BUG();
+
+	__scst_cmd_put(cmd);
+
+	/* SCST_EXEC_* match SCST_CMD_STATE_RES_* */
+	return res;
+}
+
+static int scst_exec(struct scst_cmd **active_cmd)
+{
+	struct scst_cmd *cmd = *active_cmd;
+	struct scst_cmd *ref_cmd;
+	struct scst_device *dev = cmd->dev;
+	int res = SCST_CMD_STATE_RES_CONT_NEXT, count;
+
+	if (unlikely(scst_check_blocked_dev(cmd)))
+		goto out;
+
+	/* To protect tgt_dev */
+	ref_cmd = cmd;
+	__scst_cmd_get(ref_cmd);
+
+	count = 0;
+	while (1) {
+		int rc;
+
+		cmd->sent_for_exec = 1;
+		/*
+		 * To sync with scst_abort_cmd(). The above assignment must
+		 * be before SCST_CMD_ABORTED test, done later in
+		 * scst_check_local_events(). It's far from here, so the order
+		 * is virtually guaranteed, but let's have it just in case.
+		 */
+		smp_mb();
+
+		cmd->scst_cmd_done = scst_cmd_done_local;
+		cmd->state = SCST_CMD_STATE_LOCAL_EXEC;
+
+		rc = scst_do_local_exec(cmd);
+		if (likely(rc == SCST_EXEC_NOT_COMPLETED))
+			/* Nothing to do */;
+		else {
+			BUG_ON(rc != SCST_EXEC_COMPLETED);
+			goto done;
+		}
+
+		cmd->state = SCST_CMD_STATE_REAL_EXEC;
+
+		rc = scst_do_real_exec(cmd);
+		BUG_ON(rc != SCST_EXEC_COMPLETED);
+
+done:
+		count++;
+
+		cmd = scst_post_exec_sn(cmd, false);
+		if (cmd == NULL)
+			break;
+
+		if (unlikely(scst_check_blocked_dev(cmd)))
+			break;
+
+		__scst_cmd_put(ref_cmd);
+		ref_cmd = cmd;
+		__scst_cmd_get(ref_cmd);
+	}
+
+	*active_cmd = cmd;
+
+	if (count == 0)
+		goto out_put;
+
+	if (dev->scsi_dev != NULL)
+		generic_unplug_device(dev->scsi_dev->request_queue);
+
+out_put:
+	__scst_cmd_put(ref_cmd);
+	/* !! At this point sess, dev and tgt_dev can be already freed !! */
+
+out:
+	return res;
+}
+
+static int scst_send_for_exec(struct scst_cmd **active_cmd)
+{
+	int res;
+	struct scst_cmd *cmd = *active_cmd;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	typeof(tgt_dev->expected_sn) expected_sn;
+
+	if (unlikely(cmd->internal))
+		goto exec;
+
+	if (unlikely(cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE))
+		goto exec;
+
+	BUG_ON(!cmd->sn_set);
+
+	expected_sn = tgt_dev->expected_sn;
+	/* Optimized for lockless fast path */
+	if ((cmd->sn != expected_sn) || (tgt_dev->hq_cmd_count > 0)) {
+		spin_lock_irq(&tgt_dev->sn_lock);
+
+		tgt_dev->def_cmd_count++;
+		/*
+		 * Memory barrier is needed here to implement lockless fast
+		 * path. We need the exact order of read and write between
+		 * def_cmd_count and expected_sn. Otherwise, we can miss case,
+		 * when expected_sn was changed to be equal to cmd->sn while
+		 * we are queuing cmd the deferred list after the expected_sn
+		 * below. It will lead to a forever stuck command. But with
+		 * the barrier in such case __scst_check_deferred_commands()
+		 * will be called and it will take sn_lock, so we will be
+		 * synchronized.
+		 */
+		smp_mb();
+
+		expected_sn = tgt_dev->expected_sn;
+		if ((cmd->sn != expected_sn) || (tgt_dev->hq_cmd_count > 0)) {
+			if (unlikely(test_bit(SCST_CMD_ABORTED,
+					      &cmd->cmd_flags))) {
+				/* Necessary to allow aborting out of sn cmds */
+				TRACE_MGMT_DBG("Aborting out of sn cmd %p "
+					"(tag %llu, sn %u)", cmd,
+					(long long unsigned)cmd->tag, cmd->sn);
+				tgt_dev->def_cmd_count--;
+				scst_set_cmd_abnormal_done_state(cmd);
+				res = SCST_CMD_STATE_RES_CONT_SAME;
+			} else {
+				TRACE_SN("Deferring cmd %p (sn=%d, set %d, "
+					"expected_sn=%d)", cmd, cmd->sn,
+					cmd->sn_set, expected_sn);
+				list_add_tail(&cmd->sn_cmd_list_entry,
+					      &tgt_dev->deferred_cmd_list);
+				res = SCST_CMD_STATE_RES_CONT_NEXT;
+			}
+			spin_unlock_irq(&tgt_dev->sn_lock);
+			goto out;
+		} else {
+			TRACE_SN("Somebody incremented expected_sn %d, "
+				"continuing", expected_sn);
+			tgt_dev->def_cmd_count--;
+			spin_unlock_irq(&tgt_dev->sn_lock);
+		}
+	}
+
+exec:
+	res = scst_exec(active_cmd);
+
+out:
+	return res;
+}
+
+/* No locks supposed to be held */
+static int scst_check_sense(struct scst_cmd *cmd)
+{
+	int res = 0;
+	struct scst_device *dev = cmd->dev;
+
+	if (unlikely(cmd->ua_ignore))
+		goto out;
+
+	/* If we had internal bus reset behind us, set the command error UA */
+	if ((dev->scsi_dev != NULL) &&
+	    unlikely(cmd->host_status == DID_RESET) &&
+	    scst_is_ua_command(cmd)) {
+		TRACE(TRACE_MGMT, "DID_RESET: was_reset=%d host_status=%x",
+		      dev->scsi_dev->was_reset, cmd->host_status);
+		scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_reset_UA));
+		/* It looks like it is safe to clear was_reset here */
+		dev->scsi_dev->was_reset = 0;
+	}
+
+	if (unlikely(cmd->status == SAM_STAT_CHECK_CONDITION) &&
+	    SCST_SENSE_VALID(cmd->sense)) {
+		PRINT_BUFF_FLAG(TRACE_SCSI, "Sense", cmd->sense,
+			cmd->sense_valid_len);
+
+		/* Check Unit Attention Sense Key */
+		if (scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)) {
+			if (scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
+					SCST_SENSE_ASC_VALID,
+					0, SCST_SENSE_ASC_UA_RESET, 0)) {
+				if (cmd->double_ua_possible) {
+					TRACE_MGMT_DBG("Double UA "
+						"detected for device %p", dev);
+					TRACE_MGMT_DBG("Retrying cmd"
+						" %p (tag %llu)", cmd,
+						(long long unsigned)cmd->tag);
+
+					cmd->status = 0;
+					cmd->msg_status = 0;
+					cmd->host_status = DID_OK;
+					cmd->driver_status = 0;
+					cmd->completed = 0;
+
+					mempool_free(cmd->sense,
+						     scst_sense_mempool);
+					cmd->sense = NULL;
+
+					scst_check_restore_sg_buff(cmd);
+
+					BUG_ON(cmd->dbl_ua_orig_resp_data_len < 0);
+					cmd->data_direction =
+						cmd->dbl_ua_orig_data_direction;
+					cmd->resp_data_len =
+						cmd->dbl_ua_orig_resp_data_len;
+
+					cmd->state = SCST_CMD_STATE_REAL_EXEC;
+					cmd->retry = 1;
+					res = 1;
+					goto out;
+				}
+			}
+			scst_dev_check_set_UA(dev, cmd,	cmd->sense,
+				cmd->sense_valid_len);
+		}
+	}
+
+	if (unlikely(cmd->double_ua_possible)) {
+		if (scst_is_ua_command(cmd)) {
+			TRACE_MGMT_DBG("Clearing dbl_ua_possible flag (dev %p, "
+				"cmd %p)", dev, cmd);
+			/*
+			 * Lock used to protect other flags in the bitfield
+			 * (just in case, actually). Those flags can't be
+			 * changed in parallel, because the device is
+			 * serialized.
+			 */
+			spin_lock_bh(&dev->dev_lock);
+			dev->dev_double_ua_possible = 0;
+			spin_unlock_bh(&dev->dev_lock);
+		}
+	}
+
+out:
+	return res;
+}
+
+static int scst_check_auto_sense(struct scst_cmd *cmd)
+{
+	int res = 0;
+
+	if (unlikely(cmd->status == SAM_STAT_CHECK_CONDITION) &&
+	    (!SCST_SENSE_VALID(cmd->sense) ||
+	     SCST_NO_SENSE(cmd->sense))) {
+		TRACE(TRACE_SCSI|TRACE_MINOR_AND_MGMT_DBG, "CHECK_CONDITION, "
+			"but no sense: cmd->status=%x, cmd->msg_status=%x, "
+		      "cmd->host_status=%x, cmd->driver_status=%x (cmd %p)",
+		      cmd->status, cmd->msg_status, cmd->host_status,
+		      cmd->driver_status, cmd);
+		res = 1;
+	} else if (unlikely(cmd->host_status)) {
+		if ((cmd->host_status == DID_REQUEUE) ||
+		    (cmd->host_status == DID_IMM_RETRY) ||
+		    (cmd->host_status == DID_SOFT_ERROR) ||
+		    (cmd->host_status == DID_ABORT)) {
+			scst_set_busy(cmd);
+		} else {
+			TRACE(TRACE_SCSI|TRACE_MINOR_AND_MGMT_DBG, "Host "
+				"status %x received, returning HARDWARE ERROR "
+				"instead (cmd %p)", cmd->host_status, cmd);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+		}
+	}
+	return res;
+}
+
+static int scst_pre_dev_done(struct scst_cmd *cmd)
+{
+	int res = SCST_CMD_STATE_RES_CONT_SAME, rc;
+
+	if (unlikely(scst_check_auto_sense(cmd))) {
+		PRINT_INFO("Command finished with CHECK CONDITION, but "
+			    "without sense data (opcode 0x%x), issuing "
+			    "REQUEST SENSE", cmd->cdb[0]);
+		rc = scst_prepare_request_sense(cmd);
+		if (rc == 0)
+			res = SCST_CMD_STATE_RES_CONT_NEXT;
+		else {
+			PRINT_ERROR("%s", "Unable to issue REQUEST SENSE, "
+				    "returning HARDWARE ERROR");
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+		}
+		goto out;
+	} else if (unlikely(scst_check_sense(cmd)))
+		goto out;
+
+	if (likely(scsi_status_is_good(cmd->status))) {
+		unsigned char type = cmd->dev->type;
+		if (unlikely((cmd->cdb[0] == MODE_SENSE ||
+			      cmd->cdb[0] == MODE_SENSE_10)) &&
+		    (cmd->tgt_dev->acg_dev->rd_only || cmd->dev->swp ||
+		     cmd->dev->rd_only) &&
+		    (type == TYPE_DISK ||
+		     type == TYPE_WORM ||
+		     type == TYPE_MOD ||
+		     type == TYPE_TAPE)) {
+			int32_t length;
+			uint8_t *address;
+			bool err = false;
+
+			length = scst_get_buf_first(cmd, &address);
+			if (length < 0) {
+				PRINT_ERROR("%s", "Unable to get "
+					"MODE_SENSE buffer");
+				scst_set_cmd_error(cmd,
+					SCST_LOAD_SENSE(
+						scst_sense_hardw_error));
+				err = true;
+			} else if (length > 2 && cmd->cdb[0] == MODE_SENSE)
+				address[2] |= 0x80;   /* Write Protect*/
+			else if (length > 3 && cmd->cdb[0] == MODE_SENSE_10)
+				address[3] |= 0x80;   /* Write Protect*/
+			scst_put_buf(cmd, address);
+
+			if (err)
+				goto out;
+		}
+
+		/*
+		 * Check and clear NormACA option for the device, if necessary,
+		 * since we don't support ACA
+		 */
+		if (unlikely((cmd->cdb[0] == INQUIRY)) &&
+		    /* Std INQUIRY data (no EVPD) */
+		    !(cmd->cdb[1] & SCST_INQ_EVPD) &&
+		    (cmd->resp_data_len > SCST_INQ_BYTE3)) {
+			uint8_t *buffer;
+			int buflen;
+			bool err = false;
+
+			/* ToDo: all pages ?? */
+			buflen = scst_get_buf_first(cmd, &buffer);
+			if (buflen > SCST_INQ_BYTE3) {
+#ifdef CONFIG_SCST_EXTRACHECKS
+				if (buffer[SCST_INQ_BYTE3] & SCST_INQ_NORMACA_BIT) {
+					PRINT_INFO("NormACA set for device: "
+					    "lun=%lld, type 0x%02x. Clear it, "
+					    "since it's unsupported.",
+					    (long long unsigned int)cmd->lun,
+					    buffer[0]);
+				}
+#endif
+				buffer[SCST_INQ_BYTE3] &= ~SCST_INQ_NORMACA_BIT;
+			} else if (buflen != 0) {
+				PRINT_ERROR("%s", "Unable to get INQUIRY "
+				    "buffer");
+				scst_set_cmd_error(cmd,
+				       SCST_LOAD_SENSE(scst_sense_hardw_error));
+				err = true;
+			}
+			if (buflen > 0)
+				scst_put_buf(cmd, buffer);
+
+			if (err)
+				goto out;
+		}
+
+		if (unlikely((cmd->cdb[0] == MODE_SELECT) ||
+		    (cmd->cdb[0] == MODE_SELECT_10) ||
+		    (cmd->cdb[0] == LOG_SELECT))) {
+			TRACE(TRACE_SCSI,
+				"MODE/LOG SELECT succeeded (LUN %lld)",
+				(long long unsigned int)cmd->lun);
+			cmd->state = SCST_CMD_STATE_MODE_SELECT_CHECKS;
+			goto out;
+		}
+	} else {
+		TRACE(TRACE_SCSI, "cmd %p not succeeded with status %x",
+			cmd, cmd->status);
+
+		if ((cmd->cdb[0] == RESERVE) || (cmd->cdb[0] == RESERVE_10)) {
+			if (!test_bit(SCST_TGT_DEV_RESERVED,
+					&cmd->tgt_dev->tgt_dev_flags)) {
+				struct scst_tgt_dev *tgt_dev_tmp;
+				struct scst_device *dev = cmd->dev;
+
+				TRACE(TRACE_SCSI, "RESERVE failed lun=%lld, "
+					"status=%x",
+					(long long unsigned int)cmd->lun,
+					cmd->status);
+				PRINT_BUFF_FLAG(TRACE_SCSI, "Sense", cmd->sense,
+					cmd->sense_valid_len);
+
+				/* Clearing the reservation */
+				spin_lock_bh(&dev->dev_lock);
+				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;
+				spin_unlock_bh(&dev->dev_lock);
+			}
+		}
+
+		/* Check for MODE PARAMETERS CHANGED UA */
+		if ((cmd->dev->scsi_dev != NULL) &&
+		    (cmd->status == SAM_STAT_CHECK_CONDITION) &&
+		    scst_is_ua_sense(cmd->sense, cmd->sense_valid_len) &&
+		    scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
+					SCST_SENSE_ASCx_VALID,
+					0, 0x2a, 0x01)) {
+			TRACE(TRACE_SCSI, "MODE PARAMETERS CHANGED UA (lun "
+				"%lld)", (long long unsigned int)cmd->lun);
+			cmd->state = SCST_CMD_STATE_MODE_SELECT_CHECKS;
+			goto out;
+		}
+	}
+
+	cmd->state = SCST_CMD_STATE_DEV_DONE;
+
+out:
+	return res;
+}
+
+static int scst_mode_select_checks(struct scst_cmd *cmd)
+{
+	int res = SCST_CMD_STATE_RES_CONT_SAME;
+
+	if (likely(scsi_status_is_good(cmd->status))) {
+		int atomic = scst_cmd_atomic(cmd);
+		if (unlikely((cmd->cdb[0] == MODE_SELECT) ||
+		    (cmd->cdb[0] == MODE_SELECT_10) ||
+		    (cmd->cdb[0] == LOG_SELECT))) {
+			struct scst_device *dev = cmd->dev;
+			int sl;
+			uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
+
+			if (atomic && (dev->scsi_dev != NULL)) {
+				TRACE_DBG("%s", "MODE/LOG SELECT: thread "
+					"context required");
+				res = SCST_CMD_STATE_RES_NEED_THREAD;
+				goto out;
+			}
+
[… more lines in the raw message]

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Part of the diff is not shown here. View the raw message.

#6 lore raw

PATCH v4 6/19 : SCST internal library functions

In reply to Vladislav Bolkhovitin

This patch contains SCST internal library functions. For instance to create and destroy various internal objects, like commands, targets, sessions, etc.

---
 scst_lib.c | 7007 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 7007 insertions(+)
drivers/scst/scst_lib.c renamed from linux-2.6.35/drivers/scst/scst_lib.c +7007 -0
diff -uprN orig/linux-2.6.35/drivers/scst/scst_lib.c linux-2.6.35/drivers/scst/scst_lib.c
@@ -0,0 +1,7007 @@
+/*
+ *  scst_lib.c
+ *
+ *  Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <[email protected]>
+ *  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 <linux/vmalloc.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 {
+	void *data;
+	void (*done)(void *data, char *sense, int result, int resid);
+	char sense[SCST_SENSE_BUFFERSIZE];
+};
+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_IMPLICIT_ORDERED, 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},
+	{0x42, "O               ", "UNMAP",
+	 SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 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_IMPLICIT_ORDERED, 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_IMPLICIT_ORDERED, 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);
+
+	/* To protect sess_tgt_dev_list */
+	mutex_lock(&scst_mutex);
+
+	for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+		struct list_head *head = &sess->sess_tgt_dev_list[i];
+		struct scst_tgt_dev *tgt_dev;
+
+		list_for_each_entry(tgt_dev, 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);
+
+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;
+}
+
+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 *head;
+	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 < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+		head = &sess->sess_tgt_dev_list[i];
+
+		list_for_each_entry(tgt_dev, head,
+				sess_tgt_dev_list_entry) {
+			/* Lockdep triggers here a false positive.. */
+			spin_lock(&tgt_dev->tgt_dev_lock);
+		}
+	}
+
+	for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+		head = &sess->sess_tgt_dev_list[i];
+
+		list_for_each_entry(tgt_dev, head, 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 = SESS_TGT_DEV_LIST_HASH_SIZE-1; i >= 0; i--) {
+		head = &sess->sess_tgt_dev_list[i];
+
+		list_for_each_entry_reverse(tgt_dev, head,
+						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 < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+		struct list_head *head;
+		struct scst_tgt_dev *tgt_dev;
+
+		head = &sess->sess_tgt_dev_list[i];
+
+		list_for_each_entry(tgt_dev, head,
+				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 *head;
+		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 */
+		head = &aen->sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(lun)];
+		list_for_each_entry(tgt_dev, head,
+				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 *head;
+	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 < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+			head = &sess->sess_tgt_dev_list[i];
+
+			list_for_each_entry(tgt_dev, head,
+					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 < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+		struct scst_tgt_dev *t;
+		head = &sess->sess_tgt_dev_list[i];
+
+		list_for_each_entry_safe(tgt_dev, t, head,
+					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 < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
+			head = &sess->sess_tgt_dev_list[i];
+
+			list_for_each_entry(tgt_dev, head,
+					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)
+{
+
+	if (!cmd->expected_values_set) {
+		cmd->adjusted_resp_data_len = cmd->resp_data_len;
+		goto out;
+	}
+
+	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);
+	}
+
+out:
+	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);
+	INIT_LIST_HEAD(&dev->blocked_cmd_list);
+	INIT_LIST_HEAD(&dev->dev_tgt_dev_list);
+	INIT_LIST_HEAD(&dev->dev_acg_dev_list);
+	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);
+	cpumask_copy(&acg->acg_cpu_mask, &default_cpu_mask);
+	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 */
[… more lines in the raw message]

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Part of the diff is not shown here. View the raw message.

#7 lore raw

PATCH v4 7/19 : SCST Persistent Reservations implementation

In reply to Vladislav Bolkhovitin

This patch contains SCST Persistent Reservations implementation.

SCST implements Persistent Reservations with full set of capabilities, including "Persistence Through Power Loss".

The "Persistence Through Power Loss" data are saved in /var/lib/scst/pr with files with names the same as the names of the corresponding devices. Also this directory contains backup versions of those files with suffix ".1". Those backup files are used in case of power or other failure to prevent Persistent Reservation information from corruption during update.

---
 scst_pres.c | 2498 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scst_pres.h |  159 +++
 2 files changed, 2657 insertions(+)
drivers/scst/scst_pres.c renamed from linux-2.6.35/drivers/scst/scst_pres.c +2498 -0
diff -uprN orig/linux-2.6.35/drivers/scst/scst_pres.c linux-2.6.35/drivers/scst/scst_pres.c
@@ -0,0 +1,2498 @@
+/*
+ *  scst_pres.c
+ *
+ *  Copyright (C) 2009 - 2010 Alexey Obitotskiy <[email protected]>
+ *  Copyright (C) 2009 - 2010 Open-E, Inc.
+ *  Copyright (C) 2009 - 2010 Vladislav Bolkhovitin <[email protected]>
+ *
+ *  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/smp_lock.h>
+#include <linux/unistd.h>
+#include <linux/string.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/ctype.h>
+#include <asm/byteorder.h>
+#include <linux/syscalls.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/fcntl.h>
+#include <linux/uaccess.h>
+#include <linux/namei.h>
+#include <linux/version.h>
+#include <linux/vmalloc.h>
+#include <asm/unaligned.h>
+
+#include <scst/scst.h>
+#include <scst/scst_const.h>
+#include "scst_priv.h"
+#include "scst_pres.h"
+
+#define SCST_PR_ROOT_ENTRY	"pr"
+#define SCST_PR_FILE_SIGN	0xBBEEEEAAEEBBDD77LLU
+#define SCST_PR_FILE_VERSION	1LLU
+
+#define FILE_BUFFER_SIZE	512
+
+#ifndef isblank
+#define isblank(c)		((c) == ' ' || (c) == '\t')
+#endif
+
+static inline int tid_size(const uint8_t *tid)
+{
+	BUG_ON(tid == NULL);
+
+	if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI)
+		return be16_to_cpu(get_unaligned((__be16 *)&tid[2])) + 4;
+	else
+		return TID_COMMON_SIZE;
+}
+
+/* Secures tid by setting 0 in the last byte of NULL-terminated tid's */
+static inline void tid_secure(uint8_t *tid)
+{
+	if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) {
+		int size = tid_size(tid);
+		tid[size - 1] = '\0';
+	}
+
+	return;
+}
+
+/* Returns false if tid's are not equal, true otherwise */
+static bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b)
+{
+	int len;
+
+	if (tid_a == NULL || tid_b == NULL)
+		return false;
+
+	if ((tid_a[0] & 0x0f) != (tid_b[0] & 0x0f)) {
+		TRACE_DBG("%s", "Different protocol IDs");
+		return false;
+	}
+
+	if ((tid_a[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) {
+		const uint8_t tid_a_fmt = tid_a[0] & 0xc0;
+		const uint8_t tid_b_fmt = tid_b[0] & 0xc0;
+		int tid_a_len, tid_a_max = tid_size(tid_a) - 4;
+		int tid_b_len, tid_b_max = tid_size(tid_b) - 4;
+		int i;
+
+		tid_a += 4;
+		tid_b += 4;
+
+		if (tid_a_fmt == 0x00)
+			tid_a_len = strnlen(tid_a, tid_a_max);
+		else if (tid_a_fmt == 0x40) {
+			if (tid_a_fmt != tid_b_fmt) {
+				uint8_t *p = strnchr(tid_a, tid_a_max, ',');
+				if (p == NULL)
+					goto out_error;
+				tid_a_len = p - tid_a;
+
+				BUG_ON(tid_a_len > tid_a_max);
+				BUG_ON(tid_a_len < 0);
+			} else
+				tid_a_len = strnlen(tid_a, tid_a_max);
+		} else
+			goto out_error;
+
+		if (tid_b_fmt == 0x00)
+			tid_b_len = strnlen(tid_b, tid_b_max);
+		else if (tid_b_fmt == 0x40) {
+			if (tid_a_fmt != tid_b_fmt) {
+				uint8_t *p = strnchr(tid_b, tid_b_max, ',');
+				if (p == NULL)
+					goto out_error;
+				tid_b_len = p - tid_b;
+
+				BUG_ON(tid_b_len > tid_b_max);
+				BUG_ON(tid_b_len < 0);
+			} else
+				tid_b_len = strnlen(tid_b, tid_b_max);
+		} else
+			goto out_error;
+
+		if (tid_a_len != tid_b_len)
+			return false;
+
+		len = tid_a_len;
+
+		/* ISCSI names are case insensitive */
+		for (i = 0; i < len; i++)
+			if (tolower(tid_a[i]) != tolower(tid_b[i]))
+				return false;
+		return true;
+	} else
+		len = TID_COMMON_SIZE;
+
+	return memcmp(tid_a, tid_b, len) == 0;
+
+out_error:
+	PRINT_ERROR("%s", "Invalid initiator port transport id");
+	return false;
+}
+
+/* Must be called under dev_pr_mutex */
+static inline void scst_pr_set_holder(struct scst_device *dev,
+	struct scst_dev_registrant *holder, uint8_t scope, uint8_t type)
+{
+	dev->pr_is_set = 1;
+	dev->pr_scope = scope;
+	dev->pr_type = type;
+	if (dev->pr_type != TYPE_EXCLUSIVE_ACCESS_ALL_REG &&
+	    dev->pr_type != TYPE_WRITE_EXCLUSIVE_ALL_REG)
+		dev->pr_holder = holder;
+}
+
+/* Must be called under dev_pr_mutex */
+static bool scst_pr_is_holder(struct scst_device *dev,
+	struct scst_dev_registrant *reg)
+{
+	bool res = false;
+
+	if (!dev->pr_is_set)
+		goto out;
+
+	if (dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG ||
+	    dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG) {
+		res = (reg != NULL);
+	} else
+		res = (dev->pr_holder == reg);
+
+out:
+	return res;
+}
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+
+/* Must be called under dev_pr_mutex */
+void scst_pr_dump_prs(struct scst_device *dev, bool force)
+{
+	if (!force) {
+#if defined(CONFIG_SCST_DEBUG)
+		if ((trace_flag & TRACE_PRES) == 0)
+#endif
+			goto out;
+	}
+
+	PRINT_INFO("Persistent reservations for device %s:", dev->virt_name);
+
+	if (list_empty(&dev->dev_registrants_list))
+		PRINT_INFO("%s", "  No registrants");
+	else {
+		struct scst_dev_registrant *reg;
+		int i = 0;
+		list_for_each_entry(reg, &dev->dev_registrants_list,
+					dev_registrants_list_entry) {
+			PRINT_INFO("  [%d] registrant %s/%d, key %016llx "
+				"(reg %p, tgt_dev %p)", i++,
+				debug_transport_id_to_initiator_name(
+					reg->transport_id),
+				reg->rel_tgt_id, reg->key, reg, reg->tgt_dev);
+		}
+	}
+
+	if (dev->pr_is_set) {
+		struct scst_dev_registrant *holder = dev->pr_holder;
+		if (holder != NULL)
+			PRINT_INFO("Reservation holder is %s/%d (key %016llx, "
+				"scope %x, type %x, reg %p, tgt_dev %p)",
+				debug_transport_id_to_initiator_name(
+							holder->transport_id),
+				holder->rel_tgt_id, holder->key, dev->pr_scope,
+				dev->pr_type, holder, holder->tgt_dev);
+		else
+			PRINT_INFO("All registrants are reservation holders "
+				"(scope %x, type %x)", dev->pr_scope,
+				dev->pr_type);
+	} else
+		PRINT_INFO("%s", "Not reserved");
+
+out:
+	return;
+}
+
+#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
+
+/* dev_pr_mutex must be locked */
+static void scst_pr_find_registrants_list_all(struct scst_device *dev,
+	struct scst_dev_registrant *exclude_reg, struct list_head *list)
+{
+	struct scst_dev_registrant *reg;
+
+	TRACE_PR("Finding all registered records for device '%s' "
+		"with exclude reg key %016llx",
+		dev->virt_name, exclude_reg->key);
+
+	list_for_each_entry(reg, &dev->dev_registrants_list,
+				dev_registrants_list_entry) {
+		if (reg == exclude_reg)
+			continue;
+		TRACE_PR("Adding registrant %s/%d (%p) to find list (key %016llx)",
+			debug_transport_id_to_initiator_name(reg->transport_id),
+			reg->rel_tgt_id, reg, reg->key);
+		list_add_tail(&reg->aux_list_entry, list);
+	}
+	return;
+}
+
+/* dev_pr_mutex must be locked */
+static void scst_pr_find_registrants_list_key(struct scst_device *dev,
+	__be64 key, struct list_head *list)
+{
+	struct scst_dev_registrant *reg;
+
+	TRACE_PR("Finding registrants for device '%s' with key %016llx",
+		dev->virt_name, key);
+
+	list_for_each_entry(reg, &dev->dev_registrants_list,
+				dev_registrants_list_entry) {
+		if (reg->key == key) {
+			TRACE_PR("Adding registrant %s/%d (%p) to the find "
+				"list (key %016llx)",
+				debug_transport_id_to_initiator_name(
+					reg->transport_id),
+				reg->rel_tgt_id, reg->tgt_dev, key);
+			list_add_tail(&reg->aux_list_entry, list);
+		}
+	}
+	return;
+}
+
+/* dev_pr_mutex must be locked */
+static struct scst_dev_registrant *scst_pr_find_reg(
+	struct scst_device *dev, const uint8_t *transport_id,
+	const uint16_t rel_tgt_id)
+{
+	struct scst_dev_registrant *reg, *res = NULL;
+
+	list_for_each_entry(reg, &dev->dev_registrants_list,
+				dev_registrants_list_entry) {
+		if ((reg->rel_tgt_id == rel_tgt_id) &&
+		    tid_equal(reg->transport_id, transport_id)) {
+			res = reg;
+			break;
+		}
+	}
+	return res;
+}
+
+/* Must be called under dev_pr_mutex */
+static void scst_pr_clear_reservation(struct scst_device *dev)
+{
+
+	WARN_ON(!dev->pr_is_set);
+
+	dev->pr_is_set = 0;
+	dev->pr_scope = SCOPE_LU;
+	dev->pr_type = TYPE_UNSPECIFIED;
+
+	dev->pr_holder = NULL;
+	return;
+}
+
+/* Must be called under dev_pr_mutex */
+static void scst_pr_clear_holder(struct scst_device *dev)
+{
+
+	WARN_ON(!dev->pr_is_set);
+
+	if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG ||
+	    dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) {
+		if (list_empty(&dev->dev_registrants_list))
+			scst_pr_clear_reservation(dev);
+	} else
+		scst_pr_clear_reservation(dev);
+
+	dev->pr_holder = NULL;
+	return;
+}
+
+/* Must be called under dev_pr_mutex */
+static struct scst_dev_registrant *scst_pr_add_registrant(
+	struct scst_device *dev, const uint8_t *transport_id,
+	const uint16_t rel_tgt_id, __be64 key,
+	bool dev_lock_locked)
+{
+	struct scst_dev_registrant *reg;
+	struct scst_tgt_dev *t;
+	gfp_t gfp_flags = dev_lock_locked ? GFP_ATOMIC : GFP_KERNEL;
+
+	BUG_ON(dev == NULL);
+	BUG_ON(transport_id == NULL);
+
+	TRACE_PR("Registering %s/%d (dev %s)",
+		debug_transport_id_to_initiator_name(transport_id),
+		rel_tgt_id, dev->virt_name);
+
+	reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id);
+	if (reg != NULL) {
+		/*
+		 * It might happen when a target driver would make >1 session
+		 * from the same initiator to the same target.
+		 */
+		PRINT_ERROR("Registrant %p/%d (dev %s) already exists!", reg,
+			rel_tgt_id, dev->virt_name);
+		PRINT_BUFFER("TransportID", transport_id, 24);
+		WARN_ON(1);
+		reg = NULL;
+		goto out;
+	}
+
+	reg = kzalloc(sizeof(*reg), gfp_flags);
+	if (reg == NULL) {
+		PRINT_ERROR("%s", "Unable to allocate registration record");
+		goto out;
+	}
+
+	reg->transport_id = kmalloc(tid_size(transport_id), gfp_flags);
+	if (reg->transport_id == NULL) {
+		PRINT_ERROR("%s", "Unable to allocate initiator port "
+			"transport id");
+		goto out_free;
+	}
+	memcpy(reg->transport_id, transport_id, tid_size(transport_id));
+
+	reg->rel_tgt_id = rel_tgt_id;
+	reg->key = key;
+
+	/*
+	 * We can't use scst_mutex here, because of the circular
+	 * locking dependency with dev_pr_mutex.
+	 */
+	if (!dev_lock_locked)
+		spin_lock_bh(&dev->dev_lock);
+	list_for_each_entry(t, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) {
+		if (tid_equal(t->sess->transport_id, transport_id) &&
+		    (t->sess->tgt->rel_tgt_id == rel_tgt_id) &&
+		    (t->registrant == NULL)) {
+			/*
+			 * We must assign here, because t can die
+			 * immediately after we release dev_lock.
+			 */
+			TRACE_PR("Found tgt_dev %p", t);
+			reg->tgt_dev = t;
+			t->registrant = reg;
+			break;
+		}
+	}
+	if (!dev_lock_locked)
+		spin_unlock_bh(&dev->dev_lock);
+
+	list_add_tail(&reg->dev_registrants_list_entry,
+		&dev->dev_registrants_list);
+
+	TRACE_PR("Reg %p registered (dev %s, tgt_dev %p)", reg,
+		dev->virt_name, reg->tgt_dev);
+
+out:
+	return reg;
+
+out_free:
+	kfree(reg);
+	reg = NULL;
+	goto out;
+}
+
+/* Must be called under dev_pr_mutex */
+static void scst_pr_remove_registrant(struct scst_device *dev,
+	struct scst_dev_registrant *reg)
+{
+
+	TRACE_PR("Removing registrant %s/%d (reg %p, tgt_dev %p, key %016llx, "
+		"dev %s)", debug_transport_id_to_initiator_name(reg->transport_id),
+		reg->rel_tgt_id, reg, reg->tgt_dev, reg->key, dev->virt_name);
+
+	list_del(&reg->dev_registrants_list_entry);
+
+	if (scst_pr_is_holder(dev, reg))
+		scst_pr_clear_holder(dev);
+
+	if (reg->tgt_dev)
+		reg->tgt_dev->registrant = NULL;
+
+	kfree(reg->transport_id);
+	kfree(reg);
+	return;
+}
+
+/* Must be called under dev_pr_mutex */
+static void scst_pr_send_ua_reg(struct scst_device *dev,
+	struct scst_dev_registrant *reg,
+	int key, int asc, int ascq)
+{
+	static uint8_t ua[SCST_STANDARD_SENSE_LEN];
+
+	scst_set_sense(ua, sizeof(ua), dev->d_sense, key, asc, ascq);
+
+	TRACE_PR("Queuing UA [%x %x %x]: registrant %s/%d (%p), tgt_dev %p, "
+		"key %016llx", ua[2], ua[12], ua[13],
+		debug_transport_id_to_initiator_name(reg->transport_id),
+		reg->rel_tgt_id, reg, reg->tgt_dev, reg->key);
+
+	if (reg->tgt_dev)
+		scst_check_set_UA(reg->tgt_dev, ua, sizeof(ua), 0);
+	return;
+}
+
+/* Must be called under dev_pr_mutex */
+static void scst_pr_send_ua_all(struct scst_device *dev,
+	struct scst_dev_registrant *exclude_reg,
+	int key, int asc, int ascq)
+{
+	struct scst_dev_registrant *reg;
+
+	list_for_each_entry(reg, &dev->dev_registrants_list,
+				dev_registrants_list_entry) {
+		if (reg != exclude_reg)
+			scst_pr_send_ua_reg(dev, reg, key, asc, ascq);
+	}
+	return;
+}
+
+/* Must be called under dev_pr_mutex */
+static void scst_pr_abort_reg(struct scst_device *dev,
+	struct scst_cmd *pr_cmd, struct scst_dev_registrant *reg)
+{
+	struct scst_session *sess;
+	__be64 packed_lun;
+	int rc;
+
+	if (reg->tgt_dev == NULL) {
+		TRACE_PR("Registrant %s/%d (%p, key 0x%016llx) has no session",
+			debug_transport_id_to_initiator_name(reg->transport_id),
+			reg->rel_tgt_id, reg, reg->key);
+		goto out;
+	}
+
+	sess = reg->tgt_dev->sess;
+
+	TRACE_PR("Aborting %d commands for %s/%d (reg %p, key 0x%016llx, "
+		"tgt_dev %p, sess %p)",
+		atomic_read(&reg->tgt_dev->tgt_dev_cmd_count),
+		debug_transport_id_to_initiator_name(reg->transport_id),
+		reg->rel_tgt_id, reg, reg->key, reg->tgt_dev, sess);
+
+	packed_lun = scst_pack_lun(reg->tgt_dev->lun, sess->acg->addr_method);
+
+	rc = scst_rx_mgmt_fn_lun(sess, SCST_PR_ABORT_ALL,
+		(uint8_t *)&packed_lun, sizeof(packed_lun), SCST_NON_ATOMIC,
+		pr_cmd);
+	if (rc != 0) {
+		/*
+		 * There's nothing more we can do here... Hopefully, it would
+		 * never happen.
+		 */
+		PRINT_ERROR("SCST_PR_ABORT_ALL failed %d (sess %p)",
+			rc, sess);
+	}
+
+out:
+	return;
+}
+
+/* Abstract vfs_unlink & path_put for different kernel versions */
+static inline void scst_pr_vfs_unlink_and_put(struct nameidata *nd)
+{
+	vfs_unlink(nd->path.dentry->d_parent->d_inode,
+		nd->path.dentry);
+	path_put(&nd->path);
+}
+
+static inline void scst_pr_path_put(struct nameidata *nd)
+{
+	path_put(&nd->path);
+}
+
+/* Called under scst_mutex */
+static int scst_pr_do_load_device_file(struct scst_device *dev,
+	const char *file_name)
+{
+	int res = 0, rc;
+	struct file *file = NULL;
+	struct inode *inode;
+	char *buf = NULL;
+	loff_t file_size, pos, data_size;
+	uint64_t sign, version;
+	mm_segment_t old_fs;
+	uint8_t pr_is_set, aptpl;
+	__be64 key;
+	uint16_t rel_tgt_id;
+
+	old_fs = get_fs();
+	set_fs(KERNEL_DS);
+
+	TRACE_PR("Loading persistent file '%s'", file_name);
+
+	file = filp_open(file_name, O_RDONLY, 0);
+	if (IS_ERR(file)) {
+		res = PTR_ERR(file);
+		TRACE_PR("Unable to open file '%s' - error %d", file_name, res);
+		goto out;
+	}
+
+	inode = file->f_dentry->d_inode;
+
+	if (S_ISREG(inode->i_mode))
+		/* Nothing to do */;
+	else if (S_ISBLK(inode->i_mode))
+		inode = inode->i_bdev->bd_inode;
+	else {
+		PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode);
+		goto out_close;
+	}
+
+	file_size = inode->i_size;
+
+	/* Let's limit the file size by some reasonable number */
+	if ((file_size == 0) || (file_size >= 15*1024*1024)) {
+		PRINT_ERROR("Invalid PR file size %d", (int)file_size);
+		res = -EINVAL;
+		goto out_close;
+	}
+
+	buf = vmalloc(file_size);
+	if (buf == NULL) {
+		res = -ENOMEM;
+		PRINT_ERROR("%s", "Unable to allocate buffer");
+		goto out_close;
+	}
+
+	pos = 0;
+	rc = vfs_read(file, (void __force __user *)buf, file_size, &pos);
+	if (rc != file_size) {
+		PRINT_ERROR("Unable to read file '%s' - error %d", file_name,
+			rc);
+		res = rc;
+		goto out_close;
+	}
+
+	data_size = 0;
+	data_size += sizeof(sign);
+	data_size += sizeof(version);
+	data_size += sizeof(aptpl);
+	data_size += sizeof(pr_is_set);
+	data_size += sizeof(dev->pr_type);
+	data_size += sizeof(dev->pr_scope);
+
+	if (file_size < data_size) {
+		res = -EINVAL;
+		PRINT_ERROR("Invalid file '%s' - size too small", file_name);
+		goto out_close;
+	}
+
+	pos = 0;
+
+	sign = get_unaligned((uint64_t *)&buf[pos]);
+	if (sign != SCST_PR_FILE_SIGN) {
+		res = -EINVAL;
+		PRINT_ERROR("Invalid persistent file signature %016llx "
+			"(expected %016llx)", sign, SCST_PR_FILE_SIGN);
+		goto out_close;
+	}
+	pos += sizeof(sign);
+
+	version = get_unaligned((uint64_t *)&buf[pos]);
+	if (version != SCST_PR_FILE_VERSION) {
+		res = -EINVAL;
+		PRINT_ERROR("Invalid persistent file version %016llx "
+			"(expected %016llx)", version, SCST_PR_FILE_VERSION);
+		goto out_close;
+	}
+	pos += sizeof(version);
+
+	while (data_size < file_size) {
+		uint8_t *tid;
+
+		data_size++;
+		tid = &buf[data_size];
+		data_size += tid_size(tid);
+		data_size += sizeof(key);
+		data_size += sizeof(rel_tgt_id);
+
+		if (data_size > file_size) {
+			res = -EINVAL;
+			PRINT_ERROR("Invalid file '%s' - size mismatch have "
+				"%lld expected %lld", file_name, file_size,
+				data_size);
+			goto out_close;
+		}
+	}
+
+	aptpl = buf[pos];
+	dev->pr_aptpl = aptpl ? 1 : 0;
+	pos += sizeof(aptpl);
+
+	pr_is_set = buf[pos];
+	dev->pr_is_set = pr_is_set ? 1 : 0;
+	pos += sizeof(pr_is_set);
+
+	dev->pr_type = buf[pos];
+	pos += sizeof(dev->pr_type);
+
+	dev->pr_scope = buf[pos];
+	pos += sizeof(dev->pr_scope);
+
+	while (pos < file_size) {
+		uint8_t is_holder;
+		uint8_t *tid;
+		struct scst_dev_registrant *reg = NULL;
+
+		is_holder = buf[pos++];
+
+		tid = &buf[pos];
+		pos += tid_size(tid);
+
+		key = get_unaligned((__be64 *)&buf[pos]);
+		pos += sizeof(key);
+
+		rel_tgt_id = get_unaligned((uint16_t *)&buf[pos]);
+		pos += sizeof(rel_tgt_id);
+
+		reg = scst_pr_add_registrant(dev, tid, rel_tgt_id, key, false);
+		if (reg == NULL) {
+			res = -ENOMEM;
+			goto out_close;
+		}
+
+		if (is_holder)
+			dev->pr_holder = reg;
+	}
+
+out_close:
+	filp_close(file, NULL);
+
+out:
+	if (buf != NULL)
+		vfree(buf);
+
+	set_fs(old_fs);
+	return res;
+}
+
+static int scst_pr_load_device_file(struct scst_device *dev)
+{
+	int res;
+
+	if (dev->pr_file_name == NULL || dev->pr_file_name1 == NULL) {
+		PRINT_ERROR("Invalid file paths for '%s'", dev->virt_name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	res = scst_pr_do_load_device_file(dev, dev->pr_file_name);
+	if (res == 0)
+		goto out;
+	else if (res == -ENOMEM)
+		goto out;
+
+	res = scst_pr_do_load_device_file(dev, dev->pr_file_name1);
+
+	scst_pr_dump_prs(dev, false);
+
+out:
+	return res;
+}
+
+static int scst_pr_copy_file(const char *src, const char *dest)
+{
+	int res = 0;
+	struct inode *inode;
+	loff_t file_size, pos;
+	uint8_t *buf = NULL;
+	struct file *file_src = NULL, *file_dest = NULL;
+	mm_segment_t old_fs = get_fs();
+
+	if (src == NULL || dest == NULL) {
+		res = -EINVAL;
+		PRINT_ERROR("%s", "Invalid persistent files path - backup "
+			"skipped");
+		goto out;
+	}
+
+	TRACE_PR("Copying '%s' into '%s'", src, dest);
+
+	set_fs(KERNEL_DS);
+
+	file_src = filp_open(src, O_RDONLY, 0);
+	if (IS_ERR(file_src)) {
+		res = PTR_ERR(file_src);
+		TRACE_PR("Unable to open file '%s' - error %d", src,
+			res);
+		goto out_free;
+	}
+
+	file_dest = filp_open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+	if (IS_ERR(file_dest)) {
+		res = PTR_ERR(file_dest);
+		TRACE_PR("Unable to open backup file '%s' - error %d", dest,
+			res);
+		goto out_close;
+	}
+
+	inode = file_src->f_dentry->d_inode;
+
+	if (S_ISREG(inode->i_mode))
+		/* Nothing to do */;
+	else if (S_ISBLK(inode->i_mode))
+		inode = inode->i_bdev->bd_inode;
+	else {
+		PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode);
+		res = -EINVAL;
+		set_fs(old_fs);
+		goto out_skip;
+	}
+
+	file_size = inode->i_size;
+
+	buf = vmalloc(file_size);
+	if (buf == NULL) {
+		res = -ENOMEM;
+		PRINT_ERROR("%s", "Unable to allocate temporary buffer");
+		goto out_skip;
+	}
+
+	pos = 0;
+	res = vfs_read(file_src, (void __force __user *)buf, file_size, &pos);
+	if (res != file_size) {
+		PRINT_ERROR("Unable to read file '%s' - error %d", src, res);
+		goto out_skip;
+	}
+
+	pos = 0;
+	res = vfs_write(file_dest, (void __force __user *)buf, file_size, &pos);
+	if (res != file_size) {
+		PRINT_ERROR("Unable to write to '%s' - error %d", dest, res);
+		goto out_skip;
+	}
+
+	res = vfs_fsync(file_dest, 0);
+	if (res != 0) {
+		PRINT_ERROR("fsync() of the backup PR file failed: %d", res);
+		goto out_skip;
+	}
+
+out_skip:
+	filp_close(file_dest, NULL);
+
+out_close:
+	filp_close(file_src, NULL);
+
+out_free:
+	if (buf != NULL)
+		vfree(buf);
+
+	set_fs(old_fs);
+
+out:
+	return res;
+}
+
+static void scst_pr_remove_device_files(struct scst_tgt_dev *tgt_dev)
+{
+	int res = 0;
+	struct scst_device *dev = tgt_dev->dev;
+	struct nameidata nd;
+	mm_segment_t old_fs = get_fs();
+
+	set_fs(KERNEL_DS);
+
+	res = path_lookup(dev->pr_file_name, 0, &nd);
+	if (!res)
+		scst_pr_vfs_unlink_and_put(&nd);
+	else
+		TRACE_DBG("Unable to lookup file '%s' - error %d",
+			dev->pr_file_name, res);
+
+	res = path_lookup(dev->pr_file_name1, 0, &nd);
+	if (!res)
+		scst_pr_vfs_unlink_and_put(&nd);
+	else
+		TRACE_DBG("Unable to lookup file '%s' - error %d",
+			dev->pr_file_name1, res);
+
+	set_fs(old_fs);
+	return;
+}
+
+/* Must be called under dev_pr_mutex */
+void scst_pr_sync_device_file(struct scst_tgt_dev *tgt_dev, struct scst_cmd *cmd)
+{
+	int res = 0;
+	struct scst_device *dev = tgt_dev->dev;
+	struct file *file;
+	mm_segment_t old_fs = get_fs();
+	loff_t pos = 0;
+	uint64_t sign;
+	uint64_t version;
+	uint8_t pr_is_set, aptpl;
+
+	if ((dev->pr_aptpl == 0) || list_empty(&dev->dev_registrants_list)) {
+		scst_pr_remove_device_files(tgt_dev);
+		goto out;
+	}
+
+	scst_pr_copy_file(dev->pr_file_name, dev->pr_file_name1);
+
+	set_fs(KERNEL_DS);
+
+	file = filp_open(dev->pr_file_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+	if (IS_ERR(file)) {
+		res = PTR_ERR(file);
+		PRINT_ERROR("Unable to (re)create PR file '%s' - error %d",
+			dev->pr_file_name, res);
+		goto out_set_fs;
+	}
+
+	TRACE_PR("Updating pr file '%s'", dev->pr_file_name);
+
+	/*
+	 * signature
+	 */
+	sign = 0;
+	pos = 0;
+	res = vfs_write(file, (void __force __user *)&sign, sizeof(sign), &pos);
+	if (res != sizeof(sign))
+		goto write_error;
+
+	/*
+	 * version
+	 */
+	version = SCST_PR_FILE_VERSION;
+	res = vfs_write(file, (void __force __user *)&version, sizeof(version), &pos);
+	if (res != sizeof(version))
+		goto write_error;
+
+	/*
+	 * APTPL
+	 */
+	aptpl = dev->pr_aptpl;
+	res = vfs_write(file, (void __force __user *)&aptpl, sizeof(aptpl), &pos);
+	if (res != sizeof(aptpl))
+		goto write_error;
+
+	/*
+	 * reservation
+	 */
+	pr_is_set = dev->pr_is_set;
+	res = vfs_write(file, (void __force __user *)&pr_is_set, sizeof(pr_is_set), &pos);
+	if (res != sizeof(pr_is_set))
+		goto write_error;
+
+	res = vfs_write(file, (void __force __user *)&dev->pr_type, sizeof(dev->pr_type), &pos);
+	if (res != sizeof(dev->pr_type))
+		goto write_error;
+
+	res = vfs_write(file, (void __force __user *)&dev->pr_scope, sizeof(dev->pr_scope), &pos);
+	if (res != sizeof(dev->pr_scope))
+		goto write_error;
+
+	/*
+	 * registration records
+	 */
+	if (!list_empty(&dev->dev_registrants_list)) {
+		struct scst_dev_registrant *reg;
+
+		list_for_each_entry(reg, &dev->dev_registrants_list,
+					dev_registrants_list_entry) {
+			uint8_t is_holder = 0;
+			int size;
+
+			is_holder = (dev->pr_holder == reg);
+
+			res = vfs_write(file, (void __force __user *)&is_holder, sizeof(is_holder),
+					&pos);
+			if (res != sizeof(is_holder))
+				goto write_error;
+
+			size = tid_size(reg->transport_id);
+			res = vfs_write(file, (void __force __user *)reg->transport_id, size, &pos);
+			if (res != size)
+				goto write_error;
+
+			res = vfs_write(file, (void __force __user *)&reg->key,
+					sizeof(reg->key), &pos);
+			if (res != sizeof(reg->key))
+				goto write_error;
+
+			res = vfs_write(file, (void __force __user *)&reg->rel_tgt_id,
+					sizeof(reg->rel_tgt_id), &pos);
+			if (res != sizeof(reg->rel_tgt_id))
+				goto write_error;
+		}
+	}
+
+	res = vfs_fsync(file, 0);
+	if (res != 0) {
+		PRINT_ERROR("fsync() of the PR file failed: %d", res);
+		goto write_error_close;
+	}
+
+	sign = SCST_PR_FILE_SIGN;
+	pos = 0;
+	res = vfs_write(file, (void __force __user *)&sign, sizeof(sign), &pos);
+	if (res != sizeof(sign))
+		goto write_error;
+
+	res = vfs_fsync(file, 0);
+	if (res != 0) {
+		PRINT_ERROR("fsync() of the PR file failed: %d", res);
+		goto write_error_close;
+	}
+
+	res = 0;
+
+	filp_close(file, NULL);
+
+out_set_fs:
+	set_fs(old_fs);
+
+out:
+	if (res != 0) {
+		PRINT_CRIT_ERROR("Unable to save persistent information "
+			"(target %s, initiator %s, device %s)",
+			tgt_dev->sess->tgt->tgt_name,
+			tgt_dev->sess->initiator_name, dev->virt_name);
+#if 0	/*
+	 * Looks like it's safer to return SUCCESS and expect operator's
+	 * intervention to be able to save the PR's state next time, than
+	 * to return HARDWARE ERROR and screw up all the interaction with
+	 * the affected initiator.
+	 */
+	if (cmd != NULL)
+		scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+#endif
+	}
+	return;
+
+write_error:
+	PRINT_ERROR("Error writing to '%s' - error %d", dev->pr_file_name, res);
+
+write_error_close:
+	filp_close(file, NULL);
+	{
+		struct nameidata nd;
+		int rc;
+
+		rc = path_lookup(dev->pr_file_name, 0,	&nd);
+		if (!rc)
+			scst_pr_vfs_unlink_and_put(&nd);
+		else
+			TRACE_PR("Unable to lookup '%s' - error %d",
+				dev->pr_file_name, rc);
+	}
+	goto out_set_fs;
+}
+
+int scst_pr_check_pr_path(void)
+{
+	int res;
+	struct nameidata nd;
+	mm_segment_t old_fs = get_fs();
+
+	set_fs(KERNEL_DS);
+
+	res = path_lookup(SCST_PR_DIR, 0, &nd);
+	if (res != 0) {
+		PRINT_ERROR("Unable to find %s (err %d), you should create "
+			"this directory manually or reinstall SCST",
+			SCST_PR_DIR, res);
+		goto out_setfs;
+	}
+
+	scst_pr_path_put(&nd);
+
+out_setfs:
+	set_fs(old_fs);
+	return res;
+}
+
+/* Called under scst_mutex */
+int scst_pr_init_dev(struct scst_device *dev)
+{
+	int res = 0;
+	uint8_t q;
+	int name_len;
+
+	name_len = snprintf(&q, sizeof(q), "%s/%s", SCST_PR_DIR, dev->virt_name) + 1;
+	dev->pr_file_name = kmalloc(name_len, GFP_KERNEL);
+	if (dev->pr_file_name == NULL) {
+		PRINT_ERROR("Allocation of device '%s' file path failed",
+			dev->virt_name);
+		res = -ENOMEM;
+		goto out;
+	} else
+		snprintf(dev->pr_file_name, name_len, "%s/%s", SCST_PR_DIR,
+			dev->virt_name);
+
+	name_len = snprintf(&q, sizeof(q), "%s/%s.1", SCST_PR_DIR, dev->virt_name) + 1;
+	dev->pr_file_name1 = kmalloc(name_len, GFP_KERNEL);
+	if (dev->pr_file_name1 == NULL) {
+		PRINT_ERROR("Allocation of device '%s' backup file path failed",
+			dev->virt_name);
+		res = -ENOMEM;
+		goto out_free_name;
+	} else
+		snprintf(dev->pr_file_name1, name_len, "%s/%s.1", SCST_PR_DIR,
+			dev->virt_name);
+
+	res = scst_pr_load_device_file(dev);
+	if (res == -ENOENT)
+		res = 0;
+
+	if (res != 0)
+		goto out_free_name1;
+
+out:
+	return res;
+
+out_free_name1:
+	kfree(dev->pr_file_name1);
+	dev->pr_file_name1 = NULL;
+
+out_free_name:
+	kfree(dev->pr_file_name);
+	dev->pr_file_name = NULL;
+	goto out;
+}
+
+/* Called under scst_mutex */
+void scst_pr_clear_dev(struct scst_device *dev)
+{
+	struct scst_dev_registrant *reg, *tmp_reg;
+
+	list_for_each_entry_safe(reg, tmp_reg, &dev->dev_registrants_list,
+			dev_registrants_list_entry) {
+		scst_pr_remove_registrant(dev, reg);
+	}
+
+	kfree(dev->pr_file_name);
+	kfree(dev->pr_file_name1);
+	return;
+}
+
+/* Called under scst_mutex */
+int scst_pr_init_tgt_dev(struct scst_tgt_dev *tgt_dev)
+{
+	int res = 0;
+	struct scst_dev_registrant *reg;
+	struct scst_device *dev = tgt_dev->dev;
+	const uint8_t *transport_id = tgt_dev->sess->transport_id;
+	const uint16_t rel_tgt_id = tgt_dev->sess->tgt->rel_tgt_id;
+
+	if (tgt_dev->sess->transport_id == NULL)
+		goto out;
+
+	scst_pr_write_lock(dev);
+
+	reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id);
+	if ((reg != NULL) && (reg->tgt_dev == NULL)) {
+		TRACE_PR("Assigning reg %s/%d (%p) to tgt_dev %p (dev %s)",
+			debug_transport_id_to_initiator_name(transport_id),
+			rel_tgt_id, reg, tgt_dev, dev->virt_name);
+		tgt_dev->registrant = reg;
+		reg->tgt_dev = tgt_dev;
+	}
+
+	scst_pr_write_unlock(dev);
+
+out:
+	return res;
+}
+
+/* Called under scst_mutex */
+void scst_pr_clear_tgt_dev(struct scst_tgt_dev *tgt_dev)
+{
+
+	if (tgt_dev->registrant != NULL) {
+		struct scst_dev_registrant *reg = tgt_dev->registrant;
+		struct scst_device *dev = tgt_dev->dev;
+		struct scst_tgt_dev *t;
+
+		scst_pr_write_lock(dev);
+
+		tgt_dev->registrant = NULL;
+		reg->tgt_dev = NULL;
+
+		/* Just in case, actually. It should never happen. */
+		list_for_each_entry(t, &dev->dev_tgt_dev_list,
+					dev_tgt_dev_list_entry) {
+			if (t == tgt_dev)
+				continue;
+			if ((t->sess->tgt->rel_tgt_id == reg->rel_tgt_id) &&
+			    tid_equal(t->sess->transport_id, reg->transport_id)) {
+				TRACE_PR("Reassigning reg %s/%d (%p) to tgt_dev "
+					"%p (being cleared tgt_dev %p)",
+					debug_transport_id_to_initiator_name(
+						reg->transport_id),
+					reg->rel_tgt_id, reg, t, tgt_dev);
+				t->registrant = reg;
+				reg->tgt_dev = t;
+				break;
+			}
+		}
+
+		scst_pr_write_unlock(dev);
+	}
+	return;
+}
+
+/* Called with dev_pr_mutex locked. Might also be called under scst_mutex2. */
+static int scst_pr_register_with_spec_i_pt(struct scst_cmd *cmd,
+	const uint16_t rel_tgt_id, uint8_t *buffer, int buffer_size,
+	struct list_head *rollback_list)
+{
+	int res = 0;
+	int offset, ext_size;
+	__be64 action_key;
+	struct scst_device *dev = cmd->dev;
+	struct scst_dev_registrant *reg;
+	uint8_t *transport_id;
+
+	action_key = get_unaligned((__be64 *)&buffer[8]);
+
+	ext_size = be32_to_cpu(get_unaligned((__be32 *)&buffer[24]));
+	if ((ext_size + 28) > buffer_size) {
+		TRACE_PR("Invalid buffer size %d (max %d)", buffer_size,
+			ext_size + 28);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
+		res = -EINVAL;
+		goto out;
+	}
+
+	offset = 0;
+	while (offset < ext_size) {
+		transport_id = &buffer[28 + offset];
+
+		if ((offset + tid_size(transport_id)) > ext_size) {
+			TRACE_PR("Invalid transport_id size %d (max %d)",
+				tid_size(transport_id), ext_size - offset);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
+			res = -EINVAL;
+			goto out;
+		}
+		tid_secure(transport_id);
+		offset += tid_size(transport_id);
+	}
+
+	offset = 0;
+	while (offset < ext_size) {
+		struct scst_tgt_dev *t;
+
+		transport_id = &buffer[28 + offset];
+
+		TRACE_PR("rel_tgt_id %d, transport_id %s", rel_tgt_id,
+			debug_transport_id_to_initiator_name(transport_id));
+
+		if ((transport_id[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI &&
+		    (transport_id[0] & 0xc0) == 0) {
+			TRACE_PR("Wildcard iSCSI TransportID %s",
+				&transport_id[4]);
+			/*
+			 * We can't use scst_mutex here, because of the
+			 * circular locking dependency with dev_pr_mutex.
+			 */
+			spin_lock_bh(&dev->dev_lock);
+			list_for_each_entry(t, &dev->dev_tgt_dev_list,
+						dev_tgt_dev_list_entry) {
+				/*
+				 * We must go over all matching tgt_devs and
+				 * register them on the requested rel_tgt_id
+				 */
+				if (!tid_equal(t->sess->transport_id,
+						transport_id))
+					continue;
+
+				reg = scst_pr_find_reg(dev,
+					t->sess->transport_id, rel_tgt_id);
+				if (reg == NULL) {
+					reg = scst_pr_add_registrant(dev,
+						t->sess->transport_id,
+						rel_tgt_id, action_key, true);
+					if (reg == NULL) {
+						spin_unlock_bh(&dev->dev_lock);
+						scst_set_busy(cmd);
+						res = -ENOMEM;
+						goto out;
+					}
+				} else if (reg->key != action_key) {
+					TRACE_PR("Changing key of reg %p "
+						"(tgt_dev %p)", reg, t);
+					reg->rollback_key = reg->key;
+					reg->key = action_key;
+				} else
+					continue;
+
+				list_add_tail(&reg->aux_list_entry,
+					rollback_list);
+			}
+			spin_unlock_bh(&dev->dev_lock);
+		} else {
+			reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id);
+			if (reg != NULL) {
+				if (reg->key == action_key)
+					goto next;
+				TRACE_PR("Changing key of reg %p (tgt_dev %p)",
+					reg, reg->tgt_dev);
+				reg->rollback_key = reg->key;
+				reg->key = action_key;
+			} else {
+				reg = scst_pr_add_registrant(dev, transport_id,
+						rel_tgt_id, action_key, false);
+				if (reg == NULL) {
+					scst_set_busy(cmd);
+					res = -ENOMEM;
+					goto out;
+				}
+			}
+
+			list_add_tail(&reg->aux_list_entry,
+				rollback_list);
+		}
+next:
+		offset += tid_size(transport_id);
+	}
+out:
+	return res;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+static void scst_pr_unregister(struct scst_device *dev,
+	struct scst_dev_registrant *reg)
+{
+	bool is_holder;
+	uint8_t pr_type;
+
+	TRACE_PR("Unregistering key %0llx", reg->key);
+
+	is_holder = scst_pr_is_holder(dev, reg);
+	pr_type = dev->pr_type;
+
+	scst_pr_remove_registrant(dev, reg);
+
+	if (is_holder && !dev->pr_is_set) {
+		/* A registration just released */
+		switch (pr_type) {
+		case TYPE_WRITE_EXCLUSIVE_REGONLY:
+		case TYPE_EXCLUSIVE_ACCESS_REGONLY:
+			scst_pr_send_ua_all(dev, NULL,
+				SCST_LOAD_SENSE(scst_sense_reservation_released));
+			break;
+		}
+	}
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+static void scst_pr_unregister_all_tg_pt(struct scst_device *dev,
+	const uint8_t *transport_id)
+{
+	struct scst_tgt_template *tgtt;
+	uint8_t proto_id = transport_id[0] & 0x0f;
+
+	/*
+	 * We can't use scst_mutex here, because of the circular locking
+	 * dependency with dev_pr_mutex.
+	 */
+	mutex_lock(&scst_mutex2);
+
+	list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) {
+		struct scst_tgt *tgt;
+
+		if (tgtt->get_initiator_port_transport_id == NULL)
+			continue;
+
+		if (tgtt->get_initiator_port_transport_id(NULL, NULL) != proto_id)
+			continue;
+
+		list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
+			struct scst_dev_registrant *reg;
+
+			reg = scst_pr_find_reg(dev, transport_id,
+					tgt->rel_tgt_id);
+			if (reg == NULL)
+				continue;
+
+			scst_pr_unregister(dev, reg);
+		}
+	}
+
+	mutex_unlock(&scst_mutex2);
+	return;
+}
+
+/* Called with dev_pr_mutex locked. Might also be called under scst_mutex2. */
+static int scst_pr_register_on_tgt_id(struct scst_cmd *cmd,
+	const uint16_t rel_tgt_id, uint8_t *buffer, int buffer_size,
+	bool spec_i_pt, struct list_head *rollback_list)
+{
+	int res;
+
+	TRACE_PR("rel_tgt_id %d, spec_i_pt %d", rel_tgt_id, spec_i_pt);
+
+	if (spec_i_pt) {
+		res = scst_pr_register_with_spec_i_pt(cmd, rel_tgt_id, buffer,
+					buffer_size, rollback_list);
+		if (res != 0)
+			goto out;
+	}
+
+	/* tgt_dev can be among TIDs for scst_pr_register_with_spec_i_pt() */
+
+	if (scst_pr_find_reg(cmd->dev, cmd->sess->transport_id, rel_tgt_id) == NULL) {
+		__be64 action_key;
+		struct scst_dev_registrant *reg;
+
+		action_key = get_unaligned((__be64 *)&buffer[8]);
+
+		reg = scst_pr_add_registrant(cmd->dev, cmd->sess->transport_id,
+			rel_tgt_id, action_key, false);
+		if (reg == NULL) {
+			res = -ENOMEM;
+			scst_set_busy(cmd);
+			goto out;
+		}
+
+		list_add_tail(&reg->aux_list_entry, rollback_list);
+	}
+
+out:
+	return res;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+static int scst_pr_register_all_tg_pt(struct scst_cmd *cmd, uint8_t *buffer,
+	int buffer_size, bool spec_i_pt, struct list_head *rollback_list)
+{
+	int res = 0;
+	struct scst_tgt_template *tgtt;
+	uint8_t proto_id = cmd->sess->transport_id[0] & 0x0f;
+
+	/*
+	 * We can't use scst_mutex here, because of the circular locking
+	 * dependency with dev_pr_mutex.
+	 */
+	mutex_lock(&scst_mutex2);
+
+	list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) {
+		struct scst_tgt *tgt;
+
+		if (tgtt->get_initiator_port_transport_id == NULL)
+			continue;
+
+		if (tgtt->get_initiator_port_transport_id(NULL, NULL) != proto_id)
+			continue;
+
+		TRACE_PR("tgtt %s, spec_i_pt %d", tgtt->name, spec_i_pt);
+
+		list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
+			if (tgt->rel_tgt_id == 0)
+				continue;
+			TRACE_PR("tgt %s, rel_tgt_id %d", tgt->tgt_name,
+				tgt->rel_tgt_id);
+			res = scst_pr_register_on_tgt_id(cmd, tgt->rel_tgt_id,
+				buffer, buffer_size, spec_i_pt, rollback_list);
+			if (res != 0)
+				goto out_unlock;
+		}
+	}
+
+out_unlock:
+	mutex_unlock(&scst_mutex2);
+	return res;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+static int __scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer,
+	int buffer_size, bool spec_i_pt, bool all_tg_pt)
+{
+	int res;
+	struct scst_dev_registrant *reg, *treg;
+	LIST_HEAD(rollback_list);
+
+	if (all_tg_pt) {
+		res = scst_pr_register_all_tg_pt(cmd, buffer, buffer_size,
+				spec_i_pt, &rollback_list);
+		if (res != 0)
+			goto out_rollback;
+	} else {
+		res = scst_pr_register_on_tgt_id(cmd,
+			cmd->sess->tgt->rel_tgt_id, buffer, buffer_size,
+			spec_i_pt, &rollback_list);
+		if (res != 0)
+			goto out_rollback;
+	}
+
+	list_for_each_entry(reg, &rollback_list, aux_list_entry) {
+		reg->rollback_key = 0;
+	}
+
+out:
+	return res;
+
+out_rollback:
+	list_for_each_entry_safe(reg, treg, &rollback_list, aux_list_entry) {
+		list_del(&reg->aux_list_entry);
+		if (reg->rollback_key == 0)
+			scst_pr_remove_registrant(cmd->dev, reg);
+		else {
+			reg->key = reg->rollback_key;
+			reg->rollback_key = 0;
+		}
+	}
+	goto out;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
+{
+	int aptpl, spec_i_pt, all_tg_pt;
+	__be64 key, action_key;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_session *sess = cmd->sess;
+	struct scst_dev_registrant *reg;
+
+	aptpl = buffer[20] & 0x01;
+	spec_i_pt = (buffer[20] >> 3) & 0x01;
+	all_tg_pt = (buffer[20] >> 2) & 0x01;
+	key = get_unaligned((__be64 *)&buffer[0]);
+	action_key = get_unaligned((__be64 *)&buffer[8]);
+
+	if (spec_i_pt == 0 && buffer_size != 24) {
+		TRACE_PR("Invalid buffer size %d", buffer_size);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
+		goto out;
+	}
+
+	reg = tgt_dev->registrant;
+
+	TRACE_PR("Register: initiator %s/%d (%p), key %0llx, action_key %0llx "
+		"(tgt_dev %p)",
+		debug_transport_id_to_initiator_name(sess->transport_id),
+		sess->tgt->rel_tgt_id, reg, key, action_key, tgt_dev);
+
+	if (reg == NULL) {
+		TRACE_PR("tgt_dev %p is not registered yet - registering",
+			tgt_dev);
+		if (key) {
+			TRACE_PR("%s", "Key must be zero on new registration");
+			scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+			goto out;
+		}
+		if (action_key) {
+			int rc = __scst_pr_register(cmd, buffer, buffer_size,
+					spec_i_pt, all_tg_pt);
+			if (rc != 0)
+				goto out;
+		} else
+			TRACE_PR("%s", "Doing nothing - action_key is zero");
+	} else {
+		if (reg->key != key) {
+			TRACE_PR("tgt_dev %p already registered - reservation "
+				"key %0llx mismatch", tgt_dev, reg->key);
+			scst_set_cmd_error_status(cmd,
+				SAM_STAT_RESERVATION_CONFLICT);
+			goto out;
+		}
+		if (spec_i_pt) {
+			TRACE_PR("%s", "spec_i_pt must be zero in this case");
+			scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+				scst_sense_invalid_field_in_cdb));
+			goto out;
+		}
+		if (action_key == 0) {
+			if (all_tg_pt)
+				scst_pr_unregister_all_tg_pt(dev,
+					sess->transport_id);
+			else
+				scst_pr_unregister(dev, reg);
+		} else
+			reg->key = action_key;
+	}
+
+	dev->pr_generation++;
+
+	dev->pr_aptpl = aptpl;
+
+	scst_pr_dump_prs(dev, false);
+
+out:
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_register_and_ignore(struct scst_cmd *cmd, uint8_t *buffer,
+	int buffer_size)
+{
+	int aptpl, all_tg_pt;
+	__be64 action_key;
+	struct scst_dev_registrant *reg = NULL;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_session *sess = cmd->sess;
+
+	aptpl = buffer[20] & 0x01;
+	all_tg_pt = (buffer[20] >> 2) & 0x01;
+	action_key = get_unaligned((__be64 *)&buffer[8]);
+
+	if (buffer_size != 24) {
+		TRACE_PR("Invalid buffer size %d", buffer_size);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
+		goto out;
+	}
+
+	reg = tgt_dev->registrant;
+
+	TRACE_PR("Register and ignore: initiator %s/%d (%p), action_key "
+		"%016llx (tgt_dev %p)",
+		debug_transport_id_to_initiator_name(sess->transport_id),
+		sess->tgt->rel_tgt_id, reg, action_key, tgt_dev);
+
+	if (reg == NULL) {
+		TRACE_PR("Tgt_dev %p is not registered yet - trying to "
+			"register", tgt_dev);
+		if (action_key) {
+			int rc = __scst_pr_register(cmd, buffer, buffer_size,
+					false, all_tg_pt);
+			if (rc != 0)
+				goto out;
+		} else
+			TRACE_PR("%s", "Doing nothing, action_key is zero");
+	} else {
+		if (action_key == 0) {
+			if (all_tg_pt)
+				scst_pr_unregister_all_tg_pt(dev,
+					sess->transport_id);
+			else
+				scst_pr_unregister(dev, reg);
+		} else
+			reg->key = action_key;
+	}
+
+	dev->pr_generation++;
+
+	dev->pr_aptpl = aptpl;
+
+	scst_pr_dump_prs(dev, false);
+
+out:
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_register_and_move(struct scst_cmd *cmd, uint8_t *buffer,
+	int buffer_size)
+{
+	int aptpl;
+	int unreg;
+	int tid_buffer_size;
+	__be64 key, action_key;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_session *sess = cmd->sess;
+	struct scst_dev_registrant *reg, *reg_move;
+	const uint8_t *transport_id = NULL;
+	uint8_t *transport_id_move = NULL;
+	uint16_t rel_tgt_id_move;
+
+	aptpl = buffer[17] & 0x01;
+	key = get_unaligned((__be64 *)&buffer[0]);
+	action_key = get_unaligned((__be64 *)&buffer[8]);
+	unreg = (buffer[17] >> 1) & 0x01;
+	tid_buffer_size = be32_to_cpu(get_unaligned((__be32 *)&buffer[20]));
+
+	if ((tid_buffer_size + 24) > buffer_size) {
+		TRACE_PR("Invalid buffer size %d (%d)",
+			buffer_size, tid_buffer_size + 24);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
+		goto out;
+	}
+
+	if (tid_buffer_size < 24) {
+		TRACE_PR("%s", "Transport id buffer too small");
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
+		goto out;
+	}
+
+	reg = tgt_dev->registrant;
+	/* We already checked reg is not NULL */
+	if (reg->key != key) {
+		TRACE_PR("Registrant's %s/%d (%p) key %016llx mismatch with "
+			"%016llx (tgt_dev %p)",
+			debug_transport_id_to_initiator_name(reg->transport_id),
+			reg->rel_tgt_id, reg, reg->key, key, tgt_dev);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out;
+	}
+
+	if (!dev->pr_is_set) {
+		TRACE_PR("%s", "There must be a PR");
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out;
+	}
+
+	/*
+	 * This check also required by table "PERSISTENT RESERVE OUT service
+	 * actions that are allowed in the presence of various reservations".
+	 */
+	if (!scst_pr_is_holder(dev, reg)) {
+		TRACE_PR("Registrant %s/%d (%p) is not a holder (tgt_dev %p)",
+			debug_transport_id_to_initiator_name(
+				reg->transport_id), reg->rel_tgt_id,
+			reg, tgt_dev);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out;
+	}
+
+	if (action_key == 0) {
+		TRACE_PR("%s", "Action key must be non-zero");
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out;
+	}
+
+	transport_id = sess->transport_id;
+	transport_id_move = (uint8_t *)&buffer[24];
+	rel_tgt_id_move = be16_to_cpu(get_unaligned((__be16 *)&buffer[18]));
+
+	if ((tid_size(transport_id_move) + 24) > buffer_size) {
+		TRACE_PR("Invalid buffer size %d (%d)",
+			buffer_size, tid_size(transport_id_move) + 24);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
+		goto out;
+	}
+
+	tid_secure(transport_id_move);
+
+	if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG ||
+	    dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) {
+		TRACE_PR("Unable to finish operation due to wrong reservation "
+			"type %02x", dev->pr_type);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out;
+	}
+
+	if (tid_equal(transport_id, transport_id_move)) {
+		TRACE_PR("%s", "Equal transport id's");
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
+		goto out;
+	}
+
+	reg_move = scst_pr_find_reg(dev, transport_id_move, rel_tgt_id_move);
+	if (reg_move == NULL) {
+		reg_move = scst_pr_add_registrant(dev, transport_id_move,
+			rel_tgt_id_move, action_key, false);
+		if (reg_move == NULL) {
+			scst_set_busy(cmd);
+			goto out;
+		}
+	} else if (reg_move->key != action_key) {
+		TRACE_PR("Changing key for reg %p", reg);
+		reg_move->key = action_key;
+	}
+
+	TRACE_PR("Register and move: from initiator %s/%d (%p, tgt_dev %p) to "
+		"initiator %s/%d (%p, tgt_dev %p), key %016llx (unreg %d)",
+		debug_transport_id_to_initiator_name(reg->transport_id),
+		reg->rel_tgt_id, reg, reg->tgt_dev,
+		debug_transport_id_to_initiator_name(transport_id_move),
+		rel_tgt_id_move, reg_move, reg_move->tgt_dev, action_key,
+		unreg);
+
+	/* Move the holder */
+	scst_pr_set_holder(dev, reg_move, dev->pr_scope, dev->pr_type);
+
+	if (unreg)
+		scst_pr_remove_registrant(dev, reg);
+
+	dev->pr_generation++;
+
+	dev->pr_aptpl = aptpl;
+
+	scst_pr_dump_prs(dev, false);
+
+out:
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_reserve(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
+{
+	uint8_t scope, type;
+	__be64 key;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_dev_registrant *reg;
+
+	key = get_unaligned((__be64 *)&buffer[0]);
+	scope = (cmd->cdb[2] & 0x0f) >> 4;
+	type = cmd->cdb[2] & 0x0f;
+
+	if (buffer_size != 24) {
+		TRACE_PR("Invalid buffer size %d", buffer_size);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
+		goto out;
+	}
+
+	if (!scst_pr_type_valid(type)) {
+		TRACE_PR("Invalid reservation type %d", type);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out;
+	}
+
+	if (((cmd->cdb[2] & 0x0f) >> 4) != SCOPE_LU) {
+		TRACE_PR("Invalid reservation scope %d", scope);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out;
+	}
+
+	reg = tgt_dev->registrant;
+
+	TRACE_PR("Reserve: initiator %s/%d (%p), key %016llx, scope %d, "
+		"type %d (tgt_dev %p)",
+		debug_transport_id_to_initiator_name(cmd->sess->transport_id),
+		cmd->sess->tgt->rel_tgt_id, reg, key, scope, type, tgt_dev);
+
+	/* We already checked reg is not NULL */
+	if (reg->key != key) {
+		TRACE_PR("Registrant's %p key %016llx mismatch with %016llx",
+			reg, reg->key, key);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out;
+	}
+
+	if (!dev->pr_is_set)
+		scst_pr_set_holder(dev, reg, scope, type);
+	else {
+		if (!scst_pr_is_holder(dev, reg)) {
+			/*
+			 * This check also required by table "PERSISTENT
+			 * RESERVE OUT service actions that are allowed in the
+			 * presence of various reservations".
+			 */
+			TRACE_PR("Only holder can override - reg %p is not a "
+				"holder", reg);
+			scst_set_cmd_error_status(cmd,
+				SAM_STAT_RESERVATION_CONFLICT);
+			goto out;
+		} else {
+			if (dev->pr_scope != scope || dev->pr_type != type) {
+				TRACE_PR("Error overriding scope or type for "
+					"reg %p", reg);
+				scst_set_cmd_error_status(cmd,
+					SAM_STAT_RESERVATION_CONFLICT);
+				goto out;
+			} else
+				TRACE_PR("Do nothing: reservation of reg %p "
+					"is the same", reg);
+		}
+	}
+
+	scst_pr_dump_prs(dev, false);
+
+out:
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_release(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
+{
+	int scope, type;
+	__be64 key;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_dev_registrant *reg;
+	uint8_t cur_pr_type;
+
+	key = get_unaligned((__be64 *)&buffer[0]);
+	scope = (cmd->cdb[2] & 0x0f) >> 4;
+	type = cmd->cdb[2] & 0x0f;
+
+	if (buffer_size != 24) {
+		TRACE_PR("Invalid buffer size %d", buffer_size);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
+		goto out;
+	}
+
+	if (!dev->pr_is_set) {
+		TRACE_PR("%s", "There is no PR - do nothing");
+		goto out;
+	}
+
+	reg = tgt_dev->registrant;
+
+	TRACE_PR("Release: initiator %s/%d (%p), key %016llx, scope %d, type "
+		"%d (tgt_dev %p)", debug_transport_id_to_initiator_name(
+					cmd->sess->transport_id),
+		cmd->sess->tgt->rel_tgt_id, reg, key, scope, type, tgt_dev);
+
+	/* We already checked reg is not NULL */
+	if (reg->key != key) {
+		TRACE_PR("Registrant's %p key %016llx mismatch with %016llx",
+			reg, reg->key, key);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out;
+	}
+
+	if (!scst_pr_is_holder(dev, reg)) {
+		TRACE_PR("Registrant %p is not a holder - do nothing", reg);
+		goto out;
+	}
+
+	if (dev->pr_scope != scope || dev->pr_type != type) {
+		TRACE_PR("%s", "Released scope or type do not match with "
+			"holder");
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_release));
+		goto out;
+	}
+
+	cur_pr_type = dev->pr_type; /* it will be cleared */
+
+	scst_pr_clear_reservation(dev);
+
+	switch (cur_pr_type) {
+	case TYPE_WRITE_EXCLUSIVE_REGONLY:
+	case TYPE_EXCLUSIVE_ACCESS_REGONLY:
+	case TYPE_WRITE_EXCLUSIVE_ALL_REG:
+	case TYPE_EXCLUSIVE_ACCESS_ALL_REG:
+		scst_pr_send_ua_all(dev, reg,
+			SCST_LOAD_SENSE(scst_sense_reservation_released));
+	}
+
+	scst_pr_dump_prs(dev, false);
+
+out:
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_clear(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
+{
+	int scope, type;
+	__be64 key;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_dev_registrant *reg, *r, *t;
+
+	key = get_unaligned((__be64 *)&buffer[0]);
+	scope = (cmd->cdb[2] & 0x0f) >> 4;
+	type = cmd->cdb[2] & 0x0f;
+
+	if (buffer_size != 24) {
+		TRACE_PR("Invalid buffer size %d", buffer_size);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
+		goto out;
+	}
+
+	reg = tgt_dev->registrant;
+
+	TRACE_PR("Clear: initiator %s/%d (%p), key %016llx (tgt_dev %p)",
+		debug_transport_id_to_initiator_name(cmd->sess->transport_id),
+		cmd->sess->tgt->rel_tgt_id, reg, key, tgt_dev);
+
+	/* We already checked reg is not NULL */
+	if (reg->key != key) {
+		TRACE_PR("Registrant's %p key %016llx mismatch with %016llx",
+			reg, reg->key, key);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out;
+	}
+
+	scst_pr_send_ua_all(dev, reg,
+		SCST_LOAD_SENSE(scst_sense_reservation_preempted));
+
+	list_for_each_entry_safe(r, t, &dev->dev_registrants_list,
+					dev_registrants_list_entry) {
+		scst_pr_remove_registrant(dev, r);
+	}
+
+	dev->pr_generation++;
+
+	scst_pr_dump_prs(dev, false);
+
+out:
+	return;
+}
+
+static void scst_pr_do_preempt(struct scst_cmd *cmd, uint8_t *buffer,
+	int buffer_size, bool abort)
+{
+	__be64 key, action_key;
+	int scope, type;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_dev_registrant *reg, *r, *rt;
+	LIST_HEAD(preempt_list);
+
+	key = get_unaligned((__be64 *)&buffer[0]);
+	action_key = get_unaligned((__be64 *)&buffer[8]);
+	scope = (cmd->cdb[2] & 0x0f) >> 4;
+	type = cmd->cdb[2] & 0x0f;
+
+	if (buffer_size != 24) {
+		TRACE_PR("Invalid buffer size %d", buffer_size);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
+		goto out;
+	}
+
+	if (!scst_pr_type_valid(type)) {
+		TRACE_PR("Invalid reservation type %d", type);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out;
+	}
+
+	reg = tgt_dev->registrant;
+
+	TRACE_PR("Preempt%s: initiator %s/%d (%p), key %016llx, action_key "
+		"%016llx, scope %x type %x (tgt_dev %p)",
+		abort ? " and abort" : "",
+		debug_transport_id_to_initiator_name(cmd->sess->transport_id),
+		cmd->sess->tgt->rel_tgt_id, reg, key, action_key, scope, type,
+		tgt_dev);
+
+	/* We already checked reg is not NULL */
+	if (reg->key != key) {
+		TRACE_PR("Registrant's %p key %016llx mismatch with %016llx",
+			reg, reg->key, key);
+		scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+		goto out;
+	}
+
+	if (!dev->pr_is_set) {
+		scst_pr_find_registrants_list_key(dev, action_key,
+			&preempt_list);
+		if (list_empty(&preempt_list))
+			goto out_error;
+		list_for_each_entry_safe(r, rt, &preempt_list, aux_list_entry) {
+			if (r != reg)
+				scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE(
+					scst_sense_registrations_preempted));
+			scst_pr_remove_registrant(dev, r);
+		}
+		goto done;
+	}
+
+	if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG ||
+	    dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) {
+		if (action_key == 0) {
+			scst_pr_find_registrants_list_all(dev, reg,
+				&preempt_list);
+			list_for_each_entry_safe(r, rt, &preempt_list,
+					aux_list_entry) {
+				if (r != reg)
+					scst_pr_send_ua_reg(dev, r,
+						SCST_LOAD_SENSE(
+						scst_sense_registrations_preempted));
+				else
+					reg = NULL;
+				scst_pr_remove_registrant(dev, r);
+			}
+			if (reg != NULL)
+				scst_pr_set_holder(dev, reg, scope, type);
+		} else {
+			scst_pr_find_registrants_list_key(dev, action_key,
+				&preempt_list);
+			if (list_empty(&preempt_list))
+				goto out_error;
+			list_for_each_entry_safe(r, rt, &preempt_list,
+					aux_list_entry) {
+				if (r != reg)
+					scst_pr_send_ua_reg(dev, r,
+						SCST_LOAD_SENSE(
+						scst_sense_registrations_preempted));
+				else
+					reg = NULL;
+				scst_pr_remove_registrant(dev, r);
+			}
+		}
+		goto done;
+	}
+
+	BUG_ON(dev->pr_holder == NULL);
+
+	if (dev->pr_holder->key != action_key) {
+		if (action_key == 0) {
+			scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+				scst_sense_invalid_field_in_parm_list));
+			goto out;
+		} else {
+			scst_pr_find_registrants_list_key(dev, action_key,
+				&preempt_list);
+			if (list_empty(&preempt_list))
+				goto out_error;
+			list_for_each_entry_safe(r, rt, &preempt_list,
+					aux_list_entry) {
+				if (r != reg)
+					scst_pr_send_ua_reg(dev, r,
+						SCST_LOAD_SENSE(
+						scst_sense_registrations_preempted));
+				else
+					reg = NULL;
+				scst_pr_remove_registrant(dev, r);
+			}
+			goto done;
+		}
+	}
+
+	scst_pr_find_registrants_list_key(dev, action_key,
+		&preempt_list);
+
+	list_for_each_entry_safe(r, rt, &preempt_list, aux_list_entry) {
+		if (abort)
+			scst_pr_abort_reg(dev, cmd, r);
+		if (r != reg)
+			scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE(
+				scst_sense_registrations_preempted));
+		else
+			reg = NULL;
+		scst_pr_remove_registrant(dev, r);
+	}
+
+	if (dev->pr_type != type || dev->pr_scope != scope)
+		list_for_each_entry(r, &dev->dev_registrants_list,
+					dev_registrants_list_entry) {
+			if (r != reg)
+				scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE(
+					scst_sense_reservation_released));
+		}
+
+	if (reg != NULL)
+		scst_pr_set_holder(dev, reg, scope, type);
+
+done:
+	dev->pr_generation++;
+
+	scst_pr_dump_prs(dev, false);
+
+out:
+	return;
+
+out_error:
+	TRACE_PR("Invalid key %016llx", action_key);
+	scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
+	goto out;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_preempt(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
+{
+
+	scst_pr_do_preempt(cmd, buffer, buffer_size, false);
+	return;
+}
+
+static void scst_cmd_done_pr_preempt(struct scst_cmd *cmd, int next_state,
+	enum scst_exec_context pref_context)
+{
+	void (*saved_cmd_done) (struct scst_cmd *cmd, int next_state,
+		enum scst_exec_context pref_context);
+
+	saved_cmd_done = NULL; /* to remove warning that it's used not inited */
+
+	if (cmd->pr_abort_counter != NULL) {
+		if (!atomic_dec_and_test(&cmd->pr_abort_counter->pr_abort_pending_cnt))
+			goto out;
+		saved_cmd_done = cmd->pr_abort_counter->saved_cmd_done;
+		kfree(cmd->pr_abort_counter);
+		cmd->pr_abort_counter = NULL;
+	}
+
+	saved_cmd_done(cmd, next_state, pref_context);
+
+out:
+	return;
+}
+
+/*
+ * Called with dev_pr_mutex locked, no IRQ. Expects session_list_lock
+ * not locked
+ */
+void scst_pr_preempt_and_abort(struct scst_cmd *cmd, uint8_t *buffer,
+	int buffer_size)
+{
+
+	cmd->pr_abort_counter = kzalloc(sizeof(*cmd->pr_abort_counter),
+		GFP_KERNEL);
+	if (cmd->pr_abort_counter == NULL) {
+		PRINT_ERROR("Unable to allocate PR abort counter (size %zd)",
+			sizeof(*cmd->pr_abort_counter));
+		scst_set_busy(cmd);
+		goto out;
+	}
+
+	/* 1 to protect cmd from be done by the TM thread too early */
+	atomic_set(&cmd->pr_abort_counter->pr_abort_pending_cnt, 1);
+	atomic_set(&cmd->pr_abort_counter->pr_aborting_cnt, 1);
+	init_completion(&cmd->pr_abort_counter->pr_aborting_cmpl);
+
+	cmd->pr_abort_counter->saved_cmd_done = cmd->scst_cmd_done;
+	cmd->scst_cmd_done = scst_cmd_done_pr_preempt;
+
+	scst_pr_do_preempt(cmd, buffer, buffer_size, true);
+
+	if (!atomic_dec_and_test(&cmd->pr_abort_counter->pr_aborting_cnt))
+		wait_for_completion(&cmd->pr_abort_counter->pr_aborting_cmpl);
+
+out:
+	return;
+}
+
+/* Checks if this is a Compatible Reservation Handling (CRH) case */
+bool scst_pr_crh_case(struct scst_cmd *cmd)
+{
+	bool allowed;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_dev_registrant *reg;
+	uint8_t type;
+
+	TRACE_DBG("Test if there is a CRH case for command %s (0x%x) from "
+		"%s", cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name);
+
+	if (!dev->pr_is_set) {
+		TRACE_PR("%s", "PR not set");
+		allowed = false;
+		goto out;
+	}
+
+	reg = tgt_dev->registrant;
+	type = dev->pr_type;
+
+	switch (type) {
+	case TYPE_WRITE_EXCLUSIVE:
+	case TYPE_EXCLUSIVE_ACCESS:
+		WARN_ON(dev->pr_holder == NULL);
+		if (reg == dev->pr_holder)
+			allowed = true;
+		else
+			allowed = false;
+		break;
+
+	case TYPE_WRITE_EXCLUSIVE_REGONLY:
+	case TYPE_EXCLUSIVE_ACCESS_REGONLY:
+	case TYPE_WRITE_EXCLUSIVE_ALL_REG:
+	case TYPE_EXCLUSIVE_ACCESS_ALL_REG:
+		allowed = (reg != NULL);
+		break;
+
+	default:
+		PRINT_ERROR("Invalid PR type %x", type);
+		allowed = false;
+		break;
+	}
+
+	if (!allowed)
+		TRACE_PR("Command %s (0x%x) from %s rejected due to not CRH "
+			"reservation", cmd->op_name, cmd->cdb[0],
+			cmd->sess->initiator_name);
+	else
+		TRACE_DBG("Command %s (0x%x) from %s is allowed to execute "
+			"due to CRH", cmd->op_name, cmd->cdb[0],
+			cmd->sess->initiator_name);
+
+out:
+	return allowed;
+
+}
+
+/* Check if command allowed in presence of reservation */
+bool scst_pr_is_cmd_allowed(struct scst_cmd *cmd)
+{
+	bool allowed;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_dev_registrant *reg;
+	uint8_t type;
+	bool unlock;
+
+	unlock = scst_pr_read_lock(dev);
+
+	TRACE_DBG("Testing if command %s (0x%x) from %s allowed to execute",
+		cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name);
+
+	/* Recheck, because it can change while we were waiting for the lock */
+	if (unlikely(!dev->pr_is_set)) {
+		allowed = true;
+		goto out_unlock;
+	}
+
+	reg = tgt_dev->registrant;
+	type = dev->pr_type;
+
+	switch (type) {
+	case TYPE_WRITE_EXCLUSIVE:
+		if (reg && reg == dev->pr_holder)
+			allowed = true;
+		else
+			allowed = (cmd->op_flags & SCST_WRITE_EXCL_ALLOWED) != 0;
+		break;
+
+	case TYPE_EXCLUSIVE_ACCESS:
+		if (reg && reg == dev->pr_holder)
+			allowed = true;
+		else
+			allowed = (cmd->op_flags & SCST_EXCL_ACCESS_ALLOWED) != 0;
+		break;
+
+	case TYPE_WRITE_EXCLUSIVE_REGONLY:
+	case TYPE_WRITE_EXCLUSIVE_ALL_REG:
+		if (reg)
+			allowed = true;
+		else
+			allowed = (cmd->op_flags & SCST_WRITE_EXCL_ALLOWED) != 0;
+		break;
+
+	case TYPE_EXCLUSIVE_ACCESS_REGONLY:
+	case TYPE_EXCLUSIVE_ACCESS_ALL_REG:
+		if (reg)
+			allowed = true;
+		else
+			allowed = (cmd->op_flags & SCST_EXCL_ACCESS_ALLOWED) != 0;
+		break;
+
+	default:
+		PRINT_ERROR("Invalid PR type %x", type);
+		allowed = false;
+		break;
+	}
+
+	if (!allowed)
+		TRACE_PR("Command %s (0x%x) from %s rejected due "
+			"to PR", cmd->op_name, cmd->cdb[0],
+			cmd->sess->initiator_name);
+	else
+		TRACE_DBG("Command %s (0x%x) from %s is allowed to execute",
+			cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name);
+
+out_unlock:
+	scst_pr_read_unlock(dev, unlock);
+	return allowed;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_read_keys(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
+{
+	int i, offset = 0, size, size_max;
+	struct scst_device *dev = cmd->dev;
+	struct scst_dev_registrant *reg;
+
+	if (buffer_size < 8) {
+		TRACE_PR("buffer_size too small: %d. expected >= 8 "
+			"(buffer %p)", buffer_size, buffer);
+		goto skip;
+	}
+
+	TRACE_PR("Read Keys (dev %s): PRGen %d", dev->virt_name,
+			dev->pr_generation);
+
+	put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&buffer[0]);
+
+	offset = 8;
+	size = 0;
+	size_max = buffer_size - 8;
+
+	i = 0;
+	list_for_each_entry(reg, &dev->dev_registrants_list,
+				dev_registrants_list_entry) {
+		if (size_max - size >= 8) {
+			TRACE_PR("Read Keys (dev %s): key 0x%llx",
+				dev->virt_name, reg->key);
+
+			WARN_ON(reg->key == 0);
+
+			put_unaligned(reg->key,
+				(__be64 *)&buffer[offset + 8 * i]);
+
+			offset += 8;
+		}
+		size += 8;
+	}
+
+	put_unaligned(cpu_to_be32(size), (__be32 *)&buffer[4]);
+
+skip:
+	scst_set_resp_data_len(cmd, offset);
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_read_reservation(struct scst_cmd *cmd, uint8_t *buffer,
+	int buffer_size)
+{
+	struct scst_device *dev = cmd->dev;
+	uint8_t b[24];
+	int size = 0;
+
+	if (buffer_size < 8) {
+		TRACE_PR("buffer_size too small: %d. expected >= 8 "
+			"(buffer %p)", buffer_size, buffer);
+		goto skip;
+	}
+
+	memset(b, 0, sizeof(b));
+
+	put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&buffer[0]);
+
+	if (!dev->pr_is_set) {
+		TRACE_PR("Read Reservation: no reservations for dev %s",
+			dev->virt_name);
+		b[4] =
+		b[5] =
+		b[6] =
+		b[7] = 0;
+
+		size = 8;
+	} else {
+		__be64 key = dev->pr_holder ? dev->pr_holder->key : 0;
+
+		TRACE_PR("Read Reservation: dev %s, holder %p, key 0x%llx, "
+			"scope %d, type %d", dev->virt_name, dev->pr_holder,
+			key, dev->pr_scope, dev->pr_type);
+
+		b[4] =
+		b[5] =
+		b[6] = 0;
+		b[7] = 0x10;
+
+		put_unaligned(key, (__be64 *)&b[8]);
+		b[21] = dev->pr_scope << 4 | dev->pr_type;
+
+		size = 24;
+	}
+
+	memset(buffer, 0, buffer_size);
+	memcpy(buffer, b, min(size, buffer_size));
+
+skip:
+	scst_set_resp_data_len(cmd, size);
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_report_caps(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
+{
+	int offset = 0;
+	unsigned int crh = 1;
+	unsigned int atp_c = 1;
+	unsigned int sip_c = 1;
+	unsigned int ptpl_c = 1;
+	struct scst_device *dev = cmd->dev;
+
+	if (buffer_size < 8) {
+		TRACE_PR("buffer_size too small: %d. expected >= 8 "
+			"(buffer %p)", buffer_size, buffer);
+		goto skip;
+	}
+
+	TRACE_PR("Reporting capabilities (dev %s):  crh %x, sip_c %x, "
+		"atp_c %x, ptpl_c %x, pr_aptpl %x", dev->virt_name,
+		crh, sip_c, atp_c, ptpl_c, dev->pr_aptpl);
+
+	buffer[0] = 0;
+	buffer[1] = 8;
+
+	buffer[2] = crh << 4 | sip_c << 3 | atp_c << 2 | ptpl_c;
+	buffer[3] = (1 << 7) | (dev->pr_aptpl > 0 ? 1 : 0);
+
+	/* All commands supported */
+	buffer[4] = 0xEA;
+	buffer[5] = 0x1;
+
+	offset += 8;
+
+skip:
+	scst_set_resp_data_len(cmd, offset);
+	return;
+}
+
+/* Called with dev_pr_mutex locked, no IRQ */
+void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer,
+	int buffer_size)
+{
+	int offset = 0, size, size_max;
+	struct scst_device *dev = cmd->dev;
+	struct scst_dev_registrant *reg;
+
+	if (buffer_size < 8)
+		goto skip;
+
+	put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&buffer[0]);
+	offset += 8;
+
+	size = 0;
+	size_max = buffer_size - 8;
+
+	list_for_each_entry(reg, &dev->dev_registrants_list,
+				dev_registrants_list_entry) {
+		int ts;
+		int rec_len;
+
+		ts = tid_size(reg->transport_id);
+		rec_len = 24 + ts;
+
+		if (size_max - size > rec_len) {
+			memset(&buffer[offset], 0, rec_len);
+
+			put_unaligned(reg->key, (__be64 *)(&buffer[offset]));
+
+			if (dev->pr_is_set && scst_pr_is_holder(dev, reg)) {
+				buffer[offset + 12] = 1;
+				buffer[offset + 13] = (dev->pr_scope << 8) | dev->pr_type;
+			}
+
+			put_unaligned(cpu_to_be16(reg->rel_tgt_id),
+				(__be16 *)&buffer[offset + 18]);
+			put_unaligned(cpu_to_be32(ts),
+				(__be32 *)&buffer[offset + 20]);
+
+			memcpy(&buffer[offset + 24], reg->transport_id, ts);
+
+			offset += rec_len;
+		}
+		size += rec_len;
+	}
+
+	put_unaligned(cpu_to_be32(size), (__be32 *)&buffer[4]);
+
+skip:
+	scst_set_resp_data_len(cmd, offset);
+	return;
+}
drivers/scst/scst_pres.h renamed from linux-2.6.35/drivers/scst/scst_pres.h +159 -0

159 lines not on this page · raw message

#8 lore raw

PATCH v4 8/19 : SCST SYSFS interface implementation

In reply to Vladislav Bolkhovitin

This patch contains SYSFS interface implementation.

This interface provides possibility for a user to configure his/her SCST server configuration: add/delete/manage target drivers, targets, dev handlers, virtual devices and access control to them as well as it allows to see current SCST configuration with necessary statistical and debug info (e.g. SGV cache statistics).

For some management events processing is redirected to dedicated thread for 2 reasons:

1. It naturally serializes all SYSFS management operations, so allows to simplify locking in target drivers and dev handlers. For instance, it allows in add_target() callback don't worry if del_target() or another add_target() for the same target name called simultaneously.

2. To make processing be done outside of the internal SYSFS locking.

All internal SCST management is done for simplicity under scst_mutex. It's simple, robust and worked well even under the highest load for ages. But in 2.6.35 sysfs was improved to make lockdep to check s_active related deadlocks and we discovered potential circular locking dependency between scst_mutex and s_active. On some management operations lockdep triggered output like:

[ 2036.926891] =======================================================
[ 2036.927670] [ INFO: possible circular locking dependency detected ]
[ 2036.927670] 2.6.35-scst-dbg #15
[ 2036.927670] -------------------------------------------------------
[ 2036.927670] rmmod/4715 is trying to acquire lock:
[ 2036.927670]  (s_active#230){++++.+}, at: [<78240a24>] sysfs_hash_and_remove+0x63/0x67
[ 2036.927670] 
[ 2036.927670] but task is already holding lock:
[ 2036.927670]  (&scst_mutex){+.+.+.}, at: [<fefd7fe2>] scst_unregister_virtual_device+0x58/0x216 [scst]
[ 2036.927670] 
[ 2036.927670] which lock already depends on the new lock.
[ 2036.927670] 
[ 2036.927670] 
[ 2036.927670] the existing dependency chain (in reverse order) is:
[ 2036.927670] 
[ 2036.927670] -> #2 (&scst_mutex){+.+.+.}:
[ 2036.927670]        [<78168d67>] lock_acquire+0x76/0x129
[ 2036.927670]        [<78467619>] __mutex_lock_common+0x58/0x3fc
[ 2036.927670]        [<78467a6d>] mutex_lock_nested+0x36/0x3d
[ 2036.927670]        [<f8ecec91>] vcdrom_change+0x1b9/0x500 [scst_vdisk]
[ 2036.927670]        [<f8ecf030>] vcdrom_sysfs_filename_store+0x58/0xd8 [scst_vdisk]
[ 2036.927670]        [<feffd139>] scst_dev_attr_store+0x44/0x5d [scst]
[ 2036.927670]        [<7824104f>] sysfs_write_file+0x9e/0xe8
[ 2036.927670]        [<781ee836>] vfs_write+0x91/0x17e
[ 2036.927670]        [<781ef213>] sys_write+0x42/0x69
[ 2036.927670]        [<78102d13>] sysenter_do_call+0x12/0x32
[ 2036.927670] 
[ 2036.927670] -> #1 (&virt_dev->vdev_sysfs_mutex){+.+.+.}:
[ 2036.927670]        [<78168d67>] lock_acquire+0x76/0x129
[ 2036.927670]        [<78467619>] __mutex_lock_common+0x58/0x3fc
[ 2036.927670]        [<784679f3>] mutex_lock_interruptible_nested+0x36/0x3d
[ 2036.927670]        [<f8ecebd6>] vcdrom_change+0xfe/0x500 [scst_vdisk]
[ 2036.927670]        [<f8ecf030>] vcdrom_sysfs_filename_store+0x58/0xd8 [scst_vdisk]
[ 2036.927670]        [<feffd139>] scst_dev_attr_store+0x44/0x5d [scst]
[ 2036.927670]        [<7824104f>] sysfs_write_file+0x9e/0xe8
[ 2036.927670]        [<781ee836>] vfs_write+0x91/0x17e
[ 2036.927670]        [<781ef213>] sys_write+0x42/0x69
[ 2036.927670]        [<78102d13>] sysenter_do_call+0x12/0x32
[ 2036.927670] 
[ 2036.927670] -> #0 (s_active#230){++++.+}:
[ 2036.927670]        [<78168af4>] __lock_acquire+0x1013/0x1210
[ 2036.927670]        [<78168d67>] lock_acquire+0x76/0x129
[ 2036.927670]        [<78242417>] sysfs_addrm_finish+0x100/0x150
[ 2036.927670]        [<78240a24>] sysfs_hash_and_remove+0x63/0x67
[ 2036.927670]        [<782415b6>] sysfs_remove_file+0x14/0x16
[ 2036.927670]        [<feffdb29>] scst_devt_dev_sysfs_put+0x75/0x133 [scst]
[ 2036.927670]        [<fefd6410>] scst_assign_dev_handler+0x109/0x5b6 [scst]
[ 2036.927670]        [<fefd80ce>] scst_unregister_virtual_device+0x144/0x216 [scst]
[ 2036.927670]        [<f8ed06f3>] vdev_del_device+0x47/0xd4 [scst_vdisk]
[ 2036.927670]        [<f8ed6701>] exit_scst_vdisk+0x60/0xe6 [scst_vdisk]
[ 2036.927670]        [<f8ed67b1>] exit_scst_vdisk_driver+0x12/0x46 [scst_vdisk]
[ 2036.927670]        [<7817253a>] sys_delete_module+0x139/0x214
[ 2036.927670]        [<78102d13>] sysenter_do_call+0x12/0x32
[ 2036.927670] 
[ 2036.927670] other info that might help us debug this:
[ 2036.927670] 
[ 2036.927670] 2 locks held by rmmod/4715:
[ 2036.927670]  #0:  (scst_vdisk_mutex){+.+.+.}, at: [<f8ed66f0>] exit_scst_vdisk+0x4f/0xe6 [scst_vdisk]
[ 2036.927670]  #1:  (&scst_mutex){+.+.+.}, at: [<fefd7fe2>] scst_unregister_virtual_device+0x58/0x216 [scst]
[ 2036.927670] 
[ 2036.927670] stack backtrace:
[ 2036.927670] Pid: 4715, comm: rmmod Not tainted 2.6.35-scst-dbg #15
[ 2036.927670] Call Trace:
[ 2036.927670]  [<784660a3>] ? printk+0x2d/0x32
[ 2036.927670]  [<78166cbc>] print_circular_bug+0xb4/0xb9
[ 2036.927670]  [<78168af4>] __lock_acquire+0x1013/0x1210
[ 2036.927670]  [<78168d67>] lock_acquire+0x76/0x129
[ 2036.927670]  [<78240a24>] ? sysfs_hash_and_remove+0x63/0x67
[ 2036.927670]  [<78242417>] sysfs_addrm_finish+0x100/0x150
[ 2036.927670]  [<78240a24>] ? sysfs_hash_and_remove+0x63/0x67
[ 2036.927670]  [<78240a24>] sysfs_hash_and_remove+0x63/0x67
[ 2036.927670]  [<782415b6>] sysfs_remove_file+0x14/0x16
[ 2036.927670]  [<feffdb29>] scst_devt_dev_sysfs_put+0x75/0x133 [scst]
[ 2036.927670]  [<fefd5b20>] ? scst_stop_dev_threads+0x77/0x111 [scst]
[ 2036.927670]  [<f8ece3c2>] ? vdisk_detach+0x88/0x133 [scst_vdisk]
[ 2036.927670]  [<fefd6410>] scst_assign_dev_handler+0x109/0x5b6 [scst]
[ 2036.927670]  [<ff007368>] ? scst_pr_clear_dev+0x8e/0xfc [scst]
[ 2036.927670]  [<fefd80ce>] scst_unregister_virtual_device+0x144/0x216 [scst]
[ 2036.927670]  [<f8ed06f3>] vdev_del_device+0x47/0xd4 [scst_vdisk]
[ 2036.927670]  [<f8ed6701>] exit_scst_vdisk+0x60/0xe6 [scst_vdisk]
[ 2036.927670]  [<f8ed67b1>] exit_scst_vdisk_driver+0x12/0x46 [scst_vdisk]
[ 2036.927670]  [<7817253a>] sys_delete_module+0x139/0x214
[ 2036.927670]  [<7846c87e>] ? sub_preempt_count+0x7e/0xad
[ 2036.927670]  [<78102d42>] ? sysenter_exit+0xf/0x1a
[ 2036.927670]  [<7816789d>] ? trace_hardirqs_on_caller+0x10c/0x14d
[ 2036.927670]  [<78102d13>] sysenter_do_call+0x12/0x32
[ 2036.927670]  [<7846007b>] ? init_intel_cacheinfo+0x317/0x38b

It is caused by a chicken and egg problem: SCST objects, including their sysfs hierarchy (kobjects) are created under scst_mutex, but for some of them (ACGs and their names attributes, ACNs, are the most problematic objects) the creation is triggered from inside the SYSFS.

I spent a LOT of time trying to rule out this problem in an acceptable manner. Particularly, I analyzed splitting creation of SCST objects and their kobjects, so the latter would be created outside of scst_mutex, and making a fine grain locking for the SCST management instead of the single scst_mutex, but all the options lead to unacceptably complicated code. So, I have chosen the use of the separate thread for all the SYSFS management operation with scst_mutex/s_active deadlock detecting (see scst_sysfs_queue_wait_work()) and, if the deadlock possibility detected, returning EAGAIN asking the user space to poll completion of the command using last_sysfs_mgmt_res attribute. It is documented in the README and scstadmin is doing that. It, definitely, isn't a piece of beauty, but it's simple and works, so, I believe, good enough. User space, anyway, is supposes to hide all the complexities of the direct SYSFS manipulations under higher level management tools like scstadmin.

---
 scst_sysfs.c | 5194 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 5194 insertions(+)
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +5194 -0
diff -uprN orig/linux-2.6.35/drivers/scst/scst_sysfs.c linux-2.6.35/drivers/scst/scst_sysfs.c
@@ -0,0 +1,5194 @@
+/*
+ *  scst_sysfs.c
+ *
+ *  Copyright (C) 2009 Daniel Henrique Debonzi <[email protected]>
+ *  Copyright (C) 2009 - 2010 Vladislav Bolkhovitin <[email protected]>
+ *  Copyright (C) 2009 - 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/kobject.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ctype.h>
+#include <linux/slab.h>
+#include <linux/kthread.h>
+
+#include <scst/scst.h>
+#include "scst_priv.h"
+#include "scst_mem.h"
+#include "scst_pres.h"
+
+static DECLARE_COMPLETION(scst_sysfs_root_release_completion);
+
+static struct kobject scst_sysfs_root_kobj;
+static struct kobject *scst_targets_kobj;
+static struct kobject *scst_devices_kobj;
+static struct kobject *scst_sgv_kobj;
+static struct kobject *scst_handlers_kobj;
+
+static const char *scst_dev_handler_types[] = {
+    "Direct-access device (e.g., magnetic disk)",
+    "Sequential-access device (e.g., magnetic tape)",
+    "Printer device",
+    "Processor device",
+    "Write-once device (e.g., some optical disks)",
+    "CD-ROM device",
+    "Scanner device (obsolete)",
+    "Optical memory device (e.g., some optical disks)",
+    "Medium changer device (e.g., jukeboxes)",
+    "Communications device (obsolete)",
+    "Defined by ASC IT8 (Graphic arts pre-press devices)",
+    "Defined by ASC IT8 (Graphic arts pre-press devices)",
+    "Storage array controller device (e.g., RAID)",
+    "Enclosure services device",
+    "Simplified direct-access device (e.g., magnetic disk)",
+    "Optical card reader/writer device"
+};
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+
+static DEFINE_MUTEX(scst_log_mutex);
+
+static struct scst_trace_log scst_trace_tbl[] = {
+    { TRACE_OUT_OF_MEM,		"out_of_mem" },
+    { TRACE_MINOR,		"minor" },
+    { TRACE_SG_OP,		"sg" },
+    { TRACE_MEMORY,		"mem" },
+    { TRACE_BUFF,		"buff" },
+    { TRACE_PID,		"pid" },
+    { TRACE_LINE,		"line" },
+    { TRACE_FUNCTION,		"function" },
+    { TRACE_DEBUG,		"debug" },
+    { TRACE_SPECIAL,		"special" },
+    { TRACE_SCSI,		"scsi" },
+    { TRACE_MGMT,		"mgmt" },
+    { TRACE_MGMT_DEBUG,		"mgmt_dbg" },
+    { TRACE_FLOW_CONTROL,	"flow_control" },
+    { TRACE_PRES,		"pr" },
+    { 0,			NULL }
+};
+
+static struct scst_trace_log scst_local_trace_tbl[] = {
+    { TRACE_RTRY,		"retry" },
+    { TRACE_SCSI_SERIALIZING,	"scsi_serializing" },
+    { TRACE_RCV_BOT,		"recv_bot" },
+    { TRACE_SND_BOT,		"send_bot" },
+    { TRACE_RCV_TOP,		"recv_top" },
+    { TRACE_SND_TOP,		"send_top" },
+    { 0,			NULL }
+};
+
+static ssize_t scst_trace_level_show(const struct scst_trace_log *local_tbl,
+	unsigned long log_level, char *buf, const char *help);
+static int scst_write_trace(const char *buf, size_t length,
+	unsigned long *log_level, unsigned long default_level,
+	const char *name, const struct scst_trace_log *tbl);
+
+#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
+
+static ssize_t scst_luns_mgmt_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_luns_mgmt_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_tgt_addr_method_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_tgt_addr_method_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_tgt_io_grouping_type_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_tgt_io_grouping_type_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_tgt_cpu_mask_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_tgt_cpu_mask_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_ini_group_mgmt_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_ini_group_mgmt_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_rel_tgt_id_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_rel_tgt_id_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_acg_luns_mgmt_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_acg_ini_mgmt_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_acg_ini_mgmt_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_acg_addr_method_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_acg_addr_method_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_acg_io_grouping_type_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_acg_io_grouping_type_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_acg_cpu_mask_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf);
+static ssize_t scst_acg_cpu_mask_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count);
+static ssize_t scst_acn_file_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+
+/**
+ ** Sysfs work
+ **/
+
+static DEFINE_SPINLOCK(sysfs_work_lock);
+static LIST_HEAD(sysfs_work_list);
+static DECLARE_WAIT_QUEUE_HEAD(sysfs_work_waitQ);
+static int active_sysfs_works;
+static int last_sysfs_work_res;
+static struct task_struct *sysfs_work_thread;
+
+/**
+ * scst_alloc_sysfs_work() - allocates a sysfs work
+ */
+int scst_alloc_sysfs_work(int (*sysfs_work_fn)(struct scst_sysfs_work_item *),
+	bool read_only_action, struct scst_sysfs_work_item **res_work)
+{
+	int res = 0;
+	struct scst_sysfs_work_item *work;
+
+	if (sysfs_work_fn == NULL) {
+		PRINT_ERROR("%s", "sysfs_work_fn is NULL");
+		res = -EINVAL;
+		goto out;
+	}
+
+	*res_work = NULL;
+
+	work = kzalloc(sizeof(*work), GFP_KERNEL);
+	if (work == NULL) {
+		PRINT_ERROR("Unable to alloc sysfs work (size %zd)",
+			sizeof(*work));
+		res = -ENOMEM;
+		goto out;
+	}
+
+	work->read_only_action = read_only_action;
+	kref_init(&work->sysfs_work_kref);
+	init_completion(&work->sysfs_work_done);
+	work->sysfs_work_fn = sysfs_work_fn;
+
+	*res_work = work;
+
+out:
+	return res;
+}
+EXPORT_SYMBOL(scst_alloc_sysfs_work);
+
+static void scst_sysfs_work_release(struct kref *kref)
+{
+	struct scst_sysfs_work_item *work;
+
+	work = container_of(kref, struct scst_sysfs_work_item,
+			sysfs_work_kref);
+
+	TRACE_DBG("Freeing sysfs work %p (buf %p)", work, work->buf);
+
+	kfree(work->buf);
+	kfree(work->res_buf);
+	kfree(work);
+	return;
+}
+
+/**
+ * scst_sysfs_work_get() - increases ref counter of the sysfs work
+ */
+void scst_sysfs_work_get(struct scst_sysfs_work_item *work)
+{
+	kref_get(&work->sysfs_work_kref);
+}
+EXPORT_SYMBOL(scst_sysfs_work_get);
+
+/**
+ * scst_sysfs_work_put() - decreases ref counter of the sysfs work
+ */
+void scst_sysfs_work_put(struct scst_sysfs_work_item *work)
+{
+	kref_put(&work->sysfs_work_kref, scst_sysfs_work_release);
+}
+EXPORT_SYMBOL(scst_sysfs_work_put);
+
+/**
+ * scst_sysfs_queue_wait_work() - waits for the work to complete
+ *
+ * Returnes status of the completed work or -EAGAIN if the work not
+ * completed before timeout. In the latter case a user should poll
+ * last_sysfs_mgmt_res until it returns the result of the processing.
+ */
+int scst_sysfs_queue_wait_work(struct scst_sysfs_work_item *work)
+{
+	int res = 0, rc;
+	unsigned long timeout = 15*HZ;
+
+	spin_lock(&sysfs_work_lock);
+
+	TRACE_DBG("Adding sysfs work %p to the list", work);
+	list_add_tail(&work->sysfs_work_list_entry, &sysfs_work_list);
+
+	active_sysfs_works++;
+
+	spin_unlock(&sysfs_work_lock);
+
+	kref_get(&work->sysfs_work_kref);
+
+	wake_up(&sysfs_work_waitQ);
+
+	while (1) {
+		rc = wait_for_completion_interruptible_timeout(
+			&work->sysfs_work_done, timeout);
+		if (rc == 0) {
+			if (!mutex_is_locked(&scst_mutex)) {
+				TRACE_DBG("scst_mutex not locked, continue "
+					"waiting (work %p)", work);
+				timeout = 5*HZ;
+				continue;
+			}
+			TRACE_MGMT_DBG("Time out waiting for work %p",
+				work);
+			res = -EAGAIN;
+			goto out_put;
+		} else if (rc < 0) {
+			res = rc;
+			goto out_put;
+		}
+		break;
+	}
+
+	res = work->work_res;
+
+out_put:
+	kref_put(&work->sysfs_work_kref, scst_sysfs_work_release);
+	return res;
+}
+EXPORT_SYMBOL(scst_sysfs_queue_wait_work);
+
+/* Called under sysfs_work_lock and drops/reaquire it inside */
+static void scst_process_sysfs_works(void)
+{
+	struct scst_sysfs_work_item *work;
+
+	while (!list_empty(&sysfs_work_list)) {
+		work = list_entry(sysfs_work_list.next,
+			struct scst_sysfs_work_item, sysfs_work_list_entry);
+		list_del(&work->sysfs_work_list_entry);
+		spin_unlock(&sysfs_work_lock);
+
+		TRACE_DBG("Sysfs work %p", work);
+
+		work->work_res = work->sysfs_work_fn(work);
+
+		spin_lock(&sysfs_work_lock);
+		if (!work->read_only_action)
+			last_sysfs_work_res = work->work_res;
+		active_sysfs_works--;
+		spin_unlock(&sysfs_work_lock);
+
+		complete_all(&work->sysfs_work_done);
+		kref_put(&work->sysfs_work_kref, scst_sysfs_work_release);
+
+		spin_lock(&sysfs_work_lock);
+	}
+	return;
+}
+
+static inline int test_sysfs_work_list(void)
+{
+	int res = !list_empty(&sysfs_work_list) ||
+		  unlikely(kthread_should_stop());
+	return res;
+}
+
+static int sysfs_work_thread_fn(void *arg)
+{
+
+	PRINT_INFO("User interface thread started, PID %d", current->pid);
+
+	current->flags |= PF_NOFREEZE;
+
+	set_user_nice(current, -10);
+
+	spin_lock(&sysfs_work_lock);
+	while (!kthread_should_stop()) {
+		wait_queue_t wait;
+		init_waitqueue_entry(&wait, current);
+
+		if (!test_sysfs_work_list()) {
+			add_wait_queue_exclusive(&sysfs_work_waitQ, &wait);
+			for (;;) {
+				set_current_state(TASK_INTERRUPTIBLE);
+				if (test_sysfs_work_list())
+					break;
+				spin_unlock(&sysfs_work_lock);
+				schedule();
+				spin_lock(&sysfs_work_lock);
+			}
+			set_current_state(TASK_RUNNING);
+			remove_wait_queue(&sysfs_work_waitQ, &wait);
+		}
+
+		scst_process_sysfs_works();
+	}
+	spin_unlock(&sysfs_work_lock);
+
+	/*
+	 * If kthread_should_stop() is true, we are guaranteed to be
+	 * on the module unload, so both lists must be empty.
+	 */
+	BUG_ON(!list_empty(&sysfs_work_list));
+
+	PRINT_INFO("User interface thread PID %d finished", current->pid);
+	return 0;
+}
+
+/* No locks */
+static int scst_check_grab_tgtt_ptr(struct scst_tgt_template *tgtt)
+{
+	int res = 0;
+	struct scst_tgt_template *tt;
+
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(tt, &scst_template_list, scst_template_list_entry) {
+		if (tt == tgtt) {
+			tgtt->tgtt_active_sysfs_works_count++;
+			goto out_unlock;
+		}
+	}
+
+	TRACE_DBG("Tgtt %p not found", tgtt);
+	res = -ENOENT;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	return res;
+}
+
+/* No locks */
+static void scst_ungrab_tgtt_ptr(struct scst_tgt_template *tgtt)
+{
+
+	mutex_lock(&scst_mutex);
+	tgtt->tgtt_active_sysfs_works_count--;
+	mutex_unlock(&scst_mutex);
+	return;
+}
+
+/* scst_mutex supposed to be locked */
+static int scst_check_tgt_acg_ptrs(struct scst_tgt *tgt, struct scst_acg *acg)
+{
+	int res = 0;
+	struct scst_tgt_template *tgtt;
+
+	list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) {
+		struct scst_tgt *t;
+		list_for_each_entry(t, &tgtt->tgt_list, tgt_list_entry) {
+			if (t == tgt) {
+				struct scst_acg *a;
+				if (acg == NULL)
+					goto out;
+				if (acg == tgt->default_acg)
+					goto out;
+				list_for_each_entry(a, &tgt->tgt_acg_list,
+							acg_list_entry) {
+					if (a == acg)
+						goto out;
+				}
+			}
+		}
+	}
+
+	TRACE_DBG("Tgt %p/ACG %p not found", tgt, acg);
+	res = -ENOENT;
+
+out:
+	return res;
+}
+
+/* scst_mutex supposed to be locked */
+static int scst_check_devt_ptr(struct scst_dev_type *devt,
+	struct list_head *list)
+{
+	int res = 0;
+	struct scst_dev_type *dt;
+
+	list_for_each_entry(dt, list, dev_type_list_entry) {
+		if (dt == devt)
+			goto out;
+	}
+
+	TRACE_DBG("Devt %p not found", devt);
+	res = -ENOENT;
+
+out:
+	return res;
+}
+
+/* scst_mutex supposed to be locked */
+static int scst_check_dev_ptr(struct scst_device *dev)
+{
+	int res = 0;
+	struct scst_device *d;
+
+	list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
+		if (d == dev)
+			goto out;
+	}
+
+	TRACE_DBG("Dev %p not found", dev);
+	res = -ENOENT;
+
+out:
+	return res;
+}
+
+/* No locks */
+static int scst_check_grab_devt_ptr(struct scst_dev_type *devt,
+	struct list_head *list)
+{
+	int res = 0;
+	struct scst_dev_type *dt;
+
+	mutex_lock(&scst_mutex);
+
+	list_for_each_entry(dt, list, dev_type_list_entry) {
+		if (dt == devt) {
+			devt->devt_active_sysfs_works_count++;
+			goto out_unlock;
+		}
+	}
+
+	TRACE_DBG("Devt %p not found", devt);
+	res = -ENOENT;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	return res;
+}
+
+/* No locks */
+static void scst_ungrab_devt_ptr(struct scst_dev_type *devt)
+{
+
+	mutex_lock(&scst_mutex);
+	devt->devt_active_sysfs_works_count--;
+	mutex_unlock(&scst_mutex);
+	return;
+}
+
+/**
+ ** Regular SCST sysfs ops
+ **/
+static ssize_t scst_show(struct kobject *kobj, struct attribute *attr,
+			 char *buf)
+{
+	struct kobj_attribute *kobj_attr;
+	kobj_attr = container_of(attr, struct kobj_attribute, attr);
+
+	return kobj_attr->show(kobj, kobj_attr, buf);
+}
+
+static ssize_t scst_store(struct kobject *kobj, struct attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct kobj_attribute *kobj_attr;
+	kobj_attr = container_of(attr, struct kobj_attribute, attr);
+
+	if (kobj_attr->store)
+		return kobj_attr->store(kobj, kobj_attr, buf, count);
+	else
+		return -EIO;
+}
+
+static const struct sysfs_ops scst_sysfs_ops = {
+	.show = scst_show,
+	.store = scst_store,
+};
+
+const struct sysfs_ops *scst_sysfs_get_sysfs_ops(void)
+{
+	return &scst_sysfs_ops;
+}
+EXPORT_SYMBOL_GPL(scst_sysfs_get_sysfs_ops);
+
+/**
+ ** Target Template
+ **/
+
+static void scst_tgtt_release(struct kobject *kobj)
+{
+	struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+	complete_all(&tgtt->tgtt_kobj_release_cmpl);
+	return;
+}
+
+static struct kobj_type tgtt_ktype = {
+	.sysfs_ops = &scst_sysfs_ops,
+	.release = scst_tgtt_release,
+};
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+
+static ssize_t scst_tgtt_trace_level_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+
+	return scst_trace_level_show(tgtt->trace_tbl,
+		tgtt->trace_flags ? *tgtt->trace_flags : 0, buf,
+		tgtt->trace_tbl_help);
+}
+
+static ssize_t scst_tgtt_trace_level_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+
+	if (mutex_lock_interruptible(&scst_log_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	res = scst_write_trace(buf, count, tgtt->trace_flags,
+		tgtt->default_trace_flags, tgtt->name, tgtt->trace_tbl);
+
+	mutex_unlock(&scst_log_mutex);
+
+out:
+	return res;
+}
+
+static struct kobj_attribute tgtt_trace_attr =
+	__ATTR(trace_level, S_IRUGO | S_IWUSR,
+	       scst_tgtt_trace_level_show, scst_tgtt_trace_level_store);
+
+#endif /* #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
+
+static ssize_t scst_tgtt_mgmt_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+char *help = "Usage: echo \"add_target target_name [parameters]\" "
+				">mgmt\n"
+		     "       echo \"del_target target_name\" >mgmt\n"
+		     "%s%s"
+		     "%s"
+		     "\n"
+		     "where parameters are one or more "
+		     "param_name=value pairs separated by ';'\n\n"
+		     "%s%s%s%s%s%s%s%s\n";
+		struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+
+	return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, help,
+		(tgtt->tgtt_optional_attributes != NULL) ?
+			"       echo \"add_attribute <attribute> <value>\" >mgmt\n"
+			"       echo \"del_attribute <attribute> <value>\" >mgmt\n" : "",
+		(tgtt->tgt_optional_attributes != NULL) ?
+			"       echo \"add_target_attribute target_name <attribute> <value>\" >mgmt\n"
+			"       echo \"del_target_attribute target_name <attribute> <value>\" >mgmt\n" : "",
+		(tgtt->mgmt_cmd_help) ? tgtt->mgmt_cmd_help : "",
+		(tgtt->add_target_parameters != NULL) ?
+			"The following parameters available: " : "",
+		(tgtt->add_target_parameters != NULL) ?
+			tgtt->add_target_parameters : "",
+		(tgtt->tgtt_optional_attributes != NULL) ?
+			"The following target driver attributes available: " : "",
+		(tgtt->tgtt_optional_attributes != NULL) ?
+			tgtt->tgtt_optional_attributes : "",
+		(tgtt->tgtt_optional_attributes != NULL) ? "\n" : "",
+		(tgtt->tgt_optional_attributes != NULL) ?
+			"The following target attributes available: " : "",
+		(tgtt->tgt_optional_attributes != NULL) ?
+			tgtt->tgt_optional_attributes : "",
+		(tgtt->tgt_optional_attributes != NULL) ? "\n" : "");
+}
+
+static int scst_process_tgtt_mgmt_store(char *buffer,
+	struct scst_tgt_template *tgtt)
+{
+	int res = 0;
+	char *p, *pp, *target_name;
+
+	TRACE_DBG("buffer %s", buffer);
+
+	/* Check if our pointer is still alive and, if yes, grab it */
+	if (scst_check_grab_tgtt_ptr(tgtt) != 0)
+		goto out;
+
+	pp = buffer;
+	if (pp[strlen(pp) - 1] == '\n')
+		pp[strlen(pp) - 1] = '\0';
+
+	p = scst_get_next_lexem(&pp);
+
+	if (strcasecmp("add_target", p) == 0) {
+		target_name = scst_get_next_lexem(&pp);
+		if (*target_name == '\0') {
+			PRINT_ERROR("%s", "Target name required");
+			res = -EINVAL;
+			goto out_ungrab;
+		}
+		res = tgtt->add_target(target_name, pp);
+	} else if (strcasecmp("del_target", p) == 0) {
+		target_name = scst_get_next_lexem(&pp);
+		if (*target_name == '\0') {
+			PRINT_ERROR("%s", "Target name required");
+			res = -EINVAL;
+			goto out_ungrab;
+		}
+
+		p = scst_get_next_lexem(&pp);
+		if (*p != '\0')
+			goto out_syntax_err;
+
+		res = tgtt->del_target(target_name);
+	} else if (tgtt->mgmt_cmd != NULL) {
+		scst_restore_token_str(p, pp);
+		res = tgtt->mgmt_cmd(buffer);
+	} else {
+		PRINT_ERROR("Unknown action \"%s\"", p);
+		res = -EINVAL;
+		goto out_ungrab;
+	}
+
+out_ungrab:
+	scst_ungrab_tgtt_ptr(tgtt);
+
+out:
+	return res;
+
+out_syntax_err:
+	PRINT_ERROR("Syntax error on \"%s\"", p);
+	res = -EINVAL;
+	goto out_ungrab;
+}
+
+static int scst_tgtt_mgmt_store_work_fn(struct scst_sysfs_work_item *work)
+{
+	return scst_process_tgtt_mgmt_store(work->buf, work->tgtt);
+}
+
+static ssize_t scst_tgtt_mgmt_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	char *buffer;
+	struct scst_sysfs_work_item *work;
+	struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+
+	buffer = kzalloc(count+1, GFP_KERNEL);
+	if (buffer == NULL) {
+		res = -ENOMEM;
+		goto out;
+	}
+	memcpy(buffer, buf, count);
+	buffer[count] = '\0';
+
+	res = scst_alloc_sysfs_work(scst_tgtt_mgmt_store_work_fn, false, &work);
+	if (res != 0)
+		goto out_free;
+
+	work->buf = buffer;
+	work->tgtt = tgtt;
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res == 0)
+		res = count;
+
+out:
+	return res;
+
+out_free:
+	kfree(buffer);
+	goto out;
+}
+
+static struct kobj_attribute scst_tgtt_mgmt =
+	__ATTR(mgmt, S_IRUGO | S_IWUSR, scst_tgtt_mgmt_show,
+	       scst_tgtt_mgmt_store);
+
+int scst_tgtt_sysfs_create(struct scst_tgt_template *tgtt)
+{
+	int res = 0;
+	const struct attribute **pattr;
+
+	init_completion(&tgtt->tgtt_kobj_release_cmpl);
+
+	res = kobject_init_and_add(&tgtt->tgtt_kobj, &tgtt_ktype,
+			scst_targets_kobj, tgtt->name);
+	if (res != 0) {
+		PRINT_ERROR("Can't add tgtt %s to sysfs", tgtt->name);
+		goto out;
+	}
+
+	if (tgtt->add_target != NULL) {
+		res = sysfs_create_file(&tgtt->tgtt_kobj,
+				&scst_tgtt_mgmt.attr);
+		if (res != 0) {
+			PRINT_ERROR("Can't add mgmt attr for target driver %s",
+				tgtt->name);
+			goto out_del;
+		}
+	}
+
+	pattr = tgtt->tgtt_attrs;
+	if (pattr != NULL) {
+		while (*pattr != NULL) {
+			TRACE_DBG("Creating attr %s for target driver %s",
+				(*pattr)->name, tgtt->name);
+			res = sysfs_create_file(&tgtt->tgtt_kobj, *pattr);
+			if (res != 0) {
+				PRINT_ERROR("Can't add attr %s for target "
+					"driver %s", (*pattr)->name,
+					tgtt->name);
+				goto out_del;
+			}
+			pattr++;
+		}
+	}
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+	if (tgtt->trace_flags != NULL) {
+		res = sysfs_create_file(&tgtt->tgtt_kobj,
+				&tgtt_trace_attr.attr);
+		if (res != 0) {
+			PRINT_ERROR("Can't add trace_flag for target "
+				"driver %s", tgtt->name);
+			goto out_del;
+		}
+	}
+#endif
+
+out:
+	return res;
+
+out_del:
+	scst_tgtt_sysfs_del(tgtt);
+	goto out;
+}
+
+/*
+ * Must not be called under scst_mutex, due to possible deadlock with
+ * sysfs ref counting in sysfs works (it is waiting for the last put, but
+ * the last ref counter holder is waiting for scst_mutex)
+ */
+void scst_tgtt_sysfs_del(struct scst_tgt_template *tgtt)
+{
+	int rc;
+
+	kobject_del(&tgtt->tgtt_kobj);
+	kobject_put(&tgtt->tgtt_kobj);
+
+	rc = wait_for_completion_timeout(&tgtt->tgtt_kobj_release_cmpl, HZ);
+	if (rc == 0) {
+		PRINT_INFO("Waiting for releasing sysfs entry "
+			"for target template %s (%d refs)...", tgtt->name,
+			atomic_read(&tgtt->tgtt_kobj.kref.refcount));
+		wait_for_completion(&tgtt->tgtt_kobj_release_cmpl);
+		PRINT_INFO("Done waiting for releasing sysfs "
+			"entry for target template %s", tgtt->name);
+	}
+	return;
+}
+
+/**
+ ** Target directory implementation
+ **/
+
+static void scst_tgt_release(struct kobject *kobj)
+{
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	complete_all(&tgt->tgt_kobj_release_cmpl);
+	return;
+}
+
+static struct kobj_type tgt_ktype = {
+	.sysfs_ops = &scst_sysfs_ops,
+	.release = scst_tgt_release,
+};
+
+static void scst_acg_release(struct kobject *kobj)
+{
+	struct scst_acg *acg;
+
+	acg = container_of(kobj, struct scst_acg, acg_kobj);
+	complete_all(&acg->acg_kobj_release_cmpl);
+	return;
+}
+
+static struct kobj_type acg_ktype = {
+	.sysfs_ops = &scst_sysfs_ops,
+	.release = scst_acg_release,
+};
+
+static struct kobj_attribute scst_luns_mgmt =
+	__ATTR(mgmt, S_IRUGO | S_IWUSR, scst_luns_mgmt_show,
+	       scst_luns_mgmt_store);
+
+static struct kobj_attribute scst_acg_luns_mgmt =
+	__ATTR(mgmt, S_IRUGO | S_IWUSR, scst_luns_mgmt_show,
+	       scst_acg_luns_mgmt_store);
+
+static struct kobj_attribute scst_acg_ini_mgmt =
+	__ATTR(mgmt, S_IRUGO | S_IWUSR, scst_acg_ini_mgmt_show,
+	       scst_acg_ini_mgmt_store);
+
+static struct kobj_attribute scst_ini_group_mgmt =
+	__ATTR(mgmt, S_IRUGO | S_IWUSR, scst_ini_group_mgmt_show,
+	       scst_ini_group_mgmt_store);
+
+static struct kobj_attribute scst_tgt_addr_method =
+	__ATTR(addr_method, S_IRUGO | S_IWUSR, scst_tgt_addr_method_show,
+	       scst_tgt_addr_method_store);
+
+static struct kobj_attribute scst_tgt_io_grouping_type =
+	__ATTR(io_grouping_type, S_IRUGO | S_IWUSR,
+	       scst_tgt_io_grouping_type_show,
+	       scst_tgt_io_grouping_type_store);
+
+static struct kobj_attribute scst_tgt_cpu_mask =
+	__ATTR(cpu_mask, S_IRUGO | S_IWUSR,
+	       scst_tgt_cpu_mask_show,
+	       scst_tgt_cpu_mask_store);
+
+static struct kobj_attribute scst_rel_tgt_id =
+	__ATTR(rel_tgt_id, S_IRUGO | S_IWUSR, scst_rel_tgt_id_show,
+	       scst_rel_tgt_id_store);
+
+static struct kobj_attribute scst_acg_addr_method =
+	__ATTR(addr_method, S_IRUGO | S_IWUSR, scst_acg_addr_method_show,
+		scst_acg_addr_method_store);
+
+static struct kobj_attribute scst_acg_io_grouping_type =
+	__ATTR(io_grouping_type, S_IRUGO | S_IWUSR,
+	       scst_acg_io_grouping_type_show,
+	       scst_acg_io_grouping_type_store);
+
+static struct kobj_attribute scst_acg_cpu_mask =
+	__ATTR(cpu_mask, S_IRUGO | S_IWUSR,
+	       scst_acg_cpu_mask_show,
+	       scst_acg_cpu_mask_store);
+
+static ssize_t scst_tgt_enable_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	struct scst_tgt *tgt;
+	int res;
+	bool enabled;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+
+	enabled = tgt->tgtt->is_target_enabled(tgt);
+
+	res = sprintf(buf, "%d\n", enabled ? 1 : 0);
+	return res;
+}
+
+static int scst_process_tgt_enable_store(struct scst_tgt *tgt, bool enable)
+{
+	int res;
+
+	/* Tgt protected by kobject reference */
+
+	TRACE_DBG("tgt %s, enable %d", tgt->tgt_name, enable);
+
+	if (enable) {
+		if (tgt->rel_tgt_id == 0) {
+			res = gen_relative_target_port_id(&tgt->rel_tgt_id);
+			if (res != 0)
+				goto out_put;
+			PRINT_INFO("Using autogenerated rel ID %d for target "
+				"%s", tgt->rel_tgt_id, tgt->tgt_name);
+		} else {
+			if (!scst_is_relative_target_port_id_unique(
+					    tgt->rel_tgt_id, tgt)) {
+				PRINT_ERROR("Relative port id %d is not unique",
+					tgt->rel_tgt_id);
+				res = -EBADSLT;
+				goto out_put;
+			}
+		}
+	}
+
+	res = tgt->tgtt->enable_target(tgt, enable);
+
+out_put:
+	kobject_put(&tgt->tgt_kobj);
+	return res;
+}
+
+static int scst_tgt_enable_store_work_fn(struct scst_sysfs_work_item *work)
+{
+	return scst_process_tgt_enable_store(work->tgt, work->enable);
+}
+
+static ssize_t scst_tgt_enable_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	struct scst_tgt *tgt;
+	bool enable;
+	struct scst_sysfs_work_item *work;
+
+	if (buf == NULL) {
+		PRINT_ERROR("%s: NULL buffer?", __func__);
+		res = -EINVAL;
+		goto out;
+	}
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+
+	switch (buf[0]) {
+	case '0':
+		enable = false;
+		break;
+	case '1':
+		enable = true;
+		break;
+	default:
+		PRINT_ERROR("%s: Requested action not understood: %s",
+		       __func__, buf);
+		res = -EINVAL;
+		goto out;
+	}
+
+	res = scst_alloc_sysfs_work(scst_tgt_enable_store_work_fn, false,
+					&work);
+	if (res != 0)
+		goto out;
+
+	work->tgt = tgt;
+	work->enable = enable;
+
+	kobject_get(&tgt->tgt_kobj);
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res == 0)
+		res = count;
+
+out:
+	return res;
+}
+
+static struct kobj_attribute tgt_enable_attr =
+	__ATTR(enabled, S_IRUGO | S_IWUSR,
+	       scst_tgt_enable_show, scst_tgt_enable_store);
+
+/*
+ * Supposed to be called under scst_mutex. In case of error will drop,
+ * then reacquire it.
+ */
+int scst_tgt_sysfs_create(struct scst_tgt *tgt)
+{
+	int res;
+	const struct attribute **pattr;
+
+	init_completion(&tgt->tgt_kobj_release_cmpl);
+
+	res = kobject_init_and_add(&tgt->tgt_kobj, &tgt_ktype,
+			&tgt->tgtt->tgtt_kobj, tgt->tgt_name);
+	if (res != 0) {
+		PRINT_ERROR("Can't add tgt %s to sysfs", tgt->tgt_name);
+		goto out;
+	}
+
+	if ((tgt->tgtt->enable_target != NULL) &&
+	    (tgt->tgtt->is_target_enabled != NULL)) {
+		res = sysfs_create_file(&tgt->tgt_kobj,
+				&tgt_enable_attr.attr);
+		if (res != 0) {
+			PRINT_ERROR("Can't add attr %s to sysfs",
+				tgt_enable_attr.attr.name);
+			goto out_err;
+		}
+	}
+
+	tgt->tgt_sess_kobj = kobject_create_and_add("sessions", &tgt->tgt_kobj);
+	if (tgt->tgt_sess_kobj == NULL) {
+		PRINT_ERROR("Can't create sess kobj for tgt %s", tgt->tgt_name);
+		goto out_nomem;
+	}
+
+	tgt->tgt_luns_kobj = kobject_create_and_add("luns", &tgt->tgt_kobj);
+	if (tgt->tgt_luns_kobj == NULL) {
+		PRINT_ERROR("Can't create luns kobj for tgt %s", tgt->tgt_name);
+		goto out_nomem;
+	}
+
+	res = sysfs_create_file(tgt->tgt_luns_kobj, &scst_luns_mgmt.attr);
+	if (res != 0) {
+		PRINT_ERROR("Can't add attribute %s for tgt %s",
+			scst_luns_mgmt.attr.name, tgt->tgt_name);
+		goto out_err;
+	}
+
+	tgt->tgt_ini_grp_kobj = kobject_create_and_add("ini_groups",
+					&tgt->tgt_kobj);
+	if (tgt->tgt_ini_grp_kobj == NULL) {
+		PRINT_ERROR("Can't create ini_grp kobj for tgt %s",
+			tgt->tgt_name);
+		goto out_nomem;
+	}
+
+	res = sysfs_create_file(tgt->tgt_ini_grp_kobj,
+			&scst_ini_group_mgmt.attr);
+	if (res != 0) {
+		PRINT_ERROR("Can't add attribute %s for tgt %s",
+			scst_ini_group_mgmt.attr.name, tgt->tgt_name);
+		goto out_err;
+	}
+
+	res = sysfs_create_file(&tgt->tgt_kobj,
+			&scst_rel_tgt_id.attr);
+	if (res != 0) {
+		PRINT_ERROR("Can't add attribute %s for tgt %s",
+			scst_rel_tgt_id.attr.name, tgt->tgt_name);
+		goto out_err;
+	}
+
+	res = sysfs_create_file(&tgt->tgt_kobj,
+			&scst_tgt_addr_method.attr);
+	if (res != 0) {
+		PRINT_ERROR("Can't add attribute %s for tgt %s",
+			scst_tgt_addr_method.attr.name, tgt->tgt_name);
+		goto out_err;
+	}
+
+	res = sysfs_create_file(&tgt->tgt_kobj,
+			&scst_tgt_io_grouping_type.attr);
+	if (res != 0) {
+		PRINT_ERROR("Can't add attribute %s for tgt %s",
+			scst_tgt_io_grouping_type.attr.name, tgt->tgt_name);
+		goto out_err;
+	}
+
+	res = sysfs_create_file(&tgt->tgt_kobj, &scst_tgt_cpu_mask.attr);
+	if (res != 0) {
+		PRINT_ERROR("Can't add attribute %s for tgt %s",
+			scst_tgt_cpu_mask.attr.name, tgt->tgt_name);
+		goto out_err;
+	}
+
+	pattr = tgt->tgtt->tgt_attrs;
+	if (pattr != NULL) {
+		while (*pattr != NULL) {
+			TRACE_DBG("Creating attr %s for tgt %s", (*pattr)->name,
+				tgt->tgt_name);
+			res = sysfs_create_file(&tgt->tgt_kobj, *pattr);
+			if (res != 0) {
+				PRINT_ERROR("Can't add tgt attr %s for tgt %s",
+					(*pattr)->name, tgt->tgt_name);
+				goto out_err;
+			}
+			pattr++;
+		}
+	}
+
+out:
+	return res;
+
+out_nomem:
+	res = -ENOMEM;
+
+out_err:
+	mutex_unlock(&scst_mutex);
+	scst_tgt_sysfs_del(tgt);
+	mutex_lock(&scst_mutex);
+	goto out;
+}
+
+/*
+ * Must not be called under scst_mutex, due to possible deadlock with
+ * sysfs ref counting in sysfs works (it is waiting for the last put, but
+ * the last ref counter holder is waiting for scst_mutex)
+ */
+void scst_tgt_sysfs_del(struct scst_tgt *tgt)
+{
+	int rc;
+
+	kobject_del(tgt->tgt_sess_kobj);
+	kobject_put(tgt->tgt_sess_kobj);
+
+	kobject_del(tgt->tgt_luns_kobj);
+	kobject_put(tgt->tgt_luns_kobj);
+
+	kobject_del(tgt->tgt_ini_grp_kobj);
+	kobject_put(tgt->tgt_ini_grp_kobj);
+
+	kobject_del(&tgt->tgt_kobj);
+	kobject_put(&tgt->tgt_kobj);
+
+	rc = wait_for_completion_timeout(&tgt->tgt_kobj_release_cmpl, HZ);
+	if (rc == 0) {
+		PRINT_INFO("Waiting for releasing sysfs entry "
+			"for target %s (%d refs)...", tgt->tgt_name,
+			atomic_read(&tgt->tgt_kobj.kref.refcount));
+		wait_for_completion(&tgt->tgt_kobj_release_cmpl);
+		PRINT_INFO("Done waiting for releasing sysfs "
+			"entry for target %s", tgt->tgt_name);
+	}
+	return;
+}
+
+/**
+ ** Devices directory implementation
+ **/
+
+static ssize_t scst_dev_sysfs_type_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+
+	struct scst_device *dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	pos = sprintf(buf, "%d - %s\n", dev->type,
+		(unsigned)dev->type > ARRAY_SIZE(scst_dev_handler_types) ?
+		      "unknown" : scst_dev_handler_types[dev->type]);
+
+	return pos;
+}
+
+static struct kobj_attribute dev_type_attr =
+	__ATTR(type, S_IRUGO, scst_dev_sysfs_type_show, NULL);
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+
+static ssize_t scst_dev_sysfs_dump_prs(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	struct scst_device *dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	scst_pr_dump_prs(dev, true);
+	return count;
+}
+
+static struct kobj_attribute dev_dump_prs_attr =
+	__ATTR(dump_prs, S_IWUSR, NULL, scst_dev_sysfs_dump_prs);
+
+#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
+
+static int scst_process_dev_sysfs_threads_data_store(
+	struct scst_device *dev, int threads_num,
+	enum scst_dev_type_threads_pool_type threads_pool_type)
+{
+	int res = 0;
+	int oldtn = dev->threads_num;
+	enum scst_dev_type_threads_pool_type oldtt = dev->threads_pool_type;
+
+	TRACE_DBG("dev %p, threads_num %d, threads_pool_type %d", dev,
+		threads_num, threads_pool_type);
+
+	res = scst_suspend_activity(true);
+	if (res != 0)
+		goto out;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out_resume;
+	}
+
+	/* Check if our pointer is still alive */
+	if (scst_check_dev_ptr(dev) != 0)
+		goto out_unlock;
+
+	scst_stop_dev_threads(dev);
+
+	dev->threads_num = threads_num;
+	dev->threads_pool_type = threads_pool_type;
+
+	res = scst_create_dev_threads(dev);
+	if (res != 0)
+		goto out_unlock;
+
+	if (oldtn != dev->threads_num)
+		PRINT_INFO("Changed cmd threads num to %d", dev->threads_num);
+	else if (oldtt != dev->threads_pool_type)
+		PRINT_INFO("Changed cmd threads pool type to %d",
+			dev->threads_pool_type);
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+
+out_resume:
+	scst_resume_activity();
+
+out:
+	return res;
+}
+
+static int scst_dev_sysfs_threads_data_store_work_fn(
+	struct scst_sysfs_work_item *work)
+{
+	return scst_process_dev_sysfs_threads_data_store(work->dev,
+		work->new_threads_num, work->new_threads_pool_type);
+}
+
+static ssize_t scst_dev_sysfs_check_threads_data(
+	struct scst_device *dev, int threads_num,
+	enum scst_dev_type_threads_pool_type threads_pool_type, bool *stop)
+{
+	int res = 0;
+
+	*stop = false;
+
+	if (dev->threads_num < 0) {
+		PRINT_ERROR("Threads pool disabled for device %s",
+			dev->virt_name);
+		res = -EPERM;
+		goto out;
+	}
+
+	if ((threads_num == dev->threads_num) &&
+	    (threads_pool_type == dev->threads_pool_type)) {
+		*stop = true;
+		goto out;
+	}
+
+out:
+	return res;
+}
+
+static ssize_t scst_dev_sysfs_threads_num_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	pos = sprintf(buf, "%d\n%s", dev->threads_num,
+		(dev->threads_num != dev->handler->threads_num) ?
+			SCST_SYSFS_KEY_MARK "\n" : "");
+	return pos;
+}
+
+static ssize_t scst_dev_sysfs_threads_num_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	struct scst_device *dev;
+	long newtn;
+	bool stop;
+	struct scst_sysfs_work_item *work;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	res = strict_strtol(buf, 0, &newtn);
+	if (res != 0) {
+		PRINT_ERROR("strict_strtol() for %s failed: %d ", buf, res);
+		goto out;
+	}
+	if (newtn < 0) {
+		PRINT_ERROR("Illegal threads num value %ld", newtn);
+		res = -EINVAL;
+		goto out;
+	}
+
+	res = scst_dev_sysfs_check_threads_data(dev, newtn,
+		dev->threads_pool_type, &stop);
+	if ((res != 0) || stop)
+		goto out;
+
+	res = scst_alloc_sysfs_work(scst_dev_sysfs_threads_data_store_work_fn,
+					false, &work);
+	if (res != 0)
+		goto out;
+
+	work->dev = dev;
+	work->new_threads_num = newtn;
+	work->new_threads_pool_type = dev->threads_pool_type;
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res == 0)
+		res = count;
+
+out:
+	return res;
+}
+
+static struct kobj_attribute dev_threads_num_attr =
+	__ATTR(threads_num, S_IRUGO | S_IWUSR,
+		scst_dev_sysfs_threads_num_show,
+		scst_dev_sysfs_threads_num_store);
+
+static ssize_t scst_dev_sysfs_threads_pool_type_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	if (dev->threads_num == 0) {
+		pos = sprintf(buf, "Async\n");
+		goto out;
+	} else if (dev->threads_num < 0) {
+		pos = sprintf(buf, "Not valid\n");
+		goto out;
+	}
+
+	switch (dev->threads_pool_type) {
+	case SCST_THREADS_POOL_PER_INITIATOR:
+		pos = sprintf(buf, "%s\n%s", SCST_THREADS_POOL_PER_INITIATOR_STR,
+			(dev->threads_pool_type != dev->handler->threads_pool_type) ?
+				SCST_SYSFS_KEY_MARK "\n" : "");
+		break;
+	case SCST_THREADS_POOL_SHARED:
+		pos = sprintf(buf, "%s\n%s", SCST_THREADS_POOL_SHARED_STR,
+			(dev->threads_pool_type != dev->handler->threads_pool_type) ?
+				SCST_SYSFS_KEY_MARK "\n" : "");
+		break;
+	default:
+		pos = sprintf(buf, "Unknown\n");
+		break;
+	}
+
+out:
+	return pos;
+}
+
+static ssize_t scst_dev_sysfs_threads_pool_type_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	struct scst_device *dev;
+	enum scst_dev_type_threads_pool_type newtpt;
+	struct scst_sysfs_work_item *work;
+	bool stop;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	newtpt = scst_parse_threads_pool_type(buf, count);
+	if (newtpt == SCST_THREADS_POOL_TYPE_INVALID) {
+		PRINT_ERROR("Illegal threads pool type %s", buf);
+		res = -EINVAL;
+		goto out;
+	}
+
+	TRACE_DBG("buf %s, count %zd, newtpt %d", buf, count, newtpt);
+
+	res = scst_dev_sysfs_check_threads_data(dev, dev->threads_num,
+		newtpt, &stop);
+	if ((res != 0) || stop)
+		goto out;
+
+	res = scst_alloc_sysfs_work(scst_dev_sysfs_threads_data_store_work_fn,
+					false, &work);
+	if (res != 0)
+		goto out;
+
+	work->dev = dev;
+	work->new_threads_num = dev->threads_num;
+	work->new_threads_pool_type = newtpt;
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res == 0)
+		res = count;
+
+out:
+	return res;
+}
+
+static struct kobj_attribute dev_threads_pool_type_attr =
+	__ATTR(threads_pool_type, S_IRUGO | S_IWUSR,
+		scst_dev_sysfs_threads_pool_type_show,
+		scst_dev_sysfs_threads_pool_type_store);
+
+static struct attribute *scst_dev_attrs[] = {
+	&dev_type_attr.attr,
+	NULL,
+};
+
+static void scst_sysfs_dev_release(struct kobject *kobj)
+{
+	struct scst_device *dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	complete_all(&dev->dev_kobj_release_cmpl);
+	return;
+}
+
+int scst_devt_dev_sysfs_create(struct scst_device *dev)
+{
+	int res = 0;
+	const struct attribute **pattr;
+
+	if (dev->handler == &scst_null_devtype)
+		goto out;
+
+	res = sysfs_create_link(&dev->dev_kobj,
+			&dev->handler->devt_kobj, "handler");
+	if (res != 0) {
+		PRINT_ERROR("Can't create handler link for dev %s",
+			dev->virt_name);
+		goto out;
+	}
+
+	res = sysfs_create_link(&dev->handler->devt_kobj,
+			&dev->dev_kobj, dev->virt_name);
+	if (res != 0) {
+		PRINT_ERROR("Can't create handler link for dev %s",
+			dev->virt_name);
+		goto out_err;
+	}
+
+	if (dev->handler->threads_num >= 0) {
+		res = sysfs_create_file(&dev->dev_kobj,
+				&dev_threads_num_attr.attr);
+		if (res != 0) {
+			PRINT_ERROR("Can't add dev attr %s for dev %s",
+				dev_threads_num_attr.attr.name,
+				dev->virt_name);
+			goto out_err;
+		}
+		res = sysfs_create_file(&dev->dev_kobj,
+				&dev_threads_pool_type_attr.attr);
+		if (res != 0) {
+			PRINT_ERROR("Can't add dev attr %s for dev %s",
+				dev_threads_pool_type_attr.attr.name,
+				dev->virt_name);
+			goto out_err;
+		}
+	}
+
+	pattr = dev->handler->dev_attrs;
+	if (pattr != NULL) {
+		while (*pattr != NULL) {
+			res = sysfs_create_file(&dev->dev_kobj, *pattr);
+			if (res != 0) {
+				PRINT_ERROR("Can't add dev attr %s for dev %s",
+					(*pattr)->name, dev->virt_name);
+				goto out_err;
+			}
+			pattr++;
+		}
+	}
+
+out:
+	return res;
+
+out_err:
+	scst_devt_dev_sysfs_del(dev);
+	goto out;
+}
+
+void scst_devt_dev_sysfs_del(struct scst_device *dev)
+{
+	const struct attribute **pattr;
+
+	if (dev->handler == &scst_null_devtype)
+		goto out;
+
+	pattr = dev->handler->dev_attrs;
+	if (pattr != NULL) {
+		while (*pattr != NULL) {
+			sysfs_remove_file(&dev->dev_kobj, *pattr);
+			pattr++;
+		}
+	}
+
+	sysfs_remove_link(&dev->dev_kobj, "handler");
+	sysfs_remove_link(&dev->handler->devt_kobj, dev->virt_name);
+
+	if (dev->handler->threads_num >= 0) {
+		sysfs_remove_file(&dev->dev_kobj,
+			&dev_threads_num_attr.attr);
+		sysfs_remove_file(&dev->dev_kobj,
+			&dev_threads_pool_type_attr.attr);
+	}
+
+out:
+	return;
+}
+
+static struct kobj_type scst_dev_ktype = {
+	.sysfs_ops = &scst_sysfs_ops,
+	.release = scst_sysfs_dev_release,
+	.default_attrs = scst_dev_attrs,
+};
+
+/*
+ * Must not be called under scst_mutex, because it can call
+ * scst_dev_sysfs_del()
+ */
+int scst_dev_sysfs_create(struct scst_device *dev)
+{
+	int res = 0;
+
+	init_completion(&dev->dev_kobj_release_cmpl);
+
+	res = kobject_init_and_add(&dev->dev_kobj, &scst_dev_ktype,
+				      scst_devices_kobj, dev->virt_name);
+	if (res != 0) {
+		PRINT_ERROR("Can't add device %s to sysfs", dev->virt_name);
+		goto out;
+	}
+
+	dev->dev_exp_kobj = kobject_create_and_add("exported",
+						   &dev->dev_kobj);
+	if (dev->dev_exp_kobj == NULL) {
+		PRINT_ERROR("Can't create exported link for device %s",
+			dev->virt_name);
+		res = -ENOMEM;
+		goto out_del;
+	}
+
+	if (dev->scsi_dev != NULL) {
+		res = sysfs_create_link(&dev->dev_kobj,
+			&dev->scsi_dev->sdev_dev.kobj, "scsi_device");
+		if (res != 0) {
+			PRINT_ERROR("Can't create scsi_device link for dev %s",
+				dev->virt_name);
+			goto out_del;
+		}
+	}
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+	if (dev->scsi_dev == NULL) {
+		res = sysfs_create_file(&dev->dev_kobj,
+				&dev_dump_prs_attr.attr);
+		if (res != 0) {
+			PRINT_ERROR("Can't create attr %s for dev %s",
+				dev_dump_prs_attr.attr.name, dev->virt_name);
+			goto out_del;
+		}
+	}
+#endif
+
+out:
+	return res;
+
+out_del:
+	scst_dev_sysfs_del(dev);
+	goto out;
+}
+
+/*
+ * Must not be called under scst_mutex, due to possible deadlock with
+ * sysfs ref counting in sysfs works (it is waiting for the last put, but
+ * the last ref counter holder is waiting for scst_mutex)
+ */
+void scst_dev_sysfs_del(struct scst_device *dev)
+{
+	int rc;
+
+	kobject_del(dev->dev_exp_kobj);
+	kobject_put(dev->dev_exp_kobj);
+
+	kobject_del(&dev->dev_kobj);
+	kobject_put(&dev->dev_kobj);
+
+	rc = wait_for_completion_timeout(&dev->dev_kobj_release_cmpl, HZ);
+	if (rc == 0) {
+		PRINT_INFO("Waiting for releasing sysfs entry "
+			"for device %s (%d refs)...", dev->virt_name,
+			atomic_read(&dev->dev_kobj.kref.refcount));
+		wait_for_completion(&dev->dev_kobj_release_cmpl);
+		PRINT_INFO("Done waiting for releasing sysfs "
+			"entry for device %s", dev->virt_name);
+	}
+	return;
+}
+
+/**
+ ** Tgt_dev's directory implementation
+ **/
+
+#ifdef CONFIG_SCST_MEASURE_LATENCY
+
+static char *scst_io_size_names[] = {
+	"<=8K  ",
+	"<=32K ",
+	"<=128K",
+	"<=512K",
+	">512K "
+};
+
+static ssize_t scst_tgt_dev_latency_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buffer)
+{
+	int res = 0, i;
+	char buf[50];
+	struct scst_tgt_dev *tgt_dev;
+
+	tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj);
+
+	for (i = 0; i < SCST_LATENCY_STATS_NUM; i++) {
+		uint64_t scst_time_wr, tgt_time_wr, dev_time_wr;
+		unsigned int processed_cmds_wr;
+		uint64_t scst_time_rd, tgt_time_rd, dev_time_rd;
+		unsigned int processed_cmds_rd;
+		struct scst_ext_latency_stat *latency_stat;
+
+		latency_stat = &tgt_dev->dev_latency_stat[i];
+		scst_time_wr = latency_stat->scst_time_wr;
+		scst_time_rd = latency_stat->scst_time_rd;
+		tgt_time_wr = latency_stat->tgt_time_wr;
+		tgt_time_rd = latency_stat->tgt_time_rd;
+		dev_time_wr = latency_stat->dev_time_wr;
+		dev_time_rd = latency_stat->dev_time_rd;
+		processed_cmds_wr = latency_stat->processed_cmds_wr;
+		processed_cmds_rd = latency_stat->processed_cmds_rd;
+
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			 "%-5s %-9s %-15lu ", "Write", scst_io_size_names[i],
+			(unsigned long)processed_cmds_wr);
+		if (processed_cmds_wr == 0)
+			processed_cmds_wr = 1;
+
+		do_div(scst_time_wr, processed_cmds_wr);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_scst_time_wr,
+			(unsigned long)scst_time_wr,
+			(unsigned long)latency_stat->max_scst_time_wr,
+			(unsigned long)latency_stat->scst_time_wr);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s", buf);
+
+		do_div(tgt_time_wr, processed_cmds_wr);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_tgt_time_wr,
+			(unsigned long)tgt_time_wr,
+			(unsigned long)latency_stat->max_tgt_time_wr,
+			(unsigned long)latency_stat->tgt_time_wr);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s", buf);
+
+		do_div(dev_time_wr, processed_cmds_wr);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_dev_time_wr,
+			(unsigned long)dev_time_wr,
+			(unsigned long)latency_stat->max_dev_time_wr,
+			(unsigned long)latency_stat->dev_time_wr);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s\n", buf);
+
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-5s %-9s %-15lu ", "Read", scst_io_size_names[i],
+			(unsigned long)processed_cmds_rd);
+		if (processed_cmds_rd == 0)
+			processed_cmds_rd = 1;
+
+		do_div(scst_time_rd, processed_cmds_rd);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_scst_time_rd,
+			(unsigned long)scst_time_rd,
+			(unsigned long)latency_stat->max_scst_time_rd,
+			(unsigned long)latency_stat->scst_time_rd);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s", buf);
+
+		do_div(tgt_time_rd, processed_cmds_rd);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_tgt_time_rd,
+			(unsigned long)tgt_time_rd,
+			(unsigned long)latency_stat->max_tgt_time_rd,
+			(unsigned long)latency_stat->tgt_time_rd);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s", buf);
+
+		do_div(dev_time_rd, processed_cmds_rd);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_dev_time_rd,
+			(unsigned long)dev_time_rd,
+			(unsigned long)latency_stat->max_dev_time_rd,
+			(unsigned long)latency_stat->dev_time_rd);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s\n", buf);
+	}
+	return res;
+}
+
+static struct kobj_attribute tgt_dev_latency_attr =
+	__ATTR(latency, S_IRUGO,
+		scst_tgt_dev_latency_show, NULL);
+
+#endif /* CONFIG_SCST_MEASURE_LATENCY */
+
+static ssize_t scst_tgt_dev_active_commands_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_tgt_dev *tgt_dev;
+
+	tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj);
+
+	pos = sprintf(buf, "%d\n", atomic_read(&tgt_dev->tgt_dev_cmd_count));
+
+	return pos;
+}
+
+static struct kobj_attribute tgt_dev_active_commands_attr =
+	__ATTR(active_commands, S_IRUGO,
+		scst_tgt_dev_active_commands_show, NULL);
+
+static struct attribute *scst_tgt_dev_attrs[] = {
+	&tgt_dev_active_commands_attr.attr,
+#ifdef CONFIG_SCST_MEASURE_LATENCY
+	&tgt_dev_latency_attr.attr,
+#endif
+	NULL,
+};
+
+static void scst_sysfs_tgt_dev_release(struct kobject *kobj)
+{
+	struct scst_tgt_dev *tgt_dev;
+
+	tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj);
+	complete_all(&tgt_dev->tgt_dev_kobj_release_cmpl);
+	return;
+}
+
+static struct kobj_type scst_tgt_dev_ktype = {
+	.sysfs_ops = &scst_sysfs_ops,
+	.release = scst_sysfs_tgt_dev_release,
+	.default_attrs = scst_tgt_dev_attrs,
+};
+
+int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev)
+{
+	int res = 0;
+
+	init_completion(&tgt_dev->tgt_dev_kobj_release_cmpl);
+
+	res = kobject_init_and_add(&tgt_dev->tgt_dev_kobj, &scst_tgt_dev_ktype,
+			      &tgt_dev->sess->sess_kobj, "lun%lld",
+			      (unsigned long long)tgt_dev->lun);
+	if (res != 0) {
+		PRINT_ERROR("Can't add tgt_dev %lld to sysfs",
+			(unsigned long long)tgt_dev->lun);
+		goto out;
+	}
+
+out:
+	return res;
+}
+
+/*
+ * Called with scst_mutex held.
+ *
+ * !! No sysfs works must use kobject_get() to protect tgt_dev, due to possible
+ * !! deadlock with scst_mutex (it is waiting for the last put, but
+ * !! the last ref counter holder is waiting for scst_mutex)
+ */
+void scst_tgt_dev_sysfs_del(struct scst_tgt_dev *tgt_dev)
+{
+	int rc;
+
+	kobject_del(&tgt_dev->tgt_dev_kobj);
+	kobject_put(&tgt_dev->tgt_dev_kobj);
+
+	rc = wait_for_completion_timeout(
+			&tgt_dev->tgt_dev_kobj_release_cmpl, HZ);
+	if (rc == 0) {
+		PRINT_INFO("Waiting for releasing sysfs entry "
+			"for tgt_dev %lld (%d refs)...",
+			(unsigned long long)tgt_dev->lun,
+			atomic_read(&tgt_dev->tgt_dev_kobj.kref.refcount));
+		wait_for_completion(&tgt_dev->tgt_dev_kobj_release_cmpl);
+		PRINT_INFO("Done waiting for releasing sysfs entry for "
+			"tgt_dev %lld", (unsigned long long)tgt_dev->lun);
+	}
+	return;
+}
+
+/**
+ ** Sessions subdirectory implementation
+ **/
+
+#ifdef CONFIG_SCST_MEASURE_LATENCY
+
+static ssize_t scst_sess_latency_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buffer)
+{
+	ssize_t res = 0;
+	struct scst_session *sess;
+	int i;
+	char buf[50];
+	uint64_t scst_time, tgt_time, dev_time;
+	unsigned int processed_cmds;
+
+	sess = container_of(kobj, struct scst_session, sess_kobj);
+
+	res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+		"%-15s %-15s %-46s %-46s %-46s\n",
+		"T-L names", "Total commands", "SCST latency",
+		"Target latency", "Dev latency (min/avg/max/all ns)");
+
+	spin_lock_bh(&sess->lat_lock);
+
+	for (i = 0; i < SCST_LATENCY_STATS_NUM ; i++) {
+		uint64_t scst_time_wr, tgt_time_wr, dev_time_wr;
+		unsigned int processed_cmds_wr;
+		uint64_t scst_time_rd, tgt_time_rd, dev_time_rd;
+		unsigned int processed_cmds_rd;
+		struct scst_ext_latency_stat *latency_stat;
+
+		latency_stat = &sess->sess_latency_stat[i];
+		scst_time_wr = latency_stat->scst_time_wr;
+		scst_time_rd = latency_stat->scst_time_rd;
+		tgt_time_wr = latency_stat->tgt_time_wr;
+		tgt_time_rd = latency_stat->tgt_time_rd;
+		dev_time_wr = latency_stat->dev_time_wr;
+		dev_time_rd = latency_stat->dev_time_rd;
+		processed_cmds_wr = latency_stat->processed_cmds_wr;
+		processed_cmds_rd = latency_stat->processed_cmds_rd;
+
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-5s %-9s %-15lu ",
+			"Write", scst_io_size_names[i],
+			(unsigned long)processed_cmds_wr);
+		if (processed_cmds_wr == 0)
+			processed_cmds_wr = 1;
+
+		do_div(scst_time_wr, processed_cmds_wr);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_scst_time_wr,
+			(unsigned long)scst_time_wr,
+			(unsigned long)latency_stat->max_scst_time_wr,
+			(unsigned long)latency_stat->scst_time_wr);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s", buf);
+
+		do_div(tgt_time_wr, processed_cmds_wr);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_tgt_time_wr,
+			(unsigned long)tgt_time_wr,
+			(unsigned long)latency_stat->max_tgt_time_wr,
+			(unsigned long)latency_stat->tgt_time_wr);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s", buf);
+
+		do_div(dev_time_wr, processed_cmds_wr);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_dev_time_wr,
+			(unsigned long)dev_time_wr,
+			(unsigned long)latency_stat->max_dev_time_wr,
+			(unsigned long)latency_stat->dev_time_wr);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s\n", buf);
+
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-5s %-9s %-15lu ",
+			"Read", scst_io_size_names[i],
+			(unsigned long)processed_cmds_rd);
+		if (processed_cmds_rd == 0)
+			processed_cmds_rd = 1;
+
+		do_div(scst_time_rd, processed_cmds_rd);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_scst_time_rd,
+			(unsigned long)scst_time_rd,
+			(unsigned long)latency_stat->max_scst_time_rd,
+			(unsigned long)latency_stat->scst_time_rd);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s", buf);
+
+		do_div(tgt_time_rd, processed_cmds_rd);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_tgt_time_rd,
+			(unsigned long)tgt_time_rd,
+			(unsigned long)latency_stat->max_tgt_time_rd,
+			(unsigned long)latency_stat->tgt_time_rd);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s", buf);
+
+		do_div(dev_time_rd, processed_cmds_rd);
+		snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+			(unsigned long)latency_stat->min_dev_time_rd,
+			(unsigned long)dev_time_rd,
+			(unsigned long)latency_stat->max_dev_time_rd,
+			(unsigned long)latency_stat->dev_time_rd);
+		res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+			"%-47s\n", buf);
+	}
+
+	scst_time = sess->scst_time;
+	tgt_time = sess->tgt_time;
+	dev_time = sess->dev_time;
+	processed_cmds = sess->processed_cmds;
+
+	res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+		"\n%-15s %-16d", "Overall ", processed_cmds);
+
+	if (processed_cmds == 0)
+		processed_cmds = 1;
+
+	do_div(scst_time, processed_cmds);
+	snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+		(unsigned long)sess->min_scst_time,
+		(unsigned long)scst_time,
+		(unsigned long)sess->max_scst_time,
+		(unsigned long)sess->scst_time);
+	res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+		"%-47s", buf);
+
+	do_div(tgt_time, processed_cmds);
+	snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+		(unsigned long)sess->min_tgt_time,
+		(unsigned long)tgt_time,
+		(unsigned long)sess->max_tgt_time,
+		(unsigned long)sess->tgt_time);
+	res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+		"%-47s", buf);
+
+	do_div(dev_time, processed_cmds);
+	snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
+		(unsigned long)sess->min_dev_time,
+		(unsigned long)dev_time,
+		(unsigned long)sess->max_dev_time,
+		(unsigned long)sess->dev_time);
+	res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
+		"%-47s\n\n", buf);
+
+	spin_unlock_bh(&sess->lat_lock);
+	return res;
+}
+
+static int scst_sess_zero_latency(struct scst_sysfs_work_item *work)
+{
+	int res = 0, t;
+	struct scst_session *sess = work->sess;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out_put;
+	}
+
+	PRINT_INFO("Zeroing latency statistics for initiator "
+		"%s", sess->initiator_name);
+
+	spin_lock_bh(&sess->lat_lock);
+
+	sess->scst_time = 0;
+	sess->tgt_time = 0;
+	sess->dev_time = 0;
+	sess->min_scst_time = 0;
+	sess->min_tgt_time = 0;
+	sess->min_dev_time = 0;
+	sess->max_scst_time = 0;
+	sess->max_tgt_time = 0;
+	sess->max_dev_time = 0;
+	sess->processed_cmds = 0;
+	memset(sess->sess_latency_stat, 0,
+		sizeof(sess->sess_latency_stat));
+
+	for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) {
+		struct list_head *head = &sess->sess_tgt_dev_list[t];
+		struct scst_tgt_dev *tgt_dev;
+		list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
+			tgt_dev->scst_time = 0;
+			tgt_dev->tgt_time = 0;
+			tgt_dev->dev_time = 0;
+			tgt_dev->processed_cmds = 0;
+			memset(tgt_dev->dev_latency_stat, 0,
+				sizeof(tgt_dev->dev_latency_stat));
+		}
+	}
+
+	spin_unlock_bh(&sess->lat_lock);
+
+	mutex_unlock(&scst_mutex);
+
+out_put:
+	kobject_put(&sess->sess_kobj);
+	return res;
+}
+
+static ssize_t scst_sess_latency_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	struct scst_session *sess;
+	struct scst_sysfs_work_item *work;
+
+	sess = container_of(kobj, struct scst_session, sess_kobj);
+
+	res = scst_alloc_sysfs_work(scst_sess_zero_latency, false, &work);
+	if (res != 0)
+		goto out;
+
+	work->sess = sess;
+
+	kobject_get(&sess->sess_kobj);
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res == 0)
+		res = count;
+
+out:
+	return res;
+}
+
+static struct kobj_attribute session_latency_attr =
+	__ATTR(latency, S_IRUGO | S_IWUSR, scst_sess_latency_show,
+	       scst_sess_latency_store);
+
+#endif /* CONFIG_SCST_MEASURE_LATENCY */
+
+static ssize_t scst_sess_sysfs_commands_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	struct scst_session *sess;
+
+	sess = container_of(kobj, struct scst_session, sess_kobj);
+
+	return sprintf(buf, "%i\n", atomic_read(&sess->sess_cmd_count));
+}
+
+static struct kobj_attribute session_commands_attr =
+	__ATTR(commands, S_IRUGO, scst_sess_sysfs_commands_show, NULL);
+
+static int scst_sysfs_sess_get_active_commands(struct scst_session *sess)
+{
+	int res;
+	int active_cmds = 0, t;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out_put;
+	}
+
+	for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) {
+		struct list_head *head = &sess->sess_tgt_dev_list[t];
+		struct scst_tgt_dev *tgt_dev;
+		list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
+			active_cmds += atomic_read(&tgt_dev->tgt_dev_cmd_count);
+		}
+	}
+
+	mutex_unlock(&scst_mutex);
+
+	res = active_cmds;
+
+out_put:
+	kobject_put(&sess->sess_kobj);
+	return res;
+}
+
+static int scst_sysfs_sess_get_active_commands_work_fn(struct scst_sysfs_work_item *work)
+{
+	return scst_sysfs_sess_get_active_commands(work->sess);
+}
+
+static ssize_t scst_sess_sysfs_active_commands_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	int res;
+	struct scst_session *sess;
+	struct scst_sysfs_work_item *work;
+
+	sess = container_of(kobj, struct scst_session, sess_kobj);
+
+	res = scst_alloc_sysfs_work(scst_sysfs_sess_get_active_commands_work_fn,
+			true, &work);
+	if (res != 0)
+		goto out;
+
+	work->sess = sess;
+
+	kobject_get(&sess->sess_kobj);
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res != -EAGAIN)
+		res = sprintf(buf, "%i\n", res);
+
+out:
+	return res;
+}
+
+static struct kobj_attribute session_active_commands_attr =
+	__ATTR(active_commands, S_IRUGO, scst_sess_sysfs_active_commands_show,
+		NULL);
+
+static ssize_t scst_sess_sysfs_initiator_name_show(struct kobject *kobj,
+			    struct kobj_attribute *attr, char *buf)
+{
+	struct scst_session *sess;
+
+	sess = container_of(kobj, struct scst_session, sess_kobj);
+
+	return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n",
+		sess->initiator_name);
+}
+
+static struct kobj_attribute session_initiator_name_attr =
+	__ATTR(initiator_name, S_IRUGO, scst_sess_sysfs_initiator_name_show, NULL);
+
+static struct attribute *scst_session_attrs[] = {
+	&session_commands_attr.attr,
+	&session_active_commands_attr.attr,
+	&session_initiator_name_attr.attr,
+#ifdef CONFIG_SCST_MEASURE_LATENCY
+	&session_latency_attr.attr,
+#endif /* CONFIG_SCST_MEASURE_LATENCY */
+	NULL,
+};
+
+static void scst_sysfs_session_release(struct kobject *kobj)
+{
+	struct scst_session *sess;
+
+	sess = container_of(kobj, struct scst_session, sess_kobj);
+	complete_all(&sess->sess_kobj_release_cmpl);
+	return;
+}
+
+static struct kobj_type scst_session_ktype = {
+	.sysfs_ops = &scst_sysfs_ops,
+	.release = scst_sysfs_session_release,
+	.default_attrs = scst_session_attrs,
+};
+
+static int scst_create_sess_luns_link(struct scst_session *sess)
+{
+	int res;
+
+	/*
+	 * No locks are needed, because sess supposed to be in acg->acg_sess_list
+	 * and tgt->sess_list, so blocking them from disappearing.
+	 */
+
+	if (sess->acg == sess->tgt->default_acg)
+		res = sysfs_create_link(&sess->sess_kobj,
+				sess->tgt->tgt_luns_kobj, "luns");
+	else
+		res = sysfs_create_link(&sess->sess_kobj,
+				sess->acg->luns_kobj, "luns");
+
+	if (res != 0)
+		PRINT_ERROR("Can't create luns link for initiator %s",
+			sess->initiator_name);
+
+	return res;
+}
+
+int scst_recreate_sess_luns_link(struct scst_session *sess)
+{
+	sysfs_remove_link(&sess->sess_kobj, "luns");
+	return scst_create_sess_luns_link(sess);
+}
+
+/* Supposed to be called under scst_mutex */
+int scst_sess_sysfs_create(struct scst_session *sess)
+{
+	int res = 0;
+	struct scst_session *s;
+	const struct attribute **pattr;
+	char *name = (char *)sess->initiator_name;
+	int len = strlen(name) + 1, n = 1;
+
+restart:
+	list_for_each_entry(s, &sess->tgt->sess_list, sess_list_entry) {
+		if (!s->sess_kobj_ready)
+			continue;
+
+		if (strcmp(name, kobject_name(&s->sess_kobj)) == 0) {
+			if (s == sess)
+				continue;
+
+			TRACE_DBG("Dublicated session from the same initiator "
+				"%s found", name);
+
+			if (name == sess->initiator_name) {
+				len = strlen(sess->initiator_name);
+				len += 20;
+				name = kmalloc(len, GFP_KERNEL);
+				if (name == NULL) {
+					PRINT_ERROR("Unable to allocate a "
+						"replacement name (size %d)",
+						len);
+				}
+			}
+
+			snprintf(name, len, "%s_%d", sess->initiator_name, n);
+			n++;
+			goto restart;
+		}
+	}
+
+	init_completion(&sess->sess_kobj_release_cmpl);
+
+	TRACE_DBG("Adding session %s to sysfs", name);
+
+	res = kobject_init_and_add(&sess->sess_kobj, &scst_session_ktype,
+			      sess->tgt->tgt_sess_kobj, name);
+	if (res != 0) {
+		PRINT_ERROR("Can't add session %s to sysfs", name);
+		goto out_free;
+	}
+
+	sess->sess_kobj_ready = 1;
+
+	pattr = sess->tgt->tgtt->sess_attrs;
+	if (pattr != NULL) {
+		while (*pattr != NULL) {
+			res = sysfs_create_file(&sess->sess_kobj, *pattr);
+			if (res != 0) {
+				PRINT_ERROR("Can't add sess attr %s for sess "
+					"for initiator %s", (*pattr)->name,
+					name);
+				goto out_free;
+			}
+			pattr++;
+		}
+	}
+
+	res = scst_create_sess_luns_link(sess);
+
+out_free:
+	if (name != sess->initiator_name)
+		kfree(name);
+	return res;
+}
+
+/*
+ * Must not be called under scst_mutex, due to possible deadlock with
+ * sysfs ref counting in sysfs works (it is waiting for the last put, but
+ * the last ref counter holder is waiting for scst_mutex)
+ */
+void scst_sess_sysfs_del(struct scst_session *sess)
+{
+	int rc;
+
+	if (!sess->sess_kobj_ready)
+		goto out;
+
+	TRACE_DBG("Deleting session %s from sysfs",
+		kobject_name(&sess->sess_kobj));
+
+	kobject_del(&sess->sess_kobj);
+	kobject_put(&sess->sess_kobj);
+
+	rc = wait_for_completion_timeout(&sess->sess_kobj_release_cmpl, HZ);
+	if (rc == 0) {
+		PRINT_INFO("Waiting for releasing sysfs entry "
+			"for session from %s (%d refs)...", sess->initiator_name,
+			atomic_read(&sess->sess_kobj.kref.refcount));
+		wait_for_completion(&sess->sess_kobj_release_cmpl);
+		PRINT_INFO("Done waiting for releasing sysfs "
+			"entry for session %s", sess->initiator_name);
+	}
+
+out:
+	return;
+}
+
+/**
+ ** Target luns directory implementation
+ **/
+
+static void scst_acg_dev_release(struct kobject *kobj)
+{
+	struct scst_acg_dev *acg_dev;
+
+	acg_dev = container_of(kobj, struct scst_acg_dev, acg_dev_kobj);
+	complete_all(&acg_dev->acg_dev_kobj_release_cmpl);
+	return;
+}
+
+static ssize_t scst_lun_rd_only_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf)
+{
+	struct scst_acg_dev *acg_dev;
+
+	acg_dev = container_of(kobj, struct scst_acg_dev, acg_dev_kobj);
+
+	if (acg_dev->rd_only || acg_dev->dev->rd_only)
+		return sprintf(buf, "%d\n%s\n", 1, SCST_SYSFS_KEY_MARK);
+	else
+		return sprintf(buf, "%d\n", 0);
+}
+
+static struct kobj_attribute lun_options_attr =
+	__ATTR(read_only, S_IRUGO, scst_lun_rd_only_show, NULL);
+
+static struct attribute *lun_attrs[] = {
+	&lun_options_attr.attr,
+	NULL,
+};
+
+static struct kobj_type acg_dev_ktype = {
+	.sysfs_ops = &scst_sysfs_ops,
+	.release = scst_acg_dev_release,
+	.default_attrs = lun_attrs,
+};
+
+/*
+ * Called with scst_mutex held.
+ *
+ * !! No sysfs works must use kobject_get() to protect acg_dev, due to possible
+ * !! deadlock with scst_mutex (it is waiting for the last put, but
+ * !! the last ref counter holder is waiting for scst_mutex)
+ */
+void scst_acg_dev_sysfs_del(struct scst_acg_dev *acg_dev)
+{
+	int rc;
+
+	if (acg_dev->dev != NULL) {
+		sysfs_remove_link(acg_dev->dev->dev_exp_kobj,
+			acg_dev->acg_dev_link_name);
+		kobject_put(&acg_dev->dev->dev_kobj);
+	}
+
+	kobject_del(&acg_dev->acg_dev_kobj);
+	kobject_put(&acg_dev->acg_dev_kobj);
+
+	rc = wait_for_completion_timeout(&acg_dev->acg_dev_kobj_release_cmpl, HZ);
+	if (rc == 0) {
+		PRINT_INFO("Waiting for releasing sysfs entry "
+			"for acg_dev %p (%d refs)...", acg_dev,
+			atomic_read(&acg_dev->acg_dev_kobj.kref.refcount));
+		wait_for_completion(&acg_dev->acg_dev_kobj_release_cmpl);
+		PRINT_INFO("Done waiting for releasing sysfs "
+			"entry for acg_dev %p", acg_dev);
+	}
+	return;
+}
+
+int scst_acg_dev_sysfs_create(struct scst_acg_dev *acg_dev,
+	struct kobject *parent)
+{
+	int res;
+
+	init_completion(&acg_dev->acg_dev_kobj_release_cmpl);
+
+	res = kobject_init_and_add(&acg_dev->acg_dev_kobj, &acg_dev_ktype,
+				      parent, "%u", acg_dev->lun);
+	if (res != 0) {
+		PRINT_ERROR("Can't add acg_dev %p to sysfs", acg_dev);
+		goto out;
+	}
+
+	kobject_get(&acg_dev->dev->dev_kobj);
+
+	snprintf(acg_dev->acg_dev_link_name, sizeof(acg_dev->acg_dev_link_name),
+		"export%u", acg_dev->dev->dev_exported_lun_num++);
+
+	res = sysfs_create_link(acg_dev->dev->dev_exp_kobj,
+			   &acg_dev->acg_dev_kobj, acg_dev->acg_dev_link_name);
+	if (res != 0) {
+		PRINT_ERROR("Can't create acg %s LUN link",
+			acg_dev->acg->acg_name);
+		goto out_del;
+	}
+
+	res = sysfs_create_link(&acg_dev->acg_dev_kobj,
+			&acg_dev->dev->dev_kobj, "device");
+	if (res != 0) {
+		PRINT_ERROR("Can't create acg %s device link",
+			acg_dev->acg->acg_name);
+		goto out_del;
+	}
+
+out:
+	return res;
+
+out_del:
+	scst_acg_dev_sysfs_del(acg_dev);
+	goto out;
+}
+
+static int __scst_process_luns_mgmt_store(char *buffer,
+	struct scst_tgt *tgt, struct scst_acg *acg, bool tgt_kobj)
+{
+	int res, read_only = 0, action;
+	char *p, *e = NULL;
+	unsigned int virt_lun;
+	struct scst_acg_dev *acg_dev = NULL, *acg_dev_tmp;
+	struct scst_device *d, *dev = NULL;
+
+#define SCST_LUN_ACTION_ADD	1
+#define SCST_LUN_ACTION_DEL	2
+#define SCST_LUN_ACTION_REPLACE	3
+#define SCST_LUN_ACTION_CLEAR	4
+
+	TRACE_DBG("buffer %s", buffer);
+
+	p = buffer;
+	if (p[strlen(p) - 1] == '\n')
+		p[strlen(p) - 1] = '\0';
+	if (strncasecmp("add", p, 3) == 0) {
+		p += 3;
+		action = SCST_LUN_ACTION_ADD;
+	} else if (strncasecmp("del", p, 3) == 0) {
+		p += 3;
+		action = SCST_LUN_ACTION_DEL;
+	} else if (!strncasecmp("replace", p, 7)) {
+		p += 7;
+		action = SCST_LUN_ACTION_REPLACE;
+	} else if (!strncasecmp("clear", p, 5)) {
+		p += 5;
+		action = SCST_LUN_ACTION_CLEAR;
+	} else {
+		PRINT_ERROR("Unknown action \"%s\"", p);
+		res = -EINVAL;
+		goto out;
+	}
+
+	res = scst_suspend_activity(true);
+	if (res != 0)
+		goto out;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out_resume;
+	}
+
+	/* Check if tgt and acg not already freed while we were coming here */
+	if (scst_check_tgt_acg_ptrs(tgt, acg) != 0)
+		goto out_unlock;
+
+	if ((action != SCST_LUN_ACTION_CLEAR) &&
+	    (action != SCST_LUN_ACTION_DEL)) {
+		if (!isspace(*p)) {
+			PRINT_ERROR("%s", "Syntax error");
+			res = -EINVAL;
+			goto out_unlock;
+		}
+
+		while (isspace(*p) && *p != '\0')
+			p++;
+		e = p; /* save p */
+		while (!isspace(*e) && *e != '\0')
+			e++;
+		*e = '\0';
+
+		list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
+			if (!strcmp(d->virt_name, p)) {
+				dev = d;
+				TRACE_DBG("Device %p (%s) found", dev, p);
+				break;
+			}
+		}
+		if (dev == NULL) {
+			PRINT_ERROR("Device '%s' not found", p);
+			res = -EINVAL;
+			goto out_unlock;
+		}
+	}
+
+	switch (action) {
+	case SCST_LUN_ACTION_ADD:
+	case SCST_LUN_ACTION_REPLACE:
+	{
+		bool dev_replaced = false;
+
+		e++;
+		while (isspace(*e) && *e != '\0')
+			e++;
+		virt_lun = simple_strtoul(e, &e, 0);
+
+		while (isspace(*e) && *e != '\0')
+			e++;
+
+		while (1) {
+			char *pp;
+			unsigned long val;
+			char *param = scst_get_next_token_str(&e);
+			if (param == NULL)
+				break;
+
+			p = scst_get_next_lexem(&param);
+			if (*p == '\0') {
+				PRINT_ERROR("Syntax error at %s (device %s)",
+					param, dev->virt_name);
+				res = -EINVAL;
+				goto out_unlock;
+			}
+
+			pp = scst_get_next_lexem(&param);
+			if (*pp == '\0') {
+				PRINT_ERROR("Parameter %s value missed for device %s",
+					p, dev->virt_name);
+				res = -EINVAL;
+				goto out_unlock;
+			}
+
+			if (scst_get_next_lexem(&param)[0] != '\0') {
+				PRINT_ERROR("Too many parameter's %s values (device %s)",
+					p, dev->virt_name);
+				res = -EINVAL;
+				goto out_unlock;
+			}
+
+			res = strict_strtoul(pp, 0, &val);
+			if (res != 0) {
+				PRINT_ERROR("strict_strtoul() for %s failed: %d "
+					"(device %s)", pp, res, dev->virt_name);
+				goto out_unlock;
+			}
+
+			if (!strcasecmp("read_only", p)) {
+				read_only = val;
+				TRACE_DBG("READ ONLY %d", read_only);
+			} else {
+				PRINT_ERROR("Unknown parameter %s (device %s)",
+					p, dev->virt_name);
+				res = -EINVAL;
+				goto out_unlock;
+			}
+		}
+
+		acg_dev = NULL;
+		list_for_each_entry(acg_dev_tmp, &acg->acg_dev_list,
+				    acg_dev_list_entry) {
+			if (acg_dev_tmp->lun == virt_lun) {
+				acg_dev = acg_dev_tmp;
+				break;
+			}
+		}
+
+		if (acg_dev != NULL) {
+			if (action == SCST_LUN_ACTION_ADD) {
+				PRINT_ERROR("virt lun %d already exists in "
+					"group %s", virt_lun, acg->acg_name);
+				res = -EEXIST;
+				goto out_unlock;
+			} else {
+				/* Replace */
+				res = scst_acg_del_lun(acg, acg_dev->lun,
+						false);
+				if (res != 0)
+					goto out_unlock;
+
+				dev_replaced = true;
+			}
+		}
+
+		res = scst_acg_add_lun(acg,
+			tgt_kobj ? tgt->tgt_luns_kobj : acg->luns_kobj,
+			dev, virt_lun, read_only, !dev_replaced, NULL);
+		if (res != 0)
+			goto out_unlock;
+
+		if (dev_replaced) {
+			struct scst_tgt_dev *tgt_dev;
+
+			list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
+				dev_tgt_dev_list_entry) {
+				if ((tgt_dev->acg_dev->acg == acg) &&
+				    (tgt_dev->lun == virt_lun)) {
+					TRACE_MGMT_DBG("INQUIRY DATA HAS CHANGED"
+						" on tgt_dev %p", tgt_dev);
+					scst_gen_aen_or_ua(tgt_dev,
+						SCST_LOAD_SENSE(scst_sense_inquery_data_changed));
+				}
+			}
+		}
+
+		break;
+	}
+	case SCST_LUN_ACTION_DEL:
+		while (isspace(*p) && *p != '\0')
+			p++;
+		virt_lun = simple_strtoul(p, &p, 0);
+
+		res = scst_acg_del_lun(acg, virt_lun, true);
+		if (res != 0)
+			goto out_unlock;
+		break;
+	case SCST_LUN_ACTION_CLEAR:
+		PRINT_INFO("Removed all devices from group %s",
+			acg->acg_name);
+		list_for_each_entry_safe(acg_dev, acg_dev_tmp,
+					 &acg->acg_dev_list,
+					 acg_dev_list_entry) {
+			res = scst_acg_del_lun(acg, acg_dev->lun,
+				list_is_last(&acg_dev->acg_dev_list_entry,
+					     &acg->acg_dev_list));
+			if (res)
+				goto out_unlock;
+		}
+		break;
+	}
+
+	res = 0;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+
+out_resume:
+	scst_resume_activity();
+
+out:
+	return res;
+
+#undef SCST_LUN_ACTION_ADD
+#undef SCST_LUN_ACTION_DEL
+#undef SCST_LUN_ACTION_REPLACE
+#undef SCST_LUN_ACTION_CLEAR
+}
+
+static int scst_luns_mgmt_store_work_fn(struct scst_sysfs_work_item *work)
+{
+	return __scst_process_luns_mgmt_store(work->buf, work->tgt, work->acg,
+			work->is_tgt_kobj);
+}
+
+static ssize_t __scst_acg_mgmt_store(struct scst_acg *acg,
+	const char *buf, size_t count, bool is_tgt_kobj,
+	int (*sysfs_work_fn)(struct scst_sysfs_work_item *))
+{
+	int res;
+	char *buffer;
+	struct scst_sysfs_work_item *work;
+
+	buffer = kzalloc(count+1, GFP_KERNEL);
+	if (buffer == NULL) {
+		res = -ENOMEM;
+		goto out;
+	}
+	memcpy(buffer, buf, count);
+	buffer[count] = '\0';
+
+	res = scst_alloc_sysfs_work(sysfs_work_fn, false, &work);
+	if (res != 0)
+		goto out_free;
+
+	work->buf = buffer;
+	work->tgt = acg->tgt;
+	work->acg = acg;
+	work->is_tgt_kobj = is_tgt_kobj;
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res == 0)
+		res = count;
+
+out:
+	return res;
+
+out_free:
+	kfree(buffer);
+	goto out;
+}
+
+static ssize_t __scst_luns_mgmt_store(struct scst_acg *acg,
+	bool tgt_kobj, const char *buf, size_t count)
+{
+	return __scst_acg_mgmt_store(acg, buf, count, tgt_kobj,
+			scst_luns_mgmt_store_work_fn);
+}
+
+static ssize_t scst_luns_mgmt_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf)
+{
+	static char *help = "Usage: echo \"add|del H:C:I:L lun [parameters]\" >mgmt\n"
+			    "       echo \"add VNAME lun [parameters]\" >mgmt\n"
+			    "       echo \"del lun\" >mgmt\n"
+			    "       echo \"replace H:C:I:L lun [parameters]\" >mgmt\n"
+			    "       echo \"replace VNAME lun [parameters]\" >mgmt\n"
+			    "       echo \"clear\" >mgmt\n"
+			    "\n"
+			    "where parameters are one or more "
+			    "param_name=value pairs separated by ';'\n"
+			    "\nThe following parameters available: read_only.";
+
+	return sprintf(buf, "%s", help);
+}
+
+static ssize_t scst_luns_mgmt_store(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    const char *buf, size_t count)
+{
+	int res;
+	struct scst_acg *acg;
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj->parent, struct scst_tgt, tgt_kobj);
+	acg = tgt->default_acg;
+
+	res = __scst_luns_mgmt_store(acg, true, buf, count);
+	return res;
+}
+
+static ssize_t __scst_acg_addr_method_show(struct scst_acg *acg, char *buf)
+{
+	int res;
+
+	switch (acg->addr_method) {
+	case SCST_LUN_ADDR_METHOD_FLAT:
+		res = sprintf(buf, "FLAT\n%s\n", SCST_SYSFS_KEY_MARK);
+		break;
+	case SCST_LUN_ADDR_METHOD_PERIPHERAL:
+		res = sprintf(buf, "PERIPHERAL\n");
+		break;
+	default:
+		res = sprintf(buf, "UNKNOWN\n");
+		break;
+	}
+
+	return res;
+}
+
+static ssize_t __scst_acg_addr_method_store(struct scst_acg *acg,
+	const char *buf, size_t count)
+{
+	int res = count;
+
+	if (strncasecmp(buf, "FLAT", min_t(int, 4, count)) == 0)
+		acg->addr_method = SCST_LUN_ADDR_METHOD_FLAT;
+	else if (strncasecmp(buf, "PERIPHERAL", min_t(int, 10, count)) == 0)
+		acg->addr_method = SCST_LUN_ADDR_METHOD_PERIPHERAL;
+	else {
+		PRINT_ERROR("Unknown address method %s", buf);
+		res = -EINVAL;
+	}
+
+	TRACE_DBG("acg %p, addr_method %d", acg, acg->addr_method);
+
+	return res;
+}
+
+static ssize_t scst_tgt_addr_method_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	struct scst_acg *acg;
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	acg = tgt->default_acg;
+
+	return __scst_acg_addr_method_show(acg, buf);
+}
+
+static ssize_t scst_tgt_addr_method_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	struct scst_acg *acg;
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	acg = tgt->default_acg;
+
+	res = __scst_acg_addr_method_store(acg, buf, count);
+	return res;
+}
+
+static ssize_t __scst_acg_io_grouping_type_show(struct scst_acg *acg, char *buf)
+{
+	int res;
+
+	switch (acg->acg_io_grouping_type) {
+	case SCST_IO_GROUPING_AUTO:
+		res = sprintf(buf, "%s\n", SCST_IO_GROUPING_AUTO_STR);
+		break;
+	case SCST_IO_GROUPING_THIS_GROUP_ONLY:
+		res = sprintf(buf, "%s\n%s\n",
+			SCST_IO_GROUPING_THIS_GROUP_ONLY_STR,
+			SCST_SYSFS_KEY_MARK);
+		break;
+	case SCST_IO_GROUPING_NEVER:
+		res = sprintf(buf, "%s\n%s\n", SCST_IO_GROUPING_NEVER_STR,
+			SCST_SYSFS_KEY_MARK);
+		break;
+	default:
+		res = sprintf(buf, "%d\n%s\n", acg->acg_io_grouping_type,
+			SCST_SYSFS_KEY_MARK);
+		break;
+	}
+
+	return res;
+}
+
+static int __scst_acg_process_io_grouping_type_store(struct scst_tgt *tgt,
+	struct scst_acg *acg, int io_grouping_type)
+{
+	int res = 0;
+	struct scst_acg_dev *acg_dev;
+
+	TRACE_DBG("tgt %p, acg %p, io_grouping_type %d", tgt, acg,
+		io_grouping_type);
+
+	res = scst_suspend_activity(true);
+	if (res != 0)
+		goto out;
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out_resume;
+	}
+
+	/* Check if tgt and acg not already freed while we were coming here */
+	if (scst_check_tgt_acg_ptrs(tgt, acg) != 0)
+		goto out_unlock;
+
+	acg->acg_io_grouping_type = io_grouping_type;
+
+	list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) {
+		int rc;
+
+		scst_stop_dev_threads(acg_dev->dev);
+
+		rc = scst_create_dev_threads(acg_dev->dev);
+		if (rc != 0)
+			res = rc;
+	}
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+
+out_resume:
+	scst_resume_activity();
+
+out:
+	return res;
+}
+
+static int __scst_acg_io_grouping_type_store_work_fn(struct scst_sysfs_work_item *work)
+{
+	return __scst_acg_process_io_grouping_type_store(work->tgt, work->acg,
+			work->io_grouping_type);
+}
+
+static ssize_t __scst_acg_io_grouping_type_store(struct scst_acg *acg,
+	const char *buf, size_t count)
+{
+	int res = 0;
+	int prev = acg->acg_io_grouping_type;
+	long io_grouping_type;
+	struct scst_sysfs_work_item *work;
+
+	if (strncasecmp(buf, SCST_IO_GROUPING_AUTO_STR,
+			min_t(int, strlen(SCST_IO_GROUPING_AUTO_STR), count)) == 0)
+		io_grouping_type = SCST_IO_GROUPING_AUTO;
+	else if (strncasecmp(buf, SCST_IO_GROUPING_THIS_GROUP_ONLY_STR,
+			min_t(int, strlen(SCST_IO_GROUPING_THIS_GROUP_ONLY_STR), count)) == 0)
+		io_grouping_type = SCST_IO_GROUPING_THIS_GROUP_ONLY;
+	else if (strncasecmp(buf, SCST_IO_GROUPING_NEVER_STR,
+			min_t(int, strlen(SCST_IO_GROUPING_NEVER_STR), count)) == 0)
+		io_grouping_type = SCST_IO_GROUPING_NEVER;
+	else {
+		res = strict_strtol(buf, 0, &io_grouping_type);
+		if ((res != 0) || (io_grouping_type <= 0)) {
+			PRINT_ERROR("Unknown or not allowed I/O grouping type "
+				"%s", buf);
+			res = -EINVAL;
+			goto out;
+		}
+	}
+
+	if (prev == io_grouping_type)
+		goto out;
+
+	res = scst_alloc_sysfs_work(__scst_acg_io_grouping_type_store_work_fn,
+					false, &work);
+	if (res != 0)
+		goto out;
+
+	work->tgt = acg->tgt;
+	work->acg = acg;
+	work->io_grouping_type = io_grouping_type;
+
+	res = scst_sysfs_queue_wait_work(work);
+
+out:
+	return res;
+}
+
+static ssize_t scst_tgt_io_grouping_type_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	struct scst_acg *acg;
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	acg = tgt->default_acg;
+
+	return __scst_acg_io_grouping_type_show(acg, buf);
+}
+
+static ssize_t scst_tgt_io_grouping_type_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	struct scst_acg *acg;
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	acg = tgt->default_acg;
+
+	res = __scst_acg_io_grouping_type_store(acg, buf, count);
+	if (res != 0)
+		goto out;
+
+	res = count;
+
+out:
+	return res;
+}
+
+static ssize_t __scst_acg_cpu_mask_show(struct scst_acg *acg, char *buf)
+{
+	int res;
+
+	res = cpumask_scnprintf(buf, SCST_SYSFS_BLOCK_SIZE,
+		&acg->acg_cpu_mask);
+	if (!cpus_equal(acg->acg_cpu_mask, default_cpu_mask))
+		res += sprintf(&buf[res], "\n%s\n", SCST_SYSFS_KEY_MARK);
+
+	return res;
+}
+
+static int __scst_acg_process_cpu_mask_store(struct scst_tgt *tgt,
+	struct scst_acg *acg, cpumask_t *cpu_mask)
+{
+	int res = 0;
+	struct scst_session *sess;
+
+	TRACE_DBG("tgt %p, acg %p", tgt, acg);
+
+	if (mutex_lock_interruptible(&scst_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
[… more lines in the raw message]

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Part of the diff is not shown here. View the raw message.

#9 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 1 line · Vladislav Bolkhovitin

This patch contains SYSFS interface implementation.

Nice, but you forgot to document it. All sysfs changes need to be documented in Documentation/ABI/

Please add a file to this patch that does so.

drivers/scst/scst_sysfs.c line 560

Quoted 7 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +7 -0
+static void scst_tgtt_release(struct kobject *kobj)
+{
+	struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+	complete_all(&tgtt->tgtt_kobj_release_cmpl);
+	return;

Don't you also need to free the memory of your kobject here?

drivers/scst/scst_sysfs.c line 850

Quoted 7 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +7 -0
+static void scst_tgt_release(struct kobject *kobj)
+{
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	complete_all(&tgt->tgt_kobj_release_cmpl);
+	return;

Same here, no kfree?

drivers/scst/scst_sysfs.c line 863

Quoted 6 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +6 -0
+static void scst_acg_release(struct kobject *kobj)
+{
+	struct scst_acg *acg;
+
+	acg = container_of(kobj, struct scst_acg, acg_kobj);
+	complete_all(&acg->acg_kobj_release_cmpl);

And here.

thanks,

greg k-h

#10 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 7 lines · Greg KH, on 10/10/2010 01:20 AM wrote:
Quoted 1 line · Vladislav Bolkhovitin

This patch contains SYSFS interface implementation.

Nice, but you forgot to document it. All sysfs changes need to be documented in Documentation/ABI/

Please add a file to this patch that does so.

I'll do. I didn't know about this. Thanks for pointing on it.

Quoted 20 lines

drivers/scst/scst_sysfs.c line 560

Quoted 7 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +7 -0
+static void scst_tgtt_release(struct kobject *kobj)
+{
+	struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+	complete_all(&tgtt->tgtt_kobj_release_cmpl);
+	return;

Don't you also need to free the memory of your kobject here?

drivers/scst/scst_sysfs.c line 850

Quoted 7 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +7 -0
+static void scst_tgt_release(struct kobject *kobj)
+{
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	complete_all(&tgt->tgt_kobj_release_cmpl);
+	return;

Same here, no kfree?

Quoted 8 lines

drivers/scst/scst_sysfs.c line 863

Quoted 6 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +6 -0
+static void scst_acg_release(struct kobject *kobj)
+{
+	struct scst_acg *acg;
+
+	acg = container_of(kobj, struct scst_acg, acg_kobj);
+	complete_all(&acg->acg_kobj_release_cmpl);

And here.

Thanks for the review. In all those functions kobjects for simplicity are embedded into the outer objects, so they will be freed as part of the outer objects free. Hence, kfree() for the kobjects in the release functions are not needed.

Thanks again,
Vlad

#11 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 31 lines · Vladislav Bolkhovitin
Quoted 21 lines · Greg KH, on 10/10/2010 01:20 AM wrote:

drivers/scst/scst_sysfs.c line 560

Quoted 7 lines · Vladislav Bolkhovitin
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +7 -0
+static void scst_tgtt_release(struct kobject *kobj)
+{
+	struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+	complete_all(&tgtt->tgtt_kobj_release_cmpl);
+	return;

Don't you also need to free the memory of your kobject here?

drivers/scst/scst_sysfs.c line 850

Quoted 7 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +7 -0
+static void scst_tgt_release(struct kobject *kobj)
+{
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	complete_all(&tgt->tgt_kobj_release_cmpl);
+	return;

Same here, no kfree?

Quoted 8 lines

drivers/scst/scst_sysfs.c line 863

Quoted 6 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +6 -0
+static void scst_acg_release(struct kobject *kobj)
+{
+	struct scst_acg *acg;
+
+	acg = container_of(kobj, struct scst_acg, acg_kobj);
+	complete_all(&acg->acg_kobj_release_cmpl);

And here.

Quoted 4 lines

Thanks for the review. In all those functions kobjects for simplicity are embedded into the outer objects, so they will be freed as part of the outer objects free. Hence, kfree() for the kobjects in the release functions are not needed.

Sweet, you now have opened yourself up to public ridicule as per the documentation in the kernel for how to use kobjects!

Nice job :)

Seriously, you CAN NOT DO THIS! If you embed a kobject in a different structure, then you have to rely on the kobject to handle the reference counting for that larger structure. To do ANYTHING else is a bug and wrong.

Please read the kobject documentation and fix this code up before submitting it again.

thanks,

greg k-h

#12 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 37 lines · Greg KH, on 10/12/2010 01:32 AM wrote:
Quoted 31 lines · Vladislav Bolkhovitin
Quoted 21 lines · Greg KH, on 10/10/2010 01:20 AM wrote:

drivers/scst/scst_sysfs.c line 560

Quoted 7 lines · Vladislav Bolkhovitin
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +7 -0
+static void scst_tgtt_release(struct kobject *kobj)
+{
+	struct scst_tgt_template *tgtt;
+
+	tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
+	complete_all(&tgtt->tgtt_kobj_release_cmpl);
+	return;

Don't you also need to free the memory of your kobject here?

drivers/scst/scst_sysfs.c line 850

Quoted 7 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +7 -0
+static void scst_tgt_release(struct kobject *kobj)
+{
+	struct scst_tgt *tgt;
+
+	tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
+	complete_all(&tgt->tgt_kobj_release_cmpl);
+	return;

Same here, no kfree?

Quoted 8 lines

drivers/scst/scst_sysfs.c line 863

Quoted 6 lines
drivers/scst/scst_sysfs.c renamed from linux-2.6.35/drivers/scst/scst_sysfs.c +6 -0
+static void scst_acg_release(struct kobject *kobj)
+{
+	struct scst_acg *acg;
+
+	acg = container_of(kobj, struct scst_acg, acg_kobj);
+	complete_all(&acg->acg_kobj_release_cmpl);

And here.

Quoted 4 lines

Thanks for the review. In all those functions kobjects for simplicity are embedded into the outer objects, so they will be freed as part of the outer objects free. Hence, kfree() for the kobjects in the release functions are not needed.

Quoted 4 lines

Sweet, you now have opened yourself up to public ridicule as per the documentation in the kernel for how to use kobjects!

Nice job :)

Thanks :)

Quoted 7 lines

Seriously, you CAN NOT DO THIS! If you embed a kobject in a different structure, then you have to rely on the kobject to handle the reference counting for that larger structure. To do ANYTHING else is a bug and wrong.

Please read the kobject documentation and fix this code up before submitting it again.

Sure, I have read it and we rely on the kobject to handle the reference counting for the larger structure. It's only done not in a straightforward way, because the way it is implemented is simpler for us
+ for some other reasons.

For instance, for structure scst_tgt it is done using tgt_kobj_release_cmpl completion. When a target driver calls scst_unregister_target(), scst_unregister_target() in the end calls scst_tgt_sysfs_del(), which calls kobject_put(&tgt->tgt_kobj) and wait for tgt_kobj_release_cmpl to complete. At this point tgt_kobj can be taken only by the SYSFS. Scst_tgt_sysfs_del() can wait as much as needed until the SYSFS code released it. As far as I can see, it can't be forever, so it's OK. Then, after scst_tgt_sysfs_del() returned, scst_unregister_target() will free scst_tgt together with embedded tgt_kobj.

Sure, if you insist, I can convert tgt_kobj and other similar kobjects to pointers, but it would be just a formal code introducing additional kmalloc()/kfree() pair per each kobject without changing any logic anywhere.

Thanks,
Vlad

#13 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 5 lines · Vladislav Bolkhovitin
Quoted 5 lines

Seriously, you CAN NOT DO THIS! If you embed a kobject in a different structure, then you have to rely on the kobject to handle the reference counting for that larger structure. To do ANYTHING else is a bug and wrong.

Quoted 7 lines
Quoted 2 lines

Please read the kobject documentation and fix this code up before submitting it again.

Sure, I have read it and we rely on the kobject to handle the reference counting for the larger structure. It's only done not in a straightforward way, because the way it is implemented is simpler for us
+ for some other reasons.

Sorry, but I don't buy it.

Quoted 5 lines

For instance, for structure scst_tgt it is done using tgt_kobj_release_cmpl completion. When a target driver calls scst_unregister_target(), scst_unregister_target() in the end calls scst_tgt_sysfs_del(), which calls kobject_put(&tgt->tgt_kobj) and wait for tgt_kobj_release_cmpl to complete.

Wait, why shouldn't the release then free the memory?

Quoted 3 lines

At this point tgt_kobj can be taken only by the SYSFS. Scst_tgt_sysfs_del() can wait as much as needed until the SYSFS code released it. As far as I can see, it can't be forever, so it's OK.

I don't understand, why can't you just free the memory, what are you having to wait for?

You are only having one kobject for your structure, right? If so, then free the memory in the release, to wait for something else to free the memory is wrong.

Quoted 2 lines

Then, after scst_tgt_sysfs_del() returned, scst_unregister_target() will free scst_tgt together with embedded tgt_kobj.

As no other kernel code is like this, I don't think it's valid to be doing so, sorry.

Please fix this.

good luck,

greg k-h

#14 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 37 lines · Greg KH, on 10/12/2010 11:03 PM wrote:
Quoted 5 lines · Vladislav Bolkhovitin
Quoted 5 lines

Seriously, you CAN NOT DO THIS! If you embed a kobject in a different structure, then you have to rely on the kobject to handle the reference counting for that larger structure. To do ANYTHING else is a bug and wrong.

Quoted 7 lines
Quoted 2 lines

Please read the kobject documentation and fix this code up before submitting it again.

Sure, I have read it and we rely on the kobject to handle the reference counting for the larger structure. It's only done not in
a straightforward way, because the way it is implemented is
simpler for us + for some other reasons.

Sorry, but I don't buy it.

Quoted 5 lines

For instance, for structure scst_tgt it is done using tgt_kobj_release_cmpl completion. When a target driver calls scst_unregister_target(), scst_unregister_target() in the end calls scst_tgt_sysfs_del(), which calls kobject_put(&tgt->tgt_kobj) and wait for tgt_kobj_release_cmpl to complete.

Wait, why shouldn't the release then free the memory?

Quoted 4 lines

At this point tgt_kobj can be taken only by the SYSFS. Scst_tgt_sysfs_del() can wait as much as needed until the SYSFS code released it. As far as I can see, it can't be forever, so it's OK.

I don't understand, why can't you just free the memory, what are you having to wait for?

You are only having one kobject for your structure, right? If so, then free the memory in the release, to wait for something else to free the memory is wrong.

Quoted 8 lines
Quoted 3 lines

Then, after scst_tgt_sysfs_del() returned, scst_unregister_target() will free scst_tgt together with embedded tgt_kobj.

As no other kernel code is like this, I don't think it's valid to be doing so, sorry.

Please fix this.

I'm sorry, but after I started implementing it I'm confused..

Originally I thought you are asking to make tgt_kobj be not embedded in struct scst_tgt, but a pointer in it, so scst_tgtt_release() will kfree() tgt_kobj. Hence, all the above I wrote about why we have tgt_kobj embedded.

But now I feel like you are asking that scst_tgtt_release() should kfree() tgt, not tgt_kobj.

Is it correct?

If yes, we did it this way to make errors processing uniform and straightforward. In the code (simplified) our current errors recovery looks like:

void scst_tgt_sysfs_del(struct scst_tgt *tgt)
{
...

	kobject_put(&tgt->tgt_kobj);
	wait_for_completion(&tgt->tgt_kobj_release_cmpl);
}

int scst_tgt_sysfs_create(struct scst_tgt *tgt)

{
	init_completion(&tgt->tgt_kobj_release_cmpl);

	res = kobject_init_and_add(&tgt->tgt_kobj, &tgt_ktype,
			&tgt->tgtt->tgtt_kobj, tgt->tgt_name);
	if (res != 0)
		goto out;

	res = sysfs_create_file(&tgt->tgt_kobj,
			&tgt_enable_attr.attr);
	if (res != 0)
		goto out_err;

...

out:

	return res;

out_err:

	scst_tgt_sysfs_del(tgt);
	goto out;
}

struct scst_tgt *scst_register_target()

{
	struct scst_tgt *tgt;
	int rc = 0;

	TRACE_ENTRY();

	rc = scst_alloc_tgt();
	if (rc != 0)
		goto out;
...
	tgt->tgt_name = kmalloc(strlen(target_name) + 1, GFP_KERNEL);
	if (tgt->tgt_name == NULL)
		goto out_free_tgt;
...
	mutex_lock();

	rc = scst_tgt_sysfs_create(tgt);
	if (rc < 0)
		goto out_unlock;

	tgt->default_acg = scst_alloc_add_acg();
	if (tgt->default_acg == NULL)
		goto out_sysfs_del;

...

out:

	return tgt;

out_sysfs_del:

	mutex_unlock();
	scst_tgt_sysfs_del(tgt)
	goto out_free_tgt;

out_unlock:

	mutex_unlock();

out_free_tgt:

	scst_free_tgt(tgt);
}

We have a simple and straightforward errors recovery semantic: if scst_tgt_sysfs_create() failed, then tgt_kobj returned deinited as if scst_tgt_sysfs_create() has never been called. This is a regular practice in the kernel: don't return half-initialized objects.

If we implement freeing tgt in scst_tgtt_release() as you requesting, we will need to add in the error recovery path additional recovery code to track and delete half-initialized tgt_kobj. Particularly, we will need to add additional flag to track that tgt_kobj initialized, hence needs deleting. Similar flag and code will be added in all similar to scst_tgt SCST objects.

This code will be quite errors prone as you can see on the example of device_register() which on failure requires device_put() to be called
(http://lkml.org/lkml/2010/9/19/93). (I'm not questioning device_register() implementation, there might be very good reasons to implement it this way (I don't know), I mean, it is too easy to forget to do the needed recovery of the half-created objects as this case demonstrating.)

Could you confirm if I understand you correctly and need to implement freeing tgt in the kobject release() function, please?

Thanks you very much,
Vlad

#15 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 9 lines · Vladislav Bolkhovitin

Originally I thought you are asking to make tgt_kobj be not embedded in struct scst_tgt, but a pointer in it, so scst_tgtt_release() will kfree() tgt_kobj. Hence, all the above I wrote about why we have tgt_kobj embedded.

But now I feel like you are asking that scst_tgtt_release() should kfree() tgt, not tgt_kobj.

Is it correct?

I am asking that ANY kobject release function call kfree to release the memory that object is embedded in. That is how kobjects work, please read the documentation for more details.

Quoted 4 lines

We have a simple and straightforward errors recovery semantic: if scst_tgt_sysfs_create() failed, then tgt_kobj returned deinited as if scst_tgt_sysfs_create() has never been called. This is a regular practice in the kernel: don't return half-initialized objects.

True.

Quoted 10 lines

If we implement freeing tgt in scst_tgtt_release() as you requesting, we will need to add in the error recovery path additional recovery code to track and delete half-initialized tgt_kobj. Particularly, we will need to add additional flag to track that tgt_kobj initialized, hence needs deleting. Similar flag and code will be added in all similar to scst_tgt SCST objects.

This code will be quite errors prone as you can see on the example of device_register() which on failure requires device_put() to be called
(http://lkml.org/lkml/2010/9/19/93).

That's not "error prone", it's "people don't read the provided documentation about how to use the API".

And yes, one could argue to make the API easier to use, and patches are always welcome to do so.

Quoted 4 lines

(I'm not questioning device_register() implementation, there might be very good reasons to implement it this way (I don't know), I mean, it is too easy to forget to do the needed recovery of the half-created objects as this case demonstrating.)

There are good reasons, but the most important one being, if you pass off an object, as of that moment in time, you had better handle the reference counting correctly. If you think of it this way, cleanup logic is even simpler as that's the only rule you ever need to think about, none of thies "half-initialized" stuff.

Quoted 2 lines

Could you confirm if I understand you correctly and need to implement freeing tgt in the kobject release() function, please?

Again, yes. Please read the documentation.

thanks,

greg k-h

#16 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 39 lines · Greg KH, on 10/15/2010 12:04 AM wrote:

I am asking that ANY kobject release function call kfree to release the memory that object is embedded in. That is how kobjects work, please read the documentation for more details.

Quoted 4 lines

We have a simple and straightforward errors recovery semantic: if scst_tgt_sysfs_create() failed, then tgt_kobj returned deinited as if scst_tgt_sysfs_create() has never been called. This is a regular practice in the kernel: don't return half-initialized objects.

True.

Quoted 10 lines

If we implement freeing tgt in scst_tgtt_release() as you requesting, we will need to add in the error recovery path additional recovery code to track and delete half-initialized tgt_kobj. Particularly, we will need to add additional flag to track that tgt_kobj initialized, hence needs deleting. Similar flag and code will be added in all similar to scst_tgt SCST objects.

This code will be quite errors prone as you can see on the example of device_register() which on failure requires device_put() to be called
(http://lkml.org/lkml/2010/9/19/93).

That's not "error prone", it's "people don't read the provided documentation about how to use the API".

And yes, one could argue to make the API easier to use, and patches are always welcome to do so.

Quoted 4 lines

(I'm not questioning device_register() implementation, there might be very good reasons to implement it this way (I don't know), I mean, it is too easy to forget to do the needed recovery of the half-created objects as this case demonstrating.)

There are good reasons, but the most important one being, if you pass off an object, as of that moment in time, you had better handle the reference counting correctly. If you think of it this way, cleanup logic is even simpler as that's the only rule you ever need to think about, none of thies "half-initialized" stuff.

Quoted 4 lines
Quoted 2 lines

Could you confirm if I understand you correctly and need to implement freeing tgt in the kobject release() function, please?

Again, yes. Please read the documentation.

Here is the patch. It converts all the cases except 3:

- struct scst_tgt_template and struct scst_dev_type, because they are supplied by
target drivers and dev handlers correspondingly and usually static.

- struct scst_session, because sometimes we need to create several sessions for the
same initiator (for iSCSI sessions reinstatement, for instance) and for such cases need a logic to have different SYSFS names for them. To have this logic functioning correctly, we need to keep sessions alive longer than life of their kobject (see scst_sess_sysfs_create() and scst_free_session() for more details).

I don't like this patch, because it makes the code in almost 100 lines of code bigger and worse by making objects initialization/freeing a lot more complicated and harder to audit. But, since you insist, I applied it.

It isn't late to revert it, just say a word.

Thanks,
Vlad

---
 drivers/scst/scst_lib.c   |  102 ++++++++++++++----
 drivers/scst/scst_main.c  |   26 +---
 drivers/scst/scst_priv.h  |   37 +++++-
 drivers/scst/scst_sysfs.c |  247 ++++++++++++++++++++++++----------------------
 include/scst/scst.h       |   22 +++-
 5 files changed, 261 insertions(+), 173 deletions(-)
scst/scst.h +17 -5
diff -upr linux-2.6.35/include/scst/scst.h linux-2.6.35/include/scst/scst.h
@@ -1431,8 +1431,10 @@ struct scst_tgt {
 	char *default_group_name;
 #endif
 
+	unsigned int tgt_kobj_initialized:1;
+
 	/* sysfs release completion */
-	struct completion tgt_kobj_release_cmpl;
+	struct completion *tgt_kobj_release_cmpl;
 
 	struct kobject tgt_kobj; /* main targets/target kobject */
 	struct kobject *tgt_sess_kobj; /* target/sessions/ */
@@ -2077,6 +2079,9 @@ struct scst_device {
 	/* If set, dev is read only */
 	unsigned short rd_only:1;
 
+	/* If set, dev_kobj was initialized */
+	unsigned short dev_kobj_initialized:1;
+
 	/**************************************************************/
 
 	/*************************************************************
@@ -2217,7 +2222,7 @@ struct scst_device {
 	enum scst_dev_type_threads_pool_type threads_pool_type;
 
 	/* sysfs release completion */
-	struct completion dev_kobj_release_cmpl;
+	struct completion *dev_kobj_release_cmpl;
 
 	struct kobject dev_kobj; /* kobject for this struct */
 	struct kobject *dev_exp_kobj; /* exported groups */
@@ -2342,6 +2347,9 @@ struct scst_tgt_dev {
 	/* Set if INQUIRY DATA HAS CHANGED UA is needed */
 	unsigned int inq_changed_ua_needed:1;
 
+	/* Set if tgt_dev_kobj was initialized */
+	unsigned int tgt_dev_kobj_initialized:1;
+
 	/*
 	 * Stored Unit Attention sense and its length for possible
 	 * subsequent REQUEST SENSE. Both protected by tgt_dev_lock.
@@ -2350,7 +2358,7 @@ struct scst_tgt_dev {
 	uint8_t tgt_dev_sense[SCST_SENSE_BUFFERSIZE];
 
 	/* sysfs release completion */
-	struct completion tgt_dev_kobj_release_cmpl;
+	struct completion *tgt_dev_kobj_release_cmpl;
 
 	struct kobject tgt_dev_kobj; /* kobject for this struct */
 
@@ -2378,6 +2386,9 @@ struct scst_acg_dev {
 	/* If set, the corresponding LU is read only */
 	unsigned int rd_only:1;
 
+	/* If set acg_dev_kobj was initialized */
+	unsigned int acg_dev_kobj_initialized:1;
+
 	struct scst_acg *acg; /* parent acg */
 
 	/* List entry in dev->dev_acg_dev_list */
@@ -2390,7 +2401,7 @@ struct scst_acg_dev {
 	struct kobject acg_dev_kobj;
 
 	/* sysfs release completion */
-	struct completion acg_dev_kobj_release_cmpl;
+	struct completion *acg_dev_kobj_release_cmpl;
 
 	/* Name of the link to the corresponding LUN */
 	char acg_dev_link_name[20];
@@ -2431,9 +2442,10 @@ struct scst_acg {
 	cpumask_t acg_cpu_mask;
 
 	unsigned int tgt_acg:1;
+	unsigned int acg_kobj_initialized:1;
 
 	/* sysfs release completion */
-	struct completion acg_kobj_release_cmpl;
+	struct completion *acg_kobj_release_cmpl;
 
 	/* kobject for this structure */
 	struct kobject acg_kobj;
scst/scst_main.c +6 -20
diff -upr linux-2.6.35/drivers/scst/scst_main.c linux-2.6.35/drivers/scst/scst_main.c
@@ -553,7 +553,6 @@ out:
 #ifndef CONFIG_SCST_PROC
 out_sysfs_del:
 	mutex_unlock(&scst_mutex);
-	scst_tgt_sysfs_del(tgt);
 	goto out_free_tgt;
 #endif
 
@@ -640,10 +639,6 @@ again:
 	mutex_unlock(&scst_mutex);
 	scst_resume_activity();
 
-#ifndef CONFIG_SCST_PROC
-	scst_tgt_sysfs_del(tgt);
-#endif
-
 	PRINT_INFO("Target %s for template %s unregistered successfully",
 		tgt->tgt_name, vtt->name);
 
@@ -862,7 +857,7 @@ static int scst_register_device(struct s
 #endif
 	}
 
-	res = scst_alloc_device(GFP_KERNEL, &dev);
+	res = scst_alloc_dev(GFP_KERNEL, &dev);
 	if (res != 0)
 		goto out_unlock;
 
@@ -925,7 +920,7 @@ out_del:
 	list_del(&dev->dev_list_entry);
 
 out_free_dev:
-	scst_free_device(dev);
+	scst_free_dev(dev);
 
 out_unlock:
 	mutex_unlock(&scst_mutex);
@@ -973,12 +968,10 @@ static void scst_unregister_device(struc
 
 	scst_resume_activity();
 
-	scst_dev_sysfs_del(dev);
-
 	PRINT_INFO("Detached from scsi%d, channel %d, id %d, lun %d, type %d",
 		scsidp->host->host_no, scsidp->channel, scsidp->id,
 		scsidp->lun, scsidp->type);
 
-	scst_free_device(dev);
+	scst_free_dev(dev);
 
 out:
@@ -1054,7 +1047,6 @@ int scst_register_virtual_device(struct 
 {
 	int res, rc;
 	struct scst_device *dev, *d;
-	bool sysfs_del = false;
 
@@ -1088,7 +1080,7 @@ int scst_register_virtual_device(struct 
 		goto out_resume;
 	}
 
-	res = scst_alloc_device(GFP_KERNEL, &dev);
+	res = scst_alloc_dev(GFP_KERNEL, &dev);
 	if (res != 0)
 		goto out_unlock;
 
@@ -1135,7 +1127,6 @@ int scst_register_virtual_device(struct 
 		if (strcmp(d->virt_name, dev_name) == 0) {
 			PRINT_ERROR("Device %s already exists", dev_name);
 			res = -EEXIST;
-			sysfs_del = true;
 			goto out_pr_clear_dev;
 		}
 	}
@@ -1143,7 +1134,6 @@ int scst_register_virtual_device(struct 
 	rc = scst_assign_dev_handler(dev, dev_handler);
 	if (rc != 0) {
 		res = rc;
-		sysfs_del = true;
 		goto out_pr_clear_dev;
 	}
 
@@ -1171,9 +1161,7 @@ out_pr_clear_dev:
 
 out_free_dev:
 	mutex_unlock(&scst_mutex);
-	if (sysfs_del)
-		scst_dev_sysfs_del(dev);
-	scst_free_device(dev);
+	scst_free_dev(dev);
 	goto out_resume;
 
 out_unlock:
@@ -1225,12 +1213,10 @@ void scst_unregister_virtual_device(int 
 	mutex_unlock(&scst_mutex);
 	scst_resume_activity();
 
-	scst_dev_sysfs_del(dev);
-
 	PRINT_INFO("Detached from virtual device %s (id %d)",
 		dev->virt_name, dev->virt_id);
 
-	scst_free_device(dev);
+	scst_free_dev(dev);
 
 out:
scst/scst_priv.h +27 -10
diff -upr linux-2.6.35/drivers/scst/scst_priv.h linux-2.6.35/drivers/scst/scst_priv.h
@@ -306,13 +306,16 @@ int scst_queue_retry_cmd(struct scst_cmd
 
 int scst_alloc_tgt(struct scst_tgt_template *tgtt, struct scst_tgt **tgt);
 void scst_free_tgt(struct scst_tgt *tgt);
+void __scst_free_tgt(struct scst_tgt *tgt);
 
-int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev);
-void scst_free_device(struct scst_device *dev);
+int scst_alloc_dev(gfp_t gfp_mask, struct scst_device **out_dev);
+void scst_free_dev(struct scst_device *dev);
+void __scst_free_dev(struct scst_device *dev);
 
 struct scst_acg *scst_alloc_add_acg(struct scst_tgt *tgt,
 	const char *acg_name, bool tgt_acg);
 void scst_del_free_acg(struct scst_acg *acg);
+void __scst_free_acg(struct scst_acg *acg);
 
 struct scst_acg *scst_tgt_find_acg(struct scst_tgt *tgt, const char *name);
 struct scst_acg *scst_find_acg(const struct scst_session *sess);
@@ -323,6 +326,8 @@ int scst_sess_alloc_tgt_devs(struct scst
 void scst_sess_free_tgt_devs(struct scst_session *sess);
 void scst_nexus_loss(struct scst_tgt_dev *tgt_dev, bool queue_UA);
 
+void __scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev);
+
 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);
@@ -336,6 +341,8 @@ int scst_acg_remove_name(struct scst_acg
 void scst_del_free_acn(struct scst_acn *acn, bool reassign);
 struct scst_acn *scst_find_acn(struct scst_acg *acg, const char *name);
 
+void scst_free_acg_dev(struct scst_acg_dev *acg_dev);
+
 /* The activity supposed to be suspended and scst_mutex held */
 static inline bool scst_acg_sess_is_empty(struct scst_acg *acg)
 {
@@ -426,19 +433,24 @@ static inline int scst_sysfs_init(void)
 }
 static inline void scst_sysfs_cleanup(void) { }
 
+static inline void scst_tgt_sysfs_del_free(struct scst_tgt *tgt) { BUG(); }
+
 static inline int scst_devt_dev_sysfs_create(struct scst_device *dev)
 {
 	return 0;
 }
 static inline void scst_devt_dev_sysfs_del(struct scst_device *dev) { }
 
-static inline void scst_dev_sysfs_del(struct scst_device *dev) { }
+static inline void scst_dev_sysfs_del_free(struct scst_device *dev) { BUG(); }
 
 static inline int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev)
 {
 	return 0;
 }
-static inline void scst_tgt_dev_sysfs_del(struct scst_tgt_dev *tgt_dev) { }
+static inline void scst_tgt_dev_sysfs_del_free(struct scst_tgt_dev *tgt_dev)
+{
+	BUG();
+}
 
 static inline int scst_sess_sysfs_create(struct scst_session *sess)
 {
@@ -451,7 +463,12 @@ static inline int scst_acg_dev_sysfs_cre
 	return 0;
 }
 
-static inline void scst_acg_dev_sysfs_del(struct scst_acg_dev *acg_dev) { }
+static inline void scst_acg_dev_sysfs_del_free(struct scst_acg_dev *acg_dev)
+{
+	BUG();
+}
+
+static inline void scst_acg_sysfs_del_free(struct scst_acg *acg) { BUG(); }
 
 static inline int scst_acn_sysfs_create(struct scst_acn *acn)
 {
@@ -473,7 +490,7 @@ int scst_tgtt_sysfs_create(struct scst_t
 void scst_tgtt_sysfs_del(struct scst_tgt_template *tgtt);
 int scst_tgt_sysfs_create(struct scst_tgt *tgt);
 void scst_tgt_sysfs_prepare_put(struct scst_tgt *tgt);
-void scst_tgt_sysfs_del(struct scst_tgt *tgt);
+void scst_tgt_sysfs_del_free(struct scst_tgt *tgt);
 int scst_sess_sysfs_create(struct scst_session *sess);
 void scst_sess_sysfs_del(struct scst_session *sess);
 int scst_recreate_sess_luns_link(struct scst_session *sess);
@@ -482,17 +499,17 @@ void scst_sgv_sysfs_del(struct sgv_pool 
 int scst_devt_sysfs_create(struct scst_dev_type *devt);
 void scst_devt_sysfs_del(struct scst_dev_type *devt);
 int scst_dev_sysfs_create(struct scst_device *dev);
-void scst_dev_sysfs_del(struct scst_device *dev);
+void scst_dev_sysfs_del_free(struct scst_device *dev);
 int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev);
-void scst_tgt_dev_sysfs_del(struct scst_tgt_dev *tgt_dev);
+void scst_tgt_dev_sysfs_del_free(struct scst_tgt_dev *tgt_dev);
 int scst_devt_dev_sysfs_create(struct scst_device *dev);
 void scst_devt_dev_sysfs_del(struct scst_device *dev);
 int scst_acg_sysfs_create(struct scst_tgt *tgt,
 	struct scst_acg *acg);
-void scst_acg_sysfs_del(struct scst_acg *acg);
+void scst_acg_sysfs_del_free(struct scst_acg *acg);
 int scst_acg_dev_sysfs_create(struct scst_acg_dev *acg_dev,
 	struct kobject *parent);
-void scst_acg_dev_sysfs_del(struct scst_acg_dev *acg_dev);
+void scst_acg_dev_sysfs_del_free(struct scst_acg_dev *acg_dev);
 int scst_acn_sysfs_create(struct scst_acn *acn);
 void scst_acn_sysfs_del(struct scst_acn *acn);
 
scst/scst_lib.c +73 -25

196 lines not on this page · raw message

scst/scst_sysfs.c +134 -113

553 lines not on this page · raw message

#17 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

scst/scst.h struct scst_tgt { · line 1434

Quoted 1 line · Vladislav Bolkhovitin
scst/scst.h +1 -0
+	unsigned int tgt_kobj_initialized:1;

It's the middle of the merge window, and I'm about to go on vacation, so I didn't read this patch after this line.

It's obvious that this patch is wrong, you shouldn't need to worry about this. And even if you did, you don't need this flag.

Why are you trying to do something that no one else needs? Why make things harder than they have to be.

{sigh}

greg k-h

#18 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 11 lines · Greg KH, on 10/22/2010 09:56 PM wrote:

scst/scst.h struct scst_tgt { · line 1434

Quoted 1 line · Vladislav Bolkhovitin
scst/scst.h +1 -0
+	unsigned int tgt_kobj_initialized:1;

It's the middle of the merge window, and I'm about to go on vacation, so I didn't read this patch after this line.

It's obvious that this patch is wrong, you shouldn't need to worry about this. And even if you did, you don't need this flag.

Why are you trying to do something that no one else needs? Why make things harder than they have to be.

I tried to explain that to you in http://lkml.org/lkml/2010/10/14/291
and mentioned there the need to create this flag to track half-initialized kobjects. You agreed
(http://lkml.org/lkml/2010/10/14/299) that don't return half-initialized objects is a regular kernel practice, but then requested to strictly bound the larger object freeing to its kobject release(), which means that all SYSFS creating functions now have to return half-initialized SYSFS hierarchy in case of any error. Hence the flag to track it.

Simply, any SCST object has a lot of other things to initialize besides its kobject, hence the need either to free it independently from its kobject, or track by a flag if its kobjects initialized.

For illustration, here is the simplified code from my previous example, i.e. without this patch. I added to it scst_unregister_target() to make it more complete.

void scst_tgt_sysfs_del(struct scst_tgt *tgt)
{
...

	kobject_put(&tgt->tgt_kobj);
	wait_for_completion(&tgt->tgt_kobj_release_cmpl);
}

int scst_tgt_sysfs_create(struct scst_tgt *tgt)

{
	init_completion(&tgt->tgt_kobj_release_cmpl);

	res = kobject_init_and_add(&tgt->tgt_kobj, &tgt_ktype,
			&tgt->tgtt->tgtt_kobj, tgt->tgt_name);
	if (res != 0)
		goto out;

	res = sysfs_create_file(&tgt->tgt_kobj,
			&tgt_enable_attr.attr);
	if (res != 0)
		goto out_err;
...
out:
	return res;

out_err:

	scst_tgt_sysfs_del(tgt);
	goto out;
}

struct scst_tgt *scst_register_target()

{
	struct scst_tgt *tgt;
	int rc = 0;

	rc = scst_alloc_tgt();
	if (rc != 0)
		goto out;
...
	tgt->tgt_name = kmalloc(strlen(target_name) + 1, GFP_KERNEL);
	if (tgt->tgt_name == NULL)
		goto out_free_tgt;

...

	mutex_lock();

	rc = scst_tgt_sysfs_create(tgt);
	if (rc < 0)
		goto out_unlock;

	tgt->default_acg = scst_alloc_add_acg();
	if (tgt->default_acg == NULL)
		goto out_sysfs_del;

...

out:

	return tgt;

out_sysfs_del:

	mutex_unlock();
	scst_tgt_sysfs_del(tgt)
	goto out_free_tgt;

out_unlock:

	mutex_unlock();

out_free_tgt:

	scst_free_tgt(tgt);
}

void scst_unregister_target(struct scst_tgt *tgt)
{
...

	scst_tgt_sysfs_del(tgt);
...
	scst_free_tgt(tgt);
}

You can see complete source code of those functions in the original patch set I sent in this thread (patches 4 and 8).

What am I missing and how errors processing should be done with neither freeing tgt not in tgt_kobj release() as above, nor without tgt_kobj_initialized flag as in the patch?

Thanks,
Vlad

#19 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 13 lines · Vladislav Bolkhovitin
Quoted 11 lines · Greg KH, on 10/22/2010 09:56 PM wrote:

scst/scst.h struct scst_tgt { · line 1434

Quoted 1 line · Vladislav Bolkhovitin
scst/scst.h +1 -0
+	unsigned int tgt_kobj_initialized:1;

It's the middle of the merge window, and I'm about to go on vacation, so I didn't read this patch after this line.

It's obvious that this patch is wrong, you shouldn't need to worry about this. And even if you did, you don't need this flag.

Why are you trying to do something that no one else needs? Why make things harder than they have to be.

Quoted 8 lines

I tried to explain that to you in http://lkml.org/lkml/2010/10/14/291
and mentioned there the need to create this flag to track half-initialized kobjects. You agreed
(http://lkml.org/lkml/2010/10/14/299) that don't return half-initialized objects is a regular kernel practice, but then requested to strictly bound the larger object freeing to its kobject release(), which means that all SYSFS creating functions now have to return half-initialized SYSFS hierarchy in case of any error. Hence the flag to track it.

I agreed that you needed to do something about it, not that this is the correct way to do it.

Think for a second as to why your code path looks different from EVERY other kobject user in the kernel. Perhaps it is incorrect? You don't need all this completion mess, in fact, it's wrong.

Just do what everyone else does please, as that is the simpler, and correct, way to do it.

Oh, and why are you using a kobject at all anyway? Shouldn't you be using a 'struct device'?

Anyway, I don't have time for this anymore for the next 2 weeks, good luck.

greg k-h

#20 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 24 lines · Greg KH, on 10/22/2010 10:54 PM wrote:
Quoted 11 lines · Vladislav Bolkhovitin
Quoted 10 lines

scst/scst.h struct scst_tgt { · line 1434

Quoted 1 line
scst/scst.h +1 -0
+	unsigned int tgt_kobj_initialized:1;

It's the middle of the merge window, and I'm about to go on vacation, so I didn't read this patch after this line.

It's obvious that this patch is wrong, you shouldn't need to worry about this. And even if you did, you don't need this flag.

Why are you trying to do something that no one else needs? Why make things harder than they have to be.

Quoted 8 lines

I tried to explain that to you in http://lkml.org/lkml/2010/10/14/291
and mentioned there the need to create this flag to track half-initialized kobjects. You agreed
(http://lkml.org/lkml/2010/10/14/299) that don't return half-initialized objects is a regular kernel practice, but then requested to strictly bound the larger object freeing to its kobject release(), which means that all SYSFS creating functions now have to return half-initialized SYSFS hierarchy in case of any error. Hence the flag to track it.

I agreed that you needed to do something about it, not that this is the correct way to do it.

Quoted 6 lines

Think for a second as to why your code path looks different from EVERY other kobject user in the kernel. Perhaps it is incorrect? You don't need all this completion mess, in fact, it's wrong.

Just do what everyone else does please, as that is the simpler, and correct, way to do it.

Hello Greg,

Why SCST objects are different from most other kernel objects and why they can't be implemented the same way as them is exactly what I'm trying to explain you. Let me try again from a bit different angle.

SCST objects are different from the most other kernel objects, because they are very complex, hence complex to initialize and delete with dependencies in order how initialization and delete actions should be performed. Particularly, SCST objects have a lot of attributes and sub-objects, so their kobjects can't be inited and exposed to SYSFS or removed from it until they reach some point during initialization or delete correspondingly, otherwise their attributes' reads and writes can crash.

I can elaborate all those on examples, if you request.

I understand you are very busy and appreciate very much your review and comments, so be glad to implement what you are requesting. But I don't see how to implement that in an acceptable manner with neither the completion-based delete as now, nor with x_initialized flag as in the previous patch.

Note, SCST's kobjects are not the only kobjects in the kernel using the completion based delete. See, for instance, ktype_cpufreq, ktype_cpuidle or ext4_ktype.

Also, elevator (struct elevator_queue) uses "registered" flag to see if it was added to the SYSFS.

Thus, you can see, both the completion and the flag approaches already used in the kernel. Hence, I believe, the way how SCST is doing it should also be acceptable.

I'm not a person who, as you, probably, guessed, at first doing, only then thinking and reading docs. I before writing a code line at first read all the available docs and carefully consider possible alternatives, so there isn't a well thought bit in SCST.

I believe, the completions aren't mess, they are refinement.

Quoted 2 lines

Oh, and why are you using a kobject at all anyway? Shouldn't you be using a 'struct device'?

We need only to represent our internal SCST objects on the SYSFS. The objects are not devices, but special entities for special purposes. For instance, struct scst_session is a representation of SCSI I_T nexuses. Struct scst_tgt_dev - I_T_L nexuses. Another example is struct scst_acg, which is a representation of per initiator access control groups to determine which initiators can see which LUNs. Hence, for such purpose kobjects are fully sufficient.

Thanks,
Vlad

#21 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 29 lines · Vladislav Bolkhovitin
Quoted 28 lines · Greg KH, on 10/22/2010 10:54 PM wrote:
Quoted 11 lines · Vladislav Bolkhovitin
Quoted 10 lines

scst/scst.h struct scst_tgt { · line 1434

Quoted 1 line
scst/scst.h +1 -0
+	unsigned int tgt_kobj_initialized:1;

It's the middle of the merge window, and I'm about to go on vacation, so I didn't read this patch after this line.

It's obvious that this patch is wrong, you shouldn't need to worry about this. And even if you did, you don't need this flag.

Why are you trying to do something that no one else needs? Why make things harder than they have to be.

Quoted 8 lines

I tried to explain that to you in http://lkml.org/lkml/2010/10/14/291
and mentioned there the need to create this flag to track half-initialized kobjects. You agreed
(http://lkml.org/lkml/2010/10/14/299) that don't return half-initialized objects is a regular kernel practice, but then requested to strictly bound the larger object freeing to its kobject release(), which means that all SYSFS creating functions now have to return half-initialized SYSFS hierarchy in case of any error. Hence the flag to track it.

I agreed that you needed to do something about it, not that this is the correct way to do it.

Think for a second as to why your code path looks different from EVERY other kobject user in the kernel. Perhaps it is incorrect? You don't need all this completion mess, in fact, it's wrong.

Quoted 8 lines
Quoted 2 lines

Just do what everyone else does please, as that is the simpler, and correct, way to do it.

Hello Greg,

Why SCST objects are different from most other kernel objects and why they can't be implemented the same way as them is exactly what I'm trying to explain you. Let me try again from a bit different angle.

I'm sorry, but I just don't buy it.

Quoted 4 lines

SCST objects are different from the most other kernel objects, because they are very complex, hence complex to initialize and delete with dependencies in order how initialization and delete actions should be performed.

Then don't abuse kobjects with this "different" type of kobject, as that is not how the kobject code was designed to be used.

It was only designed to be used with the "sane" type of kernel objects :)

Quoted 5 lines

Particularly, SCST objects have a lot of attributes and sub-objects, so their kobjects can't be inited and exposed to SYSFS or removed from it until they reach some point during initialization or delete correspondingly, otherwise their attributes' reads and writes can crash.

That sounds like an implementation error. No other kernel code has that problem from what I can see.

Quoted 3 lines

Note, SCST's kobjects are not the only kobjects in the kernel using the completion based delete. See, for instance, ktype_cpufreq, ktype_cpuidle or ext4_ktype.

ext4 is using this to get stuff under the /sys/fs/ext4 location. And even there, I don't think it is using kobjects correctly, but I really don't want to go audit that code at the moment.

cpufreq and cpuidle is also probably incorrect, but again, I don't feel like auditing it right now.

Basically, don't copy bad examples as a valid usage model for the code.

Quoted 2 lines

Also, elevator (struct elevator_queue) uses "registered" flag to see if it was added to the SYSFS.

It's wrong and should be fixed then.

Quoted 10 lines
Quoted 2 lines

Oh, and why are you using a kobject at all anyway? Shouldn't you be using a 'struct device'?

We need only to represent our internal SCST objects on the SYSFS. The objects are not devices, but special entities for special purposes. For instance, struct scst_session is a representation of SCSI I_T nexuses. Struct scst_tgt_dev - I_T_L nexuses. Another example is struct scst_acg, which is a representation of per initiator access control groups to determine which initiators can see which LUNs. Hence, for such purpose kobjects are fully sufficient.

No, you should be using a struct device as you are putting stuff into the device tree. NEVER put a kobject into the device tree, that is just wrong and will cause problems.

thanks,

greg k-h

#22 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 46 lines · Greg KH, on 11/09/2010 03:28 AM wrote:
Quoted 29 lines · Vladislav Bolkhovitin
Quoted 28 lines · Greg KH, on 10/22/2010 10:54 PM wrote:
Quoted 11 lines · Vladislav Bolkhovitin
Quoted 10 lines

scst/scst.h struct scst_tgt { · line 1434

Quoted 1 line
scst/scst.h +1 -0
+	unsigned int tgt_kobj_initialized:1;

It's the middle of the merge window, and I'm about to go on vacation, so I didn't read this patch after this line.

It's obvious that this patch is wrong, you shouldn't need to worry about this. And even if you did, you don't need this flag.

Why are you trying to do something that no one else needs? Why make things harder than they have to be.

Quoted 8 lines

I tried to explain that to you in http://lkml.org/lkml/2010/10/14/291
and mentioned there the need to create this flag to track half-initialized kobjects. You agreed
(http://lkml.org/lkml/2010/10/14/299) that don't return half-initialized objects is a regular kernel practice, but then requested to strictly bound the larger object freeing to its kobject release(), which means that all SYSFS creating functions now have to return half-initialized SYSFS hierarchy in case of any error. Hence the flag to track it.

I agreed that you needed to do something about it, not that this is the correct way to do it.

Think for a second as to why your code path looks different from EVERY other kobject user in the kernel. Perhaps it is incorrect? You don't need all this completion mess, in fact, it's wrong.

Quoted 8 lines
Quoted 2 lines

Just do what everyone else does please, as that is the simpler, and correct, way to do it.

Hello Greg,

Why SCST objects are different from most other kernel objects and why they can't be implemented the same way as them is exactly what I'm trying to explain you. Let me try again from a bit different angle.

I'm sorry, but I just don't buy it.

Quoted 4 lines

SCST objects are different from the most other kernel objects, because they are very complex, hence complex to initialize and delete with dependencies in order how initialization and delete actions should be performed.

Quoted 5 lines

Then don't abuse kobjects with this "different" type of kobject, as that is not how the kobject code was designed to be used.

It was only designed to be used with the "sane" type of kernel objects :)

SCST objects are generally 1:1 mapping of SCSI Architecture Model (SAM) objects. Is SAM insane and should have been designed with kobjects in mind? ;)

Quoted 6 lines
Quoted 5 lines

Particularly, SCST objects have a lot of attributes and sub-objects, so their kobjects can't be inited and exposed to SYSFS or removed from it until they reach some point during initialization or delete correspondingly, otherwise their attributes' reads and writes can crash.

Quoted 8 lines

That sounds like an implementation error. No other kernel code has that problem from what I can see.

Quoted 3 lines

Note, SCST's kobjects are not the only kobjects in the kernel using the completion based delete. See, for instance, ktype_cpufreq, ktype_cpuidle or ext4_ktype.

ext4 is using this to get stuff under the /sys/fs/ext4 location.

"To get stuff under /sys/..." is exactly for what SCST needs kobjects. SCST objects are user interface agnostic and can be equally well exposed to user space via SYSFS, PROCFS, or anything else, like CONFIGFS or custom IOCTLs.

Quoted 6 lines

And
even there, I don't think it is using kobjects correctly, but I really don't want to go audit that code at the moment.

cpufreq and cpuidle is also probably incorrect, but again, I don't feel like auditing it right now.

Sorry, but what is incorrect in the working implementation without any bugs doing its job in the simplest, smallest and clearest way?

If those objects remade to free themselves in the kobjects release(), what value would it add to them? Would the implementation be simpler, smaller or clearer? Not, I believe, new implementation would be only bigger and less clear. So, what's the point to do it to make the code worse?

Quoted 11 lines
Quoted 10 lines
Quoted 2 lines

Oh, and why are you using a kobject at all anyway? Shouldn't you be using a 'struct device'?

We need only to represent our internal SCST objects on the SYSFS. The objects are not devices, but special entities for special purposes. For instance, struct scst_session is a representation of SCSI I_T nexuses. Struct scst_tgt_dev - I_T_L nexuses. Another example is struct scst_acg, which is a representation of per initiator access control groups to determine which initiators can see which LUNs. Hence, for such purpose kobjects are fully sufficient.

Quoted 3 lines

No, you should be using a struct device as you are putting stuff into the device tree. NEVER put a kobject into the device tree, that is just wrong and will cause problems.

Hmm, we never put any stuff into the device tree. Why do you think we do? None of SCST objects has any relation to any hardware device, hence there's no need to touch the device tree.

Thanks,
Vlad

#23 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 9 lines · Vladislav Bolkhovitin

Sorry, but what is incorrect in the working implementation without any bugs doing its job in the simplest, smallest and clearest way?

If those objects remade to free themselves in the kobjects release(), what value would it add to them? Would the implementation be simpler, smaller or clearer? Not, I believe, new implementation would be only bigger and less clear. So, what's the point to do it to make the code worse?

Totally theoretically speaking, since I have not inspected the code.

If today you wait for the count to reach zero, then unregister and send an event to some other subsystem to free the object.

Is it not the same as if you take an extra refcount, unregister and send the event at count=1. Then at that other place decrement the last count to cause the object to be freed.

I agree that it is hard to do lockless. what some places do is have an extra kref. The kobj has a single ref on it. everything takes the other kref. when that reaches zero the unregister and event fires and at free you decrement the only kobj ref to deallocate. This is one way. In some situations you can manage with a single counter it all depends.

Boaz

#24 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 20 lines · Boaz Harrosh, on 11/10/2010 12:58 PM wrote:
Quoted 9 lines · Vladislav Bolkhovitin

Sorry, but what is incorrect in the working implementation without any bugs doing its job in the simplest, smallest and clearest way?

If those objects remade to free themselves in the kobjects release(), what value would it add to them? Would the implementation be simpler, smaller or clearer? Not, I believe, new implementation would be only bigger and less clear. So, what's the point to do it to make the code worse?

Totally theoretically speaking, since I have not inspected the code.

If today you wait for the count to reach zero, then unregister and send an event to some other subsystem to free the object.

Is it not the same as if you take an extra refcount, unregister and send the event at count=1. Then at that other place decrement the last count to cause the object to be freed.

Quoted 6 lines

I agree that it is hard to do lockless. what some places do is have an extra kref. The kobj has a single ref on it. everything takes the other kref. when that reaches zero the unregister and event fires and at free you decrement the only kobj ref to deallocate. This is one way. In some situations you can manage with a single counter it all depends.

Thanks for sharing your thoughts with us. But the question isn't about if it's possible to implement what we need locklessly. The question is in two approaches how to synchronously delete objects with entries on SYSFS:

1. struct object_x {
	...
	struct kobject kobj;
	struct completion *release_completion;
};

static void x_release(struct kobject *kobj)

{
	struct object_x *x;
	struct completion *c;

	x = container_of(kobj, struct object_x, kobj);
	c = x->release_completion;
	kfree(x);
	complete_all(c);
}

void del_object(struct object_x *x)

{
	DECLARE_COMPLETION_ONSTACK(completion);

	...
	x->release_completion = &completion;
	kobject_put(&x->kobj);
	wait_for_completion(&completion);
}

and

2. struct object_x {
	...
	struct kobject kobj;
	struct completion release_completion;
};

static void x_release(struct kobject *kobj)

{
	struct object_x *x;

	x = container_of(kobj, struct object_x, kobj);
	complete_all(&x->release_completion);
}

void del_object(struct object_x *x)

{
	...
	kobject_put(&x->kobj);
	wait_for_completion(&completion);
	...
	kfree(x);
}

Greg asserts that (1) is the only correct approach while (2) is incorrect, and I'm trying to justify that (2) is correct too and sometimes could be better, i.e. simpler and clearer, because it decouples object_x from SYSFS and its kobj. Then kobj becomes an ordinary member of struct object_x without any special treatment and with the same lifetime rules as other members of struct object_x. While in (1) all lifetime of struct object_x is strictly attached to kobj, so it needs be specially handled with additional code for that if struct object_x has many other members which needed to be initialized/deleted _before and after_ kobj as we have in SCST.

Vlad

#25 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 75 lines · Vladislav Bolkhovitin
Quoted 20 lines · Boaz Harrosh, on 11/10/2010 12:58 PM wrote:
Quoted 9 lines · Vladislav Bolkhovitin

Sorry, but what is incorrect in the working implementation without any bugs doing its job in the simplest, smallest and clearest way?

If those objects remade to free themselves in the kobjects release(), what value would it add to them? Would the implementation be simpler, smaller or clearer? Not, I believe, new implementation would be only bigger and less clear. So, what's the point to do it to make the code worse?

Totally theoretically speaking, since I have not inspected the code.

If today you wait for the count to reach zero, then unregister and send an event to some other subsystem to free the object.

Is it not the same as if you take an extra refcount, unregister and send the event at count=1. Then at that other place decrement the last count to cause the object to be freed.

Quoted 6 lines

I agree that it is hard to do lockless. what some places do is have an extra kref. The kobj has a single ref on it. everything takes the other kref. when that reaches zero the unregister and event fires and at free you decrement the only kobj ref to deallocate. This is one way. In some situations you can manage with a single counter it all depends.

Thanks for sharing your thoughts with us. But the question isn't about if it's possible to implement what we need locklessly. The question is in two approaches how to synchronously delete objects with entries on SYSFS:

1. struct object_x {
	...
	struct kobject kobj;
	struct completion *release_completion;
};

static void x_release(struct kobject *kobj)

{
	struct object_x *x;
	struct completion *c;

	x = container_of(kobj, struct object_x, kobj);
	c = x->release_completion;
	kfree(x);
	complete_all(c);
}

void del_object(struct object_x *x)

{
	DECLARE_COMPLETION_ONSTACK(completion);

	...
	x->release_completion = &completion;
	kobject_put(&x->kobj);
	wait_for_completion(&completion);
}

and

2. struct object_x {
	...
	struct kobject kobj;
	struct completion release_completion;
};

static void x_release(struct kobject *kobj)

{
	struct object_x *x;

	x = container_of(kobj, struct object_x, kobj);
	complete_all(&x->release_completion);
}
Quoted 8 lines

void del_object(struct object_x *x)

{
	...
	kobject_put(&x->kobj);
	wait_for_completion(&completion);
	...
	kfree(x);
}

I'll admit I don't understand this all that well, but why not just have x_release() (based on (2)) do free(x), and have del_object
do the kobject_put(&x->kobj) as its very last thing? Then you don't need the completion.

Quoted 13 lines

Greg asserts that (1) is the only correct approach while (2) is incorrect, and I'm trying to justify that (2) is correct too and sometimes could be better, i.e. simpler and clearer, because it decouples object_x from SYSFS and its kobj. Then kobj becomes an ordinary member of struct object_x without any special treatment and with the same lifetime rules as other members of struct object_x. While in (1) all lifetime of struct object_x is strictly attached to kobj, so it needs be specially handled with additional code for that if struct object_x has many other members which needed to be initialized/deleted _before and after_ kobj as we have in SCST.

Vlad

#26 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

Quoted 56 lines · Joe Eykholt, on 11/10/2010 11:29 PM wrote:
Quoted 47 lines

Thanks for sharing your thoughts with us. But the question isn't about if it's possible to implement what we need locklessly. The question is in two approaches how to synchronously delete objects with entries on SYSFS:

1. struct object_x {
	...
	struct kobject kobj;
	struct completion *release_completion;
};

static void x_release(struct kobject *kobj)

{
	struct object_x *x;
	struct completion *c;

	x = container_of(kobj, struct object_x, kobj);
	c = x->release_completion;
	kfree(x);
	complete_all(c);
}

void del_object(struct object_x *x)

{
	DECLARE_COMPLETION_ONSTACK(completion);

	...
	x->release_completion = &completion;
	kobject_put(&x->kobj);
	wait_for_completion(&completion);
}

and

2. struct object_x {
	...
	struct kobject kobj;
	struct completion release_completion;
};

static void x_release(struct kobject *kobj)

{
	struct object_x *x;

	x = container_of(kobj, struct object_x, kobj);
	complete_all(&x->release_completion);
}
Quoted 8 lines

void del_object(struct object_x *x)

{
	...
	kobject_put(&x->kobj);
	wait_for_completion(&completion);
	...
	kfree(x);
}
Quoted 5 lines

I'll admit I don't understand this all that well, but why not just have x_release() (based on (2)) do free(x), and have del_object
do the kobject_put(&x->kobj) as its very last thing? Then you don't need the completion.

We are discussing _synchronous_ delete of x, so need to wait until x->kobj released, hence the completion is needed in both cases.

For instance, the sync delete is needed for targets to let the corresponding target driver be safely unloaded after its target unregistered.

Vlad

#27 lore raw

Re: [PATCH 8/19]: SCST SYSFS interface implementation

In reply to Joe Eykholt
Quoted 87 lines · Joe Eykholt
Quoted 75 lines · Vladislav Bolkhovitin
Quoted 20 lines · Boaz Harrosh, on 11/10/2010 12:58 PM wrote:
Quoted 9 lines · Vladislav Bolkhovitin

Sorry, but what is incorrect in the working implementation without any bugs doing its job in the simplest, smallest and clearest way?

If those objects remade to free themselves in the kobjects release(), what value would it add to them? Would the implementation be simpler, smaller or clearer? Not, I believe, new implementation would be only bigger and less clear. So, what's the point to do it to make the code worse?

Totally theoretically speaking, since I have not inspected the code.

If today you wait for the count to reach zero, then unregister and send an event to some other subsystem to free the object.

Is it not the same as if you take an extra refcount, unregister and send the event at count=1. Then at that other place decrement the last count to cause the object to be freed.

Quoted 6 lines

I agree that it is hard to do lockless. what some places do is have an extra kref. The kobj has a single ref on it. everything takes the other kref. when that reaches zero the unregister and event fires and at free you decrement the only kobj ref to deallocate. This is one way. In some situations you can manage with a single counter it all depends.

Thanks for sharing your thoughts with us. But the question isn't about if it's possible to implement what we need locklessly. The question is in two approaches how to synchronously delete objects with entries on SYSFS:

1. struct object_x {
	...
	struct kobject kobj;
	struct completion *release_completion;
};

static void x_release(struct kobject *kobj)

{
	struct object_x *x;
	struct completion *c;

	x = container_of(kobj, struct object_x, kobj);
	c = x->release_completion;
	kfree(x);
	complete_all(c);
}

void del_object(struct object_x *x)

{
	DECLARE_COMPLETION_ONSTACK(completion);

	...
	x->release_completion = &completion;
	kobject_put(&x->kobj);
	wait_for_completion(&completion);
}

and

2. struct object_x {
	...
	struct kobject kobj;
	struct completion release_completion;
};

static void x_release(struct kobject *kobj)

{
	struct object_x *x;

	x = container_of(kobj, struct object_x, kobj);
	complete_all(&x->release_completion);
}
Quoted 8 lines

void del_object(struct object_x *x)

{
	...
	kobject_put(&x->kobj);
	wait_for_completion(&completion);
	...
	kfree(x);
}
Quoted 5 lines

I'll admit I don't understand this all that well, but why not just have x_release() (based on (2)) do free(x), and have del_object
do the kobject_put(&x->kobj) as its very last thing? Then you don't need the completion.

Ah, well to answer my own question, I guess that's (1). Never mind.

Joe

Quoted 12 lines
Quoted 12 lines

Greg asserts that (1) is the only correct approach while (2) is incorrect, and I'm trying to justify that (2) is correct too and sometimes could be better, i.e. simpler and clearer, because it decouples object_x from SYSFS and its kobj. Then kobj becomes an ordinary member of struct object_x without any special treatment and with the same lifetime rules as other members of struct object_x. While in (1) all lifetime of struct object_x is strictly attached to kobj, so it needs be specially handled with additional code for that if struct object_x has many other members which needed to be initialized/deleted _before and after_ kobj as we have in SCST.

Vlad