- Module scst_user and user space utility to test it added
authorvlnb <vlnb@d57e44dd-8a1f-0410-8b47-8ef2f437770f>
Thu, 31 May 2007 17:11:57 +0000 (17:11 +0000)
committervlnb <vlnb@d57e44dd-8a1f-0410-8b47-8ef2f437770f>
Thu, 31 May 2007 17:11:57 +0000 (17:11 +0000)
 - Support for per-target default security groups added
 - FILEIO made multithreaded
 - BLOCKIO made async
 - Other improvements, fixes and cleanups

git-svn-id: https://scst.svn.sourceforge.net/svnroot/scst/trunk@121 d57e44dd-8a1f-0410-8b47-8ef2f437770f

22 files changed:
Makefile
scst/ChangeLog
scst/README
scst/include/scsi_tgt.h
scst/include/scst_user.h [new file with mode: 0644]
scst/kernel/in-tree/Kconfig.scsi_tgt
scst/kernel/in-tree/Makefile.scsi_tgt
scst/src/Makefile
scst/src/dev_handlers/Makefile
scst/src/dev_handlers/scst_user.c [new file with mode: 0644]
scst/src/dev_handlers/scst_vdisk.c
scst/src/scst.c
scst/src/scst_lib.c
scst/src/scst_priv.h
scst/src/scst_proc.c
scst/src/scst_targ.c
usr/fileio/Makefile [new file with mode: 0644]
usr/fileio/common.c [new file with mode: 0644]
usr/fileio/common.h [new file with mode: 0644]
usr/fileio/debug.c [new file with mode: 0644]
usr/fileio/debug.h [new file with mode: 0644]
usr/fileio/fileio.c [new file with mode: 0644]

index 5ad9406..31f93f9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -22,33 +22,39 @@ SCST_DIR=scst
 QLA_INI_DIR=qla2x00t
 QLA_DIR=qla2x00t/qla2x00-target
 LSI_DIR=mpt
+USR_DIR=usr/fileio
 
 all:
        cd $(SCST_DIR) && $(MAKE) $@
        @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi
 #      @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi
+       @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi
 
 install: 
        cd $(SCST_DIR) && $(MAKE) $@
        @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi
-       @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi
+#      @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi
+       @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi
 
 uninstall: 
        cd $(SCST_DIR) && $(MAKE) $@
        @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi
        @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi
+       @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi
 
 clean: 
        cd $(SCST_DIR) && $(MAKE) $@
        @if [ -d $(QLA_INI_DIR) ]; then cd $(QLA_INI_DIR) && $(MAKE) $@; fi
        @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi
        @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi
+       @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi
 
 extraclean: 
        cd $(SCST_DIR) && $(MAKE) $@
        @if [ -d $(QLA_INI_DIR) ]; then cd $(QLA_INI_DIR) && $(MAKE) $@; fi
        @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi
        @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi
+       @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi
 
 scst: 
        cd $(SCST_DIR) && $(MAKE)
@@ -97,6 +103,21 @@ lsi_clean:
 lsi_extraclean:
        cd $(LSI_DIR) && $(MAKE) extraclean
 
+usr:
+       cd $(USR_DIR) && $(MAKE)
+
+usr_install:
+       cd $(USR_DIR) && $(MAKE) install
+
+usr_uninstall:
+       cd $(USR_DIR) && $(MAKE) uninstall
+
+usr_clean: 
+       cd $(USR_DIR) && $(MAKE) clean
+
+usr_extraclean:
+       cd $(USR_DIR) && $(MAKE) extraclean
+
 help:
        @echo "         all (the default) : make all"
        @echo "         clean             : clean files"
@@ -121,6 +142,12 @@ help:
        @echo "         lsi_extraclean  : lsi target: clean + clean dependencies"
        @echo "         lsi_install     : lsi target: install"
        @echo "         lsi_uninstall   : lsi target: uninstall"
+       @echo ""
+       @echo "         usr             : make usr target"
+       @echo "         usr_clean       : usr target: clean "
+       @echo "         usr_extraclean  : usr target: clean + clean dependencies"
+       @echo "         usr_install     : usr target: install"
+       @echo "         usr_uninstall   : usr target: uninstall"
        @echo " Notes :"
        @echo "         - install and uninstall must be made as root"
 
@@ -128,3 +155,4 @@ help:
        qla qla_install qla_uninstall qla_clean qla_extraclean \
        lsi lsi_install lsi_uninstall lsi_clean lsi_extraclean \
        scst scst_install scst_uninstall scst_clean scst_extraclean
+       usr usr_install usr_uninstall usr_clean usr_extraclean
index 327a558..898c0e7 100644 (file)
@@ -4,11 +4,13 @@ Summary of changes between versions 0.9.5 and 0.9.6
  - FILEIO was renamed to VDISK. BLOCKIO added to it, thanks to Ross S. W.
    Walker and Vu Pham.
 
- - Internal locking and execution context were reimplemnted. Particularly,
-   implemented full support for SCSI task attributes (SIMPLE, ORDERED,
-   etc.).
+ - Updated to work on 2.6.20.x, no update for 2.6.21.x isn't needed
 
- - Updated to work on 2.6.20.x, no update for 2.6.21.x isn't needed.
+ - Internal locking and execution context were reimplemnted. As some of
+   the results now FILEIO has >1 IO threads and implemented full support
+   for SCSI task attributes (SIMPLE, ORDERED, etc.).
+
+ - Ability to have per-target default security groups added.
 
  - Updated to work on 2.6.19.x, thanks to Ming Zhang.
 
index 0d12e76..db22d54 100644 (file)
@@ -106,7 +106,7 @@ allows to work directly over a block device, e.g. local IDE or SCSI disk
 or ever disk partition, where there is no file systems overhead. Using
 block devices comparing to sending SCSI commands directly to SCSI
 mid-level via scsi_do_req()/scsi_execute_async() has advantage that data
-are transfered via system cache, so it is possible to fully benefit from
+are transferred via system cache, so it is possible to fully benefit from
 caching and read ahead performed by Linux's VM subsystem. The only
 disadvantage here that in the FILEIO mode there is superfluous data
 copying between the cache and SCST's buffers. This issue is going to be
@@ -145,12 +145,12 @@ in/out in Makefile:
  - EXTRACHECKS - adds extra validity checks in the various places.
 
  - DEBUG_TM - turns on task management functions debugging, when on
-   LUN 0 in the "Default" group some of the commands will be delayed for
-   about 60 sec., so making the remote initiator send TM functions, eg
-   ABORT TASK and TARGET RESET. Also set TM_DBG_GO_OFFLINE symbol in the
-   Makefile to 1 if you want that the device eventually become
-   completely unresponsive, or to 0 otherwise to circle around ABORTs
-   and RESETs code. Needs DEBUG turned on.
+   LUN 0 in the default access control group some of the commands will
+   be delayed for about 60 sec., so making the remote initiator send TM
+   functions, eg ABORT TASK and TARGET RESET. Also set TM_DBG_GO_OFFLINE
+   symbol in the Makefile to 1 if you want that the device eventually
+   become completely unresponsive, or to 0 otherwise to circle around
+   ABORTs and RESETs code. Needs DEBUG turned on.
 
  - STRICT_SERIALIZING - makes SCST send all commands to underlying SCSI
    device synchronously, one after one. This makes task management more
@@ -235,19 +235,21 @@ Access and devices visibility management (LUN masking)
 Access and devices visibility management allows for an initiator or
 group of initiators to have different limited set of LUs/LUNs (security
 group) each with appropriate access permissions. Initiator is
-represented as a SCST session. Session is binded to security group on
-its registration time by character "name" parameter of the registration
+represented as a SCST session. Session is bound to security group on its
+registration time by character "name" parameter of the registration
 function, which provided by target driver, based on its internal
-authentication. For example, for FC "name" could be WWN or just loop
-ID. For iSCSI this could be iSCSI login credentials or iSCSI initiator
-name. Each security group has set of names assigned to it by system
-administrator. Session is binded to security group with provided name.
-If no such groups found, the session binded to "Default" group.
+authentication. For example, for FC "name" could be WWN or just loop ID.
+For iSCSI this could be iSCSI login credentials or iSCSI initiator name.
+Each security group has set of names assigned to it by system
+administrator. Session is bound to security group with provided name. If
+no such groups found, the session bound to either "Default_target_name",
+or "Default" group, depending from either "Default_target_name" exists
+or not. In "Default_target_name" target name means name of the target.
 
 In /proc/scsi_tgt each group represented as "groups/GROUP_NAME/"
 subdirectory. In it there are files "devices" and "users". File
 "devices" lists all devices and their LUNs in the group, file "users"
-lists all names that should be binded to this group.
+lists all names that should be bound to this group.
 
 To configure access and devices visibility management SCST provides the
 following files and directories under /proc/scsi_tgt:
@@ -310,7 +312,7 @@ subdirectories "vdisk" and "vcdrom". They have similar layout:
       device "NAME" with block size "BLOCK_SIZE" bytes with flags
       "FLAGS". "PATH" could be empty only for VDISK CDROM. "BLOCK_SIZE"
       and "FLAGS" are valid only for disk VDISK. The block size must be
-      power of 2 and >= 512 bytes Default is 512. Possible flags:
+      power of 2 and >= 512 bytes. Default is 512. Possible flags:
       
       - WRITE_THROUGH - write back caching disabled
       
@@ -479,8 +481,15 @@ II. In order to get the maximum performance you should:
 
  - Disable in Makefile TRACING, DEBUG
 
-IMPORTANT: Some of those options enabled by default, i.e. SCST is optimized
-=========  currently rather for development, not for performance.
+ - If your initiator(s) use dedicated exported from the target virtual
+   SCSI devices and have more or equal amount of memory, than the
+   target, it is recommended to use O_DIRECT option (currently it is
+   available only with fileio_tgt user space program) or BLOCKIO. With
+   them you could have up to 100% increase in throughput.
+
+IMPORTANT: Some of the compilation options enabled by default, i.e. SCST
+=========  is optimized currently rather for development and bug hunting,
+           not for performance.
 
 4. For kernel:
 
@@ -496,7 +505,11 @@ IMPORTANT: Some of those options enabled by default, i.e. SCST is optimized
    parameters in /sys/block/device directory, they also affect the
    performance. If you find the best values, please share them with us.
 
- - Also it is recommended to turn the kernel preemption off, i.e. set
+ - Use on the target deadline IO scheduler with read_expire and
+   write_expire increased on all exported devices to 5000 and 20000
+   correspondingly.
+
+ - It is recommended to turn the kernel preemption off, i.e. set
    the kernel preemption model to "No Forced Preemption (Server)".
 
 5. For hardware.
index bf5b8b5..cf6e3bc 100644 (file)
@@ -39,7 +39,7 @@
 /* Version numbers, the same as for the kernel */
 #define SCST_VERSION_CODE 0x000906
 #define SCST_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
-#define SCST_VERSION_STRING "0.9.6-pre1"
+#define SCST_VERSION_STRING "0.9.6-pre2"
 
 /*************************************************************
  ** States of command processing state machine
@@ -481,8 +481,15 @@ struct scst_tgt_template
        void (*on_free_cmd) (struct scst_cmd *cmd);
 
        /*
-        * This function allows the target driver to handle data buffer
+        * This function allows target driver to handle data buffer
         * allocations on its own.
+        *
+        * Target driver doesn't have to always allocate buffer in this
+        * function, but if it decide to do it, it must check that
+        * scst_cmd_get_data_buff_alloced() returns 0, otherwise to avoid
+        * double buffer allocation and memory leaks alloc_data_buf() shall
+        * fail.
+        *
         * Shall return 0 in case of success or < 0 (preferrably -ENOMEM)
         * in case of error, or > 0 if the regular SCST allocation should be
         * done. In case of returning successfully, scst_cmd->data_buf_alloced
@@ -492,9 +499,9 @@ struct scst_tgt_template
         * desired or fails and consequently < 0 is returned, this function
         * will be re-called in thread context.
         *
-        * Please note that the driver will have to handle all relevant details
-        * such as scatterlist setup, highmem, freeing the allocated memory, ...
-        * itself.
+        * Please note that the driver will have to handle itself all relevant
+        * details such as scatterlist setup, highmem, freeing the allocated
+        * memory, etc.
         *
         * OPTIONAL.
         */
@@ -593,6 +600,14 @@ struct scst_tgt_template
         */
        const char name[50];
 
+       /* 
+        * Number of additional threads to the pool of dedicated threads.
+        * Used if xmit_response() or rdy_to_xfer() is blocking.
+        * It is the target driver's duty to ensure that not more, than that
+        * number of threads, are blocked in those functions at any time.
+        */
+       int threads_num;
+
        /* Private, must be inited to 0 by memset() */
 
        /* List of targets per template, protected by scst_mutex */
@@ -620,7 +635,6 @@ struct scst_dev_type
        unsigned parse_atomic:1;
        unsigned exec_atomic:1;
        unsigned dev_done_atomic:1;
-       unsigned dedicated_thread:1;
 
        /* Set, if no /proc files should be automatically created by SCST */
        unsigned no_proc:1;
@@ -748,6 +762,12 @@ struct scst_dev_type
        /* SCSI type of the supported device. MUST HAVE */
        int type;
 
+       /*
+        * Number of dedicated threads. If 0 - no dedicated threads will 
+        * be created, if <0 - creation of dedicated threads is prohibited.
+        */
+       int threads_num;
+
        struct module *module;
 
        /* private: */
@@ -795,6 +815,9 @@ struct scst_tgt
 
        /* Used for storage of target driver private stuff */
        void *tgt_priv;
+
+       /* Name on the default security group ("Default_target_name") */
+       char *default_group_name;
 };
 
 /* Hash size and hash fn for hash based lun translation */
@@ -1314,8 +1337,8 @@ struct scst_tgt_dev
        /* internal tmp list entry */
        struct list_head extra_tgt_dev_list_entry;
 
-       /* Dedicated thread. Doesn't need any protection. */
-       struct task_struct *thread;
+       /* List of dedicated threads. Doesn't need any protection.  */
+       struct list_head threads_list;
 };
 
 /*
@@ -1419,9 +1442,15 @@ void scst_unregister_target_template(struct scst_tgt_template *vtt);
 
 /* 
  * Registers and returns target adapter
- * Returns new target structure on success or NULL otherwise
+ * Returns new target structure on success or NULL otherwise.
+ *
+ * If parameter "target_name" isn't NULL, then new security group with name 
+ * "Default_##target_name" will be created and all sessions, which don't
+ * belong to any defined security groups, will be assigned to it instead of
+ * the "Default" one.
  */
-struct scst_tgt *scst_register(struct scst_tgt_template *vtt);
+struct scst_tgt *scst_register(struct scst_tgt_template *vtt,
+       const char *target_name);
 
 /* 
  * Unregisters target adapter
diff --git a/scst/include/scst_user.h b/scst/include/scst_user.h
new file mode 100644 (file)
index 0000000..3c036cb
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ *  include/scst_user.h
+ *  
+ *  Copyright (C) 2007 Vladislav Bolkhovitin <vst@vlnb.net>
+ *  
+ *  Contains macroses for execution tracing and error reporting
+ * 
+ *  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_USER_H
+#define __SCST_USER_H
+
+#include <scst_const.h>
+
+#define DEV_USER_NAME                "scst_user"
+#define DEV_USER_PATH                  "/dev/"
+#define DEV_USER_VERSION               96
+
+/* 
+ * Chosen so sizeof(scst_user_sess) <= sizeof(scst_user_scsi_cmd_exec) 
+ * (the largest one)
+ */
+#define SCST_MAX_NAME                  45
+
+#define SCST_USER_PARSE_STANDARD       0
+#define SCST_USER_PARSE_CALL           1
+#define SCST_USER_PARSE_EXCEPTION      2
+#define SCST_USER_MAX_PARSE_OPT                SCST_USER_PARSE_EXCEPTION
+
+#define SCST_USER_ON_FREE_CMD_CALL     0
+#define SCST_USER_ON_FREE_CMD_IGNORE   1
+#define SCST_USER_MAX_ON_FREE_CMD_OPT  SCST_USER_ON_FREE_CMD_IGNORE
+
+#define SCST_USER_MEM_NO_REUSE         0
+#define SCST_USER_MEM_REUSE_READ       1
+#define SCST_USER_MEM_REUSE_WRITE      2
+#define SCST_USER_MEM_REUSE_ALL                3
+#define SCST_USER_MAX_MEM_REUSE_OPT    SCST_USER_MEM_REUSE_ALL
+
+#define SCST_USER_PRIO_QUEUE_SINGLE    0
+#define SCST_USER_PRIO_QUEUE_SEPARATE  1
+#define SCST_USER_MAX_PRIO_QUEUE_OPT   SCST_USER_PRIO_QUEUE_SEPARATE
+
+#define SCST_USER_PARTIAL_TRANSFERS_NOT_SUPPORTED      0
+#define SCST_USER_PARTIAL_TRANSFERS_SUPPORTED_ORDERED  1
+#define SCST_USER_PARTIAL_TRANSFERS_SUPPORTED          2
+#define SCST_USER_MAX_PARTIAL_TRANSFERS_OPT            SCST_USER_PARTIAL_TRANSFERS_SUPPORTED
+
+#ifndef aligned_u64
+#define aligned_u64 uint64_t __attribute__((aligned(8)))
+#endif
+
+/************************************************************* 
+ ** Private ucmd states
+ *************************************************************/
+#define UCMD_STATE_NEW                 0
+#define UCMD_STATE_PARSING             1
+#define UCMD_STATE_BUF_ALLOCING                2
+#define UCMD_STATE_EXECING             3
+#define UCMD_STATE_ON_FREEING          4
+#define UCMD_STATE_ON_FREE_SKIPPED     5
+#define UCMD_STATE_ON_CACHE_FREEING    6
+#define UCMD_STATE_TM_EXECING          7
+
+#define UCMD_STATE_ATTACH_SESS         0x20
+#define UCMD_STATE_DETACH_SESS         0x21
+
+/* Must be changed under cmd_lists.cmd_list_lock */
+#define UCMD_STATE_SENT_MASK           0x10000
+#define UCMD_STATE_RECV_MASK           0x20000
+#define UCMD_STATE_JAMMED_MASK         0x40000
+
+#define UCMD_STATE_MASK                        (UCMD_STATE_SENT_MASK | \
+                                        UCMD_STATE_RECV_MASK | \
+                                        UCMD_STATE_JAMMED_MASK)
+
+struct scst_user_opt
+{
+       uint8_t parse_type;
+       uint8_t on_free_cmd_type;
+       uint8_t memory_reuse_type;
+       uint8_t prio_queue_type;
+       uint8_t partial_transfers_type;
+       int32_t partial_len;
+};
+
+struct scst_user_dev_desc
+{
+       uint8_t version;
+       uint8_t type;
+       struct scst_user_opt opt;
+       uint32_t block_size;
+       char name[SCST_MAX_NAME];
+};
+
+struct scst_user_sess
+{
+       aligned_u64 sess_h;
+       aligned_u64 lun;
+       uint8_t rd_only;
+       char initiator_name[SCST_MAX_NAME];
+};
+
+struct scst_user_scsi_cmd_parse
+{
+       aligned_u64 sess_h;
+
+       uint8_t cdb[SCST_MAX_CDB_SIZE];
+       int32_t cdb_len;
+
+       uint32_t timeout;
+       int32_t bufflen;
+
+       uint8_t queue_type;
+       uint8_t data_direction;
+
+       uint8_t expected_values_set;
+       uint8_t expected_data_direction;
+       int32_t expected_transfer_len;
+};
+
+struct scst_user_scsi_cmd_alloc_mem
+{
+       aligned_u64 sess_h;
+
+       uint8_t cdb[SCST_MAX_CDB_SIZE];
+       int32_t cdb_len;
+
+       int32_t alloc_len;
+
+       uint8_t queue_type;
+       uint8_t data_direction;
+};
+
+struct scst_user_scsi_cmd_exec
+{
+       aligned_u64 sess_h;
+
+       uint8_t cdb[SCST_MAX_CDB_SIZE];
+       int32_t cdb_len;
+
+       int32_t data_len;
+       int32_t bufflen;
+       int32_t alloc_len;
+       aligned_u64 pbuf;
+       uint8_t queue_type;
+       uint8_t data_direction;
+       uint8_t partial;
+       uint32_t timeout;
+
+       uint32_t parent_cmd_h;
+       int32_t parent_cmd_data_len;
+       uint32_t partial_offset;
+};
+
+struct scst_user_scsi_on_free_cmd
+{
+       aligned_u64 pbuf;
+       int32_t resp_data_len;
+       uint8_t buffer_cached;
+       uint8_t status;
+};
+
+struct scst_user_on_cached_mem_free
+{
+       aligned_u64 pbuf;
+};
+
+struct scst_user_tm
+{
+       aligned_u64 sess_h;
+       uint32_t fn;
+       uint32_t cmd_h_to_abort;
+};
+
+struct scst_user_get_cmd
+{
+       aligned_u64 preply;
+       uint32_t cmd_h;
+       uint32_t subcode;
+       union {
+               struct scst_user_sess sess;
+               struct scst_user_scsi_cmd_parse parse_cmd;
+               struct scst_user_scsi_cmd_alloc_mem alloc_cmd;
+               struct scst_user_scsi_cmd_exec exec_cmd;
+               struct scst_user_scsi_on_free_cmd on_free_cmd;
+               struct scst_user_on_cached_mem_free on_cached_mem_free;
+               struct scst_user_tm tm_cmd;
+       };
+};
+
+struct scst_user_scsi_cmd_reply_parse
+{
+       uint8_t queue_type;
+       uint8_t data_direction;
+       int32_t data_len;
+       int32_t bufflen;
+};
+
+struct scst_user_scsi_cmd_reply_alloc_mem
+{
+       aligned_u64 pbuf;
+};
+
+struct scst_user_scsi_cmd_reply_exec
+{
+       int32_t resp_data_len;
+       aligned_u64 pbuf;
+
+#define SCST_EXEC_REPLY_BACKGROUND     0
+#define SCST_EXEC_REPLY_COMPLETED      1
+       uint8_t reply_type;
+
+       uint8_t status;
+       uint8_t sense_len;
+       aligned_u64 psense_buffer;
+};
+
+struct scst_user_reply_cmd
+{
+       uint32_t cmd_h;
+       uint32_t subcode;
+       union {
+               int32_t result;
+               struct scst_user_scsi_cmd_reply_parse parse_reply;
+               struct scst_user_scsi_cmd_reply_alloc_mem alloc_reply;
+               struct scst_user_scsi_cmd_reply_exec exec_reply;
+       };
+};
+
+#define SCST_USER_REGISTER_DEVICE      _IOW('u', 1, struct scst_user_dev_desc)
+#define SCST_USER_SET_OPTIONS          _IOW('u', 3, struct scst_user_opt)
+#define SCST_USER_GET_OPTIONS          _IOR('u', 4, struct scst_user_opt)
+#define SCST_USER_REPLY_AND_GET_CMD    _IOWR('u', 5, struct scst_user_get_cmd)
+#define SCST_USER_REPLY_AND_GET_PRIO_CMD _IOWR('u', 6, struct scst_user_get_cmd)
+#define SCST_USER_REPLY_CMD            _IOW('u', 7, struct scst_user_reply_cmd)
+
+/* Values for scst_user_get_cmd.subcode */
+#define SCST_USER_ATTACH_SESS          _IOR('s', UCMD_STATE_ATTACH_SESS, struct scst_user_sess)
+#define SCST_USER_DETACH_SESS          _IOR('s', UCMD_STATE_DETACH_SESS, struct scst_user_sess)
+#define SCST_USER_PARSE                        _IOWR('s', UCMD_STATE_PARSING, struct scst_user_scsi_cmd_parse)
+#define SCST_USER_ALLOC_MEM            _IOWR('s', UCMD_STATE_BUF_ALLOCING, struct scst_user_scsi_cmd_alloc_mem)
+#define SCST_USER_EXEC                 _IOWR('s', UCMD_STATE_EXECING, struct scst_user_scsi_cmd_exec)
+#define SCST_USER_ON_FREE_CMD          _IOR('s', UCMD_STATE_ON_FREEING, struct scst_user_scsi_on_free_cmd)
+#define SCST_USER_ON_CACHED_MEM_FREE   _IOR('s', UCMD_STATE_ON_CACHE_FREEING, struct scst_user_on_cached_mem_free)
+#define SCST_USER_TASK_MGMT            _IOWR('s', UCMD_STATE_TM_EXECING, struct scst_user_tm)
+
+#endif /* __SCST_USER_H */
index 72c28d4..659cb7b 100644 (file)
@@ -63,6 +63,13 @@ config SCSI_TARGET_VDISK
        ---help---
          SCSI TARGET handler for virtual disk and/or cdrom device.
 
+config SCSI_TARGET_USER
+       tristate "SCSI user space virtual target devices support"
+       default SCSI_TARGET
+       depends on SCSI && PROC_FS && SCSI_TARGET
+       ---help---
+         SCSI TARGET handler for virtual user space device.
+
 config SCSI_TARGET_EXTRACHECKS
        bool "Extrachecks support"
        ---help---
index 39e3ccb..dc45c29 100644 (file)
@@ -15,4 +15,5 @@ obj-$(CONFIG_SCSI_TARGET_MODISK)      += scst_modisk.o
 obj-$(CONFIG_SCSI_TARGET_CHANGER)      += scst_changer.o
 obj-$(CONFIG_SCSI_TARGET_RAID)         += scst_raid.o
 obj-$(CONFIG_SCSI_TARGET_PROCESSOR)    += scst_processor.o
-obj-$(CONFIG_SCSI_TARGET_VDISK )       += scst_vdisk.o
+obj-$(CONFIG_SCSI_TARGET_VDISK)                += scst_vdisk.o
+obj-$(CONFIG_SCSI_TARGET_USER)         += scst_user.o
index 8b3a918..73bfe8e 100644 (file)
@@ -70,6 +70,7 @@ install: all
        install -d $(INSTALL_DIR_H)
        install -m 644 ../include/scsi_tgt.h $(INSTALL_DIR_H)
        install -m 644 ../include/scst_debug.h $(INSTALL_DIR_H)
+       install -m 644 ../include/scst_user.h $(INSTALL_DIR_H)
        install -m 644 ../include/scst_const.h $(INSTALL_DIR_H)
 ifneq ($(MODS_VERS),)
        rm -f $(INSTALL_DIR_H)/Module.symvers
index 2293789..ac58e2c 100644 (file)
@@ -30,7 +30,7 @@ ifneq ($(KERNELRELEASE),)
 SCST_INC_DIR := $(SUBDIRS)/../include
 
 obj-m := scst_cdrom.o scst_changer.o scst_disk.o scst_modisk.o scst_tape.o \
-       scst_vdisk.o scst_raid.o scst_processor.o
+       scst_vdisk.o scst_raid.o scst_processor.o scst_user.o
 
 obj-$(CONFIG_SCSI_TARGET_DISK)    += scst_disk.o
 obj-$(CONFIG_SCSI_TARGET_TAPE)    += scst_tape.o
@@ -40,6 +40,7 @@ obj-$(CONFIG_SCSI_TARGET_CHANGER) += scst_changer.o
 obj-$(CONFIG_SCSI_TARGET_RAID)    += scst_raid.o
 obj-$(CONFIG_SCSI_TARGET_PROCESSOR) += scst_processor.o
 obj-$(CONFIG_SCSI_TARGET_VDISK)  += scst_vdisk.o
+obj-$(CONFIG_SCSI_TARGET_USER)  += scst_user.o
 
 else
 ifeq ($(KDIR),)
diff --git a/scst/src/dev_handlers/scst_user.c b/scst/src/dev_handlers/scst_user.c
new file mode 100644 (file)
index 0000000..54be32c
--- /dev/null
@@ -0,0 +1,3039 @@
+/*
+ *  scst_user.c
+ *  
+ *  Copyright (C) 2007 Vladislav Bolkhovitin <vst@vlnb.net>
+ *
+ *  SCSI virtual user space device handler
+ *  
+ *  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/kthread.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+
+#define LOG_PREFIX             DEV_USER_NAME
+
+#include "scsi_tgt.h"
+#include "scst_user.h"
+#include "scst_dev_handler.h"
+
+#ifndef CONFIG_NOHIGHMEM
+#warning HIGHMEM kernel configurations are not supported. Consider \
+       changing VMSPLIT option or using 64-bit configuration.
+#endif
+
+#if defined(DEBUG) && defined(CONFIG_DEBUG_SLAB)
+#define DEV_USER_SLAB_FLAGS ( SLAB_RED_ZONE | SLAB_POISON )
+#else
+#define DEV_USER_SLAB_FLAGS 0L
+#endif
+
+#define DEV_USER_MAJOR                 237
+#define DEV_USER_CMD_HASH_ORDER                6
+#define DEV_USER_TM_TIMEOUT            (10*HZ)
+#define DEV_USER_ATTACH_TIMEOUT                (5*HZ)
+#define DEV_USER_DETACH_TIMEOUT                (5*HZ)
+#define DEV_USER_PRE_UNREG_POLL_TIME   (HZ/10)
+
+struct scst_user_dev
+{
+       struct rw_semaphore dev_rwsem;
+
+       struct scst_cmd_lists cmd_lists;
+       /* All 3 protected by cmd_lists.cmd_list_lock */
+       struct list_head ready_cmd_list;
+       struct list_head prio_ready_cmd_list;
+       wait_queue_head_t prio_cmd_list_waitQ;
+
+       /* All, including detach_cmd_count, protected by cmd_lists.cmd_list_lock */
+       unsigned short blocking:1;
+       unsigned short cleaning:1;
+       unsigned short cleanup_done:1;
+       unsigned short attach_cmd_active:1;
+       unsigned short tm_cmd_active:1;
+       unsigned short internal_reset_active:1;
+       unsigned short pre_unreg_sess_active:1; /* just a small optimization */
+
+       unsigned short detach_cmd_count;
+
+       int (*generic_parse)(struct scst_cmd *cmd, struct scst_info_cdb *info_cdb,
+               int (*get_block)(struct scst_cmd *cmd));
+
+       int block;
+       int def_block;
+
+       struct sgv_pool *pool;
+
+       uint8_t parse_type;
+       uint8_t on_free_cmd_type;
+       uint8_t memory_reuse_type;
+       uint8_t prio_queue_type;
+       uint8_t partial_transfers_type;
+       uint32_t partial_len;
+
+       struct scst_dev_type devtype;
+
+       /* Both protected by cmd_lists.cmd_list_lock */
+       unsigned int handle_counter;
+       struct list_head ucmd_hash[1<<DEV_USER_CMD_HASH_ORDER];
+
+       int virt_id;
+       struct list_head dev_list_entry;
+       char name[SCST_MAX_NAME];
+
+       /* Protected by cmd_lists.cmd_list_lock */
+       struct list_head pre_unreg_sess_list;
+
+       struct list_head cleanup_list_entry;
+       struct completion cleanup_cmpl;
+};
+
+struct dev_user_pre_unreg_sess_obj
+{
+       struct scst_tgt_dev *tgt_dev;
+       unsigned int active:1;
+       unsigned int exit:1;
+       struct list_head pre_unreg_sess_list_entry;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+       struct work_struct pre_unreg_sess_work;
+#else
+       struct delayed_work pre_unreg_sess_work;
+#endif
+};
+
+/* Most fields are unprotected, since only one thread at time can access them */
+struct dev_user_cmd
+{
+       struct scst_cmd *cmd;
+       struct scst_user_dev *dev;
+       
+       atomic_t ucmd_ref;
+
+       unsigned int buff_cached:1;
+       unsigned int buf_dirty:1;
+       unsigned int background_exec:1;
+       unsigned int internal_reset_tm:1;
+
+       struct dev_user_cmd *buf_ucmd;
+
+       int cur_data_page;
+       int num_data_pages;
+       int first_page_offset;
+       unsigned long ubuff;
+       struct page **data_pages;
+       struct sgv_pool_obj *sgv;
+
+       unsigned int state;
+
+       struct list_head ready_cmd_list_entry;
+
+       unsigned int h;
+       struct list_head hash_list_entry;
+
+       struct scst_user_get_cmd user_cmd;
+
+       struct completion *cmpl;
+       int result;
+};
+
+static struct dev_user_cmd *dev_user_alloc_ucmd(struct scst_user_dev *dev,
+       int gfp_mask);
+static void dev_user_free_ucmd(struct dev_user_cmd *ucmd);
+
+static int dev_user_parse(struct scst_cmd *cmd, struct scst_info_cdb *info_cdb);
+static int dev_user_exec(struct scst_cmd *cmd);
+static void dev_user_on_free_cmd(struct scst_cmd *cmd);
+static int dev_user_task_mgmt_fn(struct scst_mgmt_cmd *mcmd, 
+       struct scst_tgt_dev *tgt_dev);
+
+static int dev_user_disk_done(struct scst_cmd *cmd);
+static int dev_user_tape_done(struct scst_cmd *cmd);
+
+static struct page *dev_user_alloc_pages(struct scatterlist *sg,
+       gfp_t gfp_mask, void *priv);
+static void dev_user_free_sg_entries(struct scatterlist *sg, int sg_count,
+        void *priv);
+
+static void dev_user_add_to_ready(struct dev_user_cmd *ucmd);
+
+static void dev_user_unjam_cmd(struct dev_user_cmd *ucmd, int busy,
+       unsigned long *flags);
+static void dev_user_unjam_dev(struct scst_user_dev *dev, int tm,
+       struct scst_tgt_dev *tgt_dev);
+
+static int dev_user_process_reply_tm_exec(struct dev_user_cmd *ucmd,
+       int status);
+static int dev_user_process_reply_sess(struct dev_user_cmd *ucmd, int status);
+static int dev_user_register_dev(struct file *file,
+       const struct scst_user_dev_desc *dev_desc);
+static int __dev_user_set_opt(struct scst_user_dev *dev,
+       const struct scst_user_opt *opt);
+static int dev_user_set_opt(struct file *file, const struct scst_user_opt *opt);
+static int dev_user_get_opt(struct file *file, void *arg);
+
+static unsigned int dev_user_poll(struct file *filp, poll_table *wait);
+static long dev_user_ioctl(struct file *file, unsigned int cmd,
+       unsigned long arg);
+static int dev_user_release(struct inode *inode, struct file *file);
+
+/** Data **/
+
+static struct kmem_cache *user_cmd_cachep;
+
+static DEFINE_MUTEX(dev_user_mutex);
+
+static struct file_operations dev_user_fops = {
+       .poll           = dev_user_poll,
+       .unlocked_ioctl = dev_user_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl   = dev_user_ioctl,
+#endif
+       .release        = dev_user_release,
+};
+
+static struct class *dev_user_sysfs_class;
+
+static LIST_HEAD(dev_list);
+
+static spinlock_t cleanup_lock = SPIN_LOCK_UNLOCKED;
+static LIST_HEAD(cleanup_list);
+static DECLARE_WAIT_QUEUE_HEAD(cleanup_list_waitQ);
+static struct task_struct *cleanup_thread;
+
+static inline void ucmd_get(struct dev_user_cmd *ucmd, int barrier)
+{
+       TRACE_DBG("ucmd %p, ucmd_ref %d", ucmd, atomic_read(&ucmd->ucmd_ref));
+       atomic_inc(&ucmd->ucmd_ref);
+       if (barrier)
+               smp_mb__after_atomic_inc();
+}
+
+static inline void ucmd_put(struct dev_user_cmd *ucmd)
+{
+       TRACE_DBG("ucmd %p, ucmd_ref %d", ucmd, atomic_read(&ucmd->ucmd_ref));
+       if (atomic_dec_and_test(&ucmd->ucmd_ref))
+               dev_user_free_ucmd(ucmd);
+}
+
+static inline int calc_num_pg(unsigned long buf, int len)
+{
+       len += buf & ~PAGE_MASK;
+       return (len >> PAGE_SHIFT) + ((len & ~PAGE_MASK) != 0);
+}
+
+static inline int is_need_offs_page(unsigned long buf, int len)
+{
+       return ((buf & ~PAGE_MASK) != 0) && 
+               ((buf & PAGE_MASK) != ((buf+len-1) & PAGE_MASK));
+}
+
+static void __dev_user_not_reg(void)
+{
+       PRINT_ERROR_PR("%s", "Device not registered");
+       return;
+}
+
+static inline int dev_user_check_reg(struct scst_user_dev *dev)
+{
+       if (dev == NULL) {
+               __dev_user_not_reg();
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static inline int dev_user_cmd_hashfn(int h)
+{
+       return h & ((1 << DEV_USER_CMD_HASH_ORDER) - 1);
+}
+
+static inline struct dev_user_cmd *__ucmd_find_hash(struct scst_user_dev *dev,
+       unsigned int h)
+{
+       struct list_head *head;
+       struct dev_user_cmd *ucmd;
+
+       head = &dev->ucmd_hash[dev_user_cmd_hashfn(h)];
+       list_for_each_entry(ucmd, head, hash_list_entry) {
+               if (ucmd->h == h) {
+                       TRACE_DBG("Found ucmd %p", ucmd);
+                       return ucmd;
+               }
+       }
+       return NULL;
+}
+
+static void cmnd_insert_hash(struct dev_user_cmd *ucmd)
+{
+       struct list_head *head;
+       struct scst_user_dev *dev = ucmd->dev;
+       struct dev_user_cmd *u;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->cmd_lists.cmd_list_lock, flags);
+       do {
+               ucmd->h = dev->handle_counter++;
+               u = __ucmd_find_hash(dev, ucmd->h);
+       } while(u != NULL);
+       head = &dev->ucmd_hash[dev_user_cmd_hashfn(ucmd->h)];
+       list_add_tail(&ucmd->hash_list_entry, head);
+       spin_unlock_irqrestore(&dev->cmd_lists.cmd_list_lock, flags);
+
+       TRACE_DBG("Inserted ucmd %p, h=%d", ucmd, ucmd->h);
+       return;
+}
+
+static inline void cmnd_remove_hash(struct dev_user_cmd *ucmd)
+{
+       unsigned long flags;
+       spin_lock_irqsave(&ucmd->dev->cmd_lists.cmd_list_lock, flags);
+       list_del(&ucmd->hash_list_entry);
+       spin_unlock_irqrestore(&ucmd->dev->cmd_lists.cmd_list_lock, flags);
+
+       TRACE_DBG("Removed ucmd %p, h=%d", ucmd, ucmd->h);
+       return;
+}
+
+static void dev_user_free_ucmd(struct dev_user_cmd *ucmd)
+{
+       TRACE_ENTRY();
+
+       TRACE_MEM("Freeing ucmd %p", ucmd);
+
+       cmnd_remove_hash(ucmd);
+       EXTRACHECKS_BUG_ON(ucmd->cmd != NULL);
+
+       kmem_cache_free(user_cmd_cachep, ucmd);
+
+       TRACE_EXIT();
+       return;
+}
+
+static struct page *dev_user_alloc_pages(struct scatterlist *sg,
+       gfp_t gfp_mask, void *priv)
+{
+       struct dev_user_cmd *ucmd = (struct dev_user_cmd*)priv;
+
+       TRACE_ENTRY();
+
+       /* *sg supposed to be zeroed */
+
+       TRACE_MEM("ucmd %p, ubuff %lx, ucmd->cur_data_page %d", ucmd,
+               ucmd->ubuff, ucmd->cur_data_page);
+
+       if (ucmd->cur_data_page == 0) {
+               TRACE_MEM("ucmd->first_page_offset %d",
+                       ucmd->first_page_offset);
+               sg->offset = ucmd->first_page_offset;
+               ucmd_get(ucmd, 0);
+       }
+
+       if (ucmd->cur_data_page >= ucmd->num_data_pages)
+               goto out;
+
+       sg->page = ucmd->data_pages[ucmd->cur_data_page];
+       sg->length = PAGE_SIZE - sg->offset;
+
+       ucmd->cur_data_page++;
+
+       TRACE_MEM("page=%p, length=%d", sg->page, sg->length);
+       TRACE_BUFFER("Page data", page_address(sg->page), sg->length);
+
+out:
+       TRACE_EXIT();
+       return sg->page;
+}
+
+static void dev_user_on_cached_mem_free(struct dev_user_cmd *ucmd)
+{
+       TRACE_ENTRY();
+
+       TRACE_DBG("Preparing ON_CACHED_MEM_FREE (ucmd %p, h %d, ubuff %lx)",
+               ucmd, ucmd->h, ucmd->ubuff);
+
+       ucmd->user_cmd.cmd_h = ucmd->h;
+       ucmd->user_cmd.subcode = SCST_USER_ON_CACHED_MEM_FREE;
+       ucmd->user_cmd.on_cached_mem_free.pbuf = ucmd->ubuff;
+
+       ucmd->state = UCMD_STATE_ON_CACHE_FREEING;
+
+       dev_user_add_to_ready(ucmd);
+
+       TRACE_EXIT();
+       return;
+}
+
+static void dev_user_unmap_buf(struct dev_user_cmd *ucmd)
+{
+       int i;
+
+       TRACE_ENTRY();
+
+       TRACE_MEM("Unmapping data pages (ucmd %p, ubuff %lx, num %d)", ucmd,
+               ucmd->ubuff, ucmd->num_data_pages);
+
+       for(i = 0; i < ucmd->num_data_pages; i++) {
+               struct page *page = ucmd->data_pages[i];
+
+               if (ucmd->buf_dirty)
+                       SetPageDirty(page);
+
+               page_cache_release(page);
+       }
+       kfree(ucmd->data_pages);
+       ucmd->data_pages = NULL;
+
+       TRACE_EXIT();
+       return;
+}
+
+static void dev_user_free_sg_entries(struct scatterlist *sg, int sg_count,
+       void *priv)
+{
+       struct dev_user_cmd *ucmd = (struct dev_user_cmd*)priv;
+
+       TRACE_ENTRY();
+
+       sBUG_ON(ucmd->data_pages == NULL);
+
+       TRACE_MEM("Freeing data pages (ucmd=%p, ubuff=%lx, sg=%p, sg_count=%d, "
+               "buff_cached=%d)", ucmd, ucmd->ubuff, sg, sg_count,
+               ucmd->buff_cached);
+
+       dev_user_unmap_buf(ucmd);
+
+       if (ucmd->buff_cached)
+               dev_user_on_cached_mem_free(ucmd);
+       else
+               ucmd_put(ucmd);
+
+       TRACE_EXIT();
+       return;
+}
+
+static inline int is_buff_cached(struct dev_user_cmd *ucmd)
+{
+       int mem_reuse_type = ucmd->dev->memory_reuse_type;
+
+       if ((mem_reuse_type == SCST_USER_MEM_REUSE_ALL) ||
+           ((ucmd->cmd->data_direction == SCST_DATA_READ) &&
+            (mem_reuse_type == SCST_USER_MEM_REUSE_READ)) ||
+           ((ucmd->cmd->data_direction == SCST_DATA_WRITE) &&
+            (mem_reuse_type == SCST_USER_MEM_REUSE_WRITE))) {
+               return 1;
+       } else
+               return 0;
+}
+
+/*
+ * Returns 0 for success, <0 for fatal failure, >0 - need pages.
+ * Unmaps the buffer, if needed in case of error
+ */
+static int dev_user_alloc_sg(struct dev_user_cmd *ucmd, int cached_buff)
+{
+       int res = 0;
+       struct scst_cmd *cmd = ucmd->cmd;
+       struct scst_user_dev *dev = ucmd->dev;
+       int gfp_mask, flags = 0;
+       int bufflen = cmd->bufflen;
+       int last_len = 0;
+
+       TRACE_ENTRY();
+
+       gfp_mask = __GFP_NOWARN;
+       gfp_mask |= (scst_cmd_atomic(cmd) ? GFP_ATOMIC : GFP_KERNEL);
+
+       if (cached_buff) {
+               flags |= SCST_POOL_RETURN_OBJ_ON_ALLOC_FAIL;
+               if (ucmd->ubuff == 0)
+                       flags |= SCST_POOL_NO_ALLOC_ON_CACHE_MISS;
+       } else {
+               TRACE_MEM("%s", "Not cached buff");
+               flags |= SCST_POOL_ALLOC_NO_CACHED;
+               if (ucmd->ubuff == 0) {
+                       res = 1;
+                       goto out;
+               }
+               bufflen += ucmd->first_page_offset;
+               if (is_need_offs_page(ucmd->ubuff, cmd->bufflen))
+                       last_len = bufflen & ~PAGE_MASK;
+               else
+                       last_len = cmd->bufflen & ~PAGE_MASK;
+               if (last_len == 0)
+                       last_len = PAGE_SIZE;
+       }
+       ucmd->buff_cached = cached_buff;
+
+       cmd->sg = sgv_pool_alloc(dev->pool, bufflen, gfp_mask, flags,
+                       &cmd->sg_cnt, &ucmd->sgv, ucmd);
+       if (cmd->sg != NULL) {
+               struct dev_user_cmd *buf_ucmd =
+                       (struct dev_user_cmd*)sgv_get_priv(ucmd->sgv);
+
+               TRACE_MEM("Buf ucmd %p", buf_ucmd);
+
+               ucmd->ubuff = buf_ucmd->ubuff;
+               ucmd->buf_ucmd = buf_ucmd;
+
+               TRACE_MEM("Buf alloced (ucmd %p, cached_buff %d, ubuff %lx, "
+                       "last_len %d, l %d)", ucmd, cached_buff, ucmd->ubuff,
+                       last_len, cmd->sg[cmd->sg_cnt-1].length);
+
+               EXTRACHECKS_BUG_ON((ucmd->data_pages != NULL) &&
+                                  (ucmd != buf_ucmd));
+
+               if (last_len != 0) {
+                       /* We don't use clustering, so the assignment is safe */
+                       cmd->sg[cmd->sg_cnt-1].length = last_len;
+               }
+
+               if (unlikely(cmd->sg_cnt > cmd->tgt_dev->max_sg_cnt)) {
+                       static int ll;
+                       if (ll < 10) {
+                               PRINT_INFO("Unable to complete command due to "
+                                       "SG IO count limitation (requested %d, "
+                                       "available %d, tgt lim %d)", cmd->sg_cnt,
+                                       cmd->tgt_dev->max_sg_cnt,
+                                       cmd->tgt->sg_tablesize);
+                               ll++;
+                       }
+                       sgv_pool_free(ucmd->sgv);
+                       ucmd->sgv = NULL;
+                       cmd->sg = NULL;
+                       res = -1;
+               }
+       } else {
+               TRACE_MEM("Buf not alloced (ucmd %p, h %d, buff_cached, %d, "
+                       "sg_cnt %d, ubuff %lx, sgv %p", ucmd, ucmd->h,
+                       ucmd->buff_cached, cmd->sg_cnt, ucmd->ubuff, ucmd->sgv);
+               if (unlikely(cmd->sg_cnt == 0)) {
+                       res = -1;
+                       if (ucmd->data_pages != NULL)
+                               dev_user_unmap_buf(ucmd);
+               } else {
+                       switch(ucmd->state & ~UCMD_STATE_MASK) {
+                       case UCMD_STATE_BUF_ALLOCING:
+                               res = 1;
+                               break;
+                       case UCMD_STATE_EXECING:
+                               res = -1;
+                               if (ucmd->data_pages != NULL)
+                                       dev_user_unmap_buf(ucmd);
+                               break;
+                       default:
+                               sBUG();
+                               break;
+                       }
+               }
+       }
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_alloc_space(struct dev_user_cmd *ucmd)
+{
+       int rc, res = SCST_CMD_STATE_DEFAULT;
+       struct scst_cmd *cmd = ucmd->cmd;
+
+       TRACE_ENTRY();
+
+       ucmd->state = UCMD_STATE_BUF_ALLOCING;
+       cmd->data_buf_alloced = 1;
+
+       rc = dev_user_alloc_sg(ucmd, is_buff_cached(ucmd));
+       if (rc == 0)
+               goto out;
+       else if (rc < 0) {
+               scst_set_busy(cmd);
+               res = SCST_CMD_STATE_XMIT_RESP;
+               goto out;
+       }
+
+       if ((cmd->data_direction != SCST_DATA_WRITE) &&
+           !scst_is_cmd_local(cmd)) {
+               TRACE_DBG("Delayed alloc, ucmd %p", ucmd);
+               goto out;
+       }
+
+       ucmd->user_cmd.cmd_h = ucmd->h;
+       ucmd->user_cmd.subcode = SCST_USER_ALLOC_MEM;
+       ucmd->user_cmd.alloc_cmd.sess_h = (unsigned long)cmd->tgt_dev;
+       memcpy(ucmd->user_cmd.alloc_cmd.cdb, cmd->cdb, 
+               min(sizeof(ucmd->user_cmd.alloc_cmd.cdb), sizeof(cmd->cdb)));
+       ucmd->user_cmd.alloc_cmd.cdb_len = cmd->cdb_len;
+       ucmd->user_cmd.alloc_cmd.alloc_len = ucmd->buff_cached ? 
+               (cmd->sg_cnt << PAGE_SHIFT) : cmd->bufflen;
+       ucmd->user_cmd.alloc_cmd.queue_type = cmd->queue_type;
+       ucmd->user_cmd.alloc_cmd.data_direction = cmd->data_direction;
+
+       dev_user_add_to_ready(ucmd);
+
+       res = SCST_CMD_STATE_STOP;
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static struct dev_user_cmd *dev_user_alloc_ucmd(struct scst_user_dev *dev,
+       int gfp_mask)
+{
+       struct dev_user_cmd *ucmd = NULL;
+
+       TRACE_ENTRY();
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17)
+       ucmd = kmem_cache_alloc(user_cmd_cachep, gfp_mask);
+       if (ucmd != NULL)
+               memset(ucmd, 0, sizeof(*ucmd));
+#else
+       ucmd = kmem_cache_zalloc(user_cmd_cachep, gfp_mask);
+#endif
+       if (unlikely(ucmd == NULL)) {
+               TRACE(TRACE_OUT_OF_MEM, "Unable to allocate "
+                       "user cmd (gfp_mask %x)", gfp_mask);
+               goto out;
+       }
+       ucmd->dev = dev;
+       atomic_set(&ucmd->ucmd_ref, 1);
+
+       cmnd_insert_hash(ucmd);
+
+       TRACE_MEM("ucmd %p allocated", ucmd);
+
+out:
+       TRACE_EXIT_HRES((unsigned long)ucmd);
+       return ucmd;
+}
+
+static int dev_user_get_block(struct scst_cmd *cmd)
+{
+       struct scst_user_dev *dev = (struct scst_user_dev*)cmd->dev->dh_priv;
+       /* 
+        * No need for locks here, since *_detach() can not be
+        * called, when there are existing commands.
+        */
+       TRACE_EXIT_RES(dev->block);
+       return dev->block;
+}
+
+static int dev_user_parse(struct scst_cmd *cmd, struct scst_info_cdb *info_cdb)
+{
+       int rc, res = SCST_CMD_STATE_DEFAULT;
+       struct dev_user_cmd *ucmd;
+       int atomic = scst_cmd_atomic(cmd);
+       struct scst_user_dev *dev = (struct scst_user_dev*)cmd->dev->dh_priv;
+       int gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL;
+
+       TRACE_ENTRY();
+
+       if (cmd->dh_priv == NULL) {
+               ucmd = dev_user_alloc_ucmd(dev, gfp_mask);
+               if (unlikely(ucmd == NULL)) {
+                       if (atomic) {
+                               res = SCST_CMD_STATE_NEED_THREAD_CTX;
+                               goto out;
+                       } else {
+                               scst_set_busy(cmd);
+                               goto out_error;
+                       }
+               }
+               ucmd->cmd = cmd;
+               cmd->dh_priv = ucmd;
+       } else {
+               ucmd = (struct dev_user_cmd*)cmd->dh_priv;
+               TRACE_DBG("Used ucmd %p, state %x", ucmd, ucmd->state);
+       }
+
+       TRACE_DBG("ucmd %p, cmd %p, state %x", ucmd, cmd, ucmd->state);
+
+       if (ucmd->state != UCMD_STATE_NEW)
+               goto alloc;
+
+       switch(dev->parse_type) {
+       case SCST_USER_PARSE_STANDARD:
+               TRACE_DBG("PARSE STANDARD: ucmd %p", ucmd);
+               rc = dev->generic_parse(cmd, info_cdb, dev_user_get_block);
+               if ((rc != 0) || (info_cdb->flags & SCST_INFO_INVALID))
+                       goto out_invalid;
+               ucmd->cmd->skip_parse = 1;
+               break;
+
+       case SCST_USER_PARSE_EXCEPTION:
+               TRACE_DBG("PARSE EXCEPTION: ucmd %p", ucmd);
+               rc = dev->generic_parse(cmd, info_cdb, dev_user_get_block);
+               if ((rc == 0) && (!(info_cdb->flags & SCST_INFO_INVALID))) {
+                       ucmd->cmd->skip_parse = 1;
+                       break;
+               } else if (rc == SCST_CMD_STATE_NEED_THREAD_CTX) {
+                       TRACE_MEM("Restarting PARSE to thread context "
+                               "(ucmd %p)", ucmd);
+                       res = SCST_CMD_STATE_NEED_THREAD_CTX;
+                       goto out;
+               }
+               /* else go through */
+
+       case SCST_USER_PARSE_CALL:
+               TRACE_DBG("Preparing PARSE for user space (ucmd=%p, h=%d, "
+                       "bufflen %d)", ucmd, ucmd->h, cmd->bufflen);
+               ucmd->cmd->skip_parse = 1;
+               ucmd->user_cmd.cmd_h = ucmd->h;
+               ucmd->user_cmd.subcode = SCST_USER_PARSE;
+               ucmd->user_cmd.parse_cmd.sess_h = (unsigned long)cmd->tgt_dev;
+               memcpy(ucmd->user_cmd.parse_cmd.cdb, cmd->cdb, 
+                       min(sizeof(ucmd->user_cmd.parse_cmd.cdb),
+                           sizeof(cmd->cdb)));
+               ucmd->user_cmd.parse_cmd.cdb_len = cmd->cdb_len;
+               ucmd->user_cmd.parse_cmd.timeout = cmd->timeout;
+               ucmd->user_cmd.parse_cmd.bufflen = cmd->bufflen;
+               ucmd->user_cmd.parse_cmd.queue_type = cmd->queue_type;
+               ucmd->user_cmd.parse_cmd.data_direction = cmd->data_direction;
+               ucmd->user_cmd.parse_cmd.expected_values_set =
+                                       cmd->expected_values_set;
+               ucmd->user_cmd.parse_cmd.expected_data_direction =
+                                       cmd->expected_data_direction;
+               ucmd->user_cmd.parse_cmd.expected_transfer_len =
+                                       cmd->expected_transfer_len;
+               ucmd->state = UCMD_STATE_PARSING;
+               dev_user_add_to_ready(ucmd);
+               res = SCST_CMD_STATE_STOP;
+               goto out;
+
+       default:
+               sBUG();
+               goto out;
+       }
+
+alloc:
+       if (cmd->data_direction != SCST_DATA_NONE)
+               res = dev_user_alloc_space(ucmd);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_invalid:
+       PRINT_ERROR_PR("PARSE failed (ucmd %p, rc %d, invalid %d)", ucmd, rc,
+               info_cdb->flags & SCST_INFO_INVALID);
+       scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+
+out_error:
+       res = SCST_CMD_STATE_XMIT_RESP;
+       goto out;
+}
+
+static void dev_user_flush_dcache(struct dev_user_cmd *ucmd)
+{
+       struct dev_user_cmd *buf_ucmd = ucmd->buf_ucmd;
+       unsigned long start = buf_ucmd->ubuff;
+       int i;
+
+       TRACE_ENTRY();
+
+       if (start == 0)
+               goto out;
+
+       for(i = 0; i < buf_ucmd->num_data_pages; i++) {
+               struct page *page;
+               page = buf_ucmd->data_pages[i];
+#ifdef ARCH_HAS_FLUSH_ANON_PAGE
+               struct vm_area_struct *vma = find_vma(current->mm, start);
+               if (vma != NULL) 
+                       flush_anon_page(vma, page, start);
+#endif
+               flush_dcache_page(page);
+               start += PAGE_SIZE;
+       }
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static int dev_user_exec(struct scst_cmd *cmd)
+{
+       struct dev_user_cmd *ucmd = (struct dev_user_cmd*)cmd->dh_priv;
+
+       TRACE_ENTRY();
+
+       TRACE_DBG("Preparing EXEC for user space (ucmd=%p, h=%d, "
+               "bufflen %d, data_len %d, ubuff %lx)", ucmd, ucmd->h,
+               cmd->bufflen, cmd->data_len, ucmd->ubuff);
+
+       if (cmd->data_direction == SCST_DATA_WRITE)
+               dev_user_flush_dcache(ucmd);
+
+       ucmd->user_cmd.cmd_h = ucmd->h;
+       ucmd->user_cmd.subcode = SCST_USER_EXEC;
+       ucmd->user_cmd.exec_cmd.sess_h = (unsigned long)cmd->tgt_dev;
+       memcpy(ucmd->user_cmd.exec_cmd.cdb, cmd->cdb, 
+               min(sizeof(ucmd->user_cmd.exec_cmd.cdb),
+                   sizeof(cmd->cdb)));
+       ucmd->user_cmd.exec_cmd.cdb_len = cmd->cdb_len;
+       ucmd->user_cmd.exec_cmd.bufflen = cmd->bufflen;
+       ucmd->user_cmd.exec_cmd.data_len = cmd->data_len;
+       ucmd->user_cmd.exec_cmd.pbuf = ucmd->ubuff;
+       if ((ucmd->ubuff == 0) && (cmd->data_direction != SCST_DATA_NONE)) {
+               ucmd->user_cmd.exec_cmd.alloc_len = ucmd->buff_cached ? 
+                       (cmd->sg_cnt << PAGE_SHIFT) : cmd->bufflen;
+       }
+       ucmd->user_cmd.exec_cmd.queue_type = cmd->queue_type;
+       ucmd->user_cmd.exec_cmd.data_direction = cmd->data_direction;
+       ucmd->user_cmd.exec_cmd.partial = 0;
+       ucmd->user_cmd.exec_cmd.timeout = cmd->timeout;
+
+       ucmd->state = UCMD_STATE_EXECING;
+
+       dev_user_add_to_ready(ucmd);
+
+       TRACE_EXIT();
+       return SCST_EXEC_COMPLETED;
+}
+
+static void dev_user_free_sgv(struct dev_user_cmd *ucmd)
+{
+       if (ucmd->sgv != NULL) {
+               sgv_pool_free(ucmd->sgv);
+               ucmd->sgv = NULL;
+       }
+}
+
+static void dev_user_on_free_cmd(struct scst_cmd *cmd)
+{
+       struct dev_user_cmd *ucmd = (struct dev_user_cmd*)cmd->dh_priv;
+
+       TRACE_ENTRY();
+
+       if (unlikely(ucmd == NULL))
+               goto out;
+
+       TRACE_DBG("ucmd %p, cmd %p, buff_cached %d, ubuff %lx", ucmd, ucmd->cmd,
+               ucmd->buff_cached, ucmd->ubuff);
+
+       ucmd->cmd = NULL;
+       if ((cmd->data_direction == SCST_DATA_WRITE) && (ucmd->buf_ucmd != NULL))
+               ucmd->buf_ucmd->buf_dirty = 1;
+
+       if (ucmd->dev->on_free_cmd_type == SCST_USER_ON_FREE_CMD_IGNORE) {
+               ucmd->state = UCMD_STATE_ON_FREE_SKIPPED;
+               /* The state assignment must be before freeing sgv! */
+               dev_user_free_sgv(ucmd);
+               ucmd_put(ucmd);
+               goto out;
+       }
+
+       ucmd->user_cmd.cmd_h = ucmd->h;
+       ucmd->user_cmd.subcode = SCST_USER_ON_FREE_CMD;
+
+       ucmd->user_cmd.on_free_cmd.pbuf = ucmd->ubuff;
+       ucmd->user_cmd.on_free_cmd.resp_data_len = cmd->resp_data_len;
+       ucmd->user_cmd.on_free_cmd.buffer_cached = ucmd->buff_cached;
+       ucmd->user_cmd.on_free_cmd.status = cmd->status;
+
+       ucmd->state = UCMD_STATE_ON_FREEING;
+
+       dev_user_add_to_ready(ucmd);
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static void dev_user_set_block(struct scst_cmd *cmd, int block)
+{
+       struct scst_user_dev *dev = (struct scst_user_dev*)cmd->dev->dh_priv;
+       /* 
+        * No need for locks here, since *_detach() can not be
+        * called, when there are existing commands.
+        */
+       TRACE_DBG("dev %p, new block %d", dev, block);
+       if (block != 0)
+               dev->block = block;
+       else
+               dev->block = dev->def_block;
+       return;
+}
+
+static int dev_user_disk_done(struct scst_cmd *cmd)
+{
+       int res = SCST_CMD_STATE_DEFAULT;
+
+       TRACE_ENTRY();
+
+       res = scst_block_generic_dev_done(cmd, dev_user_set_block);
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_tape_done(struct scst_cmd *cmd)
+{
+       int res = SCST_CMD_STATE_DEFAULT;
+
+       TRACE_ENTRY();
+
+       res = scst_tape_generic_dev_done(cmd, dev_user_set_block);
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static void dev_user_add_to_ready(struct dev_user_cmd *ucmd)
+{
+       struct scst_user_dev *dev = ucmd->dev;
+       unsigned long flags;
+       int do_wake;
+
+       TRACE_ENTRY();
+
+       do_wake = (in_interrupt() || 
+                  (ucmd->state == UCMD_STATE_ON_CACHE_FREEING));
+       if (ucmd->cmd)
+               do_wake |= ucmd->cmd->preprocessing_only;
+
+       EXTRACHECKS_BUG_ON(ucmd->state & UCMD_STATE_JAMMED_MASK);
+
+       spin_lock_irqsave(&dev->cmd_lists.cmd_list_lock, flags);
+
+       /* Hopefully, compiler will make it as a single test/jmp */
+       if (unlikely(dev->attach_cmd_active || dev->tm_cmd_active ||
+                    dev->internal_reset_active || dev->pre_unreg_sess_active ||
+                    (dev->detach_cmd_count != 0))) {
+               switch(ucmd->state) {
+               case UCMD_STATE_PARSING:
+               case UCMD_STATE_BUF_ALLOCING:
+               case UCMD_STATE_EXECING:
+                       if (dev->pre_unreg_sess_active &&
+                           !(dev->attach_cmd_active || dev->tm_cmd_active ||
+                             dev->internal_reset_active ||
+                             (dev->detach_cmd_count != 0))) {
+                               struct dev_user_pre_unreg_sess_obj *p, *found = NULL;
+                               list_for_each_entry(p, &dev->pre_unreg_sess_list,
+                                       pre_unreg_sess_list_entry) {
+                                       if (p->tgt_dev == ucmd->cmd->tgt_dev) {
+                                               if (p->active)
+                                                       found = p;
+                                               break;
+                                       }
+                               }
+                               if (found == NULL) {
+                                       TRACE_MGMT_DBG("No pre unreg sess "
+                                               "active (ucmd %p)", ucmd);
+                                       break;
+                               } else {
+                                       TRACE(TRACE_MGMT, "Pre unreg sess %p "
+                                               "active (ucmd %p)", found, ucmd);
+                               }
+                       }
+                       TRACE(TRACE_MGMT, "Mgmt cmd active, returning BUSY for "
+                               "ucmd %p", ucmd);
+                       dev_user_unjam_cmd(ucmd, 1, &flags);
+                       spin_unlock_irqrestore(&dev->cmd_lists.cmd_list_lock, flags);
+                       goto out;
+               }
+       }
+
+       if (unlikely(ucmd->state == UCMD_STATE_TM_EXECING) ||
+           unlikely(ucmd->state == UCMD_STATE_ATTACH_SESS) ||
+           unlikely(ucmd->state == UCMD_STATE_DETACH_SESS)) {
+               if (dev->prio_queue_type == SCST_USER_PRIO_QUEUE_SEPARATE) {
+                       TRACE_MGMT_DBG("Adding mgmt ucmd %p to prio ready cmd "
+                               "list", ucmd);
+                       list_add_tail(&ucmd->ready_cmd_list_entry,
+                               &dev->prio_ready_cmd_list);
+                       wake_up(&dev->prio_cmd_list_waitQ);
+                       do_wake = 0;
+               } else {
+                       TRACE_MGMT_DBG("Adding mgmt ucmd %p to ready cmd "
+                               "list", ucmd);
+                       list_add_tail(&ucmd->ready_cmd_list_entry,
+                               &dev->ready_cmd_list);
+                       do_wake = 1;
+               }
+       } else if ((ucmd->cmd != NULL) &&
+           unlikely((ucmd->cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE))) {
+               TRACE_DBG("Adding ucmd %p to head ready cmd list", ucmd);
+               list_add(&ucmd->ready_cmd_list_entry, &dev->ready_cmd_list);
+       } else {
+               TRACE_DBG("Adding ucmd %p to ready cmd list", ucmd);
+               list_add_tail(&ucmd->ready_cmd_list_entry, &dev->ready_cmd_list);
+       }
+
+       if (do_wake) {
+               TRACE_DBG("Waking up dev %p", dev);
+               wake_up(&dev->cmd_lists.cmd_list_waitQ);
+       }
+
+       spin_unlock_irqrestore(&dev->cmd_lists.cmd_list_lock, flags);
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static int dev_user_map_buf(struct dev_user_cmd *ucmd, unsigned long ubuff,
+       int num_pg)
+{
+       int res = 0, rc;
+       int i;
+
+       TRACE_ENTRY();
+
+       if (unlikely(ubuff == 0))
+               goto out_nomem;
+
+       ucmd->num_data_pages = num_pg;
+
+       ucmd->data_pages = kzalloc(sizeof(*ucmd->data_pages)*ucmd->num_data_pages,
+               GFP_KERNEL);
+       if (ucmd->data_pages == NULL) {
+               TRACE(TRACE_OUT_OF_MEM, "Unable to allocate data_pages array "
+                       "(num_data_pages=%d)", ucmd->num_data_pages);
+               res = -ENOMEM;
+               goto out_nomem;
+       }
+
+       TRACE_MEM("Mapping buffer (ucmd %p, ubuff %lx, ucmd->num_data_pages %d, "
+               "first_page_offset %d, len %d)", ucmd, ubuff,
+               ucmd->num_data_pages, (int)(ubuff & ~PAGE_MASK),
+               ucmd->cmd->bufflen);
+
+       down_read(&current->mm->mmap_sem);
+       rc = get_user_pages(current, current->mm, ubuff, ucmd->num_data_pages,
+               1/*writable*/, 0/*don't force*/, ucmd->data_pages, NULL);
+       up_read(&current->mm->mmap_sem);
+
+       /* get_user_pages() flushes dcache */
+
+       if (rc < ucmd->num_data_pages)
+               goto out_unmap;
+
+       ucmd->ubuff = ubuff;
+       ucmd->first_page_offset = (ubuff & ~PAGE_MASK);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_nomem:
+       scst_set_busy(ucmd->cmd);
+       /* go through */
+
+out_err:
+       ucmd->cmd->state = SCST_CMD_STATE_XMIT_RESP;
+       goto out;
+
+out_unmap:
+       PRINT_ERROR_PR("Failed to get %d user pages (rc %d)",
+               ucmd->num_data_pages, rc);
+       if (rc > 0) {
+               for(i = 0; i < rc; i++)
+                       page_cache_release(ucmd->data_pages[i]);
+       }
+       kfree(ucmd->data_pages);
+       ucmd->data_pages = NULL;
+       res = -EFAULT;
+       scst_set_cmd_error(ucmd->cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+       goto out_err;
+}
+
+static int dev_user_process_reply_alloc(struct dev_user_cmd *ucmd,
+       struct scst_user_reply_cmd *reply)
+{
+       int res = 0;
+       struct scst_cmd *cmd = ucmd->cmd;
+
+       TRACE_ENTRY();
+
+       TRACE_DBG("ucmd %p, pbuf %Lx", ucmd, reply->alloc_reply.pbuf);
+
+       if (likely(reply->alloc_reply.pbuf != 0)) {
+               int pages;
+               if (ucmd->buff_cached) {
+                       if (unlikely((reply->alloc_reply.pbuf & ~PAGE_MASK) != 0)) {
+                               PRINT_ERROR_PR("Supplied pbuf %Lx isn't "
+                                       "page aligned", reply->alloc_reply.pbuf);
+                               goto out_hwerr;
+                       }
+                       pages = cmd->sg_cnt;
+               } else
+                       pages = calc_num_pg(reply->alloc_reply.pbuf, cmd->bufflen);
+               res = dev_user_map_buf(ucmd, reply->alloc_reply.pbuf, pages);
+       } else {
+               scst_set_busy(ucmd->cmd);
+               ucmd->cmd->state = SCST_CMD_STATE_XMIT_RESP;
+       }
+
+out_process:
+       scst_process_active_cmd(cmd, SCST_CONTEXT_DIRECT);
+
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_hwerr:
+       scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+       res = -EINVAL;
+       goto out_process;
+}
+
+static int dev_user_process_reply_parse(struct dev_user_cmd *ucmd,
+       struct scst_user_reply_cmd *reply)
+{
+       int res = 0;
+       struct scst_user_scsi_cmd_reply_parse *preply = 
+               &reply->parse_reply;
+       struct scst_cmd *cmd = ucmd->cmd;
+
+       TRACE_ENTRY();
+
+       if (unlikely(preply->queue_type > SCST_CMD_QUEUE_ACA))
+               goto out_inval;
+
+       if (unlikely((preply->data_direction != SCST_DATA_WRITE) &&
+                    (preply->data_direction != SCST_DATA_READ) &&
+                    (preply->data_direction != SCST_DATA_NONE)))
+               goto out_inval;
+
+       if (unlikely((preply->data_direction != SCST_DATA_NONE) &&
+                    (preply->bufflen == 0)))
+               goto out_inval;
+
+       if (unlikely((preply->bufflen < 0) || (preply->data_len < 0)))
+               goto out_inval;
+
+       TRACE_DBG("ucmd %p, queue_type %x, data_direction, %x, bufflen %d, "
+               "data_len %d, pbuf %Lx", ucmd, preply->queue_type,
+               preply->data_direction, preply->bufflen, preply->data_len,
+               reply->alloc_reply.pbuf);
+
+       cmd->queue_type = preply->queue_type;
+       cmd->data_direction = preply->data_direction;
+       cmd->bufflen = preply->bufflen;
+       cmd->data_len = preply->data_len;
+
+out_process:
+       scst_process_active_cmd(cmd, SCST_CONTEXT_DIRECT);
+
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_inval:
+       PRINT_ERROR_PR("%s", "Invalid parse_reply parameter(s)");
+       scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+       res = -EINVAL;
+       goto out_process;
+}
+
+static int dev_user_process_reply_on_free(struct dev_user_cmd *ucmd)
+{
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       TRACE_DBG("ON FREE ucmd %p", ucmd);
+
+       dev_user_free_sgv(ucmd);
+       ucmd_put(ucmd);
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_process_reply_on_cache_free(struct dev_user_cmd *ucmd)
+{
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       TRACE_DBG("ON CACHE FREE ucmd %p", ucmd);
+
+       ucmd_put(ucmd);
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_process_reply_exec(struct dev_user_cmd *ucmd,
+       struct scst_user_reply_cmd *reply)
+{
+       int res = 0;
+       struct scst_user_scsi_cmd_reply_exec *ereply =
+               &reply->exec_reply;
+       struct scst_cmd *cmd = ucmd->cmd;
+
+       TRACE_ENTRY();
+
+       if (ereply->reply_type == SCST_EXEC_REPLY_COMPLETED) {
+               if (ucmd->background_exec) {
+                       TRACE_DBG("Background ucmd %p finished", ucmd);
+                       ucmd_put(ucmd);
+                       goto out;
+               }
+               if (unlikely(ereply->resp_data_len > cmd->bufflen))
+                       goto out_inval;
+               if (unlikely((cmd->data_direction != SCST_DATA_READ) &&
+                            (ereply->resp_data_len != 0)))
+                       goto out_inval;
+       } else if (ereply->reply_type == SCST_EXEC_REPLY_BACKGROUND) {
+               if (unlikely(ucmd->background_exec))
+                       goto out_inval;
+               if (unlikely((cmd->data_direction == SCST_DATA_READ) ||
+                            (cmd->resp_data_len != 0)))
+                       goto out_inval;
+               ucmd_get(ucmd, 1);
+               ucmd->background_exec = 1;
+               TRACE_DBG("Background ucmd %p", ucmd);
+               goto out_compl;
+       } else
+               goto out_inval;
+
+       TRACE_DBG("ucmd %p, status %d, resp_data_len %d", ucmd,
+               ereply->status, ereply->resp_data_len);
+
+        if (ereply->resp_data_len != 0) {
+               if (ucmd->ubuff == 0) {
+                       int pages, rc;
+                       if (unlikely(ereply->pbuf == 0))
+                               goto out_busy;
+                       if (ucmd->buff_cached) {
+                               if (unlikely((ereply->pbuf & ~PAGE_MASK) != 0)) {
+                                       PRINT_ERROR_PR("Supplied pbuf %Lx isn't "
+                                               "page aligned", ereply->pbuf);
+                                       goto out_hwerr;
+                               }
+                               pages = cmd->sg_cnt;
+                       } else
+                               pages = calc_num_pg(ereply->pbuf, cmd->bufflen);
+                       rc = dev_user_map_buf(ucmd, ereply->pbuf, pages);
+                       if ((rc != 0) || (ucmd->ubuff == 0))
+                               goto out_compl;
+
+                       rc = dev_user_alloc_sg(ucmd, ucmd->buff_cached);
+                       if (unlikely(rc != 0))
+                               goto out_busy;
+               } else
+                       dev_user_flush_dcache(ucmd);
+               cmd->may_need_dma_sync = 1;
+               scst_set_resp_data_len(cmd, ereply->resp_data_len);
+       } else if (cmd->resp_data_len != ereply->resp_data_len) {
+               if (ucmd->ubuff == 0)
+                       cmd->resp_data_len = ereply->resp_data_len;
+               else
+                       scst_set_resp_data_len(cmd, ereply->resp_data_len);
+       }
+
+       cmd->status = ereply->status;
+       if (ereply->sense_len != 0) {
+               res = copy_from_user(cmd->sense_buffer,
+                       (void*)(unsigned long)ereply->psense_buffer,
+                       min(sizeof(cmd->sense_buffer),
+                               (unsigned int)ereply->sense_len));
+               if (res < 0) {
+                       PRINT_ERROR_PR("%s", "Unable to get sense data");
+                       goto out_hwerr_res_set;
+               }
+       }
+
+out_compl:
+       cmd->completed = 1;
+       cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_inval:
+       PRINT_ERROR_PR("%s", "Invalid exec_reply parameter(s)");
+
+out_hwerr:
+       res = -EINVAL;
+
+out_hwerr_res_set:
+       if (ucmd->background_exec) {
+               ucmd_put(ucmd);
+               goto out;
+       } else {
+               scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+               goto out_compl;
+       }
+
+out_busy:
+       scst_set_busy(cmd);
+       goto out_compl;
+}
+
+static int dev_user_process_reply(struct scst_user_dev *dev,
+       struct scst_user_reply_cmd *reply)
+{
+       int res = 0;
+       struct dev_user_cmd *ucmd;
+       int state;
+
+       TRACE_ENTRY();
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       ucmd = __ucmd_find_hash(dev, reply->cmd_h);
+       if (ucmd == NULL) {
+               TRACE_MGMT_DBG("cmd_h %d not found", reply->cmd_h);
+               res = -ESRCH;
+               goto out_unlock;
+       }
+
+       if (ucmd->background_exec) {
+               state = UCMD_STATE_EXECING;
+               goto unlock_process;
+       }
+
+       if (unlikely(!(ucmd->state & UCMD_STATE_SENT_MASK))) {
+               if (ucmd->state & UCMD_STATE_JAMMED_MASK) {
+                       TRACE_MGMT_DBG("Reply on jammed ucmd %p, ignoring",
+                               ucmd);
+               } else {
+                       TRACE_MGMT_DBG("Ucmd %p isn't in the sent to user "
+                               "state %x", ucmd, ucmd->state);
+                       res = -EBUSY;
+               }
+               goto out_unlock;
+       }
+
+       if (unlikely(reply->subcode != ucmd->user_cmd.subcode))
+               goto out_wrong_state;
+
+       if (unlikely(_IOC_NR(reply->subcode) !=
+                       (ucmd->state & ~UCMD_STATE_SENT_MASK)))
+               goto out_wrong_state;
+
+       ucmd->state &= ~UCMD_STATE_SENT_MASK;
+       state = ucmd->state;
+       ucmd->state |= UCMD_STATE_RECV_MASK;
+
+unlock_process:
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       switch(state) {
+       case UCMD_STATE_PARSING:
+               res = dev_user_process_reply_parse(ucmd, reply);
+               break;
+
+       case UCMD_STATE_BUF_ALLOCING:
+               res = dev_user_process_reply_alloc(ucmd, reply);
+               break;
+
+       case UCMD_STATE_EXECING:
+               res = dev_user_process_reply_exec(ucmd, reply);
+               break;
+       
+       case UCMD_STATE_ON_FREEING:
+               res = dev_user_process_reply_on_free(ucmd);
+               break;
+
+       case UCMD_STATE_ON_CACHE_FREEING:
+               res = dev_user_process_reply_on_cache_free(ucmd);
+               break;
+
+       case UCMD_STATE_TM_EXECING:
+               res = dev_user_process_reply_tm_exec(ucmd, reply->result);
+               break;
+
+       case UCMD_STATE_ATTACH_SESS:
+       case UCMD_STATE_DETACH_SESS:
+               res = dev_user_process_reply_sess(ucmd, reply->result);
+               break;
+
+       default:
+               sBUG();
+               break;
+       }
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_wrong_state:
+       PRINT_ERROR_PR("Command's %p subcode %x doesn't match internal "
+               "command's state %x or reply->subcode (%x) != ucmd->subcode "
+               "(%x)", ucmd, _IOC_NR(reply->subcode), ucmd->state,
+               reply->subcode, ucmd->user_cmd.subcode);
+       res = -EINVAL;
+       dev_user_unjam_cmd(ucmd, 0, NULL);
+
+out_unlock:
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+       goto out;
+}
+
+static int dev_user_reply_cmd(struct file *file, unsigned long arg)
+{
+       int res = 0;
+       struct scst_user_dev *dev;
+       struct scst_user_reply_cmd *reply;
+
+       TRACE_ENTRY();
+
+       mutex_lock(&dev_user_mutex);
+       dev = (struct scst_user_dev*)file->private_data;
+       res = dev_user_check_reg(dev);
+       if (res != 0) {
+               mutex_unlock(&dev_user_mutex);
+               goto out;
+       }
+       down_read(&dev->dev_rwsem);
+       mutex_unlock(&dev_user_mutex);
+
+       reply = kzalloc(sizeof(*reply), GFP_KERNEL);
+       if (reply == NULL) {
+               res = -ENOMEM;
+               goto out_up;
+       }
+
+       res = copy_from_user(reply, (void*)arg, sizeof(*reply));
+       if (res < 0)
+               goto out_free;
+
+       TRACE_BUFFER("Reply", reply, sizeof(*reply));
+
+       res = dev_user_process_reply(dev, reply);
+       if (res < 0)
+               goto out_free;
+
+out_free:
+       kfree(reply);
+
+out_up:
+       up_read(&dev->dev_rwsem);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_process_scst_commands(struct scst_user_dev *dev)
+{
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       while (!list_empty(&dev->cmd_lists.active_cmd_list)) {
+               struct scst_cmd *cmd = list_entry(
+                       dev->cmd_lists.active_cmd_list.next, typeof(*cmd),
+                       cmd_list_entry);
+               TRACE_DBG("Deleting cmd %p from active cmd list", cmd);
+               list_del(&cmd->cmd_list_entry);
+               spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+               scst_process_active_cmd(cmd, SCST_CONTEXT_DIRECT);
+               spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+               res++;
+       }
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+struct dev_user_cmd *__dev_user_get_next_cmd(struct list_head *cmd_list)
+{
+       struct dev_user_cmd *u = NULL;
+
+again:
+       if (!list_empty(cmd_list)) {
+               u = list_entry(cmd_list->next, typeof(*u), ready_cmd_list_entry);
+               TRACE_DBG("Found ready ucmd %p", u);
+               list_del(&u->ready_cmd_list_entry);
+               EXTRACHECKS_BUG_ON(u->state & UCMD_STATE_JAMMED_MASK);
+               if ((u->cmd != NULL) &&
+                    unlikely(test_bit(SCST_CMD_ABORTED,
+                               &u->cmd->cmd_flags))) {
+                       switch(u->state) {
+                       case UCMD_STATE_PARSING:
+                       case UCMD_STATE_BUF_ALLOCING:
+                       case UCMD_STATE_EXECING:
+                               TRACE_MGMT_DBG("Aborting ucmd %p", u);
+                               dev_user_unjam_cmd(u, 0, NULL);
+                               goto again;
+                       }
+               }
+               u->state |= UCMD_STATE_SENT_MASK;
+       }
+       return u;
+}
+
+static inline int test_cmd_lists(struct scst_user_dev *dev)
+{
+       int res = !list_empty(&dev->cmd_lists.active_cmd_list) ||
+                 !list_empty(&dev->ready_cmd_list) ||
+                 !dev->blocking || dev->cleanup_done ||
+                 signal_pending(current);
+       return res;
+}
+
+/* Called under cmd_lists.cmd_list_lock and IRQ off */
+static int dev_user_get_next_cmd(struct scst_user_dev *dev,
+       struct dev_user_cmd **ucmd)
+{
+       int res = 0;
+       wait_queue_t wait;
+
+       TRACE_ENTRY();
+
+       init_waitqueue_entry(&wait, current);
+
+       while(1) {
+               if (!test_cmd_lists(dev)) {
+                       add_wait_queue_exclusive(&dev->cmd_lists.cmd_list_waitQ,
+                               &wait);
+                       for (;;) {
+                               set_current_state(TASK_INTERRUPTIBLE);
+                               if (test_cmd_lists(dev))
+                                       break;
+                               spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+                               schedule();
+                               spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+                       }
+                       set_current_state(TASK_RUNNING);
+                       remove_wait_queue(&dev->cmd_lists.cmd_list_waitQ,
+                               &wait);
+               }
+
+               dev_user_process_scst_commands(dev);
+
+               *ucmd = __dev_user_get_next_cmd(&dev->ready_cmd_list);
+               if (*ucmd != NULL)
+                       break;
+
+               if (!dev->blocking || dev->cleanup_done) {
+                       res = -EAGAIN;
+                       TRACE_DBG("No ready commands, returning %d", res);
+                       break;
+               }
+
+               if (signal_pending(current)) {
+                       res = -EINTR;
+                       TRACE_DBG("Signal pending, returning %d", res);
+                       break;
+               }
+       }
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static inline int test_prio_cmd_list(struct scst_user_dev *dev)
+{
+       /*
+        * Prio queue is always blocking, because poll() seems doesn't
+        * support, when different threads wait with different events
+        * mask. Only one thread is woken up on each event and if it
+        * isn't interested in such events, another (interested) one
+        * will not be woken up. Does't know if it's a bug or feature.
+        */
+       int res = !list_empty(&dev->prio_ready_cmd_list) ||
+                 dev->cleaning || dev->cleanup_done ||
+                 signal_pending(current);
+       return res;
+}
+
+/* Called under cmd_lists.cmd_list_lock and IRQ off */
+static int dev_user_get_next_prio_cmd(struct scst_user_dev *dev,
+       struct dev_user_cmd **ucmd)
+{
+       int res = 0;
+       wait_queue_t wait;
+
+       TRACE_ENTRY();
+
+       init_waitqueue_entry(&wait, current);
+
+       while(1) {
+               if (!test_prio_cmd_list(dev)) {
+                       add_wait_queue_exclusive(&dev->prio_cmd_list_waitQ,
+                               &wait);
+                       for (;;) {
+                               set_current_state(TASK_INTERRUPTIBLE);
+                               if (test_prio_cmd_list(dev))
+                                       break;
+                               spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+                               schedule();
+                               spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+                       }
+                       set_current_state(TASK_RUNNING);
+                       remove_wait_queue(&dev->prio_cmd_list_waitQ, &wait);
+               }
+
+               *ucmd = __dev_user_get_next_cmd(&dev->prio_ready_cmd_list);
+               if (*ucmd != NULL)
+                       break;
+
+               if (dev->cleaning || dev->cleanup_done) {
+                       res = -EAGAIN;
+                       TRACE_DBG("No ready commands, returning %d", res);
+                       break;
+               }
+
+               if (signal_pending(current)) {
+                       res = -EINTR;
+                       TRACE_DBG("Signal pending, returning %d", res);
+                       break;
+               }
+       }
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_reply_get_cmd(struct file *file, unsigned long arg,
+       int prio)
+{
+       int res = 0;
+       struct scst_user_dev *dev;
+       struct scst_user_get_cmd *cmd;
+       struct scst_user_reply_cmd *reply;
+       struct dev_user_cmd *ucmd;
+       uint64_t ureply;
+
+       TRACE_ENTRY();
+
+       mutex_lock(&dev_user_mutex);
+       dev = (struct scst_user_dev*)file->private_data;
+       res = dev_user_check_reg(dev);
+       if (res != 0) {
+               mutex_unlock(&dev_user_mutex);
+               goto out;
+       }
+       down_read(&dev->dev_rwsem);
+       mutex_unlock(&dev_user_mutex);
+
+       res = copy_from_user(&ureply, (void*)arg, sizeof(ureply));
+       if (res < 0)
+               goto out_up;
+
+       TRACE_DBG("ureply %Ld", ureply);
+
+       cmd = kzalloc(max(sizeof(*cmd), sizeof(*reply)), GFP_KERNEL);
+       if (cmd == NULL) {
+               res = -ENOMEM;
+               goto out_up;
+       }
+
+       if (ureply != 0) {
+               unsigned long u = (unsigned long)ureply;
+               reply = (struct scst_user_reply_cmd*)cmd;
+               res = copy_from_user(reply, (void*)u, sizeof(*reply));
+               if (res < 0)
+                       goto out_free;
+
+               TRACE_BUFFER("Reply", reply, sizeof(*reply));
+
+               res = dev_user_process_reply(dev, reply);
+               if (res < 0)
+                       goto out_free;
+       }
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+       if (prio && (dev->prio_queue_type == SCST_USER_PRIO_QUEUE_SEPARATE))
+               res = dev_user_get_next_prio_cmd(dev, &ucmd);
+       else
+               res = dev_user_get_next_cmd(dev, &ucmd);
+       if (res == 0) {
+               *cmd = ucmd->user_cmd;
+               spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+               TRACE_BUFFER("UCMD", cmd, sizeof(*cmd));
+               res = copy_to_user((void*)arg, cmd, sizeof(*cmd));
+       } else
+               spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+out_free:
+       kfree(cmd);
+
+out_up:
+       up_read(&dev->dev_rwsem);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static long dev_user_ioctl(struct file *file, unsigned int cmd,
+       unsigned long arg)
+{
+       long res;
+
+       TRACE_ENTRY();
+
+       switch (cmd) {
+       case SCST_USER_REPLY_AND_GET_CMD:
+               TRACE_DBG("%s", "REPLY_AND_GET_CMD");
+               res = dev_user_reply_get_cmd(file, arg, 0);
+               break;
+
+       case SCST_USER_REPLY_CMD:
+               TRACE_DBG("%s", "REPLY_CMD");
+               res = dev_user_reply_cmd(file, arg);
+               break;
+
+       case SCST_USER_REPLY_AND_GET_PRIO_CMD:
+               TRACE_DBG("%s", "REPLY_AND_GET_PRIO_CMD");
+               res = dev_user_reply_get_cmd(file, arg, 1);
+               break;
+
+       case SCST_USER_REGISTER_DEVICE:
+       {
+               struct scst_user_dev_desc *dev_desc;
+               TRACE_DBG("%s", "REGISTER_DEVICE");
+               dev_desc = kmalloc(sizeof(*dev_desc), GFP_KERNEL);
+               if (dev_desc == NULL) {
+                       res = -ENOMEM;
+                       goto out;
+               }
+               res = copy_from_user(dev_desc, (void*)arg, sizeof(*dev_desc));
+               if (res < 0) {
+                       kfree(dev_desc);
+                       goto out;
+               }
+               TRACE_BUFFER("dev_desc", dev_desc, sizeof(*dev_desc));
+               res = dev_user_register_dev(file, dev_desc);
+               kfree(dev_desc);
+               break;
+       }
+
+       case SCST_USER_SET_OPTIONS:
+       {
+               struct scst_user_opt opt;
+               TRACE_DBG("%s", "SET_OPTIONS");
+               res = copy_from_user(&opt, (void*)arg, sizeof(opt));
+               if (res < 0)
+                       goto out;
+               TRACE_BUFFER("opt", &opt, sizeof(opt));
+               res = dev_user_set_opt(file, &opt);
+               break;
+       }
+
+       case SCST_USER_GET_OPTIONS:
+               TRACE_DBG("%s", "GET_OPTIONS");
+               res = dev_user_get_opt(file, (void*)arg);
+               break;
+
+       default:
+               PRINT_ERROR_PR("Invalid ioctl cmd %x", cmd);
+               res = -EINVAL;
+               goto out;
+       }
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static unsigned int dev_user_poll(struct file *file, poll_table *wait)
+{
+       int res = 0;
+       struct scst_user_dev *dev;
+
+       TRACE_ENTRY();
+
+       mutex_lock(&dev_user_mutex);
+       dev = (struct scst_user_dev*)file->private_data;
+       res = dev_user_check_reg(dev);
+       if (res != 0) {
+               mutex_unlock(&dev_user_mutex);
+               goto out;
+       }
+       down_read(&dev->dev_rwsem);
+       mutex_unlock(&dev_user_mutex);
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       if (!list_empty(&dev->ready_cmd_list) ||
+           !list_empty(&dev->cmd_lists.active_cmd_list)) {
+               res |= POLLIN | POLLRDNORM;
+               goto out_unlock;
+       }
+
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       TRACE_DBG("Before poll_wait() (dev %p)", dev);
+       poll_wait(file, &dev->cmd_lists.cmd_list_waitQ, wait);
+       TRACE_DBG("After poll_wait() (dev %p)", dev);
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       if (!list_empty(&dev->ready_cmd_list) ||
+           !list_empty(&dev->cmd_lists.active_cmd_list)) {
+               res |= POLLIN | POLLRDNORM;
+               goto out_unlock;
+       }
+
+out_unlock:
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       up_read(&dev->dev_rwsem);
+
+out:
+       TRACE_EXIT_HRES(res);
+       return res;
+}
+
+/*
+ * Called under cmd_lists.cmd_list_lock, but can drop it inside, then reaquire.
+ */
+static void dev_user_unjam_cmd(struct dev_user_cmd *ucmd, int busy,
+       unsigned long *flags)
+{
+       int state = ucmd->state & ~UCMD_STATE_MASK;
+       struct scst_user_dev *dev = ucmd->dev;
+
+       TRACE_ENTRY();
+
+       if (ucmd->state & UCMD_STATE_JAMMED_MASK)
+               goto out;
+
+       TRACE_MGMT_DBG("Unjamming ucmd %p (busy %d, state %x)", ucmd, busy,
+               ucmd->state);
+
+       ucmd->state = state | UCMD_STATE_JAMMED_MASK;
+
+       switch(state) {
+       case UCMD_STATE_PARSING:
+       case UCMD_STATE_BUF_ALLOCING:
+               if (busy)
+                       scst_set_busy(ucmd->cmd);
+               else
+                       scst_set_cmd_error(ucmd->cmd,
+                               SCST_LOAD_SENSE(scst_sense_hardw_error));
+               TRACE_MGMT_DBG("Adding ucmd %p to active list", ucmd);
+               list_add(&ucmd->cmd->cmd_list_entry,
+                       &ucmd->cmd->cmd_lists->active_cmd_list);
+               wake_up(&ucmd->dev->cmd_lists.cmd_list_waitQ);
+               break;
+
+       case UCMD_STATE_EXECING:
+               if (flags != NULL)
+                       spin_unlock_irqrestore(&dev->cmd_lists.cmd_list_lock, *flags);
+               else
+                       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+               if (busy)
+                       scst_set_busy(ucmd->cmd);
+               else
+                       scst_set_cmd_error(ucmd->cmd,
+                               SCST_LOAD_SENSE(scst_sense_hardw_error));
+               TRACE_MGMT_DBG("EXEC: unjamming ucmd %p", ucmd);
+               ucmd->cmd->completed = 1;
+               ucmd->cmd->scst_cmd_done(ucmd->cmd, SCST_CMD_STATE_DEFAULT);
+               if (flags != NULL)
+                       spin_lock_irqsave(&dev->cmd_lists.cmd_list_lock, *flags);
+               else
+                       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+               break;
+
+       case UCMD_STATE_ON_FREEING:
+       case UCMD_STATE_ON_CACHE_FREEING:
+       case UCMD_STATE_TM_EXECING:
+       case UCMD_STATE_ATTACH_SESS:
+       case UCMD_STATE_DETACH_SESS:
+       {
+               if (flags != NULL)
+                       spin_unlock_irqrestore(&dev->cmd_lists.cmd_list_lock, *flags);
+               else
+                       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+               switch(state) {
+               case UCMD_STATE_ON_FREEING:
+                       dev_user_process_reply_on_free(ucmd);
+                       break;
+
+               case UCMD_STATE_ON_CACHE_FREEING:
+                       dev_user_process_reply_on_cache_free(ucmd);
+                       break;
+
+               case UCMD_STATE_TM_EXECING:
+                       dev_user_process_reply_tm_exec(ucmd, SCST_MGMT_STATUS_FAILED);
+                       break;
+
+               case UCMD_STATE_ATTACH_SESS:
+               case UCMD_STATE_DETACH_SESS:
+                       dev_user_process_reply_sess(ucmd, -EFAULT);
+                       break;
+               }
+
+               if (flags != NULL)
+                       spin_lock_irqsave(&dev->cmd_lists.cmd_list_lock, *flags);
+               else
+                       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+               break;
+       }
+
+       default:
+               PRINT_ERROR_PR("Wrong ucmd state %x", state);
+               sBUG();
+               break;
+       }
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static int __unjam_check_tgt_dev(struct dev_user_cmd *ucmd, int state,
+       struct scst_tgt_dev *tgt_dev)
+{
+       int res = 0;
+
+       if (ucmd->cmd == NULL)
+               goto out;
+
+       if (ucmd->cmd->tgt_dev != tgt_dev)
+               goto out;
+
+       switch(state & ~UCMD_STATE_MASK) {
+       case UCMD_STATE_PARSING:
+       case UCMD_STATE_BUF_ALLOCING:
+       case UCMD_STATE_EXECING:
+               break;
+       default:
+               goto out;
+       }
+
+       res = 1;
+out:
+       return res;
+}
+
+static int __unjam_check_tm(struct dev_user_cmd *ucmd, int state)
+{
+       int res = 0;
+
+       switch(state & ~UCMD_STATE_MASK) {
+       case UCMD_STATE_PARSING:
+       case UCMD_STATE_BUF_ALLOCING:
+       case UCMD_STATE_EXECING:
+               if ((ucmd->cmd != NULL) &&
+                   (!test_bit(SCST_CMD_ABORTED,
+                               &ucmd->cmd->cmd_flags)))
+                       goto out;
+               break;
+       default:
+               goto out;
+       }
+
+       res = 1;
+out:
+       return res;
+}
+
+static void dev_user_unjam_dev(struct scst_user_dev *dev, int tm,
+       struct scst_tgt_dev *tgt_dev)
+{
+       int i;
+       unsigned long flags;
+       struct dev_user_cmd *ucmd;
+
+       TRACE_ENTRY();
+
+       TRACE_MGMT_DBG("Unjamming dev %p", dev);
+
+       spin_lock_irqsave(&dev->cmd_lists.cmd_list_lock, flags);
+
+repeat:
+       for(i = 0; i < (int)ARRAY_SIZE(dev->ucmd_hash); i++) {
+               struct list_head *head = &dev->ucmd_hash[i];
+               list_for_each_entry(ucmd, head, hash_list_entry) {
+                       TRACE_DBG("ALL: ucmd %p, state %x, scst_cmd %p",
+                               ucmd, ucmd->state, ucmd->cmd);
+                       if (ucmd->state & UCMD_STATE_SENT_MASK) {
+                               int st = ucmd->state & ~UCMD_STATE_SENT_MASK;
+                               if (tgt_dev != NULL) {
+                                       if (__unjam_check_tgt_dev(ucmd, st, 
+                                                       tgt_dev) == 0)
+                                               continue;
+                               } else if (tm) {
+                                       if (__unjam_check_tm(ucmd, st) == 0)
+                                               continue;
+                               }
+                               dev_user_unjam_cmd(ucmd, 0, &flags);
+                               goto repeat;
+                       }
+               }
+       }
+
+       if ((tgt_dev != NULL) || tm) {
+               list_for_each_entry(ucmd, &dev->ready_cmd_list,
+                               ready_cmd_list_entry) {
+                       TRACE_DBG("READY: ucmd %p, state %x, scst_cmd %p",
+                               ucmd, ucmd->state, ucmd->cmd);
+                       if (tgt_dev != NULL) {
+                               if (__unjam_check_tgt_dev(ucmd, ucmd->state,
+                                               tgt_dev) == 0)
+                                       continue;
+                       } else if (tm) {
+                               if (__unjam_check_tm(ucmd, ucmd->state) == 0)
+                                       continue;
+                       }
+                       list_del(&ucmd->ready_cmd_list_entry);
+                       dev_user_unjam_cmd(ucmd, 0, &flags);
+                       goto repeat;
+               }
+       }
+
+       if (dev_user_process_scst_commands(dev) != 0)
+               goto repeat;
+
+       spin_unlock_irqrestore(&dev->cmd_lists.cmd_list_lock, flags);
+
+       TRACE_EXIT();
+       return;
+}
+
+/**
+ ** In order to deal with user space handler hangups we rely on remote
+ ** initiators, which in case if a command doesn't respond for too long
+ ** supposed to issue a task management command, so on that event we can
+ ** "unjam" the command. In order to prevent TM command from stalling, we
+ ** use a timer. In order to prevent too many queued TM commands, we
+ ** enqueue only 2 of them, the first one with the requested TM function,
+ ** the second - with TARGET_RESET as the most comprehensive function.
+ **
+ ** The only exception here is DETACH_SESS subcode, where there are no TM
+ ** commands could be expected, so we need manually after a timeout "unjam"
+ ** all the commands on the device.
+ **
+ ** We also don't queue >1 ATTACH_SESS commands and after timeout fail it.
+ **/
+
+static int dev_user_process_reply_tm_exec(struct dev_user_cmd *ucmd,
+       int status)
+{
+       int res = 0;
+       unsigned long flags;
+
+       TRACE_ENTRY();
+
+       TRACE_MGMT_DBG("TM reply (ucmd %p, fn %d, status %d)", ucmd,
+               ucmd->user_cmd.tm_cmd.fn, status);
+
+       ucmd->result = status;
+
+       spin_lock_irqsave(&ucmd->dev->cmd_lists.cmd_list_lock, flags);
+
+       if (ucmd->internal_reset_tm) {
+               TRACE_MGMT_DBG("Internal TM ucmd %p finished", ucmd);
+               ucmd->dev->internal_reset_active = 0;
+       } else {
+               TRACE_MGMT_DBG("TM ucmd %p finished", ucmd);
+               ucmd->dev->tm_cmd_active = 0;
+       }
+
+       if (ucmd->cmpl != NULL)
+               complete_all(ucmd->cmpl);
+
+       spin_unlock_irqrestore(&ucmd->dev->cmd_lists.cmd_list_lock, flags);
+
+       ucmd_put(ucmd);
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_task_mgmt_fn(struct scst_mgmt_cmd *mcmd, 
+       struct scst_tgt_dev *tgt_dev)
+{
+       int res, rc;
+       struct dev_user_cmd *ucmd;
+       struct scst_user_dev *dev = (struct scst_user_dev*)tgt_dev->dev->dh_priv;
+
+       TRACE_ENTRY();
+
+       /* We can't afford missing TM command due to memory shortage */
+       ucmd = dev_user_alloc_ucmd(dev, GFP_KERNEL|__GFP_NOFAIL);
+       ucmd->cmpl = kmalloc(sizeof(*ucmd->cmpl), GFP_KERNEL|__GFP_NOFAIL);
+
+       init_completion(ucmd->cmpl);
+
+       ucmd->user_cmd.cmd_h = ucmd->h;
+       ucmd->user_cmd.subcode = SCST_USER_TASK_MGMT;
+       ucmd->user_cmd.tm_cmd.sess_h = (unsigned long)tgt_dev;
+       ucmd->user_cmd.tm_cmd.fn = mcmd->fn;
+
+       if (mcmd->cmd_to_abort != NULL) {
+               struct dev_user_cmd *ucmd_to_abort =
+                       (struct dev_user_cmd*)mcmd->cmd_to_abort->dh_priv;
+               if (ucmd_to_abort != NULL)
+                       ucmd->user_cmd.tm_cmd.cmd_h_to_abort = ucmd_to_abort->h;
+       }
+
+       TRACE_MGMT_DBG("Preparing TM ucmd %p (h %d, fn %d, cmd_to_abort %p, "
+               "ucmd_to_abort %d)", ucmd, ucmd->h, mcmd->fn,
+               mcmd->cmd_to_abort, ucmd->user_cmd.tm_cmd.cmd_h_to_abort);
+
+       ucmd->state = UCMD_STATE_TM_EXECING;
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+       if (dev->internal_reset_active) {
+               PRINT_ERROR_PR("Loosing TM cmd %d, because there are other "
+                       "unprocessed TM commands", mcmd->fn);
+               res = SCST_MGMT_STATUS_FAILED;
+               goto out_locked_free;
+       } else if (dev->tm_cmd_active) {
+               /*
+                * We are going to miss some TM commands, so replace it
+                * by the hardest one.
+                */
+               PRINT_ERROR_PR("Replacing TM cmd %d by TARGET_RESET, because "
+                       "there is another unprocessed TM command", mcmd->fn);
+               ucmd->user_cmd.tm_cmd.fn = SCST_TARGET_RESET;
+               ucmd->internal_reset_tm = 1;
+               dev->internal_reset_active = 1;
+       } else
+               dev->tm_cmd_active = 1;
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       ucmd_get(ucmd, 0);
+       dev_user_add_to_ready(ucmd);
+
+       /*
+        * Since the user space handler should not wait for affecting tasks to
+        * complete it shall complete the TM request ASAP, otherwise the device
+        * will be considered stalled.
+        */
+       rc = wait_for_completion_timeout(ucmd->cmpl, DEV_USER_TM_TIMEOUT);
+       if (rc > 0)
+               res = ucmd->result;
+       else {
+               PRINT_ERROR_PR("Task management command %p timeout", ucmd);
+               res = SCST_MGMT_STATUS_FAILED;
+       }
+
+       sBUG_ON(irqs_disabled());
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+out_locked_free:
+       kfree(ucmd->cmpl);
+       ucmd->cmpl = NULL;
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       dev_user_unjam_dev(ucmd->dev, 1, NULL);
+
+       ucmd_put(ucmd);
+
+       TRACE_EXIT();
+       return res;
+}
+
+static int dev_user_attach(struct scst_device *sdev)
+{
+       int res = 0;
+       struct scst_user_dev *dev = NULL, *d;
+
+       TRACE_ENTRY();
+
+       mutex_lock(&dev_user_mutex);
+       list_for_each_entry(d, &dev_list, dev_list_entry) {
+               if (strcmp(d->name, sdev->virt_name) == 0) {
+                       dev = d;
+                       break;
+               }
+       }
+       mutex_unlock(&dev_user_mutex);
+       if (dev == NULL) {
+               PRINT_ERROR_PR("Device %s not found", sdev->virt_name);
+               res = -EINVAL;
+               goto out;
+       }
+
+       sdev->dh_priv = dev;
+
+       PRINT_INFO_PR("Attached user space SCSI target virtual device \"%s\"",
+               dev->name);
+
+out:
+       TRACE_EXIT();
+       return res;
+}
+
+static void dev_user_detach(struct scst_device *sdev)
+{
+       struct scst_user_dev *dev = (struct scst_user_dev*)sdev->dh_priv;
+
+       TRACE_ENTRY();
+
+       TRACE_DBG("virt_id %d", sdev->virt_id);
+
+       PRINT_INFO_PR("Detached user space SCSI target virtual device \"%s\"",
+               dev->name);
+
+       /* dev will be freed by the caller */
+       sdev->dh_priv = NULL;
+       
+       TRACE_EXIT();
+       return;
+}
+
+static int dev_user_process_reply_sess(struct dev_user_cmd *ucmd, int status)
+{
+       int res = 0;
+       unsigned long flags;
+
+       TRACE_ENTRY();
+
+       TRACE_MGMT_DBG("ucmd %p, cmpl %p, status %d", ucmd, ucmd->cmpl, status);
+
+       spin_lock_irqsave(&ucmd->dev->cmd_lists.cmd_list_lock, flags);
+
+       if ((ucmd->state & ~UCMD_STATE_MASK) ==
+                       UCMD_STATE_ATTACH_SESS) {
+               TRACE_MGMT_DBG("%s", "ATTACH_SESS finished");
+               ucmd->result = status;
+               ucmd->dev->attach_cmd_active = 0;
+       } else if ((ucmd->state & ~UCMD_STATE_MASK) ==
+                       UCMD_STATE_DETACH_SESS) {
+               TRACE_MGMT_DBG("%s", "DETACH_SESS finished");
+               ucmd->dev->detach_cmd_count--;
+       } else
+               sBUG();
+
+       if (ucmd->cmpl != NULL)
+               complete_all(ucmd->cmpl);
+
+       spin_unlock_irqrestore(&ucmd->dev->cmd_lists.cmd_list_lock, flags);
+
+       ucmd_put(ucmd);
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_attach_tgt(struct scst_tgt_dev *tgt_dev)
+{
+       struct scst_user_dev *dev =
+               (struct scst_user_dev*)tgt_dev->dev->dh_priv;
+       int res = 0, rc;
+       struct dev_user_cmd *ucmd;
+
+       TRACE_ENTRY();
+
+       tgt_dev->p_cmd_lists = &dev->cmd_lists;
+
+       ucmd = dev_user_alloc_ucmd(dev, GFP_KERNEL);
+       if (ucmd == NULL)
+               goto out_nomem;
+
+       ucmd->cmpl = kmalloc(sizeof(*ucmd->cmpl), GFP_KERNEL);
+       if (ucmd->cmpl == NULL)
+               goto out_put_nomem;
+
+       init_completion(ucmd->cmpl);
+
+       ucmd->user_cmd.cmd_h = ucmd->h;
+       ucmd->user_cmd.subcode = SCST_USER_ATTACH_SESS;
+       ucmd->user_cmd.sess.sess_h = (unsigned long)tgt_dev;
+       ucmd->user_cmd.sess.lun = (uint64_t)tgt_dev->lun;
+       ucmd->user_cmd.sess.rd_only = tgt_dev->acg_dev->rd_only_flag;
+       strncpy(ucmd->user_cmd.sess.initiator_name,
+               tgt_dev->sess->initiator_name,
+               sizeof(ucmd->user_cmd.sess.initiator_name)-1);
+       ucmd->user_cmd.sess.initiator_name[
+               sizeof(ucmd->user_cmd.sess.initiator_name)-1] = '\0';
+
+       TRACE_MGMT_DBG("Preparing ATTACH_SESS %p (h %d, sess_h %Lx, LUN %Lx, "
+               "rd_only_flag %d, initiator %s)", ucmd, ucmd->h,
+               ucmd->user_cmd.sess.sess_h, ucmd->user_cmd.sess.lun,
+               ucmd->user_cmd.sess.rd_only, ucmd->user_cmd.sess.initiator_name);
+
+       ucmd->state = UCMD_STATE_ATTACH_SESS;
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+       if (dev->attach_cmd_active) {
+               PRINT_ERROR_PR("%s", "ATTACH_SESS command failed, because "
+                       "there is another unprocessed ATTACH_SESS command");
+               res = -EBUSY;
+               goto out_locked_free;
+       }
+       dev->attach_cmd_active = 1;
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       ucmd_get(ucmd, 0);
+       dev_user_add_to_ready(ucmd);
+
+       rc = wait_for_completion_timeout(ucmd->cmpl, DEV_USER_ATTACH_TIMEOUT);
+       if (rc > 0)
+               res = ucmd->result;
+       else {
+               PRINT_ERROR_PR("%s", "ATTACH_SESS command timeout");
+               res = -EFAULT;
+       }
+
+       sBUG_ON(irqs_disabled());
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+out_locked_free:
+       kfree(ucmd->cmpl);
+       ucmd->cmpl = NULL;
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       ucmd_put(ucmd);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_put_nomem:
+       ucmd_put(ucmd);
+
+out_nomem:
+       res = -ENOMEM;
+       goto out;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+static void dev_user_pre_unreg_sess_work_fn(void *p)
+#else
+static void dev_user_pre_unreg_sess_work_fn(struct work_struct *work)
+#endif
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+       struct dev_user_pre_unreg_sess_obj *pd = (struct dev_user_pre_unreg_sess_obj*)p;
+#else
+       struct dev_user_pre_unreg_sess_obj *pd = container_of(
+               (struct delayed_work*)work, struct dev_user_pre_unreg_sess_obj,
+               pre_unreg_sess_work);
+#endif
+       struct scst_user_dev *dev =
+               (struct scst_user_dev*)pd->tgt_dev->dev->dh_priv;
+
+       TRACE_ENTRY();
+
+       TRACE_MGMT_DBG("Unreg sess: unjaming dev %p (tgt_dev %p)", dev,
+               pd->tgt_dev);
+
+       pd->active = 1;
+
+       dev_user_unjam_dev(dev, 0, pd->tgt_dev);
+
+       if (!pd->exit) {
+               TRACE_MGMT_DBG("Rescheduling pre_unreg_sess work %p (dev %p, "
+                       "tgt_dev %p)", pd, dev, pd->tgt_dev);
+               schedule_delayed_work(&pd->pre_unreg_sess_work,
+                       DEV_USER_PRE_UNREG_POLL_TIME);
+       }
+
+       TRACE_EXIT();
+       return;
+}
+
+static void dev_user_pre_unreg_sess(struct scst_tgt_dev *tgt_dev)
+{
+       struct scst_user_dev *dev =
+               (struct scst_user_dev*)tgt_dev->dev->dh_priv;
+       struct dev_user_pre_unreg_sess_obj *pd;
+
+       TRACE_ENTRY();
+
+       /* We can't afford missing DETACH command due to memory shortage */
+       pd = kzalloc(sizeof(*pd), GFP_KERNEL|__GFP_NOFAIL);
+
+       pd->tgt_dev = tgt_dev;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+       INIT_WORK(&pd->pre_unreg_sess_work, dev_user_pre_unreg_sess_work_fn, pd);
+#else
+       INIT_DELAYED_WORK(&pd->pre_unreg_sess_work, dev_user_pre_unreg_sess_work_fn);
+#endif
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+       dev->pre_unreg_sess_active = 1;
+       list_add_tail(&pd->pre_unreg_sess_list_entry, &dev->pre_unreg_sess_list);
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       TRACE_MGMT_DBG("Scheduling pre_unreg_sess work %p (dev %p, tgt_dev %p)",
+               pd, dev, pd->tgt_dev);
+
+       schedule_delayed_work(&pd->pre_unreg_sess_work, DEV_USER_DETACH_TIMEOUT);
+
+       TRACE_EXIT();
+       return;
+}
+
+static void dev_user_detach_tgt(struct scst_tgt_dev *tgt_dev)
+{
+       struct scst_user_dev *dev =
+               (struct scst_user_dev*)tgt_dev->dev->dh_priv;
+       struct dev_user_cmd *ucmd;
+       struct dev_user_pre_unreg_sess_obj *pd = NULL, *p;
+
+       TRACE_ENTRY();
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+       list_for_each_entry(p, &dev->pre_unreg_sess_list,
+                       pre_unreg_sess_list_entry) {
+               if (p->tgt_dev == tgt_dev) {
+                       list_del(&p->pre_unreg_sess_list_entry);
+                       if (list_empty(&dev->pre_unreg_sess_list))
+                               dev->pre_unreg_sess_active = 0;
+                       pd = p;
+                       break;
+               }
+       }
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       if (pd != NULL) {
+               pd->exit = 1;
+               TRACE_MGMT_DBG("Canceling pre unreg work %p", pd);
+               cancel_delayed_work(&pd->pre_unreg_sess_work);
+               flush_scheduled_work();
+               kfree(pd);
+       }
+
+       ucmd = dev_user_alloc_ucmd(dev, GFP_KERNEL);
+       if (ucmd == NULL)
+               goto out;
+
+       TRACE_MGMT_DBG("Preparing DETACH_SESS %p (h %d, sess_h %Lx)", ucmd,
+               ucmd->h, ucmd->user_cmd.sess.sess_h);
+
+       ucmd->user_cmd.cmd_h = ucmd->h;
+       ucmd->user_cmd.subcode = SCST_USER_DETACH_SESS;
+       ucmd->user_cmd.sess.sess_h = (unsigned long)tgt_dev;
+
+       spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+       dev->detach_cmd_count++;
+       spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+
+       ucmd->state = UCMD_STATE_DETACH_SESS;
+
+       dev_user_add_to_ready(ucmd);
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+/* No locks are needed, but the activity must be suspended */
+static void dev_user_setup_functions(struct scst_user_dev *dev)
+{
+       TRACE_ENTRY();
+
+       dev->devtype.parse = dev_user_parse;
+       dev->devtype.dev_done = NULL;
+
+       if (dev->parse_type != SCST_USER_PARSE_CALL) {
+               switch(dev->devtype.type) {
+               case TYPE_DISK:
+                       dev->generic_parse = scst_sbc_generic_parse;
+                       dev->devtype.dev_done = dev_user_disk_done;
+                       break;
+
+               case TYPE_TAPE:
+                       dev->generic_parse = scst_tape_generic_parse;
+                       dev->devtype.dev_done = dev_user_tape_done;
+                       break;
+
+               case TYPE_MOD:
+                       dev->generic_parse = scst_modisk_generic_parse;
+                       dev->devtype.dev_done = dev_user_disk_done;
+                       break;
+
+               case TYPE_ROM:
+                       dev->generic_parse = scst_cdrom_generic_parse;
+                       dev->devtype.dev_done = dev_user_disk_done;
+                       break;
+
+               case TYPE_MEDIUM_CHANGER:
+                       dev->generic_parse = scst_changer_generic_parse;
+                       break;
+
+               case TYPE_PROCESSOR:
+                       dev->generic_parse = scst_processor_generic_parse;
+                       break;
+
+               case TYPE_RAID:
+                       dev->generic_parse = scst_raid_generic_parse;
+                       break;
+
+               default:
+                       PRINT_INFO_PR("Unknown SCSI type %x, using PARSE_CALL "
+                               "for it", dev->devtype.type);
+                       dev->parse_type = SCST_USER_PARSE_CALL;
+                       break;
+               }
+       } else {
+               dev->generic_parse = NULL;
+               dev->devtype.dev_done = NULL;
+       }
+
+       TRACE_EXIT();
+       return;
+}
+
+static int dev_user_register_dev(struct file *file,
+       const struct scst_user_dev_desc *dev_desc)
+{
+       int res = -ENOMEM, i;
+       struct scst_user_dev *dev, *d;
+       int block;
+
+       TRACE_ENTRY();
+
+       if (dev_desc->version != DEV_USER_VERSION) {
+               PRINT_ERROR_PR("Version mismatch (requested %d, required %d)",
+                       dev_desc->version, DEV_USER_VERSION);
+               res = -EINVAL;
+               goto out;
+       }
+
+       if (dev_desc->type != TYPE_TAPE) {
+               if (dev_desc->block_size == 0) {
+                       PRINT_ERROR_PR("Wrong block size %d", dev_desc->block_size);
+                       res = -EINVAL;
+                       goto out;
+               }
+               block = scst_calc_block_shift(dev_desc->block_size);
+               if (block == -1) {
+                       res = -EINVAL;
+                       goto out;
+               }
+       } else
+               block = dev_desc->block_size;
+
+       if (!try_module_get(THIS_MODULE)) {
+               PRINT_ERROR_PR("%s", "Fail to get module");
+               goto out;
+       }
+
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (dev == NULL)
+               goto out_put;
+
+       init_rwsem(&dev->dev_rwsem);
+       spin_lock_init(&dev->cmd_lists.cmd_list_lock);
+       INIT_LIST_HEAD(&dev->cmd_lists.active_cmd_list);
+       init_waitqueue_head(&dev->cmd_lists.cmd_list_waitQ);
+       INIT_LIST_HEAD(&dev->ready_cmd_list);
+       INIT_LIST_HEAD(&dev->prio_ready_cmd_list);
+       init_waitqueue_head(&dev->prio_cmd_list_waitQ);
+       if (file->f_flags & O_NONBLOCK) {
+               TRACE_DBG("%s", "Non-blocking operations");
+               dev->blocking = 0;
+       } else
+               dev->blocking = 1;
+       for (i = 0; i < (int)ARRAY_SIZE(dev->ucmd_hash); i++)
+               INIT_LIST_HEAD(&dev->ucmd_hash[i]);
+       INIT_LIST_HEAD(&dev->pre_unreg_sess_list);
+
+       strncpy(dev->name, dev_desc->name, sizeof(dev->name)-1);
+       dev->name[sizeof(dev->name)-1] = '\0';
+
+       mutex_lock(&dev_user_mutex);
+
+       list_for_each_entry(d, &dev_list, dev_list_entry) {
+               if (strcmp(d->name, dev->name) == 0) {
+                       PRINT_ERROR_PR("Device %s already exist",
+                               dev->name);
+                       res = -EEXIST;
+                       goto out_free_unlock;
+               }
+       }
+
+       /*
+        * We don't use clustered pool, since it implies pages reordering,
+        * which isn't possible with user space supplied buffers. Although
+        * it's still possible to cluster pages by the tail of each other,
+        * seems it doesn't worth the effort.
+        */
+       dev->pool = sgv_pool_create(dev->name, 0);
+       if (dev->pool == NULL)
+               goto out_free_unlock;
+       sgv_pool_set_allocator(dev->pool, dev_user_alloc_pages,
+               dev_user_free_sg_entries);
+
+       scnprintf(dev->devtype.name, sizeof(dev->devtype.name), "dh-%s",
+               dev->name);
+       dev->devtype.type = dev_desc->type;
+       dev->devtype.threads_num = -1;
+       dev->devtype.parse_atomic = 1;
+       dev->devtype.exec_atomic = 0; /* no point to make it 1 */
+       dev->devtype.dev_done_atomic = 1;
+       dev->devtype.no_proc = 1;
+       dev->devtype.inc_expected_sn_on_done = 1;
+       dev->devtype.attach = dev_user_attach;
+       dev->devtype.detach = dev_user_detach;
+       dev->devtype.attach_tgt = dev_user_attach_tgt;
+       dev->devtype.pre_unreg_sess = dev_user_pre_unreg_sess;
+       dev->devtype.detach_tgt = dev_user_detach_tgt;
+       dev->devtype.exec = dev_user_exec;
+       dev->devtype.on_free_cmd = dev_user_on_free_cmd;
+       dev->devtype.task_mgmt_fn = dev_user_task_mgmt_fn;
+
+       init_completion(&dev->cleanup_cmpl);
+       dev->block = block;
+       dev->def_block = dev->block;
+
+       res = __dev_user_set_opt(dev, &dev_desc->opt);
+
+       TRACE_MEM("dev %p, name %s", dev, dev->name);
+
+       list_add_tail(&dev->dev_list_entry, &dev_list);
+
+       mutex_unlock(&dev_user_mutex);
+
+       if (res != 0)
+               goto out_free_pool;
+
+       res = scst_register_virtual_dev_driver(&dev->devtype);
+       if (res < 0)
+               goto out_free_pool;
+
+       dev->virt_id = scst_register_virtual_device(&dev->devtype, dev->name);
+       if (dev->virt_id < 0) {
+               res = dev->virt_id;
+               goto out_unreg_handler;
+       }
+
+       mutex_lock(&dev_user_mutex);
+       if (file->private_data != NULL) {
+               mutex_unlock(&dev_user_mutex);
+               PRINT_ERROR_PR("%s", "Device already registered");
+               res = -EINVAL;
+               goto out_unreg_drv;
+       }
+       file->private_data = dev;
+       mutex_unlock(&dev_user_mutex);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_unreg_drv:
+       scst_unregister_virtual_device(dev->virt_id);
+
+out_unreg_handler:
+       scst_unregister_virtual_dev_driver(&dev->devtype);
+
+out_free_pool:
+       mutex_lock(&dev_user_mutex);
+       list_del(&dev->dev_list_entry);
+       mutex_unlock(&dev_user_mutex);
+       sgv_pool_destroy(dev->pool);
+       kfree(dev);
+       goto out_put;
+
+out_free_unlock:
+       kfree(dev);
+       mutex_unlock(&dev_user_mutex);
+
+out_put:
+       module_put(THIS_MODULE);
+       goto out;
+}
+
+static int __dev_user_set_opt(struct scst_user_dev *dev,
+       const struct scst_user_opt *opt)
+{
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       TRACE_DBG("parse_type %x, on_free_cmd_type %x, memory_reuse_type %x, "
+               "partial_transfers_type %x, partial_len %d", opt->parse_type,
+               opt->on_free_cmd_type, opt->memory_reuse_type,
+               opt->partial_transfers_type, opt->partial_len);
+
+       if ((opt->parse_type > SCST_USER_MAX_PARSE_OPT) ||
+           (opt->on_free_cmd_type > SCST_USER_MAX_ON_FREE_CMD_OPT) ||
+           (opt->memory_reuse_type > SCST_USER_MAX_MEM_REUSE_OPT) ||
+           (opt->prio_queue_type > SCST_USER_MAX_PRIO_QUEUE_OPT) ||
+           (opt->partial_transfers_type > SCST_USER_MAX_PARTIAL_TRANSFERS_OPT)) {
+               PRINT_ERROR_PR("%s", "Invalid option");
+               res = -EINVAL;
+               goto out;
+       }
+
+       if ((dev->prio_queue_type != opt->prio_queue_type) &&
+           (opt->prio_queue_type == SCST_USER_PRIO_QUEUE_SINGLE)) {
+               struct dev_user_cmd *u, *t;
+               /* No need for lock, the activity is suspended */
+               list_for_each_entry_safe(u, t, &dev->prio_ready_cmd_list,
+                               ready_cmd_list_entry) {
+                       list_move_tail(&u->ready_cmd_list_entry,
+                               &dev->ready_cmd_list);
+               }
+       }
+
+       dev->prio_queue_type = opt->prio_queue_type;
+       dev->parse_type = opt->parse_type;
+       dev->on_free_cmd_type = opt->on_free_cmd_type;
+       dev->memory_reuse_type = opt->memory_reuse_type;
+       dev->partial_transfers_type = opt->partial_transfers_type;
+       dev->partial_len = opt->partial_len;
+
+       dev_user_setup_functions(dev);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_set_opt(struct file *file, const struct scst_user_opt *opt)
+{
+       int res = 0;
+       struct scst_user_dev *dev;
+
+       TRACE_ENTRY();
+
+       mutex_lock(&dev_user_mutex);
+       dev = (struct scst_user_dev*)file->private_data;
+       res = dev_user_check_reg(dev);
+       if (res != 0) {
+               mutex_unlock(&dev_user_mutex);
+               goto out;
+       }
+       down_read(&dev->dev_rwsem);
+       mutex_unlock(&dev_user_mutex);
+
+       scst_suspend_activity();
+       res = __dev_user_set_opt(dev, opt);
+       scst_resume_activity();
+
+       up_read(&dev->dev_rwsem);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_user_get_opt(struct file *file, void *arg)
+{
+       int res = 0;
+       struct scst_user_dev *dev;
+       struct scst_user_opt opt;
+
+       TRACE_ENTRY();
+
+       mutex_lock(&dev_user_mutex);
+       dev = (struct scst_user_dev*)file->private_data;
+       res = dev_user_check_reg(dev);
+       if (res != 0) {
+               mutex_unlock(&dev_user_mutex);
+               goto out;
+       }
+       down_read(&dev->dev_rwsem);
+       mutex_unlock(&dev_user_mutex);
+
+       opt.parse_type = dev->parse_type;
+       opt.on_free_cmd_type = dev->on_free_cmd_type;
+       opt.memory_reuse_type = dev->memory_reuse_type;
+       opt.prio_queue_type = dev->prio_queue_type;
+       opt.partial_transfers_type = dev->partial_transfers_type;
+       opt.partial_len = dev->partial_len;
+
+       TRACE_DBG("parse_type %x, on_free_cmd_type %x, memory_reuse_type %x, "
+               "partial_transfers_type %x, partial_len %d", opt.parse_type,
+               opt.on_free_cmd_type, opt.memory_reuse_type,
+               opt.partial_transfers_type, opt.partial_len);
+
+       res = copy_to_user(arg, &opt, sizeof(opt));
+
+       up_read(&dev->dev_rwsem);
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int dev_usr_parse(struct scst_cmd *cmd, struct scst_info_cdb *info_cdb)
+{
+       sBUG();
+       return SCST_CMD_STATE_DEFAULT;
+}
+
+/* Needed only for /proc support */
+#define USR_TYPE {             \
+  name:     DEV_USER_NAME,     \
+  type:     -1,                        \
+  parse:    dev_usr_parse,     \
+}
+
+static struct scst_dev_type dev_user_devtype = USR_TYPE;
+
+static int dev_user_release(struct inode *inode, struct file *file)
+{
+       int res = 0;
+       struct scst_user_dev *dev;
+
+       TRACE_ENTRY();
+
+       mutex_lock(&dev_user_mutex);
+       dev = (struct scst_user_dev*)file->private_data;
+       if (dev == NULL) {
+               mutex_unlock(&dev_user_mutex);
+               goto out;
+       }
+       file->private_data = NULL;
+       list_del(&dev->dev_list_entry);
+       mutex_unlock(&dev_user_mutex);
+
+       down_write(&dev->dev_rwsem);
+
+       TRACE_DBG("Releasing dev %p", dev);
+
+       spin_lock(&cleanup_lock);
+       list_add_tail(&dev->cleanup_list_entry, &cleanup_list);
+       spin_unlock(&cleanup_lock);
+
+       wake_up(&cleanup_list_waitQ);
+       wake_up(&dev->prio_cmd_list_waitQ);
+       wake_up(&dev->cmd_lists.cmd_list_waitQ);
+
+       scst_unregister_virtual_device(dev->virt_id);
+       scst_unregister_virtual_dev_driver(&dev->devtype);
+
+       sgv_pool_destroy(dev->pool);
+
+       TRACE_DBG("Unregistering finished (dev %p)", dev);
+
+       dev->cleanup_done = 1;
+       wake_up(&cleanup_list_waitQ);
+       wake_up(&dev->prio_cmd_list_waitQ);
+       wake_up(&dev->cmd_lists.cmd_list_waitQ);
+       wait_for_completion(&dev->cleanup_cmpl);
+
+       up_write(&dev->dev_rwsem); /* to make the debug check happy */
+
+       TRACE_DBG("Releasing completed (dev %p)", dev);
+
+       kfree(dev);
+
+       module_put(THIS_MODULE);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static void dev_user_process_cleanup(struct scst_user_dev *dev)
+{
+       struct dev_user_cmd *ucmd;
+       int rc;
+
+       TRACE_ENTRY();
+
+       dev->prio_queue_type = SCST_USER_PRIO_QUEUE_SINGLE;
+       dev->cleaning = 1;
+       dev->blocking = 1;
+
+       while(1) {
+               TRACE_DBG("Cleanuping dev %p", dev);
+
+               dev_user_unjam_dev(dev, 0, NULL);
+
+               spin_lock_irq(&dev->cmd_lists.cmd_list_lock);
+               rc = dev_user_get_next_prio_cmd(dev, &ucmd);
+               if (rc != 0)
+                       rc = dev_user_get_next_cmd(dev, &ucmd);
+               if (rc == 0)
+                       dev_user_unjam_cmd(ucmd, 1, NULL);
+               spin_unlock_irq(&dev->cmd_lists.cmd_list_lock);
+               if ((rc == -EAGAIN) && dev->cleanup_done)
+                       break;
+       }
+
+#ifdef EXTRACHECKS
+{
+       int i;
+       for(i = 0; i < (int)ARRAY_SIZE(dev->ucmd_hash); i++) {
+               struct list_head *head = &dev->ucmd_hash[i];
+               struct dev_user_cmd *ucmd, *t;
+               list_for_each_entry_safe(ucmd, t, head, hash_list_entry) {
+                       PRINT_ERROR_PR("Lost ucmd %p (state %x, ref %d)", ucmd,
+                               ucmd->state, atomic_read(&ucmd->ucmd_ref));
+                       ucmd_put(ucmd);
+               }
+       }
+}
+#endif
+
+       TRACE_DBG("Cleanuping done (dev %p)", dev);
+       complete_all(&dev->cleanup_cmpl);
+
+       TRACE_EXIT();
+       return;
+}
+
+static inline int test_cleanup_list(void)
+{
+       int res = !list_empty(&cleanup_list) ||
+                 unlikely(kthread_should_stop());
+       return res;
+}
+
+static int dev_user_cleanup_thread(void *arg)
+{
+       struct scst_user_dev *dev;
+
+       TRACE_ENTRY();
+
+       current->flags |= PF_NOFREEZE;
+
+       spin_lock(&cleanup_lock);
+       while(!kthread_should_stop()) {
+               wait_queue_t wait;
+               init_waitqueue_entry(&wait, current);
+
+               if (!test_cleanup_list()) {
+                       add_wait_queue_exclusive(&cleanup_list_waitQ, &wait);
+                       for (;;) {
+                               set_current_state(TASK_INTERRUPTIBLE);
+                               if (test_cleanup_list())
+                                       break;
+                               spin_unlock(&cleanup_lock);
+                               schedule();
+                               spin_lock(&cleanup_lock);
+                       }
+                       set_current_state(TASK_RUNNING);
+                       remove_wait_queue(&cleanup_list_waitQ, &wait);
+               }
+restart:
+               list_for_each_entry(dev, &cleanup_list, cleanup_list_entry) {
+                       list_del(&dev->cleanup_list_entry);
+                       spin_unlock(&cleanup_lock);
+                       dev_user_process_cleanup(dev);
+                       spin_lock(&cleanup_lock);
+                       goto restart;
+               }
+       }
+       spin_unlock(&cleanup_lock);
+
+       /*
+        * If kthread_should_stop() is true, we are guaranteed to be
+        * on the module unload, so cleanup_list must be empty.
+        */
+       sBUG_ON(!list_empty(&cleanup_list));
+
+       TRACE_EXIT();
+       return 0;
+}
+
+static int __init init_scst_user(void)
+{
+       int res = 0;
+       struct class_device *class_member;
+
+       TRACE_ENTRY();
+
+#ifndef CONFIG_NOHIGHMEM
+       PRINT_ERROR_PR("%s", "HIGHMEM kernel configurations are not supported. "
+               "Consider changing VMSPLIT option or using 64-bit "
+               "configuration.");
+       res = -EINVAL;
+       goto out;
+#endif
+
+       user_cmd_cachep = kmem_cache_create("scst_user_cmd",
+               sizeof(struct dev_user_cmd), 0, DEV_USER_SLAB_FLAGS, NULL,
+               NULL);
+       if (user_cmd_cachep == NULL) {
+               res = -ENOMEM;
+               goto out;
+       }
+
+       dev_user_devtype.module = THIS_MODULE;
+       if (scst_register_virtual_dev_driver(&dev_user_devtype) < 0) {
+               res = -ENODEV;
+               goto out_cache;
+       }
+
+       res = scst_dev_handler_build_std_proc(&dev_user_devtype);
+       if (res != 0)
+               goto out_unreg;
+
+       dev_user_sysfs_class = class_create(THIS_MODULE, DEV_USER_NAME);
+       if (IS_ERR(dev_user_sysfs_class)) {
+               printk(KERN_ERR "Unable create sysfs class for SCST user "
+                       "space handler\n");
+               res = PTR_ERR(dev_user_sysfs_class);
+               goto out_proc;
+       }
+
+       res = register_chrdev(DEV_USER_MAJOR, DEV_USER_NAME, &dev_user_fops);
+       if (res) {
+               printk(KERN_ERR "Unable to get major %d for SCSI tapes\n",
+                      DEV_USER_MAJOR);
+               goto out_class;
+       }
+
+       class_member = class_device_create(dev_user_sysfs_class, NULL,
+               MKDEV(DEV_USER_MAJOR, 0), NULL, DEV_USER_NAME);
+       if (IS_ERR(class_member)) {
+               res = PTR_ERR(class_member);
+               goto out_chrdev;
+       }
+
+       cleanup_thread = kthread_run(dev_user_cleanup_thread, NULL,
+               "scst_usr_cleanupd");
+       if (IS_ERR(cleanup_thread)) {
+               res = PTR_ERR(cleanup_thread);
+               PRINT_ERROR_PR("kthread_create() failed: %d", res);
+               goto out_dev;
+       }
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+
+out_dev:
+       class_device_destroy(dev_user_sysfs_class, MKDEV(DEV_USER_MAJOR, 0));
+
+out_chrdev:
+       unregister_chrdev(DEV_USER_MAJOR, DEV_USER_NAME);
+
+out_class:
+       class_destroy(dev_user_sysfs_class);
+
+out_proc:
+       scst_dev_handler_destroy_std_proc(&dev_user_devtype);
+
+out_unreg:
+       scst_unregister_dev_driver(&dev_user_devtype);
+
+out_cache:
+       kmem_cache_destroy(user_cmd_cachep);
+       goto out;
+}
+
+static void __exit exit_scst_user(void)
+{
+       int rc;
+
+       TRACE_ENTRY();
+
+       rc = kthread_stop(cleanup_thread);
+       if (rc < 0) {
+               TRACE_MGMT_DBG("kthread_stop() failed: %d", rc);
+       }
+
+       unregister_chrdev(DEV_USER_MAJOR, DEV_USER_NAME);
+       class_device_destroy(dev_user_sysfs_class, MKDEV(DEV_USER_MAJOR, 0));
+       class_destroy(dev_user_sysfs_class);
+
+       scst_dev_handler_destroy_std_proc(&dev_user_devtype);
+       scst_unregister_virtual_dev_driver(&dev_user_devtype);
+
+       kmem_cache_destroy(user_cmd_cachep);
+
+       TRACE_EXIT();
+       return;
+}
+
+module_init(init_scst_user);
+module_exit(exit_scst_user);
+
+MODULE_AUTHOR("Vladislav Bolkhovitin");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Virtual user space device handler for SCST");
+MODULE_VERSION(SCST_VERSION_STRING);
+MODULE_ALIAS_CHARDEV_MAJOR(DEV_USER_MAJOR);
index 1f96800..03a79fc 100644 (file)
@@ -178,10 +178,10 @@ static int vcdrom_write_proc(char *buffer, char **start, off_t offset,
 #define VDISK_TYPE {                   \
   name:         VDISK_NAME,            \
   type:         TYPE_DISK,             \
+  threads_num: -1,                     \
   parse_atomic: 1,                     \
   exec_atomic:  0,                     \
   dev_done_atomic: 1,                  \
-  dedicated_thread: 1,                 \
   attach:       vdisk_attach,          \
   detach:       vdisk_detach,          \
   attach_tgt:   vdisk_attach_tgt,      \
@@ -192,13 +192,30 @@ static int vcdrom_write_proc(char *buffer, char **start, off_t offset,
   write_proc:   vdisk_write_proc,      \
 }
 
+#define VDISK_BLK_TYPE {               \
+  name:         VDISK_NAME "_blk",     \
+  type:         TYPE_DISK,             \
+  threads_num: 0,                      \
+  parse_atomic: 1,                     \
+  exec_atomic:  0,                     \
+  dev_done_atomic: 1,                  \
+  no_proc: 1,                          \
+  inc_expected_sn_on_done: 1,          \
+  attach:       vdisk_attach,          \
+  detach:       vdisk_detach,          \
+  attach_tgt:   vdisk_attach_tgt,      \
+  detach_tgt:   vdisk_detach_tgt,      \
+  parse:        vdisk_parse,           \
+  exec:         vdisk_do_job,          \
+}
+
 #define VCDROM_TYPE {                  \
   name:         VCDROM_NAME,           \
   type:         TYPE_ROM,              \
+  threads_num: -1,                     \
   parse_atomic: 1,                     \
   exec_atomic:  0,                     \
   dev_done_atomic: 1,                  \
-  dedicated_thread: 1,                 \
   attach:       vdisk_attach,          \
   detach:       vdisk_detach,          \
   attach_tgt:   vdisk_attach_tgt,      \
@@ -214,6 +231,7 @@ static LIST_HEAD(vdisk_dev_list);
 static LIST_HEAD(vcdrom_dev_list);
 
 static struct scst_dev_type vdisk_devtype = VDISK_TYPE;
+static struct scst_dev_type vdisk_blk_devtype = VDISK_BLK_TYPE;
 static struct scst_dev_type vcdrom_devtype = VCDROM_TYPE;
 
 static char *vdisk_proc_help_string =
@@ -2497,9 +2515,14 @@ static int vdisk_write_proc(char *buffer, char **start, off_t offset,
                list_add_tail(&virt_dev->vdisk_dev_list_entry,
                                  &vdisk_dev_list);
 
-               virt_dev->virt_id =
-                       scst_register_virtual_device(&vdisk_devtype,
-                                                virt_dev->name);
+               if (virt_dev->blockio)
+                       virt_dev->virt_id =
+                               scst_register_virtual_device(&vdisk_blk_devtype,
+                                                        virt_dev->name);
+               else
+                       virt_dev->virt_id =
+                               scst_register_virtual_device(&vdisk_devtype,
+                                                        virt_dev->name);
                if (virt_dev->virt_id < 0) {
                        res = virt_dev->virt_id;
                        goto out_free_vpath;
@@ -2999,13 +3022,14 @@ static int __init init_scst_vdisk(struct scst_dev_type *devtype)
        if (res < 0)
                goto out;
 
-       res = scst_dev_handler_build_std_proc(devtype);
-       if (res < 0)
-               goto out_unreg;
+       if (!devtype->no_proc) {
+               res = scst_dev_handler_build_std_proc(devtype);
+               if (res < 0)
+                       goto out_unreg;
 
-       res = vdisk_proc_help_build(devtype);
-       if (res < 0) {
-               goto out_destroy_proc;
+               res = vdisk_proc_help_build(devtype);
+               if (res < 0)
+                       goto out_destroy_proc;
        }
 
 out:
@@ -3013,7 +3037,8 @@ out:
        return res;
 
 out_destroy_proc:
-       scst_dev_handler_destroy_std_proc(devtype);
+       if (!devtype->no_proc)
+               scst_dev_handler_destroy_std_proc(devtype);
 
 out_unreg:
        scst_unregister_virtual_dev_driver(devtype);
@@ -3046,8 +3071,10 @@ static void __exit exit_scst_vdisk(struct scst_dev_type *devtype,
        }
        up(&scst_vdisk_mutex);
 
-       vdisk_proc_help_destroy(devtype);
-       scst_dev_handler_destroy_std_proc(devtype);
+       if (!devtype->no_proc) {
+               vdisk_proc_help_destroy(devtype);
+               scst_dev_handler_destroy_std_proc(devtype);
+       }
 
        scst_unregister_virtual_dev_driver(devtype);
 
@@ -3057,7 +3084,7 @@ static void __exit exit_scst_vdisk(struct scst_dev_type *devtype,
 
 static int __init init_scst_vdisk_driver(void)
 {
-       int res;
+       int res, num_threads;
 
        vdisk_thr_cachep = kmem_cache_create("vdisk_thr_data",
                sizeof(struct scst_vdisk_thr), 0, VDISK_SLAB_FLAGS, NULL,
@@ -3067,10 +3094,18 @@ static int __init init_scst_vdisk_driver(void)
                goto out;
        }
 
+       num_threads = num_online_cpus() + 2;
+       vdisk_devtype.threads_num = num_threads;
+       vcdrom_devtype.threads_num = num_threads;
+
        res = init_scst_vdisk(&vdisk_devtype);
        if (res != 0)
                goto out_free_slab;
 
+       res = init_scst_vdisk(&vdisk_blk_devtype);
+       if (res != 0)
+               goto out_free_vdisk;
+
        res = init_scst_vdisk(&vcdrom_devtype);
        if (res != 0)
                goto out_err;
@@ -3079,6 +3114,9 @@ out:
        return res;
 
 out_err:
+       exit_scst_vdisk(&vdisk_blk_devtype, &vdisk_dev_list);
+
+out_free_vdisk:
        exit_scst_vdisk(&vdisk_devtype, &vdisk_dev_list);
 
 out_free_slab:
@@ -3088,6 +3126,7 @@ out_free_slab:
 
 static void __exit exit_scst_vdisk_driver(void)
 {
+       exit_scst_vdisk(&vdisk_blk_devtype, &vdisk_dev_list);
        exit_scst_vdisk(&vdisk_devtype, &vdisk_dev_list);
        exit_scst_vdisk(&vcdrom_devtype, &vcdrom_dev_list);
        kmem_cache_destroy(vdisk_thr_cachep);
index 853346c..84d5067 100644 (file)
@@ -255,7 +255,8 @@ out_up:
        return;
 }
 
-struct scst_tgt *scst_register(struct scst_tgt_template *vtt)
+struct scst_tgt *scst_register(struct scst_tgt_template *vtt,
+       const char *target_name)
 {
        struct scst_tgt *tgt;
 
@@ -281,27 +282,46 @@ struct scst_tgt *scst_register(struct scst_tgt_template *vtt)
        scst_suspend_activity();
        down(&scst_mutex);
 
-       if (scst_build_proc_target_entries(tgt) < 0) {
-               kfree(tgt);
-               tgt = NULL;
-               goto out_up;
-       } else
+       if (target_name != NULL) {
+               int len = strlen(target_name) + 1 +
+                       strlen(SCST_DEFAULT_ACG_NAME) + 1;
+
+               tgt->default_group_name = kmalloc(len, GFP_KERNEL);
+               if (tgt->default_group_name == NULL) {
+                       TRACE(TRACE_OUT_OF_MEM, "%s", "Allocation of default "
+                               "group name failed");
+                       goto out_free_err;
+               }
+               sprintf(tgt->default_group_name, "%s_%s", SCST_DEFAULT_ACG_NAME,
+                       target_name);
+       }
+
+       if (scst_build_proc_target_entries(tgt) < 0)
+               goto out_free_name;
+       else
                list_add_tail(&tgt->tgt_list_entry, &vtt->tgt_list);
 
        up(&scst_mutex);
        scst_resume_activity();
 
-       PRINT_INFO_PR("Target for template %s registered successfully",
-               vtt->name);
+       PRINT_INFO_PR("Target %s for template %s registered successfully",
+               target_name, vtt->name);
 
 out:
        TRACE_EXIT();
        return tgt;
 
-out_up:
+out_free_name:
+       if (tgt->default_group_name)
+               kfree(tgt->default_group_name);
+
+out_free_err:
        up(&scst_mutex);
        scst_resume_activity();
 
+       kfree(tgt);
+       tgt = NULL;
+
 out_err:
        PRINT_ERROR_PR("Failed to register target for template %s", vtt->name);
        goto out;
@@ -344,6 +364,9 @@ void scst_unregister(struct scst_tgt *tgt)
 
        scst_cleanup_proc_target_entries(tgt);
 
+       if (tgt->default_group_name)
+               kfree(tgt->default_group_name);
+
        up(&scst_mutex);
        scst_resume_activity();
 
index 69a57eb..aa3d1ed 100644 (file)
@@ -314,33 +314,50 @@ out:
 
 static int scst_create_tgt_threads(struct scst_tgt_dev *tgt_dev)
 {
-       int res = 0;
+       int i, res = 0;
+       int threads_num = tgt_dev->dev->handler->threads_num +
+               tgt_dev->sess->tgt->tgtt->threads_num;
        static atomic_t major = ATOMIC_INIT(0);
+       int N, n = 0;
        char nm[12];
 
        TRACE_ENTRY();
 
-       if ( !tgt_dev->dev->handler->dedicated_thread)
+       if (tgt_dev->dev->handler->threads_num < 0)
+               threads_num = 0;
+
+       if (threads_num == 0)
                goto out;
 
        spin_lock_init(&tgt_dev->cmd_lists.cmd_list_lock);
        INIT_LIST_HEAD(&tgt_dev->cmd_lists.active_cmd_list);
        init_waitqueue_head(&tgt_dev->cmd_lists.cmd_list_waitQ);
 
-       /* 
-        * Only ONE thread must be run here, otherwise the commands could
-        * be executed out of order !!
-        */
+       if (tgt_dev->dev->handler->threads_num == 0)
+               threads_num += num_online_cpus();
 
-       strncpy(nm, tgt_dev->dev->handler->name, ARRAY_SIZE(nm)-1);
-       nm[ARRAY_SIZE(nm)-1] = '\0';
-       tgt_dev->thread = kthread_run(scst_cmd_thread, &tgt_dev->cmd_lists,
-               "%sd%d", nm, atomic_inc_return(&major));
-       if (IS_ERR(tgt_dev->thread)) {
-               res = PTR_ERR(tgt_dev->thread);
-               PRINT_ERROR_PR("kthread_create() failed: %d", res);
-               tgt_dev->thread = NULL;
-               goto out;
+       N = atomic_inc_return(&major);
+
+       for (i = 0; i < threads_num; i++) {
+               struct scst_cmd_thread_t *thr;
+
+               thr = kmalloc(sizeof(*thr), GFP_KERNEL);
+               if (!thr) {
+                       res = -ENOMEM;
+                       PRINT_ERROR_PR("fail to allocate thr %d", res);
+                       goto out;
+               }
+               strncpy(nm, tgt_dev->dev->handler->name, ARRAY_SIZE(nm)-1);
+               nm[ARRAY_SIZE(nm)-1] = '\0';
+               thr->cmd_thread = kthread_run(scst_cmd_thread,
+                       &tgt_dev->cmd_lists, "%sd%d_%d", nm, N, n++);
+               if (IS_ERR(thr->cmd_thread)) {
+                       res = PTR_ERR(thr->cmd_thread);
+                       PRINT_ERROR_PR("kthread_create() failed: %d", res);
+                       kfree(thr);
+                       goto out;
+               }
+               list_add(&thr->thread_list_entry, &tgt_dev->threads_list);
        }
 
        down(&scst_suspend_mutex);
@@ -357,16 +374,21 @@ out:
 
 static void scst_stop_tgt_threads(struct scst_tgt_dev *tgt_dev)
 {
-       int rc;
+       struct scst_cmd_thread_t *ct, *tmp;
 
        TRACE_ENTRY();
 
-       if (tgt_dev->thread == NULL)
+       if (list_empty(&tgt_dev->threads_list))
                goto out;
 
-       rc = kthread_stop(tgt_dev->thread);
-       if (rc < 0) {
-               TRACE_MGMT_DBG("kthread_stop() failed: %d", rc);
+       list_for_each_entry_safe(ct, tmp, &tgt_dev->threads_list,
+                               thread_list_entry) {
+               int rc = kthread_stop(ct->cmd_thread);
+               if (rc < 0) {
+                       TRACE_MGMT_DBG("kthread_stop() failed: %d", rc);
+               }
+               list_del(&ct->thread_list_entry);
+               kfree(ct);
        }
 
        if (tgt_dev->p_cmd_lists == &tgt_dev->cmd_lists) {
@@ -475,6 +497,7 @@ static struct scst_tgt_dev *scst_alloc_add_tgt_dev(struct scst_session *sess,
        tgt_dev->cur_sn_slot = &tgt_dev->sn_slots[0];
        for(i = 0; i < (int)ARRAY_SIZE(tgt_dev->sn_slots); i++)
                atomic_set(&tgt_dev->sn_slots[i], 0);
+       INIT_LIST_HEAD(&tgt_dev->threads_list);
 
        if (dev->handler->parse_atomic && 
            sess->tgt->tgtt->preprocessing_done_atomic) {
@@ -2955,9 +2978,7 @@ static const int tm_dbg_on_state_num_passes[] = { 5, 1, 0x7ffffff };
 void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev,
        struct scst_acg_dev *acg_dev)
 {
-       if (((acg_dev->acg == scst_default_acg) || 
-            (acg_dev->acg == tgt_dev->sess->tgt->default_acg)) && 
-           (acg_dev->lun == 0)) {
+       if ((acg_dev->acg == scst_default_acg) && (acg_dev->lun == 0)) {
                unsigned long flags;
                spin_lock_irqsave(&scst_tm_dbg_lock, flags);
                if (!tm_dbg_flags.tm_dbg_active) {
index 61648a2..72ad238 100644 (file)
@@ -179,7 +179,6 @@ extern spinlock_t scst_mcmd_lock;
 /* The following lists protected by scst_mcmd_lock */
 extern struct list_head scst_active_mgmt_cmd_list;
 extern struct list_head scst_delayed_mgmt_cmd_list;
-
 extern wait_queue_head_t scst_mgmt_cmd_list_waitQ;
 
 struct scst_tasklet
@@ -252,6 +251,9 @@ void scst_free_device(struct scst_device *tgt_dev);
 
 struct scst_acg *scst_alloc_add_acg(const char *acg_name);
 int scst_destroy_acg(struct scst_acg *acg);
+int scst_proc_group_add_tree(struct scst_acg *acg, const char *name);
+void scst_proc_del_acg_tree(struct proc_dir_entry *acg_proc_root,
+       const char *name);
 
 int scst_sess_alloc_tgt_devs(struct scst_session *sess);
 void scst_sess_free_tgt_devs(struct scst_session *sess);
index 9e37e0b..033e0fb 100644 (file)
@@ -408,17 +408,17 @@ static void __exit scst_proc_cleanup_module_log(void)
 #endif
 }
 
-static int scst_proc_group_add_tree(struct scst_acg *acg, const char *p)
+int scst_proc_group_add_tree(struct scst_acg *acg, const char *name)
 {
        int res = 0;
        struct proc_dir_entry *generic;
 
        TRACE_ENTRY();
 
-       acg->acg_proc_root = proc_mkdir(p, scst_proc_groups_root);
+       acg->acg_proc_root = proc_mkdir(name, scst_proc_groups_root);
        if (acg->acg_proc_root == NULL) {
                PRINT_ERROR_PR("Not enough memory to register %s entry in "
-                              "/proc/%s/%s", p, SCST_PROC_ENTRY_NAME,
+                              "/proc/%s/%s", name, SCST_PROC_ENTRY_NAME,
                               SCST_PROC_GROUPS_ENTRY_NAME);
                goto out;
        }
@@ -431,7 +431,7 @@ static int scst_proc_group_add_tree(struct scst_acg *acg, const char *p)
                PRINT_ERROR_PR("cannot init /proc/%s/%s/%s/%s",
                               SCST_PROC_ENTRY_NAME,
                               SCST_PROC_GROUPS_ENTRY_NAME,
-                              p, SCST_PROC_GROUPS_DEVICES_ENTRY_NAME);
+                              name, SCST_PROC_GROUPS_DEVICES_ENTRY_NAME);
                res = -ENOMEM;
                goto out_remove;
        }
@@ -444,7 +444,7 @@ static int scst_proc_group_add_tree(struct scst_acg *acg, const char *p)
                PRINT_ERROR_PR("cannot init /proc/%s/%s/%s/%s",
                               SCST_PROC_ENTRY_NAME,
                               SCST_PROC_GROUPS_ENTRY_NAME,
-                              p, SCST_PROC_GROUPS_USERS_ENTRY_NAME);
+                              name, SCST_PROC_GROUPS_USERS_ENTRY_NAME);
                res = -ENOMEM;
                goto out_remove1;
        }
@@ -458,11 +458,11 @@ out_remove1:
                          acg->acg_proc_root);
 
 out_remove:
-       remove_proc_entry(p, scst_proc_groups_root);
+       remove_proc_entry(name, scst_proc_groups_root);
        goto out;
 }
 
-static void scst_proc_del_acg_tree(struct proc_dir_entry *acg_proc_root,
+void scst_proc_del_acg_tree(struct proc_dir_entry *acg_proc_root,
        const char *name)
 {
        TRACE_ENTRY();
@@ -1610,10 +1610,6 @@ static int scst_version_info_show(struct seq_file *seq, void *v)
        seq_printf(seq, "Strict serializing enabled\n");
 #endif
 
-#ifdef SINGLE_DEFAULT_GROUP
-       seq_printf(seq, "Single default group\n");
-#endif
-
 #ifdef EXTRACHECKS
        seq_printf(seq, "EXTRACHECKS\n");
 #endif
index 91230bc..aea1652 100644 (file)
@@ -137,7 +137,7 @@ out_redirect:
        if (cmd->preprocessing_only) {
                /*
                 * Poor man solution for single threaded targets, where 
-                * blocking receiver at least somtimes means blocking all.
+                * blocking receiver at least sometimes means blocking all.
                 */
                sBUG_ON(context != SCST_CONTEXT_DIRECT);
                scst_set_busy(cmd);
@@ -598,14 +598,6 @@ static int scst_prepare_space(struct scst_cmd *cmd)
 
                TRACE_MEM("%s", "Custom tgt data buf allocation requested");
 
-               if (unlikely(cmd->data_buf_alloced)) {
-                       PRINT_ERROR_PR("Target driver %s requested own data "
-                               "allocation, but dev handler %s already "
-                               "allocated them", cmd->tgtt->name,
-                               cmd->dev->handler->name);
-                       goto out_error;
-               }
-
                r = cmd->tgtt->alloc_data_buf(cmd);
                if (r > 0)
                        goto alloc;
@@ -618,15 +610,15 @@ static int scst_prepare_space(struct scst_cmd *cmd)
                                        cmd->bufflen);
                                goto out_error;
                        }
-               }
-               goto check;
+               } else
+                       goto check;
        }
 
 alloc:
-       if (!cmd->data_buf_alloced) {
-               r = scst_check_mem(cmd);
-               if (unlikely(r != 0))
-                       goto out;
+       r = scst_check_mem(cmd);
+       if (unlikely(r != 0))
+               goto out;
+       else if (!cmd->data_buf_alloced) {
                r = scst_alloc_space(cmd);
        } else {
                TRACE_MEM("%s", "data_buf_alloced set, returning");
@@ -4082,10 +4074,31 @@ out:
        return res;
 }
 
+/* scst_mutex supposed to be held */
+static struct scst_acg *scst_find_acg_by_name(const char *acg_name)
+{
+       struct scst_acg *acg, *res = NULL;
+
+       TRACE_ENTRY();
+       
+       list_for_each_entry(acg, &scst_acg_list, scst_acg_list_entry) {
+               if (strcmp(acg->acg_name, acg_name) == 0) {
+                       TRACE_DBG("Access control group %s found", 
+                               acg->acg_name);
+                       res = acg;
+                       goto out;
+               }
+       }
+
+out:   
+       TRACE_EXIT_HRES(res);
+       return res;
+}
+
 static int scst_init_session(struct scst_session *sess)
 {
        int res = 0;
-       struct scst_acg *acg;
+       struct scst_acg *acg = NULL;
        struct scst_cmd *cmd;
        struct scst_mgmt_cmd *mcmd, *tm;
        int mwake = 0;
@@ -4095,17 +4108,16 @@ static int scst_init_session(struct scst_session *sess)
        scst_suspend_activity();        
        down(&scst_mutex);
 
-       if (sess->initiator_name) {
+       if (sess->initiator_name)
                acg = scst_find_acg(sess->initiator_name);
-               if (acg == NULL) {
-                       PRINT_INFO_PR("Name %s not found, using default group",
-                               sess->initiator_name);
-                       acg = scst_default_acg;
-               }
-       }
-       else
+       if ((acg == NULL) && (sess->tgt->default_group_name != NULL))
+               acg = scst_find_acg_by_name(sess->tgt->default_group_name);
+       if (acg == NULL)
                acg = scst_default_acg;
 
+       PRINT_INFO_PR("Using security group \"%s\" for initiator \"%s\"",
+               acg->acg_name, sess->initiator_name);
+
        sess->acg = acg;
        TRACE_DBG("Assigning session %p to acg %s", sess, acg->acg_name);
        list_add_tail(&sess->acg_sess_list_entry, &acg->acg_sess_list);
diff --git a/usr/fileio/Makefile b/usr/fileio/Makefile
new file mode 100644 (file)
index 0000000..c77e844
--- /dev/null
@@ -0,0 +1,79 @@
+#
+#  SCSI target mid-level makefile
+#  
+#  Copyright (C) 2007 Vladislav Bolkhovitin <vst@vlnb.net>
+#  
+#  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.
+
+SRCS_F = fileio.c common.c debug.c
+OBJS_F = $(SRCS_F:.c=.o)
+
+#SRCS_C = 
+#OBJS_C = $(SRCS_C:.c=.o)
+
+SCST_INC_DIR := ../../scst/include
+INSTALL_DIR := /usr/local/bin/scst
+
+CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes \
+       -I$(SCST_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \
+       -D__USE_LARGEFILE64
+PROGS = fileio_tgt
+LIBS = -lpthread
+
+CFLAGS += -DEXTRACHECKS
+#CFLAGS += -DTRACING
+CFLAGS += -DDEBUG -g
+
+#CFLAGS += -DDEBUG_NOMEM
+#CFLAGS += -DDEBUG_SENSE
+#CFLAGS += -DDEBUG_TM_IGNORE
+#CFLAGS += -DDEBUG_TM_IGNORE -DDEBUG_TM_FN_IGNORE
+#CFLAGS += -DDEBUG_TM_IGNORE_ALL
+
+all: $(PROGS)
+
+fileio_tgt: .depend_f $(OBJS_F)
+       $(CC) $(OBJS_F) $(LIBS) -o $@
+
+#cdrom_tgt: .depend_c  $(OBJS_C)
+#      $(CC) $(OBJS_C) $(LIBS) -o $@
+
+ifeq (.depend_f,$(wildcard .depend_f))
+-include .depend_f
+endif
+
+#ifeq (.depend_c,$(wildcard .depend_c))
+#-include .depend_c
+#endif
+
+%.o: %.c Makefile
+       $(CC) -c -o $(@) $(CFLAGS) $(<)
+
+.depend_f:
+       $(CC) -M $(CFLAGS) $(SRCS_F) >$(@)
+
+#.depend_c:
+#      $(CC) -M $(CFLAGS) $(SRCS_C) >$(@)
+
+install: all
+       install -d $(INSTALL_DIR)
+       install -m 755 $(PROGS) $(INSTALL_DIR)
+
+uninstall:
+       rm -f $(INSTALL_DIR)/$(PROGS)
+       rm -rf $(INSTALL_DIR)
+
+clean:
+       rm -f *.o $(PROGS) .depend*
+
+extraclean: clean
+
+.PHONY: all install uninstall clean extraclean
diff --git a/usr/fileio/common.c b/usr/fileio/common.c
new file mode 100644 (file)
index 0000000..7c5dfd2
--- /dev/null
@@ -0,0 +1,1710 @@
+/*
+ *  common.c
+ *  
+ *  Copyright (C) 2007 Vladislav Bolkhovitin <vst@vlnb.net>
+ *  
+ *  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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <getopt.h>
+
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+
+#include <pthread.h>
+
+#include "common.h"
+
+static void exec_inquiry(struct vdisk_cmd *vcmd);
+static void exec_mode_sense(struct vdisk_cmd *vcmd);
+static void exec_mode_select(struct vdisk_cmd *vcmd);
+static void exec_read_capacity(struct vdisk_cmd *vcmd);
+static void exec_read_capacity16(struct vdisk_cmd *vcmd);
+static void exec_read_toc(struct vdisk_cmd *vcmd);
+static void exec_prevent_allow_medium_removal(struct vdisk_cmd *vcmd);
+static int exec_fsync(struct vdisk_cmd *vcmd);
+static void exec_read(struct vdisk_cmd *vcmd, loff_t loff);
+static void exec_write(struct vdisk_cmd *vcmd, loff_t loff);
+static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff);
+
+static inline void set_cmd_error_status(struct scst_user_scsi_cmd_reply_exec *reply,
+       int status)
+{
+       reply->status = status;
+       reply->resp_data_len = 0;
+       return;
+}
+
+static int set_sense(uint8_t *buffer, int len, int key, int asc, int ascq)
+{
+       int res = 14;
+       EXTRACHECKS_BUG_ON(len < res);
+       memset(buffer, 0, res);
+       buffer[0] = 0x70;       /* Error Code                   */
+       buffer[2] = key;        /* Sense Key                    */
+       buffer[7] = 0x0a;       /* Additional Sense Length      */
+       buffer[12] = asc;       /* ASC                          */
+       buffer[13] = ascq;      /* ASCQ                         */
+       TRACE_BUFFER("Sense set", buffer, res);
+       return res;
+}
+
+void set_cmd_error(struct vdisk_cmd *vcmd, int key, int asc, int ascq)
+{
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+
+       TRACE_ENTRY();
+
+       EXTRACHECKS_BUG_ON(vcmd->cmd->subcode != SCST_USER_EXEC);
+
+       set_cmd_error_status(reply, SAM_STAT_CHECK_CONDITION);
+       reply->sense_len = set_sense(vcmd->sense, sizeof(vcmd->sense), key,
+               asc, ascq);
+       reply->psense_buffer = (unsigned long)vcmd->sense;
+
+       TRACE_EXIT();
+       return;
+}
+
+void set_busy(struct scst_user_scsi_cmd_reply_exec *reply)
+{
+       TRACE_ENTRY();
+
+       set_cmd_error_status(reply, SAM_STAT_TASK_SET_FULL);
+       TRACE_MGMT_DBG("%s", "Sending QUEUE_FULL status");
+
+       TRACE_EXIT();
+       return;
+}
+
+static int do_parse(struct vdisk_cmd *vcmd)
+{
+       int res = 0;
+       struct scst_user_scsi_cmd_parse *cmd = &vcmd->cmd->parse_cmd;
+       struct scst_user_scsi_cmd_reply_parse *reply = &vcmd->reply->parse_reply;
+
+       TRACE_ENTRY();
+
+       memset(reply, 0, sizeof(*reply));
+       vcmd->reply->cmd_h = vcmd->cmd->cmd_h;
+       vcmd->reply->subcode = vcmd->cmd->subcode;
+
+       if (cmd->expected_values_set == 0) {
+               PRINT_ERROR_PR("%s", "Oops, expected values are not set");
+               reply->bufflen = -1; /* invalid value */
+               goto out;
+       }
+
+       reply->queue_type = cmd->queue_type;
+       reply->data_direction = cmd->expected_data_direction;
+       reply->data_len = cmd->expected_transfer_len;
+       reply->bufflen = cmd->expected_transfer_len;
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+struct vdisk_tgt_dev *find_tgt_dev(struct vdisk_dev *dev, uint64_t sess_h)
+{
+       unsigned int i;
+       struct vdisk_tgt_dev *res = NULL;
+
+       for(i = 0; i < ARRAY_SIZE(dev->tgt_devs); i++) {
+               if (dev->tgt_devs[i].sess_h == sess_h) {
+                       res = &dev->tgt_devs[i];
+                       break;
+               }
+       }
+       return res;
+}
+
+struct vdisk_tgt_dev *find_empty_tgt_dev(struct vdisk_dev *dev)
+{
+       unsigned int i;
+       struct vdisk_tgt_dev *res = NULL;
+
+       for(i = 0; i < ARRAY_SIZE(dev->tgt_devs); i++) {
+               if (dev->tgt_devs[i].sess_h == 0) {
+                       res = &dev->tgt_devs[i];
+                       break;
+               }
+       }
+       return res;
+}
+
+static inline int sync_queue_type(enum scst_cmd_queue_type qt)
+{
+       switch(qt) {
+               case SCST_CMD_QUEUE_ORDERED:
+               case SCST_CMD_QUEUE_HEAD_OF_QUEUE:
+                       return 1;
+               default:
+                       return 0;
+       }
+}
+
+static inline int need_pre_sync(enum scst_cmd_queue_type cur,
+       enum scst_cmd_queue_type last)
+{
+       if (sync_queue_type(cur))
+               if (!sync_queue_type(last))
+                       return 1;
+       return 0;
+}
+
+static int do_exec(struct vdisk_cmd *vcmd)
+{
+       int res = 0;
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       uint64_t lba_start = 0;
+       loff_t data_len = 0;
+       uint8_t *cdb = cmd->cdb;
+       int opcode = cdb[0];
+       loff_t loff;
+       int fua = 0;
+
+       TRACE_ENTRY();
+
+       memset(reply, 0, sizeof(*reply));
+       vcmd->reply->cmd_h = vcmd->cmd->cmd_h;
+       vcmd->reply->subcode = vcmd->cmd->subcode;
+       reply->reply_type = SCST_EXEC_REPLY_COMPLETED;
+
+#ifdef DEBUG_SENSE
+       if ((random() % 100000) == 75) {
+               set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+               goto out;
+       }
+#endif
+
+#ifdef DEBUG_TM_IGNORE
+       if (dev->debug_tm_ignore && (random() % 200000) == 75) {
+               TRACE_MGMT_DBG("Ignore cmd op %x (h=%d)", cdb[0],
+                       vcmd->cmd->cmd_h);
+               res = 150;
+               goto out;
+       }
+#endif
+
+       if ((cmd->pbuf == 0) && (cmd->alloc_len != 0)) {
+#ifdef DEBUG_NOMEM
+               if ((random() % 100) == 75)
+                       cmd->pbuf = 0;
+               else
+#endif
+                       cmd->pbuf = (unsigned long)dev->alloc_fn(cmd->alloc_len);
+               TRACE_MEM("Buf %Lx alloced, len %d", cmd->pbuf, cmd->alloc_len);
+               reply->pbuf = cmd->pbuf;
+               if (cmd->pbuf == 0) {
+                       TRACE(TRACE_OUT_OF_MEM, "Unable to allocate buffer "
+                               "(len %d)", cmd->alloc_len);
+#ifndef DEBUG_NOMEM
+                       set_busy(reply);
+#endif
+                       goto out;
+               }
+       }
+
+       switch (opcode) {
+       case READ_6:
+       case WRITE_6:
+       case VERIFY_6:
+               lba_start = (((cdb[1] & 0x1f) << (BYTE * 2)) +
+                            (cdb[2] << (BYTE * 1)) +
+                            (cdb[3] << (BYTE * 0)));
+               data_len = cmd->bufflen;
+               break;
+       case READ_10:
+       case READ_12:
+       case WRITE_10:
+       case WRITE_12:
+       case VERIFY:
+       case WRITE_VERIFY:
+       case WRITE_VERIFY_12:
+       case VERIFY_12:
+               lba_start |= ((uint64_t)cdb[2]) << 24;
+               lba_start |= ((uint64_t)cdb[3]) << 16;
+               lba_start |= ((uint64_t)cdb[4]) << 8;
+               lba_start |= ((uint64_t)cdb[5]);
+               data_len = cmd->bufflen;
+               break;
+       case SYNCHRONIZE_CACHE:
+               lba_start |= ((uint64_t)cdb[2]) << 24;
+               lba_start |= ((uint64_t)cdb[3]) << 16;
+               lba_start |= ((uint64_t)cdb[4]) << 8;
+               lba_start |= ((uint64_t)cdb[5]);
+               data_len = ((cdb[7] << (BYTE * 1)) + (cdb[8] << (BYTE * 0))) 
+                               << dev->block_shift;
+               if (data_len == 0)
+                       data_len = dev->file_size - 
+                               ((loff_t)lba_start << dev->block_shift);
+               break;
+       case READ_16:
+       case WRITE_16:
+       case WRITE_VERIFY_16:
+       case VERIFY_16:
+               lba_start |= ((uint64_t)cdb[2]) << 56;
+               lba_start |= ((uint64_t)cdb[3]) << 48;
+               lba_start |= ((uint64_t)cdb[4]) << 40;
+               lba_start |= ((uint64_t)cdb[5]) << 32;
+               lba_start |= ((uint64_t)cdb[6]) << 16;
+               lba_start |= ((uint64_t)cdb[7]) << 8;
+               lba_start |= ((uint64_t)cdb[8]);
+               data_len = cmd->bufflen;
+               break;
+       }
+
+       loff = (loff_t)lba_start << dev->block_shift;
+       TRACE_DBG("cmd %d, buf %Lx, lba_start %Ld, loff %Ld, data_len %Ld",
+               vcmd->cmd->cmd_h, cmd->pbuf, lba_start, (uint64_t)loff,
+               (uint64_t)data_len);
+       if ((loff < 0) || (data_len < 0) || ((loff + data_len) > dev->file_size)) {
+               PRINT_INFO_PR("Access beyond the end of the device "
+                       "(%lld of %lld, len %Ld)", (uint64_t)loff, 
+                       (uint64_t)dev->file_size, (uint64_t)data_len);
+               set_cmd_error(vcmd, SCST_LOAD_SENSE(
+                               scst_sense_block_out_range_error));
+               goto out;
+       }
+
+       switch (opcode) {
+       case WRITE_10:
+       case WRITE_12:
+       case WRITE_16:
+               fua = (cdb[1] & 0x8);
+               if (cdb[1] & 0x8) {
+                       TRACE(TRACE_ORDER, "FUA(%d): loff=%Ld, "
+                               "data_len=%Ld", fua, (uint64_t)loff,
+                               (uint64_t)data_len);
+               }
+               break;
+       }
+
+       switch (opcode) {
+       case READ_6:
+       case READ_10:
+       case READ_12:
+       case READ_16:
+               exec_read(vcmd, loff);
+               break;
+       case WRITE_6:
+       case WRITE_10:
+       case WRITE_12:
+       case WRITE_16:
+               if (!dev->rd_only_flag) {
+                       int do_fsync = sync_queue_type(cmd->queue_type);
+                       struct vdisk_tgt_dev *tgt_dev;
+                       enum scst_cmd_queue_type last_queue_type;
+
+                       tgt_dev = find_tgt_dev(dev, cmd->sess_h);
+                       if (tgt_dev == NULL) {
+                               PRINT_ERROR_PR("Session %Lx not found",
+                                       cmd->sess_h);
+                               set_cmd_error(vcmd,
+                                   SCST_LOAD_SENSE(scst_sense_hardw_error));
+                               goto out;
+                       }
+
+                       last_queue_type = tgt_dev->last_write_cmd_queue_type;
+                       tgt_dev->last_write_cmd_queue_type = cmd->queue_type;
+                       if (need_pre_sync(cmd->queue_type, last_queue_type)) {
+                               TRACE(TRACE_ORDER, "ORDERED "
+                                       "WRITE(%d): loff=%Ld, data_len=%Ld",
+                                       cmd->queue_type, (uint64_t)loff,
+                                       (uint64_t)data_len);
+                               do_fsync = 1;
+                               if (exec_fsync(vcmd) != 0)
+                                       goto out;
+                       }
+                       exec_write(vcmd, loff);
+                       /* O_SYNC flag is used for WT devices */
+                       if (do_fsync || fua)
+                               exec_fsync(vcmd);
+               } else {
+                       TRACE(TRACE_MINOR, "Attempt to write to read-only "
+                               "device %s", dev->name);
+                       set_cmd_error(vcmd,
+                               SCST_LOAD_SENSE(scst_sense_data_protect));
+               }
+               break;
+       case WRITE_VERIFY:
+       case WRITE_VERIFY_12:
+       case WRITE_VERIFY_16:
+               if (!dev->rd_only_flag) {
+                       int do_fsync = sync_queue_type(cmd->queue_type);
+                       struct vdisk_tgt_dev *tgt_dev;
+                       enum scst_cmd_queue_type last_queue_type;
+
+                       tgt_dev = find_tgt_dev(dev, cmd->sess_h);
+                       if (tgt_dev == NULL) {
+                               PRINT_ERROR_PR("Session %Lx not found",
+                                       cmd->sess_h);
+                               set_cmd_error(vcmd,
+                                   SCST_LOAD_SENSE(scst_sense_hardw_error));
+                               goto out;
+                       }
+
+                       last_queue_type = tgt_dev->last_write_cmd_queue_type;
+                       tgt_dev->last_write_cmd_queue_type = cmd->queue_type;
+                       if (need_pre_sync(cmd->queue_type, last_queue_type)) {
+                               TRACE(TRACE_ORDER, "ORDERED "
+                                       "WRITE_VERIFY(%d): loff=%Ld, data_len=%Ld",
+                                       cmd->queue_type, (uint64_t)loff,
+                                       (uint64_t)data_len);
+                               do_fsync = 1;
+                               if (exec_fsync(vcmd) != 0)
+                                       goto out;
+                       }
+                       exec_write(vcmd, loff);
+                       /* O_SYNC flag is used for WT devices */
+                       if (reply->status == 0)
+                               exec_verify(vcmd, loff);
+                       else if (do_fsync)
+                               exec_fsync(vcmd);
+               } else {
+                       TRACE(TRACE_MINOR, "Attempt to write to read-only "
+                               "device %s", dev->name);
+                       set_cmd_error(vcmd,
+                               SCST_LOAD_SENSE(scst_sense_data_protect));
+               }
+               break;
+       case SYNCHRONIZE_CACHE:
+       {
+               int immed = cdb[1] & 0x2;
+               TRACE(TRACE_ORDER, "SYNCHRONIZE_CACHE: "
+                       "loff=%Ld, data_len=%Ld, immed=%d", (uint64_t)loff,
+                       (uint64_t)data_len, immed);
+               if (immed) {
+                       /* ToDo: backgroung exec */
+                       exec_fsync(vcmd);
+                       break;
+               } else {
+                       exec_fsync(vcmd);
+                       break;
+               }
+       }
+       case VERIFY_6:
+       case VERIFY:
+       case VERIFY_12:
+       case VERIFY_16:
+               exec_verify(vcmd, loff);
+               break;
+       case MODE_SENSE:
+       case MODE_SENSE_10:
+               exec_mode_sense(vcmd);
+               break;
+       case MODE_SELECT:
+       case MODE_SELECT_10:
+               exec_mode_select(vcmd);
+               break;
+       case ALLOW_MEDIUM_REMOVAL:
+               exec_prevent_allow_medium_removal(vcmd);
+               break;
+       case READ_TOC:
+               exec_read_toc(vcmd);
+               break;
+       case START_STOP:
+               exec_fsync(vcmd/*, 0, dev->file_size*/);
+               break;
+       case RESERVE:
+       case RESERVE_10:
+       case RELEASE:
+       case RELEASE_10:
+       case TEST_UNIT_READY:
+               break;
+       case INQUIRY:
+               exec_inquiry(vcmd);
+               break;
+       case READ_CAPACITY:
+               exec_read_capacity(vcmd);
+               break;
+        case SERVICE_ACTION_IN:
+               if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16) {
+                       exec_read_capacity16(vcmd);
+                       break;
+               }
+               /* else go through */
+       case REPORT_LUNS:
+       default:
+               TRACE_DBG("Invalid opcode %d", opcode);
+               set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+               break;
+       }
+
+out:
+       TRACE_EXIT();
+       return res;
+}
+
+static int do_alloc_mem(struct vdisk_cmd *vcmd)
+{
+       struct scst_user_get_cmd *cmd = vcmd->cmd;
+       struct scst_user_reply_cmd *reply = vcmd->reply;
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       TRACE_MEM("Alloc mem (cmd %d, sess_h %Lx, cdb_len %d, "
+               "alloc_len %d, queue_type %d, data_direction %d)", cmd->cmd_h,
+               cmd->alloc_cmd.sess_h, cmd->alloc_cmd.cdb_len,
+               cmd->alloc_cmd.alloc_len, cmd->alloc_cmd.queue_type,
+               cmd->alloc_cmd.data_direction);
+
+       TRACE_BUFF_FLAG(TRACE_MEMORY, "CDB", cmd->alloc_cmd.cdb,
+               cmd->alloc_cmd.cdb_len);
+
+       memset(reply, 0, sizeof(*reply));
+       reply->cmd_h = cmd->cmd_h;
+       reply->subcode = cmd->subcode;
+#ifdef DEBUG_NOMEM
+       if ((random() % 100) == 75)
+               reply->alloc_reply.pbuf = 0;
+       else
+#endif
+               reply->alloc_reply.pbuf = (unsigned long)vcmd->dev->alloc_fn(
+                                               cmd->alloc_cmd.alloc_len);
+       TRACE_MEM("Buf %Lx alloced, len %d", reply->alloc_reply.pbuf,
+               cmd->alloc_cmd.alloc_len);
+       if (reply->alloc_reply.pbuf == 0) {
+               TRACE(TRACE_OUT_OF_MEM, "Unable to allocate buffer (len %d)",
+                       cmd->alloc_cmd.alloc_len);
+       }
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int do_cached_mem_free(struct vdisk_cmd *vcmd)
+{
+       struct scst_user_get_cmd *cmd = vcmd->cmd;
+       struct scst_user_reply_cmd *reply = vcmd->reply;
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       TRACE_MEM("Cached mem free (cmd %d, buf %Lx)", cmd->cmd_h,
+               cmd->on_cached_mem_free.pbuf);
+
+       free((void*)(unsigned long)cmd->on_cached_mem_free.pbuf);
+
+       memset(reply, 0, sizeof(*reply));
+       reply->cmd_h = cmd->cmd_h;
+       reply->subcode = cmd->subcode;
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int do_on_free_cmd(struct vdisk_cmd *vcmd)
+{
+       struct scst_user_get_cmd *cmd = vcmd->cmd;
+       struct scst_user_reply_cmd *reply = vcmd->reply;
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       TRACE_MEM("On free cmd (cmd %d, buf %Lx, buffer_cached %d)", cmd->cmd_h,
+               cmd->on_free_cmd.pbuf, cmd->on_free_cmd.buffer_cached);
+
+       if (!cmd->on_free_cmd.buffer_cached && (cmd->on_free_cmd.pbuf != 0)) {
+               TRACE_MEM("Freeing buf %Lx", cmd->on_free_cmd.pbuf);
+               free((void*)(unsigned long)cmd->on_free_cmd.pbuf);
+       }
+
+       memset(reply, 0, sizeof(*reply));
+       reply->cmd_h = cmd->cmd_h;
+       reply->subcode = cmd->subcode;
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int do_tm(struct vdisk_cmd *vcmd)
+{
+       struct scst_user_get_cmd *cmd = vcmd->cmd;
+       struct scst_user_reply_cmd *reply = vcmd->reply;
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       TRACE(TRACE_MGMT, "TM fn %d (sess_h %Lx, cmd_h_to_abort %d)",
+               cmd->tm_cmd.fn, cmd->tm_cmd.sess_h, cmd->tm_cmd.cmd_h_to_abort);
+
+       memset(reply, 0, sizeof(*reply));
+       reply->cmd_h = cmd->cmd_h;
+       reply->subcode = cmd->subcode;
+       reply->result = 0;
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int do_sess(struct vdisk_cmd *vcmd)
+{
+       struct scst_user_get_cmd *cmd = vcmd->cmd;
+       struct scst_user_reply_cmd *reply = vcmd->reply;
+       int res = 0;
+       struct vdisk_tgt_dev *tgt_dev;
+
+       TRACE_ENTRY();
+
+       /*
+        * We are guaranteed to have one and only one command at this point,
+        * which is ATTACH_SESS/DETACH_SESS, so no protection is needed
+        */
+
+       tgt_dev = find_tgt_dev(vcmd->dev, cmd->sess.sess_h);
+
+       if (cmd->subcode == SCST_USER_ATTACH_SESS) {
+               if (tgt_dev != NULL) {
+                       PRINT_ERROR_PR("Session %Lx already exists)",
+                               cmd->sess.sess_h);
+                       res = EEXIST;
+                       goto reply;
+               }
+
+               tgt_dev = find_empty_tgt_dev(vcmd->dev);
+               if (tgt_dev == NULL) {
+                       PRINT_ERROR_PR("Too many initiators, session %Lx refused)",
+                               cmd->sess.sess_h);
+                       res = ENOMEM;
+                       goto reply;
+               }
+
+               tgt_dev->sess_h = cmd->sess.sess_h;
+               tgt_dev->last_write_cmd_queue_type = SCST_CMD_QUEUE_SIMPLE;
+
+               PRINT_INFO_PR("Session from initiator %s attached (LUN %Lx, "
+                       "rd_only %d, sess_h %Lx)", cmd->sess.initiator_name,
+                       cmd->sess.lun, cmd->sess.rd_only, cmd->sess.sess_h);
+       } else {
+               if (tgt_dev == NULL) {
+                       PRINT_ERROR_PR("Session %Lx not found)", cmd->sess.sess_h);
+                       res = ESRCH;
+                       goto reply;
+               }
+               tgt_dev->sess_h = 0;
+               PRINT_INFO_PR("Session detached (sess_h %Lx)", cmd->sess.sess_h);
+       }
+
+reply:
+       memset(reply, 0, sizeof(*reply));
+       reply->cmd_h = cmd->cmd_h;
+       reply->subcode = cmd->subcode;
+       reply->result = res;
+
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static int open_dev_fd(struct vdisk_dev *dev)
+{
+       int res;
+       int open_flags = O_LARGEFILE;
+
+       if (dev->rd_only_flag)
+               open_flags |= O_RDONLY;
+       else
+               open_flags |= O_RDWR;
+       if (dev->o_direct_flag)
+               open_flags |= O_DIRECT;
+       if (dev->wt_flag)
+               open_flags |= O_SYNC;
+
+       TRACE_DBG("Opening file %s, flags 0x%x", dev->file_name, open_flags);
+       res = open(dev->file_name, open_flags);
+
+       return res;
+}
+
+void *main_loop(void *arg)
+{
+       int res = 0;
+       struct vdisk_dev *dev = (struct vdisk_dev*)arg;
+       struct scst_user_get_cmd cmd;
+       struct scst_user_reply_cmd reply;
+       struct vdisk_cmd vcmd = { -1, &cmd, dev, &reply, {0}};
+       int scst_usr_fd = dev->scst_usr_fd;
+       struct pollfd pl;
+
+       TRACE_ENTRY();
+
+       vcmd.fd = open_dev_fd(dev);
+       if (vcmd.fd < 0) {
+               res = vcmd.fd;
+               PRINT_ERROR_PR("Unable to open file %s (%s)", dev->file_name,
+                       strerror(res));
+               goto out;
+       }
+
+       memset(&pl, 0, sizeof(pl));
+       pl.fd = scst_usr_fd;
+       pl.events = POLLIN;
+
+       cmd.preply = 0;
+
+       while(1) {
+#ifdef DEBUG_TM_IGNORE_ALL
+               if (dev->debug_tm_ignore && (random() % 50000) == 55) {
+                       TRACE_MGMT_DBG("%s", "Ignore ALL");
+                       dev->debug_tm_ignore_all = 1;
+               }
+               if (dev->debug_tm_ignore_all) {
+                       /* Go Astral */
+                       while(1) {
+                               sleep(60);
+                       }
+               }
+#endif
+
+               res = ioctl(scst_usr_fd, SCST_USER_REPLY_AND_GET_CMD, &cmd);
+               if (res != 0) {
+                       res = errno;
+                       switch(res) {
+                       case ESRCH:
+                       case EBUSY:
+                       case EINTR:
+                               TRACE_MGMT_DBG("SCST_USER_REPLY_AND_GET_CMD returned "
+                                       "%d (%s)", res, strerror(res));
+                               cmd.preply = 0;
+                               continue;
+                       case EAGAIN:
+                               TRACE_DBG("SCST_USER_REPLY_AND_GET_CMD returned "
+                                       "EAGAIN (%d)", res);
+                               cmd.preply = 0;
+                               if (dev->non_blocking)
+                                       break;
+                               else
+                                       continue;
+                       default:
+                               PRINT_ERROR_PR("SCST_USER_REPLY_AND_GET_CMD failed: "
+                                       "%s (%d)", strerror(res), res);
+                               goto out_close;
+                       }
+again_poll:
+                       res = poll(&pl, 1, 2000);
+                       if (res > 0)
+                               continue;
+                       else if (res == 0)
+                               goto again_poll;
+                       else {
+                               res = errno;
+                               switch(res) {
+                               case ESRCH:
+                               case EBUSY:
+                               case EAGAIN:
+                               case EINTR:
+                                       TRACE_MGMT_DBG("poll() returned %d "
+                                               "(%s)", res, strerror(res));
+                                       goto again_poll;
+                               default:
+                                       PRINT_ERROR_PR("poll() failed: %s", strerror(res));
+                                       goto out_close;
+                               }
+                       }
+               }
+
+               TRACE_BUFFER("Received cmd", &cmd, sizeof(cmd));
+
+               switch(cmd.subcode) {
+               case SCST_USER_EXEC:
+                       if (cmd.exec_cmd.data_direction == SCST_DATA_WRITE) {
+                               TRACE_BUFFER("Received cmd data",
+                                       (void*)(unsigned long)cmd.exec_cmd.pbuf,
+                                       cmd.exec_cmd.bufflen);
+                       }
+                       res = do_exec(&vcmd);
+#ifdef DEBUG_TM_IGNORE
+                       if (res == 150) {
+                               cmd.preply = 0;
+                               continue;
+                       }
+#endif
+                       if (reply.exec_reply.resp_data_len != 0) {
+                               TRACE_BUFFER("Reply data",
+                                       (void*)(unsigned long)reply.exec_reply.pbuf,
+                                       reply.exec_reply.resp_data_len);
+                       }
+                       break;
+
+               case SCST_USER_ALLOC_MEM:
+                       res = do_alloc_mem(&vcmd);
+                       break;
+
+               case SCST_USER_PARSE:
+                       res = do_parse(&vcmd);
+                       break;
+
+               case SCST_USER_ON_CACHED_MEM_FREE:
+                       res = do_cached_mem_free(&vcmd);
+                       break;
+
+               case SCST_USER_ON_FREE_CMD:
+                       res = do_on_free_cmd(&vcmd);
+                       break;
+
+               case SCST_USER_TASK_MGMT:
+                       if (dev->prio_thr)
+                               goto err;
+                       res = do_tm(&vcmd);
+#if DEBUG_TM_FN_IGNORE
+                       if (dev->debug_tm_ignore) {
+                               sleep(15);
+                       }
+#endif
+                       break;
+
+               case SCST_USER_ATTACH_SESS:
+               case SCST_USER_DETACH_SESS:
+                       if (dev->prio_thr)
+                               goto err;
+                       res = do_sess(&vcmd);
+                       break;
+
+               default:
+err:
+                       PRINT_ERROR_PR("Unknown or wrong cmd subcode %x",
+                               cmd.subcode);
+                       goto out_close;
+               }
+
+               if (res != 0)
+                       goto out_close;
+
+               cmd.preply = (unsigned long)&reply;
+               TRACE_BUFFER("Sending reply", &reply, sizeof(reply));
+       }
+
+out_close:
+       close(vcmd.fd);
+
+out:
+       PRINT_INFO_PR("Thread %d exited (res=%d)", gettid(), res);
+
+       TRACE_EXIT_RES(res);
+       return (void*)res;
+}
+
+void *prio_loop(void *arg)
+{
+       int res = 0;
+       struct vdisk_dev *dev = (struct vdisk_dev*)arg;
+       struct scst_user_get_cmd cmd;
+       struct scst_user_reply_cmd reply;
+       struct vdisk_cmd vcmd = { -1, &cmd, dev, &reply, {0}};
+       int scst_usr_fd = dev->scst_usr_fd;
+
+       TRACE_ENTRY();
+
+       cmd.preply = 0;
+
+       while(1) {
+               res = ioctl(scst_usr_fd, SCST_USER_REPLY_AND_GET_PRIO_CMD, &cmd);
+               if (res != 0) {
+                       res = errno;
+                       switch(res) {
+                       case ESRCH:
+                       case EBUSY:
+                       case EINTR:
+                       case EAGAIN:
+                               TRACE_MGMT_DBG("SCST_USER_REPLY_AND_GET_PRIO_CMD returned "
+                                       "%d (%s)", res, strerror(res));
+                               cmd.preply = 0;
+                               continue;
+                       default:
+                               PRINT_ERROR_PR("SCST_USER_REPLY_AND_GET_PRIO_CMD failed: "
+                                       "%s (%d)", strerror(res), res);
+                               goto out_close;
+                       }
+               }
+
+               TRACE_BUFFER("Received cmd", &cmd, sizeof(cmd));
+
+               switch(cmd.subcode) {
+               case SCST_USER_TASK_MGMT:
+                       res = do_tm(&vcmd);
+#if DEBUG_TM_FN_IGNORE
+                       if (dev->debug_tm_ignore) {
+                               sleep(15);
+                       }
+#endif
+                       break;
+
+               case SCST_USER_ATTACH_SESS:
+               case SCST_USER_DETACH_SESS:
+                       res = do_sess(&vcmd);
+                       break;
+
+               default:
+                       PRINT_ERROR_PR("Unknown or wrong prio cmd subcode %x",
+                               cmd.subcode);
+                       goto out_close;
+               }
+
+               if (res != 0)
+                       goto out_close;
+
+               cmd.preply = (unsigned long)&reply;
+               TRACE_BUFFER("Sending reply", &reply, sizeof(reply));
+       }
+
+out_close:
+       close(vcmd.fd);
+
+       PRINT_INFO_PR("Prio thread %d exited (res=%d)", gettid(), res);
+
+       TRACE_EXIT_RES(res);
+       return (void*)res;
+}
+
+static void exec_inquiry(struct vdisk_cmd *vcmd)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       int len, resp_len = 0;
+       int length = cmd->bufflen;
+       unsigned int i;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       uint8_t buf[INQ_BUF_SZ];
+
+       TRACE_ENTRY();
+
+       if (cmd->cdb[1] & CMDDT) {
+               TRACE_DBG("%s", "INQUIRY: CMDDT is unsupported");
+               set_cmd_error(vcmd,
+                   SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+               goto out;
+       }
+
+       memset(buf, 0, sizeof(buf));
+       buf[0] = dev->type;      /* type dev */
+       if (buf[0] == TYPE_ROM)
+               buf[1] = 0x80;      /* removable */
+       /* Vital Product */
+       if (cmd->cdb[1] & EVPD) {
+               int dev_id_num;
+               char dev_id_str[6];
+               
+               for (dev_id_num = 0, i = 0; i < strlen(dev->name); i++) {
+                       dev_id_num += dev->name[i];
+               }
+               len = snprintf(dev_id_str, 6, "%d", dev_id_num);
+               TRACE_DBG("num %d, str <%s>, len %d",
+                          dev_id_num, dev_id_str, len);
+               if (0 == cmd->cdb[2]) { /* supported vital product data pages */
+                       buf[3] = 3;
+                       buf[4] = 0x0; /* this page */
+                       buf[5] = 0x80; /* unit serial number */
+                       buf[6] = 0x83; /* device identification */
+                       resp_len = buf[3] + 4;
+               } else if (0x80 == cmd->cdb[2]) { /* unit serial number */
+                       buf[1] = 0x80;
+                       if (dev->usn == NULL) {
+                               buf[3] = MAX_USN_LEN;
+                               memset(&buf[4], 0x20, MAX_USN_LEN);
+                       } else {
+                               int usn_len;
+
+                               if (strlen(dev->usn) > MAX_USN_LEN)
+                                       usn_len = MAX_USN_LEN;
+                               else
+                                       usn_len = len;
+                               buf[3] = usn_len;
+                               strncpy((char*)&buf[4], dev->usn, usn_len);
+                       }
+                       resp_len = buf[3] + 4;
+               } else if (0x83 == cmd->cdb[2]) { /* device identification */
+                       int num = 4;
+
+                       buf[1] = 0x83;
+                       /* Two identification descriptors: */
+                       /* T10 vendor identifier field format (faked) */
+                       buf[num + 0] = 0x2;     /* ASCII */
+                       buf[num + 1] = 0x1;
+                       buf[num + 2] = 0x0;
+                       memcpy(&buf[num + 4], VENDOR, 8);
+                       memset(&buf[num + 12], ' ', 16);
+                       i = strlen(dev->name);
+                       i = i < 16 ? i : 16;
+                       memcpy(&buf[num + 12], dev->name, len);
+                       memcpy(&buf[num + 28], dev_id_str, len);
+                       buf[num + 3] = 8 + 16 + len;
+                       num += buf[num + 3] + 4;
+                       /* NAA IEEE registered identifier (faked) */
+                       buf[num] = 0x1; /* binary */
+                       buf[num + 1] = 0x3;
+                       buf[num + 2] = 0x0;
+                       buf[num + 3] = 0x8;
+                       buf[num + 4] = 0x51;    /* ieee company id=0x123456 (faked) */
+                       buf[num + 5] = 0x23;
+                       buf[num + 6] = 0x45;
+                       buf[num + 7] = 0x60;
+                       buf[num + 8] = (dev_id_num >> 24);
+                       buf[num + 9] = (dev_id_num >> 16) & 0xff;
+                       buf[num + 10] = (dev_id_num >> 8) & 0xff;
+                       buf[num + 11] = dev_id_num & 0xff;
+
+                       resp_len = num + 12 - 4;
+                       buf[2] = (resp_len >> 8) & 0xFF;
+                       buf[3] = resp_len & 0xFF;
+                       resp_len += 4;
+               } else {
+                       TRACE_DBG("INQUIRY: Unsupported EVPD page %x",
+                               cmd->cdb[2]);
+                       set_cmd_error(vcmd,
+                           SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+                       goto out;
+               }
+       } else {
+               if (cmd->cdb[2] != 0) {
+                       TRACE_DBG("INQUIRY: Unsupported page %x", cmd->cdb[2]);
+                       set_cmd_error(vcmd,
+                           SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+                       goto out;
+               }
+
+               buf[2] = 4;             /* Device complies to this standard - SPC-2  */
+               buf[3] = 2;             /* data in format specified in this standard */
+               buf[4] = 31;            /* n - 4 = 35 - 4 = 31 for full 36 byte data */
+               buf[6] = 0; buf[7] = 2; /* BQue = 0, CMDQUE = 1 commands queuing supported */
+
+               /* 8 byte ASCII Vendor Identification of the target - left aligned */
+               memcpy(&buf[8], VENDOR, 8);
+
+               /* 16 byte ASCII Product Identification of the target - left aligned */
+               memset(&buf[16], ' ', 16);
+               len = strlen(dev->name);
+               len = len < 16 ? len : 16;
+               memcpy(&buf[16], dev->name, len);
+
+               /* 4 byte ASCII Product Revision Level of the target - left aligned */
+               memcpy(&buf[32], FIO_REV, 4);
+               resp_len = buf[4] + 5;
+       }
+
+       sBUG_ON(resp_len >= (int)sizeof(buf));
+       if (length > resp_len)
+               length = resp_len;
+       memcpy(address, buf, length);
+       reply->resp_data_len = length;
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+/* 
+ * <<Following mode pages info copied from ST318451LW with some corrections>>
+ *
+ * ToDo: revise them
+ */
+
+static int err_recov_pg(unsigned char *p, int pcontrol)
+{      /* Read-Write Error Recovery page for mode_sense */
+       const unsigned char err_recov_pg[] = {0x1, 0xa, 0xc0, 11, 240, 0, 0, 0,
+                                             5, 0, 0xff, 0xff};
+
+       memcpy(p, err_recov_pg, sizeof(err_recov_pg));
+       if (1 == pcontrol)
+               memset(p + 2, 0, sizeof(err_recov_pg) - 2);
+       return sizeof(err_recov_pg);
+}
+
+static int disconnect_pg(unsigned char *p, int pcontrol)
+{      /* Disconnect-Reconnect page for mode_sense */
+       const unsigned char disconnect_pg[] = {0x2, 0xe, 128, 128, 0, 10, 0, 0,
+                                              0, 0, 0, 0, 0, 0, 0, 0};
+
+       memcpy(p, disconnect_pg, sizeof(disconnect_pg));
+       if (1 == pcontrol)
+               memset(p + 2, 0, sizeof(disconnect_pg) - 2);
+       return sizeof(disconnect_pg);
+}
+
+static int format_pg(unsigned char *p, int pcontrol,
+                            struct vdisk_dev *dev)
+{       /* Format device page for mode_sense */
+       const unsigned char format_pg[] = {0x3, 0x16, 0, 0, 0, 0, 0, 0,
+                                          0, 0, 0, 0, 0, 0, 0, 0,
+                                          0, 0, 0, 0, 0x40, 0, 0, 0};
+
+        memcpy(p, format_pg, sizeof(format_pg));
+        p[10] = (DEF_SECTORS_PER >> 8) & 0xff;
+        p[11] = DEF_SECTORS_PER & 0xff;
+        p[12] = (dev->block_size >> 8) & 0xff;
+        p[13] = dev->block_size & 0xff;
+        if (1 == pcontrol)
+                memset(p + 2, 0, sizeof(format_pg) - 2);
+        return sizeof(format_pg);
+}
+
+static int caching_pg(unsigned char *p, int pcontrol,
+                            struct vdisk_dev *dev)
+{      /* Caching page for mode_sense */
+       const unsigned char caching_pg[] = {0x8, 18, 0x10, 0, 0xff, 0xff, 0, 0,
+               0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0, 0, 0, 0, 0};
+
+       memcpy(p, caching_pg, sizeof(caching_pg));
+       p[2] |= !(dev->wt_flag) ? WCE : 0;
+       if (1 == pcontrol)
+               memset(p + 2, 0, sizeof(caching_pg) - 2);
+       return sizeof(caching_pg);
+}
+
+static int ctrl_m_pg(unsigned char *p, int pcontrol,
+                           struct vdisk_dev *dev)
+{      /* Control mode page for mode_sense */
+       const unsigned char ctrl_m_pg[] = {0xa, 0xa, 0x20, 0, 0, 0x40, 0, 0,
+                                          0, 0, 0x2, 0x4b};
+
+       memcpy(p, ctrl_m_pg, sizeof(ctrl_m_pg));
+       if (!dev->wt_flag && !dev->nv_cache)
+               p[3] |= 0x10; /* Enable unrestricted reordering */
+       if (1 == pcontrol)
+               memset(p + 2, 0, sizeof(ctrl_m_pg) - 2);
+       return sizeof(ctrl_m_pg);
+}
+
+static int iec_m_pg(unsigned char *p, int pcontrol)
+{      /* Informational Exceptions control mode page for mode_sense */
+       const unsigned char iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0,
+                                         0, 0, 0x0, 0x0};
+       memcpy(p, iec_m_pg, sizeof(iec_m_pg));
+       if (1 == pcontrol)
+               memset(p + 2, 0, sizeof(iec_m_pg) - 2);
+       return sizeof(iec_m_pg);
+}
+
+static void exec_mode_sense(struct vdisk_cmd *vcmd)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       int length = cmd->bufflen;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       uint8_t buf[MSENSE_BUF_SZ];
+       int blocksize;
+       uint64_t nblocks;
+       unsigned char dbd, type;
+       int pcontrol, pcode, subpcode;
+       unsigned char dev_spec;
+       int msense_6, offset = 0, len;
+       unsigned char *bp;
+
+       TRACE_ENTRY();
+
+       blocksize = dev->block_size;
+       nblocks = dev->nblocks;
+       
+       type = dev->type;    /* type dev */
+       dbd = cmd->cdb[1] & DBD;
+       pcontrol = (cmd->cdb[2] & 0xc0) >> 6;
+       pcode = cmd->cdb[2] & 0x3f;
+       subpcode = cmd->cdb[3];
+       msense_6 = (MODE_SENSE == cmd->cdb[0]);
+       dev_spec = (dev->rd_only_flag ? WP : 0) | DPOFUA;
+
+       memset(buf, 0, sizeof(buf));
+       
+       if (0x3 == pcontrol) {
+               TRACE_DBG("%s", "MODE SENSE: Saving values not supported");
+               set_cmd_error(vcmd,
+                   SCST_LOAD_SENSE(scst_sense_saving_params_unsup));
+               goto out;
+       }
+
+       if (msense_6) {
+               buf[1] = type;
+               buf[2] = dev_spec;
+               offset = 4;
+       } else {
+               buf[2] = type;
+               buf[3] = dev_spec;
+               offset = 8;
+       }
+
+       if (0 != subpcode) { /* TODO: Control Extension page */
+               TRACE_DBG("%s", "MODE SENSE: Only subpage 0 is supported");
+               set_cmd_error(vcmd,
+                   SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+               goto out;
+       }
+
+       if (!dbd) {
+               /* Create block descriptor */
+               buf[offset - 1] = 0x08;         /* block descriptor length */
+               if (nblocks >> 32) {
+                       buf[offset + 0] = 0xFF;
+                       buf[offset + 1] = 0xFF;
+                       buf[offset + 2] = 0xFF;
+                       buf[offset + 3] = 0xFF;
+               } else {
+                       buf[offset + 0] = (nblocks >> (BYTE * 3)) & 0xFF;/* num blks */
+                       buf[offset + 1] = (nblocks >> (BYTE * 2)) & 0xFF;
+                       buf[offset + 2] = (nblocks >> (BYTE * 1)) & 0xFF;
+                       buf[offset + 3] = (nblocks >> (BYTE * 0)) & 0xFF;
+               }
+               buf[offset + 4] = 0;                    /* density code */
+               buf[offset + 5] = (blocksize >> (BYTE * 2)) & 0xFF;/* blklen */
+               buf[offset + 6] = (blocksize >> (BYTE * 1)) & 0xFF;
+               buf[offset + 7] = (blocksize >> (BYTE * 0)) & 0xFF;
+
+               offset += 8;                    /* increment offset */
+       }
+
+       bp = buf + offset;
+
+       switch (pcode) {
+       case 0x1:       /* Read-Write error recovery page, direct access */
+               len = err_recov_pg(bp, pcontrol);
+               offset += len;
+               break;
+       case 0x2:       /* Disconnect-Reconnect page, all devices */
+               len = disconnect_pg(bp, pcontrol);
+               offset += len;
+               break;
+        case 0x3:       /* Format device page, direct access */
+                len = format_pg(bp, pcontrol, dev);
+                offset += len;
+                break;
+       case 0x8:       /* Caching page, direct access */
+               len = caching_pg(bp, pcontrol, dev);
+               offset += len;
+               break;
+       case 0xa:       /* Control Mode page, all devices */
+               len = ctrl_m_pg(bp, pcontrol, dev);
+               offset += len;
+               break;
+       case 0x1c:      /* Informational Exceptions Mode page, all devices */
+               len = iec_m_pg(bp, pcontrol);
+               offset += len;
+               break;
+       case 0x3f:      /* Read all Mode pages */
+               len = err_recov_pg(bp, pcontrol);
+               len += disconnect_pg(bp + len, pcontrol);
+               len += format_pg(bp + len, pcontrol, dev);
+               len += caching_pg(bp + len, pcontrol, dev);
+               len += ctrl_m_pg(bp + len, pcontrol, dev);
+               len += iec_m_pg(bp + len, pcontrol);
+               offset += len;
+               break;
+       default:
+               TRACE_DBG("MODE SENSE: Unsupported page %x", pcode);
+               set_cmd_error(vcmd,
+                   SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+               goto out;
+       }
+       if (msense_6)
+               buf[0] = offset - 1;
+       else {
+               buf[0] = ((offset - 2) >> 8) & 0xff;
+               buf[1] = (offset - 2) & 0xff;
+       }
+
+       sBUG_ON(offset >= (int)sizeof(buf));
+       if (offset > length)
+               offset = length;
+       memcpy(address, buf, offset);
+       reply->resp_data_len = offset;
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static int set_wt(struct vdisk_dev *dev, int wt)
+{
+       int res = 0;
+
+       TRACE_ENTRY();
+
+       if ((dev->wt_flag == wt) || dev->nullio)
+               goto out;
+
+       pthread_mutex_lock(&dev->dev_mutex);
+       dev->wt_flag = wt;
+       pthread_mutex_unlock(&dev->dev_mutex);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static void exec_mode_select(struct vdisk_cmd *vcmd)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       int length = cmd->bufflen;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       int mselect_6, offset;
+
+       TRACE_ENTRY();
+
+       mselect_6 = (MODE_SELECT == cmd->cdb[0]);
+
+       if (!(cmd->cdb[1] & PF) || (cmd->cdb[1] & SP)) {
+               PRINT_ERROR_PR("MODE SELECT: PF and/or SP are wrongly set "
+                       "(cdb[1]=%x)", cmd->cdb[1]);
+               set_cmd_error(vcmd,
+                   SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+               goto out;
+       }
+
+       if (mselect_6) {
+               offset = 4;
+       } else {
+               offset = 8;
+       }
+
+       if (address[offset - 1] == 8) {
+               offset += 8;
+       } else if (address[offset - 1] != 0) {
+               PRINT_ERROR_PR("%s", "MODE SELECT: Wrong parameters list "
+                       "lenght");
+               set_cmd_error(vcmd,
+                   SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
+               goto out;
+       }
+
+       while (length > offset + 2) {
+               if (address[offset] & PS) {
+                       PRINT_ERROR_PR("%s", "MODE SELECT: Illegal PS bit");
+                       set_cmd_error(vcmd, SCST_LOAD_SENSE(
+                               scst_sense_invalid_field_in_parm_list));
+                       goto out;
+               }
+               if ((address[offset] & 0x3f) == 0x8) {  /* Caching page */
+                       if (address[offset + 1] != 18) {
+                               PRINT_ERROR_PR("%s", "MODE SELECT: Invalid "
+                                       "caching page request");
+                               set_cmd_error(vcmd, SCST_LOAD_SENSE(
+                                       scst_sense_invalid_field_in_parm_list));
+                               goto out;
+                       }
+                       if (set_wt(dev,
+                             (address[offset + 2] & WCE) ? 0 : 1) != 0) {
+                               set_cmd_error(vcmd,
+                                   SCST_LOAD_SENSE(scst_sense_hardw_error));
+                               goto out;
+                       }
+                       break;
+               }
+               offset += address[offset + 1];
+       }
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static void exec_read_capacity(struct vdisk_cmd *vcmd)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       int length = cmd->bufflen;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       uint32_t blocksize;
+       uint64_t nblocks;
+       uint8_t buffer[READ_CAP_LEN];
+
+       TRACE_ENTRY();
+
+       blocksize = dev->block_size;
+       nblocks = dev->nblocks;
+
+       /* last block on the dev is (nblocks-1) */
+       memset(buffer, 0, sizeof(buffer));
+       if (nblocks >> 32) {
+               buffer[0] = 0xFF;
+               buffer[1] = 0xFF;
+               buffer[2] = 0xFF;
+               buffer[3] = 0xFF;
+       } else {
+               buffer[0] = ((nblocks - 1) >> (BYTE * 3)) & 0xFF;
+               buffer[1] = ((nblocks - 1) >> (BYTE * 2)) & 0xFF;
+               buffer[2] = ((nblocks - 1) >> (BYTE * 1)) & 0xFF;
+               buffer[3] = ((nblocks - 1) >> (BYTE * 0)) & 0xFF;
+       }
+       buffer[4] = (blocksize >> (BYTE * 3)) & 0xFF;
+       buffer[5] = (blocksize >> (BYTE * 2)) & 0xFF;
+       buffer[6] = (blocksize >> (BYTE * 1)) & 0xFF;
+       buffer[7] = (blocksize >> (BYTE * 0)) & 0xFF;
+       
+       if (length > READ_CAP_LEN)
+               length = READ_CAP_LEN;
+       memcpy(address, buffer, length);
+
+       reply->resp_data_len = length;
+
+       TRACE_EXIT();
+       return;
+}
+
+static void exec_read_capacity16(struct vdisk_cmd *vcmd)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       int length = cmd->bufflen;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       uint32_t blocksize;
+       uint64_t nblocks;
+       uint8_t buffer[READ_CAP16_LEN];
+
+       TRACE_ENTRY();
+
+       blocksize = dev->block_size;
+       nblocks = dev->nblocks - 1;
+
+       memset(buffer, 0, sizeof(buffer));
+       buffer[0] = nblocks >> 56;
+       buffer[1] = (nblocks >> 48) & 0xFF;
+       buffer[2] = (nblocks >> 40) & 0xFF;
+       buffer[3] = (nblocks >> 32) & 0xFF;
+       buffer[4] = (nblocks >> 24) & 0xFF;
+       buffer[5] = (nblocks >> 16) & 0xFF;
+       buffer[6] = (nblocks >> 8) & 0xFF;
+       buffer[7] = nblocks& 0xFF;
+
+       buffer[8] = (blocksize >> (BYTE * 3)) & 0xFF;
+       buffer[9] = (blocksize >> (BYTE * 2)) & 0xFF;
+       buffer[10] = (blocksize >> (BYTE * 1)) & 0xFF;
+       buffer[11] = (blocksize >> (BYTE * 0)) & 0xFF;
+
+       if (length > READ_CAP16_LEN)
+               length = READ_CAP16_LEN;
+       memcpy(address, buffer, length);
+
+       reply->resp_data_len = length;
+
+       TRACE_EXIT();
+       return;
+}
+
+static void exec_read_toc(struct vdisk_cmd *vcmd)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       int32_t off = 0;
+       int length = cmd->bufflen;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       uint32_t nblocks;
+       uint8_t buffer[4+8+8] = { 0x00, 0x0a, 0x01, 0x01, 0x00, 0x14, 
+                                 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       TRACE_ENTRY();
+
+       if (dev->type != TYPE_ROM) {
+               PRINT_ERROR_PR("%s", "READ TOC for non-CDROM device");
+               set_cmd_error(vcmd,
+                       SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+               goto out;
+       }
+
+       if (cmd->cdb[2] & 0x0e/*Format*/) {
+               PRINT_ERROR_PR("%s", "READ TOC: invalid requested data format");
+               set_cmd_error(vcmd,
+                       SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+               goto out;
+       }
+
+       if ((cmd->cdb[6] != 0 && (cmd->cdb[2] & 0x01)) ||
+           (cmd->cdb[6] > 1 && cmd->cdb[6] != 0xAA)) {
+               PRINT_ERROR_PR("READ TOC: invalid requested track number %x",
+                       cmd->cdb[6]);
+               set_cmd_error(vcmd,
+                       SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+               goto out;
+       }
+
+       /* FIXME when you have > 8TB ROM device. */
+       nblocks = (uint32_t)dev->nblocks;
+
+       /* Header */
+       memset(buffer, 0, sizeof(buffer));
+       buffer[2] = 0x01;    /* First Track/Session */
+       buffer[3] = 0x01;    /* Last Track/Session */
+       off = 4;
+       if (cmd->cdb[6] <= 1)
+        {
+               /* Fistr TOC Track Descriptor */
+               buffer[off+1] = 0x14; /* ADDR    0x10 - Q Sub-channel encodes current position data
+                                        CONTROL 0x04 - Data track, recoreded uninterrupted */
+               buffer[off+2] = 0x01; /* Track Number */
+               off += 8;
+        }
+       if (!(cmd->cdb[2] & 0x01))
+        {
+               /* Lead-out area TOC Track Descriptor */
+               buffer[off+1] = 0x14;
+               buffer[off+2] = 0xAA;     /* Track Number */
+               buffer[off+4] = (nblocks >> (BYTE * 3)) & 0xFF; /* Track Start Address */
+               buffer[off+5] = (nblocks >> (BYTE * 2)) & 0xFF;
+               buffer[off+6] = (nblocks >> (BYTE * 1)) & 0xFF;
+               buffer[off+7] = (nblocks >> (BYTE * 0)) & 0xFF;
+               off += 8;
+        }
+
+       buffer[1] = off - 2;    /* Data  Length */
+
+       if (off > length)
+               off = length;
+       memcpy(address, buffer, off);
+       reply->resp_data_len = off;
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static void exec_prevent_allow_medium_removal(struct vdisk_cmd *vcmd)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+
+       TRACE_DBG("PERSIST/PREVENT 0x%02x", cmd->cdb[4]);
+
+       pthread_mutex_lock(&dev->dev_mutex);
+       if (dev->type == TYPE_ROM)
+               dev->prevent_allow_medium_removal = 
+                       cmd->cdb[4] & 0x01 ? 1 : 0;
+       else {
+               PRINT_ERROR_PR("%s", "Prevent allow medium removal for "
+                       "non-CDROM device");
+               set_cmd_error(vcmd,
+                       SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+       }
+       pthread_mutex_unlock(&dev->dev_mutex);
+
+       return;
+}
+
+static int exec_fsync(struct vdisk_cmd *vcmd)
+{
+       int res = 0;
+       struct vdisk_dev *dev = vcmd->dev;
+
+       /* Hopefully, the compiler will generate the single comparison */
+       if (dev->nv_cache || dev->wt_flag || dev->rd_only_flag ||
+           dev->o_direct_flag || dev->nullio)
+               goto out;
+
+       /* ToDo: use sync_file_range() instead */
+       fsync(vcmd->fd);
+
+out:
+       TRACE_EXIT_RES(res);
+       return res;
+}
+
+static void exec_read(struct vdisk_cmd *vcmd, loff_t loff)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       int length = cmd->bufflen;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       int fd = vcmd->fd;
+       loff_t err;
+
+       TRACE_ENTRY();
+       
+       TRACE_DBG("reading off %lld, len %d", loff, length);
+       if (dev->nullio)
+               err = length;
+       else {
+               /* SEEK */      
+               err = lseek64(fd, loff, 0/*SEEK_SET*/);
+               if (err != loff) {
+                       PRINT_ERROR_PR("lseek trouble %Ld != %Ld (errno %d)",
+                               (uint64_t)err, (uint64_t)loff, errno);
+                       set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+                       goto out;
+               }
+               /* READ */
+               err = read(fd, address, length);
+       }
+
+       if ((err < 0) || (err < length)) {
+               PRINT_ERROR_PR("read() returned %Ld from %d (errno %d)",
+                       (uint64_t)err, length, errno);
+               if (err == -EAGAIN)
+                       set_busy(reply);
+               else {
+                       set_cmd_error(vcmd,
+                           SCST_LOAD_SENSE(scst_sense_read_error));
+               }
+               goto out;
+       }
+
+       reply->resp_data_len = cmd->bufflen;
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static void exec_write(struct vdisk_cmd *vcmd, loff_t loff)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       loff_t err;
+       int length = cmd->bufflen;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       int fd = vcmd->fd;
+
+       TRACE_ENTRY();
+
+restart:
+       TRACE_DBG("writing off %lld, len %d", loff, length);
+
+       if (dev->nullio)
+               err = length;
+       else {
+               /* SEEK */
+               err = lseek64(fd, loff, 0/*SEEK_SET*/);
+               if (err != loff) {
+                       PRINT_ERROR_PR("lseek trouble %Ld != %Ld (errno %d)",
+                               (uint64_t)err, (uint64_t)loff, errno);
+                       set_cmd_error(vcmd,
+                           SCST_LOAD_SENSE(scst_sense_hardw_error));
+                       goto out;
+               }
+
+               /* WRITE */
+               err = write(fd, address, length);
+       }
+
+       if (err < 0) {
+               PRINT_ERROR_PR("write() returned %Ld from %zd (errno %d)", 
+                       (uint64_t)err, length, errno);
+               if (err == -EAGAIN)
+                       set_busy(reply);
+               else {
+                       set_cmd_error(vcmd,
+                           SCST_LOAD_SENSE(scst_sense_write_error));
+               }
+               goto out;
+       } else if (err < length) {
+               /* 
+                * Probably that's wrong, but sometimes write() returns
+                * value less, than requested. Let's restart.
+                */
+               TRACE_MGMT_DBG("write() returned %d from %d", (int)err, length);
+               if (err == 0) {
+                       PRINT_INFO_PR("Suspicious: write() returned 0 from "
+                               "%d", length);
+               }
+               length -= err;
+               goto restart;
+       }
+
+out:
+       TRACE_EXIT();
+       return;
+}
+
+static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff)
+{
+       struct vdisk_dev *dev = vcmd->dev;
+       struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
+       struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
+       loff_t err;
+       int length = cmd->bufflen;
+       uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
+       int compare;
+       int fd = vcmd->fd;
+       uint8_t mem_verify[128*1024];
+
+       TRACE_ENTRY();
+
+       if (exec_fsync(vcmd) != 0)
+               goto out;
+
+       /* 
+        * Until the cache is cleared prior the verifying, there is not
+         * much point in this code. ToDo.
+        *
+        * Nevertherless, this code is valuable if the data have not read
+        * from the file/disk yet.
+        */
+
+       /* SEEK */
+       if (!dev->nullio) {
+               err = lseek64(fd, loff, 0/*SEEK_SET*/);
+               if (err != loff) {
+                       PRINT_ERROR_PR("lseek trouble %Ld != %Ld (errno %d)",
+                               (uint64_t)err, (uint64_t)loff, errno);
+                       set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+                       goto out;
+               }
+       }
+
+       if ((length == 0) && (cmd->data_len != 0)) {
+               length = cmd->data_len;
+               compare = 0;
+       } else
+               compare = 1;
+
+       while (length > 0) {
+               int len_mem = (length > (int)sizeof(mem_verify)) ?
+                                       (int)sizeof(mem_verify) : length;
+               TRACE_DBG("Verify: length %d - len_mem %d", length, len_mem);
+
+               if (!dev->nullio)
+                       err = read(fd, (char*)mem_verify, len_mem);
+               else
+                       err = len_mem;
+               if ((err < 0) || (err < len_mem)) {
+                       PRINT_ERROR_PR("read() returned %Ld from %d (errno %d)",
+                               (uint64_t)err, len_mem, errno);
+                       if (err == -EAGAIN)
+                               set_busy(reply);
+                       else {
+                               set_cmd_error(vcmd,
+                                   SCST_LOAD_SENSE(scst_sense_read_error));
+                       }
+                       goto out;
+               }
+               if (compare && memcmp(address, mem_verify, len_mem) != 0) {
+                       TRACE_DBG("Verify: error memcmp length %zd", length);
+                       set_cmd_error(vcmd,
+                           SCST_LOAD_SENSE(scst_sense_miscompare_error));
+                       goto out;
+               }
+               length -= len_mem;
+               address += len_mem;
+       }
+
+       if (length < 0) {
+               PRINT_ERROR_PR("Failure: %d", length);
+               set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
+       }
+
+out:
+       TRACE_EXIT();
+       return;
+}
diff --git a/usr/fileio/common.h b/usr/fileio/common.h
new file mode 100644 (file)
index 0000000..8bdd692
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ *  common.h
+ *  
+ *  Copyright (C) 2007 Vladislav Bolkhovitin <vst@vlnb.net>
+ *  
+ *  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 <scst_user.h>
+
+#include "debug.h"
+
+/* 8 byte ASCII Vendor */
+#define VENDOR                         "SCST_USR"
+/* 4 byte ASCII Product Revision Level - left aligned */
+#define FIO_REV                                " 096"
+
+#define MAX_USN_LEN                    20
+
+#define INQ_BUF_SZ                     128
+#define EVPD                           0x01
+#define CMDDT                          0x02
+
+#define MSENSE_BUF_SZ                  256
+#define DBD                            0x08    /* disable block descriptor */
+#define WP                             0x80    /* write protect */
+#define DPOFUA                         0x10    /* DPOFUA bit */
+#define WCE                            0x04    /* write cache enable */
+
+#define PF                             0x10    /* page format */
+#define SP                             0x01    /* save pages */
+#define PS                             0x80    /* parameter saveable */
+
+#define        BYTE                            8
+#define        DEF_SECTORS_PER                 63
+
+struct vdisk_tgt_dev {
+       uint64_t sess_h;
+       /*
+        * last_write_cmd_queue_type accessed without protection since we are
+        * guaranteed that only commands of the same type per tgt_dev can be
+        * processed simultaneously
+        */
+       int last_write_cmd_queue_type;
+};
+
+struct vdisk_dev {
+       int scst_usr_fd;
+       uint32_t block_size;
+       uint64_t nblocks;
+       int block_shift;
+       loff_t file_size;       /* in bytes */
+       void *(*alloc_fn)(size_t size);
+
+       pthread_mutex_t dev_mutex;
+
+       /* Below flags and are protected by dev_mutex */
+       unsigned int rd_only_flag:1;
+       unsigned int wt_flag:1;
+       unsigned int nv_cache:1;
+       unsigned int o_direct_flag:1;
+       unsigned int media_changed:1;
+       unsigned int prevent_allow_medium_removal:1;
+       unsigned int nullio:1;
+       unsigned int cdrom_empty:1;
+       unsigned int non_blocking:1;
+       unsigned int prio_thr:1;
+#if defined(DEBUG_TM_IGNORE) || defined(DEBUG_TM_IGNORE_ALL)
+       unsigned int debug_tm_ignore:1;
+#if defined(DEBUG_TM_IGNORE_ALL)
+       volatile int debug_tm_ignore_all;
+#endif
+#endif
+
+       struct vdisk_tgt_dev tgt_devs[25];
+
+       char *name;             /* Name of virtual device,
+                                  must be <= SCSI Model + 1 */
+       char *file_name;        /* File name */
+       char *usn;
+       int type;
+};
+
+struct vdisk_cmd
+{
+       int fd;
+       struct scst_user_get_cmd *cmd;
+       struct vdisk_dev *dev;
+       struct scst_user_reply_cmd *reply;
+       uint8_t sense[SCST_SENSE_BUFFERSIZE];
+};
+
+/*
+ * min()/max() macros that also do
+ * strict type-checking.. See the
+ * "unnecessary" pointer comparison.
+ */
+#define min(x,y) ({            \
+       typeof(x) _x = (x);     \
+       typeof(y) _y = (y);     \
+       (void) (&_x == &_y);    \
+       _x < _y ? _x : _y; })
+
+#define max(x,y) ({            \
+       typeof(x) _x = (x);     \
+       typeof(y) _y = (y);     \
+       (void) (&_x == &_y);    \
+       _x > _y ? _x : _y; })
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+void *main_loop(void *arg);
+void *prio_loop(void *arg);
diff --git a/usr/fileio/debug.c b/usr/fileio/debug.c
new file mode 100644 (file)
index 0000000..db0b87f
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ *  debug.c
+ *  
+ *  Copyright (C) 2004-2007 Vladislav Bolkhovitin <vst@vlnb.net>
+ *                 and Leonid Stoljar
+ *  
+ *  Contains helper functions for execution tracing and error reporting. 
+ *  Intended to be included in main .c file.
+ * 
+ *  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 <stdio.h>
+#include <time.h>
+
+#include "debug.h"
+
+_syscall0(pid_t,gettid);
+
+#if defined(DEBUG) || defined(TRACING)
+
+#define TRACE_BUF_SIZE    512
+
+static char trace_buf[TRACE_BUF_SIZE];
+//static spinlock_t trace_buf_lock = SPIN_LOCK_UNLOCKED;
+
+int debug_print_prefix(unsigned long trace_flag, const char *func, 
+                       int line)
+{
+       int i = 0;
+
+//     spin_lock_irqsave(&trace_buf_lock, flags);
+
+       if (trace_flag & TRACE_TIME) {
+               struct tm t;
+               time_t tt;
+               time(&tt);
+               localtime_r(&tt, &t);
+               i += snprintf(&trace_buf[i], TRACE_BUF_SIZE, "%d:%d:%d ",
+                       t.tm_hour, t.tm_min, t.tm_sec);
+       }
+       if (trace_flag & TRACE_PID)
+               i += snprintf(&trace_buf[i], TRACE_BUF_SIZE, "[%d]: ",
+                             gettid());
+       if (trace_flag & TRACE_FUNCTION)
+               i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", func);
+       if (trace_flag & TRACE_LINE)
+               i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%i:", line);
+
+       if (i > 0)
+               PRINTN("%s", trace_buf);
+
+//     spin_unlock_irqrestore(&trace_buf_lock, flags);
+
+       return i;
+}
+
+void debug_print_buffer(const void *data, int len)
+{
+       int z, z1, i;
+       const unsigned char *buf = (const unsigned char *) data;
+       int f = 0;
+
+       if (buf == NULL)
+               return;
+
+//     spin_lock_irqsave(&trace_buf_lock, flags);
+
+       PRINT(" (h)___0__1__2__3__4__5__6__7__8__9__A__B__C__D__E__F");
+       for (z = 0, z1 = 0, i = 0; z < len; z++) {
+               if (z % 16 == 0) {
+                       if (z != 0) {
+                               i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i,
+                                             " ");
+                               for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1);
+                                    z1++) {
+                                       if ((buf[z1] >= 0x20) &&
+                                           (buf[z1] < 0x80))
+                                               trace_buf[i++] = buf[z1];
+                                       else
+                                               trace_buf[i++] = '.';
+                               }
+                               trace_buf[i] = '\0';
+                               PRINT("%s", trace_buf);
+                               i = 0;
+                               f = 1;
+                       }
+                       i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i,
+                                     "%4x: ", z);
+               }
+               i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%02x ",
+                             buf[z]);
+       }
+       i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "  ");
+       for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); z1++) {
+               if ((buf[z1] > 0x20) && (buf[z1] < 0x80))
+                       trace_buf[i++] = buf[z1];
+               else
+                       trace_buf[i++] = '.';
+       }
+       trace_buf[i] = '\0';
+       if (f) {
+               PRINT("%s", trace_buf)
+       } else {
+               PRINT("%s", trace_buf);
+       }
+
+//     spin_unlock_irqrestore(&trace_buf_lock, flags);
+       return;
+}
+
+#endif /* DEBUG || TRACING */
diff --git a/usr/fileio/debug.h b/usr/fileio/debug.h
new file mode 100644 (file)
index 0000000..892e9ec
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ *  debug.h
+ *  
+ *  Copyright (C) 2004-2007 Vladislav Bolkhovitin <vst@vlnb.net>
+ *                 and Leonid Stoljar
+ *  
+ *  Contains macroses for execution tracing and error reporting
+ * 
+ *  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 __DEBUG_H
+#define __DEBUG_H
+
+#include <sys/types.h>
+#include <linux/unistd.h>
+#include <errno.h>
+
+extern pid_t gettid(void);
+
+#define sBUG() assert(0)
+#define sBUG_ON(p) assert(!(p))
+
+#ifdef EXTRACHECKS
+#define EXTRACHECKS_BUG_ON(a)  sBUG_ON(a)
+#else
+#define EXTRACHECKS_BUG_ON(a)
+#endif
+
+#define TRACE_NULL           0x00000000
+#define TRACE_DEBUG          0x00000001
+#define TRACE_FUNCTION       0x00000002
+#define TRACE_LINE           0x00000004
+#define TRACE_PID            0x00000008
+#define TRACE_ENTRYEXIT      0x00000010
+#define TRACE_BUFF           0x00000020
+#define TRACE_MEMORY         0x00000040
+#define TRACE_SG             0x00000080
+#define TRACE_OUT_OF_MEM     0x00000100
+#define TRACE_MINOR          0x00000200 /* less important events */
+#define TRACE_MGMT           0x00000400
+#define TRACE_MGMT_DEBUG     0x00000800
+#define TRACE_SCSI           0x00001000
+#define TRACE_SPECIAL        0x00002000 /* filtering debug, etc */
+#define TRACE_TIME           0x00004000
+#define TRACE_ORDER          0x00008000
+#define TRACE_ALL            0xffffffff
+
+#define PRINT(format, args...)  fprintf(stdout, format "\n", ## args);
+#define PRINTN(format, args...) fprintf(stdout, format, ## args);
+
+extern char *app_name;
+#define LOG_PREFIX     app_name
+
+#if defined(DEBUG) || defined(TRACING)
+
+extern unsigned long trace_flag;
+
+extern int debug_print_prefix(unsigned long trace_flag, const char *func, int line);
+extern void debug_print_buffer(const void *data, int len);
+
+#define TRACE(trace, format, args...)                          \
+do {                                                           \
+  if (trace_flag & (trace))                                    \
+  {                                                            \
+    debug_print_prefix(trace_flag, __FUNCTION__, __LINE__);    \
+    PRINT(format, args);                                       \
+  }                                                            \
+} while(0)
+
+#define TRACE_BUFFER(message, buff, len)                       \
+do {                                                           \
+  if (trace_flag & TRACE_BUFF)                                 \
+  {                                                            \
+    debug_print_prefix(trace_flag, __FUNCTION__, __LINE__);    \
+    PRINT("%s:", message);                                     \
+    debug_print_buffer(buff, len);                             \
+  }                                                            \
+} while(0)
+
+#define TRACE_BUFF_FLAG(flag, message, buff, len)              \
+do {                                                           \
+  if (trace_flag & (flag))                                     \
+  {                                                            \
+    debug_print_prefix(trace_flag, __FUNCTION__, __LINE__);    \
+    PRINT("%s:", message);                                     \
+    debug_print_buffer(buff, len);                             \
+  }                                                            \
+} while(0)
+
+#else  /* DEBUG || TRACING */
+
+#define TRACE(trace, args...) {}
+#define TRACE_BUFFER(message, buff, len) {}
+#define TRACE_BUFF_FLAG(flag, message, buff, len) {}
+
+#endif /* DEBUG || TRACING */
+
+#ifdef DEBUG
+
+#include <assert.h>
+
+#define TRACE_MEM(format, args...)                             \
+do {                                                           \
+  if (trace_flag & TRACE_MEMORY)                               \
+  {                                                            \
+    debug_print_prefix(trace_flag, __FUNCTION__, __LINE__);    \
+    PRINT(format, args);                                       \
+  }                                                            \
+} while(0)
+
+#define TRACE_DBG(format, args...)                             \
+do {                                                           \
+  if (trace_flag & TRACE_DEBUG)                                        \
+  {                                                            \
+    debug_print_prefix(trace_flag, __FUNCTION__, __LINE__);    \
+    PRINT(format, args);                                       \
+  }                                                            \
+} while(0)
+
+#define TRACE_MGMT_DBG(format, args...)                                \
+do {                                                           \
+  if (trace_flag & TRACE_MGMT_DEBUG)                           \
+  {                                                            \
+    debug_print_prefix(trace_flag, __FUNCTION__, __LINE__);    \
+    PRINT(format, args);                                       \
+  }                                                            \
+} while(0)
+
+#define PRINT_ERROR_PR(format, args...)                                \
+do {                                                           \
+  TRACE(trace_flag, "%s: ***ERROR*** " format,                 \
+       LOG_PREFIX, args);                                      \
+} while(0)
+
+#define PRINT_INFO_PR(format, args...)                         \
+do {                                                           \
+  TRACE(trace_flag, "%s: "                                     \
+       format, LOG_PREFIX, args);                              \
+} while(0)
+
+#define PRINT_ERROR(format, args...)                           \
+do {                                                           \
+  TRACE(trace_flag, "***ERROR*** " format, args);              \
+} while(0)
+
+#define PRINT_INFO(format, args...)                            \
+do {                                                           \
+  TRACE(trace_flag, format, args);                             \
+} while(0)
+
+#define TRACE_ENTRY()                                          \
+do {                                                           \
+  if (trace_flag & TRACE_ENTRYEXIT)                            \
+  {                                                            \
+    if (trace_flag & TRACE_PID)                                        \
+    {                                                          \
+      PRINT("[%d]: ENTRY %s", gettid(),                                \
+          __FUNCTION__);                                       \
+    }                                                          \
+    else                                                       \
+    {                                                          \
+      PRINT("ENTRY %s", __FUNCTION__);                         \
+    }                                                          \
+  }                                                            \
+} while(0)
+
+#define TRACE_EXIT()                                           \
+do {                                                           \
+  if (trace_flag & TRACE_ENTRYEXIT)                            \
+  {                                                            \
+    if (trace_flag & TRACE_PID)                                        \
+    {                                                          \
+      PRINT("[%d]: EXIT %s", gettid(),                         \
+          __FUNCTION__);                                       \
+    }                                                          \
+    else                                                       \
+    {                                                          \
+      PRINT("EXIT %s", __FUNCTION__);                          \
+    }                                                          \
+  }                                                            \
+} while(0)
+
+#define TRACE_EXIT_RES(res)                                    \
+do {                                                           \
+  if (trace_flag & TRACE_ENTRYEXIT)                            \
+  {                                                            \
+    if (trace_flag & TRACE_PID)                                        \
+    {                                                          \
+      PRINT("[%d]: EXIT %s: %ld", gettid(),                    \
+        __FUNCTION__, (long)(res));                            \
+    }                                                          \
+    else                                                       \
+    {                                                          \
+      PRINT("EXIT %s: %ld", __FUNCTION__, (long)(res));                \
+    }                                                          \
+  }                                                            \
+} while(0)
+
+#define TRACE_EXIT_HRES(res)                                   \
+do {                                                           \
+  if (trace_flag & TRACE_ENTRYEXIT)                            \
+  {                                                            \
+    if (trace_flag & TRACE_PID)                                        \
+    {                                                          \
+      PRINT("[%d]: EXIT %s: 0x%lx", gettid(),                  \
+        __FUNCTION__, (long)(res));                            \
+    }                                                          \
+    else                                                       \
+    {                                                          \
+      PRINT("EXIT %s: %lx", __FUNCTION__, (long)(res));                \
+    }                                                          \
+  }                                                            \
+} while(0)
+
+#else  /* DEBUG */
+
+#define        NDEBUG
+#include <assert.h>
+
+#define TRACE_MEM(format, args...) {}
+#define TRACE_DBG(format, args...) {}
+#define TRACE_MGMT_DBG(format, args...) {}
+#define TRACE_ENTRY() {}
+#define TRACE_EXIT() {}
+#define TRACE_EXIT_RES(res) {}
+#define TRACE_EXIT_HRES(res) {}
+
+#define PRINT_INFO_PR(format, args...)                         \
+do {                                                           \
+  PRINT("%s: " format, LOG_PREFIX, args);                      \
+} while(0)
+
+#define PRINT_ERROR_PR(format, args...)                                \
+do {                                                           \
+  PRINT("%s: ***ERROR*** "                                     \
+        format, LOG_PREFIX, args);                             \
+} while(0)
+
+#define PRINT_INFO(format, args...)                            \
+do {                                                           \
+  PRINT(format, args);                                         \
+} while(0)
+
+#define PRINT_ERROR(format, args...)                           \
+do {                                                           \
+  PRINT("***ERROR*** " format, args);                          \
+} while(0)
+
+#endif /* DEBUG */
+
+#endif /* __DEBUG_H */
diff --git a/usr/fileio/fileio.c b/usr/fileio/fileio.c
new file mode 100644 (file)
index 0000000..dfb7e7c
--- /dev/null
@@ -0,0 +1,488 @@
+/*
+ *  fileio.c
+ *  
+ *  Copyright (C) 2007 Vladislav Bolkhovitin <vst@vlnb.net>
+ *  
+ *  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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <malloc.h>
+
+#include <sys/types.h>
+#include <sys/user.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+
+#include <pthread.h>
+
+char *app_name;
+#define LOG_PREFIX     app_name
+
+#include "common.h"
+
+#if defined(DEBUG) || defined(TRACING)
+
+#ifdef DEBUG
+/*#define DEFAULT_LOG_FLAGS (TRACE_ALL & ~TRACE_MEMORY & ~TRACE_BUFF \
+        & ~TRACE_FUNCTION)
+#define DEFAULT_LOG_FLAGS (TRACE_ALL & ~TRACE_MEMORY & ~TRACE_BUFF & \
+       ~TRACE_SCSI & ~TRACE_SCSI_SERIALIZING & ~TRACE_DEBUG)
+*/
+#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR | TRACE_PID | \
+       TRACE_FUNCTION | TRACE_SPECIAL | TRACE_MGMT | TRACE_MGMT_DEBUG | TRACE_ORDER | \
+       TRACE_TIME)
+
+#define TRACE_SN(args...)      TRACE(TRACE_SCSI_SERIALIZING, args)
+
+#else /* DEBUG */
+
+# ifdef TRACING
+#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR | TRACE_PID | \
+       TRACE_TIME | TRACE_SPECIAL)
+# else
+#define DEFAULT_LOG_FLAGS 0
+# endif
+#endif /* DEBUG */
+
+unsigned long trace_flag = DEFAULT_LOG_FLAGS;
+#endif /* defined(DEBUG) || defined(TRACING) */
+
+#define DEF_BLOCK_SHIFT                9
+#define VERSION_STR            "0.9.6"
+#define THREADS                        7
+
+static struct option const long_options[] =
+{
+       {"block", required_argument, 0, 'b'},
+       {"threads", required_argument, 0, 'e'},
+       {"write_through", no_argument, 0, 't'},
+       {"read_only", no_argument, 0, 'r'},
+       {"direct", no_argument, 0, 'o'},
+       {"nullio", no_argument, 0, 'n'},
+       {"nv_cache", no_argument, 0, 'c'},
+       {"parse", required_argument, 0, 'p'},
+       {"on_free", required_argument, 0, 'f'},
+       {"mem_reuse", required_argument, 0, 'm'},
+       {"prio_thread", no_argument, 0, 's'},
+       {"non_blocking", no_argument, 0, 'l'},
+#if defined(DEBUG) || defined(TRACING)
+       {"debug", required_argument, 0, 'd'},
+#endif
+#if defined(DEBUG_TM_IGNORE) || defined(DEBUG_TM_IGNORE_ALL)
+       {"debug_tm_ignore", no_argument, 0, 'g'},
+#endif
+       {"version", no_argument, 0, 'v'},
+       {"help", no_argument, 0, 'h'},
+       {0, 0, 0, 0},
+};
+
+static void usage(void)
+{
+       printf("Usage: %s [OPTION] name path\n", app_name);
+       printf("\nFILEIO disk target emulator for SCST\n");
+       printf("  -b, --block=size      Block size, must be power of 2 and >=512\n");
+       printf("  -e, --threads=count   Number of threads, %d by default\n", THREADS);
+       printf("  -t, --write_through   Write through mode\n");
+       printf("  -r, --read_only       Read only\n");
+       printf("  -o, --direct          O_DIRECT mode\n");
+       printf("  -n, --nullio          NULLIO mode\n");
+       printf("  -c, --nv_cache        NV_CACHE mode\n");
+       printf("  -p, --parse=type      Parse type, one of \"std\" "
+               "(default), \"call\" or \"excpt\"\n");
+       printf("  -f, --on_free=type    On free call type, one of \"ignore\" "
+               "(default) or \"call\"\n");
+       printf("  -m, --mem_reuse=type  Memory reuse type, one of \"all\" "
+               "(default), \"read\", \"write\" or \"none\"\n");
+       printf("  -s, --prio_thread     Use separate thread for mgmt (prio) commands\n");
+       printf("  -l, --non_blocking    Use non-blocking operations\n");
+#if defined(DEBUG) || defined(TRACING)
+       printf("  -d, --debug=level     Debug tracing level\n");
+#endif
+#if defined(DEBUG_TM_IGNORE) || defined(DEBUG_TM_IGNORE_ALL)
+       printf("  -g, --debug_tm_ignore Turn on DEBUG_TM_IGNORE\n");
+#endif
+       return;
+}
+
+static int scst_calc_block_shift(int sector_size)
+{
+       int block_shift = 0;
+       int t = sector_size;
+
+       if (sector_size == 0)
+               goto out;
+
+       t = sector_size;
+       while(1) {
+               if ((t & 1) != 0)
+                       break;
+               t >>= 1;
+               block_shift++;
+       }
+       if (block_shift < 9) {
+               PRINT_ERROR_PR("Wrong sector size %d", sector_size);
+               block_shift = -1;
+       } 
+
+out:
+       TRACE_EXIT_RES(block_shift);
+       return block_shift;
+}
+
+static void *align_alloc(size_t size)
+{
+       return memalign(PAGE_SIZE, size);
+}
+
+int main(int argc, char **argv)
+{
+       int res = 0;
+       int ch, longindex;
+       int fd;
+       int parse_type = SCST_USER_PARSE_STANDARD;
+       int on_free_cmd_type = SCST_USER_ON_FREE_CMD_IGNORE;
+       int on_free_cmd_type_set = 0;
+       int memory_reuse_type = SCST_USER_MEM_REUSE_ALL;
+       int threads = THREADS;
+       struct scst_user_dev_desc desc;
+       struct vdisk_dev dev;
+
+       setlinebuf(stdout);
+
+       app_name = argv[0];
+
+       memset(&dev, 0, sizeof(dev));
+       dev.block_size = (1 << DEF_BLOCK_SHIFT);
+       dev.block_shift = DEF_BLOCK_SHIFT;
+       dev.type = TYPE_DISK;
+       dev.alloc_fn = align_alloc;
+
+       while ((ch = getopt_long(argc, argv, "+b:e:tronsglcp:f:m:d:vh", long_options,
+                               &longindex)) >= 0) {
+               switch (ch) {
+               case 'b':
+                       dev.block_size = atoi(optarg);
+                       PRINT_INFO("block_size %x (%s)", dev.block_size, optarg);
+                       dev.block_shift = scst_calc_block_shift(dev.block_size);
+                       if (dev.block_shift < 9) {
+                               res = -EINVAL;
+                               goto out_usage;
+                       }
+                       break;
+               case 'e':
+                       threads = strtol(optarg, (char **)NULL, 0);
+                       break;
+               case 't':
+                       dev.wt_flag = 1;
+                       break;
+#if defined(DEBUG) || defined(TRACING)
+               case 'd':
+                       trace_flag = strtol(optarg, (char **)NULL, 0);
+                       break;
+#endif
+               case 'r':
+                       dev.rd_only_flag = 1;
+                       break;
+               case 'o':
+                       dev.o_direct_flag = 1;
+                       dev.alloc_fn = align_alloc;
+                       break;
+               case 'n':
+                       dev.nullio = 1;
+                       break;
+               case 'c':
+                       dev.nv_cache = 1;
+                       break;
+               case 'p':
+                       if (strncmp(optarg, "std", 3) == 0)
+                               parse_type = SCST_USER_PARSE_STANDARD;
+                       else if (strncmp(optarg, "call", 3) == 0)
+                               parse_type = SCST_USER_PARSE_CALL;
+                       else if (strncmp(optarg, "excpt", 5) == 0)
+                               parse_type = SCST_USER_PARSE_EXCEPTION;
+                       else
+                               goto out_usage;
+                       break;
+               case 'f':
+                       on_free_cmd_type_set = 1;
+                       if (strncmp(optarg, "ignore", 6) == 0)
+                               on_free_cmd_type = SCST_USER_ON_FREE_CMD_IGNORE;
+                       else if (strncmp(optarg, "call", 3) == 0)
+                               on_free_cmd_type = SCST_USER_ON_FREE_CMD_CALL;
+                       else
+                               goto out_usage;
+                       break;
+               case 'm':
+                       if (strncmp(optarg, "all", 3) == 0)
+                               memory_reuse_type = SCST_USER_MEM_REUSE_ALL;
+                       else if (strncmp(optarg, "read", 4) == 0)
+                               memory_reuse_type = SCST_USER_MEM_REUSE_READ;
+                       else if (strncmp(optarg, "write", 5) == 0)
+                               memory_reuse_type = SCST_USER_MEM_REUSE_WRITE;
+                       else if (strncmp(optarg, "none", 4) == 0)
+                               memory_reuse_type = SCST_USER_MEM_NO_REUSE;
+                       else
+                               goto out_usage;
+                       break;
+               case 's':
+                       dev.prio_thr = 1;
+                       break;
+               case 'l':
+                       dev.non_blocking = 1;
+                       break;
+#if defined(DEBUG_TM_IGNORE) || defined(DEBUG_TM_IGNORE_ALL)
+               case 'g':
+                       dev.debug_tm_ignore = 1;
+                       break;
+#endif
+               case 'v':
+                       printf("%s version %s\n", app_name, VERSION_STR);
+                       goto out;
+               default:
+                       goto out_usage;
+               }
+       }
+
+       if (optind != (argc-2))
+               goto out_usage;
+
+       if (!on_free_cmd_type_set &&
+           (memory_reuse_type != SCST_USER_MEM_REUSE_ALL))
+               on_free_cmd_type = SCST_USER_ON_FREE_CMD_CALL;
+
+       dev.name = argv[optind];
+       dev.file_name = argv[optind+1];
+
+       TRACE_DBG("Opening file %s", dev.file_name);
+       fd = open(dev.file_name, O_RDONLY|O_LARGEFILE);
+       if (fd < 0) {
+               res = errno;
+               PRINT_ERROR_PR("Unable to open file %s (%s)", dev.file_name,
+                       strerror(res));
+               goto out;
+       }
+
+       dev.file_size = lseek64(fd, 0, SEEK_END);
+       dev.nblocks = dev.file_size >> dev.block_shift;
+
+       close(fd);
+
+       PRINT_INFO_PR("Virtual device \"%s\", path \"%s\", size %LdMb, "
+               "block size %d, nblocks %Ld, options:", dev.name, dev.file_name,
+               dev.file_size/1024/1024, dev.block_size, dev.nblocks);
+       if (dev.rd_only_flag)
+               PRINT_INFO("    %s", "READ ONLY");
+       if (dev.wt_flag)
+               PRINT_INFO("    %s", "WRITE THROUGH");
+       if (dev.nv_cache)
+               PRINT_INFO("    %s", "NV_CACHE");
+       if (dev.o_direct_flag)
+               PRINT_INFO("    %s", "O_DIRECT");
+       if (dev.nullio)
+               PRINT_INFO("    %s", "NULLIO");
+       if (dev.non_blocking)
+               PRINT_INFO("    %s", "NON-BLOCKING");
+
+       switch(parse_type) {
+       case SCST_USER_PARSE_STANDARD:
+               PRINT_INFO("    %s", "Standard parse");
+               break;
+       case SCST_USER_PARSE_CALL:
+               PRINT_INFO("    %s", "Call parse");
+               break;
+       case SCST_USER_PARSE_EXCEPTION:
+               PRINT_INFO("    %s", "Exception parse");
+               break;
+       default:
+               sBUG();
+       }
+
+       switch(on_free_cmd_type) {
+       case SCST_USER_ON_FREE_CMD_IGNORE:
+               PRINT_INFO("    %s", "Ignore on_free_cmd");
+               break;
+       case SCST_USER_ON_FREE_CMD_CALL:
+               PRINT_INFO("    %s", "Call on_free_cmd");
+               break;
+       default:
+               sBUG();
+       }
+
+       switch(memory_reuse_type) {
+       case SCST_USER_MEM_REUSE_ALL:
+               PRINT_INFO("    %s", "Full memory reuse enabled");
+               break;
+       case SCST_USER_MEM_REUSE_READ:
+               PRINT_INFO("    %s", "READ memory reuse enabled");
+               break;
+       case SCST_USER_MEM_REUSE_WRITE:
+               PRINT_INFO("    %s", "WRITE memory reuse enabled");
+               break;
+       case SCST_USER_MEM_NO_REUSE:
+               PRINT_INFO("    %s", "Memory reuse disabled");
+               break;
+       default:
+               sBUG();
+       }
+
+       if (!dev.o_direct_flag && (memory_reuse_type == SCST_USER_MEM_NO_REUSE)) {
+               PRINT_INFO("    %s", "Using unaligned buffers");
+               dev.alloc_fn = malloc;
+       }
+
+       if (dev.prio_thr) {
+               PRINT_INFO("    %s", "Using separate prio thread");
+       }
+
+#if defined(DEBUG_TM_IGNORE) || defined(DEBUG_TM_IGNORE_ALL)
+       if (dev.debug_tm_ignore) {
+               PRINT_INFO("    %s", "DEBUG_TM_IGNORE");
+       }
+#endif
+
+#ifdef DEBUG
+       PRINT_INFO("trace_flag %lx", trace_flag);
+#endif
+
+       dev.scst_usr_fd = open(DEV_USER_PATH DEV_USER_NAME, O_RDWR | 
+               (dev.non_blocking ? O_NONBLOCK : 0));
+       if (dev.scst_usr_fd < 0) {
+               res = dev.scst_usr_fd;
+               PRINT_ERROR_PR("Unable to open SCST device %s (%s)",
+                       DEV_USER_PATH DEV_USER_NAME, strerror(res));
+               goto out;
+       }
+
+       memset(&desc, 0, sizeof(desc));
+       desc.version = DEV_USER_VERSION;
+       strncpy(desc.name, dev.name, sizeof(desc.name)-1);
+       desc.name[sizeof(desc.name)-1] = '\0';
+       desc.type = dev.type;
+       desc.block_size = dev.block_size;
+
+       desc.opt.parse_type = parse_type;
+       desc.opt.on_free_cmd_type = on_free_cmd_type;
+       desc.opt.memory_reuse_type = memory_reuse_type;
+       if (dev.prio_thr)
+               desc.opt.prio_queue_type = SCST_USER_PRIO_QUEUE_SEPARATE;
+       else
+               desc.opt.prio_queue_type = SCST_USER_PRIO_QUEUE_SINGLE;
+
+       res = ioctl(dev.scst_usr_fd, SCST_USER_REGISTER_DEVICE, &desc);
+       if (res != 0) {
+               res = errno;
+               PRINT_ERROR_PR("Unable to register device: %s", strerror(res));
+               goto out_close;
+       }
+
+#if 0
+       {
+               /* Not needed, added here only as a test */
+               struct scst_user_opt opt;
+
+               res = ioctl(dev.scst_usr_fd, SCST_USER_GET_OPTIONS, &opt);
+               if (res != 0) {
+                       res = errno;
+                       PRINT_ERROR_PR("Unable to get options: %s", strerror(res));
+                       goto out_close;
+               }
+
+               opt.parse_type = parse_type;
+               opt.on_free_cmd_type = on_free_cmd_type;
+               opt.memory_reuse_type = memory_reuse_type;
+               if (dev.prio_thr)
+                       opt.prio_queue_type = SCST_USER_PRIO_QUEUE_SEPARATE;
+               else
+                       opt.prio_queue_type = SCST_USER_PRIO_QUEUE_SINGLE;
+
+               res = ioctl(dev.scst_usr_fd, SCST_USER_SET_OPTIONS, &opt);
+               if (res != 0) {
+                       res = errno;
+                       PRINT_ERROR_PR("Unable to get options: %s", strerror(res));
+                       goto out_close;
+               }
+       }
+#endif
+
+       res = pthread_mutex_init(&dev.dev_mutex, NULL);
+       if (res != 0) {
+               res = errno;
+               PRINT_ERROR_PR("pthread_mutex_init() failed: %s", strerror(res));
+               goto out_close;
+       }
+
+       {
+               pthread_t thread[threads];
+               pthread_t prio;
+               int i, j, rc;
+               void *rc1;
+               for(i = 0; i < threads; i++) {
+                       rc = pthread_create(&thread[i], NULL, main_loop, &dev);
+                       if (rc != 0) {
+                               res = errno;
+                               PRINT_ERROR_PR("pthread_create() failed: %s",
+                                       strerror(res));
+                               break;
+                       }
+               }
+
+               if (dev.prio_thr) {
+                       rc = pthread_create(&prio, NULL, prio_loop, &dev);
+                       if (rc != 0) {
+                               res = errno;
+                               PRINT_ERROR_PR("Prio pthread_create() failed: %s",
+                                       strerror(res));
+                               dev.prio_thr = 0;
+                       }
+               }
+
+               j = i;
+               for(i = 0; i < j; i++) {
+                       rc = pthread_join(thread[i], &rc1);
+                       if (rc != 0) {
+                               res = errno;
+                               PRINT_ERROR_PR("pthread_join() failed: %s",
+                                       strerror(res));
+                       } else if (rc1 != NULL)
+                               res = (int)rc1;
+               }
+               if (dev.prio_thr) {
+                       rc = pthread_join(prio, &rc1);
+                       if (rc != 0) {
+                               res = errno;
+                               PRINT_ERROR_PR("Prio pthread_join() failed: %s",
+                                       strerror(res));
+                       } else if (rc1 != NULL)
+                               res = (int)rc1;
+               }
+       }
+
+       pthread_mutex_destroy(&dev.dev_mutex);
+
+out_close:
+       close(dev.scst_usr_fd);
+
+out:
+       return res;
+
+out_usage:
+       usage();
+       goto out;
+}