aboutsummaryrefslogtreecommitdiffstats
path: root/main/linux-scst/scst-2.2.0-3.2.2.patch
diff options
context:
space:
mode:
authorCarlo Landmeter <clandmeter@gmail.com>2012-01-26 15:48:06 +0000
committerCarlo Landmeter <clandmeter@gmail.com>2012-01-26 15:48:06 +0000
commit6160b20b4f9a1cb388556228aecb583f0da590b2 (patch)
treed7cb31259015a67805cd01ef64b56c03948e31a3 /main/linux-scst/scst-2.2.0-3.2.2.patch
parent6d05d77a399d7abcbcb308533a5ddc7399a79ead (diff)
downloadaports-6160b20b4f9a1cb388556228aecb583f0da590b2.tar.bz2
aports-6160b20b4f9a1cb388556228aecb583f0da590b2.tar.xz
main/linux-scst: update scst to 2.2.0 and kernel to 3.2.2
Diffstat (limited to 'main/linux-scst/scst-2.2.0-3.2.2.patch')
-rw-r--r--main/linux-scst/scst-2.2.0-3.2.2.patch80901
1 files changed, 80901 insertions, 0 deletions
diff --git a/main/linux-scst/scst-2.2.0-3.2.2.patch b/main/linux-scst/scst-2.2.0-3.2.2.patch
new file mode 100644
index 0000000000..a6fef8db26
--- /dev/null
+++ b/main/linux-scst/scst-2.2.0-3.2.2.patch
@@ -0,0 +1,80901 @@
+=== modified file 'block/blk-map.c'
+--- old/block/blk-map.c 2012-01-10 22:58:17 +0000
++++ new/block/blk-map.c 2012-01-10 23:01:21 +0000
+@@ -5,6 +5,8 @@
+ #include <linux/module.h>
+ #include <linux/bio.h>
+ #include <linux/blkdev.h>
++#include <linux/scatterlist.h>
++#include <linux/slab.h>
+ #include <scsi/sg.h> /* for struct sg_iovec */
+
+ #include "blk.h"
+@@ -275,6 +277,339 @@ int blk_rq_unmap_user(struct bio *bio)
+ }
+ EXPORT_SYMBOL(blk_rq_unmap_user);
+
++struct blk_kern_sg_work {
++ atomic_t bios_inflight;
++ struct sg_table sg_table;
++ struct scatterlist *src_sgl;
++};
++
++static void blk_free_kern_sg_work(struct blk_kern_sg_work *bw)
++{
++ struct sg_table *sgt = &bw->sg_table;
++ struct scatterlist *sg;
++ int i;
++
++ for_each_sg(sgt->sgl, sg, sgt->orig_nents, i) {
++ struct page *pg = sg_page(sg);
++ if (pg == NULL)
++ break;
++ __free_page(pg);
++ }
++
++ sg_free_table(sgt);
++ kfree(bw);
++ return;
++}
++
++static void blk_bio_map_kern_endio(struct bio *bio, int err)
++{
++ struct blk_kern_sg_work *bw = bio->bi_private;
++
++ if (bw != NULL) {
++ /* Decrement the bios in processing and, if zero, free */
++ BUG_ON(atomic_read(&bw->bios_inflight) <= 0);
++ if (atomic_dec_and_test(&bw->bios_inflight)) {
++ if ((bio_data_dir(bio) == READ) && (err == 0)) {
++ unsigned long flags;
++
++ local_irq_save(flags); /* to protect KMs */
++ sg_copy(bw->src_sgl, bw->sg_table.sgl, 0, 0,
++ KM_BIO_DST_IRQ, KM_BIO_SRC_IRQ);
++ local_irq_restore(flags);
++ }
++ blk_free_kern_sg_work(bw);
++ }
++ }
++
++ bio_put(bio);
++ return;
++}
++
++static int blk_rq_copy_kern_sg(struct request *rq, struct scatterlist *sgl,
++ int nents, struct blk_kern_sg_work **pbw,
++ gfp_t gfp, gfp_t page_gfp)
++{
++ int res = 0, i;
++ struct scatterlist *sg;
++ struct scatterlist *new_sgl;
++ int new_sgl_nents;
++ size_t len = 0, to_copy;
++ struct blk_kern_sg_work *bw;
++
++ bw = kzalloc(sizeof(*bw), gfp);
++ if (bw == NULL)
++ goto out;
++
++ bw->src_sgl = sgl;
++
++ for_each_sg(sgl, sg, nents, i)
++ len += sg->length;
++ to_copy = len;
++
++ new_sgl_nents = PFN_UP(len);
++
++ res = sg_alloc_table(&bw->sg_table, new_sgl_nents, gfp);
++ if (res != 0)
++ goto err_free;
++
++ new_sgl = bw->sg_table.sgl;
++
++ for_each_sg(new_sgl, sg, new_sgl_nents, i) {
++ struct page *pg;
++
++ pg = alloc_page(page_gfp);
++ if (pg == NULL)
++ goto err_free;
++
++ sg_assign_page(sg, pg);
++ sg->length = min_t(size_t, PAGE_SIZE, len);
++
++ len -= PAGE_SIZE;
++ }
++
++ if (rq_data_dir(rq) == WRITE) {
++ /*
++ * We need to limit amount of copied data to to_copy, because
++ * sgl might have the last element in sgl not marked as last in
++ * SG chaining.
++ */
++ sg_copy(new_sgl, sgl, 0, to_copy,
++ KM_USER0, KM_USER1);
++ }
++
++ *pbw = bw;
++ /*
++ * REQ_COPY_USER name is misleading. It should be something like
++ * REQ_HAS_TAIL_SPACE_FOR_PADDING.
++ */
++ rq->cmd_flags |= REQ_COPY_USER;
++
++out:
++ return res;
++
++err_free:
++ blk_free_kern_sg_work(bw);
++ res = -ENOMEM;
++ goto out;
++}
++
++static int __blk_rq_map_kern_sg(struct request *rq, struct scatterlist *sgl,
++ int nents, struct blk_kern_sg_work *bw, gfp_t gfp)
++{
++ int res;
++ struct request_queue *q = rq->q;
++ int rw = rq_data_dir(rq);
++ int max_nr_vecs, i;
++ size_t tot_len;
++ bool need_new_bio;
++ struct scatterlist *sg, *prev_sg = NULL;
++ struct bio *bio = NULL, *hbio = NULL, *tbio = NULL;
++ int bios;
++
++ if (unlikely((sgl == NULL) || (sgl->length == 0) || (nents <= 0))) {
++ WARN_ON(1);
++ res = -EINVAL;
++ goto out;
++ }
++
++ /*
++ * Let's keep each bio allocation inside a single page to decrease
++ * probability of failure.
++ */
++ max_nr_vecs = min_t(size_t,
++ ((PAGE_SIZE - sizeof(struct bio)) / sizeof(struct bio_vec)),
++ BIO_MAX_PAGES);
++
++ need_new_bio = true;
++ tot_len = 0;
++ bios = 0;
++ for_each_sg(sgl, sg, nents, i) {
++ struct page *page = sg_page(sg);
++ void *page_addr = page_address(page);
++ size_t len = sg->length, l;
++ size_t offset = sg->offset;
++
++ tot_len += len;
++ prev_sg = sg;
++
++ /*
++ * Each segment must be aligned on DMA boundary and
++ * not on stack. The last one may have unaligned
++ * length as long as the total length is aligned to
++ * DMA padding alignment.
++ */
++ if (i == nents - 1)
++ l = 0;
++ else
++ l = len;
++ if (((sg->offset | l) & queue_dma_alignment(q)) ||
++ (page_addr && object_is_on_stack(page_addr + sg->offset))) {
++ res = -EINVAL;
++ goto out_free_bios;
++ }
++
++ while (len > 0) {
++ size_t bytes;
++ int rc;
++
++ if (need_new_bio) {
++ bio = bio_kmalloc(gfp, max_nr_vecs);
++ if (bio == NULL) {
++ res = -ENOMEM;
++ goto out_free_bios;
++ }
++
++ if (rw == WRITE)
++ bio->bi_rw |= REQ_WRITE;
++
++ bios++;
++ bio->bi_private = bw;
++ bio->bi_end_io = blk_bio_map_kern_endio;
++
++ if (hbio == NULL)
++ hbio = tbio = bio;
++ else
++ tbio = tbio->bi_next = bio;
++ }
++
++ bytes = min_t(size_t, len, PAGE_SIZE - offset);
++
++ rc = bio_add_pc_page(q, bio, page, bytes, offset);
++ if (rc < bytes) {
++ if (unlikely(need_new_bio || (rc < 0))) {
++ if (rc < 0)
++ res = rc;
++ else
++ res = -EIO;
++ goto out_free_bios;
++ } else {
++ need_new_bio = true;
++ len -= rc;
++ offset += rc;
++ continue;
++ }
++ }
++
++ need_new_bio = false;
++ offset = 0;
++ len -= bytes;
++ page = nth_page(page, 1);
++ }
++ }
++
++ if (hbio == NULL) {
++ res = -EINVAL;
++ goto out_free_bios;
++ }
++
++ /* Total length must be aligned on DMA padding alignment */
++ if ((tot_len & q->dma_pad_mask) &&
++ !(rq->cmd_flags & REQ_COPY_USER)) {
++ res = -EINVAL;
++ goto out_free_bios;
++ }
++
++ if (bw != NULL)
++ atomic_set(&bw->bios_inflight, bios);
++
++ while (hbio != NULL) {
++ bio = hbio;
++ hbio = hbio->bi_next;
++ bio->bi_next = NULL;
++
++ blk_queue_bounce(q, &bio);
++
++ res = blk_rq_append_bio(q, rq, bio);
++ if (unlikely(res != 0)) {
++ bio->bi_next = hbio;
++ hbio = bio;
++ /* We can have one or more bios bounced */
++ goto out_unmap_bios;
++ }
++ }
++
++ res = 0;
++
++ rq->buffer = NULL;
++out:
++ return res;
++
++out_unmap_bios:
++ blk_rq_unmap_kern_sg(rq, res);
++
++out_free_bios:
++ while (hbio != NULL) {
++ bio = hbio;
++ hbio = hbio->bi_next;
++ bio_put(bio);
++ }
++ goto out;
++}
++
++/**
++ * blk_rq_map_kern_sg - map kernel data to a request, for REQ_TYPE_BLOCK_PC
++ * @rq: request to fill
++ * @sgl: area to map
++ * @nents: number of elements in @sgl
++ * @gfp: memory allocation flags
++ *
++ * Description:
++ * Data will be mapped directly if possible. Otherwise a bounce
++ * buffer will be used.
++ */
++int blk_rq_map_kern_sg(struct request *rq, struct scatterlist *sgl,
++ int nents, gfp_t gfp)
++{
++ int res;
++
++ res = __blk_rq_map_kern_sg(rq, sgl, nents, NULL, gfp);
++ if (unlikely(res != 0)) {
++ struct blk_kern_sg_work *bw = NULL;
++
++ res = blk_rq_copy_kern_sg(rq, sgl, nents, &bw,
++ gfp, rq->q->bounce_gfp | gfp);
++ if (unlikely(res != 0))
++ goto out;
++
++ res = __blk_rq_map_kern_sg(rq, bw->sg_table.sgl,
++ bw->sg_table.nents, bw, gfp);
++ if (res != 0) {
++ blk_free_kern_sg_work(bw);
++ goto out;
++ }
++ }
++
++ rq->buffer = NULL;
++
++out:
++ return res;
++}
++EXPORT_SYMBOL(blk_rq_map_kern_sg);
++
++/**
++ * blk_rq_unmap_kern_sg - unmap a request with kernel sg
++ * @rq: request to unmap
++ * @err: non-zero error code
++ *
++ * Description:
++ * Unmap a rq previously mapped by blk_rq_map_kern_sg(). Must be called
++ * only in case of an error!
++ */
++void blk_rq_unmap_kern_sg(struct request *rq, int err)
++{
++ struct bio *bio = rq->bio;
++
++ while (bio) {
++ struct bio *b = bio;
++ bio = bio->bi_next;
++ b->bi_end_io(b, err);
++ }
++ rq->bio = NULL;
++
++ return;
++}
++EXPORT_SYMBOL(blk_rq_unmap_kern_sg);
++
+ /**
+ * blk_rq_map_kern - map kernel data to a request, for REQ_TYPE_BLOCK_PC usage
+ * @q: request queue where request should be inserted
+
+=== modified file 'include/linux/blkdev.h'
+--- old/include/linux/blkdev.h 2012-01-10 22:58:17 +0000
++++ new/include/linux/blkdev.h 2012-01-10 23:01:21 +0000
+@@ -716,6 +718,9 @@ extern int blk_rq_map_kern(struct reques
+ extern int blk_rq_map_user_iov(struct request_queue *, struct request *,
+ struct rq_map_data *, struct sg_iovec *, int,
+ unsigned int, gfp_t);
++extern int blk_rq_map_kern_sg(struct request *rq, struct scatterlist *sgl,
++ int nents, gfp_t gfp);
++extern void blk_rq_unmap_kern_sg(struct request *rq, int err);
+ extern int blk_execute_rq(struct request_queue *, struct gendisk *,
+ struct request *, int);
+ extern void blk_execute_rq_nowait(struct request_queue *, struct gendisk *,
+
+=== modified file 'include/linux/scatterlist.h'
+--- old/include/linux/scatterlist.h 2012-01-10 22:58:17 +0000
++++ new/include/linux/scatterlist.h 2012-01-10 23:01:21 +0000
+@@ -3,6 +3,7 @@
+
+ #include <asm/types.h>
+ #include <asm/scatterlist.h>
++#include <asm/kmap_types.h>
+ #include <linux/mm.h>
+ #include <linux/string.h>
+ #include <asm/io.h>
+@@ -218,6 +219,10 @@ size_t sg_copy_from_buffer(struct scatte
+ size_t sg_copy_to_buffer(struct scatterlist *sgl, unsigned int nents,
+ void *buf, size_t buflen);
+
++int sg_copy(struct scatterlist *dst_sg, struct scatterlist *src_sg,
++ int nents_to_copy, size_t copy_len,
++ enum km_type d_km_type, enum km_type s_km_type);
++
+ /*
+ * Maximum number of entries that will be allocated in one piece, if
+ * a list larger than this is required then chaining will be utilized.
+
+=== modified file 'lib/scatterlist.c'
+--- old/lib/scatterlist.c 2012-01-10 22:58:17 +0000
++++ new/lib/scatterlist.c 2012-01-10 23:01:21 +0000
+@@ -517,3 +517,132 @@ size_t sg_copy_to_buffer(struct scatterl
+ return sg_copy_buffer(sgl, nents, buf, buflen, 1);
+ }
+ EXPORT_SYMBOL(sg_copy_to_buffer);
++
++/*
++ * Can switch to the next dst_sg element, so, to copy to strictly only
++ * one dst_sg element, it must be either last in the chain, or
++ * copy_len == dst_sg->length.
++ */
++static int sg_copy_elem(struct scatterlist **pdst_sg, size_t *pdst_len,
++ size_t *pdst_offs, struct scatterlist *src_sg,
++ size_t copy_len,
++ enum km_type d_km_type, enum km_type s_km_type)
++{
++ int res = 0;
++ struct scatterlist *dst_sg;
++ size_t src_len, dst_len, src_offs, dst_offs;
++ struct page *src_page, *dst_page;
++
++ dst_sg = *pdst_sg;
++ dst_len = *pdst_len;
++ dst_offs = *pdst_offs;
++ dst_page = sg_page(dst_sg);
++
++ src_page = sg_page(src_sg);
++ src_len = src_sg->length;
++ src_offs = src_sg->offset;
++
++ do {
++ void *saddr, *daddr;
++ size_t n;
++
++ saddr = kmap_atomic(src_page +
++ (src_offs >> PAGE_SHIFT), s_km_type) +
++ (src_offs & ~PAGE_MASK);
++ daddr = kmap_atomic(dst_page +
++ (dst_offs >> PAGE_SHIFT), d_km_type) +
++ (dst_offs & ~PAGE_MASK);
++
++ if (((src_offs & ~PAGE_MASK) == 0) &&
++ ((dst_offs & ~PAGE_MASK) == 0) &&
++ (src_len >= PAGE_SIZE) && (dst_len >= PAGE_SIZE) &&
++ (copy_len >= PAGE_SIZE)) {
++ copy_page(daddr, saddr);
++ n = PAGE_SIZE;
++ } else {
++ n = min_t(size_t, PAGE_SIZE - (dst_offs & ~PAGE_MASK),
++ PAGE_SIZE - (src_offs & ~PAGE_MASK));
++ n = min(n, src_len);
++ n = min(n, dst_len);
++ n = min_t(size_t, n, copy_len);
++ memcpy(daddr, saddr, n);
++ }
++ dst_offs += n;
++ src_offs += n;
++
++ kunmap_atomic(saddr, s_km_type);
++ kunmap_atomic(daddr, d_km_type);
++
++ res += n;
++ copy_len -= n;
++ if (copy_len == 0)
++ goto out;
++
++ src_len -= n;
++ dst_len -= n;
++ if (dst_len == 0) {
++ dst_sg = sg_next(dst_sg);
++ if (dst_sg == NULL)
++ goto out;
++ dst_page = sg_page(dst_sg);
++ dst_len = dst_sg->length;
++ dst_offs = dst_sg->offset;
++ }
++ } while (src_len > 0);
++
++out:
++ *pdst_sg = dst_sg;
++ *pdst_len = dst_len;
++ *pdst_offs = dst_offs;
++ return res;
++}
++
++/**
++ * sg_copy - copy one SG vector to another
++ * @dst_sg: destination SG
++ * @src_sg: source SG
++ * @nents_to_copy: maximum number of entries to copy
++ * @copy_len: maximum amount of data to copy. If 0, then copy all.
++ * @d_km_type: kmap_atomic type for the destination SG
++ * @s_km_type: kmap_atomic type for the source SG
++ *
++ * Description:
++ * Data from the source SG vector will be copied to the destination SG
++ * vector. End of the vectors will be determined by sg_next() returning
++ * NULL. Returns number of bytes copied.
++ */
++int sg_copy(struct scatterlist *dst_sg, struct scatterlist *src_sg,
++ int nents_to_copy, size_t copy_len,
++ enum km_type d_km_type, enum km_type s_km_type)
++{
++ int res = 0;
++ size_t dst_len, dst_offs;
++
++ if (copy_len == 0)
++ copy_len = 0x7FFFFFFF; /* copy all */
++
++ if (nents_to_copy == 0)
++ nents_to_copy = 0x7FFFFFFF; /* copy all */
++
++ dst_len = dst_sg->length;
++ dst_offs = dst_sg->offset;
++
++ do {
++ int copied = sg_copy_elem(&dst_sg, &dst_len, &dst_offs,
++ src_sg, copy_len, d_km_type, s_km_type);
++ copy_len -= copied;
++ res += copied;
++ if ((copy_len == 0) || (dst_sg == NULL))
++ goto out;
++
++ nents_to_copy--;
++ if (nents_to_copy == 0)
++ goto out;
++
++ src_sg = sg_next(src_sg);
++ } while (src_sg != NULL);
++
++out:
++ return res;
++}
++EXPORT_SYMBOL(sg_copy);
+
+=== modified file 'include/linux/mm_types.h'
+--- old/include/linux/mm_types.h 2012-01-10 22:58:17 +0000
++++ new/include/linux/mm_types.h 2012-01-10 23:02:48 +0000
+@@ -149,6 +149,17 @@ struct page {
+ */
+ void *shadow;
+ #endif
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ /*
++ * Used to implement support for notification on zero-copy TCP transfer
++ * completion. It might look as not good to have this field here and
++ * it's better to have it in struct sk_buff, but it would make the code
++ * much more complicated and fragile, since all skb then would have to
++ * contain only pages with the same value in this field.
++ */
++ void *net_priv;
++#endif
+ }
+ /*
+ * If another subsystem starts using the double word pairing for atomic
+
+=== modified file 'include/linux/net.h'
+--- old/include/linux/net.h 2012-01-10 22:58:17 +0000
++++ new/include/linux/net.h 2012-01-10 23:02:48 +0000
+@@ -61,6 +61,7 @@ typedef enum {
+ #include <linux/fcntl.h> /* For O_CLOEXEC and O_NONBLOCK */
+ #include <linux/kmemcheck.h>
+ #include <linux/rcupdate.h>
++#include <linux/mm.h>
+
+ struct poll_table_struct;
+ struct pipe_inode_info;
+@@ -289,5 +290,44 @@ extern int kernel_sock_shutdown(struct s
+ MODULE_ALIAS("net-pf-" __stringify(pf) "-proto-" __stringify(proto) \
+ "-type-" __stringify(type))
+
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++/* Support for notification on zero-copy TCP transfer completion */
++typedef void (*net_get_page_callback_t)(struct page *page);
++typedef void (*net_put_page_callback_t)(struct page *page);
++
++extern net_get_page_callback_t net_get_page_callback;
++extern net_put_page_callback_t net_put_page_callback;
++
++extern int net_set_get_put_page_callbacks(
++ net_get_page_callback_t get_callback,
++ net_put_page_callback_t put_callback);
++
++/*
++ * See comment for net_set_get_put_page_callbacks() why those functions
++ * don't need any protection.
++ */
++static inline void net_get_page(struct page *page)
++{
++ if (page->net_priv != 0)
++ net_get_page_callback(page);
++ get_page(page);
++}
++static inline void net_put_page(struct page *page)
++{
++ if (page->net_priv != 0)
++ net_put_page_callback(page);
++ put_page(page);
++}
++#else
++static inline void net_get_page(struct page *page)
++{
++ get_page(page);
++}
++static inline void net_put_page(struct page *page)
++{
++ put_page(page);
++}
++#endif /* CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION */
++
+ #endif /* __KERNEL__ */
+ #endif /* _LINUX_NET_H */
+
+=== modified file 'include/linux/skbuff.h'
+--- old/include/linux/skbuff.h 2012-01-10 22:58:17 +0000
++++ new/include/linux/skbuff.h 2012-01-10 23:15:31 +0000
+@@ -1712,7 +1712,7 @@ static inline struct page *skb_frag_page
+ */
+ static inline void __skb_frag_ref(skb_frag_t *frag)
+ {
+- get_page(skb_frag_page(frag));
++ net_get_page(skb_frag_page(frag));
+ }
+
+ /**
+@@ -1735,7 +1735,7 @@ static inline void skb_frag_ref(struct s
+ */
+ static inline void __skb_frag_unref(skb_frag_t *frag)
+ {
+- put_page(skb_frag_page(frag));
++ net_put_page(skb_frag_page(frag));
+ }
+
+ /**
+
+=== modified file 'net/Kconfig'
+--- old/net/Kconfig 2012-01-10 22:58:17 +0000
++++ new/net/Kconfig 2012-01-10 23:02:48 +0000
+@@ -72,6 +72,18 @@ config INET
+
+ Short answer: say Y.
+
++config TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION
++ bool "TCP/IP zero-copy transfer completion notification"
++ depends on INET
++ default SCST_ISCSI
++ ---help---
++ Adds support for sending a notification upon completion of a
++ zero-copy TCP/IP transfer. This can speed up certain TCP/IP
++ software. Currently this is only used by the iSCSI target driver
++ iSCSI-SCST.
++
++ If unsure, say N.
++
+ if INET
+ source "net/ipv4/Kconfig"
+ source "net/ipv6/Kconfig"
+
+=== modified file 'net/core/skbuff.c'
+--- old/net/core/skbuff.c 2012-01-10 22:58:17 +0000
++++ new/net/core/skbuff.c 2012-01-10 23:02:48 +0000
+@@ -77,13 +77,13 @@ static struct kmem_cache *skbuff_fclone_
+ static void sock_pipe_buf_release(struct pipe_inode_info *pipe,
+ struct pipe_buffer *buf)
+ {
+- put_page(buf->page);
++ net_put_page(buf->page);
+ }
+
+ static void sock_pipe_buf_get(struct pipe_inode_info *pipe,
+ struct pipe_buffer *buf)
+ {
+- get_page(buf->page);
++ net_get_page(buf->page);
+ }
+
+ static int sock_pipe_buf_steal(struct pipe_inode_info *pipe,
+@@ -654,7 +654,7 @@ int skb_copy_ubufs(struct sk_buff *skb,
+ if (!page) {
+ while (head) {
+ struct page *next = (struct page *)head->private;
+- put_page(head);
++ net_put_page(head);
+ head = next;
+ }
+ return -ENOMEM;
+@@ -1493,7 +1493,7 @@ EXPORT_SYMBOL(skb_copy_bits);
+ */
+ static void sock_spd_release(struct splice_pipe_desc *spd, unsigned int i)
+ {
+- put_page(spd->pages[i]);
++ net_put_page(spd->pages[i]);
+ }
+
+ static inline struct page *linear_to_page(struct page *page, unsigned int *len,
+@@ -1517,7 +1517,7 @@ new_page:
+ off = sk->sk_sndmsg_off;
+ mlen = PAGE_SIZE - off;
+ if (mlen < 64 && mlen < *len) {
+- put_page(p);
++ net_put_page(p);
+ goto new_page;
+ }
+
+@@ -1527,7 +1527,7 @@ new_page:
+ memcpy(page_address(p) + off, page_address(page) + *offset, *len);
+ sk->sk_sndmsg_off += *len;
+ *offset = off;
+- get_page(p);
++ net_get_page(p);
+
+ return p;
+ }
+@@ -1549,7 +1549,7 @@ static inline int spd_fill_page(struct s
+ if (!page)
+ return 1;
+ } else
+- get_page(page);
++ net_get_page(page);
+
+ spd->pages[spd->nr_pages] = page;
+ spd->partial[spd->nr_pages].len = *len;
+
+=== modified file 'net/ipv4/Makefile'
+--- old/net/ipv4/Makefile 2012-01-10 22:58:17 +0000
++++ new/net/ipv4/Makefile 2012-01-10 23:02:48 +0000
+@@ -48,6 +48,7 @@ obj-$(CONFIG_TCP_CONG_LP) += tcp_lp.o
+ obj-$(CONFIG_TCP_CONG_YEAH) += tcp_yeah.o
+ obj-$(CONFIG_TCP_CONG_ILLINOIS) += tcp_illinois.o
+ obj-$(CONFIG_NETLABEL) += cipso_ipv4.o
++obj-$(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION) += tcp_zero_copy.o
+
+ obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \
+ xfrm4_output.o
+
+=== modified file 'net/ipv4/ip_output.c'
+--- old/net/ipv4/ip_output.c 2012-01-10 22:58:17 +0000
++++ new/net/ipv4/ip_output.c 2012-01-10 23:02:48 +0000
+@@ -1232,7 +1232,7 @@ ssize_t ip_append_page(struct sock *sk,
+ if (skb_can_coalesce(skb, i, page, offset)) {
+ skb_frag_size_add(&skb_shinfo(skb)->frags[i-1], len);
+ } else if (i < MAX_SKB_FRAGS) {
+- get_page(page);
++ net_get_page(page);
+ skb_fill_page_desc(skb, i, page, offset, len);
+ } else {
+ err = -EMSGSIZE;
+
+=== modified file 'net/ipv4/tcp.c'
+--- old/net/ipv4/tcp.c 2012-01-10 22:58:17 +0000
++++ new/net/ipv4/tcp.c 2012-01-10 23:02:48 +0000
+@@ -815,7 +815,7 @@ new_segment:
+ if (can_coalesce) {
+ skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
+ } else {
+- get_page(page);
++ net_get_page(page);
+ skb_fill_page_desc(skb, i, page, offset, copy);
+ }
+
+@@ -1022,7 +1022,7 @@ new_segment:
+ goto new_segment;
+ } else if (page) {
+ if (off == PAGE_SIZE) {
+- put_page(page);
++ net_put_page(page);
+ TCP_PAGE(sk) = page = NULL;
+ off = 0;
+ }
+@@ -1062,9 +1062,9 @@ new_segment:
+ } else {
+ skb_fill_page_desc(skb, i, page, off, copy);
+ if (TCP_PAGE(sk)) {
+- get_page(page);
++ net_get_page(page);
+ } else if (off + copy < PAGE_SIZE) {
+- get_page(page);
++ net_get_page(page);
+ TCP_PAGE(sk) = page;
+ }
+ }
+
+=== added file 'net/ipv4/tcp_zero_copy.c'
+--- old/net/ipv4/tcp_zero_copy.c 1970-01-01 00:00:00 +0000
++++ new/net/ipv4/tcp_zero_copy.c 2012-01-10 23:43:22 +0000
+@@ -0,0 +1,50 @@
++/*
++ * Support routines for TCP zero copy transmit
++ *
++ * Created by Vladislav Bolkhovitin
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * version 2 as published by the Free Software Foundation.
++ */
++
++#include <linux/export.h>
++#include <linux/skbuff.h>
++
++net_get_page_callback_t net_get_page_callback __read_mostly;
++EXPORT_SYMBOL_GPL(net_get_page_callback);
++
++net_put_page_callback_t net_put_page_callback __read_mostly;
++EXPORT_SYMBOL_GPL(net_put_page_callback);
++
++/*
++ * Caller of this function must ensure that at the moment when it's called
++ * there are no pages in the system with net_priv field set to non-zero
++ * value. Hence, this function, as well as net_get_page() and net_put_page(),
++ * don't need any protection.
++ */
++int net_set_get_put_page_callbacks(
++ net_get_page_callback_t get_callback,
++ net_put_page_callback_t put_callback)
++{
++ int res = 0;
++
++ if ((net_get_page_callback != NULL) && (get_callback != NULL) &&
++ (net_get_page_callback != get_callback)) {
++ res = -EBUSY;
++ goto out;
++ }
++
++ if ((net_put_page_callback != NULL) && (put_callback != NULL) &&
++ (net_put_page_callback != put_callback)) {
++ res = -EBUSY;
++ goto out;
++ }
++
++ net_get_page_callback = get_callback;
++ net_put_page_callback = put_callback;
++
++out:
++ return res;
++}
++EXPORT_SYMBOL_GPL(net_set_get_put_page_callbacks);
+
+diff --git a/drivers/Kconfig b/drivers/Kconfig
+index a2b902f..92e3d67 100644
+--- orig/linux-3.2/drivers/Kconfig
++++ linux-3.2/drivers/Kconfig
+@@ -22,6 +22,8 @@ source "drivers/ide/Kconfig"
+
+ source "drivers/scsi/Kconfig"
+
++source "drivers/scst/Kconfig"
++
+ source "drivers/ata/Kconfig"
+
+ source "drivers/md/Kconfig"
+diff --git a/drivers/Makefile b/drivers/Makefile
+index b423bb1..f780114 100644
+--- orig/linux-3.2/drivers/Makefile
++++ linux-3.2/drivers/Makefile
+@@ -115,5 +115,6 @@ obj-$(CONFIG_VLYNQ) += vlynq/
+ obj-$(CONFIG_STAGING) += staging/
+ obj-y += platform/
+ obj-y += ieee802154/
++obj-$(CONFIG_SCST) += scst/
+ #common clk code
+ obj-y += clk/
+diff -uprN orig/linux-3.2/drivers/scst/Kconfig linux-3.2/drivers/scst/Kconfig
+--- orig/linux-3.2/drivers/scst/Kconfig
++++ linux-3.2/drivers/scst/Kconfig
+@@ -0,0 +1,255 @@
++menu "SCSI target (SCST) support"
++
++config SCST
++ tristate "SCSI target (SCST) support"
++ depends on SCSI
++ help
++ SCSI target (SCST) is designed to provide unified, consistent
++ interface between SCSI target drivers and Linux kernel and
++ simplify target drivers development as much as possible. Visit
++ http://scst.sourceforge.net for more info about it.
++
++config SCST_DISK
++ tristate "SCSI target disk support"
++ default SCST
++ depends on SCSI && SCST
++ help
++ SCST pass-through device handler for disk device.
++
++config SCST_TAPE
++ tristate "SCSI target tape support"
++ default SCST
++ depends on SCSI && SCST
++ help
++ SCST pass-through device handler for tape device.
++
++config SCST_CDROM
++ tristate "SCSI target CDROM support"
++ default SCST
++ depends on SCSI && SCST
++ help
++ SCST pass-through device handler for CDROM device.
++
++config SCST_MODISK
++ tristate "SCSI target MO disk support"
++ default SCST
++ depends on SCSI && SCST
++ help
++ SCST pass-through device handler for MO disk device.
++
++config SCST_CHANGER
++ tristate "SCSI target changer support"
++ default SCST
++ depends on SCSI && SCST
++ help
++ SCST pass-through device handler for changer device.
++
++config SCST_PROCESSOR
++ tristate "SCSI target processor support"
++ default SCST
++ depends on SCSI && SCST
++ help
++ SCST pass-through device handler for processor device.
++
++config SCST_RAID
++ tristate "SCSI target storage array controller (RAID) support"
++ default SCST
++ depends on SCSI && SCST
++ help
++ SCST pass-through device handler for raid storage array controller (RAID) device.
++
++config SCST_VDISK
++ tristate "SCSI target virtual disk and/or CDROM support"
++ default SCST
++ depends on SCSI && SCST
++ help
++ SCST device handler for virtual disk and/or CDROM device.
++
++config SCST_USER
++ tristate "User-space SCSI target driver support"
++ default SCST
++ depends on SCSI && SCST && !HIGHMEM4G && !HIGHMEM64G
++ help
++ The SCST device handler scst_user allows to implement full-feature
++ SCSI target devices in user space.
++
++ If unsure, say "N".
++
++config SCST_STRICT_SERIALIZING
++ bool "Strict serialization"
++ depends on SCST
++ help
++ Enable strict SCSI command serialization. When enabled, SCST sends
++ all SCSI commands to the underlying SCSI device synchronously, one
++ after one. This makes task management more reliable, at the cost of
++ a performance penalty. This is most useful for stateful SCSI devices
++ like tapes, where the result of the execution of a command
++ depends on the device settings configured by previous commands. Disk
++ and RAID devices are stateless in most cases. The current SCSI core
++ in Linux doesn't allow to abort all commands reliably if they have
++ been sent asynchronously to a stateful device.
++ Enable this option if you use stateful device(s) and need as much
++ error recovery reliability as possible.
++
++ If unsure, say "N".
++
++config SCST_STRICT_SECURITY
++ bool "Strict security"
++ depends on SCST
++ help
++ Makes SCST clear (zero-fill) allocated data buffers. Note: this has a
++ significant performance penalty.
++
++ If unsure, say "N".
++
++config SCST_TEST_IO_IN_SIRQ
++ bool "Allow test I/O from soft-IRQ context"
++ depends on SCST
++ help
++ Allows SCST to submit selected SCSI commands (TUR and
++ READ/WRITE) from soft-IRQ context (tasklets). Enabling it will
++ decrease amount of context switches and slightly improve
++ performance. The goal of this option is to be able to measure
++ overhead of the context switches. See more info about it in
++ README.scst.
++
++ WARNING! Improperly used, this option can lead you to a kernel crash!
++
++ If unsure, say "N".
++
++config SCST_ABORT_CONSIDER_FINISHED_TASKS_AS_NOT_EXISTING
++ bool "Send back UNKNOWN TASK when an already finished task is aborted"
++ depends on SCST
++ help
++ Controls which response is sent by SCST to the initiator in case
++ the initiator attempts to abort (ABORT TASK) an already finished
++ request. If this option is enabled, the response UNKNOWN TASK is
++ sent back to the initiator. However, some initiators, particularly
++ the VMware iSCSI initiator, interpret the UNKNOWN TASK response as
++ if the target got crazy and try to RESET it. Then sometimes the
++ initiator gets crazy itself.
++
++ If unsure, say "N".
++
++config SCST_USE_EXPECTED_VALUES
++ bool "Prefer initiator-supplied SCSI command attributes"
++ depends on SCST
++ help
++ When SCST receives a SCSI command from an initiator, such a SCSI
++ command has both data transfer length and direction attributes.
++ There are two possible sources for these attributes: either the
++ values computed by SCST from its internal command translation table
++ or the values supplied by the initiator. The former are used by
++ default because of security reasons. Invalid initiator-supplied
++ attributes can crash the target, especially in pass-through mode.
++ Only consider enabling this option when SCST logs the following
++ message: "Unknown opcode XX for YY. Should you update
++ scst_scsi_op_table?" and when the initiator complains. Please
++ report any unrecognized commands to scst-devel@lists.sourceforge.net.
++
++ If unsure, say "N".
++
++config SCST_EXTRACHECKS
++ bool "Extra consistency checks"
++ depends on SCST
++ help
++ Enable additional consistency checks in the SCSI middle level target
++ code. This may be helpful for SCST developers. Enable it if you have
++ any problems.
++
++ If unsure, say "N".
++
++config SCST_TRACING
++ bool "Tracing support"
++ depends on SCST
++ default y
++ help
++ Enable SCSI middle level tracing support. Tracing can be controlled
++ dynamically via sysfs interface. The traced information
++ is sent to the kernel log and may be very helpful when analyzing
++ the cause of a communication problem between initiator and target.
++
++ If unsure, say "Y".
++
++config SCST_DEBUG
++ bool "Debugging support"
++ depends on SCST
++ select DEBUG_BUGVERBOSE
++ help
++ Enables support for debugging SCST. This may be helpful for SCST
++ developers.
++
++ If unsure, say "N".
++
++config SCST_DEBUG_OOM
++ bool "Out-of-memory debugging support"
++ depends on SCST
++ help
++ Let SCST's internal memory allocation function
++ (scst_alloc_sg_entries()) fail about once in every 10000 calls, at
++ least if the flag __GFP_NOFAIL has not been set. This allows SCST
++ developers to test the behavior of SCST in out-of-memory conditions.
++ This may be helpful for SCST developers.
++
++ If unsure, say "N".
++
++config SCST_DEBUG_RETRY
++ bool "SCSI command retry debugging support"
++ depends on SCST
++ help
++ Let SCST's internal SCSI command transfer function
++ (scst_rdy_to_xfer()) fail about once in every 100 calls. This allows
++ SCST developers to test the behavior of SCST when SCSI queues fill
++ up. This may be helpful for SCST developers.
++
++ If unsure, say "N".
++
++config SCST_DEBUG_SN
++ bool "SCSI sequence number debugging support"
++ depends on SCST
++ help
++ Allows to test SCSI command ordering via sequence numbers by
++ randomly changing the type of SCSI commands into
++ SCST_CMD_QUEUE_ORDERED, SCST_CMD_QUEUE_HEAD_OF_QUEUE or
++ SCST_CMD_QUEUE_SIMPLE for about one in 300 SCSI commands.
++ This may be helpful for SCST developers.
++
++ If unsure, say "N".
++
++config SCST_DEBUG_TM
++ bool "Task management debugging support"
++ depends on SCST_DEBUG
++ help
++ Enables support for debugging of SCST's task management functions.
++ When enabled, some of the commands on LUN 0 in the default access
++ control group will be delayed for about 60 seconds. This will
++ cause the remote initiator send SCSI task management functions,
++ e.g. ABORT TASK and TARGET RESET.
++
++ If unsure, say "N".
++
++config SCST_TM_DBG_GO_OFFLINE
++ bool "Let devices become completely unresponsive"
++ depends on SCST_DEBUG_TM
++ help
++ Enable this option if you want that the device eventually becomes
++ completely unresponsive. When disabled, the device will receive
++ ABORT and RESET commands.
++
++config SCST_MEASURE_LATENCY
++ bool "Commands processing latency measurement facility"
++ depends on SCST
++ help
++ This option enables commands processing latency measurement
++ facility in SCST. It will provide in the sysfs interface
++ average commands processing latency statistics. You can clear
++ already measured results by writing 0 in the corresponding sysfs file.
++ Note, you need a non-preemtible kernel to have correct results.
++
++ If unsure, say "N".
++
++source "drivers/scst/iscsi-scst/Kconfig"
++source "drivers/scst/scst_local/Kconfig"
++source "drivers/scst/srpt/Kconfig"
++
++endmenu
+diff -uprN orig/linux-3.2/drivers/scst/Makefile linux-3.2/drivers/scst/Makefile
+--- orig/linux-3.2/drivers/scst/Makefile
++++ linux-3.2/drivers/scst/Makefile
+@@ -0,0 +1,13 @@
++ccflags-y += -Wno-unused-parameter
++
++scst-y += scst_main.o
++scst-y += scst_pres.o
++scst-y += scst_targ.o
++scst-y += scst_lib.o
++scst-y += scst_sysfs.o
++scst-y += scst_mem.o
++scst-y += scst_tg.o
++scst-y += scst_debug.o
++
++obj-$(CONFIG_SCST) += scst.o dev_handlers/ iscsi-scst/ qla2xxx-target/ \
++ srpt/ scst_local/
+diff -uprN orig/linux-3.2/include/scst/scst.h linux-3.2/include/scst/scst.h
+--- orig/linux-3.2/include/scst/scst.h
++++ linux-3.2/include/scst/scst.h
+@@ -0,0 +1,3867 @@
++/*
++ * include/scst.h
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ * Copyright (C) 2010 - 2011 Bart Van Assche <bvanassche@acm.org>.
++ *
++ * Main SCSI target mid-level include 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.
++ */
++
++#ifndef __SCST_H
++#define __SCST_H
++
++#include <linux/types.h>
++#include <linux/blkdev.h>
++#include <linux/interrupt.h>
++#include <linux/wait.h>
++#include <linux/cpumask.h>
++
++
++#include <scsi/scsi_cmnd.h>
++#include <scsi/scsi_device.h>
++#include <scsi/scsi_eh.h>
++#include <scsi/scsi.h>
++
++#include <scst/scst_const.h>
++
++#include <scst/scst_sgv.h>
++
++#define SCST_INTERFACE_VERSION \
++ SCST_VERSION_STRING "$Revision: 3992 $" SCST_CONST_VERSION
++
++#define SCST_LOCAL_NAME "scst_local"
++
++/*************************************************************
++ ** States of command processing state machine. At first,
++ ** "active" states, then - "passive" ones. This is to have
++ ** more efficient generated code of the corresponding
++ ** "switch" statements.
++ *************************************************************/
++
++/* Dev handler's parse() is going to be called */
++#define SCST_CMD_STATE_PARSE 0
++
++/* Allocation of the cmd's data buffer */
++#define SCST_CMD_STATE_PREPARE_SPACE 1
++
++/* Calling preprocessing_done() */
++#define SCST_CMD_STATE_PREPROCESSING_DONE 2
++
++/* Target driver's rdy_to_xfer() is going to be called */
++#define SCST_CMD_STATE_RDY_TO_XFER 3
++
++/* Target driver's pre_exec() is going to be called */
++#define SCST_CMD_STATE_TGT_PRE_EXEC 4
++
++/* Cmd is going to be sent for execution */
++#define SCST_CMD_STATE_SEND_FOR_EXEC 5
++
++/* Internal post-exec checks */
++#define SCST_CMD_STATE_PRE_DEV_DONE 6
++
++/* Internal MODE SELECT pages related checks */
++#define SCST_CMD_STATE_MODE_SELECT_CHECKS 7
++
++/* Dev handler's dev_done() is going to be called */
++#define SCST_CMD_STATE_DEV_DONE 8
++
++/* Checks before target driver's xmit_response() is called */
++#define SCST_CMD_STATE_PRE_XMIT_RESP 9
++
++/* Target driver's xmit_response() is going to be called */
++#define SCST_CMD_STATE_XMIT_RESP 10
++
++/* Cmd finished */
++#define SCST_CMD_STATE_FINISHED 11
++
++/* Internal cmd finished */
++#define SCST_CMD_STATE_FINISHED_INTERNAL 12
++
++#define SCST_CMD_STATE_LAST_ACTIVE (SCST_CMD_STATE_FINISHED_INTERNAL+100)
++
++/* A cmd is created, but scst_cmd_init_done() not called */
++#define SCST_CMD_STATE_INIT_WAIT (SCST_CMD_STATE_LAST_ACTIVE+1)
++
++/* LUN translation (cmd->tgt_dev assignment) */
++#define SCST_CMD_STATE_INIT (SCST_CMD_STATE_LAST_ACTIVE+2)
++
++/* Waiting for scst_restart_cmd() */
++#define SCST_CMD_STATE_PREPROCESSING_DONE_CALLED (SCST_CMD_STATE_LAST_ACTIVE+3)
++
++/* Waiting for data from the initiator (until scst_rx_data() called) */
++#define SCST_CMD_STATE_DATA_WAIT (SCST_CMD_STATE_LAST_ACTIVE+4)
++
++/*
++ * Cmd is ready for exec (after check if its device is blocked or should
++ * be blocked)
++ */
++#define SCST_CMD_STATE_START_EXEC (SCST_CMD_STATE_LAST_ACTIVE+5)
++
++/* Cmd is being checked if it should be executed locally */
++#define SCST_CMD_STATE_LOCAL_EXEC (SCST_CMD_STATE_LAST_ACTIVE+6)
++
++/* Cmd is ready for execution */
++#define SCST_CMD_STATE_REAL_EXEC (SCST_CMD_STATE_LAST_ACTIVE+7)
++
++/* Waiting for CDB's execution finish */
++#define SCST_CMD_STATE_REAL_EXECUTING (SCST_CMD_STATE_LAST_ACTIVE+8)
++
++/* Waiting for response's transmission finish */
++#define SCST_CMD_STATE_XMIT_WAIT (SCST_CMD_STATE_LAST_ACTIVE+9)
++
++/*************************************************************
++ * Can be returned instead of cmd's state by dev handlers'
++ * functions, if the command's state should be set by default
++ *************************************************************/
++#define SCST_CMD_STATE_DEFAULT 500
++
++/*************************************************************
++ * Can be returned instead of cmd's state by dev handlers'
++ * functions, if it is impossible to complete requested
++ * task in atomic context. The cmd will be restarted in thread
++ * context.
++ *************************************************************/
++#define SCST_CMD_STATE_NEED_THREAD_CTX 1000
++
++/*************************************************************
++ * Can be returned instead of cmd's state by dev handlers'
++ * parse function, if the cmd processing should be stopped
++ * for now. The cmd will be restarted by dev handlers itself.
++ *************************************************************/
++#define SCST_CMD_STATE_STOP 1001
++
++/*************************************************************
++ ** States of mgmt command processing state machine
++ *************************************************************/
++
++/* LUN translation (mcmd->tgt_dev assignment) */
++#define SCST_MCMD_STATE_INIT 0
++
++/* Mgmt cmd is being processed */
++#define SCST_MCMD_STATE_EXEC 1
++
++/* Waiting for affected commands done */
++#define SCST_MCMD_STATE_WAITING_AFFECTED_CMDS_DONE 2
++
++/* Post actions when affected commands done */
++#define SCST_MCMD_STATE_AFFECTED_CMDS_DONE 3
++
++/* Waiting for affected local commands finished */
++#define SCST_MCMD_STATE_WAITING_AFFECTED_CMDS_FINISHED 4
++
++/* Target driver's task_mgmt_fn_done() is going to be called */
++#define SCST_MCMD_STATE_DONE 5
++
++/* The mcmd finished */
++#define SCST_MCMD_STATE_FINISHED 6
++
++/*************************************************************
++ ** Constants for "atomic" parameter of SCST's functions
++ *************************************************************/
++#define SCST_NON_ATOMIC 0
++#define SCST_ATOMIC 1
++
++/*************************************************************
++ ** Values for pref_context parameter of scst_cmd_init_done(),
++ ** scst_rx_data(), scst_restart_cmd(), scst_tgt_cmd_done()
++ ** and scst_cmd_done()
++ *************************************************************/
++
++enum scst_exec_context {
++ /*
++ * Direct cmd's processing (i.e. regular function calls in the current
++ * context) sleeping is not allowed
++ */
++ SCST_CONTEXT_DIRECT_ATOMIC,
++
++ /*
++ * Direct cmd's processing (i.e. regular function calls in the current
++ * context), sleeping is allowed, no restrictions
++ */
++ SCST_CONTEXT_DIRECT,
++
++ /* Tasklet or thread context required for cmd's processing */
++ SCST_CONTEXT_TASKLET,
++
++ /* Thread context required for cmd's processing */
++ SCST_CONTEXT_THREAD,
++
++ /*
++ * Context is the same as it was in previous call of the corresponding
++ * callback. For example, if dev handler's exec() does sync. data
++ * reading this value should be used for scst_cmd_done(). The same is
++ * true if scst_tgt_cmd_done() called directly from target driver's
++ * xmit_response(). Not allowed in scst_cmd_init_done() and
++ * scst_cmd_init_stage1_done().
++ */
++ SCST_CONTEXT_SAME
++};
++
++/*************************************************************
++ ** Values for status parameter of scst_rx_data()
++ *************************************************************/
++
++/* Success */
++#define SCST_RX_STATUS_SUCCESS 0
++
++/*
++ * Data receiving finished with error, so set the sense and
++ * finish the command, including xmit_response() call
++ */
++#define SCST_RX_STATUS_ERROR 1
++
++/*
++ * Data receiving finished with error and the sense is set,
++ * so finish the command, including xmit_response() call
++ */
++#define SCST_RX_STATUS_ERROR_SENSE_SET 2
++
++/*
++ * Data receiving finished with fatal error, so finish the command,
++ * but don't call xmit_response()
++ */
++#define SCST_RX_STATUS_ERROR_FATAL 3
++
++/*************************************************************
++ ** Values for status parameter of scst_restart_cmd()
++ *************************************************************/
++
++/* Success */
++#define SCST_PREPROCESS_STATUS_SUCCESS 0
++
++/*
++ * Command's processing finished with error, so set the sense and
++ * finish the command, including xmit_response() call
++ */
++#define SCST_PREPROCESS_STATUS_ERROR 1
++
++/*
++ * Command's processing finished with error and the sense is set,
++ * so finish the command, including xmit_response() call
++ */
++#define SCST_PREPROCESS_STATUS_ERROR_SENSE_SET 2
++
++/*
++ * Command's processing finished with fatal error, so finish the command,
++ * but don't call xmit_response()
++ */
++#define SCST_PREPROCESS_STATUS_ERROR_FATAL 3
++
++/*************************************************************
++ ** Values for AEN functions
++ *************************************************************/
++
++/*
++ * SCSI Asynchronous Event. Parameter contains SCSI sense
++ * (Unit Attention). AENs generated only for 2 the following UAs:
++ * CAPACITY DATA HAS CHANGED and REPORTED LUNS DATA HAS CHANGED.
++ * Other UAs reported regularly as CHECK CONDITION status,
++ * because it doesn't look safe to report them using AENs, since
++ * reporting using AENs opens delivery race windows even in case of
++ * untagged commands.
++ */
++#define SCST_AEN_SCSI 0
++
++/*
++ * Notifies that CPU affinity mask on the corresponding session changed
++ */
++#define SCST_AEN_CPU_MASK_CHANGED 1
++
++/*************************************************************
++ ** Allowed return/status codes for report_aen() callback and
++ ** scst_set_aen_delivery_status() function
++ *************************************************************/
++
++/* Success */
++#define SCST_AEN_RES_SUCCESS 0
++
++/* Not supported */
++#define SCST_AEN_RES_NOT_SUPPORTED -1
++
++/* Failure */
++#define SCST_AEN_RES_FAILED -2
++
++/*************************************************************
++ ** Allowed return codes for xmit_response(), rdy_to_xfer()
++ *************************************************************/
++
++/* Success */
++#define SCST_TGT_RES_SUCCESS 0
++
++/* Internal device queue is full, retry again later */
++#define SCST_TGT_RES_QUEUE_FULL -1
++
++/*
++ * It is impossible to complete requested task in atomic context.
++ * The cmd will be restarted in thread context.
++ */
++#define SCST_TGT_RES_NEED_THREAD_CTX -2
++
++/*
++ * Fatal error, if returned by xmit_response() the cmd will
++ * be destroyed, if by any other function, xmit_response()
++ * will be called with HARDWARE ERROR sense data
++ */
++#define SCST_TGT_RES_FATAL_ERROR -3
++
++/*************************************************************
++ ** Return codes for dev handler's exec()
++ *************************************************************/
++
++/* The cmd is done, go to other ones */
++#define SCST_EXEC_COMPLETED 0
++
++/* The cmd should be sent to SCSI mid-level */
++#define SCST_EXEC_NOT_COMPLETED 1
++
++/*************************************************************
++ ** Additional return code for dev handler's task_mgmt_fn()
++ *************************************************************/
++
++/* Regular standard actions for the command should be done */
++#define SCST_DEV_TM_NOT_COMPLETED 1
++
++/*************************************************************
++ ** Session initialization phases
++ *************************************************************/
++
++/* Set if session is being initialized */
++#define SCST_SESS_IPH_INITING 0
++
++/* Set if the session is successfully initialized */
++#define SCST_SESS_IPH_SUCCESS 1
++
++/* Set if the session initialization failed */
++#define SCST_SESS_IPH_FAILED 2
++
++/* Set if session is initialized and ready */
++#define SCST_SESS_IPH_READY 3
++
++/*************************************************************
++ ** Session shutdown phases
++ *************************************************************/
++
++/* Set if session is initialized and ready */
++#define SCST_SESS_SPH_READY 0
++
++/* Set if session is shutting down */
++#define SCST_SESS_SPH_SHUTDOWN 1
++
++/* Set if session is shutting down */
++#define SCST_SESS_SPH_UNREG_DONE_CALLING 2
++
++/*************************************************************
++ ** Session's async (atomic) flags
++ *************************************************************/
++
++/* Set if the sess's hw pending work is scheduled */
++#define SCST_SESS_HW_PENDING_WORK_SCHEDULED 0
++
++/*************************************************************
++ ** Cmd's async (atomic) flags
++ *************************************************************/
++
++/* Set if the cmd is aborted and ABORTED sense will be sent as the result */
++#define SCST_CMD_ABORTED 0
++
++/* Set if the cmd is aborted by other initiator */
++#define SCST_CMD_ABORTED_OTHER 1
++
++/* Set if no response should be sent to the target about this cmd */
++#define SCST_CMD_NO_RESP 2
++
++/* Set if the cmd is dead and can be destroyed at any time */
++#define SCST_CMD_CAN_BE_DESTROYED 3
++
++/*
++ * Set if the cmd's device has TAS flag set. Used only when aborted by
++ * other initiator.
++ */
++#define SCST_CMD_DEVICE_TAS 4
++
++/*************************************************************
++ ** Tgt_dev's async. flags (tgt_dev_flags)
++ *************************************************************/
++
++/* Set if tgt_dev has Unit Attention sense */
++#define SCST_TGT_DEV_UA_PENDING 0
++
++/* Set if tgt_dev is RESERVED by another session */
++#define SCST_TGT_DEV_RESERVED 1
++
++/* Set if the corresponding context should be atomic */
++#define SCST_TGT_DEV_AFTER_INIT_WR_ATOMIC 5
++#define SCST_TGT_DEV_AFTER_EXEC_ATOMIC 6
++
++#define SCST_TGT_DEV_CLUST_POOL 11
++
++/*************************************************************
++ ** I/O grouping types. Changing them don't forget to change
++ ** the corresponding *_STR values in scst_const.h!
++ *************************************************************/
++
++/*
++ * All initiators with the same name connected to this group will have
++ * shared IO context, for each name own context. All initiators with
++ * different names will have own IO context.
++ */
++#define SCST_IO_GROUPING_AUTO 0
++
++/* All initiators connected to this group will have shared IO context */
++#define SCST_IO_GROUPING_THIS_GROUP_ONLY -1
++
++/* Each initiator connected to this group will have own IO context */
++#define SCST_IO_GROUPING_NEVER -2
++
++/*************************************************************
++ ** Kernel cache creation helper
++ *************************************************************/
++
++/*************************************************************
++ ** Valid_mask constants for scst_analyze_sense()
++ *************************************************************/
++
++#define SCST_SENSE_KEY_VALID 1
++#define SCST_SENSE_ASC_VALID 2
++#define SCST_SENSE_ASCQ_VALID 4
++
++#define SCST_SENSE_ASCx_VALID (SCST_SENSE_ASC_VALID | \
++ SCST_SENSE_ASCQ_VALID)
++
++#define SCST_SENSE_ALL_VALID (SCST_SENSE_KEY_VALID | \
++ SCST_SENSE_ASC_VALID | \
++ SCST_SENSE_ASCQ_VALID)
++
++/*************************************************************
++ * TYPES
++ *************************************************************/
++
++struct scst_tgt;
++struct scst_session;
++struct scst_cmd;
++struct scst_mgmt_cmd;
++struct scst_device;
++struct scst_tgt_dev;
++struct scst_dev_type;
++struct scst_acg;
++struct scst_acg_dev;
++struct scst_acn;
++struct scst_aen;
++
++/*
++ * SCST uses 64-bit numbers to represent LUN's internally. The value
++ * NO_SUCH_LUN is guaranteed to be different of every valid LUN.
++ */
++#define NO_SUCH_LUN ((uint64_t)-1)
++
++typedef enum dma_data_direction scst_data_direction;
++
++/*
++ * SCST target template: defines target driver's parameters and callback
++ * functions.
++ *
++ * MUST HAVEs define functions that are expected to be defined in order to
++ * work. OPTIONAL says that there is a choice.
++ */
++struct scst_tgt_template {
++ /* public: */
++
++ /*
++ * SG tablesize allows to check whether scatter/gather can be used
++ * or not.
++ */
++ int sg_tablesize;
++
++ /*
++ * True, if this target adapter uses unchecked DMA onto an ISA bus.
++ */
++ unsigned unchecked_isa_dma:1;
++
++ /*
++ * True, if this target adapter can benefit from using SG-vector
++ * clustering (i.e. smaller number of segments).
++ */
++ unsigned use_clustering:1;
++
++ /*
++ * True, if this target adapter doesn't support SG-vector clustering
++ */
++ unsigned no_clustering:1;
++
++ /*
++ * True, if corresponding function supports execution in
++ * the atomic (non-sleeping) context
++ */
++ unsigned xmit_response_atomic:1;
++ unsigned rdy_to_xfer_atomic:1;
++
++ /* True, if this target doesn't need "enabled" attribute */
++ unsigned enabled_attr_not_needed:1;
++
++ /*
++ * True if SCST should report that it supports ACA although it does
++ * not yet support ACA. Necessary for the IBM virtual SCSI target
++ * driver.
++ */
++ unsigned fake_aca:1;
++
++ /*
++ * Preferred SCSI LUN addressing method.
++ */
++ enum scst_lun_addr_method preferred_addr_method;
++
++ /*
++ * The maximum time in seconds cmd can stay inside the target
++ * hardware, i.e. after rdy_to_xfer() and xmit_response(), before
++ * on_hw_pending_cmd_timeout() will be called, if defined.
++ *
++ * In the current implementation a cmd will be aborted in time t
++ * max_hw_pending_time <= t < 2*max_hw_pending_time.
++ */
++ int max_hw_pending_time;
++
++ /*
++ * This function is equivalent to the SCSI
++ * queuecommand. The target should transmit the response
++ * buffer and the status in the scst_cmd struct.
++ * The expectation is that this executing this command is NON-BLOCKING.
++ * If it is blocking, consider to set threads_num to some none 0 number.
++ *
++ * After the response is actually transmitted, the target
++ * should call the scst_tgt_cmd_done() function of the
++ * mid-level, which will allow it to free up the command.
++ * Returns one of the SCST_TGT_RES_* constants.
++ *
++ * Pay attention to "atomic" attribute of the cmd, which can be get
++ * by scst_cmd_atomic(): it is true if the function called in the
++ * atomic (non-sleeping) context.
++ *
++ * MUST HAVE
++ */
++ int (*xmit_response) (struct scst_cmd *cmd);
++
++ /*
++ * This function informs the driver that data
++ * buffer corresponding to the said command have now been
++ * allocated and it is OK to receive data for this command.
++ * This function is necessary because a SCSI target does not
++ * have any control over the commands it receives. Most lower
++ * level protocols have a corresponding function which informs
++ * the initiator that buffers have been allocated e.g., XFER_
++ * RDY in Fibre Channel. After the data is actually received
++ * the low-level driver needs to call scst_rx_data() in order to
++ * continue processing this command.
++ * Returns one of the SCST_TGT_RES_* constants.
++ *
++ * This command is expected to be NON-BLOCKING.
++ * If it is blocking, consider to set threads_num to some none 0 number.
++ *
++ * Pay attention to "atomic" attribute of the cmd, which can be get
++ * by scst_cmd_atomic(): it is true if the function called in the
++ * atomic (non-sleeping) context.
++ *
++ * OPTIONAL
++ */
++ int (*rdy_to_xfer) (struct scst_cmd *cmd);
++
++ /*
++ * Called if cmd stays inside the target hardware, i.e. after
++ * rdy_to_xfer() and xmit_response(), more than max_hw_pending_time
++ * time. The target driver supposed to cleanup this command and
++ * resume cmd's processing.
++ *
++ * OPTIONAL
++ */
++ void (*on_hw_pending_cmd_timeout) (struct scst_cmd *cmd);
++
++ /*
++ * Called to notify the driver that the command is about to be freed.
++ * Necessary, because for aborted commands xmit_response() could not
++ * be called. Could be called on IRQ context.
++ *
++ * OPTIONAL
++ */
++ void (*on_free_cmd) (struct scst_cmd *cmd);
++
++ /*
++ * 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 (preferably -ENOMEM)
++ * in case of error, or > 0 if the regular SCST allocation should be
++ * done. In case of returning successfully,
++ * scst_cmd->tgt_data_buf_alloced will be set by SCST.
++ *
++ * It is possible that both target driver and dev handler request own
++ * memory allocation. In this case, data will be memcpy() between
++ * buffers, where necessary.
++ *
++ * If allocation in atomic context - cf. scst_cmd_atomic() - is not
++ * 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 itself all relevant
++ * details such as scatterlist setup, highmem, freeing the allocated
++ * memory, etc.
++ *
++ * OPTIONAL.
++ */
++ int (*alloc_data_buf) (struct scst_cmd *cmd);
++
++ /*
++ * This function informs the driver that data
++ * buffer corresponding to the said command have now been
++ * allocated and other preprocessing tasks have been done.
++ * A target driver could need to do some actions at this stage.
++ * After the target driver done the needed actions, it shall call
++ * scst_restart_cmd() in order to continue processing this command.
++ * In case of preliminary the command completion, this function will
++ * also be called before xmit_response().
++ *
++ * Called only if the cmd is queued using scst_cmd_init_stage1_done()
++ * instead of scst_cmd_init_done().
++ *
++ * Returns void, the result is expected to be returned using
++ * scst_restart_cmd().
++ *
++ * This command is expected to be NON-BLOCKING.
++ * If it is blocking, consider to set threads_num to some none 0 number.
++ *
++ * Pay attention to "atomic" attribute of the cmd, which can be get
++ * by scst_cmd_atomic(): it is true if the function called in the
++ * atomic (non-sleeping) context.
++ *
++ * OPTIONAL.
++ */
++ void (*preprocessing_done) (struct scst_cmd *cmd);
++
++ /*
++ * This function informs the driver that the said command is about
++ * to be executed.
++ *
++ * Returns one of the SCST_PREPROCESS_* constants.
++ *
++ * This command is expected to be NON-BLOCKING.
++ * If it is blocking, consider to set threads_num to some none 0 number.
++ *
++ * OPTIONAL
++ */
++ int (*pre_exec) (struct scst_cmd *cmd);
++
++ /*
++ * This function informs the driver that all affected by the
++ * corresponding task management function commands have beed completed.
++ * No return value expected.
++ *
++ * This function is expected to be NON-BLOCKING.
++ *
++ * Called without any locks held from a thread context.
++ *
++ * OPTIONAL
++ */
++ void (*task_mgmt_affected_cmds_done) (struct scst_mgmt_cmd *mgmt_cmd);
++
++ /*
++ * This function informs the driver that the corresponding task
++ * management function has been completed, i.e. all the corresponding
++ * commands completed and freed. No return value expected.
++ *
++ * This function is expected to be NON-BLOCKING.
++ *
++ * Called without any locks held from a thread context.
++ *
++ * MUST HAVE if the target supports task management.
++ */
++ void (*task_mgmt_fn_done) (struct scst_mgmt_cmd *mgmt_cmd);
++
++ /*
++ * Called to notify target driver that the command is being aborted.
++ * If target driver wants to redirect processing to some outside
++ * processing, it should get it using scst_cmd_get().
++ *
++ * OPTIONAL
++ */
++ void (*on_abort_cmd) (struct scst_cmd *cmd);
++
++ /*
++ * This function should detect the target adapters that
++ * are present in the system. The function should return a value
++ * >= 0 to signify the number of detected target adapters.
++ * A negative value should be returned whenever there is
++ * an error.
++ *
++ * MUST HAVE
++ */
++ int (*detect) (struct scst_tgt_template *tgt_template);
++
++ /*
++ * This function should free up the resources allocated to the device.
++ * The function should return 0 to indicate successful release
++ * or a negative value if there are some issues with the release.
++ * In the current version the return value is ignored.
++ *
++ * MUST HAVE
++ */
++ int (*release) (struct scst_tgt *tgt);
++
++ /*
++ * This function is used for Asynchronous Event Notifications.
++ *
++ * Returns one of the SCST_AEN_RES_* constants.
++ * After AEN is sent, target driver must call scst_aen_done() and,
++ * optionally, scst_set_aen_delivery_status().
++ *
++ * This function is expected to be NON-BLOCKING, but can sleep.
++ *
++ * This function must be prepared to handle AENs between calls for the
++ * corresponding session of scst_unregister_session() and
++ * unreg_done_fn() callback called or before scst_unregister_session()
++ * returned, if its called in the blocking mode. AENs for such sessions
++ * should be ignored.
++ *
++ * MUST HAVE, if low-level protocol supports AENs.
++ */
++ int (*report_aen) (struct scst_aen *aen);
++
++ /*
++ * This function returns in tr_id the corresponding to sess initiator
++ * port TransportID in the form as it's used by PR commands, see
++ * "Transport Identifiers" in SPC. Space for the initiator port
++ * TransportID must be allocated via kmalloc(). Caller supposed to
++ * kfree() it, when it isn't needed anymore.
++ *
++ * If sess is NULL, this function must return TransportID PROTOCOL
++ * IDENTIFIER for the requested target.
++ *
++ * Returns 0 on success or negative error code otherwise.
++ *
++ * SHOULD HAVE, because it's required for Persistent Reservations.
++ */
++ int (*get_initiator_port_transport_id) (struct scst_tgt *tgt,
++ struct scst_session *sess, uint8_t **transport_id);
++
++ /*
++ * This function allows to enable or disable particular target.
++ * A disabled target doesn't receive and process any SCSI commands.
++ *
++ * SHOULD HAVE to avoid race when there are connected initiators,
++ * while target not yet completed the initial configuration. In this
++ * case the too early connected initiators would see not those devices,
++ * which they intended to see.
++ *
++ * If you are sure your target driver doesn't need enabling target,
++ * you should set enabled_attr_not_needed in 1.
++ */
++ int (*enable_target) (struct scst_tgt *tgt, bool enable);
++
++ /*
++ * This function shows if particular target is enabled or not.
++ *
++ * SHOULD HAVE, see above why.
++ */
++ bool (*is_target_enabled) (struct scst_tgt *tgt);
++
++ /*
++ * This function adds a virtual target.
++ *
++ * If both add_target and del_target callbacks defined, then this
++ * target driver supposed to support virtual targets. In this case
++ * an "mgmt" entry will be created in the sysfs root for this driver.
++ * The "mgmt" entry will support 2 commands: "add_target" and
++ * "del_target", for which the corresponding callbacks will be called.
++ * Also target driver can define own commands for the "mgmt" entry, see
++ * mgmt_cmd and mgmt_cmd_help below.
++ *
++ * This approach allows uniform targets management to simplify external
++ * management tools like scstadmin. See README for more details.
++ *
++ * Either both add_target and del_target must be defined, or none.
++ *
++ * MUST HAVE if virtual targets are supported.
++ */
++ ssize_t (*add_target) (const char *target_name, char *params);
++
++ /*
++ * This function deletes a virtual target. See comment for add_target
++ * above.
++ *
++ * MUST HAVE if virtual targets are supported.
++ */
++ ssize_t (*del_target) (const char *target_name);
++
++ /*
++ * This function called if not "add_target" or "del_target" command is
++ * sent to the mgmt entry (see comment for add_target above). In this
++ * case the command passed to this function as is in a string form.
++ *
++ * OPTIONAL.
++ */
++ ssize_t (*mgmt_cmd) (char *cmd);
++
++ /*
++ * Should return physical transport version. Used in the corresponding
++ * INQUIRY version descriptor. See SPC for the list of available codes.
++ *
++ * OPTIONAL
++ */
++ uint16_t (*get_phys_transport_version) (struct scst_tgt *tgt);
++
++ /*
++ * Should return SCSI transport version. Used in the corresponding
++ * INQUIRY version descriptor. See SPC for the list of available codes.
++ *
++ * OPTIONAL
++ */
++ uint16_t (*get_scsi_transport_version) (struct scst_tgt *tgt);
++
++ /*
++ * Name of the template. Must be unique to identify
++ * the template. MUST HAVE
++ */
++ const char name[SCST_MAX_NAME];
++
++ /*
++ * 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;
++
++ /* Optional default log flags */
++ const unsigned long default_trace_flags;
++
++ /* Optional pointer to trace flags */
++ unsigned long *trace_flags;
++
++ /* Optional local trace table */
++ struct scst_trace_log *trace_tbl;
++
++ /* Optional local trace table help string */
++ const char *trace_tbl_help;
++
++ /* sysfs attributes, if any */
++ const struct attribute **tgtt_attrs;
++
++ /* sysfs target attributes, if any */
++ const struct attribute **tgt_attrs;
++
++ /* sysfs session attributes, if any */
++ const struct attribute **sess_attrs;
++
++ /* Optional help string for mgmt_cmd commands */
++ const char *mgmt_cmd_help;
++
++ /* List of parameters for add_target command, if any */
++ const char *add_target_parameters;
++
++ /*
++ * List of optional, i.e. which could be added by add_attribute command
++ * and deleted by del_attribute command, sysfs attributes, if any.
++ * Helpful for scstadmin to work correctly.
++ */
++ const char *tgtt_optional_attributes;
++
++ /*
++ * List of optional, i.e. which could be added by add_target_attribute
++ * command and deleted by del_target_attribute command, sysfs
++ * attributes, if any. Helpful for scstadmin to work correctly.
++ */
++ const char *tgt_optional_attributes;
++
++ /** Private, must be inited to 0 by memset() **/
++
++ /* List of targets per template, protected by scst_mutex */
++ struct list_head tgt_list;
++
++ /* List entry of global templates list */
++ struct list_head scst_template_list_entry;
++
++ struct kobject tgtt_kobj; /* kobject for this struct */
++
++ /* Number of currently active sysfs mgmt works (scst_sysfs_work_item) */
++ int tgtt_active_sysfs_works_count;
++
++ /* sysfs release completion */
++ struct completion *tgtt_kobj_release_cmpl;
++
++ /*
++ * Optional vendor to be reported via the SCSI inquiry data. If NULL,
++ * an SCST device handler specific default value will be used, e.g.
++ * "SCST_FIO" for scst_vdisk file I/O.
++ */
++ const char *vendor;
++
++ /*
++ * Optional method that sets the product ID in [buf, buf+size) based
++ * on the device type (byte 0 of the SCSI inquiry data, which contains
++ * the peripheral qualifier in the highest three bits and the
++ * peripheral device type in the lower five bits).
++ */
++ void (*get_product_id)(const struct scst_tgt_dev *tgt_dev,
++ char *buf, int size);
++
++ /*
++ * Optional revision to be reported in the SCSI inquiry response. If
++ * NULL, an SCST device handler specific default value will be used,
++ * e.g. " 220" for scst_vdisk file I/O.
++ */
++ const char *revision;
++
++ /*
++ * Optional method that writes the serial number of a target device in
++ * [buf, buf+size) and returns the number of bytes written.
++ *
++ * Note: SCST can be configured such that a device can be accessed
++ * from several different transports at the same time. It is important
++ * that all clients see the same USN for proper operation. Overriding
++ * the serial number can lead to subtle misbehavior. Particularly,
++ * "usn" sysfs attribute of the corresponding devices will still show
++ * the devices generated or assigned serial numbers.
++ */
++ int (*get_serial)(const struct scst_tgt_dev *tgt_dev, char *buf,
++ int size);
++
++ /*
++ * Optional method that writes the SCSI inquiry vendor-specific data in
++ * [buf, buf+size) and returns the number of bytes written.
++ */
++ int (*get_vend_specific)(const struct scst_tgt_dev *tgt_dev, char *buf,
++ int size);
++};
++
++/*
++ * Threads pool types. Changing them don't forget to change
++ * the corresponding *_STR values in scst_const.h!
++ */
++enum scst_dev_type_threads_pool_type {
++ /* Each initiator will have dedicated threads pool. */
++ SCST_THREADS_POOL_PER_INITIATOR = 0,
++
++ /* All connected initiators will use shared threads pool */
++ SCST_THREADS_POOL_SHARED,
++
++ /* Invalid value for scst_parse_threads_pool_type() */
++ SCST_THREADS_POOL_TYPE_INVALID,
++};
++
++/*
++ * SCST dev handler template: defines dev handler's parameters and callback
++ * functions.
++ *
++ * MUST HAVEs define functions that are expected to be defined in order to
++ * work. OPTIONAL says that there is a choice.
++ */
++struct scst_dev_type {
++ /* SCSI type of the supported device. MUST HAVE */
++ int type;
++
++ /*
++ * True, if corresponding function supports execution in
++ * the atomic (non-sleeping) context
++ */
++ unsigned parse_atomic:1;
++ unsigned alloc_data_buf_atomic:1;
++ unsigned dev_done_atomic:1;
++
++ /*
++ * Should be true, if exec() is synchronous. This is a hint to SCST core
++ * to optimize commands order management.
++ */
++ unsigned exec_sync:1;
++
++ /*
++ * Should be set if the device wants to receive notification of
++ * Persistent Reservation commands (PR OUT only)
++ * Note: The notification will not be send if the command failed
++ */
++ unsigned pr_cmds_notifications:1;
++
++ /*
++ * Called to parse CDB from the cmd and initialize
++ * cmd->bufflen and cmd->data_direction (both - REQUIRED).
++ *
++ * Returns the command's next state or SCST_CMD_STATE_DEFAULT,
++ * if the next default state should be used, or
++ * SCST_CMD_STATE_NEED_THREAD_CTX if the function called in atomic
++ * context, but requires sleeping, or SCST_CMD_STATE_STOP if the
++ * command should not be further processed for now. In the
++ * SCST_CMD_STATE_NEED_THREAD_CTX case the function
++ * will be recalled in the thread context, where sleeping is allowed.
++ *
++ * Pay attention to "atomic" attribute of the cmd, which can be get
++ * by scst_cmd_atomic(): it is true if the function called in the
++ * atomic (non-sleeping) context.
++ *
++ * MUST HAVE
++ */
++ int (*parse) (struct scst_cmd *cmd);
++
++ /*
++ * This function allows dev handler to handle data buffer
++ * allocations on its own.
++ *
++ * Returns the command's next state or SCST_CMD_STATE_DEFAULT,
++ * if the next default state should be used, or
++ * SCST_CMD_STATE_NEED_THREAD_CTX if the function called in atomic
++ * context, but requires sleeping, or SCST_CMD_STATE_STOP if the
++ * command should not be further processed for now. In the
++ * SCST_CMD_STATE_NEED_THREAD_CTX case the function
++ * will be recalled in the thread context, where sleeping is allowed.
++ *
++ * Pay attention to "atomic" attribute of the cmd, which can be get
++ * by scst_cmd_atomic(): it is true if the function called in the
++ * atomic (non-sleeping) context.
++ *
++ * OPTIONAL
++ */
++ int (*alloc_data_buf) (struct scst_cmd *cmd);
++
++ /*
++ * Called to execute CDB. Useful, for instance, to implement
++ * data caching. The result of CDB execution is reported via
++ * cmd->scst_cmd_done() callback.
++ * Returns:
++ * - SCST_EXEC_COMPLETED - the cmd is done, go to other ones
++ * - SCST_EXEC_NOT_COMPLETED - the cmd should be sent to SCSI
++ * mid-level.
++ *
++ * If this function provides sync execution, you should set
++ * exec_sync flag and consider to setup dedicated threads by
++ * setting threads_num > 0.
++ *
++ * !! If this function is implemented, scst_check_local_events() !!
++ * !! shall be called inside it just before the actual command's !!
++ * !! execution. !!
++ *
++ * OPTIONAL, if not set, the commands will be sent directly to SCSI
++ * device.
++ */
++ int (*exec) (struct scst_cmd *cmd);
++
++ /*
++ * Called to notify dev handler about the result of cmd execution
++ * and perform some post processing. Cmd's fields is_send_status and
++ * resp_data_len should be set by this function, but SCST offers good
++ * defaults.
++ * Returns the command's next state or SCST_CMD_STATE_DEFAULT,
++ * if the next default state should be used, or
++ * SCST_CMD_STATE_NEED_THREAD_CTX if the function called in atomic
++ * context, but requires sleeping. In the last case, the function
++ * will be recalled in the thread context, where sleeping is allowed.
++ *
++ * Pay attention to "atomic" attribute of the cmd, which can be get
++ * by scst_cmd_atomic(): it is true if the function called in the
++ * atomic (non-sleeping) context.
++ *
++ * OPTIONAL
++ */
++ int (*dev_done) (struct scst_cmd *cmd);
++
++ /*
++ * Called to notify dev hander that the command is about to be freed.
++ *
++ * Could be called on IRQ context.
++ *
++ * OPTIONAL
++ */
++ void (*on_free_cmd) (struct scst_cmd *cmd);
++
++ /*
++ * Called to execute a task management command.
++ * Returns:
++ * - SCST_MGMT_STATUS_SUCCESS - the command is done with success,
++ * no further actions required
++ * - The SCST_MGMT_STATUS_* error code if the command is failed and
++ * no further actions required
++ * - SCST_DEV_TM_NOT_COMPLETED - regular standard actions for the
++ * command should be done
++ *
++ * Can be called under many internal SCST locks, including under
++ * disabled IRQs, so dev handler should be careful with locking and,
++ * if necessary, pass processing somewhere outside (in a work, e.g.)
++ *
++ * But at the moment it's called under disabled IRQs only for
++ * SCST_ABORT_TASK, however dev handler using it should add a BUG_ON
++ * trap to catch if it's changed in future.
++ *
++ * OPTIONAL
++ */
++ int (*task_mgmt_fn) (struct scst_mgmt_cmd *mgmt_cmd,
++ struct scst_tgt_dev *tgt_dev);
++
++ /*
++ * Called to notify dev handler that its sg_tablesize is too low to
++ * satisfy this command's data transfer requirements. Should return
++ * true if exec() callback will split this command's CDB on smaller
++ * transfers, false otherwise.
++ *
++ * Could be called on SIRQ context.
++ *
++ * MUST HAVE, if dev handler supports CDB splitting.
++ */
++ bool (*on_sg_tablesize_low) (struct scst_cmd *cmd);
++
++ /*
++ * Called when new device is attaching to the dev handler
++ * Returns 0 on success, error code otherwise.
++ *
++ * OPTIONAL
++ */
++ int (*attach) (struct scst_device *dev);
++
++ /*
++ * Called when a device is detaching from the dev handler.
++ *
++ * OPTIONAL
++ */
++ void (*detach) (struct scst_device *dev);
++
++ /*
++ * Called when new tgt_dev (session) is attaching to the dev handler.
++ * Returns 0 on success, error code otherwise.
++ *
++ * OPTIONAL
++ */
++ int (*attach_tgt) (struct scst_tgt_dev *tgt_dev);
++
++ /*
++ * Called when tgt_dev (session) is detaching from the dev handler.
++ *
++ * OPTIONAL
++ */
++ void (*detach_tgt) (struct scst_tgt_dev *tgt_dev);
++
++ /*
++ * This function adds a virtual device.
++ *
++ * If both add_device and del_device callbacks defined, then this
++ * dev handler supposed to support adding/deleting virtual devices.
++ * In this case an "mgmt" entry will be created in the sysfs root for
++ * this handler. The "mgmt" entry will support 2 commands: "add_device"
++ * and "del_device", for which the corresponding callbacks will be called.
++ * Also dev handler can define own commands for the "mgmt" entry, see
++ * mgmt_cmd and mgmt_cmd_help below.
++ *
++ * This approach allows uniform devices management to simplify external
++ * management tools like scstadmin. See README for more details.
++ *
++ * Either both add_device and del_device must be defined, or none.
++ *
++ * MUST HAVE if virtual devices are supported.
++ */
++ ssize_t (*add_device) (const char *device_name, char *params);
++
++ /*
++ * This function deletes a virtual device. See comment for add_device
++ * above.
++ *
++ * MUST HAVE if virtual devices are supported.
++ */
++ ssize_t (*del_device) (const char *device_name);
++
++ /*
++ * This function called if not "add_device" or "del_device" command is
++ * sent to the mgmt entry (see comment for add_device above). In this
++ * case the command passed to this function as is in a string form.
++ *
++ * OPTIONAL.
++ */
++ ssize_t (*mgmt_cmd) (char *cmd);
++
++ /*
++ * Name of the dev handler. Must be unique. MUST HAVE.
++ *
++ * It's SCST_MAX_NAME + few more bytes to match scst_user expectations.
++ */
++ char name[SCST_MAX_NAME + 10];
++
++ /*
++ * Number of threads in this handler's devices' threads pools.
++ * If 0 - no threads will be created, if <0 - creation of the threads
++ * pools is prohibited. Also pay attention to threads_pool_type below.
++ */
++ int threads_num;
++
++ /* Threads pool type. Valid only if threads_num > 0. */
++ enum scst_dev_type_threads_pool_type threads_pool_type;
++
++ /* Optional default log flags */
++ const unsigned long default_trace_flags;
++
++ /* Optional pointer to trace flags */
++ unsigned long *trace_flags;
++
++ /* Optional local trace table */
++ struct scst_trace_log *trace_tbl;
++
++ /* Optional local trace table help string */
++ const char *trace_tbl_help;
++
++ /* Optional help string for mgmt_cmd commands */
++ const char *mgmt_cmd_help;
++
++ /* List of parameters for add_device command, if any */
++ const char *add_device_parameters;
++
++ /*
++ * List of optional, i.e. which could be added by add_attribute command
++ * and deleted by del_attribute command, sysfs attributes, if any.
++ * Helpful for scstadmin to work correctly.
++ */
++ const char *devt_optional_attributes;
++
++ /*
++ * List of optional, i.e. which could be added by add_device_attribute
++ * command and deleted by del_device_attribute command, sysfs
++ * attributes, if any. Helpful for scstadmin to work correctly.
++ */
++ const char *dev_optional_attributes;
++
++ /* sysfs attributes, if any */
++ const struct attribute **devt_attrs;
++
++ /* sysfs device attributes, if any */
++ const struct attribute **dev_attrs;
++
++ /* Pointer to dev handler's private data */
++ void *devt_priv;
++
++ /* Pointer to parent dev type in the sysfs hierarchy */
++ struct scst_dev_type *parent;
++
++ struct module *module;
++
++ /** Private, must be inited to 0 by memset() **/
++
++ /* list entry in scst_(virtual_)dev_type_list */
++ struct list_head dev_type_list_entry;
++
++ struct kobject devt_kobj; /* main handlers/driver */
++
++ /* Number of currently active sysfs mgmt works (scst_sysfs_work_item) */
++ int devt_active_sysfs_works_count;
++
++ /* To wait until devt_kobj released */
++ struct completion *devt_kobj_release_compl;
++};
++
++/*
++ * An SCST target, analog of SCSI target port.
++ */
++struct scst_tgt {
++ /* List of remote sessions per target, protected by scst_mutex */
++ struct list_head sess_list;
++
++ /* List entry of targets per template (tgts_list) */
++ struct list_head tgt_list_entry;
++
++ struct scst_tgt_template *tgtt; /* corresponding target template */
++
++ struct scst_acg *default_acg; /* default acg for this target */
++
++ struct list_head tgt_acg_list; /* target ACG groups */
++
++ /*
++ * Maximum SG table size. Needed here, since different cards on the
++ * same target template can have different SG table limitations.
++ */
++ int sg_tablesize;
++
++ /* Used for storage of target driver private stuff */
++ void *tgt_priv;
++
++ /*
++ * The following fields used to store and retry cmds if target's
++ * internal queue is full, so the target is unable to accept
++ * the cmd returning QUEUE FULL.
++ * They protected by tgt_lock, where necessary.
++ */
++ bool retry_timer_active;
++ struct timer_list retry_timer;
++ atomic_t finished_cmds;
++ int retry_cmds;
++ spinlock_t tgt_lock;
++ struct list_head retry_cmd_list;
++
++ /* Used to wait until session finished to unregister */
++ wait_queue_head_t unreg_waitQ;
++
++ /* Name of the target */
++ char *tgt_name;
++
++ /* User comment to it to let easier distinguish targets */
++ char *tgt_comment;
++
++ uint16_t rel_tgt_id;
++
++ /* sysfs release completion */
++ struct completion *tgt_kobj_release_cmpl;
++
++ struct kobject tgt_kobj; /* main targets/target kobject */
++ struct kobject *tgt_sess_kobj; /* target/sessions/ */
++ struct kobject *tgt_luns_kobj; /* target/luns/ */
++ struct kobject *tgt_ini_grp_kobj; /* target/ini_groups/ */
++};
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++
++/* Defines extended latency statistics */
++struct scst_ext_latency_stat {
++ uint64_t scst_time_rd, tgt_time_rd, dev_time_rd;
++ unsigned int processed_cmds_rd;
++ uint64_t min_scst_time_rd, min_tgt_time_rd, min_dev_time_rd;
++ uint64_t max_scst_time_rd, max_tgt_time_rd, max_dev_time_rd;
++
++ uint64_t scst_time_wr, tgt_time_wr, dev_time_wr;
++ unsigned int processed_cmds_wr;
++ uint64_t min_scst_time_wr, min_tgt_time_wr, min_dev_time_wr;
++ uint64_t max_scst_time_wr, max_tgt_time_wr, max_dev_time_wr;
++};
++
++#define SCST_IO_SIZE_THRESHOLD_SMALL (8*1024)
++#define SCST_IO_SIZE_THRESHOLD_MEDIUM (32*1024)
++#define SCST_IO_SIZE_THRESHOLD_LARGE (128*1024)
++#define SCST_IO_SIZE_THRESHOLD_VERY_LARGE (512*1024)
++
++#define SCST_LATENCY_STAT_INDEX_SMALL 0
++#define SCST_LATENCY_STAT_INDEX_MEDIUM 1
++#define SCST_LATENCY_STAT_INDEX_LARGE 2
++#define SCST_LATENCY_STAT_INDEX_VERY_LARGE 3
++#define SCST_LATENCY_STAT_INDEX_OTHER 4
++#define SCST_LATENCY_STATS_NUM (SCST_LATENCY_STAT_INDEX_OTHER + 1)
++
++#endif /* CONFIG_SCST_MEASURE_LATENCY */
++
++struct scst_io_stat_entry {
++ uint64_t cmd_count;
++ uint64_t io_byte_count;
++};
++
++/*
++ * SCST session, analog of SCSI I_T nexus
++ */
++struct scst_session {
++ /*
++ * Initialization phase, one of SCST_SESS_IPH_* constants, protected by
++ * sess_list_lock
++ */
++ int init_phase;
++
++ struct scst_tgt *tgt; /* corresponding target */
++
++ /* Used for storage of target driver private stuff */
++ void *tgt_priv;
++
++ /* session's async flags */
++ unsigned long sess_aflags;
++
++ /*
++ * Hash list for tgt_dev's for this session with size and fn. It isn't
++ * hlist_entry, because we need ability to go over the list in the
++ * reverse order. Protected by scst_mutex and suspended activity.
++ */
++#define SESS_TGT_DEV_LIST_HASH_SIZE (1 << 5)
++#define SESS_TGT_DEV_LIST_HASH_FN(val) ((val) & (SESS_TGT_DEV_LIST_HASH_SIZE - 1))
++ struct list_head sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_SIZE];
++
++ /*
++ * List of cmds in this session. Protected by sess_list_lock.
++ *
++ * We must always keep commands in the sess list from the
++ * very beginning, because otherwise they can be missed during
++ * TM processing.
++ */
++ struct list_head sess_cmd_list;
++
++ spinlock_t sess_list_lock; /* protects sess_cmd_list, etc */
++
++ atomic_t refcnt; /* get/put counter */
++
++ /*
++ * Alive commands for this session. ToDo: make it part of the common
++ * IO flow control.
++ */
++ atomic_t sess_cmd_count;
++
++ /* Some statistics. Protected by sess_list_lock. */
++ struct scst_io_stat_entry io_stats[SCST_DATA_DIR_MAX];
++
++ /* Access control for this session and list entry there */
++ struct scst_acg *acg;
++
++ /* Initiator port transport id */
++ uint8_t *transport_id;
++
++ /* List entry for the sessions list inside ACG */
++ struct list_head acg_sess_list_entry;
++
++ struct delayed_work hw_pending_work;
++
++ /* Name of attached initiator */
++ const char *initiator_name;
++
++ /* List entry of sessions per target */
++ struct list_head sess_list_entry;
++
++ /* List entry for the list that keeps session, waiting for the init */
++ struct list_head sess_init_list_entry;
++
++ /*
++ * List entry for the list that keeps session, waiting for the shutdown
++ */
++ struct list_head sess_shut_list_entry;
++
++ /*
++ * Lists of deferred during session initialization commands.
++ * Protected by sess_list_lock.
++ */
++ struct list_head init_deferred_cmd_list;
++ struct list_head init_deferred_mcmd_list;
++
++ /*
++ * Shutdown phase, one of SCST_SESS_SPH_* constants, unprotected.
++ * Async. relating to init_phase, must be a separate variable, because
++ * session could be unregistered before async. registration is finished.
++ */
++ unsigned long shut_phase;
++
++ /* Used if scst_unregister_session() called in wait mode */
++ struct completion *shutdown_compl;
++
++ /* sysfs release completion */
++ struct completion *sess_kobj_release_cmpl;
++
++ unsigned int sess_kobj_ready:1;
++
++ struct kobject sess_kobj; /* kobject for this struct */
++
++ /*
++ * Functions and data for user callbacks from scst_register_session()
++ * and scst_unregister_session()
++ */
++ void *reg_sess_data;
++ void (*init_result_fn) (struct scst_session *sess, void *data,
++ int result);
++ void (*unreg_done_fn) (struct scst_session *sess);
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++ /*
++ * Must be the last to allow to work with drivers who don't know
++ * about this config time option.
++ */
++ spinlock_t lat_lock;
++ uint64_t scst_time, tgt_time, dev_time;
++ unsigned int processed_cmds;
++ uint64_t min_scst_time, min_tgt_time, min_dev_time;
++ uint64_t max_scst_time, max_tgt_time, max_dev_time;
++ struct scst_ext_latency_stat sess_latency_stat[SCST_LATENCY_STATS_NUM];
++#endif
++};
++
++/*
++ * SCST_PR_ABORT_ALL TM function helper structure
++ */
++struct scst_pr_abort_all_pending_mgmt_cmds_counter {
++ /*
++ * How many there are pending for this cmd SCST_PR_ABORT_ALL TM
++ * commands.
++ */
++ atomic_t pr_abort_pending_cnt;
++
++ /* Saved completion routine */
++ void (*saved_cmd_done) (struct scst_cmd *cmd, int next_state,
++ enum scst_exec_context pref_context);
++
++ /*
++ * How many there are pending for this cmd SCST_PR_ABORT_ALL TM
++ * commands, which not yet aborted all affected commands and
++ * a completion to signal, when it's done.
++ */
++ atomic_t pr_aborting_cnt;
++ struct completion pr_aborting_cmpl;
++};
++
++/*
++ * Structure to control commands' queuing and threads pool processing the queue
++ */
++struct scst_cmd_threads {
++ spinlock_t cmd_list_lock;
++ struct list_head active_cmd_list; /* commands queue */
++ wait_queue_head_t cmd_list_waitQ;
++
++ struct io_context *io_context; /* IO context of the threads pool */
++ int io_context_refcnt;
++
++ bool io_context_ready;
++
++ /* io_context_mutex protects io_context and io_context_refcnt. */
++ struct mutex io_context_mutex;
++
++ int nr_threads; /* number of processing threads */
++ struct list_head threads_list; /* processing threads */
++
++ struct list_head lists_list_entry;
++};
++
++/*
++ * Used to execute cmd's in order of arrival, honoring SCSI task attributes
++ */
++struct scst_order_data {
++ /*
++ * Protected by sn_lock, except expected_sn, which is protected by
++ * itself. Curr_sn must have the same size as expected_sn to
++ * overflow simultaneously.
++ */
++ int def_cmd_count;
++ spinlock_t sn_lock;
++ unsigned int expected_sn;
++ unsigned int curr_sn;
++ int hq_cmd_count;
++ struct list_head deferred_cmd_list;
++ struct list_head skipped_sn_list;
++
++ /*
++ * Set if the prev cmd was ORDERED. Size and, hence, alignment must
++ * allow unprotected modifications independently to the neighbour fields.
++ */
++ unsigned long prev_cmd_ordered;
++
++ int num_free_sn_slots; /* if it's <0, then all slots are busy */
++ atomic_t *cur_sn_slot;
++ atomic_t sn_slots[15];
++};
++
++/*
++ * SCST command, analog of I_T_L_Q nexus or task
++ */
++struct scst_cmd {
++ /* List entry for below *_cmd_threads */
++ struct list_head cmd_list_entry;
++
++ /* Pointer to lists of commands with the lock */
++ struct scst_cmd_threads *cmd_threads;
++
++ atomic_t cmd_ref;
++
++ struct scst_session *sess; /* corresponding session */
++
++ atomic_t *cpu_cmd_counter;
++
++ /* Cmd state, one of SCST_CMD_STATE_* constants */
++ int state;
++
++ /*************************************************************
++ ** Cmd's flags
++ *************************************************************/
++
++ /*
++ * Set if expected_sn should be incremented, i.e. cmd was sent
++ * for execution
++ */
++ unsigned int sent_for_exec:1;
++
++ /* Set if the cmd's action is completed */
++ unsigned int completed:1;
++
++ /* Set if we should ignore Unit Attention in scst_check_sense() */
++ unsigned int ua_ignore:1;
++
++ /* Set if cmd is being processed in atomic context */
++ unsigned int atomic:1;
++
++ /* Set if this command was sent in double UA possible state */
++ unsigned int double_ua_possible:1;
++
++ /* Set if this command contains status */
++ unsigned int is_send_status:1;
++
++ /* Set if cmd is being retried */
++ unsigned int retry:1;
++
++ /* Set if cmd is internally generated */
++ unsigned int internal:1;
++
++ /* Set if the device was blocked by scst_check_blocked_dev() */
++ unsigned int unblock_dev:1;
++
++ /* Set if this cmd incremented dev->pr_readers_count */
++ unsigned int dec_pr_readers_count_needed:1;
++
++ /* Set if scst_dec_on_dev_cmd() call is needed on the cmd's finish */
++ unsigned int dec_on_dev_needed:1;
++
++ /* Set if cmd is queued as hw pending */
++ unsigned int cmd_hw_pending:1;
++
++ /*
++ * Set, if for this cmd required to not have any IO or FS calls on
++ * memory buffers allocations, at least for READ and WRITE commands.
++ * Needed for cases like file systems mounted over scst_local's
++ * devices.
++ */
++ unsigned noio_mem_alloc:1;
++
++ /*
++ * Set if the target driver wants to alloc data buffers on its own.
++ * In this case alloc_data_buf() must be provided in the target driver
++ * template.
++ */
++ unsigned int tgt_need_alloc_data_buf:1;
++
++ /*
++ * Set by SCST if the custom data buffer allocation by the target driver
++ * succeeded.
++ */
++ unsigned int tgt_data_buf_alloced:1;
++
++ /* Set if custom data buffer allocated by dev handler */
++ unsigned int dh_data_buf_alloced:1;
++
++ /* Set if the target driver called scst_set_expected() */
++ unsigned int expected_values_set:1;
++
++ /*
++ * Set if the SG buffer was modified by scst_adjust_sg()
++ */
++ unsigned int sg_buff_modified:1;
++
++ /*
++ * Set if cmd buffer was vmallocated and copied from more
++ * then one sg chunk
++ */
++ unsigned int sg_buff_vmallocated:1;
++
++ /*
++ * Set if scst_cmd_init_stage1_done() called and the target
++ * want that preprocessing_done() will be called
++ */
++ unsigned int preprocessing_only:1;
++
++ /* Set if cmd's SN was set */
++ unsigned int sn_set:1;
++
++ /* Set if hq_cmd_count was incremented */
++ unsigned int hq_cmd_inced:1;
++
++ /*
++ * Set if scst_cmd_init_stage1_done() called and the target wants
++ * that the SN for the cmd won't be assigned until scst_restart_cmd()
++ */
++ unsigned int set_sn_on_restart_cmd:1;
++
++ /* Set if the cmd's must not use sgv cache for data buffer */
++ unsigned int no_sgv:1;
++
++ /*
++ * Set if target driver may need to call dma_sync_sg() or similar
++ * function before transferring cmd' data to the target device
++ * via DMA.
++ */
++ unsigned int may_need_dma_sync:1;
++
++ /* Set if the cmd was done or aborted out of its SN */
++ unsigned int out_of_sn:1;
++
++ /* Set if increment expected_sn in cmd->scst_cmd_done() */
++ unsigned int inc_expected_sn_on_done:1;
++
++ /* Set if tgt_sn field is valid */
++ unsigned int tgt_sn_set:1;
++
++ /* Set if any direction residual is possible */
++ unsigned int resid_possible:1;
++
++ /* Set if cmd is done */
++ unsigned int done:1;
++
++ /*
++ * Set if cmd is finished. Used under sess_list_lock to sync
++ * between scst_finish_cmd() and scst_abort_cmd()
++ */
++ unsigned int finished:1;
++
++ /*
++ * Set if scst_check_local_events() can be called more than once. Set by
++ * scst_pre_check_local_events().
++ */
++ unsigned int check_local_events_once_done:1;
++
++#ifdef CONFIG_SCST_DEBUG_TM
++ /* Set if the cmd was delayed by task management debugging code */
++ unsigned int tm_dbg_delayed:1;
++
++ /* Set if the cmd must be ignored by task management debugging code */
++ unsigned int tm_dbg_immut:1;
++#endif
++
++ /**************************************************************/
++
++ /* cmd's async flags */
++ unsigned long cmd_flags;
++
++ /* Keeps status of cmd's status/data delivery to remote initiator */
++ int delivery_status;
++
++ struct scst_tgt_template *tgtt; /* to save extra dereferences */
++ struct scst_tgt *tgt; /* to save extra dereferences */
++ struct scst_device *dev; /* to save extra dereferences */
++
++ /* corresponding I_T_L device for this cmd */
++ struct scst_tgt_dev *tgt_dev;
++
++ struct scst_order_data *cur_order_data; /* to save extra dereferences */
++
++ uint64_t lun; /* LUN for this cmd */
++
++ unsigned long start_time;
++
++ /* List entry for tgt_dev's SN related lists */
++ struct list_head sn_cmd_list_entry;
++
++ /* Cmd's serial number, used to execute cmd's in order of arrival */
++ unsigned int sn;
++
++ /* The corresponding sn_slot in tgt_dev->sn_slots */
++ atomic_t *sn_slot;
++
++ /* List entry for sess's sess_cmd_list */
++ struct list_head sess_cmd_list_entry;
++
++ /*
++ * Used to found the cmd by scst_find_cmd_by_tag(). Set by the
++ * target driver on the cmd's initialization time
++ */
++ uint64_t tag;
++
++ uint32_t tgt_sn; /* SN set by target driver (for TM purposes) */
++
++ uint8_t *cdb; /* Pointer on CDB. Points on cdb_buf for small CDBs. */
++ unsigned short cdb_len;
++ uint8_t cdb_buf[SCST_MAX_CDB_SIZE];
++
++ enum scst_cdb_flags op_flags;
++ const char *op_name;
++
++ enum scst_cmd_queue_type queue_type;
++
++ int timeout; /* CDB execution timeout in seconds */
++ int retries; /* Amount of retries that will be done by SCSI mid-level */
++
++ /* SCSI data direction, one of SCST_DATA_* constants */
++ scst_data_direction data_direction;
++
++ /* Remote initiator supplied values, if any */
++ scst_data_direction expected_data_direction;
++ int expected_transfer_len;
++ int expected_out_transfer_len; /* for bidi writes */
++
++ /*
++ * Cmd data length. Could be different from bufflen for commands like
++ * VERIFY, which transfer different amount of data (if any), than
++ * processed.
++ */
++ int data_len;
++
++ /* Completion routine */
++ void (*scst_cmd_done) (struct scst_cmd *cmd, int next_state,
++ enum scst_exec_context pref_context);
++
++ struct sgv_pool_obj *sgv; /* sgv object */
++ int bufflen; /* cmd buffer length */
++ struct scatterlist *sg; /* cmd data buffer SG vector */
++ int sg_cnt; /* SG segments count */
++
++ /*
++ * Response data length in data buffer. Must not be set
++ * directly, use scst_set_resp_data_len() for that.
++ */
++ int resp_data_len;
++
++ /*
++ * Response data length adjusted on residual, i.e.
++ * min(expected_len, resp_len), if expected len set.
++ */
++ int adjusted_resp_data_len;
++
++ /*
++ * Data length to write, i.e. transfer from the initiator. Might be
++ * different from (out_)bufflen, if the initiator asked too big or too
++ * small expected(_out_)transfer_len.
++ */
++ int write_len;
++
++ /*
++ * Write sg and sg_cnt to point out either on sg/sg_cnt, or on
++ * out_sg/out_sg_cnt.
++ */
++ struct scatterlist **write_sg;
++ int *write_sg_cnt;
++
++ /* scst_get_sg_buf_[first,next]() support */
++ struct scatterlist *get_sg_buf_cur_sg_entry;
++ int get_sg_buf_entry_num;
++
++ /* Bidirectional transfers support */
++ int out_bufflen; /* WRITE buffer length */
++ struct sgv_pool_obj *out_sgv; /* WRITE sgv object */
++ struct scatterlist *out_sg; /* WRITE data buffer SG vector */
++ int out_sg_cnt; /* WRITE SG segments count */
++
++ /*
++ * Used if both target driver and dev handler request own memory
++ * allocation. In other cases, both are equal to sg and sg_cnt
++ * correspondingly.
++ *
++ * If target driver requests own memory allocations, it MUST use
++ * functions scst_cmd_get_tgt_sg*() to get sg and sg_cnt! Otherwise,
++ * it may use functions scst_cmd_get_sg*().
++ */
++ struct scatterlist *tgt_sg;
++ int tgt_sg_cnt;
++ struct scatterlist *tgt_out_sg; /* bidirectional */
++ int tgt_out_sg_cnt; /* bidirectional */
++
++ /*
++ * The status fields in case of errors must be set using
++ * scst_set_cmd_error_status()!
++ */
++ uint8_t status; /* status byte from target device */
++ uint8_t msg_status; /* return status from host adapter itself */
++ uint8_t host_status; /* set by low-level driver to indicate status */
++ uint8_t driver_status; /* set by mid-level */
++
++ uint8_t *sense; /* pointer to sense buffer */
++ unsigned short sense_valid_len; /* length of valid sense data */
++ unsigned short sense_buflen; /* length of the sense buffer, if any */
++
++ /* Start time when cmd was sent to rdy_to_xfer() or xmit_response() */
++ unsigned long hw_pending_start;
++
++ /* Used for storage of target driver private stuff */
++ void *tgt_priv;
++
++ /* Used for storage of dev handler private stuff */
++ void *dh_priv;
++
++ /* Used to restore sg if it was modified by scst_adjust_sg() */
++ struct scatterlist *orig_sg;
++ int *p_orig_sg_cnt;
++ int orig_sg_cnt, orig_sg_entry, orig_entry_len;
++
++ /* Used to retry commands in case of double UA */
++ int dbl_ua_orig_resp_data_len, dbl_ua_orig_data_direction;
++
++ /*
++ * List of the corresponding mgmt cmds, if any. Protected by
++ * sess_list_lock.
++ */
++ struct list_head mgmt_cmd_list;
++
++ /* List entry for dev's blocked_cmd_list */
++ struct list_head blocked_cmd_list_entry;
++
++ /* Counter of the corresponding SCST_PR_ABORT_ALL TM commands */
++ struct scst_pr_abort_all_pending_mgmt_cmds_counter *pr_abort_counter;
++
++ struct scst_cmd *orig_cmd; /* Used to issue REQUEST SENSE */
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++ /*
++ * Must be the last to allow to work with drivers who don't know
++ * about this config time option.
++ */
++ uint64_t start, curr_start, parse_time, alloc_buf_time;
++ uint64_t restart_waiting_time, rdy_to_xfer_time;
++ uint64_t pre_exec_time, exec_time, dev_done_time;
++ uint64_t xmit_time, tgt_on_free_time, dev_on_free_time;
++#endif
++};
++
++/*
++ * Parameters for SCST management commands
++ */
++struct scst_rx_mgmt_params {
++ int fn;
++ uint64_t tag;
++ const uint8_t *lun;
++ int lun_len;
++ uint32_t cmd_sn;
++ int atomic;
++ void *tgt_priv;
++ unsigned char tag_set;
++ unsigned char lun_set;
++ unsigned char cmd_sn_set;
++};
++
++/*
++ * A stub structure to link an management command and affected regular commands
++ */
++struct scst_mgmt_cmd_stub {
++ struct scst_mgmt_cmd *mcmd;
++
++ /* List entry in cmd->mgmt_cmd_list */
++ struct list_head cmd_mgmt_cmd_list_entry;
++
++ /* Set if the cmd was counted in mcmd->cmd_done_wait_count */
++ unsigned int done_counted:1;
++
++ /* Set if the cmd was counted in mcmd->cmd_finish_wait_count */
++ unsigned int finish_counted:1;
++};
++
++/*
++ * SCST task management structure
++ */
++struct scst_mgmt_cmd {
++ /* List entry for *_mgmt_cmd_list */
++ struct list_head mgmt_cmd_list_entry;
++
++ struct scst_session *sess;
++
++ atomic_t *cpu_cmd_counter;
++
++ /* Mgmt cmd state, one of SCST_MCMD_STATE_* constants */
++ int state;
++
++ int fn; /* task management function */
++
++ /* Set if device(s) should be unblocked after mcmd's finish */
++ unsigned int needs_unblocking:1;
++ unsigned int lun_set:1; /* set, if lun field is valid */
++ unsigned int cmd_sn_set:1; /* set, if cmd_sn field is valid */
++
++ /*
++ * Number of commands to finish before sending response,
++ * protected by scst_mcmd_lock
++ */
++ int cmd_finish_wait_count;
++
++ /*
++ * Number of commands to complete (done) before resetting reservation,
++ * protected by scst_mcmd_lock
++ */
++ int cmd_done_wait_count;
++
++ /* Number of completed commands, protected by scst_mcmd_lock */
++ int completed_cmd_count;
++
++ uint64_t lun; /* LUN for this mgmt cmd */
++ /* or (and for iSCSI) */
++ uint64_t tag; /* tag of the corresponding cmd */
++
++ uint32_t cmd_sn; /* affected command's highest SN */
++
++ /* corresponding cmd (to be aborted, found by tag) */
++ struct scst_cmd *cmd_to_abort;
++
++ /* corresponding device for this mgmt cmd (found by lun) */
++ struct scst_tgt_dev *mcmd_tgt_dev;
++
++ /* completion status, one of the SCST_MGMT_STATUS_* constants */
++ int status;
++
++ /* Used for storage of target driver private stuff or origin PR cmd */
++ union {
++ void *tgt_priv;
++ struct scst_cmd *origin_pr_cmd;
++ };
++};
++
++/*
++ * Persistent reservations registrant
++ */
++struct scst_dev_registrant {
++ uint8_t *transport_id;
++ uint16_t rel_tgt_id;
++ __be64 key;
++
++ /* tgt_dev (I_T nexus) for this registrant, if any */
++ struct scst_tgt_dev *tgt_dev;
++
++ /* List entry for dev_registrants_list */
++ struct list_head dev_registrants_list_entry;
++
++ /* 2 auxiliary fields used to rollback changes for errors, etc. */
++ struct list_head aux_list_entry;
++ __be64 rollback_key;
++};
++
++/*
++ * SCST device
++ */
++struct scst_device {
++ unsigned short type; /* SCSI type of the device */
++
++ /*************************************************************
++ ** Dev's flags. Updates serialized by dev_lock or suspended
++ ** activity
++ *************************************************************/
++
++ /* Set if dev is RESERVED */
++ unsigned short dev_reserved:1;
++
++ /* Set if double reset UA is possible */
++ unsigned short dev_double_ua_possible:1;
++
++ /* If set, dev is read only */
++ unsigned short rd_only:1;
++
++ /* Set, if a strictly serialized cmd is waiting blocked */
++ unsigned short strictly_serialized_cmd_waiting:1;
++
++ /*
++ * Set, if this device is being unregistered. Useful to let sysfs
++ * attributes know when they should exit immediatelly to prevent
++ * possible deadlocks with their device unregistration waiting for
++ * their kobj last put.
++ */
++ unsigned short dev_unregistering:1;
++
++ /**************************************************************/
++
++ /*************************************************************
++ ** Dev's control mode page related values. Updates serialized
++ ** by scst_block_dev(). Modified independently to the above and
++ ** below fields, hence the alignment.
++ *************************************************************/
++
++ unsigned int queue_alg:4 __attribute__((aligned(sizeof(long))));
++ unsigned int tst:3;
++ unsigned int tas:1;
++ unsigned int swp:1;
++ unsigned int d_sense:1;
++
++ /*
++ * Set if device implements own ordered commands management. If not set
++ * and queue_alg is SCST_CONTR_MODE_QUEUE_ALG_RESTRICTED_REORDER,
++ * expected_sn will be incremented only after commands finished.
++ */
++ unsigned int has_own_order_mgmt:1;
++
++ /**************************************************************/
++
++ /* How many cmds alive on this dev */
++ atomic_t dev_cmd_count;
++
++ spinlock_t dev_lock; /* device lock */
++
++ /*
++ * How many times device was blocked for new cmds execution.
++ * Protected by dev_lock.
++ */
++ int block_count;
++
++ /*
++ * How many there are "on_dev" commands, i.e. ones who passed
++ * scst_check_blocked_dev(). Protected by dev_lock.
++ */
++ int on_dev_cmd_count;
++
++ /*
++ * How many threads are checking commands for PR allowance.
++ * Protected by dev_lock.
++ */
++ int pr_readers_count;
++
++ /*
++ * Set if dev is persistently reserved. Protected by dev_pr_mutex.
++ * Modified independently to the above field, hence the alignment.
++ */
++ unsigned int pr_is_set:1 __attribute__((aligned(sizeof(long))));
++
++ /*
++ * Set if there is a thread changing or going to change PR state(s).
++ * Protected by dev_pr_mutex.
++ */
++ unsigned int pr_writer_active:1;
++
++ struct scst_dev_type *handler; /* corresponding dev handler */
++
++ /* Used for storage of dev handler private stuff */
++ void *dh_priv;
++
++ /* Corresponding real SCSI device, could be NULL for virtual devices */
++ struct scsi_device *scsi_dev;
++
++ /* List of commands with lock, if dedicated threads are used */
++ struct scst_cmd_threads dev_cmd_threads;
++
++ /* Memory limits for this device */
++ struct scst_mem_lim dev_mem_lim;
++
++ /*************************************************************
++ ** Persistent reservation fields. Protected by dev_pr_mutex.
++ *************************************************************/
++
++ /*
++ * True if persist through power loss is activated. Modified
++ * independently to the above field, hence the alignment.
++ */
++ unsigned short pr_aptpl:1 __attribute__((aligned(sizeof(long))));
++
++ /* Persistent reservation type */
++ uint8_t pr_type;
++
++ /* Persistent reservation scope */
++ uint8_t pr_scope;
++
++ /* Mutex to protect PR operations */
++ struct mutex dev_pr_mutex;
++
++ /* Persistent reservation generation value */
++ uint32_t pr_generation;
++
++ /* Reference to registrant - persistent reservation holder */
++ struct scst_dev_registrant *pr_holder;
++
++ /* List of dev's registrants */
++ struct list_head dev_registrants_list;
++
++ /*
++ * Count of connected tgt_devs from transports, which don't support
++ * PRs, i.e. don't have get_initiator_port_transport_id(). Protected
++ * by scst_mutex.
++ */
++ int not_pr_supporting_tgt_devs_num;
++
++ struct scst_order_data dev_order_data;
++
++ /* Persist through power loss files */
++ char *pr_file_name;
++ char *pr_file_name1;
++
++ /**************************************************************/
++
++ /* List of blocked commands, protected by dev_lock. */
++ struct list_head blocked_cmd_list;
++
++ /* A list entry used during TM, protected by scst_mutex */
++ struct list_head tm_dev_list_entry;
++
++ int virt_id; /* virtual device internal ID */
++
++ /* Pointer to virtual device name, for convenience only */
++ char *virt_name;
++
++ struct list_head dev_list_entry; /* list entry in global devices list */
++
++ /*
++ * List of tgt_dev's, one per session, protected by scst_mutex or
++ * dev_lock for reads and both for writes
++ */
++ struct list_head dev_tgt_dev_list;
++
++ /* List of acg_dev's, one per acg, protected by scst_mutex */
++ struct list_head dev_acg_dev_list;
++
++ /* Number of threads in the device's threads pools */
++ int threads_num;
++
++ /* Threads pool type of the device. Valid only if threads_num > 0. */
++ enum scst_dev_type_threads_pool_type threads_pool_type;
++
++ /* sysfs release completion */
++ struct completion *dev_kobj_release_cmpl;
++
++ struct kobject dev_kobj; /* kobject for this struct */
++ struct kobject *dev_exp_kobj; /* exported groups */
++
++ /* Export number in the dev's sysfs list. Protected by scst_mutex */
++ int dev_exported_lun_num;
++};
++
++/*
++ * Used to store threads local tgt_dev specific data
++ */
++struct scst_thr_data_hdr {
++ /* List entry in tgt_dev->thr_data_list */
++ struct list_head thr_data_list_entry;
++ struct task_struct *owner_thr; /* the owner thread */
++ atomic_t ref;
++ /* Function that will be called on the tgt_dev destruction */
++ void (*free_fn) (struct scst_thr_data_hdr *data);
++};
++
++/*
++ * Used to clearly dispose async io_context
++ */
++struct scst_async_io_context_keeper {
++ struct kref aic_keeper_kref;
++ bool aic_ready;
++ struct io_context *aic;
++ struct task_struct *aic_keeper_thr;
++ wait_queue_head_t aic_keeper_waitQ;
++};
++
++/*
++ * Used to store per-session specific device information, analog of
++ * SCSI I_T_L nexus.
++ */
++struct scst_tgt_dev {
++ /* List entry in sess->sess_tgt_dev_list */
++ struct list_head sess_tgt_dev_list_entry;
++
++ struct scst_device *dev; /* to save extra dereferences */
++ uint64_t lun; /* to save extra dereferences */
++
++ gfp_t gfp_mask;
++ struct sgv_pool *pool;
++ int max_sg_cnt;
++
++ /*
++ * Tgt_dev's async flags. Modified independently to the neighbour
++ * fields.
++ */
++ unsigned long tgt_dev_flags;
++
++ /* Used for storage of dev handler private stuff */
++ void *dh_priv;
++
++ /* How many cmds alive on this dev in this session */
++ atomic_t tgt_dev_cmd_count;
++
++ struct scst_order_data *curr_order_data;
++ struct scst_order_data tgt_dev_order_data;
++
++ /* List of scst_thr_data_hdr and lock */
++ spinlock_t thr_data_lock;
++ struct list_head thr_data_list;
++
++ /* Pointer to lists of commands with the lock */
++ struct scst_cmd_threads *active_cmd_threads;
++
++ /* Union to save some CPU cache footprint */
++ union {
++ struct {
++ /* Copy to save fast path dereference */
++ struct io_context *async_io_context;
++
++ struct scst_async_io_context_keeper *aic_keeper;
++ };
++
++ /* Lists of commands with lock, if dedicated threads are used */
++ struct scst_cmd_threads tgt_dev_cmd_threads;
++ };
++
++ spinlock_t tgt_dev_lock; /* per-session device lock */
++
++ /* List of UA's for this device, protected by tgt_dev_lock */
++ struct list_head UA_list;
++
++ struct scst_session *sess; /* corresponding session */
++ struct scst_acg_dev *acg_dev; /* corresponding acg_dev */
++
++ /* Reference to registrant to find quicker */
++ struct scst_dev_registrant *registrant;
++
++ /* List entry in dev->dev_tgt_dev_list */
++ struct list_head dev_tgt_dev_list_entry;
++
++ /* Internal tmp list entry */
++ struct list_head extra_tgt_dev_list_entry;
++
++ /* Set if INQUIRY DATA HAS CHANGED UA is needed */
++ unsigned int inq_changed_ua_needed:1;
++
++ /*
++ * Stored Unit Attention sense and its length for possible
++ * subsequent REQUEST SENSE. Both protected by tgt_dev_lock.
++ */
++ unsigned short tgt_dev_valid_sense_len;
++ uint8_t tgt_dev_sense[SCST_SENSE_BUFFERSIZE];
++
++ /* sysfs release completion */
++ struct completion *tgt_dev_kobj_release_cmpl;
++
++ struct kobject tgt_dev_kobj; /* kobject for this struct */
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++ /*
++ * Must be the last to allow to work with drivers who don't know
++ * about this config time option.
++ *
++ * Protected by sess->lat_lock.
++ */
++ uint64_t scst_time, tgt_time, dev_time;
++ unsigned int processed_cmds;
++ struct scst_ext_latency_stat dev_latency_stat[SCST_LATENCY_STATS_NUM];
++#endif
++};
++
++/*
++ * Used to store ACG-specific device information, like LUN
++ */
++struct scst_acg_dev {
++ struct scst_device *dev; /* corresponding device */
++
++ uint64_t lun; /* device's LUN in this acg */
++
++ /* If set, the corresponding LU is read only */
++ unsigned int rd_only:1;
++
++ struct scst_acg *acg; /* parent acg */
++
++ /* List entry in dev->dev_acg_dev_list */
++ struct list_head dev_acg_dev_list_entry;
++
++ /* List entry in acg->acg_dev_list */
++ struct list_head acg_dev_list_entry;
++
++ /* kobject for this structure */
++ struct kobject acg_dev_kobj;
++
++ /* sysfs release completion */
++ struct completion *acg_dev_kobj_release_cmpl;
++
++ /* Name of the link to the corresponding LUN */
++ char acg_dev_link_name[20];
++};
++
++/*
++ * ACG - access control group. Used to store group related
++ * control information.
++ */
++struct scst_acg {
++ /* Owner target */
++ struct scst_tgt *tgt;
++
++ /* List of acg_dev's in this acg, protected by scst_mutex */
++ struct list_head acg_dev_list;
++
++ /* List of attached sessions, protected by scst_mutex */
++ struct list_head acg_sess_list;
++
++ /* List of attached acn's, protected by scst_mutex */
++ struct list_head acn_list;
++
++ /* List entry in acg_lists */
++ struct list_head acg_list_entry;
++
++ /* Name of this acg */
++ const char *acg_name;
++
++ /* Type of I/O initiators grouping */
++ int acg_io_grouping_type;
++
++ /* CPU affinity for threads in this ACG */
++ cpumask_t acg_cpu_mask;
++
++ unsigned int tgt_acg:1;
++
++ /* sysfs release completion */
++ struct completion *acg_kobj_release_cmpl;
++
++ /* kobject for this structure */
++ struct kobject acg_kobj;
++
++ struct kobject *luns_kobj;
++ struct kobject *initiators_kobj;
++
++ enum scst_lun_addr_method addr_method;
++};
++
++/*
++ * ACN - access control name. Used to store names, by which
++ * incoming sessions will be assigned to appropriate ACG.
++ */
++struct scst_acn {
++ struct scst_acg *acg; /* owner ACG */
++
++ const char *name; /* initiator's name */
++
++ /* List entry in acg->acn_list */
++ struct list_head acn_list_entry;
++
++ /* sysfs file attributes */
++ struct kobj_attribute *acn_attr;
++};
++
++/**
++ * struct scst_dev_group - A group of SCST devices (struct scst_device).
++ *
++ * Each device is member of zero or one device groups. With each device group
++ * there are zero or more target groups associated.
++ */
++struct scst_dev_group {
++ char *name;
++ struct list_head entry;
++ struct list_head dev_list;
++ struct list_head tg_list;
++ struct kobject kobj;
++ struct kobject *dev_kobj;
++ struct kobject *tg_kobj;
++};
++
++/**
++ * struct scst_dg_dev - A node in scst_dev_group.dev_list.
++ */
++struct scst_dg_dev {
++ struct list_head entry;
++ struct scst_device *dev;
++};
++
++/**
++ * struct scst_target_group - A group of SCSI targets (struct scst_tgt).
++ *
++ * Such a group is either a primary target port group or a secondary
++ * port group. See also SPC-4 for more information.
++ */
++struct scst_target_group {
++ struct scst_dev_group *dg;
++ char *name;
++ uint16_t group_id;
++ enum scst_tg_state state;
++ bool preferred;
++ struct list_head entry;
++ struct list_head tgt_list;
++ struct kobject kobj;
++};
++
++/**
++ * struct scst_tg_tgt - A node in scst_target_group.tgt_list.
++ *
++ * Such a node can either represent a local storage target (struct scst_tgt)
++ * or a storage target on another system running SCST. In the former case tgt
++ * != NULL and rel_tgt_id is ignored. In the latter case tgt == NULL and
++ * rel_tgt_id is relevant.
++ */
++struct scst_tg_tgt {
++ struct list_head entry;
++ struct scst_target_group *tg;
++ struct kobject kobj;
++ struct scst_tgt *tgt;
++ char *name;
++ uint16_t rel_tgt_id;
++};
++
++/*
++ * Used to store per-session UNIT ATTENTIONs
++ */
++struct scst_tgt_dev_UA {
++ /* List entry in tgt_dev->UA_list */
++ struct list_head UA_list_entry;
++
++ /* Set if UA is global for session */
++ unsigned short global_UA:1;
++
++ /* Unit Attention valid sense len */
++ unsigned short UA_valid_sense_len;
++ /* Unit Attention sense buf */
++ uint8_t UA_sense_buffer[SCST_SENSE_BUFFERSIZE];
++};
++
++/* Used to deliver AENs */
++struct scst_aen {
++ int event_fn; /* AEN fn */
++
++ struct scst_session *sess; /* corresponding session */
++ __be64 lun; /* corresponding LUN in SCSI form */
++
++ union {
++ /* SCSI AEN data */
++ struct {
++ int aen_sense_len;
++ uint8_t aen_sense[SCST_STANDARD_SENSE_LEN];
++ };
++ };
++
++ /* Keeps status of AEN's delivery to remote initiator */
++ int delivery_status;
++};
++
++#ifndef smp_mb__after_set_bit
++/* There is no smp_mb__after_set_bit() in the kernel */
++#define smp_mb__after_set_bit() smp_mb()
++#endif
++
++/*
++ * Registers target template.
++ * Returns 0 on success or appropriate error code otherwise.
++ */
++int __scst_register_target_template(struct scst_tgt_template *vtt,
++ const char *version);
++static inline int scst_register_target_template(struct scst_tgt_template *vtt)
++{
++ return __scst_register_target_template(vtt, SCST_INTERFACE_VERSION);
++}
++
++/*
++ * Registers target template, non-GPL version.
++ * Returns 0 on success or appropriate error code otherwise.
++ *
++ * Note: *vtt must be static!
++ */
++int __scst_register_target_template_non_gpl(struct scst_tgt_template *vtt,
++ const char *version);
++static inline int scst_register_target_template_non_gpl(
++ struct scst_tgt_template *vtt)
++{
++ return __scst_register_target_template_non_gpl(vtt,
++ SCST_INTERFACE_VERSION);
++}
++
++void scst_unregister_target_template(struct scst_tgt_template *vtt);
++
++struct scst_tgt *scst_register_target(struct scst_tgt_template *vtt,
++ const char *target_name);
++void scst_unregister_target(struct scst_tgt *tgt);
++
++struct scst_session *scst_register_session(struct scst_tgt *tgt, int atomic,
++ const char *initiator_name, void *tgt_priv, void *result_fn_data,
++ void (*result_fn) (struct scst_session *sess, void *data, int result));
++struct scst_session *scst_register_session_non_gpl(struct scst_tgt *tgt,
++ const char *initiator_name, void *tgt_priv);
++void scst_unregister_session(struct scst_session *sess, int wait,
++ void (*unreg_done_fn) (struct scst_session *sess));
++void scst_unregister_session_non_gpl(struct scst_session *sess);
++
++int __scst_register_dev_driver(struct scst_dev_type *dev_type,
++ const char *version);
++static inline int scst_register_dev_driver(struct scst_dev_type *dev_type)
++{
++ return __scst_register_dev_driver(dev_type, SCST_INTERFACE_VERSION);
++}
++void scst_unregister_dev_driver(struct scst_dev_type *dev_type);
++
++int __scst_register_virtual_dev_driver(struct scst_dev_type *dev_type,
++ const char *version);
++/*
++ * Registers dev handler driver for virtual devices (eg VDISK).
++ * Returns 0 on success or appropriate error code otherwise.
++ */
++static inline int scst_register_virtual_dev_driver(
++ struct scst_dev_type *dev_type)
++{
++ return __scst_register_virtual_dev_driver(dev_type,
++ SCST_INTERFACE_VERSION);
++}
++
++void scst_unregister_virtual_dev_driver(struct scst_dev_type *dev_type);
++
++bool scst_initiator_has_luns(struct scst_tgt *tgt, const char *initiator_name);
++
++struct scst_cmd *scst_rx_cmd(struct scst_session *sess,
++ const uint8_t *lun, int lun_len, const uint8_t *cdb,
++ unsigned int cdb_len, int atomic);
++void scst_cmd_init_done(struct scst_cmd *cmd,
++ enum scst_exec_context pref_context);
++
++/*
++ * Notifies SCST that the driver finished the first stage of the command
++ * initialization, and the command is ready for execution, but after
++ * SCST done the command's preprocessing preprocessing_done() function
++ * should be called. The second argument sets preferred command execution
++ * context. See SCST_CONTEXT_* constants for details.
++ *
++ * See comment for scst_cmd_init_done() for the serialization requirements.
++ */
++static inline void scst_cmd_init_stage1_done(struct scst_cmd *cmd,
++ enum scst_exec_context pref_context, int set_sn)
++{
++ cmd->preprocessing_only = 1;
++ cmd->set_sn_on_restart_cmd = !set_sn;
++ scst_cmd_init_done(cmd, pref_context);
++}
++
++void scst_restart_cmd(struct scst_cmd *cmd, int status,
++ enum scst_exec_context pref_context);
++
++void scst_rx_data(struct scst_cmd *cmd, int status,
++ enum scst_exec_context pref_context);
++
++void scst_tgt_cmd_done(struct scst_cmd *cmd,
++ enum scst_exec_context pref_context);
++
++int scst_rx_mgmt_fn(struct scst_session *sess,
++ const struct scst_rx_mgmt_params *params);
++
++/*
++ * Creates new management command using tag and sends it for execution.
++ * Can be used for SCST_ABORT_TASK only.
++ * Must not be called in parallel with scst_unregister_session() for the
++ * same sess. Returns 0 for success, error code otherwise.
++ *
++ * Obsolete in favor of scst_rx_mgmt_fn()
++ */
++static inline int scst_rx_mgmt_fn_tag(struct scst_session *sess, int fn,
++ uint64_t tag, int atomic, void *tgt_priv)
++{
++ struct scst_rx_mgmt_params params;
++
++ BUG_ON(fn != SCST_ABORT_TASK);
++
++ memset(&params, 0, sizeof(params));
++ params.fn = fn;
++ params.tag = tag;
++ params.tag_set = 1;
++ params.atomic = atomic;
++ params.tgt_priv = tgt_priv;
++ return scst_rx_mgmt_fn(sess, &params);
++}
++
++/*
++ * Creates new management command using LUN and sends it for execution.
++ * Currently can be used for any fn, except SCST_ABORT_TASK.
++ * Must not be called in parallel with scst_unregister_session() for the
++ * same sess. Returns 0 for success, error code otherwise.
++ *
++ * Obsolete in favor of scst_rx_mgmt_fn()
++ */
++static inline int scst_rx_mgmt_fn_lun(struct scst_session *sess, int fn,
++ const uint8_t *lun, int lun_len, int atomic, void *tgt_priv)
++{
++ struct scst_rx_mgmt_params params;
++
++ BUG_ON(fn == SCST_ABORT_TASK);
++
++ memset(&params, 0, sizeof(params));
++ params.fn = fn;
++ params.lun = lun;
++ params.lun_len = lun_len;
++ params.lun_set = 1;
++ params.atomic = atomic;
++ params.tgt_priv = tgt_priv;
++ return scst_rx_mgmt_fn(sess, &params);
++}
++
++int scst_get_cdb_info(struct scst_cmd *cmd);
++
++int scst_set_cmd_error_status(struct scst_cmd *cmd, int status);
++int scst_set_cmd_error(struct scst_cmd *cmd, int key, int asc, int ascq);
++void scst_set_busy(struct scst_cmd *cmd);
++
++void scst_check_convert_sense(struct scst_cmd *cmd);
++
++void scst_set_initial_UA(struct scst_session *sess, int key, int asc, int ascq);
++
++void scst_capacity_data_changed(struct scst_device *dev);
++
++struct scst_cmd *scst_find_cmd_by_tag(struct scst_session *sess, uint64_t tag);
++struct scst_cmd *scst_find_cmd(struct scst_session *sess, void *data,
++ int (*cmp_fn) (struct scst_cmd *cmd,
++ void *data));
++
++enum dma_data_direction scst_to_dma_dir(int scst_dir);
++enum dma_data_direction scst_to_tgt_dma_dir(int scst_dir);
++
++/*
++ * Returns true, if cmd's CDB is fully locally handled by SCST and false
++ * otherwise. Dev handlers parse() and dev_done() not called for such commands.
++ */
++static inline bool scst_is_cmd_fully_local(struct scst_cmd *cmd)
++{
++ return (cmd->op_flags & SCST_FULLY_LOCAL_CMD) != 0;
++}
++
++/*
++ * Returns true, if cmd's CDB is locally handled by SCST and
++ * false otherwise.
++ */
++static inline bool scst_is_cmd_local(struct scst_cmd *cmd)
++{
++ return (cmd->op_flags & SCST_LOCAL_CMD) != 0;
++}
++
++/* Returns true, if cmd can deliver UA */
++static inline bool scst_is_ua_command(struct scst_cmd *cmd)
++{
++ return (cmd->op_flags & SCST_SKIP_UA) == 0;
++}
++
++int scst_register_virtual_device(struct scst_dev_type *dev_handler,
++ const char *dev_name);
++void scst_unregister_virtual_device(int id);
++
++/*
++ * Get/Set functions for tgt's sg_tablesize
++ */
++static inline int scst_tgt_get_sg_tablesize(struct scst_tgt *tgt)
++{
++ return tgt->sg_tablesize;
++}
++
++static inline void scst_tgt_set_sg_tablesize(struct scst_tgt *tgt, int val)
++{
++ tgt->sg_tablesize = val;
++}
++
++/*
++ * Get/Set functions for tgt's target private data
++ */
++static inline void *scst_tgt_get_tgt_priv(struct scst_tgt *tgt)
++{
++ return tgt->tgt_priv;
++}
++
++static inline void scst_tgt_set_tgt_priv(struct scst_tgt *tgt, void *val)
++{
++ tgt->tgt_priv = val;
++}
++
++void scst_update_hw_pending_start(struct scst_cmd *cmd);
++
++/*
++ * Get/Set functions for session's target private data
++ */
++static inline void *scst_sess_get_tgt_priv(struct scst_session *sess)
++{
++ return sess->tgt_priv;
++}
++
++static inline void scst_sess_set_tgt_priv(struct scst_session *sess,
++ void *val)
++{
++ sess->tgt_priv = val;
++}
++
++uint16_t scst_lookup_tg_id(struct scst_device *dev, struct scst_tgt *tgt);
++bool scst_impl_alua_configured(struct scst_device *dev);
++int scst_tg_get_group_info(void **buf, uint32_t *response_length,
++ struct scst_device *dev, uint8_t data_format);
++
++/**
++ * Returns TRUE if cmd is being executed in atomic context.
++ *
++ * This function must be used outside of spinlocks and preempt/BH/IRQ
++ * disabled sections, because of the EXTRACHECK in it.
++ */
++static inline bool scst_cmd_atomic(struct scst_cmd *cmd)
++{
++ int res = cmd->atomic;
++#ifdef CONFIG_SCST_EXTRACHECKS
++ /*
++ * Checkpatch will complain on the use of in_atomic() below. You
++ * can safely ignore this warning since in_atomic() is used here
++ * only for debugging purposes.
++ */
++ if (unlikely((in_atomic() || in_interrupt() || irqs_disabled()) &&
++ !res)) {
++ printk(KERN_ERR "ERROR: atomic context and non-atomic cmd!\n");
++ dump_stack();
++ cmd->atomic = 1;
++ res = 1;
++ }
++#endif
++ return res;
++}
++
++/*
++ * Returns TRUE if cmd has been preliminary completed, i.e. completed or
++ * aborted.
++ */
++static inline bool scst_cmd_prelim_completed(struct scst_cmd *cmd)
++{
++ return cmd->completed || test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags);
++}
++
++static inline enum scst_exec_context __scst_estimate_context(bool atomic)
++{
++ if (in_irq())
++ return SCST_CONTEXT_TASKLET;
++/*
++ * We come here from many non reliable places, like the block layer, and don't
++ * have any reliable way to detect if we called under atomic context or not
++ * (in_atomic() isn't reliable), so let's be safe and disable this section
++ * for now to unconditionally return thread context.
++ */
++#if 0
++ else if (irqs_disabled())
++ return SCST_CONTEXT_THREAD;
++ else if (in_atomic())
++ return SCST_CONTEXT_DIRECT_ATOMIC;
++ else
++ return atomic ? SCST_CONTEXT_DIRECT :
++ SCST_CONTEXT_DIRECT_ATOMIC;
++#else
++ return SCST_CONTEXT_THREAD;
++#endif
++}
++
++static inline enum scst_exec_context scst_estimate_context(void)
++{
++ return __scst_estimate_context(false);
++}
++
++static inline enum scst_exec_context scst_estimate_context_atomic(void)
++{
++ return __scst_estimate_context(true);
++}
++
++/* Returns cmd's CDB */
++static inline const uint8_t *scst_cmd_get_cdb(struct scst_cmd *cmd)
++{
++ return cmd->cdb;
++}
++
++/* Returns cmd's CDB length */
++static inline unsigned int scst_cmd_get_cdb_len(struct scst_cmd *cmd)
++{
++ return cmd->cdb_len;
++}
++
++void scst_cmd_set_ext_cdb(struct scst_cmd *cmd,
++ uint8_t *ext_cdb, unsigned int ext_cdb_len, gfp_t gfp_mask);
++
++/* Returns cmd's session */
++static inline struct scst_session *scst_cmd_get_session(struct scst_cmd *cmd)
++{
++ return cmd->sess;
++}
++
++/* Returns cmd's response data length */
++static inline int scst_cmd_get_resp_data_len(struct scst_cmd *cmd)
++{
++ return cmd->resp_data_len;
++}
++
++/* Returns cmd's adjusted response data length */
++static inline int scst_cmd_get_adjusted_resp_data_len(struct scst_cmd *cmd)
++{
++ return cmd->adjusted_resp_data_len;
++}
++
++/* Returns if status should be sent for cmd */
++static inline int scst_cmd_get_is_send_status(struct scst_cmd *cmd)
++{
++ return cmd->is_send_status;
++}
++
++/*
++ * Returns pointer to cmd's SG data buffer.
++ *
++ * Usage of this function is not recommended, use scst_get_buf_*()
++ * family of functions instead.
++ */
++static inline struct scatterlist *scst_cmd_get_sg(struct scst_cmd *cmd)
++{
++ return cmd->sg;
++}
++
++/*
++ * Returns cmd's sg_cnt.
++ *
++ * Usage of this function is not recommended, use scst_get_buf_*()
++ * family of functions instead.
++ */
++static inline int scst_cmd_get_sg_cnt(struct scst_cmd *cmd)
++{
++ return cmd->sg_cnt;
++}
++
++/*
++ * Returns cmd's data buffer length.
++ *
++ * In case if you need to iterate over data in the buffer, usage of
++ * this function is not recommended, use scst_get_buf_*()
++ * family of functions instead.
++ */
++static inline unsigned int scst_cmd_get_bufflen(struct scst_cmd *cmd)
++{
++ return cmd->bufflen;
++}
++
++/*
++ * Returns pointer to cmd's bidirectional in (WRITE) SG data buffer.
++ *
++ * Usage of this function is not recommended, use scst_get_out_buf_*()
++ * family of functions instead.
++ */
++static inline struct scatterlist *scst_cmd_get_out_sg(struct scst_cmd *cmd)
++{
++ return cmd->out_sg;
++}
++
++/*
++ * Returns cmd's bidirectional in (WRITE) sg_cnt.
++ *
++ * Usage of this function is not recommended, use scst_get_out_buf_*()
++ * family of functions instead.
++ */
++static inline int scst_cmd_get_out_sg_cnt(struct scst_cmd *cmd)
++{
++ return cmd->out_sg_cnt;
++}
++
++void scst_restore_sg_buff(struct scst_cmd *cmd);
++
++/* Restores modified sg buffer in the original state, if necessary */
++static inline void scst_check_restore_sg_buff(struct scst_cmd *cmd)
++{
++ if (unlikely(cmd->sg_buff_modified))
++ scst_restore_sg_buff(cmd);
++}
++
++/*
++ * Returns cmd's bidirectional in (WRITE) data buffer length.
++ *
++ * In case if you need to iterate over data in the buffer, usage of
++ * this function is not recommended, use scst_get_out_buf_*()
++ * family of functions instead.
++ */
++static inline unsigned int scst_cmd_get_out_bufflen(struct scst_cmd *cmd)
++{
++ return cmd->out_bufflen;
++}
++
++/* Returns pointer to cmd's target's SG data buffer */
++static inline struct scatterlist *scst_cmd_get_tgt_sg(struct scst_cmd *cmd)
++{
++ return cmd->tgt_sg;
++}
++
++/* Returns cmd's target's sg_cnt */
++static inline int scst_cmd_get_tgt_sg_cnt(struct scst_cmd *cmd)
++{
++ return cmd->tgt_sg_cnt;
++}
++
++/* Sets cmd's target's SG data buffer */
++static inline void scst_cmd_set_tgt_sg(struct scst_cmd *cmd,
++ struct scatterlist *sg, int sg_cnt)
++{
++ cmd->tgt_sg = sg;
++ cmd->tgt_sg_cnt = sg_cnt;
++ cmd->tgt_data_buf_alloced = 1;
++}
++
++/* Returns pointer to cmd's target's OUT SG data buffer */
++static inline struct scatterlist *scst_cmd_get_out_tgt_sg(struct scst_cmd *cmd)
++{
++ return cmd->tgt_out_sg;
++}
++
++/* Returns cmd's target's OUT sg_cnt */
++static inline int scst_cmd_get_tgt_out_sg_cnt(struct scst_cmd *cmd)
++{
++ return cmd->tgt_out_sg_cnt;
++}
++
++/* Sets cmd's target's OUT SG data buffer */
++static inline void scst_cmd_set_tgt_out_sg(struct scst_cmd *cmd,
++ struct scatterlist *sg, int sg_cnt)
++{
++ WARN_ON(!cmd->tgt_data_buf_alloced);
++
++ cmd->tgt_out_sg = sg;
++ cmd->tgt_out_sg_cnt = sg_cnt;
++}
++
++/* Returns cmd's data direction */
++static inline scst_data_direction scst_cmd_get_data_direction(
++ struct scst_cmd *cmd)
++{
++ return cmd->data_direction;
++}
++
++/* Returns cmd's write len as well as write SG and sg_cnt */
++static inline int scst_cmd_get_write_fields(struct scst_cmd *cmd,
++ struct scatterlist **sg, int *sg_cnt)
++{
++ *sg = *cmd->write_sg;
++ *sg_cnt = *cmd->write_sg_cnt;
++ return cmd->write_len;
++}
++
++void scst_cmd_set_write_not_received_data_len(struct scst_cmd *cmd,
++ int not_received);
++
++bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid);
++
++/*
++ * Returns true if cmd has residual(s) and returns them in the corresponding
++ * parameters(s).
++ */
++static inline bool scst_get_resid(struct scst_cmd *cmd,
++ int *resid, int *bidi_out_resid)
++{
++ if (likely(!cmd->resid_possible))
++ return false;
++ return __scst_get_resid(cmd, resid, bidi_out_resid);
++}
++
++/* Returns cmd's status byte from host device */
++static inline uint8_t scst_cmd_get_status(struct scst_cmd *cmd)
++{
++ return cmd->status;
++}
++
++/* Returns cmd's status from host adapter itself */
++static inline uint8_t scst_cmd_get_msg_status(struct scst_cmd *cmd)
++{
++ return cmd->msg_status;
++}
++
++/* Returns cmd's status set by low-level driver to indicate its status */
++static inline uint8_t scst_cmd_get_host_status(struct scst_cmd *cmd)
++{
++ return cmd->host_status;
++}
++
++/* Returns cmd's status set by SCSI mid-level */
++static inline uint8_t scst_cmd_get_driver_status(struct scst_cmd *cmd)
++{
++ return cmd->driver_status;
++}
++
++/* Returns pointer to cmd's sense buffer */
++static inline uint8_t *scst_cmd_get_sense_buffer(struct scst_cmd *cmd)
++{
++ return cmd->sense;
++}
++
++/* Returns cmd's valid sense length */
++static inline int scst_cmd_get_sense_buffer_len(struct scst_cmd *cmd)
++{
++ return cmd->sense_valid_len;
++}
++
++/*
++ * Get/Set functions for cmd's queue_type
++ */
++static inline enum scst_cmd_queue_type scst_cmd_get_queue_type(
++ struct scst_cmd *cmd)
++{
++ return cmd->queue_type;
++}
++
++static inline void scst_cmd_set_queue_type(struct scst_cmd *cmd,
++ enum scst_cmd_queue_type queue_type)
++{
++ cmd->queue_type = queue_type;
++}
++
++/*
++ * Get/Set functions for cmd's target SN
++ */
++static inline uint64_t scst_cmd_get_tag(struct scst_cmd *cmd)
++{
++ return cmd->tag;
++}
++
++static inline void scst_cmd_set_tag(struct scst_cmd *cmd, uint64_t tag)
++{
++ cmd->tag = tag;
++}
++
++/*
++ * Get/Set functions for cmd's target private data.
++ * Variant with *_lock must be used if target driver uses
++ * scst_find_cmd() to avoid race with it, except inside scst_find_cmd()'s
++ * callback, where lock is already taken.
++ */
++static inline void *scst_cmd_get_tgt_priv(struct scst_cmd *cmd)
++{
++ return cmd->tgt_priv;
++}
++
++static inline void scst_cmd_set_tgt_priv(struct scst_cmd *cmd, void *val)
++{
++ cmd->tgt_priv = val;
++}
++
++/*
++ * Get/Set functions for tgt_need_alloc_data_buf flag
++ */
++static inline int scst_cmd_get_tgt_need_alloc_data_buf(struct scst_cmd *cmd)
++{
++ return cmd->tgt_need_alloc_data_buf;
++}
++
++static inline void scst_cmd_set_tgt_need_alloc_data_buf(struct scst_cmd *cmd)
++{
++ cmd->tgt_need_alloc_data_buf = 1;
++}
++
++/*
++ * Get/Set functions for tgt_data_buf_alloced flag
++ */
++static inline int scst_cmd_get_tgt_data_buff_alloced(struct scst_cmd *cmd)
++{
++ return cmd->tgt_data_buf_alloced;
++}
++
++static inline void scst_cmd_set_tgt_data_buff_alloced(struct scst_cmd *cmd)
++{
++ cmd->tgt_data_buf_alloced = 1;
++}
++
++/*
++ * Get/Set functions for dh_data_buf_alloced flag
++ */
++static inline int scst_cmd_get_dh_data_buff_alloced(struct scst_cmd *cmd)
++{
++ return cmd->dh_data_buf_alloced;
++}
++
++static inline void scst_cmd_set_dh_data_buff_alloced(struct scst_cmd *cmd)
++{
++ cmd->dh_data_buf_alloced = 1;
++}
++
++/*
++ * Get/Set functions for no_sgv flag
++ */
++static inline int scst_cmd_get_no_sgv(struct scst_cmd *cmd)
++{
++ return cmd->no_sgv;
++}
++
++static inline void scst_cmd_set_no_sgv(struct scst_cmd *cmd)
++{
++ cmd->no_sgv = 1;
++}
++
++/*
++ * Get/Set functions for tgt_sn
++ */
++static inline int scst_cmd_get_tgt_sn(struct scst_cmd *cmd)
++{
++ BUG_ON(!cmd->tgt_sn_set);
++ return cmd->tgt_sn;
++}
++
++static inline void scst_cmd_set_tgt_sn(struct scst_cmd *cmd, uint32_t tgt_sn)
++{
++ cmd->tgt_sn_set = 1;
++ cmd->tgt_sn = tgt_sn;
++}
++
++/*
++ * Get/Set functions for noio_mem_alloc
++ */
++static inline bool scst_cmd_get_noio_mem_alloc(struct scst_cmd *cmd)
++{
++ return cmd->noio_mem_alloc;
++}
++
++static inline void scst_cmd_set_noio_mem_alloc(struct scst_cmd *cmd)
++{
++ cmd->noio_mem_alloc = 1;
++}
++
++/*
++ * Returns 1 if the cmd was aborted, so its status is invalid and no
++ * reply shall be sent to the remote initiator. A target driver should
++ * only clear internal resources, associated with cmd.
++ */
++static inline int scst_cmd_aborted(struct scst_cmd *cmd)
++{
++ return test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags) &&
++ !test_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags);
++}
++
++/* Returns sense data format for cmd's dev */
++static inline bool scst_get_cmd_dev_d_sense(struct scst_cmd *cmd)
++{
++ return (cmd->dev != NULL) ? cmd->dev->d_sense : 0;
++}
++
++/*
++ * Get/Set functions for expected data direction, transfer length
++ * and its validity flag
++ */
++static inline int scst_cmd_is_expected_set(struct scst_cmd *cmd)
++{
++ return cmd->expected_values_set;
++}
++
++static inline scst_data_direction scst_cmd_get_expected_data_direction(
++ struct scst_cmd *cmd)
++{
++ return cmd->expected_data_direction;
++}
++
++static inline int scst_cmd_get_expected_transfer_len(
++ struct scst_cmd *cmd)
++{
++ return cmd->expected_transfer_len;
++}
++
++static inline int scst_cmd_get_expected_out_transfer_len(
++ struct scst_cmd *cmd)
++{
++ return cmd->expected_out_transfer_len;
++}
++
++static inline void scst_cmd_set_expected(struct scst_cmd *cmd,
++ scst_data_direction expected_data_direction,
++ int expected_transfer_len)
++{
++ cmd->expected_data_direction = expected_data_direction;
++ cmd->expected_transfer_len = expected_transfer_len;
++ cmd->expected_values_set = 1;
++}
++
++static inline void scst_cmd_set_expected_out_transfer_len(struct scst_cmd *cmd,
++ int expected_out_transfer_len)
++{
++ WARN_ON(!cmd->expected_values_set);
++ cmd->expected_out_transfer_len = expected_out_transfer_len;
++}
++
++/*
++ * Get/clear functions for cmd's may_need_dma_sync
++ */
++static inline int scst_get_may_need_dma_sync(struct scst_cmd *cmd)
++{
++ return cmd->may_need_dma_sync;
++}
++
++static inline void scst_clear_may_need_dma_sync(struct scst_cmd *cmd)
++{
++ cmd->may_need_dma_sync = 0;
++}
++
++/*
++ * Get/set functions for cmd's delivery_status. It is one of
++ * SCST_CMD_DELIVERY_* constants. It specifies the status of the
++ * command's delivery to initiator.
++ */
++static inline int scst_get_delivery_status(struct scst_cmd *cmd)
++{
++ return cmd->delivery_status;
++}
++
++static inline void scst_set_delivery_status(struct scst_cmd *cmd,
++ int delivery_status)
++{
++ cmd->delivery_status = delivery_status;
++}
++
++static inline unsigned int scst_get_active_cmd_count(struct scst_cmd *cmd)
++{
++ if (likely(cmd->tgt_dev != NULL))
++ return atomic_read(&cmd->tgt_dev->tgt_dev_cmd_count);
++ else
++ return (unsigned int)-1;
++}
++
++/*
++ * Get/Set function for mgmt cmd's target private data
++ */
++static inline void *scst_mgmt_cmd_get_tgt_priv(struct scst_mgmt_cmd *mcmd)
++{
++ return mcmd->tgt_priv;
++}
++
++static inline void scst_mgmt_cmd_set_tgt_priv(struct scst_mgmt_cmd *mcmd,
++ void *val)
++{
++ mcmd->tgt_priv = val;
++}
++
++/* Returns mgmt cmd's completion status (SCST_MGMT_STATUS_* constants) */
++static inline int scst_mgmt_cmd_get_status(struct scst_mgmt_cmd *mcmd)
++{
++ return mcmd->status;
++}
++
++/* Returns mgmt cmd's TM fn */
++static inline int scst_mgmt_cmd_get_fn(struct scst_mgmt_cmd *mcmd)
++{
++ return mcmd->fn;
++}
++
++/*
++ * Called by dev handler's task_mgmt_fn() to notify SCST core that mcmd
++ * is going to complete asynchronously.
++ */
++void scst_prepare_async_mcmd(struct scst_mgmt_cmd *mcmd);
++
++/*
++ * Called by dev handler to notify SCST core that async. mcmd is completed
++ * with status "status".
++ */
++void scst_async_mcmd_completed(struct scst_mgmt_cmd *mcmd, int status);
++
++/* Returns AEN's fn */
++static inline int scst_aen_get_event_fn(struct scst_aen *aen)
++{
++ return aen->event_fn;
++}
++
++/* Returns AEN's session */
++static inline struct scst_session *scst_aen_get_sess(struct scst_aen *aen)
++{
++ return aen->sess;
++}
++
++/* Returns AEN's LUN */
++static inline __be64 scst_aen_get_lun(struct scst_aen *aen)
++{
++ return aen->lun;
++}
++
++/* Returns SCSI AEN's sense */
++static inline const uint8_t *scst_aen_get_sense(struct scst_aen *aen)
++{
++ return aen->aen_sense;
++}
++
++/* Returns SCSI AEN's sense length */
++static inline int scst_aen_get_sense_len(struct scst_aen *aen)
++{
++ return aen->aen_sense_len;
++}
++
++/*
++ * Get/set functions for AEN's delivery_status. It is one of
++ * SCST_AEN_RES_* constants. It specifies the status of the
++ * command's delivery to initiator.
++ */
++static inline int scst_get_aen_delivery_status(struct scst_aen *aen)
++{
++ return aen->delivery_status;
++}
++
++static inline void scst_set_aen_delivery_status(struct scst_aen *aen,
++ int status)
++{
++ aen->delivery_status = status;
++}
++
++void scst_aen_done(struct scst_aen *aen);
++
++static inline void sg_clear(struct scatterlist *sg)
++{
++ memset(sg, 0, sizeof(*sg));
++#ifdef CONFIG_DEBUG_SG
++ sg->sg_magic = SG_MAGIC;
++#endif
++}
++
++enum scst_sg_copy_dir {
++ SCST_SG_COPY_FROM_TARGET,
++ SCST_SG_COPY_TO_TARGET
++};
++
++void scst_copy_sg(struct scst_cmd *cmd, enum scst_sg_copy_dir copy_dir);
++
++/*
++ * Functions for access to the commands data (SG) buffer. Should be used
++ * instead of direct access. Returns the buffer length for success, 0 for EOD,
++ * negative error code otherwise.
++ *
++ * Never EVER use this function to process only "the first page" of the buffer.
++ * The first SG entry can be as low as few bytes long. Use scst_get_buf_full()
++ * instead for such cases.
++ *
++ * "Buf" argument returns the mapped buffer
++ *
++ * The "put" function unmaps the buffer.
++ */
++static inline int __scst_get_buf(struct scst_cmd *cmd, int sg_cnt,
++ uint8_t **buf)
++{
++ int res = 0;
++ struct scatterlist *sg = cmd->get_sg_buf_cur_sg_entry;
++
++ if (cmd->get_sg_buf_entry_num >= sg_cnt) {
++ *buf = NULL;
++ goto out;
++ }
++
++ if (unlikely(sg_is_chain(sg)))
++ sg = sg_chain_ptr(sg);
++
++ *buf = page_address(sg_page(sg));
++ *buf += sg->offset;
++
++ res = sg->length;
++
++ cmd->get_sg_buf_entry_num++;
++ cmd->get_sg_buf_cur_sg_entry = ++sg;
++
++out:
++ return res;
++}
++
++static inline int scst_get_buf_first(struct scst_cmd *cmd, uint8_t **buf)
++{
++ if (unlikely(cmd->sg == NULL)) {
++ *buf = NULL;
++ return 0;
++ }
++ cmd->get_sg_buf_entry_num = 0;
++ cmd->get_sg_buf_cur_sg_entry = cmd->sg;
++ cmd->may_need_dma_sync = 1;
++ return __scst_get_buf(cmd, cmd->sg_cnt, buf);
++}
++
++static inline int scst_get_buf_next(struct scst_cmd *cmd, uint8_t **buf)
++{
++ return __scst_get_buf(cmd, cmd->sg_cnt, buf);
++}
++
++static inline void scst_put_buf(struct scst_cmd *cmd, void *buf)
++{
++ /* Nothing to do */
++}
++
++static inline int scst_get_out_buf_first(struct scst_cmd *cmd, uint8_t **buf)
++{
++ if (unlikely(cmd->out_sg == NULL)) {
++ *buf = NULL;
++ return 0;
++ }
++ cmd->get_sg_buf_entry_num = 0;
++ cmd->get_sg_buf_cur_sg_entry = cmd->out_sg;
++ cmd->may_need_dma_sync = 1;
++ return __scst_get_buf(cmd, cmd->out_sg_cnt, buf);
++}
++
++static inline int scst_get_out_buf_next(struct scst_cmd *cmd, uint8_t **buf)
++{
++ return __scst_get_buf(cmd, cmd->out_sg_cnt, buf);
++}
++
++static inline void scst_put_out_buf(struct scst_cmd *cmd, void *buf)
++{
++ /* Nothing to do */
++}
++
++static inline int scst_get_sg_buf_first(struct scst_cmd *cmd, uint8_t **buf,
++ struct scatterlist *sg, int sg_cnt)
++{
++ if (unlikely(sg == NULL)) {
++ *buf = NULL;
++ return 0;
++ }
++ cmd->get_sg_buf_entry_num = 0;
++ cmd->get_sg_buf_cur_sg_entry = cmd->sg;
++ cmd->may_need_dma_sync = 1;
++ return __scst_get_buf(cmd, sg_cnt, buf);
++}
++
++static inline int scst_get_sg_buf_next(struct scst_cmd *cmd, uint8_t **buf,
++ struct scatterlist *sg, int sg_cnt)
++{
++ return __scst_get_buf(cmd, sg_cnt, buf);
++}
++
++static inline void scst_put_sg_buf(struct scst_cmd *cmd, void *buf,
++ struct scatterlist *sg, int sg_cnt)
++{
++ /* Nothing to do */
++}
++
++/*
++ * Functions for access to the commands data (SG) page. Should be used
++ * instead of direct access. Returns the buffer length for success, 0 for EOD,
++ * negative error code otherwise.
++ *
++ * "Page" argument returns the starting page, "offset" - offset in it.
++ *
++ * The "put" function "puts" the buffer. It should be always be used, because
++ * in future may need to do some additional operations.
++ */
++static inline int __scst_get_sg_page(struct scst_cmd *cmd, int sg_cnt,
++ struct page **page, int *offset)
++{
++ int res = 0;
++ struct scatterlist *sg = cmd->get_sg_buf_cur_sg_entry;
++
++ if (cmd->get_sg_buf_entry_num >= sg_cnt) {
++ *page = NULL;
++ *offset = 0;
++ goto out;
++ }
++
++ if (unlikely(sg_is_chain(sg)))
++ sg = sg_chain_ptr(sg);
++
++ *page = sg_page(sg);
++ *offset = sg->offset;
++ res = sg->length;
++
++ cmd->get_sg_buf_entry_num++;
++ cmd->get_sg_buf_cur_sg_entry = ++sg;
++
++out:
++ return res;
++}
++
++static inline int scst_get_sg_page_first(struct scst_cmd *cmd,
++ struct page **page, int *offset)
++{
++ if (unlikely(cmd->sg == NULL)) {
++ *page = NULL;
++ *offset = 0;
++ return 0;
++ }
++ cmd->get_sg_buf_entry_num = 0;
++ cmd->get_sg_buf_cur_sg_entry = cmd->sg;
++ return __scst_get_sg_page(cmd, cmd->sg_cnt, page, offset);
++}
++
++static inline int scst_get_sg_page_next(struct scst_cmd *cmd,
++ struct page **page, int *offset)
++{
++ return __scst_get_sg_page(cmd, cmd->sg_cnt, page, offset);
++}
++
++static inline void scst_put_sg_page(struct scst_cmd *cmd,
++ struct page *page, int offset)
++{
++ /* Nothing to do */
++}
++
++static inline int scst_get_out_sg_page_first(struct scst_cmd *cmd,
++ struct page **page, int *offset)
++{
++ if (unlikely(cmd->out_sg == NULL)) {
++ *page = NULL;
++ *offset = 0;
++ return 0;
++ }
++ cmd->get_sg_buf_entry_num = 0;
++ cmd->get_sg_buf_cur_sg_entry = cmd->out_sg;
++ return __scst_get_sg_page(cmd, cmd->out_sg_cnt, page, offset);
++}
++
++static inline int scst_get_out_sg_page_next(struct scst_cmd *cmd,
++ struct page **page, int *offset)
++{
++ return __scst_get_sg_page(cmd, cmd->out_sg_cnt, page, offset);
++}
++
++static inline void scst_put_out_sg_page(struct scst_cmd *cmd,
++ struct page *page, int offset)
++{
++ /* Nothing to do */
++}
++
++/*
++ * Returns approximate higher rounded buffers count that
++ * scst_get_buf_[first|next]() return.
++ */
++static inline int scst_get_buf_count(struct scst_cmd *cmd)
++{
++ return (cmd->sg_cnt == 0) ? 1 : cmd->sg_cnt;
++}
++
++/*
++ * Returns approximate higher rounded buffers count that
++ * scst_get_out_buf_[first|next]() return.
++ */
++static inline int scst_get_out_buf_count(struct scst_cmd *cmd)
++{
++ return (cmd->out_sg_cnt == 0) ? 1 : cmd->out_sg_cnt;
++}
++
++int scst_get_buf_full(struct scst_cmd *cmd, uint8_t **buf);
++void scst_put_buf_full(struct scst_cmd *cmd, uint8_t *buf);
++
++#ifdef CONFIG_DEBUG_LOCK_ALLOC
++extern struct lockdep_map scst_suspend_dep_map;
++#define scst_assert_activity_suspended() \
++ WARN_ON(debug_locks && !lock_is_held(&scst_suspend_dep_map));
++#else
++#define scst_assert_activity_suspended() do { } while (0)
++#endif
++int scst_suspend_activity(bool interruptible);
++void scst_resume_activity(void);
++
++void scst_process_active_cmd(struct scst_cmd *cmd, bool atomic);
++
++void scst_post_parse(struct scst_cmd *cmd);
++void scst_post_alloc_data_buf(struct scst_cmd *cmd);
++
++int scst_check_local_events(struct scst_cmd *cmd);
++
++static inline int scst_pre_check_local_events(struct scst_cmd *cmd)
++{
++ int res = scst_check_local_events(cmd);
++ cmd->check_local_events_once_done = 1;
++ return res;
++}
++
++int scst_set_cmd_abnormal_done_state(struct scst_cmd *cmd);
++
++struct scst_trace_log {
++ unsigned int val;
++ const char *token;
++};
++
++extern struct mutex scst_mutex;
++
++const struct sysfs_ops *scst_sysfs_get_sysfs_ops(void);
++
++/*
++ * Returns target driver's root sysfs kobject.
++ * The driver can create own files/directories/links here.
++ */
++static inline struct kobject *scst_sysfs_get_tgtt_kobj(
++ struct scst_tgt_template *tgtt)
++{
++ return &tgtt->tgtt_kobj;
++}
++
++/*
++ * Returns target's root sysfs kobject.
++ * The driver can create own files/directories/links here.
++ */
++static inline struct kobject *scst_sysfs_get_tgt_kobj(
++ struct scst_tgt *tgt)
++{
++ return &tgt->tgt_kobj;
++}
++
++/*
++ * Returns device handler's root sysfs kobject.
++ * The driver can create own files/directories/links here.
++ */
++static inline struct kobject *scst_sysfs_get_devt_kobj(
++ struct scst_dev_type *devt)
++{
++ return &devt->devt_kobj;
++}
++
++/*
++ * Returns device's root sysfs kobject.
++ * The driver can create own files/directories/links here.
++ */
++static inline struct kobject *scst_sysfs_get_dev_kobj(
++ struct scst_device *dev)
++{
++ return &dev->dev_kobj;
++}
++
++/*
++ * Returns session's root sysfs kobject.
++ * The driver can create own files/directories/links here.
++ */
++static inline struct kobject *scst_sysfs_get_sess_kobj(
++ struct scst_session *sess)
++{
++ return &sess->sess_kobj;
++}
++
++/* Returns target name */
++static inline const char *scst_get_tgt_name(const struct scst_tgt *tgt)
++{
++ return tgt->tgt_name;
++}
++
++int scst_alloc_sense(struct scst_cmd *cmd, int atomic);
++int scst_alloc_set_sense(struct scst_cmd *cmd, int atomic,
++ const uint8_t *sense, unsigned int len);
++
++int scst_set_sense(uint8_t *buffer, int len, bool d_sense,
++ int key, int asc, int ascq);
++
++bool scst_is_ua_sense(const uint8_t *sense, int len);
++
++bool scst_analyze_sense(const uint8_t *sense, int len,
++ unsigned int valid_mask, int key, int asc, int ascq);
++
++unsigned long scst_random(void);
++
++void scst_set_resp_data_len(struct scst_cmd *cmd, int resp_data_len);
++
++void scst_cmd_get(struct scst_cmd *cmd);
++void scst_cmd_put(struct scst_cmd *cmd);
++
++struct scatterlist *scst_alloc(int size, gfp_t gfp_mask, int *count);
++void scst_free(struct scatterlist *sg, int count);
++
++void scst_add_thr_data(struct scst_tgt_dev *tgt_dev,
++ struct scst_thr_data_hdr *data,
++ void (*free_fn) (struct scst_thr_data_hdr *data));
++void scst_del_all_thr_data(struct scst_tgt_dev *tgt_dev);
++void scst_dev_del_all_thr_data(struct scst_device *dev);
++struct scst_thr_data_hdr *__scst_find_thr_data(struct scst_tgt_dev *tgt_dev,
++ struct task_struct *tsk);
++
++/* Finds local to the current thread data. Returns NULL, if they not found. */
++static inline struct scst_thr_data_hdr *scst_find_thr_data(
++ struct scst_tgt_dev *tgt_dev)
++{
++ return __scst_find_thr_data(tgt_dev, current);
++}
++
++/* Increase ref counter for the thread data */
++static inline void scst_thr_data_get(struct scst_thr_data_hdr *data)
++{
++ atomic_inc(&data->ref);
++}
++
++/* Decrease ref counter for the thread data */
++static inline void scst_thr_data_put(struct scst_thr_data_hdr *data)
++{
++ if (atomic_dec_and_test(&data->ref))
++ data->free_fn(data);
++}
++
++int scst_calc_block_shift(int sector_size);
++int scst_sbc_generic_parse(struct scst_cmd *cmd,
++ int (*get_block_shift)(struct scst_cmd *cmd));
++int scst_cdrom_generic_parse(struct scst_cmd *cmd,
++ int (*get_block_shift)(struct scst_cmd *cmd));
++int scst_modisk_generic_parse(struct scst_cmd *cmd,
++ int (*get_block_shift)(struct scst_cmd *cmd));
++int scst_tape_generic_parse(struct scst_cmd *cmd,
++ int (*get_block_size)(struct scst_cmd *cmd));
++int scst_changer_generic_parse(struct scst_cmd *cmd,
++ int (*nothing)(struct scst_cmd *cmd));
++int scst_processor_generic_parse(struct scst_cmd *cmd,
++ int (*nothing)(struct scst_cmd *cmd));
++int scst_raid_generic_parse(struct scst_cmd *cmd,
++ int (*nothing)(struct scst_cmd *cmd));
++
++int scst_block_generic_dev_done(struct scst_cmd *cmd,
++ void (*set_block_shift)(struct scst_cmd *cmd, int block_shift));
++int scst_tape_generic_dev_done(struct scst_cmd *cmd,
++ void (*set_block_size)(struct scst_cmd *cmd, int block_size));
++
++int scst_obtain_device_parameters(struct scst_device *dev);
++
++void scst_reassign_persistent_sess_states(struct scst_session *new_sess,
++ struct scst_session *old_sess);
++
++int scst_get_max_lun_commands(struct scst_session *sess, uint64_t lun);
++
++/*
++ * Has to be put here open coded, because Linux doesn't have equivalent, which
++ * allows exclusive wake ups of threads in LIFO order. We need it to let (yet)
++ * unneeded threads sleep and not pollute CPU cache by their stacks.
++ */
++static inline void add_wait_queue_exclusive_head(wait_queue_head_t *q,
++ wait_queue_t *wait)
++{
++ unsigned long flags;
++
++ wait->flags |= WQ_FLAG_EXCLUSIVE;
++ spin_lock_irqsave(&q->lock, flags);
++ __add_wait_queue(q, wait);
++ spin_unlock_irqrestore(&q->lock, flags);
++}
++
++/*
++ * Structure to match events to user space and replies on them
++ */
++struct scst_sysfs_user_info {
++ /* Unique cookie to identify request */
++ uint32_t info_cookie;
++
++ /* Entry in the global list */
++ struct list_head info_list_entry;
++
++ /* Set if reply from the user space is being executed */
++ unsigned int info_being_executed:1;
++
++ /* Set if this info is in the info_list */
++ unsigned int info_in_list:1;
++
++ /* Completion to wait on for the request completion */
++ struct completion info_completion;
++
++ /* Request completion status and optional data */
++ int info_status;
++ void *data;
++};
++
++int scst_sysfs_user_add_info(struct scst_sysfs_user_info **out_info);
++void scst_sysfs_user_del_info(struct scst_sysfs_user_info *info);
++struct scst_sysfs_user_info *scst_sysfs_user_get_info(uint32_t cookie);
++int scst_wait_info_completion(struct scst_sysfs_user_info *info,
++ unsigned long timeout);
++
++unsigned int scst_get_setup_id(void);
++
++/*
++ * Needed to avoid potential circular locking dependency between scst_mutex
++ * and internal sysfs locking (s_active). It could be since most sysfs entries
++ * are created and deleted under scst_mutex AND scst_mutex is taken inside
++ * sysfs functions. So, we push from the sysfs functions all the processing
++ * taking scst_mutex. To avoid deadlock, we return from them with EAGAIN
++ * if processing is taking too long. User space then should poll
++ * last_sysfs_mgmt_res until it returns the result of the processing
++ * (something other than EAGAIN).
++ */
++struct scst_sysfs_work_item {
++ /*
++ * If true, then last_sysfs_mgmt_res will not be updated. This is
++ * needed to allow read only sysfs monitoring during management actions.
++ * All management actions are supposed to be externally serialized,
++ * so then last_sysfs_mgmt_res automatically serialized too.
++ * Otherwise a monitoring action can overwrite value of simultaneous
++ * management action's last_sysfs_mgmt_res.
++ */
++ bool read_only_action;
++
++ struct list_head sysfs_work_list_entry;
++ struct kref sysfs_work_kref;
++ int (*sysfs_work_fn)(struct scst_sysfs_work_item *work);
++ struct completion sysfs_work_done;
++ char *buf;
++
++ union {
++ struct scst_dev_type *devt;
++ struct scst_tgt_template *tgtt;
++ struct {
++ struct scst_tgt *tgt;
++ struct scst_acg *acg;
++ union {
++ bool is_tgt_kobj;
++ int io_grouping_type;
++ bool enable;
++ cpumask_t cpu_mask;
++ };
++ };
++ struct {
++ struct scst_device *dev;
++ int new_threads_num;
++ enum scst_dev_type_threads_pool_type new_threads_pool_type;
++ };
++ struct scst_session *sess;
++ struct {
++ struct scst_tgt *tgt_r;
++ unsigned long rel_tgt_id;
++ };
++ struct {
++ struct kobject *kobj;
++ };
++ };
++ int work_res;
++ char *res_buf;
++};
++
++int scst_alloc_sysfs_work(int (*sysfs_work_fn)(struct scst_sysfs_work_item *),
++ bool read_only_action, struct scst_sysfs_work_item **res_work);
++int scst_sysfs_queue_wait_work(struct scst_sysfs_work_item *work);
++void scst_sysfs_work_get(struct scst_sysfs_work_item *work);
++void scst_sysfs_work_put(struct scst_sysfs_work_item *work);
++
++char *scst_get_next_lexem(char **token_str);
++void scst_restore_token_str(char *prev_lexem, char *token_str);
++char *scst_get_next_token_str(char **input_str);
++
++void scst_init_threads(struct scst_cmd_threads *cmd_threads);
++void scst_deinit_threads(struct scst_cmd_threads *cmd_threads);
++
++void scst_pass_through_cmd_done(void *data, char *sense, int result, int resid);
++int scst_scsi_exec_async(struct scst_cmd *cmd, void *data,
++ void (*done)(void *data, char *sense, int result, int resid));
++
++#endif /* __SCST_H */
+diff -uprN orig/linux-3.2/include/scst/scst_const.h linux-3.2/include/scst/scst_const.h
+--- orig/linux-3.2/include/scst/scst_const.h
++++ linux-3.2/include/scst/scst_const.h
+@@ -0,0 +1,487 @@
++/*
++ * include/scst_const.h
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * Contains common SCST constants.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#ifndef __SCST_CONST_H
++#define __SCST_CONST_H
++
++#ifndef GENERATING_UPSTREAM_PATCH
++/*
++ * Include <linux/version.h> only when not converting this header file into
++ * a patch for upstream review because only then the symbol LINUX_VERSION_CODE
++ * is needed.
++ */
++#include <linux/version.h>
++#endif
++#include <scsi/scsi.h>
++
++/*
++ * Version numbers, the same as for the kernel.
++ *
++ * Changing it don't forget to change SCST_FIO_REV in scst_vdisk.c
++ * and FIO_REV in usr/fileio/common.h as well.
++ */
++#define SCST_VERSION(a, b, c, d) (((a) << 24) + ((b) << 16) + ((c) << 8) + d)
++#define SCST_VERSION_CODE SCST_VERSION(2, 2, 0, 0)
++#define SCST_VERSION_STRING_SUFFIX
++#define SCST_VERSION_NAME "2.2.0-pre"
++#define SCST_VERSION_STRING SCST_VERSION_NAME SCST_VERSION_STRING_SUFFIX
++
++#define SCST_CONST_VERSION "$Revision: 3987 $"
++
++/*** Shared constants between user and kernel spaces ***/
++
++/* Max size of CDB */
++#define SCST_MAX_CDB_SIZE 16
++
++/* Max size of long CDB */
++#define SCST_MAX_LONG_CDB_SIZE 65536
++
++/* Max size of various names */
++#define SCST_MAX_NAME 50
++
++/* Max size of external names, like initiator name */
++#define SCST_MAX_EXTERNAL_NAME 256
++
++/* Max LUN. 2 bits are used for addressing method. */
++#define SCST_MAX_LUN ((1 << (16-2)) - 1)
++
++/*
++ * Size of sense sufficient to carry standard sense data.
++ * Warning! It's allocated on stack!
++ */
++#define SCST_STANDARD_SENSE_LEN 18
++
++/* Max size of sense */
++#define SCST_SENSE_BUFFERSIZE 96
++
++/*************************************************************
++ ** Allowed delivery statuses for cmd's delivery_status
++ *************************************************************/
++
++#define SCST_CMD_DELIVERY_SUCCESS 0
++#define SCST_CMD_DELIVERY_FAILED -1
++#define SCST_CMD_DELIVERY_ABORTED -2
++
++/*************************************************************
++ ** Values for task management functions
++ *************************************************************/
++#define SCST_ABORT_TASK 0
++#define SCST_ABORT_TASK_SET 1
++#define SCST_CLEAR_ACA 2
++#define SCST_CLEAR_TASK_SET 3
++#define SCST_LUN_RESET 4
++#define SCST_TARGET_RESET 5
++
++/** SCST extensions **/
++
++/*
++ * Notifies about I_T nexus loss event in the corresponding session.
++ * Aborts all tasks there, resets the reservation, if any, and sets
++ * up the I_T Nexus loss UA.
++ */
++#define SCST_NEXUS_LOSS_SESS 6
++
++/* Aborts all tasks in the corresponding session */
++#define SCST_ABORT_ALL_TASKS_SESS 7
++
++/*
++ * Notifies about I_T nexus loss event. Aborts all tasks in all sessions
++ * of the tgt, resets the reservations, if any, and sets up the I_T Nexus
++ * loss UA.
++ */
++#define SCST_NEXUS_LOSS 8
++
++/* Aborts all tasks in all sessions of the tgt */
++#define SCST_ABORT_ALL_TASKS 9
++
++/*
++ * Internal TM command issued by SCST in scst_unregister_session(). It is the
++ * same as SCST_NEXUS_LOSS_SESS, except:
++ * - it doesn't call task_mgmt_affected_cmds_done()
++ * - it doesn't call task_mgmt_fn_done()
++ * - it doesn't queue NEXUS LOSS UA.
++ *
++ * Target drivers must NEVER use it!!
++ */
++#define SCST_UNREG_SESS_TM 10
++
++/*
++ * Internal TM command issued by SCST in scst_pr_abort_reg(). It aborts all
++ * tasks from mcmd->origin_pr_cmd->tgt_dev, except mcmd->origin_pr_cmd.
++ * Additionally:
++ * - it signals pr_aborting_cmpl completion when all affected
++ * commands marked as aborted.
++ * - it doesn't call task_mgmt_affected_cmds_done()
++ * - it doesn't call task_mgmt_fn_done()
++ * - it calls mcmd->origin_pr_cmd->scst_cmd_done() when all affected
++ * commands aborted.
++ *
++ * Target drivers must NEVER use it!!
++ */
++#define SCST_PR_ABORT_ALL 11
++
++/*************************************************************
++ ** Values for mgmt cmd's status field. Codes taken from iSCSI
++ *************************************************************/
++#define SCST_MGMT_STATUS_SUCCESS 0
++#define SCST_MGMT_STATUS_TASK_NOT_EXIST -1
++#define SCST_MGMT_STATUS_LUN_NOT_EXIST -2
++#define SCST_MGMT_STATUS_FN_NOT_SUPPORTED -5
++#define SCST_MGMT_STATUS_REJECTED -255
++#define SCST_MGMT_STATUS_FAILED -129
++
++/*************************************************************
++ ** SCSI LUN addressing methods. See also SAM-2 and the
++ ** section about eight byte LUNs.
++ *************************************************************/
++enum scst_lun_addr_method {
++ SCST_LUN_ADDR_METHOD_PERIPHERAL = 0,
++ SCST_LUN_ADDR_METHOD_FLAT = 1,
++ SCST_LUN_ADDR_METHOD_LUN = 2,
++ SCST_LUN_ADDR_METHOD_EXTENDED_LUN = 3,
++};
++
++/*************************************************************
++ ** SCSI task attribute queue types
++ *************************************************************/
++enum scst_cmd_queue_type {
++ SCST_CMD_QUEUE_UNTAGGED = 0,
++ SCST_CMD_QUEUE_SIMPLE,
++ SCST_CMD_QUEUE_ORDERED,
++ SCST_CMD_QUEUE_HEAD_OF_QUEUE,
++ SCST_CMD_QUEUE_ACA
++};
++
++/*************************************************************
++ ** CDB flags
++ *************************************************************/
++enum scst_cdb_flags {
++ SCST_TRANSFER_LEN_TYPE_FIXED = 0x0001,
++ SCST_SMALL_TIMEOUT = 0x0002,
++ SCST_LONG_TIMEOUT = 0x0004,
++ SCST_UNKNOWN_LENGTH = 0x0008,
++ SCST_INFO_VALID = 0x0010, /* must be single bit */
++ SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED = 0x0020,
++ SCST_IMPLICIT_HQ = 0x0040,
++ SCST_SKIP_UA = 0x0080,
++ SCST_WRITE_MEDIUM = 0x0100,
++ SCST_LOCAL_CMD = 0x0200,
++ SCST_FULLY_LOCAL_CMD = 0x0400,
++ SCST_REG_RESERVE_ALLOWED = 0x0800,
++ SCST_WRITE_EXCL_ALLOWED = 0x1000,
++ SCST_EXCL_ACCESS_ALLOWED = 0x2000,
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED = 0x4000,
++#endif
++ SCST_SERIALIZED = 0x8000,
++ SCST_STRICTLY_SERIALIZED = 0x10000|SCST_SERIALIZED,
++};
++
++/*************************************************************
++ ** Data direction aliases. Changing it don't forget to change
++ ** scst_to_tgt_dma_dir and SCST_DATA_DIR_MAX as well!!
++ *************************************************************/
++#define SCST_DATA_UNKNOWN 0
++#define SCST_DATA_WRITE 1
++#define SCST_DATA_READ 2
++#define SCST_DATA_BIDI (SCST_DATA_WRITE | SCST_DATA_READ)
++#define SCST_DATA_NONE 4
++
++#define SCST_DATA_DIR_MAX (SCST_DATA_NONE+1)
++
++/*************************************************************
++ ** Default suffix for targets with NULL names
++ *************************************************************/
++#define SCST_DEFAULT_TGT_NAME_SUFFIX "_target_"
++
++/*************************************************************
++ ** Sense manipulation and examination
++ *************************************************************/
++#define SCST_LOAD_SENSE(key_asc_ascq) key_asc_ascq
++
++#define SCST_SENSE_VALID(sense) ((sense != NULL) && \
++ ((((const uint8_t *)(sense))[0] & 0x70) == 0x70))
++
++#define SCST_NO_SENSE(sense) ((sense != NULL) && \
++ (((const uint8_t *)(sense))[2] == 0))
++
++/*************************************************************
++ ** Sense data for the appropriate errors. Can be used with
++ ** scst_set_cmd_error()
++ *************************************************************/
++#define scst_sense_no_sense NO_SENSE, 0x00, 0
++#define scst_sense_hardw_error HARDWARE_ERROR, 0x44, 0
++#define scst_sense_aborted_command ABORTED_COMMAND, 0x00, 0
++#define scst_sense_invalid_opcode ILLEGAL_REQUEST, 0x20, 0
++#define scst_sense_invalid_field_in_cdb ILLEGAL_REQUEST, 0x24, 0
++#define scst_sense_invalid_field_in_parm_list ILLEGAL_REQUEST, 0x26, 0
++#define scst_sense_parameter_value_invalid ILLEGAL_REQUEST, 0x26, 2
++#define scst_sense_invalid_release ILLEGAL_REQUEST, 0x26, 4
++#define scst_sense_parameter_list_length_invalid \
++ ILLEGAL_REQUEST, 0x1A, 0
++#define scst_sense_reset_UA UNIT_ATTENTION, 0x29, 0
++#define scst_sense_nexus_loss_UA UNIT_ATTENTION, 0x29, 0x7
++#define scst_sense_saving_params_unsup ILLEGAL_REQUEST, 0x39, 0
++#define scst_sense_lun_not_supported ILLEGAL_REQUEST, 0x25, 0
++#define scst_sense_data_protect DATA_PROTECT, 0x00, 0
++#define scst_sense_miscompare_error MISCOMPARE, 0x1D, 0
++#define scst_sense_block_out_range_error ILLEGAL_REQUEST, 0x21, 0
++#define scst_sense_medium_changed_UA UNIT_ATTENTION, 0x28, 0
++#define scst_sense_read_error MEDIUM_ERROR, 0x11, 0
++#define scst_sense_write_error MEDIUM_ERROR, 0x03, 0
++#define scst_sense_not_ready NOT_READY, 0x04, 0x10
++#define scst_sense_invalid_message ILLEGAL_REQUEST, 0x49, 0
++#define scst_sense_cleared_by_another_ini_UA UNIT_ATTENTION, 0x2F, 0
++#define scst_sense_capacity_data_changed UNIT_ATTENTION, 0x2A, 0x9
++#define scst_sense_reservation_preempted UNIT_ATTENTION, 0x2A, 0x03
++#define scst_sense_reservation_released UNIT_ATTENTION, 0x2A, 0x04
++#define scst_sense_registrations_preempted UNIT_ATTENTION, 0x2A, 0x05
++#define scst_sense_asym_access_state_changed UNIT_ATTENTION, 0x2A, 0x06
++#define scst_sense_reported_luns_data_changed UNIT_ATTENTION, 0x3F, 0xE
++#define scst_sense_inquery_data_changed UNIT_ATTENTION, 0x3F, 0x3
++
++/*************************************************************
++ * SCSI opcodes not listed anywhere else
++ *************************************************************/
++#define INIT_ELEMENT_STATUS 0x07
++#define INIT_ELEMENT_STATUS_RANGE 0x37
++#define PREVENT_ALLOW_MEDIUM 0x1E
++#define REQUEST_VOLUME_ADDRESS 0xB5
++#define WRITE_VERIFY_16 0x8E
++#define VERIFY_6 0x13
++#ifndef VERIFY_12
++#define VERIFY_12 0xAF
++#endif
++#ifndef GENERATING_UPSTREAM_PATCH
++/*
++ * The constants below have been defined in the kernel header <scsi/scsi.h>
++ * and hence are not needed when this header file is included in kernel code.
++ * The definitions below are only used when this header file is included during
++ * compilation of SCST's user space components.
++ */
++#ifndef READ_16
++#define READ_16 0x88
++#endif
++#ifndef WRITE_16
++#define WRITE_16 0x8a
++#endif
++#ifndef VERIFY_16
++#define VERIFY_16 0x8f
++#endif
++#ifndef SERVICE_ACTION_IN
++#define SERVICE_ACTION_IN 0x9e
++#endif
++#ifndef SAI_READ_CAPACITY_16
++/* values for service action in */
++#define SAI_READ_CAPACITY_16 0x10
++#endif
++#endif
++#ifndef GENERATING_UPSTREAM_PATCH
++#ifndef REPORT_LUNS
++#define REPORT_LUNS 0xa0
++#endif
++#endif
++
++
++/*************************************************************
++ ** SCSI Architecture Model (SAM) Status codes. Taken from SAM-3 draft
++ ** T10/1561-D Revision 4 Draft dated 7th November 2002.
++ *************************************************************/
++#define SAM_STAT_GOOD 0x00
++#define SAM_STAT_CHECK_CONDITION 0x02
++#define SAM_STAT_CONDITION_MET 0x04
++#define SAM_STAT_BUSY 0x08
++#define SAM_STAT_INTERMEDIATE 0x10
++#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14
++#define SAM_STAT_RESERVATION_CONFLICT 0x18
++#define SAM_STAT_COMMAND_TERMINATED 0x22 /* obsolete in SAM-3 */
++#define SAM_STAT_TASK_SET_FULL 0x28
++#define SAM_STAT_ACA_ACTIVE 0x30
++#define SAM_STAT_TASK_ABORTED 0x40
++
++/*************************************************************
++ ** Control byte field in CDB
++ *************************************************************/
++#define CONTROL_BYTE_LINK_BIT 0x01
++#define CONTROL_BYTE_NACA_BIT 0x04
++
++/*************************************************************
++ ** Byte 1 in INQUIRY CDB
++ *************************************************************/
++#define SCST_INQ_EVPD 0x01
++
++/*************************************************************
++ ** Byte 3 in Standard INQUIRY data
++ *************************************************************/
++#define SCST_INQ_BYTE3 3
++
++#define SCST_INQ_NORMACA_BIT 0x20
++
++/*************************************************************
++ ** TPGS field in byte 5 of the INQUIRY response (SPC-4).
++ *************************************************************/
++enum {
++ SCST_INQ_TPGS_MODE_IMPLICIT = 0x10,
++ SCST_INQ_TPGS_MODE_EXPLICIT = 0x20,
++};
++
++/*************************************************************
++ ** Byte 2 in RESERVE_10 CDB
++ *************************************************************/
++#define SCST_RES_3RDPTY 0x10
++#define SCST_RES_LONGID 0x02
++
++/*************************************************************
++ ** Values for the control mode page TST field
++ *************************************************************/
++#define SCST_CONTR_MODE_ONE_TASK_SET 0
++#define SCST_CONTR_MODE_SEP_TASK_SETS 1
++
++/*******************************************************************
++ ** Values for the control mode page QUEUE ALGORITHM MODIFIER field
++ *******************************************************************/
++#define SCST_CONTR_MODE_QUEUE_ALG_RESTRICTED_REORDER 0
++#define SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER 1
++
++/*************************************************************
++ ** Values for the control mode page D_SENSE field
++ *************************************************************/
++#define SCST_CONTR_MODE_FIXED_SENSE 0
++#define SCST_CONTR_MODE_DESCR_SENSE 1
++
++/*************************************************************
++ ** TransportID protocol identifiers
++ *************************************************************/
++
++#define SCSI_TRANSPORTID_PROTOCOLID_FCP2 0
++#define SCSI_TRANSPORTID_PROTOCOLID_SPI5 1
++#define SCSI_TRANSPORTID_PROTOCOLID_SRP 4
++#define SCSI_TRANSPORTID_PROTOCOLID_ISCSI 5
++#define SCSI_TRANSPORTID_PROTOCOLID_SAS 6
++
++/**
++ * enum scst_tg_state - SCSI target port group asymmetric access state.
++ *
++ * See also the documentation of the REPORT TARGET PORT GROUPS command in SPC-4.
++ */
++enum scst_tg_state {
++ SCST_TG_STATE_OPTIMIZED = 0x0,
++ SCST_TG_STATE_NONOPTIMIZED = 0x1,
++ SCST_TG_STATE_STANDBY = 0x2,
++ SCST_TG_STATE_UNAVAILABLE = 0x3,
++ SCST_TG_STATE_LBA_DEPENDENT = 0x4,
++ SCST_TG_STATE_OFFLINE = 0xe,
++ SCST_TG_STATE_TRANSITIONING = 0xf,
++};
++
++/**
++ * Target port group preferred bit.
++ *
++ * See also the documentation of the REPORT TARGET PORT GROUPS command in SPC-4.
++ */
++enum {
++ SCST_TG_PREFERRED = 0x80,
++};
++
++/**
++ * enum scst_tg_sup - Supported SCSI target port group states.
++ *
++ * See also the documentation of the REPORT TARGET PORT GROUPS command in SPC-4.
++ */
++enum scst_tg_sup {
++ SCST_TG_SUP_OPTIMIZED = 0x01,
++ SCST_TG_SUP_NONOPTIMIZED = 0x02,
++ SCST_TG_SUP_STANDBY = 0x04,
++ SCST_TG_SUP_UNAVAILABLE = 0x08,
++ SCST_TG_SUP_LBA_DEPENDENT = 0x10,
++ SCST_TG_SUP_OFFLINE = 0x40,
++ SCST_TG_SUP_TRANSITION = 0x80,
++};
++
++/*************************************************************
++ ** Misc SCSI constants
++ *************************************************************/
++#define SCST_SENSE_ASC_UA_RESET 0x29
++#define BYTCHK 0x02
++#define POSITION_LEN_SHORT 20
++#define POSITION_LEN_LONG 32
++
++/*************************************************************
++ ** Various timeouts
++ *************************************************************/
++#define SCST_DEFAULT_TIMEOUT (60 * HZ)
++
++#define SCST_GENERIC_CHANGER_TIMEOUT (3 * HZ)
++#define SCST_GENERIC_CHANGER_LONG_TIMEOUT (14000 * HZ)
++
++#define SCST_GENERIC_PROCESSOR_TIMEOUT (3 * HZ)
++#define SCST_GENERIC_PROCESSOR_LONG_TIMEOUT (14000 * HZ)
++
++#define SCST_GENERIC_TAPE_SMALL_TIMEOUT (3 * HZ)
++#define SCST_GENERIC_TAPE_REG_TIMEOUT (900 * HZ)
++#define SCST_GENERIC_TAPE_LONG_TIMEOUT (14000 * HZ)
++
++#define SCST_GENERIC_MODISK_SMALL_TIMEOUT (3 * HZ)
++#define SCST_GENERIC_MODISK_REG_TIMEOUT (900 * HZ)
++#define SCST_GENERIC_MODISK_LONG_TIMEOUT (14000 * HZ)
++
++#define SCST_GENERIC_DISK_SMALL_TIMEOUT (3 * HZ)
++#define SCST_GENERIC_DISK_REG_TIMEOUT (60 * HZ)
++#define SCST_GENERIC_DISK_LONG_TIMEOUT (3600 * HZ)
++
++#define SCST_GENERIC_RAID_TIMEOUT (3 * HZ)
++#define SCST_GENERIC_RAID_LONG_TIMEOUT (14000 * HZ)
++
++#define SCST_GENERIC_CDROM_SMALL_TIMEOUT (3 * HZ)
++#define SCST_GENERIC_CDROM_REG_TIMEOUT (900 * HZ)
++#define SCST_GENERIC_CDROM_LONG_TIMEOUT (14000 * HZ)
++
++#define SCST_MAX_OTHER_TIMEOUT (14000 * HZ)
++
++/*************************************************************
++ ** I/O grouping attribute string values. Must match constants
++ ** w/o '_STR' suffix!
++ *************************************************************/
++#define SCST_IO_GROUPING_AUTO_STR "auto"
++#define SCST_IO_GROUPING_THIS_GROUP_ONLY_STR "this_group_only"
++#define SCST_IO_GROUPING_NEVER_STR "never"
++
++/*************************************************************
++ ** Threads pool type attribute string values.
++ ** Must match scst_dev_type_threads_pool_type!
++ *************************************************************/
++#define SCST_THREADS_POOL_PER_INITIATOR_STR "per_initiator"
++#define SCST_THREADS_POOL_SHARED_STR "shared"
++
++/*************************************************************
++ ** Misc constants
++ *************************************************************/
++#define SCST_SYSFS_BLOCK_SIZE PAGE_SIZE
++
++#define SCST_PR_DIR "/var/lib/scst/pr"
++
++#define TID_COMMON_SIZE 24
++
++#define SCST_SYSFS_KEY_MARK "[key]"
++
++#define SCST_MIN_REL_TGT_ID 1
++#define SCST_MAX_REL_TGT_ID 65535
++
++#endif /* __SCST_CONST_H */
+diff -uprN orig/linux-3.2/drivers/scst/scst_main.c linux-3.2/drivers/scst/scst_main.c
+--- orig/linux-3.2/drivers/scst/scst_main.c
++++ linux-3.2/drivers/scst/scst_main.c
+@@ -0,0 +1,2229 @@
++/*
++ * scst_main.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/unistd.h>
++#include <linux/string.h>
++#include <linux/kthread.h>
++#include <linux/delay.h>
++#include <linux/lockdep.h>
++
++#include <scst/scst.h>
++#include "scst_priv.h"
++#include "scst_mem.h"
++#include "scst_pres.h"
++
++#if defined(CONFIG_HIGHMEM4G) || defined(CONFIG_HIGHMEM64G)
++#warning HIGHMEM kernel configurations are fully supported, but not \
++recommended for performance reasons. Consider changing VMSPLIT \
++option or use a 64-bit configuration instead. See README file for \
++details.
++#endif
++
++/**
++ ** SCST global variables. They are all uninitialized to have their layout in
++ ** memory be exactly as specified. Otherwise compiler puts zero-initialized
++ ** variable separately from nonzero-initialized ones.
++ **/
++
++/*
++ * Main SCST mutex. All targets, devices and dev_types management is done
++ * under this mutex.
++ *
++ * It must NOT be used in any works (schedule_work(), etc.), because
++ * otherwise a deadlock (double lock, actually) is possible, e.g., with
++ * scst_user detach_tgt(), which is called under scst_mutex and calls
++ * flush_scheduled_work().
++ */
++struct mutex scst_mutex;
++EXPORT_SYMBOL_GPL(scst_mutex);
++
++/*
++ * Secondary level main mutex, inner for scst_mutex. Needed for
++ * __scst_pr_register_all_tg_pt(), since we can't use scst_mutex there,
++ * because of the circular locking dependency with dev_pr_mutex.
++ */
++struct mutex scst_mutex2;
++
++/* Both protected by scst_mutex or scst_mutex2 on read and both on write */
++struct list_head scst_template_list;
++struct list_head scst_dev_list;
++
++/* Protected by scst_mutex */
++struct list_head scst_dev_type_list;
++struct list_head scst_virtual_dev_type_list;
++
++spinlock_t scst_main_lock;
++
++static struct kmem_cache *scst_mgmt_cachep;
++mempool_t *scst_mgmt_mempool;
++static struct kmem_cache *scst_mgmt_stub_cachep;
++mempool_t *scst_mgmt_stub_mempool;
++static struct kmem_cache *scst_ua_cachep;
++mempool_t *scst_ua_mempool;
++static struct kmem_cache *scst_sense_cachep;
++mempool_t *scst_sense_mempool;
++static struct kmem_cache *scst_aen_cachep;
++mempool_t *scst_aen_mempool;
++struct kmem_cache *scst_tgtd_cachep;
++struct kmem_cache *scst_sess_cachep;
++struct kmem_cache *scst_acgd_cachep;
++
++unsigned int scst_setup_id;
++
++spinlock_t scst_init_lock;
++wait_queue_head_t scst_init_cmd_list_waitQ;
++struct list_head scst_init_cmd_list;
++unsigned int scst_init_poll_cnt;
++
++struct kmem_cache *scst_cmd_cachep;
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++unsigned long scst_trace_flag;
++#endif
++
++int scst_max_tasklet_cmd = SCST_DEF_MAX_TASKLET_CMD;
++
++unsigned long scst_flags;
++
++struct scst_cmd_threads scst_main_cmd_threads;
++
++struct scst_percpu_info scst_percpu_infos[NR_CPUS];
++
++spinlock_t scst_mcmd_lock;
++struct list_head scst_active_mgmt_cmd_list;
++struct list_head scst_delayed_mgmt_cmd_list;
++wait_queue_head_t scst_mgmt_cmd_list_waitQ;
++
++wait_queue_head_t scst_mgmt_waitQ;
++spinlock_t scst_mgmt_lock;
++struct list_head scst_sess_init_list;
++struct list_head scst_sess_shut_list;
++
++wait_queue_head_t scst_dev_cmd_waitQ;
++
++#ifdef CONFIG_LOCKDEP
++static struct lock_class_key scst_suspend_key;
++struct lockdep_map scst_suspend_dep_map =
++ STATIC_LOCKDEP_MAP_INIT("scst_suspend_activity", &scst_suspend_key);
++#endif
++static struct mutex scst_suspend_mutex;
++/* protected by scst_suspend_mutex */
++static struct list_head scst_cmd_threads_list;
++
++int scst_threads;
++static struct task_struct *scst_init_cmd_thread;
++static struct task_struct *scst_mgmt_thread;
++static struct task_struct *scst_mgmt_cmd_thread;
++
++static int suspend_count;
++
++static int scst_virt_dev_last_id; /* protected by scst_mutex */
++
++cpumask_t default_cpu_mask;
++
++static unsigned int scst_max_cmd_mem;
++unsigned int scst_max_dev_cmd_mem;
++
++module_param_named(scst_threads, scst_threads, int, 0);
++MODULE_PARM_DESC(scst_threads, "SCSI target threads count");
++
++module_param_named(scst_max_cmd_mem, scst_max_cmd_mem, int, S_IRUGO);
++MODULE_PARM_DESC(scst_max_cmd_mem, "Maximum memory allowed to be consumed by "
++ "all SCSI commands of all devices at any given time in MB");
++
++module_param_named(scst_max_dev_cmd_mem, scst_max_dev_cmd_mem, int, S_IRUGO);
++MODULE_PARM_DESC(scst_max_dev_cmd_mem, "Maximum memory allowed to be consumed "
++ "by all SCSI commands of a device at any given time in MB");
++
++struct scst_dev_type scst_null_devtype = {
++ .name = "none",
++ .threads_num = -1,
++};
++
++static void __scst_resume_activity(void);
++
++/**
++ * __scst_register_target_template() - register target template.
++ * @vtt: target template
++ * @version: SCST_INTERFACE_VERSION version string to ensure that
++ * SCST core and the target driver use the same version of
++ * the SCST interface
++ *
++ * Description:
++ * Registers a target template and returns 0 on success or appropriate
++ * error code otherwise.
++ *
++ * Target drivers supposed to behave sanely and not call register()
++ * and unregister() randomly simultaneously.
++ */
++int __scst_register_target_template(struct scst_tgt_template *vtt,
++ const char *version)
++{
++ int res = 0;
++ struct scst_tgt_template *t;
++
++ TRACE_ENTRY();
++
++ INIT_LIST_HEAD(&vtt->tgt_list);
++
++ if (strcmp(version, SCST_INTERFACE_VERSION) != 0) {
++ PRINT_ERROR("Incorrect version of target %s", vtt->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (!vtt->detect) {
++ PRINT_ERROR("Target driver %s must have "
++ "detect() method.", vtt->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (!vtt->release) {
++ PRINT_ERROR("Target driver %s must have "
++ "release() method.", vtt->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (!vtt->xmit_response) {
++ PRINT_ERROR("Target driver %s must have "
++ "xmit_response() method.", vtt->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (vtt->get_initiator_port_transport_id == NULL)
++ PRINT_WARNING("Target driver %s doesn't support Persistent "
++ "Reservations", vtt->name);
++
++ if (vtt->threads_num < 0) {
++ PRINT_ERROR("Wrong threads_num value %d for "
++ "target \"%s\"", vtt->threads_num,
++ vtt->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if ((!vtt->enable_target || !vtt->is_target_enabled) &&
++ !vtt->enabled_attr_not_needed)
++ PRINT_WARNING("Target driver %s doesn't have enable_target() "
++ "and/or is_target_enabled() method(s). This is unsafe "
++ "and can lead that initiators connected on the "
++ "initialization time can see an unexpected set of "
++ "devices or no devices at all!", vtt->name);
++
++ if (((vtt->add_target != NULL) && (vtt->del_target == NULL)) ||
++ ((vtt->add_target == NULL) && (vtt->del_target != NULL))) {
++ PRINT_ERROR("Target driver %s must either define both "
++ "add_target() and del_target(), or none.", vtt->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (vtt->rdy_to_xfer == NULL)
++ vtt->rdy_to_xfer_atomic = 1;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out;
++ list_for_each_entry(t, &scst_template_list, scst_template_list_entry) {
++ if (strcmp(t->name, vtt->name) == 0) {
++ PRINT_ERROR("Target driver %s already registered",
++ vtt->name);
++ mutex_unlock(&scst_mutex);
++ goto out_unlock;
++ }
++ }
++ mutex_unlock(&scst_mutex);
++
++ res = scst_tgtt_sysfs_create(vtt);
++ if (res != 0)
++ goto out;
++
++ mutex_lock(&scst_mutex);
++ mutex_lock(&scst_mutex2);
++ list_add_tail(&vtt->scst_template_list_entry, &scst_template_list);
++ mutex_unlock(&scst_mutex2);
++ mutex_unlock(&scst_mutex);
++
++ TRACE_DBG("%s", "Calling target driver's detect()");
++ res = vtt->detect(vtt);
++ TRACE_DBG("Target driver's detect() returned %d", res);
++ if (res < 0) {
++ PRINT_ERROR("%s", "The detect() routine failed");
++ res = -EINVAL;
++ goto out_del;
++ }
++
++ PRINT_INFO("Target template %s registered successfully", vtt->name);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ scst_tgtt_sysfs_del(vtt);
++
++ mutex_lock(&scst_mutex);
++
++ mutex_lock(&scst_mutex2);
++ list_del(&vtt->scst_template_list_entry);
++ mutex_unlock(&scst_mutex2);
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++ goto out;
++}
++EXPORT_SYMBOL_GPL(__scst_register_target_template);
++
++static int scst_check_non_gpl_target_template(struct scst_tgt_template *vtt)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (vtt->task_mgmt_affected_cmds_done || vtt->threads_num ||
++ vtt->on_hw_pending_cmd_timeout) {
++ PRINT_ERROR("Not allowed functionality in non-GPL version for "
++ "target template %s", vtt->name);
++ res = -EPERM;
++ goto out;
++ }
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * __scst_register_target_template_non_gpl() - register target template,
++ * non-GPL version
++ * @vtt: target template
++ * @version: SCST_INTERFACE_VERSION version string to ensure that
++ * SCST core and the target driver use the same version of
++ * the SCST interface
++ *
++ * Description:
++ * Registers a target template and returns 0 on success or appropriate
++ * error code otherwise.
++ *
++ * Note: *vtt must be static!
++ */
++int __scst_register_target_template_non_gpl(struct scst_tgt_template *vtt,
++ const char *version)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = scst_check_non_gpl_target_template(vtt);
++ if (res != 0)
++ goto out;
++
++ res = __scst_register_target_template(vtt, version);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(__scst_register_target_template_non_gpl);
++
++/**
++ * scst_unregister_target_template() - unregister target template
++ *
++ * Target drivers supposed to behave sanely and not call register()
++ * and unregister() randomly simultaneously. Also it is supposed that
++ * no attempts to create new targets for this vtt will be done in a race
++ * with this function.
++ */
++void scst_unregister_target_template(struct scst_tgt_template *vtt)
++{
++ struct scst_tgt *tgt;
++ struct scst_tgt_template *t;
++ int found = 0;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(t, &scst_template_list, scst_template_list_entry) {
++ if (strcmp(t->name, vtt->name) == 0) {
++ found = 1;
++ break;
++ }
++ }
++ if (!found) {
++ PRINT_ERROR("Target driver %s isn't registered", vtt->name);
++ goto out_err_up;
++ }
++
++ mutex_lock(&scst_mutex2);
++ list_del(&vtt->scst_template_list_entry);
++ mutex_unlock(&scst_mutex2);
++
++ /* Wait for outstanding sysfs mgmt calls completed */
++ while (vtt->tgtt_active_sysfs_works_count > 0) {
++ mutex_unlock(&scst_mutex);
++ msleep(100);
++ mutex_lock(&scst_mutex);
++ }
++
++restart:
++ list_for_each_entry(tgt, &vtt->tgt_list, tgt_list_entry) {
++ mutex_unlock(&scst_mutex);
++ scst_unregister_target(tgt);
++ mutex_lock(&scst_mutex);
++ goto restart;
++ }
++
++ mutex_unlock(&scst_mutex);
++
++ scst_tgtt_sysfs_del(vtt);
++
++ PRINT_INFO("Target template %s unregistered successfully", vtt->name);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_err_up:
++ mutex_unlock(&scst_mutex);
++ goto out;
++}
++EXPORT_SYMBOL(scst_unregister_target_template);
++
++/**
++ * scst_register_target() - register target
++ *
++ * Registers a target for template vtt and returns new target structure on
++ * success or NULL otherwise.
++ */
++struct scst_tgt *scst_register_target(struct scst_tgt_template *vtt,
++ const char *target_name)
++{
++ struct scst_tgt *tgt, *t;
++ int rc = 0;
++
++ TRACE_ENTRY();
++
++ rc = scst_alloc_tgt(vtt, &tgt);
++ if (rc != 0)
++ goto out;
++
++ if (target_name != NULL) {
++
++ tgt->tgt_name = kstrdup(target_name, GFP_KERNEL);
++ if (tgt->tgt_name == NULL) {
++ PRINT_ERROR("Allocation of tgt name %s failed",
++ target_name);
++ rc = -ENOMEM;
++ goto out_free_tgt;
++ }
++ } else {
++ static int tgt_num; /* protected by scst_mutex */
++
++ PRINT_WARNING("Usage of autogenerated SCST target names "
++ "is deprecated and will be removed in one of the next "
++ "versions. It is strongly recommended to update target "
++ "driver %s to use hardware related persistent target "
++ "names instead", vtt->name);
++
++ tgt->tgt_name = kasprintf(GFP_KERNEL, "%s%s%d", vtt->name,
++ SCST_DEFAULT_TGT_NAME_SUFFIX, tgt_num);
++ if (tgt->tgt_name == NULL) {
++ PRINT_ERROR("Allocation of tgt name failed "
++ "(template name %s)", vtt->name);
++ rc = -ENOMEM;
++ goto out_free_tgt;
++ }
++ tgt_num++;
++ }
++
++ rc = mutex_lock_interruptible(&scst_mutex);
++ if (rc != 0)
++ goto out_free_tgt;
++
++ list_for_each_entry(t, &vtt->tgt_list, tgt_list_entry) {
++ if (strcmp(t->tgt_name, tgt->tgt_name) == 0) {
++ PRINT_ERROR("target %s already exists", tgt->tgt_name);
++ rc = -EEXIST;
++ goto out_unlock;
++ }
++ }
++
++ rc = scst_tgt_sysfs_create(tgt);
++ if (rc < 0)
++ goto out_unlock;
++
++ tgt->default_acg = scst_alloc_add_acg(tgt, tgt->tgt_name, false);
++ if (tgt->default_acg == NULL)
++ goto out_sysfs_del;
++
++ mutex_lock(&scst_mutex2);
++ list_add_tail(&tgt->tgt_list_entry, &vtt->tgt_list);
++ mutex_unlock(&scst_mutex2);
++
++ mutex_unlock(&scst_mutex);
++
++ PRINT_INFO("Target %s for template %s registered successfully",
++ tgt->tgt_name, vtt->name);
++
++ TRACE_DBG("tgt %p", tgt);
++
++out:
++ TRACE_EXIT();
++ return tgt;
++
++out_sysfs_del:
++ mutex_unlock(&scst_mutex);
++ scst_tgt_sysfs_del(tgt);
++ goto out_free_tgt;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out_free_tgt:
++ /* In case of error tgt_name will be freed in scst_free_tgt() */
++ scst_free_tgt(tgt);
++ tgt = NULL;
++ goto out;
++}
++EXPORT_SYMBOL(scst_register_target);
++
++static inline int test_sess_list(struct scst_tgt *tgt)
++{
++ int res;
++ mutex_lock(&scst_mutex);
++ res = list_empty(&tgt->sess_list);
++ mutex_unlock(&scst_mutex);
++ return res;
++}
++
++/**
++ * scst_unregister_target() - unregister target.
++ *
++ * It is supposed that no attempts to create new sessions for this
++ * target will be done in a race with this function.
++ */
++void scst_unregister_target(struct scst_tgt *tgt)
++{
++ struct scst_session *sess;
++ struct scst_tgt_template *vtt = tgt->tgtt;
++ struct scst_acg *acg, *acg_tmp;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("%s", "Calling target driver's release()");
++ tgt->tgtt->release(tgt);
++ TRACE_DBG("%s", "Target driver's release() returned");
++
++ mutex_lock(&scst_mutex);
++again:
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ if (sess->shut_phase == SCST_SESS_SPH_READY) {
++ /*
++ * Sometimes it's hard for target driver to track all
++ * its sessions (see scst_local, eg), so let's help it.
++ */
++ mutex_unlock(&scst_mutex);
++ scst_unregister_session(sess, 0, NULL);
++ mutex_lock(&scst_mutex);
++ goto again;
++ }
++ }
++ mutex_unlock(&scst_mutex);
++
++ TRACE_DBG("%s", "Waiting for sessions shutdown");
++ wait_event(tgt->unreg_waitQ, test_sess_list(tgt));
++ TRACE_DBG("%s", "wait_event() returned");
++
++ scst_suspend_activity(false);
++ mutex_lock(&scst_mutex);
++
++ mutex_lock(&scst_mutex2);
++ list_del(&tgt->tgt_list_entry);
++ mutex_unlock(&scst_mutex2);
++
++ del_timer_sync(&tgt->retry_timer);
++
++ scst_tg_tgt_remove_by_tgt(tgt);
++
++ scst_del_free_acg(tgt->default_acg);
++
++ list_for_each_entry_safe(acg, acg_tmp, &tgt->tgt_acg_list,
++ acg_list_entry) {
++ scst_del_free_acg(acg);
++ }
++
++ mutex_unlock(&scst_mutex);
++ scst_resume_activity();
++
++ scst_tgt_sysfs_del(tgt);
++
++ PRINT_INFO("Target %s for template %s unregistered successfully",
++ tgt->tgt_name, vtt->name);
++
++ scst_free_tgt(tgt);
++
++ TRACE_DBG("Unregistering tgt %p finished", tgt);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_unregister_target);
++
++int scst_get_cmd_counter(void)
++{
++ int i, res = 0;
++ for (i = 0; i < (int)ARRAY_SIZE(scst_percpu_infos); i++)
++ res += atomic_read(&scst_percpu_infos[i].cpu_cmd_count);
++ return res;
++}
++
++static int scst_susp_wait(bool interruptible)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (interruptible) {
++ res = wait_event_interruptible_timeout(scst_dev_cmd_waitQ,
++ (scst_get_cmd_counter() == 0),
++ SCST_SUSPENDING_TIMEOUT);
++ if (res <= 0) {
++ __scst_resume_activity();
++ if (res == 0)
++ res = -EBUSY;
++ } else
++ res = 0;
++ } else
++ wait_event(scst_dev_cmd_waitQ, scst_get_cmd_counter() == 0);
++
++ TRACE_MGMT_DBG("wait_event() returned %d", res);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_suspend_activity() - globally suspend any activity
++ *
++ * Description:
++ * Globally suspends any activity and doesn't return, until there are any
++ * active commands (state after SCST_CMD_STATE_INIT). If "interruptible"
++ * is true, it returns after SCST_SUSPENDING_TIMEOUT or if it was interrupted
++ * by a signal with the corresponding error status < 0. If "interruptible"
++ * is false, it will wait virtually forever. On success returns 0.
++ *
++ * New arriving commands stay in the suspended state until
++ * scst_resume_activity() is called.
++ */
++int scst_suspend_activity(bool interruptible)
++{
++ int res = 0;
++ bool rep = false;
++
++ TRACE_ENTRY();
++
++ rwlock_acquire_read(&scst_suspend_dep_map, 0, 0, _RET_IP_);
++
++ if (interruptible) {
++ if (mutex_lock_interruptible(&scst_suspend_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++ } else
++ mutex_lock(&scst_suspend_mutex);
++
++ TRACE_MGMT_DBG("suspend_count %d", suspend_count);
++ suspend_count++;
++ if (suspend_count > 1)
++ goto out_up;
++
++ set_bit(SCST_FLAG_SUSPENDING, &scst_flags);
++ set_bit(SCST_FLAG_SUSPENDED, &scst_flags);
++ /*
++ * Assignment of SCST_FLAG_SUSPENDING and SCST_FLAG_SUSPENDED must be
++ * ordered with cpu_cmd_count in scst_get(). Otherwise lockless logic in
++ * scst_translate_lun() and scst_mgmt_translate_lun() won't work.
++ */
++ smp_mb__after_set_bit();
++
++ /*
++ * See comment in scst_user.c::dev_user_task_mgmt_fn() for more
++ * information about scst_user behavior.
++ *
++ * ToDo: make the global suspending unneeded (switch to per-device
++ * reference counting? That would mean to switch off from lockless
++ * implementation of scst_translate_lun().. )
++ */
++
++ if (scst_get_cmd_counter() != 0) {
++ PRINT_INFO("Waiting for %d active commands to complete... This "
++ "might take few minutes for disks or few hours for "
++ "tapes, if you use long executed commands, like "
++ "REWIND or FORMAT. In case, if you have a hung user "
++ "space device (i.e. made using scst_user module) not "
++ "responding to any commands, if might take virtually "
++ "forever until the corresponding user space "
++ "program recovers and starts responding or gets "
++ "killed.", scst_get_cmd_counter());
++ rep = true;
++
++ lock_contended(&scst_suspend_dep_map, _RET_IP_);
++ }
++
++ res = scst_susp_wait(interruptible);
++ if (res != 0)
++ goto out_clear;
++
++ clear_bit(SCST_FLAG_SUSPENDING, &scst_flags);
++ /* See comment about smp_mb() above */
++ smp_mb__after_clear_bit();
++
++ TRACE_MGMT_DBG("Waiting for %d active commands finally to complete",
++ scst_get_cmd_counter());
++
++ res = scst_susp_wait(interruptible);
++ if (res != 0)
++ goto out_clear;
++
++ if (rep)
++ PRINT_INFO("%s", "All active commands completed");
++
++out_up:
++ mutex_unlock(&scst_suspend_mutex);
++
++out:
++ if (res == 0)
++ lock_acquired(&scst_suspend_dep_map, _RET_IP_);
++ else
++ rwlock_release(&scst_suspend_dep_map, 1, _RET_IP_);
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_clear:
++ clear_bit(SCST_FLAG_SUSPENDING, &scst_flags);
++ /* See comment about smp_mb() above */
++ smp_mb__after_clear_bit();
++ goto out_up;
++}
++EXPORT_SYMBOL_GPL(scst_suspend_activity);
++
++static void __scst_resume_activity(void)
++{
++ struct scst_cmd_threads *l;
++
++ TRACE_ENTRY();
++
++ suspend_count--;
++ TRACE_MGMT_DBG("suspend_count %d left", suspend_count);
++ if (suspend_count > 0)
++ goto out;
++
++ clear_bit(SCST_FLAG_SUSPENDED, &scst_flags);
++ /*
++ * The barrier is needed to make sure all woken up threads see the
++ * cleared flag. Not sure if it's really needed, but let's be safe.
++ */
++ smp_mb__after_clear_bit();
++
++ list_for_each_entry(l, &scst_cmd_threads_list, lists_list_entry) {
++ wake_up_all(&l->cmd_list_waitQ);
++ }
++ wake_up_all(&scst_init_cmd_list_waitQ);
++
++ spin_lock_irq(&scst_mcmd_lock);
++ if (!list_empty(&scst_delayed_mgmt_cmd_list)) {
++ struct scst_mgmt_cmd *m;
++ m = list_entry(scst_delayed_mgmt_cmd_list.next, typeof(*m),
++ mgmt_cmd_list_entry);
++ TRACE_MGMT_DBG("Moving delayed mgmt cmd %p to head of active "
++ "mgmt cmd list", m);
++ list_move(&m->mgmt_cmd_list_entry, &scst_active_mgmt_cmd_list);
++ }
++ spin_unlock_irq(&scst_mcmd_lock);
++ wake_up_all(&scst_mgmt_cmd_list_waitQ);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_resume_activity() - globally resume all activities
++ *
++ * Resumes suspended by scst_suspend_activity() activities.
++ */
++void scst_resume_activity(void)
++{
++ TRACE_ENTRY();
++
++ rwlock_release(&scst_suspend_dep_map, 1, _RET_IP_);
++
++ mutex_lock(&scst_suspend_mutex);
++ __scst_resume_activity();
++ mutex_unlock(&scst_suspend_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_resume_activity);
++
++static int scst_register_device(struct scsi_device *scsidp)
++{
++ int res;
++ struct scst_device *dev, *d;
++
++ TRACE_ENTRY();
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out;
++
++ res = scst_alloc_device(GFP_KERNEL, &dev);
++ if (res != 0)
++ goto out_unlock;
++
++ dev->type = scsidp->type;
++
++ dev->virt_name = kasprintf(GFP_KERNEL, "%d:%d:%d:%d",
++ scsidp->host->host_no,
++ scsidp->channel, scsidp->id, scsidp->lun);
++ if (dev->virt_name == NULL) {
++ PRINT_ERROR("%s", "Unable to alloc device name");
++ res = -ENOMEM;
++ goto out_free_dev;
++ }
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if (strcmp(d->virt_name, dev->virt_name) == 0) {
++ PRINT_ERROR("Device %s already exists", dev->virt_name);
++ res = -EEXIST;
++ goto out_free_dev;
++ }
++ }
++
++ dev->scsi_dev = scsidp;
++
++ list_add_tail(&dev->dev_list_entry, &scst_dev_list);
++
++ mutex_unlock(&scst_mutex);
++
++ res = scst_dev_sysfs_create(dev);
++ if (res != 0)
++ goto out_del;
++
++ PRINT_INFO("Attached to scsi%d, channel %d, id %d, lun %d, "
++ "type %d", scsidp->host->host_no, scsidp->channel,
++ scsidp->id, scsidp->lun, scsidp->type);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ list_del(&dev->dev_list_entry);
++
++out_free_dev:
++ scst_free_device(dev);
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++ goto out;
++}
++
++static void scst_unregister_device(struct scsi_device *scsidp)
++{
++ struct scst_device *d, *dev = NULL;
++ struct scst_acg_dev *acg_dev, *aa;
++
++ TRACE_ENTRY();
++
++ scst_suspend_activity(false);
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if (d->scsi_dev == scsidp) {
++ dev = d;
++ TRACE_DBG("Device %p found", dev);
++ break;
++ }
++ }
++ if (dev == NULL) {
++ PRINT_ERROR("SCST device for SCSI device %d:%d:%d:%d not found",
++ scsidp->host->host_no, scsidp->channel, scsidp->id,
++ scsidp->lun);
++ goto out_unlock;
++ }
++
++ dev->dev_unregistering = 1;
++
++ list_del(&dev->dev_list_entry);
++
++ scst_dg_dev_remove_by_dev(dev);
++
++ scst_assign_dev_handler(dev, &scst_null_devtype);
++
++ list_for_each_entry_safe(acg_dev, aa, &dev->dev_acg_dev_list,
++ dev_acg_dev_list_entry) {
++ scst_acg_del_lun(acg_dev->acg, acg_dev->lun, true);
++ }
++
++ mutex_unlock(&scst_mutex);
++
++ scst_resume_activity();
++
++ scst_dev_sysfs_del(dev);
++
++ PRINT_INFO("Detached from scsi%d, channel %d, id %d, lun %d, type %d",
++ scsidp->host->host_no, scsidp->channel, scsidp->id,
++ scsidp->lun, scsidp->type);
++
++ scst_free_device(dev);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++ scst_resume_activity();
++ goto out;
++}
++
++static int scst_dev_handler_check(struct scst_dev_type *dev_handler)
++{
++ int res = 0;
++
++ if (dev_handler->parse == NULL) {
++ PRINT_ERROR("scst dev handler %s must have "
++ "parse() method.", dev_handler->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (((dev_handler->add_device != NULL) &&
++ (dev_handler->del_device == NULL)) ||
++ ((dev_handler->add_device == NULL) &&
++ (dev_handler->del_device != NULL))) {
++ PRINT_ERROR("Dev handler %s must either define both "
++ "add_device() and del_device(), or none.",
++ dev_handler->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (dev_handler->alloc_data_buf == NULL)
++ dev_handler->alloc_data_buf_atomic = 1;
++
++ if (dev_handler->dev_done == NULL)
++ dev_handler->dev_done_atomic = 1;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_check_device_name(const char *dev_name)
++{
++ int res = 0;
++
++ if (strchr(dev_name, '/') != NULL) {
++ PRINT_ERROR("Dev name %s contains illegal character '/'",
++ dev_name);
++ res = -EINVAL;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_register_virtual_device() - register a virtual device.
++ * @dev_handler: the device's device handler
++ * @dev_name: the new device name, NULL-terminated string. Must be uniq
++ * among all virtual devices in the system.
++ *
++ * Registers a virtual device and returns ID assigned to the device on
++ * success, or negative value otherwise
++ */
++int scst_register_virtual_device(struct scst_dev_type *dev_handler,
++ const char *dev_name)
++{
++ int res;
++ struct scst_device *dev, *d;
++ bool sysfs_del = false;
++
++ TRACE_ENTRY();
++
++ if (dev_handler == NULL) {
++ PRINT_ERROR("%s: valid device handler must be supplied",
++ __func__);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (dev_name == NULL) {
++ PRINT_ERROR("%s: device name must be non-NULL", __func__);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_check_device_name(dev_name);
++ if (res != 0)
++ goto out;
++
++ res = scst_dev_handler_check(dev_handler);
++ if (res != 0)
++ goto out;
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out_resume;
++
++ res = scst_alloc_device(GFP_KERNEL, &dev);
++ if (res != 0)
++ goto out_unlock;
++
++ dev->type = dev_handler->type;
++ dev->scsi_dev = NULL;
++ dev->virt_name = kstrdup(dev_name, GFP_KERNEL);
++ if (dev->virt_name == NULL) {
++ PRINT_ERROR("Unable to allocate virt_name for dev %s",
++ dev_name);
++ res = -ENOMEM;
++ goto out_free_dev;
++ }
++
++ while (1) {
++ dev->virt_id = scst_virt_dev_last_id++;
++ if (dev->virt_id > 0)
++ break;
++ scst_virt_dev_last_id = 1;
++ }
++
++ res = dev->virt_id;
++
++ res = scst_pr_init_dev(dev);
++ if (res != 0)
++ goto out_free_dev;
++
++ /*
++ * We can drop scst_mutex, because we have not yet added the dev in
++ * scst_dev_list, so it "doesn't exist" yet.
++ */
++ mutex_unlock(&scst_mutex);
++
++ res = scst_dev_sysfs_create(dev);
++ if (res != 0)
++ goto out_lock_pr_clear_dev;
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if (strcmp(d->virt_name, dev_name) == 0) {
++ PRINT_ERROR("Device %s already exists", dev_name);
++ res = -EEXIST;
++ sysfs_del = true;
++ goto out_pr_clear_dev;
++ }
++ }
++
++ res = scst_assign_dev_handler(dev, dev_handler);
++ if (res != 0) {
++ sysfs_del = true;
++ goto out_pr_clear_dev;
++ }
++
++ list_add_tail(&dev->dev_list_entry, &scst_dev_list);
++
++ mutex_unlock(&scst_mutex);
++ scst_resume_activity();
++
++ res = dev->virt_id;
++
++ PRINT_INFO("Attached to virtual device %s (id %d)", dev_name, res);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_lock_pr_clear_dev:
++ mutex_lock(&scst_mutex);
++
++out_pr_clear_dev:
++ scst_pr_clear_dev(dev);
++
++out_free_dev:
++ mutex_unlock(&scst_mutex);
++ if (sysfs_del)
++ scst_dev_sysfs_del(dev);
++ scst_free_device(dev);
++ goto out_resume;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out_resume:
++ scst_resume_activity();
++ goto out;
++}
++EXPORT_SYMBOL_GPL(scst_register_virtual_device);
++
++/**
++ * scst_unregister_virtual_device() - unegister a virtual device.
++ * @id: the device's ID, returned by the registration function
++ */
++void scst_unregister_virtual_device(int id)
++{
++ struct scst_device *d, *dev = NULL;
++ struct scst_acg_dev *acg_dev, *aa;
++
++ TRACE_ENTRY();
++
++ scst_suspend_activity(false);
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if (d->virt_id == id) {
++ dev = d;
++ TRACE_DBG("Virtual device %p (id %d) found", dev, id);
++ break;
++ }
++ }
++ if (dev == NULL) {
++ PRINT_ERROR("Virtual device (id %d) not found", id);
++ goto out_unlock;
++ }
++
++ dev->dev_unregistering = 1;
++
++ list_del(&dev->dev_list_entry);
++
++ scst_pr_clear_dev(dev);
++
++ scst_dg_dev_remove_by_dev(dev);
++
++ scst_assign_dev_handler(dev, &scst_null_devtype);
++
++ list_for_each_entry_safe(acg_dev, aa, &dev->dev_acg_dev_list,
++ dev_acg_dev_list_entry) {
++ scst_acg_del_lun(acg_dev->acg, acg_dev->lun, true);
++ }
++
++ mutex_unlock(&scst_mutex);
++ scst_resume_activity();
++
++ scst_dev_sysfs_del(dev);
++
++ PRINT_INFO("Detached from virtual device %s (id %d)",
++ dev->virt_name, dev->virt_id);
++
++ scst_free_device(dev);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++ scst_resume_activity();
++ goto out;
++}
++EXPORT_SYMBOL_GPL(scst_unregister_virtual_device);
++
++/**
++ * __scst_register_dev_driver() - register pass-through dev handler driver
++ * @dev_type: dev handler template
++ * @version: SCST_INTERFACE_VERSION version string to ensure that
++ * SCST core and the dev handler use the same version of
++ * the SCST interface
++ *
++ * Description:
++ * Registers a pass-through dev handler driver. Returns 0 on success
++ * or appropriate error code otherwise.
++ */
++int __scst_register_dev_driver(struct scst_dev_type *dev_type,
++ const char *version)
++{
++ int res, exist;
++ struct scst_dev_type *dt;
++
++ TRACE_ENTRY();
++
++ res = -EINVAL;
++ if (strcmp(version, SCST_INTERFACE_VERSION) != 0) {
++ PRINT_ERROR("Incorrect version of dev handler %s",
++ dev_type->name);
++ goto out;
++ }
++
++ res = scst_dev_handler_check(dev_type);
++ if (res != 0)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out;
++
++ exist = 0;
++ list_for_each_entry(dt, &scst_dev_type_list, dev_type_list_entry) {
++ if (strcmp(dt->name, dev_type->name) == 0) {
++ PRINT_ERROR("Device type handler \"%s\" already "
++ "exists", dt->name);
++ exist = 1;
++ break;
++ }
++ }
++ if (exist)
++ goto out_unlock;
++
++ list_add_tail(&dev_type->dev_type_list_entry, &scst_dev_type_list);
++
++ mutex_unlock(&scst_mutex);
++
++ res = scst_devt_sysfs_create(dev_type);
++ if (res < 0)
++ goto out;
++
++ PRINT_INFO("Device handler \"%s\" for type %d registered "
++ "successfully", dev_type->name, dev_type->type);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++ goto out;
++}
++EXPORT_SYMBOL_GPL(__scst_register_dev_driver);
++
++/**
++ * scst_unregister_dev_driver() - unregister pass-through dev handler driver
++ */
++void scst_unregister_dev_driver(struct scst_dev_type *dev_type)
++{
++ struct scst_device *dev;
++ struct scst_dev_type *dt;
++ int found = 0;
++
++ TRACE_ENTRY();
++
++ scst_suspend_activity(false);
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(dt, &scst_dev_type_list, dev_type_list_entry) {
++ if (strcmp(dt->name, dev_type->name) == 0) {
++ found = 1;
++ break;
++ }
++ }
++ if (!found) {
++ PRINT_ERROR("Dev handler \"%s\" isn't registered",
++ dev_type->name);
++ goto out_up;
++ }
++
++ list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
++ if (dev->handler == dev_type) {
++ scst_assign_dev_handler(dev, &scst_null_devtype);
++ TRACE_DBG("Dev handler removed from device %p", dev);
++ }
++ }
++
++ list_del(&dev_type->dev_type_list_entry);
++
++ mutex_unlock(&scst_mutex);
++ scst_resume_activity();
++
++ scst_devt_sysfs_del(dev_type);
++
++ PRINT_INFO("Device handler \"%s\" for type %d unloaded",
++ dev_type->name, dev_type->type);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_up:
++ mutex_unlock(&scst_mutex);
++ scst_resume_activity();
++ goto out;
++}
++EXPORT_SYMBOL_GPL(scst_unregister_dev_driver);
++
++/**
++ * __scst_register_virtual_dev_driver() - register virtual dev handler driver
++ * @dev_type: dev handler template
++ * @version: SCST_INTERFACE_VERSION version string to ensure that
++ * SCST core and the dev handler use the same version of
++ * the SCST interface
++ *
++ * Description:
++ * Registers a virtual dev handler driver. Returns 0 on success or
++ * appropriate error code otherwise.
++ */
++int __scst_register_virtual_dev_driver(struct scst_dev_type *dev_type,
++ const char *version)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (strcmp(version, SCST_INTERFACE_VERSION) != 0) {
++ PRINT_ERROR("Incorrect version of virtual dev handler %s",
++ dev_type->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_dev_handler_check(dev_type);
++ if (res != 0)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out;
++ list_add_tail(&dev_type->dev_type_list_entry, &scst_virtual_dev_type_list);
++ mutex_unlock(&scst_mutex);
++
++ res = scst_devt_sysfs_create(dev_type);
++ if (res < 0)
++ goto out;
++
++ if (dev_type->type != -1) {
++ PRINT_INFO("Virtual device handler %s for type %d "
++ "registered successfully", dev_type->name,
++ dev_type->type);
++ } else {
++ PRINT_INFO("Virtual device handler \"%s\" registered "
++ "successfully", dev_type->name);
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(__scst_register_virtual_dev_driver);
++
++/**
++ * scst_unregister_virtual_dev_driver() - unregister virtual dev driver
++ */
++void scst_unregister_virtual_dev_driver(struct scst_dev_type *dev_type)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ /* Disable sysfs mgmt calls (e.g. addition of new devices) */
++ list_del(&dev_type->dev_type_list_entry);
++
++ /* Wait for outstanding sysfs mgmt calls completed */
++ while (dev_type->devt_active_sysfs_works_count > 0) {
++ mutex_unlock(&scst_mutex);
++ msleep(100);
++ mutex_lock(&scst_mutex);
++ }
++
++ mutex_unlock(&scst_mutex);
++
++ scst_devt_sysfs_del(dev_type);
++
++ PRINT_INFO("Device handler \"%s\" unloaded", dev_type->name);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_unregister_virtual_dev_driver);
++
++/* scst_mutex supposed to be held */
++int scst_add_threads(struct scst_cmd_threads *cmd_threads,
++ struct scst_device *dev, struct scst_tgt_dev *tgt_dev, int num)
++{
++ int res = 0, i;
++ struct scst_cmd_thread_t *thr;
++ int n = 0, tgt_dev_num = 0;
++
++ TRACE_ENTRY();
++
++ if (num == 0) {
++ res = 0;
++ goto out;
++ }
++
++ list_for_each_entry(thr, &cmd_threads->threads_list, thread_list_entry) {
++ n++;
++ }
++
++ TRACE_DBG("cmd_threads %p, dev %s, tgt_dev %p, num %d, n %d",
++ cmd_threads, dev ? dev->virt_name : NULL, tgt_dev, num, n);
++
++ if (tgt_dev != NULL) {
++ struct scst_tgt_dev *t;
++ list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if (t == tgt_dev)
++ break;
++ tgt_dev_num++;
++ }
++ }
++
++ for (i = 0; i < num; i++) {
++ thr = kmalloc(sizeof(*thr), GFP_KERNEL);
++ if (!thr) {
++ res = -ENOMEM;
++ PRINT_ERROR("Fail to allocate thr %d", res);
++ goto out_wait;
++ }
++
++ if (dev != NULL) {
++ thr->cmd_thread = kthread_create(scst_cmd_thread,
++ cmd_threads, "%.13s%d", dev->virt_name, n++);
++ } else if (tgt_dev != NULL) {
++ thr->cmd_thread = kthread_create(scst_cmd_thread,
++ cmd_threads, "%.10s%d_%d",
++ tgt_dev->dev->virt_name, tgt_dev_num, n++);
++ } else
++ thr->cmd_thread = kthread_create(scst_cmd_thread,
++ cmd_threads, "scstd%d", n++);
++
++ if (IS_ERR(thr->cmd_thread)) {
++ res = PTR_ERR(thr->cmd_thread);
++ PRINT_ERROR("kthread_create() failed: %d", res);
++ kfree(thr);
++ goto out_wait;
++ }
++
++ if (tgt_dev != NULL) {
++ int rc;
++ /*
++ * sess->acg can be NULL here, if called from
++ * scst_check_reassign_sess()!
++ */
++ rc = set_cpus_allowed_ptr(thr->cmd_thread,
++ &tgt_dev->acg_dev->acg->acg_cpu_mask);
++ if (rc != 0)
++ PRINT_ERROR("Setting CPU affinity failed: "
++ "%d", rc);
++ }
++
++ list_add(&thr->thread_list_entry, &cmd_threads->threads_list);
++ cmd_threads->nr_threads++;
++
++ TRACE_DBG("Added thr %p to threads list (nr_threads %d, n %d)",
++ thr, cmd_threads->nr_threads, n);
++
++ wake_up_process(thr->cmd_thread);
++ }
++
++out_wait:
++ if (i > 0 && cmd_threads != &scst_main_cmd_threads) {
++ /*
++ * Wait for io_context gets initialized to avoid possible races
++ * for it from the sharing it tgt_devs.
++ */
++ while (!*(volatile bool*)&cmd_threads->io_context_ready) {
++ TRACE_DBG("Waiting for io_context for cmd_threads %p "
++ "initialized", cmd_threads);
++ msleep(50);
++ }
++ smp_rmb();
++ }
++
++ if (res != 0)
++ scst_del_threads(cmd_threads, i);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_mutex supposed to be held */
++void scst_del_threads(struct scst_cmd_threads *cmd_threads, int num)
++{
++ struct scst_cmd_thread_t *ct, *tmp;
++
++ TRACE_ENTRY();
++
++ if (num == 0)
++ goto out;
++
++ list_for_each_entry_safe_reverse(ct, tmp, &cmd_threads->threads_list,
++ thread_list_entry) {
++ int rc;
++ struct scst_device *dev;
++
++ rc = kthread_stop(ct->cmd_thread);
++ if (rc != 0 && rc != -EINTR)
++ TRACE_MGMT_DBG("kthread_stop() failed: %d", rc);
++
++ list_del(&ct->thread_list_entry);
++
++ list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ scst_del_thr_data(tgt_dev, ct->cmd_thread);
++ }
++ }
++
++ kfree(ct);
++
++ cmd_threads->nr_threads--;
++
++ --num;
++ if (num == 0)
++ break;
++ }
++
++ EXTRACHECKS_BUG_ON((cmd_threads->nr_threads == 0) &&
++ (cmd_threads->io_context != NULL));
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++void scst_stop_dev_threads(struct scst_device *dev)
++{
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ scst_tgt_dev_stop_threads(tgt_dev);
++ }
++
++ if ((dev->threads_num > 0) &&
++ (dev->threads_pool_type == SCST_THREADS_POOL_SHARED))
++ scst_del_threads(&dev->dev_cmd_threads, -1);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++int scst_create_dev_threads(struct scst_device *dev)
++{
++ int res = 0;
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ res = scst_tgt_dev_setup_threads(tgt_dev);
++ if (res != 0)
++ goto out_err;
++ }
++
++ if ((dev->threads_num > 0) &&
++ (dev->threads_pool_type == SCST_THREADS_POOL_SHARED)) {
++ res = scst_add_threads(&dev->dev_cmd_threads, dev, NULL,
++ dev->threads_num);
++ if (res != 0)
++ goto out_err;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_err:
++ scst_stop_dev_threads(dev);
++ goto out;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++int scst_assign_dev_handler(struct scst_device *dev,
++ struct scst_dev_type *handler)
++{
++ int res = 0;
++ struct scst_tgt_dev *tgt_dev;
++ LIST_HEAD(attached_tgt_devs);
++
++ TRACE_ENTRY();
++
++ BUG_ON(handler == NULL);
++
++ if (dev->handler == handler)
++ goto out;
++
++ if (dev->handler == NULL)
++ goto assign;
++
++ if (dev->handler->detach_tgt) {
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ TRACE_DBG("Calling dev handler's detach_tgt(%p)",
++ tgt_dev);
++ dev->handler->detach_tgt(tgt_dev);
++ TRACE_DBG("%s", "Dev handler's detach_tgt() returned");
++ }
++ }
++
++ /*
++ * devt_dev sysfs must be created AFTER attach() and deleted BEFORE
++ * detach() to avoid calls from sysfs for not yet ready or already dead
++ * objects.
++ */
++ scst_devt_dev_sysfs_del(dev);
++
++ if (dev->handler->detach) {
++ TRACE_DBG("%s", "Calling dev handler's detach()");
++ dev->handler->detach(dev);
++ TRACE_DBG("%s", "Old handler's detach() returned");
++ }
++
++ scst_stop_dev_threads(dev);
++
++assign:
++ dev->handler = handler;
++
++ if (handler == NULL)
++ goto out;
++
++ dev->threads_num = handler->threads_num;
++ dev->threads_pool_type = handler->threads_pool_type;
++
++ if (handler->attach) {
++ TRACE_DBG("Calling new dev handler's attach(%p)", dev);
++ res = handler->attach(dev);
++ TRACE_DBG("New dev handler's attach() returned %d", res);
++ if (res != 0) {
++ PRINT_ERROR("New device handler's %s attach() "
++ "failed: %d", handler->name, res);
++ goto out;
++ }
++ }
++
++ res = scst_devt_dev_sysfs_create(dev);
++ if (res != 0)
++ goto out_detach;
++
++ if (handler->attach_tgt) {
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ TRACE_DBG("Calling dev handler's attach_tgt(%p)",
++ tgt_dev);
++ res = handler->attach_tgt(tgt_dev);
++ TRACE_DBG("%s", "Dev handler's attach_tgt() returned");
++ if (res != 0) {
++ PRINT_ERROR("Device handler's %s attach_tgt() "
++ "failed: %d", handler->name, res);
++ goto out_err_remove_sysfs;
++ }
++ list_add_tail(&tgt_dev->extra_tgt_dev_list_entry,
++ &attached_tgt_devs);
++ }
++ }
++
++ res = scst_create_dev_threads(dev);
++ if (res != 0)
++ goto out_err_detach_tgt;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_err_detach_tgt:
++ if (handler && handler->detach_tgt) {
++ list_for_each_entry(tgt_dev, &attached_tgt_devs,
++ extra_tgt_dev_list_entry) {
++ TRACE_DBG("Calling handler's detach_tgt(%p)",
++ tgt_dev);
++ handler->detach_tgt(tgt_dev);
++ TRACE_DBG("%s", "Handler's detach_tgt() returned");
++ }
++ }
++
++out_err_remove_sysfs:
++ scst_devt_dev_sysfs_del(dev);
++
++out_detach:
++ if (handler && handler->detach) {
++ TRACE_DBG("%s", "Calling handler's detach()");
++ handler->detach(dev);
++ TRACE_DBG("%s", "Handler's detach() returned");
++ }
++
++ dev->handler = &scst_null_devtype;
++ dev->threads_num = scst_null_devtype.threads_num;
++ dev->threads_pool_type = scst_null_devtype.threads_pool_type;
++ goto out;
++}
++
++/**
++ * scst_init_threads() - initialize SCST processing threads pool
++ *
++ * Initializes scst_cmd_threads structure
++ */
++void scst_init_threads(struct scst_cmd_threads *cmd_threads)
++{
++ TRACE_ENTRY();
++
++ spin_lock_init(&cmd_threads->cmd_list_lock);
++ INIT_LIST_HEAD(&cmd_threads->active_cmd_list);
++ init_waitqueue_head(&cmd_threads->cmd_list_waitQ);
++ INIT_LIST_HEAD(&cmd_threads->threads_list);
++ mutex_init(&cmd_threads->io_context_mutex);
++
++ mutex_lock(&scst_suspend_mutex);
++ list_add_tail(&cmd_threads->lists_list_entry,
++ &scst_cmd_threads_list);
++ mutex_unlock(&scst_suspend_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_init_threads);
++
++/**
++ * scst_deinit_threads() - deinitialize SCST processing threads pool
++ *
++ * Deinitializes scst_cmd_threads structure
++ */
++void scst_deinit_threads(struct scst_cmd_threads *cmd_threads)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_suspend_mutex);
++ list_del(&cmd_threads->lists_list_entry);
++ mutex_unlock(&scst_suspend_mutex);
++
++ BUG_ON(cmd_threads->io_context);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_deinit_threads);
++
++static void scst_stop_global_threads(void)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ scst_del_threads(&scst_main_cmd_threads, -1);
++
++ if (scst_mgmt_cmd_thread)
++ kthread_stop(scst_mgmt_cmd_thread);
++ if (scst_mgmt_thread)
++ kthread_stop(scst_mgmt_thread);
++ if (scst_init_cmd_thread)
++ kthread_stop(scst_init_cmd_thread);
++
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* It does NOT stop ran threads on error! */
++static int scst_start_global_threads(int num)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ res = scst_add_threads(&scst_main_cmd_threads, NULL, NULL, num);
++ if (res < 0)
++ goto out_unlock;
++
++ scst_init_cmd_thread = kthread_run(scst_init_thread,
++ NULL, "scst_initd");
++ if (IS_ERR(scst_init_cmd_thread)) {
++ res = PTR_ERR(scst_init_cmd_thread);
++ PRINT_ERROR("kthread_create() for init cmd failed: %d", res);
++ scst_init_cmd_thread = NULL;
++ goto out_unlock;
++ }
++
++ scst_mgmt_cmd_thread = kthread_run(scst_tm_thread,
++ NULL, "scsi_tm");
++ if (IS_ERR(scst_mgmt_cmd_thread)) {
++ res = PTR_ERR(scst_mgmt_cmd_thread);
++ PRINT_ERROR("kthread_create() for TM failed: %d", res);
++ scst_mgmt_cmd_thread = NULL;
++ goto out_unlock;
++ }
++
++ scst_mgmt_thread = kthread_run(scst_global_mgmt_thread,
++ NULL, "scst_mgmtd");
++ if (IS_ERR(scst_mgmt_thread)) {
++ res = PTR_ERR(scst_mgmt_thread);
++ PRINT_ERROR("kthread_create() for mgmt failed: %d", res);
++ scst_mgmt_thread = NULL;
++ goto out_unlock;
++ }
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_get_setup_id() - return SCST setup ID
++ *
++ * Returns SCST setup ID. This ID can be used for multiple
++ * setups with the same configuration.
++ */
++unsigned int scst_get_setup_id(void)
++{
++ return scst_setup_id;
++}
++EXPORT_SYMBOL_GPL(scst_get_setup_id);
++
++static int scst_add(struct device *cdev, struct class_interface *intf)
++{
++ struct scsi_device *scsidp;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ scsidp = to_scsi_device(cdev->parent);
++
++ if ((scsidp->host->hostt->name == NULL) ||
++ (strcmp(scsidp->host->hostt->name, SCST_LOCAL_NAME) != 0))
++ res = scst_register_device(scsidp);
++
++ TRACE_EXIT();
++ return res;
++}
++
++static void scst_remove(struct device *cdev, struct class_interface *intf)
++{
++ struct scsi_device *scsidp;
++
++ TRACE_ENTRY();
++
++ scsidp = to_scsi_device(cdev->parent);
++
++ if ((scsidp->host->hostt->name == NULL) ||
++ (strcmp(scsidp->host->hostt->name, SCST_LOCAL_NAME) != 0))
++ scst_unregister_device(scsidp);
++
++ TRACE_EXIT();
++ return;
++}
++
++static struct class_interface scst_interface = {
++ .add_dev = scst_add,
++ .remove_dev = scst_remove,
++};
++
++static void __init scst_print_config(void)
++{
++ char buf[128];
++ int i, j;
++
++ i = snprintf(buf, sizeof(buf), "Enabled features: ");
++ j = i;
++
++#ifdef CONFIG_SCST_STRICT_SERIALIZING
++ i += snprintf(&buf[i], sizeof(buf) - i, "STRICT_SERIALIZING");
++#endif
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sEXTRACHECKS",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_TRACING
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sTRACING",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_TM
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG_TM",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_RETRY
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG_RETRY",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_OOM
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG_OOM",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_SN
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sDEBUG_SN",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_USE_EXPECTED_VALUES
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sUSE_EXPECTED_VALUES",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ i += snprintf(&buf[i], sizeof(buf) - i,
++ "%sTEST_IO_IN_SIRQ",
++ (j == i) ? "" : ", ");
++#endif
++
++#ifdef CONFIG_SCST_STRICT_SECURITY
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sSTRICT_SECURITY",
++ (j == i) ? "" : ", ");
++#endif
++
++ if (j != i)
++ PRINT_INFO("%s", buf);
++}
++
++static int __init init_scst(void)
++{
++ int res, i;
++ int scst_num_cpus;
++
++ TRACE_ENTRY();
++
++ {
++ struct scsi_sense_hdr *shdr;
++ struct scst_order_data *o;
++ struct scst_cmd *c;
++ BUILD_BUG_ON(SCST_SENSE_BUFFERSIZE < sizeof(*shdr));
++ BUILD_BUG_ON(sizeof(o->curr_sn) != sizeof(o->expected_sn));
++ BUILD_BUG_ON(sizeof(c->sn) != sizeof(o->expected_sn));
++ }
++
++ mutex_init(&scst_mutex);
++ mutex_init(&scst_mutex2);
++ INIT_LIST_HEAD(&scst_template_list);
++ INIT_LIST_HEAD(&scst_dev_list);
++ INIT_LIST_HEAD(&scst_dev_type_list);
++ INIT_LIST_HEAD(&scst_virtual_dev_type_list);
++ spin_lock_init(&scst_main_lock);
++ spin_lock_init(&scst_init_lock);
++ init_waitqueue_head(&scst_init_cmd_list_waitQ);
++ INIT_LIST_HEAD(&scst_init_cmd_list);
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ scst_trace_flag = SCST_DEFAULT_LOG_FLAGS;
++#endif
++ spin_lock_init(&scst_mcmd_lock);
++ INIT_LIST_HEAD(&scst_active_mgmt_cmd_list);
++ INIT_LIST_HEAD(&scst_delayed_mgmt_cmd_list);
++ init_waitqueue_head(&scst_mgmt_cmd_list_waitQ);
++ init_waitqueue_head(&scst_mgmt_waitQ);
++ spin_lock_init(&scst_mgmt_lock);
++ INIT_LIST_HEAD(&scst_sess_init_list);
++ INIT_LIST_HEAD(&scst_sess_shut_list);
++ init_waitqueue_head(&scst_dev_cmd_waitQ);
++ mutex_init(&scst_suspend_mutex);
++ INIT_LIST_HEAD(&scst_cmd_threads_list);
++ cpus_setall(default_cpu_mask);
++
++ scst_init_threads(&scst_main_cmd_threads);
++
++ res = scst_lib_init();
++ if (res != 0)
++ goto out_deinit_threads;
++
++ scst_num_cpus = num_online_cpus();
++
++ /* ToDo: register_cpu_notifier() */
++
++ if (scst_threads == 0)
++ scst_threads = scst_num_cpus;
++
++ if (scst_threads < 1) {
++ PRINT_ERROR("%s", "scst_threads can not be less than 1");
++ scst_threads = scst_num_cpus;
++ }
++
++#define INIT_CACHEP(p, s, o) do { \
++ p = KMEM_CACHE(s, SCST_SLAB_FLAGS); \
++ TRACE_MEM("Slab create: %s at %p size %zd", #s, p, \
++ sizeof(struct s)); \
++ if (p == NULL) { \
++ res = -ENOMEM; \
++ goto o; \
++ } \
++ } while (0)
++
++ INIT_CACHEP(scst_mgmt_cachep, scst_mgmt_cmd, out_lib_exit);
++ INIT_CACHEP(scst_mgmt_stub_cachep, scst_mgmt_cmd_stub,
++ out_destroy_mgmt_cache);
++ INIT_CACHEP(scst_ua_cachep, scst_tgt_dev_UA,
++ out_destroy_mgmt_stub_cache);
++ {
++ struct scst_sense { uint8_t s[SCST_SENSE_BUFFERSIZE]; };
++ INIT_CACHEP(scst_sense_cachep, scst_sense,
++ out_destroy_ua_cache);
++ }
++ INIT_CACHEP(scst_aen_cachep, scst_aen, out_destroy_sense_cache);
++ INIT_CACHEP(scst_cmd_cachep, scst_cmd, out_destroy_aen_cache);
++ INIT_CACHEP(scst_sess_cachep, scst_session, out_destroy_cmd_cache);
++ INIT_CACHEP(scst_tgtd_cachep, scst_tgt_dev, out_destroy_sess_cache);
++ INIT_CACHEP(scst_acgd_cachep, scst_acg_dev, out_destroy_tgt_cache);
++
++ scst_mgmt_mempool = mempool_create(64, mempool_alloc_slab,
++ mempool_free_slab, scst_mgmt_cachep);
++ if (scst_mgmt_mempool == NULL) {
++ res = -ENOMEM;
++ goto out_destroy_acg_cache;
++ }
++
++ /*
++ * All mgmt stubs, UAs and sense buffers are bursty and loosing them
++ * may have fatal consequences, so let's have big pools for them.
++ */
++
++ scst_mgmt_stub_mempool = mempool_create(1024, mempool_alloc_slab,
++ mempool_free_slab, scst_mgmt_stub_cachep);
++ if (scst_mgmt_stub_mempool == NULL) {
++ res = -ENOMEM;
++ goto out_destroy_mgmt_mempool;
++ }
++
++ scst_ua_mempool = mempool_create(512, mempool_alloc_slab,
++ mempool_free_slab, scst_ua_cachep);
++ if (scst_ua_mempool == NULL) {
++ res = -ENOMEM;
++ goto out_destroy_mgmt_stub_mempool;
++ }
++
++ scst_sense_mempool = mempool_create(1024, mempool_alloc_slab,
++ mempool_free_slab, scst_sense_cachep);
++ if (scst_sense_mempool == NULL) {
++ res = -ENOMEM;
++ goto out_destroy_ua_mempool;
++ }
++
++ scst_aen_mempool = mempool_create(100, mempool_alloc_slab,
++ mempool_free_slab, scst_aen_cachep);
++ if (scst_aen_mempool == NULL) {
++ res = -ENOMEM;
++ goto out_destroy_sense_mempool;
++ }
++
++ res = scst_sysfs_init();
++ if (res != 0)
++ goto out_destroy_aen_mempool;
++
++ scst_tg_init();
++
++ if (scst_max_cmd_mem == 0) {
++ struct sysinfo si;
++ si_meminfo(&si);
++#if BITS_PER_LONG == 32
++ scst_max_cmd_mem = min(
++ (((uint64_t)(si.totalram - si.totalhigh) << PAGE_SHIFT)
++ >> 20) >> 2, (uint64_t)1 << 30);
++#else
++ scst_max_cmd_mem = (((si.totalram - si.totalhigh) << PAGE_SHIFT)
++ >> 20) >> 2;
++#endif
++ }
++
++ if (scst_max_dev_cmd_mem != 0) {
++ if (scst_max_dev_cmd_mem > scst_max_cmd_mem) {
++ PRINT_ERROR("scst_max_dev_cmd_mem (%d) > "
++ "scst_max_cmd_mem (%d)",
++ scst_max_dev_cmd_mem,
++ scst_max_cmd_mem);
++ scst_max_dev_cmd_mem = scst_max_cmd_mem;
++ }
++ } else
++ scst_max_dev_cmd_mem = scst_max_cmd_mem * 2 / 5;
++
++ res = scst_sgv_pools_init(
++ ((uint64_t)scst_max_cmd_mem << 10) >> (PAGE_SHIFT - 10), 0);
++ if (res != 0)
++ goto out_sysfs_cleanup;
++
++ res = scsi_register_interface(&scst_interface);
++ if (res != 0)
++ goto out_destroy_sgv_pool;
++
++ for (i = 0; i < (int)ARRAY_SIZE(scst_percpu_infos); i++) {
++ atomic_set(&scst_percpu_infos[i].cpu_cmd_count, 0);
++ spin_lock_init(&scst_percpu_infos[i].tasklet_lock);
++ INIT_LIST_HEAD(&scst_percpu_infos[i].tasklet_cmd_list);
++ tasklet_init(&scst_percpu_infos[i].tasklet,
++ (void *)scst_cmd_tasklet,
++ (unsigned long)&scst_percpu_infos[i]);
++ }
++
++ TRACE_DBG("%d CPUs found, starting %d threads", scst_num_cpus,
++ scst_threads);
++
++ res = scst_start_global_threads(scst_threads);
++ if (res < 0)
++ goto out_thread_free;
++
++ PRINT_INFO("SCST version %s loaded successfully (max mem for "
++ "commands %dMB, per device %dMB)", SCST_VERSION_STRING,
++ scst_max_cmd_mem, scst_max_dev_cmd_mem);
++
++ scst_print_config();
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_thread_free:
++ scst_stop_global_threads();
++
++ scsi_unregister_interface(&scst_interface);
++
++out_destroy_sgv_pool:
++ scst_sgv_pools_deinit();
++ scst_tg_cleanup();
++
++out_sysfs_cleanup:
++ scst_sysfs_cleanup();
++
++out_destroy_aen_mempool:
++ mempool_destroy(scst_aen_mempool);
++
++out_destroy_sense_mempool:
++ mempool_destroy(scst_sense_mempool);
++
++out_destroy_ua_mempool:
++ mempool_destroy(scst_ua_mempool);
++
++out_destroy_mgmt_stub_mempool:
++ mempool_destroy(scst_mgmt_stub_mempool);
++
++out_destroy_mgmt_mempool:
++ mempool_destroy(scst_mgmt_mempool);
++
++out_destroy_acg_cache:
++ kmem_cache_destroy(scst_acgd_cachep);
++
++out_destroy_tgt_cache:
++ kmem_cache_destroy(scst_tgtd_cachep);
++
++out_destroy_sess_cache:
++ kmem_cache_destroy(scst_sess_cachep);
++
++out_destroy_cmd_cache:
++ kmem_cache_destroy(scst_cmd_cachep);
++
++out_destroy_aen_cache:
++ kmem_cache_destroy(scst_aen_cachep);
++
++out_destroy_sense_cache:
++ kmem_cache_destroy(scst_sense_cachep);
++
++out_destroy_ua_cache:
++ kmem_cache_destroy(scst_ua_cachep);
++
++out_destroy_mgmt_stub_cache:
++ kmem_cache_destroy(scst_mgmt_stub_cachep);
++
++out_destroy_mgmt_cache:
++ kmem_cache_destroy(scst_mgmt_cachep);
++
++out_lib_exit:
++ scst_lib_exit();
++
++out_deinit_threads:
++ scst_deinit_threads(&scst_main_cmd_threads);
++ goto out;
++}
++
++static void __exit exit_scst(void)
++{
++ TRACE_ENTRY();
++
++ /* ToDo: unregister_cpu_notifier() */
++
++ scst_stop_global_threads();
++
++ scst_deinit_threads(&scst_main_cmd_threads);
++
++ scsi_unregister_interface(&scst_interface);
++
++ scst_sgv_pools_deinit();
++
++ scst_tg_cleanup();
++
++ scst_sysfs_cleanup();
++
++#define DEINIT_CACHEP(p) do { \
++ kmem_cache_destroy(p); \
++ p = NULL; \
++ } while (0)
++
++ mempool_destroy(scst_mgmt_mempool);
++ mempool_destroy(scst_mgmt_stub_mempool);
++ mempool_destroy(scst_ua_mempool);
++ mempool_destroy(scst_sense_mempool);
++ mempool_destroy(scst_aen_mempool);
++
++ DEINIT_CACHEP(scst_mgmt_cachep);
++ DEINIT_CACHEP(scst_mgmt_stub_cachep);
++ DEINIT_CACHEP(scst_ua_cachep);
++ DEINIT_CACHEP(scst_sense_cachep);
++ DEINIT_CACHEP(scst_aen_cachep);
++ DEINIT_CACHEP(scst_cmd_cachep);
++ DEINIT_CACHEP(scst_sess_cachep);
++ DEINIT_CACHEP(scst_tgtd_cachep);
++ DEINIT_CACHEP(scst_acgd_cachep);
++
++ scst_lib_exit();
++
++ PRINT_INFO("%s", "SCST unloaded");
++
++ TRACE_EXIT();
++ return;
++}
++
++module_init(init_scst);
++module_exit(exit_scst);
++
++MODULE_AUTHOR("Vladislav Bolkhovitin");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCSI target core");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/scst_module.c linux-3.2/drivers/scst/scst_module.c
+--- orig/linux-3.2/drivers/scst/scst_module.c
++++ linux-3.2/drivers/scst/scst_module.c
+@@ -0,0 +1,70 @@
++/*
++ * scst_module.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * Support for loading target modules. The usage is similar to scsi_module.c
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++#include <linux/init.h>
++
++#include <scst.h>
++
++static int __init init_this_scst_driver(void)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = scst_register_target_template(&driver_target_template);
++ TRACE_DBG("scst_register_target_template() returned %d", res);
++ if (res < 0)
++ goto out;
++
++#ifdef SCST_REGISTER_INITIATOR_DRIVER
++ driver_template.module = THIS_MODULE;
++ scsi_register_module(MODULE_SCSI_HA, &driver_template);
++ TRACE_DBG("driver_template.present=%d",
++ driver_template.present);
++ if (driver_template.present == 0) {
++ res = -ENODEV;
++ MOD_DEC_USE_COUNT;
++ goto out;
++ }
++#endif
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void __exit exit_this_scst_driver(void)
++{
++ TRACE_ENTRY();
++
++#ifdef SCST_REGISTER_INITIATOR_DRIVER
++ scsi_unregister_module(MODULE_SCSI_HA, &driver_template);
++#endif
++
++ scst_unregister_target_template(&driver_target_template);
++
++ TRACE_EXIT();
++ return;
++}
++
++module_init(init_this_scst_driver);
++module_exit(exit_this_scst_driver);
+diff -uprN orig/linux-3.2/drivers/scst/scst_priv.h linux-3.2/drivers/scst/scst_priv.h
+--- orig/linux-3.2/drivers/scst/scst_priv.h
++++ linux-3.2/drivers/scst/scst_priv.h
+@@ -0,0 +1,646 @@
++/*
++ * scst_priv.h
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#ifndef __SCST_PRIV_H
++#define __SCST_PRIV_H
++
++#include <linux/types.h>
++#include <linux/slab.h>
++#include <linux/export.h>
++#include <scsi/scsi.h>
++#include <scsi/scsi_cmnd.h>
++#include <scsi/scsi_driver.h>
++#include <scsi/scsi_device.h>
++#include <scsi/scsi_host.h>
++
++#define LOG_PREFIX "scst"
++
++#include <scst/scst_debug.h>
++
++#define TRACE_RTRY 0x80000000
++#define TRACE_SCSI_SERIALIZING 0x40000000
++/** top being the edge away from the interrupt */
++#define TRACE_SND_TOP 0x20000000
++#define TRACE_RCV_TOP 0x01000000
++/** bottom being the edge toward the interrupt */
++#define TRACE_SND_BOT 0x08000000
++#define TRACE_RCV_BOT 0x04000000
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++#define trace_flag scst_trace_flag
++extern unsigned long scst_trace_flag;
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++
++#define SCST_DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR | TRACE_PID | \
++ TRACE_LINE | TRACE_FUNCTION | TRACE_SPECIAL | TRACE_MGMT | \
++ TRACE_MGMT_DEBUG | TRACE_RTRY)
++
++#define TRACE_RETRY(args...) TRACE_DBG_FLAG(TRACE_RTRY, args)
++#define TRACE_SN(args...) TRACE_DBG_FLAG(TRACE_SCSI_SERIALIZING, args)
++#define TRACE_SEND_TOP(args...) TRACE_DBG_FLAG(TRACE_SND_TOP, args)
++#define TRACE_RECV_TOP(args...) TRACE_DBG_FLAG(TRACE_RCV_TOP, args)
++#define TRACE_SEND_BOT(args...) TRACE_DBG_FLAG(TRACE_SND_BOT, args)
++#define TRACE_RECV_BOT(args...) TRACE_DBG_FLAG(TRACE_RCV_BOT, args)
++
++#else /* CONFIG_SCST_DEBUG */
++
++# ifdef CONFIG_SCST_TRACING
++#define SCST_DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \
++ TRACE_SPECIAL)
++# else
++#define SCST_DEFAULT_LOG_FLAGS 0
++# endif
++
++#define TRACE_RETRY(args...)
++#define TRACE_SN(args...)
++#define TRACE_SEND_TOP(args...)
++#define TRACE_RECV_TOP(args...)
++#define TRACE_SEND_BOT(args...)
++#define TRACE_RECV_BOT(args...)
++
++#endif
++
++/**
++ ** Bits for scst_flags
++ **/
++
++/*
++ * Set if new commands initialization is being suspended for a while.
++ * Used to let TM commands execute while preparing the suspend, since
++ * RESET or ABORT could be necessary to free SCSI commands.
++ */
++#define SCST_FLAG_SUSPENDING 0
++
++/* Set if new commands initialization is suspended for a while */
++#define SCST_FLAG_SUSPENDED 1
++
++/**
++ ** Return codes for cmd state process functions. Codes are the same as
++ ** for SCST_EXEC_* to avoid translation to them and, hence, have better code.
++ **/
++#define SCST_CMD_STATE_RES_CONT_NEXT SCST_EXEC_COMPLETED
++#define SCST_CMD_STATE_RES_CONT_SAME SCST_EXEC_NOT_COMPLETED
++#define SCST_CMD_STATE_RES_NEED_THREAD (SCST_EXEC_NOT_COMPLETED+1)
++
++/**
++ ** Maximum count of uncompleted commands that an initiator could
++ ** queue on any device. Then it will start getting TASK QUEUE FULL status.
++ **/
++#define SCST_MAX_TGT_DEV_COMMANDS 48
++
++/**
++ ** Maximum count of uncompleted commands that could be queued on any device.
++ ** Then initiators sending commands to this device will start getting
++ ** TASK QUEUE FULL status.
++ **/
++#define SCST_MAX_DEV_COMMANDS 256
++
++#define SCST_TGT_RETRY_TIMEOUT (3/2*HZ)
++
++/* Activities suspending timeout */
++#define SCST_SUSPENDING_TIMEOUT (90 * HZ)
++
++extern struct mutex scst_mutex2;
++
++extern int scst_threads;
++
++extern unsigned int scst_max_dev_cmd_mem;
++
++extern mempool_t *scst_mgmt_mempool;
++extern mempool_t *scst_mgmt_stub_mempool;
++extern mempool_t *scst_ua_mempool;
++extern mempool_t *scst_sense_mempool;
++extern mempool_t *scst_aen_mempool;
++
++extern struct kmem_cache *scst_cmd_cachep;
++extern struct kmem_cache *scst_sess_cachep;
++extern struct kmem_cache *scst_tgtd_cachep;
++extern struct kmem_cache *scst_acgd_cachep;
++
++extern spinlock_t scst_main_lock;
++
++extern unsigned long scst_flags;
++extern struct list_head scst_template_list;
++extern struct list_head scst_dev_list;
++extern struct list_head scst_dev_type_list;
++extern struct list_head scst_virtual_dev_type_list;
++extern wait_queue_head_t scst_dev_cmd_waitQ;
++
++extern unsigned int scst_setup_id;
++
++#define SCST_DEF_MAX_TASKLET_CMD 10
++extern int scst_max_tasklet_cmd;
++
++extern spinlock_t scst_init_lock;
++extern struct list_head scst_init_cmd_list;
++extern wait_queue_head_t scst_init_cmd_list_waitQ;
++extern unsigned int scst_init_poll_cnt;
++
++extern struct scst_cmd_threads scst_main_cmd_threads;
++
++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_percpu_info {
++ atomic_t cpu_cmd_count;
++ spinlock_t tasklet_lock;
++ struct list_head tasklet_cmd_list;
++ struct tasklet_struct tasklet;
++} ____cacheline_aligned_in_smp;
++extern struct scst_percpu_info scst_percpu_infos[NR_CPUS];
++
++extern wait_queue_head_t scst_mgmt_waitQ;
++extern spinlock_t scst_mgmt_lock;
++extern struct list_head scst_sess_init_list;
++extern struct list_head scst_sess_shut_list;
++
++extern cpumask_t default_cpu_mask;
++
++struct scst_cmd_thread_t {
++ struct task_struct *cmd_thread;
++ struct list_head thread_list_entry;
++};
++
++static inline bool scst_set_io_context(struct scst_cmd *cmd,
++ struct io_context **old)
++{
++ bool res;
++
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ return false;
++#endif
++
++ if (cmd->cmd_threads == &scst_main_cmd_threads) {
++ EXTRACHECKS_BUG_ON(in_interrupt());
++ /*
++ * No need for any ref counting action, because io_context
++ * supposed to be cleared in the end of the caller function.
++ */
++ current->io_context = cmd->tgt_dev->async_io_context;
++ res = true;
++ TRACE_DBG("io_context %p (tgt_dev %p)", current->io_context,
++ cmd->tgt_dev);
++ EXTRACHECKS_BUG_ON(current->io_context == NULL);
++ } else
++ res = false;
++
++ return res;
++}
++
++static inline void scst_reset_io_context(struct scst_tgt_dev *tgt_dev,
++ struct io_context *old)
++{
++ current->io_context = old;
++ TRACE_DBG("io_context %p reset", current->io_context);
++ return;
++}
++
++/*
++ * Converts string presentation of threads pool type to enum.
++ * Returns SCST_THREADS_POOL_TYPE_INVALID if the string is invalid.
++ */
++extern enum scst_dev_type_threads_pool_type scst_parse_threads_pool_type(
++ const char *p, int len);
++
++extern int scst_add_threads(struct scst_cmd_threads *cmd_threads,
++ struct scst_device *dev, struct scst_tgt_dev *tgt_dev, int num);
++extern void scst_del_threads(struct scst_cmd_threads *cmd_threads, int num);
++
++extern int scst_create_dev_threads(struct scst_device *dev);
++extern void scst_stop_dev_threads(struct scst_device *dev);
++
++extern int scst_tgt_dev_setup_threads(struct scst_tgt_dev *tgt_dev);
++extern void scst_tgt_dev_stop_threads(struct scst_tgt_dev *tgt_dev);
++
++extern bool scst_del_thr_data(struct scst_tgt_dev *tgt_dev,
++ struct task_struct *tsk);
++
++extern struct scst_dev_type scst_null_devtype;
++
++extern struct scst_cmd *__scst_check_deferred_commands(
++ struct scst_order_data *order_data);
++
++/* Used to save the function call on the fast path */
++static inline struct scst_cmd *scst_check_deferred_commands(
++ struct scst_order_data *order_data)
++{
++ if (order_data->def_cmd_count == 0)
++ return NULL;
++ else
++ return __scst_check_deferred_commands(order_data);
++}
++
++static inline void scst_make_deferred_commands_active(
++ struct scst_order_data *order_data)
++{
++ struct scst_cmd *c;
++
++ c = __scst_check_deferred_commands(order_data);
++ if (c != NULL) {
++ TRACE_SN("Adding cmd %p to active cmd list", c);
++ spin_lock_irq(&c->cmd_threads->cmd_list_lock);
++ list_add_tail(&c->cmd_list_entry,
++ &c->cmd_threads->active_cmd_list);
++ wake_up(&c->cmd_threads->cmd_list_waitQ);
++ spin_unlock_irq(&c->cmd_threads->cmd_list_lock);
++ }
++
++ return;
++}
++
++void scst_inc_expected_sn(struct scst_order_data *order_data, atomic_t *slot);
++int scst_check_hq_cmd(struct scst_cmd *cmd);
++
++void scst_unblock_deferred(struct scst_order_data *order_data,
++ struct scst_cmd *cmd_sn);
++
++void scst_on_hq_cmd_response(struct scst_cmd *cmd);
++void scst_xmit_process_aborted_cmd(struct scst_cmd *cmd);
++
++int scst_pre_parse(struct scst_cmd *cmd);
++
++int scst_cmd_thread(void *arg);
++void scst_cmd_tasklet(long p);
++int scst_init_thread(void *arg);
++int scst_tm_thread(void *arg);
++int scst_global_mgmt_thread(void *arg);
++
++void scst_zero_write_rest(struct scst_cmd *cmd);
++void scst_limit_sg_write_len(struct scst_cmd *cmd);
++void scst_adjust_resp_data_len(struct scst_cmd *cmd);
++
++int scst_queue_retry_cmd(struct scst_cmd *cmd, int finished_cmds);
++
++int scst_alloc_tgt(struct scst_tgt_template *tgtt, struct scst_tgt **tgt);
++void scst_free_tgt(struct scst_tgt *tgt);
++
++int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev);
++void scst_free_device(struct scst_device *dev);
++
++struct scst_acg *scst_alloc_add_acg(struct scst_tgt *tgt,
++ const char *acg_name, bool tgt_acg);
++void scst_del_free_acg(struct scst_acg *acg);
++
++struct scst_acg *scst_tgt_find_acg(struct scst_tgt *tgt, const char *name);
++struct scst_acg *scst_find_acg(const struct scst_session *sess);
++
++void scst_check_reassign_sessions(void);
++
++int scst_sess_alloc_tgt_devs(struct scst_session *sess);
++void scst_sess_free_tgt_devs(struct scst_session *sess);
++void scst_nexus_loss(struct scst_tgt_dev *tgt_dev, bool queue_UA);
++
++int scst_acg_add_lun(struct scst_acg *acg, struct kobject *parent,
++ struct scst_device *dev, uint64_t lun, int read_only,
++ bool gen_scst_report_luns_changed, struct scst_acg_dev **out_acg_dev);
++int scst_acg_del_lun(struct scst_acg *acg, uint64_t lun,
++ bool gen_scst_report_luns_changed);
++
++int scst_acg_add_acn(struct scst_acg *acg, const char *name);
++void scst_del_free_acn(struct scst_acn *acn, bool reassign);
++struct scst_acn *scst_find_acn(struct scst_acg *acg, const char *name);
++
++/* The activity supposed to be suspended and scst_mutex held */
++static inline bool scst_acg_sess_is_empty(struct scst_acg *acg)
++{
++ return list_empty(&acg->acg_sess_list);
++}
++
++int scst_prepare_request_sense(struct scst_cmd *orig_cmd);
++int scst_finish_internal_cmd(struct scst_cmd *cmd);
++
++void scst_store_sense(struct scst_cmd *cmd);
++
++int scst_assign_dev_handler(struct scst_device *dev,
++ struct scst_dev_type *handler);
++
++struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask,
++ const char *initiator_name);
++void scst_free_session(struct scst_session *sess);
++void scst_free_session_callback(struct scst_session *sess);
++
++struct scst_cmd *scst_alloc_cmd(const uint8_t *cdb,
++ unsigned int cdb_len, gfp_t gfp_mask);
++void scst_free_cmd(struct scst_cmd *cmd);
++static inline void scst_destroy_cmd(struct scst_cmd *cmd)
++{
++ kmem_cache_free(scst_cmd_cachep, cmd);
++ return;
++}
++
++void scst_check_retries(struct scst_tgt *tgt);
++
++int scst_alloc_space(struct scst_cmd *cmd);
++
++int scst_lib_init(void);
++void scst_lib_exit(void);
++
++__be64 scst_pack_lun(const uint64_t lun, enum scst_lun_addr_method addr_method);
++uint64_t scst_unpack_lun(const uint8_t *lun, int len);
++
++struct scst_mgmt_cmd *scst_alloc_mgmt_cmd(gfp_t gfp_mask);
++void scst_free_mgmt_cmd(struct scst_mgmt_cmd *mcmd);
++void scst_done_cmd_mgmt(struct scst_cmd *cmd);
++
++static inline void scst_devt_cleanup(struct scst_dev_type *devt) { }
++
++void scst_tg_init(void);
++void scst_tg_cleanup(void);
++int scst_dg_add(struct kobject *parent, const char *name);
++int scst_dg_remove(const char *name);
++struct scst_dev_group *scst_lookup_dg_by_kobj(struct kobject *kobj);
++int scst_dg_dev_add(struct scst_dev_group *dg, const char *name);
++int scst_dg_dev_remove_by_name(struct scst_dev_group *dg, const char *name);
++int scst_dg_dev_remove_by_dev(struct scst_device *dev);
++int scst_tg_add(struct scst_dev_group *dg, const char *name);
++int scst_tg_remove_by_name(struct scst_dev_group *dg, const char *name);
++int scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state);
++int scst_tg_tgt_add(struct scst_target_group *tg, const char *name);
++int scst_tg_tgt_remove_by_name(struct scst_target_group *tg, const char *name);
++void scst_tg_tgt_remove_by_tgt(struct scst_tgt *tgt);
++int scst_dg_sysfs_add(struct kobject *parent, struct scst_dev_group *dg);
++void scst_dg_sysfs_del(struct scst_dev_group *dg);
++int scst_dg_dev_sysfs_add(struct scst_dev_group *dg, struct scst_dg_dev *dgdev);
++void scst_dg_dev_sysfs_del(struct scst_dev_group *dg,
++ struct scst_dg_dev *dgdev);
++int scst_tg_sysfs_add(struct scst_dev_group *dg,
++ struct scst_target_group *tg);
++void scst_tg_sysfs_del(struct scst_target_group *tg);
++int scst_tg_tgt_sysfs_add(struct scst_target_group *tg,
++ struct scst_tg_tgt *tg_tgt);
++void scst_tg_tgt_sysfs_del(struct scst_target_group *tg,
++ struct scst_tg_tgt *tg_tgt);
++
++extern const struct sysfs_ops scst_sysfs_ops;
++int scst_sysfs_init(void);
++void scst_sysfs_cleanup(void);
++int scst_tgtt_sysfs_create(struct scst_tgt_template *tgtt);
++void scst_tgtt_sysfs_del(struct scst_tgt_template *tgtt);
++int scst_tgt_sysfs_create(struct scst_tgt *tgt);
++void scst_tgt_sysfs_prepare_put(struct scst_tgt *tgt);
++void scst_tgt_sysfs_del(struct scst_tgt *tgt);
++int scst_sess_sysfs_create(struct scst_session *sess);
++void scst_sess_sysfs_del(struct scst_session *sess);
++int scst_recreate_sess_luns_link(struct scst_session *sess);
++int scst_add_sgv_kobj(struct kobject *parent, const char *name);
++void scst_del_put_sgv_kobj(void);
++int scst_devt_sysfs_create(struct scst_dev_type *devt);
++void scst_devt_sysfs_del(struct scst_dev_type *devt);
++int scst_dev_sysfs_create(struct scst_device *dev);
++void scst_dev_sysfs_del(struct scst_device *dev);
++int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev);
++void scst_tgt_dev_sysfs_del(struct scst_tgt_dev *tgt_dev);
++int scst_devt_dev_sysfs_create(struct scst_device *dev);
++void scst_devt_dev_sysfs_del(struct scst_device *dev);
++int scst_acg_sysfs_create(struct scst_tgt *tgt,
++ struct scst_acg *acg);
++void scst_acg_sysfs_del(struct scst_acg *acg);
++int scst_acg_dev_sysfs_create(struct scst_acg_dev *acg_dev,
++ struct kobject *parent);
++void scst_acg_dev_sysfs_del(struct scst_acg_dev *acg_dev);
++int scst_acn_sysfs_create(struct scst_acn *acn);
++void scst_acn_sysfs_del(struct scst_acn *acn);
++
++void __scst_dev_check_set_UA(struct scst_device *dev, struct scst_cmd *exclude,
++ const uint8_t *sense, int sense_len);
++void scst_tgt_dev_del_free_UA(struct scst_tgt_dev *tgt_dev,
++ struct scst_tgt_dev_UA *ua);
++static inline void scst_dev_check_set_UA(struct scst_device *dev,
++ struct scst_cmd *exclude, const uint8_t *sense, int sense_len)
++{
++ spin_lock_bh(&dev->dev_lock);
++ __scst_dev_check_set_UA(dev, exclude, sense, sense_len);
++ spin_unlock_bh(&dev->dev_lock);
++ return;
++}
++void scst_dev_check_set_local_UA(struct scst_device *dev,
++ struct scst_cmd *exclude, const uint8_t *sense, int sense_len);
++
++#define SCST_SET_UA_FLAG_AT_HEAD 1
++#define SCST_SET_UA_FLAG_GLOBAL 2
++
++void scst_check_set_UA(struct scst_tgt_dev *tgt_dev,
++ const uint8_t *sense, int sense_len, int flags);
++int scst_set_pending_UA(struct scst_cmd *cmd);
++
++void scst_report_luns_changed(struct scst_acg *acg);
++
++void scst_abort_cmd(struct scst_cmd *cmd, struct scst_mgmt_cmd *mcmd,
++ bool other_ini, bool call_dev_task_mgmt_fn);
++void scst_process_reset(struct scst_device *dev,
++ struct scst_session *originator, struct scst_cmd *exclude_cmd,
++ struct scst_mgmt_cmd *mcmd, bool setUA);
++
++bool scst_is_ua_global(const uint8_t *sense, int len);
++void scst_requeue_ua(struct scst_cmd *cmd);
++
++struct scst_aen *scst_alloc_aen(struct scst_session *sess,
++ uint64_t unpacked_lun);
++void scst_free_aen(struct scst_aen *aen);
++
++void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev,
++ int key, int asc, int ascq);
++
++static inline bool scst_is_implicit_hq_cmd(struct scst_cmd *cmd)
++{
++ return (cmd->op_flags & SCST_IMPLICIT_HQ) != 0;
++}
++
++static inline bool scst_is_serialized_cmd(struct scst_cmd *cmd)
++{
++ return (cmd->op_flags & SCST_SERIALIZED) != 0;
++}
++
++static inline bool scst_is_strictly_serialized_cmd(struct scst_cmd *cmd)
++{
++ return (cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED;
++}
++
++/*
++ * Some notes on devices "blocking". Blocking means that no
++ * commands will go from SCST to underlying SCSI device until it
++ * is unblocked. But, except for strictly serialized commands,
++ * we don't care about all commands that already on the device.
++ */
++
++extern void scst_block_dev(struct scst_device *dev);
++extern void scst_unblock_dev(struct scst_device *dev);
++
++bool __scst_check_blocked_dev(struct scst_cmd *cmd);
++
++/*
++ * Increases global SCST ref counters which prevent from entering into suspended
++ * activities stage, so protects from any global management operations.
++ */
++static inline atomic_t *scst_get(void)
++{
++ atomic_t *a;
++ /*
++ * We don't mind if we because of preemption inc counter from another
++ * CPU as soon in the majority cases we will the correct one. So, let's
++ * have preempt_disable/enable only in the debug build to avoid warning.
++ */
++#ifdef CONFIG_DEBUG_PREEMPT
++ preempt_disable();
++#endif
++ a = &scst_percpu_infos[smp_processor_id()].cpu_cmd_count;
++ atomic_inc(a);
++#ifdef CONFIG_DEBUG_PREEMPT
++ preempt_enable();
++#endif
++ TRACE_DBG("Incrementing cpu_cmd_count %p (new value %d)",
++ a, atomic_read(a));
++ /* See comment about smp_mb() in scst_suspend_activity() */
++ smp_mb__after_atomic_inc();
++
++ return a;
++}
++
++/*
++ * Decreases global SCST ref counters which prevent from entering into suspended
++ * activities stage, so protects from any global management operations. On
++ * all them zero, if suspending activities is waiting, it will be proceed.
++ */
++static inline void scst_put(atomic_t *a)
++{
++ int f;
++ f = atomic_dec_and_test(a);
++ /* See comment about smp_mb() in scst_suspend_activity() */
++ if (unlikely(test_bit(SCST_FLAG_SUSPENDED, &scst_flags)) && f) {
++ TRACE_MGMT_DBG("%s", "Waking up scst_dev_cmd_waitQ");
++ wake_up_all(&scst_dev_cmd_waitQ);
++ }
++ TRACE_DBG("Decrementing cpu_cmd_count %p (new value %d)",
++ a, atomic_read(a));
++}
++
++int scst_get_cmd_counter(void);
++
++void scst_sched_session_free(struct scst_session *sess);
++
++static inline void scst_sess_get(struct scst_session *sess)
++{
++ atomic_inc(&sess->refcnt);
++ TRACE_DBG("Incrementing sess %p refcnt (new value %d)",
++ sess, atomic_read(&sess->refcnt));
++}
++
++static inline void scst_sess_put(struct scst_session *sess)
++{
++ TRACE_DBG("Decrementing sess %p refcnt (new value %d)",
++ sess, atomic_read(&sess->refcnt)-1);
++ if (atomic_dec_and_test(&sess->refcnt))
++ scst_sched_session_free(sess);
++}
++
++static inline void __scst_cmd_get(struct scst_cmd *cmd)
++{
++ atomic_inc(&cmd->cmd_ref);
++ TRACE_DBG("Incrementing cmd %p ref (new value %d)",
++ cmd, atomic_read(&cmd->cmd_ref));
++}
++
++static inline void __scst_cmd_put(struct scst_cmd *cmd)
++{
++ TRACE_DBG("Decrementing cmd %p ref (new value %d)",
++ cmd, atomic_read(&cmd->cmd_ref)-1);
++ if (atomic_dec_and_test(&cmd->cmd_ref))
++ scst_free_cmd(cmd);
++}
++
++extern void scst_throttle_cmd(struct scst_cmd *cmd);
++extern void scst_unthrottle_cmd(struct scst_cmd *cmd);
++
++#ifdef CONFIG_SCST_DEBUG_TM
++extern void tm_dbg_check_released_cmds(void);
++extern int tm_dbg_check_cmd(struct scst_cmd *cmd);
++extern void tm_dbg_release_cmd(struct scst_cmd *cmd);
++extern void tm_dbg_task_mgmt(struct scst_device *dev, const char *fn,
++ int force);
++extern int tm_dbg_is_release(void);
++#else
++static inline void tm_dbg_check_released_cmds(void) {}
++static inline int tm_dbg_check_cmd(struct scst_cmd *cmd)
++{
++ return 0;
++}
++static inline void tm_dbg_release_cmd(struct scst_cmd *cmd) {}
++static inline void tm_dbg_task_mgmt(struct scst_device *dev, const char *fn,
++ int force) {}
++static inline int tm_dbg_is_release(void)
++{
++ return 0;
++}
++#endif /* CONFIG_SCST_DEBUG_TM */
++
++#ifdef CONFIG_SCST_DEBUG_SN
++void scst_check_debug_sn(struct scst_cmd *cmd);
++#else
++static inline void scst_check_debug_sn(struct scst_cmd *cmd) {}
++#endif
++
++static inline int scst_sn_before(uint32_t seq1, uint32_t seq2)
++{
++ return (int32_t)(seq1-seq2) < 0;
++}
++
++int gen_relative_target_port_id(uint16_t *id);
++bool scst_is_relative_target_port_id_unique(uint16_t id,
++ const struct scst_tgt *t);
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++
++void scst_set_start_time(struct scst_cmd *cmd);
++void scst_set_cur_start(struct scst_cmd *cmd);
++void scst_set_parse_time(struct scst_cmd *cmd);
++void scst_set_alloc_buf_time(struct scst_cmd *cmd);
++void scst_set_restart_waiting_time(struct scst_cmd *cmd);
++void scst_set_rdy_to_xfer_time(struct scst_cmd *cmd);
++void scst_set_pre_exec_time(struct scst_cmd *cmd);
++void scst_set_exec_time(struct scst_cmd *cmd);
++void scst_set_dev_done_time(struct scst_cmd *cmd);
++void scst_set_xmit_time(struct scst_cmd *cmd);
++void scst_set_tgt_on_free_time(struct scst_cmd *cmd);
++void scst_set_dev_on_free_time(struct scst_cmd *cmd);
++void scst_update_lat_stats(struct scst_cmd *cmd);
++
++#else
++
++static inline void scst_set_start_time(struct scst_cmd *cmd) {}
++static inline void scst_set_cur_start(struct scst_cmd *cmd) {}
++static inline void scst_set_parse_time(struct scst_cmd *cmd) {}
++static inline void scst_set_alloc_buf_time(struct scst_cmd *cmd) {}
++static inline void scst_set_restart_waiting_time(struct scst_cmd *cmd) {}
++static inline void scst_set_rdy_to_xfer_time(struct scst_cmd *cmd) {}
++static inline void scst_set_pre_exec_time(struct scst_cmd *cmd) {}
++static inline void scst_set_exec_time(struct scst_cmd *cmd) {}
++static inline void scst_set_dev_done_time(struct scst_cmd *cmd) {}
++static inline void scst_set_xmit_time(struct scst_cmd *cmd) {}
++static inline void scst_set_tgt_on_free_time(struct scst_cmd *cmd) {}
++static inline void scst_set_dev_on_free_time(struct scst_cmd *cmd) {}
++static inline void scst_update_lat_stats(struct scst_cmd *cmd) {}
++
++#endif /* CONFIG_SCST_MEASURE_LATENCY */
++
++#endif /* __SCST_PRIV_H */
+diff -uprN orig/linux-3.2/drivers/scst/scst_targ.c linux-3.2/drivers/scst/scst_targ.c
+--- orig/linux-3.2/drivers/scst/scst_targ.c
++++ linux-3.2/drivers/scst/scst_targ.c
+@@ -0,0 +1,6705 @@
++/*
++ * scst_targ.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/unistd.h>
++#include <linux/string.h>
++#include <linux/kthread.h>
++#include <linux/delay.h>
++#include <linux/ktime.h>
++
++#include <scst/scst.h>
++#include "scst_priv.h"
++#include "scst_pres.h"
++
++#if 0 /* Let's disable it for now to see if users will complain about it */
++/* Deleting it don't forget to delete dev_cmd_count */
++#define CONFIG_SCST_PER_DEVICE_CMD_COUNT_LIMIT
++#endif
++
++static void scst_cmd_set_sn(struct scst_cmd *cmd);
++static int __scst_init_cmd(struct scst_cmd *cmd);
++static void scst_finish_cmd_mgmt(struct scst_cmd *cmd);
++static struct scst_cmd *__scst_find_cmd_by_tag(struct scst_session *sess,
++ uint64_t tag, bool to_abort);
++static void scst_process_redirect_cmd(struct scst_cmd *cmd,
++ enum scst_exec_context context, int check_retries);
++
++/**
++ * scst_post_parse() - do post parse actions
++ *
++ * This function must be called by dev handler after its parse() callback
++ * returned SCST_CMD_STATE_STOP before calling scst_process_active_cmd().
++ */
++void scst_post_parse(struct scst_cmd *cmd)
++{
++ scst_set_parse_time(cmd);
++}
++EXPORT_SYMBOL_GPL(scst_post_parse);
++
++/**
++ * scst_post_alloc_data_buf() - do post alloc_data_buf actions
++ *
++ * This function must be called by dev handler after its alloc_data_buf()
++ * callback returned SCST_CMD_STATE_STOP before calling
++ * scst_process_active_cmd().
++ */
++void scst_post_alloc_data_buf(struct scst_cmd *cmd)
++{
++ scst_set_alloc_buf_time(cmd);
++}
++EXPORT_SYMBOL_GPL(scst_post_alloc_data_buf);
++
++static inline void scst_schedule_tasklet(struct scst_cmd *cmd)
++{
++ struct scst_percpu_info *i = &scst_percpu_infos[smp_processor_id()];
++ unsigned long flags;
++
++ if (atomic_read(&i->cpu_cmd_count) <= scst_max_tasklet_cmd) {
++ spin_lock_irqsave(&i->tasklet_lock, flags);
++ TRACE_DBG("Adding cmd %p to tasklet %d cmd list", cmd,
++ smp_processor_id());
++ list_add_tail(&cmd->cmd_list_entry, &i->tasklet_cmd_list);
++ spin_unlock_irqrestore(&i->tasklet_lock, flags);
++
++ tasklet_schedule(&i->tasklet);
++ } else {
++ spin_lock_irqsave(&cmd->cmd_threads->cmd_list_lock, flags);
++ TRACE_DBG("Too many tasklet commands (%d), adding cmd %p to "
++ "active cmd list", atomic_read(&i->cpu_cmd_count), cmd);
++ list_add_tail(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock_irqrestore(&cmd->cmd_threads->cmd_list_lock, flags);
++ }
++ return;
++}
++
++/* No locks */
++static bool scst_check_blocked_dev(struct scst_cmd *cmd)
++{
++ bool res;
++ struct scst_device *dev = cmd->dev;
++
++ spin_lock_bh(&dev->dev_lock);
++
++ dev->on_dev_cmd_count++;
++ cmd->dec_on_dev_needed = 1;
++ TRACE_DBG("New inc on_dev_count %d (cmd %p)", dev->on_dev_cmd_count,
++ cmd);
++
++ scst_inc_pr_readers_count(cmd, true);
++
++ if (unlikely(dev->block_count > 0) ||
++ unlikely(dev->dev_double_ua_possible) ||
++ unlikely(scst_is_serialized_cmd(cmd)))
++ res = __scst_check_blocked_dev(cmd);
++ else
++ res = false;
++
++ if (unlikely(res)) {
++ /* Undo increments */
++ dev->on_dev_cmd_count--;
++ cmd->dec_on_dev_needed = 0;
++ TRACE_DBG("New dec on_dev_count %d (cmd %p)",
++ dev->on_dev_cmd_count, cmd);
++
++ scst_dec_pr_readers_count(cmd, true);
++ }
++
++ spin_unlock_bh(&dev->dev_lock);
++
++ return res;
++}
++
++/* No locks */
++static void scst_check_unblock_dev(struct scst_cmd *cmd)
++{
++ struct scst_device *dev = cmd->dev;
++
++ spin_lock_bh(&dev->dev_lock);
++
++ if (likely(cmd->dec_on_dev_needed)) {
++ dev->on_dev_cmd_count--;
++ cmd->dec_on_dev_needed = 0;
++ TRACE_DBG("New dec on_dev_count %d (cmd %p)",
++ dev->on_dev_cmd_count, cmd);
++ }
++
++ if (unlikely(cmd->dec_pr_readers_count_needed))
++ scst_dec_pr_readers_count(cmd, true);
++
++ if (unlikely(cmd->unblock_dev)) {
++ TRACE_MGMT_DBG("cmd %p (tag %llu): unblocking dev %s", cmd,
++ (long long unsigned int)cmd->tag, dev->virt_name);
++ cmd->unblock_dev = 0;
++ scst_unblock_dev(dev);
++ } else if (unlikely(dev->strictly_serialized_cmd_waiting)) {
++ if (dev->on_dev_cmd_count == 0) {
++ TRACE_MGMT_DBG("Strictly serialized cmd waiting: "
++ "unblocking dev %s", dev->virt_name);
++ scst_unblock_dev(dev);
++ }
++ }
++
++ spin_unlock_bh(&dev->dev_lock);
++ return;
++}
++
++/**
++ * scst_rx_cmd() - create new command
++ * @sess: SCST session
++ * @lun: LUN for the command
++ * @lun_len: length of the LUN in bytes
++ * @cdb: CDB of the command
++ * @cdb_len: length of the CDB in bytes
++ * @atomic: true, if current context is atomic
++ *
++ * Description:
++ * Creates new SCST command. Returns new command on success or
++ * NULL otherwise.
++ *
++ * Must not be called in parallel with scst_unregister_session() for the
++ * same session.
++ */
++struct scst_cmd *scst_rx_cmd(struct scst_session *sess,
++ const uint8_t *lun, int lun_len, const uint8_t *cdb,
++ unsigned int cdb_len, int atomic)
++{
++ struct scst_cmd *cmd;
++
++ TRACE_ENTRY();
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (unlikely(sess->shut_phase != SCST_SESS_SPH_READY)) {
++ PRINT_CRIT_ERROR("%s",
++ "New cmd while shutting down the session");
++ BUG();
++ }
++#endif
++
++ cmd = scst_alloc_cmd(cdb, cdb_len, atomic ? GFP_ATOMIC : GFP_KERNEL);
++ if (unlikely(cmd == NULL))
++ goto out;
++
++ cmd->sess = sess;
++ cmd->tgt = sess->tgt;
++ cmd->tgtt = sess->tgt->tgtt;
++
++ cmd->lun = scst_unpack_lun(lun, lun_len);
++ if (unlikely(cmd->lun == NO_SUCH_LUN))
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_lun_not_supported));
++
++ TRACE_DBG("cmd %p, sess %p", cmd, sess);
++ scst_sess_get(sess);
++
++out:
++ TRACE_EXIT();
++ return cmd;
++}
++EXPORT_SYMBOL(scst_rx_cmd);
++
++/*
++ * No locks, but might be on IRQ. Returns 0 on success, <0 if processing of
++ * this command should be stopped.
++ */
++static int scst_init_cmd(struct scst_cmd *cmd, enum scst_exec_context *context)
++{
++ int rc, res = 0;
++
++ TRACE_ENTRY();
++
++ /* See the comment in scst_do_job_init() */
++ if (unlikely(!list_empty(&scst_init_cmd_list))) {
++ TRACE_MGMT_DBG("%s", "init cmd list busy");
++ goto out_redirect;
++ }
++ /*
++ * Memory barrier isn't necessary here, because CPU appears to
++ * be self-consistent and we don't care about the race, described
++ * in comment in scst_do_job_init().
++ */
++
++ rc = __scst_init_cmd(cmd);
++ if (unlikely(rc > 0))
++ goto out_redirect;
++ else if (unlikely(rc != 0)) {
++ res = 1;
++ goto out;
++ }
++
++ EXTRACHECKS_BUG_ON(*context == SCST_CONTEXT_SAME);
++
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ if (cmd->op_flags & SCST_TEST_IO_IN_SIRQ_ALLOWED)
++ goto out;
++#endif
++
++ /* Small context optimization */
++ if ((*context == SCST_CONTEXT_TASKLET) ||
++ (*context == SCST_CONTEXT_DIRECT_ATOMIC)) {
++ /*
++ * If any data_direction not set, it's SCST_DATA_UNKNOWN,
++ * which is 0, so we can safely | them
++ */
++ BUILD_BUG_ON(SCST_DATA_UNKNOWN != 0);
++ if ((cmd->data_direction | cmd->expected_data_direction) & SCST_DATA_WRITE) {
++ if (!test_bit(SCST_TGT_DEV_AFTER_INIT_WR_ATOMIC,
++ &cmd->tgt_dev->tgt_dev_flags))
++ *context = SCST_CONTEXT_THREAD;
++ } else
++ *context = SCST_CONTEXT_THREAD;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_redirect:
++ if (cmd->preprocessing_only) {
++ /*
++ * Poor man solution for single threaded targets, where
++ * blocking receiver at least sometimes means blocking all.
++ * For instance, iSCSI target won't be able to receive
++ * Data-Out PDUs.
++ */
++ BUG_ON(*context != SCST_CONTEXT_DIRECT);
++ scst_set_busy(cmd);
++ scst_set_cmd_abnormal_done_state(cmd);
++ res = 1;
++ /* Keep initiator away from too many BUSY commands */
++ msleep(50);
++ } else {
++ unsigned long flags;
++ spin_lock_irqsave(&scst_init_lock, flags);
++ TRACE_MGMT_DBG("Adding cmd %p to init cmd list)", cmd);
++ list_add_tail(&cmd->cmd_list_entry, &scst_init_cmd_list);
++ if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))
++ scst_init_poll_cnt++;
++ spin_unlock_irqrestore(&scst_init_lock, flags);
++ wake_up(&scst_init_cmd_list_waitQ);
++ res = -1;
++ }
++ goto out;
++}
++
++/**
++ * scst_cmd_init_done() - the command's initialization done
++ * @cmd: SCST command
++ * @pref_context: preferred command execution context
++ *
++ * Description:
++ * Notifies SCST that the driver finished its part of the command
++ * initialization, and the command is ready for execution.
++ * The second argument sets preferred command execution context.
++ * See SCST_CONTEXT_* constants for details.
++ *
++ * !!IMPORTANT!!
++ *
++ * If cmd->set_sn_on_restart_cmd not set, this function, as well as
++ * scst_cmd_init_stage1_done() and scst_restart_cmd(), must not be
++ * called simultaneously for the same session (more precisely,
++ * for the same session/LUN, i.e. tgt_dev), i.e. they must be
++ * somehow externally serialized. This is needed to have lock free fast
++ * path in scst_cmd_set_sn(). For majority of targets those functions are
++ * naturally serialized by the single source of commands. Only iSCSI
++ * immediate commands with multiple connections per session seems to be an
++ * exception. For it, some mutex/lock shall be used for the serialization.
++ */
++void scst_cmd_init_done(struct scst_cmd *cmd,
++ enum scst_exec_context pref_context)
++{
++ unsigned long flags;
++ struct scst_session *sess = cmd->sess;
++ int rc;
++
++ TRACE_ENTRY();
++
++ scst_set_start_time(cmd);
++
++ TRACE_DBG("Preferred context: %d (cmd %p)", pref_context, cmd);
++ TRACE(TRACE_SCSI, "tag=%llu, lun=%lld, CDB len=%d, queue_type=%x "
++ "(cmd %p, sess %p)", (long long unsigned int)cmd->tag,
++ (long long unsigned int)cmd->lun, cmd->cdb_len,
++ cmd->queue_type, cmd, sess);
++ PRINT_BUFF_FLAG(TRACE_SCSI|TRACE_RCV_BOT, "Receiving CDB",
++ cmd->cdb, cmd->cdb_len);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (unlikely((in_irq() || irqs_disabled())) &&
++ ((pref_context == SCST_CONTEXT_DIRECT) ||
++ (pref_context == SCST_CONTEXT_DIRECT_ATOMIC))) {
++ PRINT_ERROR("Wrong context %d in IRQ from target %s, use "
++ "SCST_CONTEXT_THREAD instead", pref_context,
++ cmd->tgtt->name);
++ dump_stack();
++ pref_context = SCST_CONTEXT_THREAD;
++ }
++#endif
++
++ atomic_inc(&sess->sess_cmd_count);
++
++ spin_lock_irqsave(&sess->sess_list_lock, flags);
++
++ if (unlikely(sess->init_phase != SCST_SESS_IPH_READY)) {
++ /*
++ * We must always keep commands in the sess list from the
++ * very beginning, because otherwise they can be missed during
++ * TM processing. This check is needed because there might be
++ * old, i.e. deferred, commands and new, i.e. just coming, ones.
++ */
++ if (cmd->sess_cmd_list_entry.next == NULL)
++ list_add_tail(&cmd->sess_cmd_list_entry,
++ &sess->sess_cmd_list);
++ switch (sess->init_phase) {
++ case SCST_SESS_IPH_SUCCESS:
++ break;
++ case SCST_SESS_IPH_INITING:
++ TRACE_DBG("Adding cmd %p to init deferred cmd list",
++ cmd);
++ list_add_tail(&cmd->cmd_list_entry,
++ &sess->init_deferred_cmd_list);
++ spin_unlock_irqrestore(&sess->sess_list_lock, flags);
++ goto out;
++ case SCST_SESS_IPH_FAILED:
++ spin_unlock_irqrestore(&sess->sess_list_lock, flags);
++ scst_set_busy(cmd);
++ goto set_state;
++ default:
++ BUG();
++ }
++ } else
++ list_add_tail(&cmd->sess_cmd_list_entry,
++ &sess->sess_cmd_list);
++
++ spin_unlock_irqrestore(&sess->sess_list_lock, flags);
++
++ if (unlikely(cmd->queue_type >= SCST_CMD_QUEUE_ACA)) {
++ PRINT_ERROR("Unsupported queue type %d", cmd->queue_type);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_message));
++ }
++
++set_state:
++ if (unlikely(cmd->status != SAM_STAT_GOOD)) {
++ scst_set_cmd_abnormal_done_state(cmd);
++ goto active;
++ }
++
++ /*
++ * Cmd must be inited here to preserve the order. In case if cmd
++ * already preliminary completed by target driver we need to init
++ * cmd anyway to find out in which format we should return sense.
++ */
++ cmd->state = SCST_CMD_STATE_INIT;
++ rc = scst_init_cmd(cmd, &pref_context);
++ if (unlikely(rc < 0))
++ goto out;
++
++active:
++ /* Here cmd must not be in any cmd list, no locks */
++ switch (pref_context) {
++ case SCST_CONTEXT_TASKLET:
++ scst_schedule_tasklet(cmd);
++ break;
++
++ default:
++ PRINT_ERROR("Context %x is undefined, using the thread one",
++ pref_context);
++ /* go through */
++ case SCST_CONTEXT_THREAD:
++ spin_lock_irqsave(&cmd->cmd_threads->cmd_list_lock, flags);
++ TRACE_DBG("Adding cmd %p to active cmd list", cmd);
++ if (unlikely(cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE))
++ list_add(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ else
++ list_add_tail(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock_irqrestore(&cmd->cmd_threads->cmd_list_lock, flags);
++ break;
++
++ case SCST_CONTEXT_DIRECT:
++ scst_process_active_cmd(cmd, false);
++ break;
++
++ case SCST_CONTEXT_DIRECT_ATOMIC:
++ scst_process_active_cmd(cmd, true);
++ break;
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_cmd_init_done);
++
++int scst_pre_parse(struct scst_cmd *cmd)
++{
++ int res;
++ struct scst_device *dev = cmd->dev;
++ int rc;
++
++ TRACE_ENTRY();
++
++ /*
++ * Expected transfer data supplied by the SCSI transport via the
++ * target driver are untrusted, so we prefer to fetch them from CDB.
++ * Additionally, not all transports support supplying the expected
++ * transfer data.
++ */
++
++ rc = scst_get_cdb_info(cmd);
++ if (unlikely(rc != 0)) {
++ if (rc > 0) {
++ PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
++ goto out_err;
++ }
++
++ EXTRACHECKS_BUG_ON(cmd->op_flags & SCST_INFO_VALID);
++
++ TRACE(TRACE_MINOR, "Unknown opcode 0x%02x for %s. "
++ "Should you update scst_scsi_op_table?",
++ cmd->cdb[0], dev->handler->name);
++ PRINT_BUFF_FLAG(TRACE_MINOR, "Failed CDB", cmd->cdb,
++ cmd->cdb_len);
++ } else
++ EXTRACHECKS_BUG_ON(!(cmd->op_flags & SCST_INFO_VALID));
++
++#ifdef CONFIG_SCST_STRICT_SERIALIZING
++ cmd->inc_expected_sn_on_done = 1;
++#else
++ cmd->inc_expected_sn_on_done = dev->handler->exec_sync ||
++ (!dev->has_own_order_mgmt &&
++ (dev->queue_alg == SCST_CONTR_MODE_QUEUE_ALG_RESTRICTED_REORDER ||
++ cmd->queue_type == SCST_CMD_QUEUE_ORDERED));
++#endif
++
++ TRACE_DBG("op_name <%s> (cmd %p), direction=%d "
++ "(expected %d, set %s), bufflen=%d, out_bufflen=%d (expected "
++ "len %d, out expected len %d), flags=0x%x", cmd->op_name, cmd,
++ cmd->data_direction, cmd->expected_data_direction,
++ scst_cmd_is_expected_set(cmd) ? "yes" : "no",
++ cmd->bufflen, cmd->out_bufflen, cmd->expected_transfer_len,
++ cmd->expected_out_transfer_len, cmd->op_flags);
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_err:
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ scst_set_cmd_abnormal_done_state(cmd);
++ res = -1;
++ goto out;
++}
++
++#ifndef CONFIG_SCST_USE_EXPECTED_VALUES
++static bool scst_is_allowed_to_mismatch_cmd(struct scst_cmd *cmd)
++{
++ bool res = false;
++
++ /* VERIFY commands with BYTCHK unset shouldn't fail here */
++ if ((cmd->op_flags & SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED) &&
++ (cmd->cdb[1] & BYTCHK) == 0) {
++ res = true;
++ goto out;
++ }
++
++ switch (cmd->cdb[0]) {
++ case TEST_UNIT_READY:
++ /* Crazy VMware people sometimes do TUR with READ direction */
++ if ((cmd->expected_data_direction == SCST_DATA_READ) ||
++ (cmd->expected_data_direction == SCST_DATA_NONE))
++ res = true;
++ break;
++ }
++
++out:
++ return res;
++}
++#endif
++
++static int scst_parse_cmd(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_RES_CONT_SAME;
++ int state;
++ struct scst_device *dev = cmd->dev;
++ int orig_bufflen = cmd->bufflen;
++
++ TRACE_ENTRY();
++
++ if (likely(!scst_is_cmd_fully_local(cmd))) {
++ if (unlikely(!dev->handler->parse_atomic &&
++ scst_cmd_atomic(cmd))) {
++ /*
++ * It shouldn't be because of the SCST_TGT_DEV_AFTER_*
++ * optimization.
++ */
++ TRACE_MGMT_DBG("Dev handler %s parse() needs thread "
++ "context, rescheduling", dev->handler->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++
++ TRACE_DBG("Calling dev handler %s parse(%p)",
++ dev->handler->name, cmd);
++ TRACE_BUFF_FLAG(TRACE_SND_BOT, "Parsing: ",
++ cmd->cdb, cmd->cdb_len);
++ scst_set_cur_start(cmd);
++ state = dev->handler->parse(cmd);
++ /* Caution: cmd can be already dead here */
++ TRACE_DBG("Dev handler %s parse() returned %d",
++ dev->handler->name, state);
++
++ switch (state) {
++ case SCST_CMD_STATE_NEED_THREAD_CTX:
++ scst_set_parse_time(cmd);
++ TRACE_DBG("Dev handler %s parse() requested thread "
++ "context, rescheduling", dev->handler->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++
++ case SCST_CMD_STATE_STOP:
++ TRACE_DBG("Dev handler %s parse() requested stop "
++ "processing", dev->handler->name);
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ goto out;
++ }
++
++ scst_set_parse_time(cmd);
++
++ if (state == SCST_CMD_STATE_DEFAULT)
++ state = SCST_CMD_STATE_PREPARE_SPACE;
++ } else
++ state = SCST_CMD_STATE_PREPARE_SPACE;
++
++ if (unlikely(state == SCST_CMD_STATE_PRE_XMIT_RESP))
++ goto set_res;
++
++ if (unlikely(!(cmd->op_flags & SCST_INFO_VALID))) {
++#ifdef CONFIG_SCST_USE_EXPECTED_VALUES
++ if (scst_cmd_is_expected_set(cmd)) {
++ TRACE(TRACE_MINOR, "Using initiator supplied values: "
++ "direction %d, transfer_len %d/%d",
++ cmd->expected_data_direction,
++ cmd->expected_transfer_len,
++ cmd->expected_out_transfer_len);
++ cmd->data_direction = cmd->expected_data_direction;
++ cmd->bufflen = cmd->expected_transfer_len;
++ cmd->out_bufflen = cmd->expected_out_transfer_len;
++ } else {
++ PRINT_ERROR("Unknown opcode 0x%02x for %s and "
++ "target %s not supplied expected values",
++ cmd->cdb[0], dev->handler->name, cmd->tgtt->name);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out_done;
++ }
++#else
++ /*
++ * Let's ignore reporting T10/04-262r7 16-byte and 12-byte ATA
++ * pass-thru commands to not pollute logs (udev(?) checks them
++ * for some reason). If somebody has their description, please,
++ * update scst_scsi_op_table.
++ */
++ if ((cmd->cdb[0] != 0x85) && (cmd->cdb[0] != 0xa1))
++ PRINT_ERROR("Refusing unknown opcode %x", cmd->cdb[0]);
++ else
++ TRACE(TRACE_MINOR, "Refusing unknown opcode %x",
++ cmd->cdb[0]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out_done;
++#endif
++ }
++
++ if (unlikely(cmd->cdb_len == 0)) {
++ PRINT_ERROR("Unable to get CDB length for "
++ "opcode 0x%02x. Returning INVALID "
++ "OPCODE", cmd->cdb[0]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out_done;
++ }
++
++ EXTRACHECKS_BUG_ON(cmd->cdb_len == 0);
++
++ TRACE(TRACE_SCSI, "op_name <%s> (cmd %p), direction=%d "
++ "(expected %d, set %s), bufflen=%d, out_bufflen=%d, (expected "
++ "len %d, out expected len %d), flags=%x", cmd->op_name, cmd,
++ cmd->data_direction, cmd->expected_data_direction,
++ scst_cmd_is_expected_set(cmd) ? "yes" : "no",
++ cmd->bufflen, cmd->out_bufflen, cmd->expected_transfer_len,
++ cmd->expected_out_transfer_len, cmd->op_flags);
++
++ if (unlikely((cmd->op_flags & SCST_UNKNOWN_LENGTH) != 0)) {
++ if (scst_cmd_is_expected_set(cmd)) {
++ /*
++ * Command data length can't be easily
++ * determined from the CDB. ToDo, all such
++ * commands processing should be fixed. Until
++ * it's done, get the length from the supplied
++ * expected value, but limit it to some
++ * reasonable value (15MB).
++ */
++ cmd->bufflen = min(cmd->expected_transfer_len,
++ 15*1024*1024);
++ if (cmd->data_direction == SCST_DATA_BIDI)
++ cmd->out_bufflen = min(cmd->expected_out_transfer_len,
++ 15*1024*1024);
++ cmd->op_flags &= ~SCST_UNKNOWN_LENGTH;
++ } else {
++ PRINT_ERROR("Unknown data transfer length for opcode "
++ "0x%x (handler %s, target %s)", cmd->cdb[0],
++ dev->handler->name, cmd->tgtt->name);
++ PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_message));
++ goto out_done;
++ }
++ }
++
++ if (unlikely(cmd->cdb[cmd->cdb_len - 1] & CONTROL_BYTE_NACA_BIT)) {
++ PRINT_ERROR("NACA bit in control byte CDB is not supported "
++ "(opcode 0x%02x)", cmd->cdb[0]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_done;
++ }
++
++ if (unlikely(cmd->cdb[cmd->cdb_len - 1] & CONTROL_BYTE_LINK_BIT)) {
++ PRINT_ERROR("Linked commands are not supported "
++ "(opcode 0x%02x)", cmd->cdb[0]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_done;
++ }
++
++ if (cmd->dh_data_buf_alloced &&
++ unlikely((orig_bufflen > cmd->bufflen))) {
++ PRINT_ERROR("Dev handler supplied data buffer (size %d), "
++ "is less, than required (size %d)", cmd->bufflen,
++ orig_bufflen);
++ PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
++ goto out_hw_error;
++ }
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if ((cmd->bufflen != 0) &&
++ ((cmd->data_direction == SCST_DATA_NONE) ||
++ ((cmd->sg == NULL) && (state > SCST_CMD_STATE_PREPARE_SPACE)))) {
++ PRINT_ERROR("Dev handler %s parse() returned "
++ "invalid cmd data_direction %d, bufflen %d, state %d "
++ "or sg %p (opcode 0x%x)", dev->handler->name,
++ cmd->data_direction, cmd->bufflen, state, cmd->sg,
++ cmd->cdb[0]);
++ PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
++ goto out_hw_error;
++ }
++#endif
++
++ if (scst_cmd_is_expected_set(cmd)) {
++#ifdef CONFIG_SCST_USE_EXPECTED_VALUES
++ if (unlikely((cmd->data_direction != cmd->expected_data_direction) ||
++ (cmd->bufflen != cmd->expected_transfer_len) ||
++ (cmd->out_bufflen != cmd->expected_out_transfer_len))) {
++ TRACE(TRACE_MINOR, "Expected values don't match "
++ "decoded ones: data_direction %d, "
++ "expected_data_direction %d, "
++ "bufflen %d, expected_transfer_len %d, "
++ "out_bufflen %d, expected_out_transfer_len %d",
++ cmd->data_direction,
++ cmd->expected_data_direction,
++ cmd->bufflen, cmd->expected_transfer_len,
++ cmd->out_bufflen, cmd->expected_out_transfer_len);
++ PRINT_BUFF_FLAG(TRACE_MINOR, "Suspicious CDB",
++ cmd->cdb, cmd->cdb_len);
++ cmd->data_direction = cmd->expected_data_direction;
++ cmd->bufflen = cmd->expected_transfer_len;
++ cmd->out_bufflen = cmd->expected_out_transfer_len;
++ cmd->resid_possible = 1;
++ }
++#else
++ if (unlikely(cmd->data_direction !=
++ cmd->expected_data_direction)) {
++ if (((cmd->expected_data_direction != SCST_DATA_NONE) ||
++ (cmd->bufflen != 0)) &&
++ !scst_is_allowed_to_mismatch_cmd(cmd)) {
++ PRINT_ERROR("Expected data direction %d for "
++ "opcode 0x%02x (handler %s, target %s) "
++ "doesn't match decoded value %d",
++ cmd->expected_data_direction,
++ cmd->cdb[0], dev->handler->name,
++ cmd->tgtt->name, cmd->data_direction);
++ PRINT_BUFFER("Failed CDB", cmd->cdb,
++ cmd->cdb_len);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_message));
++ goto out_done;
++ }
++ }
++ if (unlikely(cmd->bufflen != cmd->expected_transfer_len)) {
++ TRACE(TRACE_MINOR, "Warning: expected "
++ "transfer length %d for opcode 0x%02x "
++ "(handler %s, target %s) doesn't match "
++ "decoded value %d",
++ cmd->expected_transfer_len, cmd->cdb[0],
++ dev->handler->name, cmd->tgtt->name,
++ cmd->bufflen);
++ PRINT_BUFF_FLAG(TRACE_MINOR, "Suspicious CDB",
++ cmd->cdb, cmd->cdb_len);
++ if ((cmd->data_direction & SCST_DATA_READ) ||
++ (cmd->data_direction & SCST_DATA_WRITE))
++ cmd->resid_possible = 1;
++ }
++ if (unlikely(cmd->out_bufflen != cmd->expected_out_transfer_len)) {
++ TRACE(TRACE_MINOR, "Warning: expected bidirectional OUT "
++ "transfer length %d for opcode 0x%02x "
++ "(handler %s, target %s) doesn't match "
++ "decoded value %d",
++ cmd->expected_out_transfer_len, cmd->cdb[0],
++ dev->handler->name, cmd->tgtt->name,
++ cmd->out_bufflen);
++ PRINT_BUFF_FLAG(TRACE_MINOR, "Suspicious CDB",
++ cmd->cdb, cmd->cdb_len);
++ cmd->resid_possible = 1;
++ }
++#endif
++ }
++
++ if (unlikely(cmd->data_direction == SCST_DATA_UNKNOWN)) {
++ PRINT_ERROR("Unknown data direction. Opcode 0x%x, handler %s, "
++ "target %s", cmd->cdb[0], dev->handler->name,
++ cmd->tgtt->name);
++ PRINT_BUFFER("Failed CDB", cmd->cdb, cmd->cdb_len);
++ goto out_hw_error;
++ }
++
++set_res:
++ if (cmd->data_len == -1)
++ cmd->data_len = cmd->bufflen;
++
++ if (cmd->bufflen == 0) {
++ /*
++ * According to SPC bufflen 0 for data transfer commands isn't
++ * an error, so we need to fix the transfer direction.
++ */
++ cmd->data_direction = SCST_DATA_NONE;
++ }
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ switch (state) {
++ case SCST_CMD_STATE_PREPARE_SPACE:
++ case SCST_CMD_STATE_PARSE:
++ case SCST_CMD_STATE_RDY_TO_XFER:
++ case SCST_CMD_STATE_TGT_PRE_EXEC:
++ case SCST_CMD_STATE_SEND_FOR_EXEC:
++ case SCST_CMD_STATE_START_EXEC:
++ case SCST_CMD_STATE_LOCAL_EXEC:
++ case SCST_CMD_STATE_REAL_EXEC:
++ case SCST_CMD_STATE_PRE_DEV_DONE:
++ case SCST_CMD_STATE_DEV_DONE:
++ case SCST_CMD_STATE_PRE_XMIT_RESP:
++ case SCST_CMD_STATE_XMIT_RESP:
++ case SCST_CMD_STATE_FINISHED:
++ case SCST_CMD_STATE_FINISHED_INTERNAL:
++#endif
++ cmd->state = state;
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++#ifdef CONFIG_SCST_EXTRACHECKS
++ break;
++
++ default:
++ if (state >= 0) {
++ PRINT_ERROR("Dev handler %s parse() returned "
++ "invalid cmd state %d (opcode %d)",
++ dev->handler->name, state, cmd->cdb[0]);
++ } else {
++ PRINT_ERROR("Dev handler %s parse() returned "
++ "error %d (opcode %d)", dev->handler->name,
++ state, cmd->cdb[0]);
++ }
++ goto out_hw_error;
++ }
++#endif
++
++ if (cmd->resp_data_len == -1) {
++ if (cmd->data_direction & SCST_DATA_READ)
++ cmd->resp_data_len = cmd->bufflen;
++ else
++ cmd->resp_data_len = 0;
++ }
++
++ /* We already completed (with an error) */
++ if (unlikely(cmd->completed))
++ goto out_done;
++
++#ifndef CONFIG_SCST_TEST_IO_IN_SIRQ
++ /*
++ * We can't allow atomic command on the exec stages. It shouldn't
++ * be because of the SCST_TGT_DEV_AFTER_* optimization, but during
++ * parsing data_direction can change, so we need to recheck.
++ */
++ if (unlikely(scst_cmd_atomic(cmd) &&
++ !(cmd->data_direction & SCST_DATA_WRITE))) {
++ TRACE_DBG_FLAG(TRACE_DEBUG|TRACE_MINOR, "Atomic context and "
++ "non-WRITE data direction, rescheduling (cmd %p)", cmd);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++#endif
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++
++out_hw_error:
++ /* dev_done() will be called as part of the regular cmd's finish */
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++
++out_done:
++ scst_set_cmd_abnormal_done_state(cmd);
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++ goto out;
++}
++
++static void scst_set_write_len(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(!(cmd->data_direction & SCST_DATA_WRITE));
++
++ if (cmd->data_direction & SCST_DATA_READ) {
++ cmd->write_len = cmd->out_bufflen;
++ cmd->write_sg = &cmd->out_sg;
++ cmd->write_sg_cnt = &cmd->out_sg_cnt;
++ } else {
++ cmd->write_len = cmd->bufflen;
++ /* write_sg and write_sg_cnt already initialized correctly */
++ }
++
++ TRACE_MEM("cmd %p, write_len %d, write_sg %p, write_sg_cnt %d, "
++ "resid_possible %d", cmd, cmd->write_len, *cmd->write_sg,
++ *cmd->write_sg_cnt, cmd->resid_possible);
++
++ if (unlikely(cmd->resid_possible)) {
++ if (cmd->data_direction & SCST_DATA_READ) {
++ cmd->write_len = min(cmd->out_bufflen,
++ cmd->expected_out_transfer_len);
++ if (cmd->write_len == cmd->out_bufflen)
++ goto out;
++ } else {
++ cmd->write_len = min(cmd->bufflen,
++ cmd->expected_transfer_len);
++ if (cmd->write_len == cmd->bufflen)
++ goto out;
++ }
++ scst_limit_sg_write_len(cmd);
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int scst_prepare_space(struct scst_cmd *cmd)
++{
++ int r = 0, res = SCST_CMD_STATE_RES_CONT_SAME;
++ struct scst_device *dev = cmd->dev;
++
++ TRACE_ENTRY();
++
++ if (cmd->data_direction == SCST_DATA_NONE)
++ goto done;
++
++ if (likely(!scst_is_cmd_fully_local(cmd)) &&
++ (dev->handler->alloc_data_buf != NULL)) {
++ int state;
++
++ if (unlikely(!dev->handler->alloc_data_buf_atomic &&
++ scst_cmd_atomic(cmd))) {
++ /*
++ * It shouldn't be because of the SCST_TGT_DEV_AFTER_*
++ * optimization.
++ */
++ TRACE_MGMT_DBG("Dev handler %s alloc_data_buf() needs "
++ "thread context, rescheduling",
++ dev->handler->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++
++ TRACE_DBG("Calling dev handler %s alloc_data_buf(%p)",
++ dev->handler->name, cmd);
++ scst_set_cur_start(cmd);
++ state = dev->handler->alloc_data_buf(cmd);
++ /* Caution: cmd can be already dead here */
++ TRACE_DBG("Dev handler %s alloc_data_buf() returned %d",
++ dev->handler->name, state);
++
++ switch (state) {
++ case SCST_CMD_STATE_NEED_THREAD_CTX:
++ scst_set_alloc_buf_time(cmd);
++ TRACE_DBG("Dev handler %s alloc_data_buf() requested "
++ "thread context, rescheduling",
++ dev->handler->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++
++ case SCST_CMD_STATE_STOP:
++ TRACE_DBG("Dev handler %s alloc_data_buf() requested "
++ "stop processing", dev->handler->name);
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ goto out;
++ }
++
++ scst_set_alloc_buf_time(cmd);
++
++ if (unlikely(state != SCST_CMD_STATE_DEFAULT)) {
++ cmd->state = state;
++ goto out;
++ }
++ }
++
++ if (cmd->tgt_need_alloc_data_buf) {
++ int orig_bufflen = cmd->bufflen;
++
++ TRACE_MEM("Custom tgt data buf allocation requested (cmd %p)",
++ cmd);
++
++ scst_set_cur_start(cmd);
++ r = cmd->tgtt->alloc_data_buf(cmd);
++ scst_set_alloc_buf_time(cmd);
++
++ if (r > 0)
++ goto alloc;
++ else if (r == 0) {
++ if (unlikely(cmd->bufflen == 0)) {
++ /* See comment in scst_alloc_space() */
++ if (cmd->sg == NULL)
++ goto alloc;
++ }
++
++ cmd->tgt_data_buf_alloced = 1;
++
++ if (unlikely(orig_bufflen < cmd->bufflen)) {
++ PRINT_ERROR("Target driver allocated data "
++ "buffer (size %d), is less, than "
++ "required (size %d)", orig_bufflen,
++ cmd->bufflen);
++ goto out_error;
++ }
++ TRACE_MEM("tgt_data_buf_alloced (cmd %p)", cmd);
++ } else
++ goto check;
++ }
++
++alloc:
++ if (!cmd->tgt_data_buf_alloced && !cmd->dh_data_buf_alloced) {
++ r = scst_alloc_space(cmd);
++ } else if (cmd->dh_data_buf_alloced && !cmd->tgt_data_buf_alloced) {
++ TRACE_MEM("dh_data_buf_alloced set (cmd %p)", cmd);
++ r = 0;
++ } else if (cmd->tgt_data_buf_alloced && !cmd->dh_data_buf_alloced) {
++ TRACE_MEM("tgt_data_buf_alloced set (cmd %p)", cmd);
++ cmd->sg = cmd->tgt_sg;
++ cmd->sg_cnt = cmd->tgt_sg_cnt;
++ cmd->out_sg = cmd->tgt_out_sg;
++ cmd->out_sg_cnt = cmd->tgt_out_sg_cnt;
++ r = 0;
++ } else {
++ TRACE_MEM("Both *_data_buf_alloced set (cmd %p, sg %p, "
++ "sg_cnt %d, tgt_sg %p, tgt_sg_cnt %d)", cmd, cmd->sg,
++ cmd->sg_cnt, cmd->tgt_sg, cmd->tgt_sg_cnt);
++ r = 0;
++ }
++
++check:
++ if (r != 0) {
++ if (scst_cmd_atomic(cmd)) {
++ TRACE_MEM("%s", "Atomic memory allocation failed, "
++ "rescheduling to the thread");
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ } else
++ goto out_no_space;
++ }
++
++done:
++ if (cmd->preprocessing_only) {
++ cmd->state = SCST_CMD_STATE_PREPROCESSING_DONE;
++ if (cmd->data_direction & SCST_DATA_WRITE)
++ scst_set_write_len(cmd);
++ } else if (cmd->data_direction & SCST_DATA_WRITE) {
++ cmd->state = SCST_CMD_STATE_RDY_TO_XFER;
++ scst_set_write_len(cmd);
++ } else
++ cmd->state = SCST_CMD_STATE_TGT_PRE_EXEC;
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++
++out_no_space:
++ TRACE(TRACE_OUT_OF_MEM, "Unable to allocate or build requested buffer "
++ "(size %d), sending BUSY or QUEUE FULL status", cmd->bufflen);
++ scst_set_busy(cmd);
++ scst_set_cmd_abnormal_done_state(cmd);
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++ goto out;
++
++out_error:
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ scst_set_cmd_abnormal_done_state(cmd);
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++ goto out;
++}
++
++static int scst_preprocessing_done(struct scst_cmd *cmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(!cmd->preprocessing_only);
++
++ cmd->preprocessing_only = 0;
++
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ cmd->state = SCST_CMD_STATE_PREPROCESSING_DONE_CALLED;
++
++ TRACE_DBG("Calling preprocessing_done(cmd %p)", cmd);
++ scst_set_cur_start(cmd);
++ cmd->tgtt->preprocessing_done(cmd);
++ TRACE_DBG("%s", "preprocessing_done() returned");
++
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/**
++ * scst_restart_cmd() - restart execution of the command
++ * @cmd: SCST commands
++ * @status: completion status
++ * @pref_context: preferred command execution context
++ *
++ * Description:
++ * Notifies SCST that the driver finished its part of the command's
++ * preprocessing and it is ready for further processing.
++ *
++ * The second argument sets completion status
++ * (see SCST_PREPROCESS_STATUS_* constants for details)
++ *
++ * See also comment for scst_cmd_init_done() for the serialization
++ * requirements.
++ */
++void scst_restart_cmd(struct scst_cmd *cmd, int status,
++ enum scst_exec_context pref_context)
++{
++ TRACE_ENTRY();
++
++ scst_set_restart_waiting_time(cmd);
++
++ TRACE_DBG("Preferred context: %d", pref_context);
++ TRACE_DBG("tag=%llu, status=%#x",
++ (long long unsigned int)scst_cmd_get_tag(cmd),
++ status);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if ((in_irq() || irqs_disabled()) &&
++ ((pref_context == SCST_CONTEXT_DIRECT) ||
++ (pref_context == SCST_CONTEXT_DIRECT_ATOMIC))) {
++ PRINT_ERROR("Wrong context %d in IRQ from target %s, use "
++ "SCST_CONTEXT_THREAD instead", pref_context,
++ cmd->tgtt->name);
++ dump_stack();
++ pref_context = SCST_CONTEXT_THREAD;
++ }
++#endif
++
++ switch (status) {
++ case SCST_PREPROCESS_STATUS_SUCCESS:
++ if (cmd->data_direction & SCST_DATA_WRITE)
++ cmd->state = SCST_CMD_STATE_RDY_TO_XFER;
++ else
++ cmd->state = SCST_CMD_STATE_TGT_PRE_EXEC;
++ if (cmd->set_sn_on_restart_cmd)
++ scst_cmd_set_sn(cmd);
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ if (cmd->op_flags & SCST_TEST_IO_IN_SIRQ_ALLOWED)
++ break;
++#endif
++ /* Small context optimization */
++ if ((pref_context == SCST_CONTEXT_TASKLET) ||
++ (pref_context == SCST_CONTEXT_DIRECT_ATOMIC) ||
++ ((pref_context == SCST_CONTEXT_SAME) &&
++ scst_cmd_atomic(cmd)))
++ pref_context = SCST_CONTEXT_THREAD;
++ break;
++
++ case SCST_PREPROCESS_STATUS_ERROR_SENSE_SET:
++ scst_set_cmd_abnormal_done_state(cmd);
++ pref_context = SCST_CONTEXT_THREAD;
++ break;
++
++ case SCST_PREPROCESS_STATUS_ERROR_FATAL:
++ set_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags);
++ /* go through */
++ case SCST_PREPROCESS_STATUS_ERROR:
++ if (cmd->sense != NULL)
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ scst_set_cmd_abnormal_done_state(cmd);
++ pref_context = SCST_CONTEXT_THREAD;
++ break;
++
++ default:
++ PRINT_ERROR("%s() received unknown status %x", __func__,
++ status);
++ scst_set_cmd_abnormal_done_state(cmd);
++ pref_context = SCST_CONTEXT_THREAD;
++ break;
++ }
++
++ scst_process_redirect_cmd(cmd, pref_context, 1);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_restart_cmd);
++
++static int scst_rdy_to_xfer(struct scst_cmd *cmd)
++{
++ int res, rc;
++ struct scst_tgt_template *tgtt = cmd->tgtt;
++
++ TRACE_ENTRY();
++
++ if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) {
++ TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd);
++ goto out_dev_done;
++ }
++
++ if ((tgtt->rdy_to_xfer == NULL) || unlikely(cmd->internal)) {
++ cmd->state = SCST_CMD_STATE_TGT_PRE_EXEC;
++#ifndef CONFIG_SCST_TEST_IO_IN_SIRQ
++ /* We can't allow atomic command on the exec stages */
++ if (scst_cmd_atomic(cmd)) {
++ TRACE_DBG("NULL rdy_to_xfer() and atomic context, "
++ "rescheduling (cmd %p)", cmd);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ } else
++#endif
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++ goto out;
++ }
++
++ if (unlikely(!tgtt->rdy_to_xfer_atomic && scst_cmd_atomic(cmd))) {
++ /*
++ * It shouldn't be because of the SCST_TGT_DEV_AFTER_*
++ * optimization.
++ */
++ TRACE_MGMT_DBG("Target driver %s rdy_to_xfer() needs thread "
++ "context, rescheduling", tgtt->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++
++ while (1) {
++ int finished_cmds = atomic_read(&cmd->tgt->finished_cmds);
++
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ cmd->state = SCST_CMD_STATE_DATA_WAIT;
++
++ if (tgtt->on_hw_pending_cmd_timeout != NULL) {
++ struct scst_session *sess = cmd->sess;
++ cmd->hw_pending_start = jiffies;
++ cmd->cmd_hw_pending = 1;
++ if (!test_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags)) {
++ TRACE_DBG("Sched HW pending work for sess %p "
++ "(max time %d)", sess,
++ tgtt->max_hw_pending_time);
++ set_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED,
++ &sess->sess_aflags);
++ schedule_delayed_work(&sess->hw_pending_work,
++ tgtt->max_hw_pending_time * HZ);
++ }
++ }
++
++ scst_set_cur_start(cmd);
++
++ TRACE_DBG("Calling rdy_to_xfer(%p)", cmd);
++#ifdef CONFIG_SCST_DEBUG_RETRY
++ if (((scst_random() % 100) == 75))
++ rc = SCST_TGT_RES_QUEUE_FULL;
++ else
++#endif
++ rc = tgtt->rdy_to_xfer(cmd);
++ TRACE_DBG("rdy_to_xfer() returned %d", rc);
++
++ if (likely(rc == SCST_TGT_RES_SUCCESS))
++ goto out;
++
++ scst_set_rdy_to_xfer_time(cmd);
++
++ cmd->cmd_hw_pending = 0;
++
++ /* Restore the previous state */
++ cmd->state = SCST_CMD_STATE_RDY_TO_XFER;
++
++ switch (rc) {
++ case SCST_TGT_RES_QUEUE_FULL:
++ if (scst_queue_retry_cmd(cmd, finished_cmds) == 0)
++ break;
++ else
++ continue;
++
++ case SCST_TGT_RES_NEED_THREAD_CTX:
++ TRACE_DBG("Target driver %s "
++ "rdy_to_xfer() requested thread "
++ "context, rescheduling", tgtt->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++
++ default:
++ goto out_error_rc;
++ }
++ break;
++ }
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++
++out_error_rc:
++ if (rc == SCST_TGT_RES_FATAL_ERROR) {
++ PRINT_ERROR("Target driver %s rdy_to_xfer() returned "
++ "fatal error", tgtt->name);
++ } else {
++ PRINT_ERROR("Target driver %s rdy_to_xfer() returned invalid "
++ "value %d", tgtt->name, rc);
++ }
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++
++out_dev_done:
++ scst_set_cmd_abnormal_done_state(cmd);
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++ goto out;
++}
++
++/* No locks, but might be in IRQ */
++static void scst_process_redirect_cmd(struct scst_cmd *cmd,
++ enum scst_exec_context context, int check_retries)
++{
++ struct scst_tgt *tgt = cmd->tgt;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Context: %x", context);
++
++ if (check_retries)
++ scst_check_retries(tgt);
++
++ if (context == SCST_CONTEXT_SAME)
++ context = scst_cmd_atomic(cmd) ? SCST_CONTEXT_DIRECT_ATOMIC :
++ SCST_CONTEXT_DIRECT;
++
++ switch (context) {
++ case SCST_CONTEXT_DIRECT_ATOMIC:
++ scst_process_active_cmd(cmd, true);
++ break;
++
++ case SCST_CONTEXT_DIRECT:
++ scst_process_active_cmd(cmd, false);
++ break;
++
++ case SCST_CONTEXT_TASKLET:
++ scst_schedule_tasklet(cmd);
++ break;
++
++ default:
++ PRINT_ERROR("Context %x is unknown, using the thread one",
++ context);
++ /* go through */
++ case SCST_CONTEXT_THREAD:
++ spin_lock_irqsave(&cmd->cmd_threads->cmd_list_lock, flags);
++ TRACE_DBG("Adding cmd %p to active cmd list", cmd);
++ if (unlikely(cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE))
++ list_add(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ else
++ list_add_tail(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock_irqrestore(&cmd->cmd_threads->cmd_list_lock, flags);
++ break;
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_rx_data() - the command's data received
++ * @cmd: SCST commands
++ * @status: data receiving completion status
++ * @pref_context: preferred command execution context
++ *
++ * Description:
++ * Notifies SCST that the driver received all the necessary data
++ * and the command is ready for further processing.
++ *
++ * The second argument sets data receiving completion status
++ * (see SCST_RX_STATUS_* constants for details)
++ */
++void scst_rx_data(struct scst_cmd *cmd, int status,
++ enum scst_exec_context pref_context)
++{
++ TRACE_ENTRY();
++
++ scst_set_rdy_to_xfer_time(cmd);
++
++ TRACE_DBG("Preferred context: %d", pref_context);
++ TRACE(TRACE_SCSI, "cmd %p, status %#x", cmd, status);
++
++ cmd->cmd_hw_pending = 0;
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if ((in_irq() || irqs_disabled()) &&
++ ((pref_context == SCST_CONTEXT_DIRECT) ||
++ (pref_context == SCST_CONTEXT_DIRECT_ATOMIC))) {
++ PRINT_ERROR("Wrong context %d in IRQ from target %s, use "
++ "SCST_CONTEXT_THREAD instead", pref_context,
++ cmd->tgtt->name);
++ dump_stack();
++ pref_context = SCST_CONTEXT_THREAD;
++ }
++#endif
++
++ switch (status) {
++ case SCST_RX_STATUS_SUCCESS:
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ if (trace_flag & TRACE_RCV_BOT) {
++ int i, j;
++ struct scatterlist *sg;
++ if (cmd->out_sg != NULL)
++ sg = cmd->out_sg;
++ else if (cmd->tgt_out_sg != NULL)
++ sg = cmd->tgt_out_sg;
++ else if (cmd->tgt_sg != NULL)
++ sg = cmd->tgt_sg;
++ else
++ sg = cmd->sg;
++ if (sg != NULL) {
++ TRACE_RECV_BOT("RX data for cmd %p "
++ "(sg_cnt %d, sg %p, sg[0].page %p)",
++ cmd, cmd->tgt_sg_cnt, sg,
++ (void *)sg_page(&sg[0]));
++ for (i = 0, j = 0; i < cmd->tgt_sg_cnt; ++i, ++j) {
++ if (unlikely(sg_is_chain(&sg[j]))) {
++ sg = sg_chain_ptr(&sg[j]);
++ j = 0;
++ }
++ PRINT_BUFF_FLAG(TRACE_RCV_BOT, "RX sg",
++ sg_virt(&sg[j]), sg[j].length);
++ }
++ }
++ }
++#endif
++ cmd->state = SCST_CMD_STATE_TGT_PRE_EXEC;
++
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ if (cmd->op_flags & SCST_TEST_IO_IN_SIRQ_ALLOWED)
++ break;
++#endif
++
++ /* Small context optimization */
++ if ((pref_context == SCST_CONTEXT_TASKLET) ||
++ (pref_context == SCST_CONTEXT_DIRECT_ATOMIC) ||
++ ((pref_context == SCST_CONTEXT_SAME) &&
++ scst_cmd_atomic(cmd)))
++ pref_context = SCST_CONTEXT_THREAD;
++ break;
++
++ case SCST_RX_STATUS_ERROR_SENSE_SET:
++ scst_set_cmd_abnormal_done_state(cmd);
++ pref_context = SCST_CONTEXT_THREAD;
++ break;
++
++ case SCST_RX_STATUS_ERROR_FATAL:
++ set_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags);
++ /* go through */
++ case SCST_RX_STATUS_ERROR:
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ scst_set_cmd_abnormal_done_state(cmd);
++ pref_context = SCST_CONTEXT_THREAD;
++ break;
++
++ default:
++ PRINT_ERROR("scst_rx_data() received unknown status %x",
++ status);
++ scst_set_cmd_abnormal_done_state(cmd);
++ pref_context = SCST_CONTEXT_THREAD;
++ break;
++ }
++
++ scst_process_redirect_cmd(cmd, pref_context, 1);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_rx_data);
++
++static int scst_tgt_pre_exec(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_RES_CONT_SAME, rc;
++
++ TRACE_ENTRY();
++
++ if (unlikely(cmd->resid_possible)) {
++ if (cmd->data_direction & SCST_DATA_WRITE) {
++ bool do_zero = false;
++ if (cmd->data_direction & SCST_DATA_READ) {
++ if (cmd->write_len != cmd->out_bufflen)
++ do_zero = true;
++ } else {
++ if (cmd->write_len != cmd->bufflen)
++ do_zero = true;
++ }
++ if (do_zero) {
++ scst_check_restore_sg_buff(cmd);
++ scst_zero_write_rest(cmd);
++ }
++ }
++ }
++
++ cmd->state = SCST_CMD_STATE_SEND_FOR_EXEC;
++
++ if ((cmd->tgtt->pre_exec == NULL) || unlikely(cmd->internal))
++ goto out;
++
++ TRACE_DBG("Calling pre_exec(%p)", cmd);
++ scst_set_cur_start(cmd);
++ rc = cmd->tgtt->pre_exec(cmd);
++ scst_set_pre_exec_time(cmd);
++ TRACE_DBG("pre_exec() returned %d", rc);
++
++ if (unlikely(rc != SCST_PREPROCESS_STATUS_SUCCESS)) {
++ switch (rc) {
++ case SCST_PREPROCESS_STATUS_ERROR_SENSE_SET:
++ scst_set_cmd_abnormal_done_state(cmd);
++ break;
++ case SCST_PREPROCESS_STATUS_ERROR_FATAL:
++ set_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags);
++ /* go through */
++ case SCST_PREPROCESS_STATUS_ERROR:
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ scst_set_cmd_abnormal_done_state(cmd);
++ break;
++ default:
++ BUG();
++ break;
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void scst_do_cmd_done(struct scst_cmd *cmd, int result,
++ const uint8_t *rq_sense, int rq_sense_len, int resid)
++{
++ TRACE_ENTRY();
++
++ scst_set_exec_time(cmd);
++
++ cmd->status = result & 0xff;
++ cmd->msg_status = msg_byte(result);
++ cmd->host_status = host_byte(result);
++ cmd->driver_status = driver_byte(result);
++ if (unlikely(resid != 0)) {
++ if ((cmd->data_direction & SCST_DATA_READ) &&
++ (resid > 0) && (resid < cmd->resp_data_len))
++ scst_set_resp_data_len(cmd, cmd->resp_data_len - resid);
++ /*
++ * We ignore write direction residue, because from the
++ * initiator's POV we already transferred all the data.
++ */
++ }
++
++ if (unlikely(cmd->status == SAM_STAT_CHECK_CONDITION)) {
++ /* We might have double reset UA here */
++ cmd->dbl_ua_orig_resp_data_len = cmd->resp_data_len;
++ cmd->dbl_ua_orig_data_direction = cmd->data_direction;
++
++ scst_alloc_set_sense(cmd, 1, rq_sense, rq_sense_len);
++ }
++
++ TRACE(TRACE_SCSI, "cmd %p, result %x, cmd->status %x, resid %d, "
++ "cmd->msg_status %x, cmd->host_status %x, "
++ "cmd->driver_status %x", cmd, result, cmd->status, resid,
++ cmd->msg_status, cmd->host_status, cmd->driver_status);
++
++ cmd->completed = 1;
++
++ TRACE_EXIT();
++ return;
++}
++
++/* For small context optimization */
++static inline enum scst_exec_context scst_optimize_post_exec_context(
++ struct scst_cmd *cmd, enum scst_exec_context context)
++{
++ if (((context == SCST_CONTEXT_SAME) && scst_cmd_atomic(cmd)) ||
++ (context == SCST_CONTEXT_TASKLET) ||
++ (context == SCST_CONTEXT_DIRECT_ATOMIC)) {
++ if (!test_bit(SCST_TGT_DEV_AFTER_EXEC_ATOMIC,
++ &cmd->tgt_dev->tgt_dev_flags))
++ context = SCST_CONTEXT_THREAD;
++ }
++ return context;
++}
++
++/**
++ * scst_pass_through_cmd_done - done callback for pass-through commands
++ * @data: private opaque data
++ * @sense: pointer to the sense data, if any
++ * @result: command's execution result
++ * @resid: residual, if any
++ */
++void scst_pass_through_cmd_done(void *data, char *sense, int result, int resid)
++{
++ struct scst_cmd *cmd;
++
++ TRACE_ENTRY();
++
++ cmd = (struct scst_cmd *)data;
++ if (cmd == NULL)
++ goto out;
++
++ scst_do_cmd_done(cmd, result, sense, SCSI_SENSE_BUFFERSIZE, resid);
++
++ cmd->state = SCST_CMD_STATE_PRE_DEV_DONE;
++
++ scst_process_redirect_cmd(cmd,
++ scst_optimize_post_exec_context(cmd, scst_estimate_context()), 0);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_pass_through_cmd_done);
++
++static void scst_cmd_done_local(struct scst_cmd *cmd, int next_state,
++ enum scst_exec_context pref_context)
++{
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(cmd->pr_abort_counter != NULL);
++
++ scst_set_exec_time(cmd);
++
++ TRACE(TRACE_SCSI, "cmd %p, status %x, msg_status %x, host_status %x, "
++ "driver_status %x, resp_data_len %d", cmd, cmd->status,
++ cmd->msg_status, cmd->host_status, cmd->driver_status,
++ cmd->resp_data_len);
++
++ if (next_state == SCST_CMD_STATE_DEFAULT)
++ next_state = SCST_CMD_STATE_PRE_DEV_DONE;
++
++#if defined(CONFIG_SCST_DEBUG)
++ if (next_state == SCST_CMD_STATE_PRE_DEV_DONE) {
++ if ((trace_flag & TRACE_RCV_TOP) && (cmd->sg != NULL)) {
++ int i, j;
++ struct scatterlist *sg = cmd->sg;
++ TRACE_RECV_TOP("Exec'd %d S/G(s) at %p sg[0].page at "
++ "%p", cmd->sg_cnt, sg, (void *)sg_page(&sg[0]));
++ for (i = 0, j = 0; i < cmd->sg_cnt; ++i, ++j) {
++ if (unlikely(sg_is_chain(&sg[j]))) {
++ sg = sg_chain_ptr(&sg[j]);
++ j = 0;
++ }
++ TRACE_BUFF_FLAG(TRACE_RCV_TOP,
++ "Exec'd sg", sg_virt(&sg[j]),
++ sg[j].length);
++ }
++ }
++ }
++#endif
++
++ cmd->state = next_state;
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if ((next_state != SCST_CMD_STATE_PRE_DEV_DONE) &&
++ (next_state != SCST_CMD_STATE_PRE_XMIT_RESP) &&
++ (next_state != SCST_CMD_STATE_FINISHED) &&
++ (next_state != SCST_CMD_STATE_FINISHED_INTERNAL)) {
++ PRINT_ERROR("%s() received invalid cmd state %d (opcode %d)",
++ __func__, next_state, cmd->cdb[0]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ scst_set_cmd_abnormal_done_state(cmd);
++ }
++#endif
++ pref_context = scst_optimize_post_exec_context(cmd, pref_context);
++ scst_process_redirect_cmd(cmd, pref_context, 0);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int scst_report_luns_local(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_COMPLETED, rc;
++ int dev_cnt = 0;
++ int buffer_size;
++ int i;
++ struct scst_tgt_dev *tgt_dev = NULL;
++ uint8_t *buffer;
++ int offs, overflow = 0;
++
++ TRACE_ENTRY();
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++
++ if ((cmd->cdb[2] != 0) && (cmd->cdb[2] != 2)) {
++ PRINT_ERROR("Unsupported SELECT REPORT value %x in REPORT "
++ "LUNS command", cmd->cdb[2]);
++ goto out_err;
++ }
++
++ buffer_size = scst_get_buf_full(cmd, &buffer);
++ if (unlikely(buffer_size == 0))
++ goto out_compl;
++ else if (unlikely(buffer_size < 0))
++ goto out_hw_err;
++
++ if (buffer_size < 16)
++ goto out_put_err;
++
++ memset(buffer, 0, buffer_size);
++ offs = 8;
++
++ /*
++ * cmd won't allow to suspend activities, so we can access
++ * sess->sess_tgt_dev_list without any additional protection.
++ */
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &cmd->sess->sess_tgt_dev_list[i];
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ if (!overflow) {
++ if ((buffer_size - offs) < 8) {
++ overflow = 1;
++ goto inc_dev_cnt;
++ }
++ *(__force __be64 *)&buffer[offs]
++ = scst_pack_lun(tgt_dev->lun,
++ cmd->sess->acg->addr_method);
++ offs += 8;
++ }
++inc_dev_cnt:
++ dev_cnt++;
++ }
++ }
++
++ /* Set the response header */
++ dev_cnt *= 8;
++ buffer[0] = (dev_cnt >> 24) & 0xff;
++ buffer[1] = (dev_cnt >> 16) & 0xff;
++ buffer[2] = (dev_cnt >> 8) & 0xff;
++ buffer[3] = dev_cnt & 0xff;
++
++ scst_put_buf_full(cmd, buffer);
++
++ dev_cnt += 8;
++ if (dev_cnt < cmd->resp_data_len)
++ scst_set_resp_data_len(cmd, dev_cnt);
++
++out_compl:
++ cmd->completed = 1;
++
++ /* Clear left sense_reported_luns_data_changed UA, if any. */
++
++ /*
++ * cmd won't allow to suspend activities, so we can access
++ * sess->sess_tgt_dev_list without any additional protection.
++ */
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &cmd->sess->sess_tgt_dev_list[i];
++
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ struct scst_tgt_dev_UA *ua;
++
++ spin_lock_bh(&tgt_dev->tgt_dev_lock);
++ list_for_each_entry(ua, &tgt_dev->UA_list,
++ UA_list_entry) {
++ if (scst_analyze_sense(ua->UA_sense_buffer,
++ ua->UA_valid_sense_len,
++ SCST_SENSE_ALL_VALID,
++ SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed))) {
++ TRACE_MGMT_DBG("Freeing not needed "
++ "REPORTED LUNS DATA CHANGED UA "
++ "%p", ua);
++ scst_tgt_dev_del_free_UA(tgt_dev, ua);
++ break;
++ }
++ }
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++ }
++ }
++
++out_done:
++ /* Report the result */
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_put_err:
++ scst_put_buf_full(cmd, buffer);
++
++out_err:
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_compl;
++
++out_hw_err:
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_compl;
++}
++
++static int scst_request_sense_local(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_COMPLETED, rc;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ uint8_t *buffer;
++ int buffer_size = 0, sl = 0;
++
++ TRACE_ENTRY();
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++
++ spin_lock_bh(&tgt_dev->tgt_dev_lock);
++
++ if (tgt_dev->tgt_dev_valid_sense_len == 0)
++ goto out_unlock_not_completed;
++
++ TRACE(TRACE_SCSI, "%s: Returning stored sense", cmd->op_name);
++
++ buffer_size = scst_get_buf_full(cmd, &buffer);
++ if (unlikely(buffer_size == 0))
++ goto out_unlock_compl;
++ else if (unlikely(buffer_size < 0))
++ goto out_unlock_hw_err;
++
++ memset(buffer, 0, buffer_size);
++
++ if (((tgt_dev->tgt_dev_sense[0] == 0x70) ||
++ (tgt_dev->tgt_dev_sense[0] == 0x71)) && (cmd->cdb[1] & 1)) {
++ PRINT_WARNING("%s: Fixed format of the saved sense, but "
++ "descriptor format requested. Conversion will "
++ "truncated data", cmd->op_name);
++ PRINT_BUFFER("Original sense", tgt_dev->tgt_dev_sense,
++ tgt_dev->tgt_dev_valid_sense_len);
++
++ buffer_size = min(SCST_STANDARD_SENSE_LEN, buffer_size);
++ sl = scst_set_sense(buffer, buffer_size, true,
++ tgt_dev->tgt_dev_sense[2], tgt_dev->tgt_dev_sense[12],
++ tgt_dev->tgt_dev_sense[13]);
++ } else if (((tgt_dev->tgt_dev_sense[0] == 0x72) ||
++ (tgt_dev->tgt_dev_sense[0] == 0x73)) && !(cmd->cdb[1] & 1)) {
++ PRINT_WARNING("%s: Descriptor format of the "
++ "saved sense, but fixed format requested. Conversion "
++ "will truncated data", cmd->op_name);
++ PRINT_BUFFER("Original sense", tgt_dev->tgt_dev_sense,
++ tgt_dev->tgt_dev_valid_sense_len);
++
++ buffer_size = min(SCST_STANDARD_SENSE_LEN, buffer_size);
++ sl = scst_set_sense(buffer, buffer_size, false,
++ tgt_dev->tgt_dev_sense[1], tgt_dev->tgt_dev_sense[2],
++ tgt_dev->tgt_dev_sense[3]);
++ } else {
++ if (buffer_size >= tgt_dev->tgt_dev_valid_sense_len)
++ sl = tgt_dev->tgt_dev_valid_sense_len;
++ else {
++ sl = buffer_size;
++ TRACE(TRACE_MINOR, "%s: Being returned sense truncated "
++ "to size %d (needed %d)", cmd->op_name,
++ buffer_size, tgt_dev->tgt_dev_valid_sense_len);
++ }
++ memcpy(buffer, tgt_dev->tgt_dev_sense, sl);
++ }
++
++ scst_put_buf_full(cmd, buffer);
++
++ tgt_dev->tgt_dev_valid_sense_len = 0;
++
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++
++ scst_set_resp_data_len(cmd, sl);
++
++out_compl:
++ cmd->completed = 1;
++
++out_done:
++ /* Report the result */
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unlock_hw_err:
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_compl;
++
++out_unlock_not_completed:
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++ res = SCST_EXEC_NOT_COMPLETED;
++ goto out;
++
++out_unlock_compl:
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++ goto out_compl;
++}
++
++static int scst_reserve_local(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_NOT_COMPLETED, rc;
++ struct scst_device *dev;
++ struct scst_tgt_dev *tgt_dev_tmp;
++
++ TRACE_ENTRY();
++
++ if ((cmd->cdb[0] == RESERVE_10) && (cmd->cdb[2] & SCST_RES_3RDPTY)) {
++ PRINT_ERROR("RESERVE_10: 3rdPty RESERVE not implemented "
++ "(lun=%lld)", (long long unsigned int)cmd->lun);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_done;
++ }
++
++ dev = cmd->dev;
++
++ /*
++ * There's no need to block this device, even for
++ * SCST_CONTR_MODE_ONE_TASK_SET, or anyhow else protect reservations
++ * changes, because:
++ *
++ * 1. The reservation changes are (rather) atomic, i.e., in contrast
++ * to persistent reservations, don't have any invalid intermediate
++ * states during being changed.
++ *
++ * 2. It's a duty of initiators to ensure order of regular commands
++ * around the reservation command either by ORDERED attribute, or by
++ * queue draining, or etc. For case of SCST_CONTR_MODE_ONE_TASK_SET
++ * there are no target drivers which can ensure even for ORDERED
++ * commands order of their delivery, so, because initiators know
++ * it, also there's no point to do any extra protection actions.
++ */
++
++ rc = scst_pre_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ if (!list_empty(&dev->dev_registrants_list)) {
++ if (scst_pr_crh_case(cmd))
++ goto out_completed;
++ else {
++ scst_set_cmd_error_status(cmd,
++ SAM_STAT_RESERVATION_CONFLICT);
++ goto out_done;
++ }
++ }
++
++ spin_lock_bh(&dev->dev_lock);
++
++ if (test_bit(SCST_TGT_DEV_RESERVED, &cmd->tgt_dev->tgt_dev_flags)) {
++ spin_unlock_bh(&dev->dev_lock);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out_done;
++ }
++
++ list_for_each_entry(tgt_dev_tmp, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if (cmd->tgt_dev != tgt_dev_tmp)
++ set_bit(SCST_TGT_DEV_RESERVED,
++ &tgt_dev_tmp->tgt_dev_flags);
++ }
++ dev->dev_reserved = 1;
++
++ spin_unlock_bh(&dev->dev_lock);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_completed:
++ cmd->completed = 1;
++
++out_done:
++ /* Report the result */
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++ res = SCST_EXEC_COMPLETED;
++ goto out;
++}
++
++static int scst_release_local(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_NOT_COMPLETED, rc;
++ struct scst_tgt_dev *tgt_dev_tmp;
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ dev = cmd->dev;
++
++ /*
++ * See comment in scst_reserve_local() why no dev blocking or any
++ * other protection is needed here.
++ */
++
++ rc = scst_pre_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ if (!list_empty(&dev->dev_registrants_list)) {
++ if (scst_pr_crh_case(cmd))
++ goto out_completed;
++ else {
++ scst_set_cmd_error_status(cmd,
++ SAM_STAT_RESERVATION_CONFLICT);
++ goto out_done;
++ }
++ }
++
++ spin_lock_bh(&dev->dev_lock);
++
++ /*
++ * The device could be RELEASED behind us, if RESERVING session
++ * is closed (see scst_free_tgt_dev()), but this actually doesn't
++ * matter, so use lock and no retest for DEV_RESERVED bits again
++ */
++ if (test_bit(SCST_TGT_DEV_RESERVED, &cmd->tgt_dev->tgt_dev_flags)) {
++ res = SCST_EXEC_COMPLETED;
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++ cmd->completed = 1;
++ } else {
++ list_for_each_entry(tgt_dev_tmp,
++ &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ clear_bit(SCST_TGT_DEV_RESERVED,
++ &tgt_dev_tmp->tgt_dev_flags);
++ }
++ dev->dev_reserved = 0;
++ }
++
++ spin_unlock_bh(&dev->dev_lock);
++
++ if (res == SCST_EXEC_COMPLETED)
++ goto out_done;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_completed:
++ cmd->completed = 1;
++
++out_done:
++ res = SCST_EXEC_COMPLETED;
++ /* Report the result */
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++ goto out;
++}
++
++/* No locks, no IRQ or IRQ-disabled context allowed */
++static int scst_persistent_reserve_in_local(struct scst_cmd *cmd)
++{
++ int rc;
++ struct scst_device *dev;
++ struct scst_tgt_dev *tgt_dev;
++ struct scst_session *session;
++ int action;
++ uint8_t *buffer;
++ int buffer_size;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(scst_cmd_atomic(cmd));
++
++ dev = cmd->dev;
++ tgt_dev = cmd->tgt_dev;
++ session = cmd->sess;
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ if (unlikely(dev->not_pr_supporting_tgt_devs_num != 0)) {
++ PRINT_WARNING("Persistent Reservation command %x refused for "
++ "device %s, because the device has not supporting PR "
++ "transports connected", cmd->cdb[0], dev->virt_name);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out_done;
++ }
++
++ if (dev->dev_reserved) {
++ TRACE_PR("PR command rejected, because device %s holds regular "
++ "reservation", dev->virt_name);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out_done;
++ }
++
++ if (dev->scsi_dev != NULL) {
++ PRINT_WARNING("PR commands for pass-through devices not "
++ "supported (device %s)", dev->virt_name);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out_done;
++ }
++
++ buffer_size = scst_get_buf_full(cmd, &buffer);
++ if (unlikely(buffer_size <= 0)) {
++ if (buffer_size < 0)
++ scst_set_busy(cmd);
++ goto out_done;
++ }
++
++ scst_pr_write_lock(dev);
++
++ /* We can be aborted by another PR command while waiting for the lock */
++ if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) {
++ TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd);
++ goto out_unlock;
++ }
++
++ action = cmd->cdb[1] & 0x1f;
++
++ TRACE(TRACE_SCSI, "PR action %x for '%s' (LUN %llx) from '%s'", action,
++ dev->virt_name, tgt_dev->lun, session->initiator_name);
++
++ switch (action) {
++ case PR_READ_KEYS:
++ scst_pr_read_keys(cmd, buffer, buffer_size);
++ break;
++ case PR_READ_RESERVATION:
++ scst_pr_read_reservation(cmd, buffer, buffer_size);
++ break;
++ case PR_REPORT_CAPS:
++ scst_pr_report_caps(cmd, buffer, buffer_size);
++ break;
++ case PR_READ_FULL_STATUS:
++ scst_pr_read_full_status(cmd, buffer, buffer_size);
++ break;
++ default:
++ PRINT_ERROR("Unsupported action %x", action);
++ scst_pr_write_unlock(dev);
++ goto out_err;
++ }
++
++out_complete:
++ cmd->completed = 1;
++
++out_unlock:
++ scst_pr_write_unlock(dev);
++
++ scst_put_buf_full(cmd, buffer);
++
++out_done:
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++
++ TRACE_EXIT_RES(SCST_EXEC_COMPLETED);
++ return SCST_EXEC_COMPLETED;
++
++out_err:
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_complete;
++}
++
++/* No locks, no IRQ or IRQ-disabled context allowed */
++static int scst_persistent_reserve_out_local(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_COMPLETED;
++ int rc;
++ struct scst_device *dev;
++ struct scst_tgt_dev *tgt_dev;
++ struct scst_session *session;
++ int action;
++ uint8_t *buffer;
++ int buffer_size;
++ bool aborted = false;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(scst_cmd_atomic(cmd));
++
++ dev = cmd->dev;
++ tgt_dev = cmd->tgt_dev;
++ session = cmd->sess;
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ if (unlikely(dev->not_pr_supporting_tgt_devs_num != 0)) {
++ PRINT_WARNING("Persistent Reservation command %x refused for "
++ "device %s, because the device has not supporting PR "
++ "transports connected", cmd->cdb[0], dev->virt_name);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out_done;
++ }
++
++ action = cmd->cdb[1] & 0x1f;
++
++ TRACE(TRACE_SCSI, "PR action %x for '%s' (LUN %llx) from '%s'", action,
++ dev->virt_name, tgt_dev->lun, session->initiator_name);
++
++ if (dev->dev_reserved) {
++ TRACE_PR("PR command rejected, because device %s holds regular "
++ "reservation", dev->virt_name);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out_done;
++ }
++
++ /*
++ * Check if tgt_dev already registered. Also by this check we make
++ * sure that table "PERSISTENT RESERVE OUT service actions that are
++ * allowed in the presence of various reservations" is honored.
++ * REGISTER AND MOVE and RESERVE will be additionally checked for
++ * conflicts later.
++ */
++ if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) &&
++ (tgt_dev->registrant == NULL)) {
++ TRACE_PR("'%s' not registered", cmd->sess->initiator_name);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out_done;
++ }
++
++ buffer_size = scst_get_buf_full(cmd, &buffer);
++ if (unlikely(buffer_size <= 0)) {
++ if (buffer_size < 0)
++ scst_set_busy(cmd);
++ goto out_done;
++ }
++
++ /* Check scope */
++ if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) &&
++ (action != PR_CLEAR) && ((cmd->cdb[2] & 0x0f) >> 4) != SCOPE_LU) {
++ TRACE_PR("Scope must be SCOPE_LU for action %x", action);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put_buf_full;
++ }
++
++ /* Check SPEC_I_PT (PR_REGISTER_AND_MOVE has another format) */
++ if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_MOVE) &&
++ ((buffer[20] >> 3) & 0x01)) {
++ TRACE_PR("SPEC_I_PT must be zero for action %x", action);
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_invalid_field_in_cdb));
++ goto out_put_buf_full;
++ }
++
++ /* Check ALL_TG_PT (PR_REGISTER_AND_MOVE has another format) */
++ if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) &&
++ (action != PR_REGISTER_AND_MOVE) && ((buffer[20] >> 2) & 0x01)) {
++ TRACE_PR("ALL_TG_PT must be zero for action %x", action);
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_invalid_field_in_cdb));
++ goto out_put_buf_full;
++ }
++
++ scst_pr_write_lock(dev);
++
++ /* We can be aborted by another PR command while waiting for the lock */
++ aborted = test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags);
++ if (unlikely(aborted)) {
++ TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd);
++ goto out_unlock;
++ }
++
++ switch (action) {
++ case PR_REGISTER:
++ scst_pr_register(cmd, buffer, buffer_size);
++ break;
++ case PR_RESERVE:
++ scst_pr_reserve(cmd, buffer, buffer_size);
++ break;
++ case PR_RELEASE:
++ scst_pr_release(cmd, buffer, buffer_size);
++ break;
++ case PR_CLEAR:
++ scst_pr_clear(cmd, buffer, buffer_size);
++ break;
++ case PR_PREEMPT:
++ scst_pr_preempt(cmd, buffer, buffer_size);
++ break;
++ case PR_PREEMPT_AND_ABORT:
++ scst_pr_preempt_and_abort(cmd, buffer, buffer_size);
++ break;
++ case PR_REGISTER_AND_IGNORE:
++ scst_pr_register_and_ignore(cmd, buffer, buffer_size);
++ break;
++ case PR_REGISTER_AND_MOVE:
++ scst_pr_register_and_move(cmd, buffer, buffer_size);
++ break;
++ default:
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_unlock;
++ }
++
++ if (cmd->status == SAM_STAT_GOOD)
++ scst_pr_sync_device_file(tgt_dev, cmd);
++
++ if ((dev->handler->pr_cmds_notifications) &&
++ (cmd->status == SAM_STAT_GOOD)) /* sync file may change status */
++ res = SCST_EXEC_NOT_COMPLETED;
++
++out_unlock:
++ scst_pr_write_unlock(dev);
++
++out_put_buf_full:
++ scst_put_buf_full(cmd, buffer);
++
++out_done:
++ if (SCST_EXEC_COMPLETED == res) {
++ if (!aborted)
++ cmd->completed = 1;
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT,
++ SCST_CONTEXT_SAME);
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_check_local_events() - check if there are any local SCSI events
++ *
++ * Description:
++ * Checks if the command can be executed or there are local events,
++ * like reservations, pending UAs, etc. Returns < 0 if command must be
++ * aborted, > 0 if there is an event and command should be immediately
++ * completed, or 0 otherwise.
++ *
++ * !! 1.Dev handlers implementing exec() callback must call this function there
++ * !! just before the actual command's execution!
++ * !!
++ * !! 2. If this function can be called more than once on the processing path
++ * !! scst_pre_check_local_events() should be used for the first call!
++ *
++ * On call no locks, no IRQ or IRQ-disabled context allowed.
++ */
++int scst_check_local_events(struct scst_cmd *cmd)
++{
++ int res, rc;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_device *dev = cmd->dev;
++
++ TRACE_ENTRY();
++
++ /*
++ * There's no race here, because we need to trace commands sent
++ * *after* dev_double_ua_possible flag was set.
++ */
++ if (unlikely(dev->dev_double_ua_possible))
++ cmd->double_ua_possible = 1;
++
++ /* Reserve check before Unit Attention */
++ if (unlikely(test_bit(SCST_TGT_DEV_RESERVED,
++ &tgt_dev->tgt_dev_flags))) {
++ if ((cmd->op_flags & SCST_REG_RESERVE_ALLOWED) == 0) {
++ scst_set_cmd_error_status(cmd,
++ SAM_STAT_RESERVATION_CONFLICT);
++ goto out_dec_pr_readers_count;
++ }
++ }
++
++ if (likely(!cmd->check_local_events_once_done)) {
++ if (dev->pr_is_set) {
++ if (unlikely(!scst_pr_is_cmd_allowed(cmd))) {
++ scst_set_cmd_error_status(cmd,
++ SAM_STAT_RESERVATION_CONFLICT);
++ goto out_complete;
++ }
++ } else
++ scst_dec_pr_readers_count(cmd, false);
++ }
++
++ /*
++ * Let's check for ABORTED after scst_pr_is_cmd_allowed(), because
++ * we might sleep for a while there.
++ */
++ if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) {
++ TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd);
++ goto out_uncomplete;
++ }
++
++ /* If we had internal bus reset, set the command error unit attention */
++ if ((dev->scsi_dev != NULL) &&
++ unlikely(dev->scsi_dev->was_reset)) {
++ if (scst_is_ua_command(cmd)) {
++ int done = 0;
++ /*
++ * Prevent more than 1 cmd to be triggered by was_reset
++ */
++ spin_lock_bh(&dev->dev_lock);
++ if (dev->scsi_dev->was_reset) {
++ TRACE(TRACE_MGMT, "was_reset is %d", 1);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_reset_UA));
++ /*
++ * It looks like it is safe to clear was_reset
++ * here
++ */
++ dev->scsi_dev->was_reset = 0;
++ done = 1;
++ }
++ spin_unlock_bh(&dev->dev_lock);
++
++ if (done)
++ goto out_complete;
++ }
++ }
++
++ if (unlikely(test_bit(SCST_TGT_DEV_UA_PENDING,
++ &cmd->tgt_dev->tgt_dev_flags))) {
++ if (scst_is_ua_command(cmd)) {
++ rc = scst_set_pending_UA(cmd);
++ if (rc == 0)
++ goto out_complete;
++ }
++ }
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_dec_pr_readers_count:
++ if (cmd->dec_pr_readers_count_needed)
++ scst_dec_pr_readers_count(cmd, false);
++
++out_complete:
++ res = 1;
++ BUG_ON(!cmd->completed);
++ goto out;
++
++out_uncomplete:
++ res = -1;
++ goto out;
++}
++EXPORT_SYMBOL_GPL(scst_check_local_events);
++
++/* No locks */
++void scst_inc_expected_sn(struct scst_order_data *order_data, atomic_t *slot)
++{
++ if (slot == NULL)
++ goto inc;
++
++ /* Optimized for lockless fast path */
++
++ TRACE_SN("Slot %zd, *cur_sn_slot %d", slot - order_data->sn_slots,
++ atomic_read(slot));
++
++ if (!atomic_dec_and_test(slot))
++ goto out;
++
++ TRACE_SN("Slot is 0 (num_free_sn_slots=%d)",
++ order_data->num_free_sn_slots);
++ if (order_data->num_free_sn_slots < (int)ARRAY_SIZE(order_data->sn_slots)-1) {
++ spin_lock_irq(&order_data->sn_lock);
++ if (likely(order_data->num_free_sn_slots < (int)ARRAY_SIZE(order_data->sn_slots)-1)) {
++ if (order_data->num_free_sn_slots < 0)
++ order_data->cur_sn_slot = slot;
++ /* To be in-sync with SIMPLE case in scst_cmd_set_sn() */
++ smp_mb();
++ order_data->num_free_sn_slots++;
++ TRACE_SN("Incremented num_free_sn_slots (%d)",
++ order_data->num_free_sn_slots);
++
++ }
++ spin_unlock_irq(&order_data->sn_lock);
++ }
++
++inc:
++ /*
++ * No protection of expected_sn is needed, because only one thread
++ * at time can be here (serialized by sn). Also it is supposed that
++ * there could not be half-incremented halves.
++ */
++ order_data->expected_sn++;
++ /*
++ * Write must be before def_cmd_count read to be in sync. with
++ * scst_post_exec_sn(). See comment in scst_send_for_exec().
++ */
++ smp_mb();
++ TRACE_SN("Next expected_sn: %d", order_data->expected_sn);
++
++out:
++ return;
++}
++
++/* No locks */
++static struct scst_cmd *scst_post_exec_sn(struct scst_cmd *cmd,
++ bool make_active)
++{
++ /* For HQ commands SN is not set */
++ bool inc_expected_sn = !cmd->inc_expected_sn_on_done &&
++ cmd->sn_set && !cmd->retry;
++ struct scst_order_data *order_data = cmd->cur_order_data;
++ struct scst_cmd *res;
++
++ TRACE_ENTRY();
++
++ if (inc_expected_sn)
++ scst_inc_expected_sn(order_data, cmd->sn_slot);
++
++ if (make_active) {
++ scst_make_deferred_commands_active(order_data);
++ res = NULL;
++ } else
++ res = scst_check_deferred_commands(order_data);
++
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/* cmd must be additionally referenced to not die inside */
++static int scst_do_real_exec(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_NOT_COMPLETED;
++ int rc;
++ struct scst_device *dev = cmd->dev;
++ struct scst_dev_type *handler = dev->handler;
++ struct io_context *old_ctx = NULL;
++ bool ctx_changed = false;
++ struct scsi_device *scsi_dev;
++
++ TRACE_ENTRY();
++
++ ctx_changed = scst_set_io_context(cmd, &old_ctx);
++
++ cmd->state = SCST_CMD_STATE_REAL_EXECUTING;
++
++ if (handler->exec) {
++ TRACE_DBG("Calling dev handler %s exec(%p)",
++ handler->name, cmd);
++ TRACE_BUFF_FLAG(TRACE_SND_TOP, "Execing: ", cmd->cdb,
++ cmd->cdb_len);
++ scst_set_cur_start(cmd);
++ res = handler->exec(cmd);
++ TRACE_DBG("Dev handler %s exec() returned %d",
++ handler->name, res);
++
++ if (res == SCST_EXEC_COMPLETED)
++ goto out_complete;
++
++ scst_set_exec_time(cmd);
++
++ BUG_ON(res != SCST_EXEC_NOT_COMPLETED);
++ }
++
++ TRACE_DBG("Sending cmd %p to SCSI mid-level", cmd);
++
++ scsi_dev = dev->scsi_dev;
++
++ if (unlikely(scsi_dev == NULL)) {
++ PRINT_ERROR("Command for virtual device must be "
++ "processed by device handler (LUN %lld)!",
++ (long long unsigned int)cmd->lun);
++ goto out_error;
++ }
++
++ res = scst_check_local_events(cmd);
++ if (unlikely(res != 0))
++ goto out_done;
++
++ scst_set_cur_start(cmd);
++
++ rc = scst_scsi_exec_async(cmd, cmd, scst_pass_through_cmd_done);
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("scst pass-through exec failed: %x", rc);
++ if (((int)rc == -EINVAL) &&
++ (cmd->bufflen > queue_max_hw_sectors(scsi_dev->request_queue)))
++ PRINT_ERROR("Too low max_hw_sectors %d sectors on %s "
++ "to serve command %x with bufflen %db."
++ "See README for more details.",
++ queue_max_hw_sectors(scsi_dev->request_queue),
++ dev->virt_name, cmd->cdb[0], cmd->bufflen);
++ goto out_error;
++ }
++
++out_complete:
++ res = SCST_EXEC_COMPLETED;
++
++ if (ctx_changed)
++ scst_reset_io_context(cmd->tgt_dev, old_ctx);
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_error:
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_done;
++
++out_done:
++ res = SCST_EXEC_COMPLETED;
++ /* Report the result */
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++ goto out_complete;
++}
++
++static inline int scst_real_exec(struct scst_cmd *cmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ BUILD_BUG_ON(SCST_CMD_STATE_RES_CONT_SAME != SCST_EXEC_NOT_COMPLETED);
++ BUILD_BUG_ON(SCST_CMD_STATE_RES_CONT_NEXT != SCST_EXEC_COMPLETED);
++
++ __scst_cmd_get(cmd);
++
++ res = scst_do_real_exec(cmd);
++ if (likely(res == SCST_EXEC_COMPLETED)) {
++ scst_post_exec_sn(cmd, true);
++ } else
++ BUG();
++
++ __scst_cmd_put(cmd);
++
++ /* SCST_EXEC_* match SCST_CMD_STATE_RES_* */
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_do_local_exec(struct scst_cmd *cmd)
++{
++ int res;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++
++ TRACE_ENTRY();
++
++ /* Check READ_ONLY device status */
++ if ((cmd->op_flags & SCST_WRITE_MEDIUM) &&
++ (tgt_dev->acg_dev->rd_only || cmd->dev->swp ||
++ cmd->dev->rd_only)) {
++ PRINT_WARNING("Attempt of write access to read-only device: "
++ "initiator %s, LUN %lld, op %x",
++ cmd->sess->initiator_name, cmd->lun, cmd->cdb[0]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_data_protect));
++ goto out_done;
++ }
++
++ if (!scst_is_cmd_local(cmd)) {
++ res = SCST_EXEC_NOT_COMPLETED;
++ goto out;
++ }
++
++ switch (cmd->cdb[0]) {
++ case RESERVE:
++ case RESERVE_10:
++ res = scst_reserve_local(cmd);
++ break;
++ case RELEASE:
++ case RELEASE_10:
++ res = scst_release_local(cmd);
++ break;
++ case PERSISTENT_RESERVE_IN:
++ res = scst_persistent_reserve_in_local(cmd);
++ break;
++ case PERSISTENT_RESERVE_OUT:
++ res = scst_persistent_reserve_out_local(cmd);
++ break;
++ case REPORT_LUNS:
++ res = scst_report_luns_local(cmd);
++ break;
++ case REQUEST_SENSE:
++ res = scst_request_sense_local(cmd);
++ break;
++ default:
++ res = SCST_EXEC_NOT_COMPLETED;
++ break;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_done:
++ /* Report the result */
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++ res = SCST_EXEC_COMPLETED;
++ goto out;
++}
++
++static int scst_local_exec(struct scst_cmd *cmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ BUILD_BUG_ON(SCST_CMD_STATE_RES_CONT_SAME != SCST_EXEC_NOT_COMPLETED);
++ BUILD_BUG_ON(SCST_CMD_STATE_RES_CONT_NEXT != SCST_EXEC_COMPLETED);
++
++ __scst_cmd_get(cmd);
++
++ res = scst_do_local_exec(cmd);
++ if (likely(res == SCST_EXEC_NOT_COMPLETED))
++ cmd->state = SCST_CMD_STATE_REAL_EXEC;
++ else if (res == SCST_EXEC_COMPLETED)
++ scst_post_exec_sn(cmd, true);
++ else
++ BUG();
++
++ __scst_cmd_put(cmd);
++
++ /* SCST_EXEC_* match SCST_CMD_STATE_RES_* */
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_exec(struct scst_cmd **active_cmd)
++{
++ struct scst_cmd *cmd = *active_cmd;
++ struct scst_cmd *ref_cmd;
++ int res = SCST_CMD_STATE_RES_CONT_NEXT, count = 0;
++
++ TRACE_ENTRY();
++
++ cmd->state = SCST_CMD_STATE_START_EXEC;
++
++ if (unlikely(scst_check_blocked_dev(cmd)))
++ goto out;
++
++ /* To protect tgt_dev */
++ ref_cmd = cmd;
++ __scst_cmd_get(ref_cmd);
++
++ while (1) {
++ int rc;
++
++ cmd->sent_for_exec = 1;
++ /*
++ * To sync with scst_abort_cmd(). The above assignment must
++ * be before SCST_CMD_ABORTED test, done later in
++ * scst_check_local_events(). It's far from here, so the order
++ * is virtually guaranteed, but let's have it just in case.
++ */
++ smp_mb();
++
++ cmd->scst_cmd_done = scst_cmd_done_local;
++ cmd->state = SCST_CMD_STATE_LOCAL_EXEC;
++
++ rc = scst_do_local_exec(cmd);
++ if (likely(rc == SCST_EXEC_NOT_COMPLETED))
++ /* Nothing to do */;
++ else {
++ BUG_ON(rc != SCST_EXEC_COMPLETED);
++ goto done;
++ }
++
++ cmd->state = SCST_CMD_STATE_REAL_EXEC;
++
++ rc = scst_do_real_exec(cmd);
++ BUG_ON(rc != SCST_EXEC_COMPLETED);
++
++done:
++ count++;
++
++ cmd = scst_post_exec_sn(cmd, false);
++ if (cmd == NULL)
++ break;
++
++ cmd->state = SCST_CMD_STATE_START_EXEC;
++
++ if (unlikely(scst_check_blocked_dev(cmd)))
++ break;
++
++ __scst_cmd_put(ref_cmd);
++ ref_cmd = cmd;
++ __scst_cmd_get(ref_cmd);
++
++ }
++
++ *active_cmd = cmd;
++
++ if (count == 0)
++ goto out_put;
++
++out_put:
++ __scst_cmd_put(ref_cmd);
++ /* !! At this point sess, dev and tgt_dev can be already freed !! */
++
++out:
++ EXTRACHECKS_BUG_ON(res == SCST_CMD_STATE_RES_NEED_THREAD);
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_send_for_exec(struct scst_cmd **active_cmd)
++{
++ int res;
++ struct scst_cmd *cmd = *active_cmd;
++ struct scst_order_data *order_data = cmd->cur_order_data;
++ typeof(order_data->expected_sn) expected_sn;
++
++ TRACE_ENTRY();
++
++ if (unlikely(cmd->internal))
++ goto exec;
++
++ if (unlikely(cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE))
++ goto exec;
++
++ BUG_ON(!cmd->sn_set);
++
++ expected_sn = order_data->expected_sn;
++ /* Optimized for lockless fast path */
++ if ((cmd->sn != expected_sn) || (order_data->hq_cmd_count > 0)) {
++ spin_lock_irq(&order_data->sn_lock);
++
++ order_data->def_cmd_count++;
++ /*
++ * Memory barrier is needed here to implement lockless fast
++ * path. We need the exact order of read and write between
++ * def_cmd_count and expected_sn. Otherwise, we can miss case,
++ * when expected_sn was changed to be equal to cmd->sn while
++ * we are queueing cmd the deferred list after the expected_sn
++ * below. It will lead to a forever stuck command. But with
++ * the barrier in such case __scst_check_deferred_commands()
++ * will be called and it will take sn_lock, so we will be
++ * synchronized.
++ */
++ smp_mb();
++
++ expected_sn = order_data->expected_sn;
++ if ((cmd->sn != expected_sn) || (order_data->hq_cmd_count > 0)) {
++ if (unlikely(test_bit(SCST_CMD_ABORTED,
++ &cmd->cmd_flags))) {
++ /* Necessary to allow aborting out of sn cmds */
++ TRACE_MGMT_DBG("Aborting out of sn cmd %p "
++ "(tag %llu, sn %u)", cmd,
++ (long long unsigned)cmd->tag, cmd->sn);
++ order_data->def_cmd_count--;
++ scst_set_cmd_abnormal_done_state(cmd);
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++ } else {
++ TRACE_SN("Deferring cmd %p (sn=%d, set %d, "
++ "expected_sn=%d)", cmd, cmd->sn,
++ cmd->sn_set, expected_sn);
++ list_add_tail(&cmd->sn_cmd_list_entry,
++ &order_data->deferred_cmd_list);
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ }
++ spin_unlock_irq(&order_data->sn_lock);
++ goto out;
++ } else {
++ TRACE_SN("Somebody incremented expected_sn %d, "
++ "continuing", expected_sn);
++ order_data->def_cmd_count--;
++ spin_unlock_irq(&order_data->sn_lock);
++ }
++ }
++
++exec:
++ res = scst_exec(active_cmd);
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/* No locks supposed to be held */
++static int scst_check_sense(struct scst_cmd *cmd)
++{
++ int res = 0;
++ struct scst_device *dev = cmd->dev;
++
++ TRACE_ENTRY();
++
++ if (unlikely(cmd->ua_ignore))
++ goto out;
++
++ /* If we had internal bus reset behind us, set the command error UA */
++ if ((dev->scsi_dev != NULL) &&
++ unlikely(cmd->host_status == DID_RESET) &&
++ scst_is_ua_command(cmd)) {
++ TRACE(TRACE_MGMT, "DID_RESET: was_reset=%d host_status=%x",
++ dev->scsi_dev->was_reset, cmd->host_status);
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_reset_UA));
++ /* It looks like it is safe to clear was_reset here */
++ dev->scsi_dev->was_reset = 0;
++ }
++
++ if (unlikely(cmd->status == SAM_STAT_CHECK_CONDITION) &&
++ SCST_SENSE_VALID(cmd->sense)) {
++ PRINT_BUFF_FLAG(TRACE_SCSI, "Sense", cmd->sense,
++ cmd->sense_valid_len);
++
++ /* Check Unit Attention Sense Key */
++ if (scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)) {
++ if (scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
++ SCST_SENSE_ASC_VALID,
++ 0, SCST_SENSE_ASC_UA_RESET, 0)) {
++ if (cmd->double_ua_possible) {
++ TRACE_MGMT_DBG("Double UA "
++ "detected for device %p", dev);
++ TRACE_MGMT_DBG("Retrying cmd"
++ " %p (tag %llu)", cmd,
++ (long long unsigned)cmd->tag);
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++ cmd->completed = 0;
++
++ mempool_free(cmd->sense,
++ scst_sense_mempool);
++ cmd->sense = NULL;
++
++ scst_check_restore_sg_buff(cmd);
++
++ BUG_ON(cmd->dbl_ua_orig_resp_data_len < 0);
++ cmd->data_direction =
++ cmd->dbl_ua_orig_data_direction;
++ cmd->resp_data_len =
++ cmd->dbl_ua_orig_resp_data_len;
++
++ cmd->state = SCST_CMD_STATE_REAL_EXEC;
++ cmd->retry = 1;
++ scst_reset_requeued_cmd(cmd);
++ res = 1;
++ goto out;
++ }
++ }
++ scst_dev_check_set_UA(dev, cmd, cmd->sense,
++ cmd->sense_valid_len);
++ }
++ }
++
++ if (unlikely(cmd->double_ua_possible)) {
++ if (scst_is_ua_command(cmd)) {
++ TRACE_MGMT_DBG("Clearing dbl_ua_possible flag (dev %p, "
++ "cmd %p)", dev, cmd);
++ /*
++ * Lock used to protect other flags in the bitfield
++ * (just in case, actually). Those flags can't be
++ * changed in parallel, because the device is
++ * serialized.
++ */
++ spin_lock_bh(&dev->dev_lock);
++ dev->dev_double_ua_possible = 0;
++ spin_unlock_bh(&dev->dev_lock);
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_check_auto_sense(struct scst_cmd *cmd)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (unlikely(cmd->status == SAM_STAT_CHECK_CONDITION) &&
++ (!SCST_SENSE_VALID(cmd->sense) ||
++ SCST_NO_SENSE(cmd->sense))) {
++ TRACE(TRACE_SCSI|TRACE_MINOR_AND_MGMT_DBG, "CHECK_CONDITION, "
++ "but no sense: cmd->status=%x, cmd->msg_status=%x, "
++ "cmd->host_status=%x, cmd->driver_status=%x (cmd %p)",
++ cmd->status, cmd->msg_status, cmd->host_status,
++ cmd->driver_status, cmd);
++ res = 1;
++ } else if (unlikely(cmd->host_status)) {
++ if ((cmd->host_status == DID_REQUEUE) ||
++ (cmd->host_status == DID_IMM_RETRY) ||
++ (cmd->host_status == DID_SOFT_ERROR) ||
++ (cmd->host_status == DID_ABORT)) {
++ scst_set_busy(cmd);
++ } else {
++ TRACE(TRACE_SCSI|TRACE_MINOR_AND_MGMT_DBG, "Host "
++ "status %x received, returning HARDWARE ERROR "
++ "instead (cmd %p)", cmd->host_status, cmd);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_pre_dev_done(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_RES_CONT_SAME, rc;
++
++ TRACE_ENTRY();
++
++ if (unlikely(scst_check_auto_sense(cmd))) {
++ PRINT_INFO("Command finished with CHECK CONDITION, but "
++ "without sense data (opcode 0x%x), issuing "
++ "REQUEST SENSE", cmd->cdb[0]);
++ rc = scst_prepare_request_sense(cmd);
++ if (rc == 0)
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ else {
++ PRINT_ERROR("%s", "Unable to issue REQUEST SENSE, "
++ "returning HARDWARE ERROR");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ goto out;
++ } else if (unlikely(scst_check_sense(cmd))) {
++ /*
++ * We can't allow atomic command on the exec stages, so
++ * restart to the thread
++ */
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++
++ if (likely(scsi_status_is_good(cmd->status))) {
++ unsigned char type = cmd->dev->type;
++ if (unlikely((cmd->cdb[0] == MODE_SENSE ||
++ cmd->cdb[0] == MODE_SENSE_10)) &&
++ (cmd->tgt_dev->acg_dev->rd_only || cmd->dev->swp ||
++ cmd->dev->rd_only) &&
++ (type == TYPE_DISK ||
++ type == TYPE_WORM ||
++ type == TYPE_MOD ||
++ type == TYPE_TAPE)) {
++ int32_t length;
++ uint8_t *address;
++ bool err = false;
++
++ length = scst_get_buf_full(cmd, &address);
++ if (length < 0) {
++ PRINT_ERROR("%s", "Unable to get "
++ "MODE_SENSE buffer");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(
++ scst_sense_hardw_error));
++ err = true;
++ } else if (length > 2 && cmd->cdb[0] == MODE_SENSE)
++ address[2] |= 0x80; /* Write Protect*/
++ else if (length > 3 && cmd->cdb[0] == MODE_SENSE_10)
++ address[3] |= 0x80; /* Write Protect*/
++ scst_put_buf_full(cmd, address);
++
++ if (err)
++ goto out;
++ }
++
++ /*
++ * Check and clear NormACA option for the device, if necessary,
++ * since we don't support ACA
++ */
++ if (unlikely((cmd->cdb[0] == INQUIRY)) &&
++ /* Std INQUIRY data (no EVPD) */
++ !(cmd->cdb[1] & SCST_INQ_EVPD) &&
++ (cmd->resp_data_len > SCST_INQ_BYTE3)) {
++ uint8_t *buffer;
++ int buflen;
++ bool err = false;
++
++ buflen = scst_get_buf_full(cmd, &buffer);
++ if (buflen > SCST_INQ_BYTE3 && !cmd->tgtt->fake_aca) {
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (buffer[SCST_INQ_BYTE3] & SCST_INQ_NORMACA_BIT) {
++ PRINT_INFO("NormACA set for device: "
++ "lun=%lld, type 0x%02x. Clear it, "
++ "since it's unsupported.",
++ (long long unsigned int)cmd->lun,
++ buffer[0]);
++ }
++#endif
++ buffer[SCST_INQ_BYTE3] &= ~SCST_INQ_NORMACA_BIT;
++ } else if (buflen <= SCST_INQ_BYTE3 && buflen != 0) {
++ PRINT_ERROR("%s", "Unable to get INQUIRY "
++ "buffer");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ err = true;
++ }
++ if (buflen > 0)
++ scst_put_buf_full(cmd, buffer);
++
++ if (err)
++ goto out;
++ }
++
++ if (unlikely((cmd->cdb[0] == MODE_SELECT) ||
++ (cmd->cdb[0] == MODE_SELECT_10) ||
++ (cmd->cdb[0] == LOG_SELECT))) {
++ TRACE(TRACE_SCSI,
++ "MODE/LOG SELECT succeeded (LUN %lld)",
++ (long long unsigned int)cmd->lun);
++ cmd->state = SCST_CMD_STATE_MODE_SELECT_CHECKS;
++ goto out;
++ }
++ } else {
++ TRACE(TRACE_SCSI, "cmd %p not succeeded with status %x",
++ cmd, cmd->status);
++
++ if ((cmd->cdb[0] == RESERVE) || (cmd->cdb[0] == RESERVE_10)) {
++ if (!test_bit(SCST_TGT_DEV_RESERVED,
++ &cmd->tgt_dev->tgt_dev_flags)) {
++ struct scst_tgt_dev *tgt_dev_tmp;
++ struct scst_device *dev = cmd->dev;
++
++ TRACE(TRACE_SCSI, "RESERVE failed lun=%lld, "
++ "status=%x",
++ (long long unsigned int)cmd->lun,
++ cmd->status);
++ PRINT_BUFF_FLAG(TRACE_SCSI, "Sense", cmd->sense,
++ cmd->sense_valid_len);
++
++ /* Clearing the reservation */
++ spin_lock_bh(&dev->dev_lock);
++ list_for_each_entry(tgt_dev_tmp,
++ &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ clear_bit(SCST_TGT_DEV_RESERVED,
++ &tgt_dev_tmp->tgt_dev_flags);
++ }
++ dev->dev_reserved = 0;
++ spin_unlock_bh(&dev->dev_lock);
++ }
++ }
++
++ /* Check for MODE PARAMETERS CHANGED UA */
++ if ((cmd->dev->scsi_dev != NULL) &&
++ (cmd->status == SAM_STAT_CHECK_CONDITION) &&
++ scst_is_ua_sense(cmd->sense, cmd->sense_valid_len) &&
++ scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
++ SCST_SENSE_ASCx_VALID,
++ 0, 0x2a, 0x01)) {
++ TRACE(TRACE_SCSI, "MODE PARAMETERS CHANGED UA (lun "
++ "%lld)", (long long unsigned int)cmd->lun);
++ cmd->state = SCST_CMD_STATE_MODE_SELECT_CHECKS;
++ goto out;
++ }
++ }
++
++ cmd->state = SCST_CMD_STATE_DEV_DONE;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_mode_select_checks(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_RES_CONT_SAME;
++
++ TRACE_ENTRY();
++
++ if (likely(scsi_status_is_good(cmd->status))) {
++ int atomic = scst_cmd_atomic(cmd);
++ if (unlikely((cmd->cdb[0] == MODE_SELECT) ||
++ (cmd->cdb[0] == MODE_SELECT_10) ||
++ (cmd->cdb[0] == LOG_SELECT))) {
++ struct scst_device *dev = cmd->dev;
++ int sl;
++ uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
++
++ if (atomic && (dev->scsi_dev != NULL)) {
++ TRACE_DBG("%s", "MODE/LOG SELECT: thread "
++ "context required");
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++
++ TRACE(TRACE_SCSI, "MODE/LOG SELECT succeeded, "
++ "setting the SELECT UA (lun=%lld)",
++ (long long unsigned int)cmd->lun);
++
++ spin_lock_bh(&dev->dev_lock);
++ if (cmd->cdb[0] == LOG_SELECT) {
++ sl = scst_set_sense(sense_buffer,
++ sizeof(sense_buffer),
++ dev->d_sense,
++ UNIT_ATTENTION, 0x2a, 0x02);
++ } else {
++ sl = scst_set_sense(sense_buffer,
++ sizeof(sense_buffer),
++ dev->d_sense,
++ UNIT_ATTENTION, 0x2a, 0x01);
++ }
++ scst_dev_check_set_local_UA(dev, cmd, sense_buffer, sl);
++ spin_unlock_bh(&dev->dev_lock);
++
++ if (dev->scsi_dev != NULL)
++ scst_obtain_device_parameters(dev);
++ }
++ } else if ((cmd->status == SAM_STAT_CHECK_CONDITION) &&
++ scst_is_ua_sense(cmd->sense, cmd->sense_valid_len) &&
++ /* mode parameters changed */
++ (scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
++ SCST_SENSE_ASCx_VALID,
++ 0, 0x2a, 0x01) ||
++ scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
++ SCST_SENSE_ASC_VALID,
++ 0, 0x29, 0) /* reset */ ||
++ scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
++ SCST_SENSE_ASC_VALID,
++ 0, 0x28, 0) /* medium changed */ ||
++ /* cleared by another ini (just in case) */
++ scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
++ SCST_SENSE_ASC_VALID,
++ 0, 0x2F, 0))) {
++ int atomic = scst_cmd_atomic(cmd);
++ if (atomic) {
++ TRACE_DBG("Possible parameters changed UA %x: "
++ "thread context required", cmd->sense[12]);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++
++ TRACE(TRACE_SCSI, "Possible parameters changed UA %x "
++ "(LUN %lld): getting new parameters", cmd->sense[12],
++ (long long unsigned int)cmd->lun);
++
++ scst_obtain_device_parameters(cmd->dev);
++ } else
++ BUG();
++
++ cmd->state = SCST_CMD_STATE_DEV_DONE;
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++static void scst_inc_check_expected_sn(struct scst_cmd *cmd)
++{
++ if (likely(cmd->sn_set))
++ scst_inc_expected_sn(cmd->cur_order_data, cmd->sn_slot);
++
++ scst_make_deferred_commands_active(cmd->cur_order_data);
++}
++
++static int scst_dev_done(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_RES_CONT_SAME;
++ int state;
++ struct scst_device *dev = cmd->dev;
++
++ TRACE_ENTRY();
++
++ state = SCST_CMD_STATE_PRE_XMIT_RESP;
++
++ if (likely(!scst_is_cmd_fully_local(cmd)) &&
++ likely(dev->handler->dev_done != NULL)) {
++ int rc;
++
++ if (unlikely(!dev->handler->dev_done_atomic &&
++ scst_cmd_atomic(cmd))) {
++ /*
++ * It shouldn't be because of the SCST_TGT_DEV_AFTER_*
++ * optimization.
++ */
++ TRACE_MGMT_DBG("Dev handler %s dev_done() needs thread "
++ "context, rescheduling", dev->handler->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++
++ TRACE_DBG("Calling dev handler %s dev_done(%p)",
++ dev->handler->name, cmd);
++ scst_set_cur_start(cmd);
++ rc = dev->handler->dev_done(cmd);
++ scst_set_dev_done_time(cmd);
++ TRACE_DBG("Dev handler %s dev_done() returned %d",
++ dev->handler->name, rc);
++ if (rc != SCST_CMD_STATE_DEFAULT)
++ state = rc;
++ }
++
++ switch (state) {
++#ifdef CONFIG_SCST_EXTRACHECKS
++ case SCST_CMD_STATE_PRE_XMIT_RESP:
++ case SCST_CMD_STATE_PARSE:
++ case SCST_CMD_STATE_PREPARE_SPACE:
++ case SCST_CMD_STATE_RDY_TO_XFER:
++ case SCST_CMD_STATE_TGT_PRE_EXEC:
++ case SCST_CMD_STATE_SEND_FOR_EXEC:
++ case SCST_CMD_STATE_START_EXEC:
++ case SCST_CMD_STATE_LOCAL_EXEC:
++ case SCST_CMD_STATE_REAL_EXEC:
++ case SCST_CMD_STATE_PRE_DEV_DONE:
++ case SCST_CMD_STATE_MODE_SELECT_CHECKS:
++ case SCST_CMD_STATE_DEV_DONE:
++ case SCST_CMD_STATE_XMIT_RESP:
++ case SCST_CMD_STATE_FINISHED:
++ case SCST_CMD_STATE_FINISHED_INTERNAL:
++#else
++ default:
++#endif
++ cmd->state = state;
++ break;
++ case SCST_CMD_STATE_NEED_THREAD_CTX:
++ TRACE_DBG("Dev handler %s dev_done() requested "
++ "thread context, rescheduling",
++ dev->handler->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++#ifdef CONFIG_SCST_EXTRACHECKS
++ default:
++ if (state >= 0) {
++ PRINT_ERROR("Dev handler %s dev_done() returned "
++ "invalid cmd state %d",
++ dev->handler->name, state);
++ } else {
++ PRINT_ERROR("Dev handler %s dev_done() returned "
++ "error %d", dev->handler->name,
++ state);
++ }
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ scst_set_cmd_abnormal_done_state(cmd);
++ break;
++#endif
++ }
++
++ scst_check_unblock_dev(cmd);
++
++ if (cmd->inc_expected_sn_on_done && cmd->sent_for_exec)
++ scst_inc_check_expected_sn(cmd);
++
++ if (unlikely(cmd->internal))
++ cmd->state = SCST_CMD_STATE_FINISHED_INTERNAL;
++
++#ifndef CONFIG_SCST_TEST_IO_IN_SIRQ
++ if (cmd->state != SCST_CMD_STATE_PRE_XMIT_RESP) {
++ /* We can't allow atomic command on the exec stages */
++ if (scst_cmd_atomic(cmd)) {
++ switch (state) {
++ case SCST_CMD_STATE_TGT_PRE_EXEC:
++ case SCST_CMD_STATE_SEND_FOR_EXEC:
++ case SCST_CMD_STATE_START_EXEC:
++ case SCST_CMD_STATE_LOCAL_EXEC:
++ case SCST_CMD_STATE_REAL_EXEC:
++ TRACE_DBG("Atomic context and redirect, "
++ "rescheduling (cmd %p)", cmd);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ break;
++ }
++ }
++ }
++#endif
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++static int scst_pre_xmit_response(struct scst_cmd *cmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(cmd->internal);
++
++#ifdef CONFIG_SCST_DEBUG_TM
++ if (cmd->tm_dbg_delayed &&
++ !test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) {
++ if (scst_cmd_atomic(cmd)) {
++ TRACE_MGMT_DBG("%s",
++ "DEBUG_TM delayed cmd needs a thread");
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ return res;
++ }
++ TRACE_MGMT_DBG("Delaying cmd %p (tag %llu) for 1 second",
++ cmd, cmd->tag);
++ schedule_timeout_uninterruptible(HZ);
++ }
++#endif
++
++ if (likely(cmd->tgt_dev != NULL)) {
++ /*
++ * Those counters protect from not getting too long processing
++ * latency, so we should decrement them after cmd completed.
++ */
++ atomic_dec(&cmd->tgt_dev->tgt_dev_cmd_count);
++#ifdef CONFIG_SCST_PER_DEVICE_CMD_COUNT_LIMIT
++ atomic_dec(&cmd->dev->dev_cmd_count);
++#endif
++ if (unlikely(cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE))
++ scst_on_hq_cmd_response(cmd);
++
++ if (unlikely(!cmd->sent_for_exec)) {
++ TRACE_SN("cmd %p was not sent to mid-lev"
++ " (sn %d, set %d)",
++ cmd, cmd->sn, cmd->sn_set);
++ scst_unblock_deferred(cmd->cur_order_data, cmd);
++ cmd->sent_for_exec = 1;
++ }
++ }
++
++ cmd->done = 1;
++ smp_mb(); /* to sync with scst_abort_cmd() */
++
++ if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)))
++ scst_xmit_process_aborted_cmd(cmd);
++ else if (unlikely(cmd->status == SAM_STAT_CHECK_CONDITION))
++ scst_store_sense(cmd);
++
++ if (unlikely(test_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags))) {
++ TRACE_MGMT_DBG("Flag NO_RESP set for cmd %p (tag %llu), "
++ "skipping", cmd, (long long unsigned int)cmd->tag);
++ cmd->state = SCST_CMD_STATE_FINISHED;
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++ goto out;
++ }
++
++ if (unlikely(cmd->resid_possible))
++ scst_adjust_resp_data_len(cmd);
++ else
++ cmd->adjusted_resp_data_len = cmd->resp_data_len;
++
++ cmd->state = SCST_CMD_STATE_XMIT_RESP;
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++static int scst_xmit_response(struct scst_cmd *cmd)
++{
++ struct scst_tgt_template *tgtt = cmd->tgtt;
++ int res, rc;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(cmd->internal);
++
++ if (unlikely(!tgtt->xmit_response_atomic &&
++ scst_cmd_atomic(cmd))) {
++ /*
++ * It shouldn't be because of the SCST_TGT_DEV_AFTER_*
++ * optimization.
++ */
++ TRACE_MGMT_DBG("Target driver %s xmit_response() needs thread "
++ "context, rescheduling", tgtt->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++
++ while (1) {
++ int finished_cmds = atomic_read(&cmd->tgt->finished_cmds);
++
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ cmd->state = SCST_CMD_STATE_XMIT_WAIT;
++
++ TRACE_DBG("Calling xmit_response(%p)", cmd);
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ if (trace_flag & TRACE_SND_BOT) {
++ int i, j;
++ struct scatterlist *sg;
++ if (cmd->tgt_sg != NULL)
++ sg = cmd->tgt_sg;
++ else
++ sg = cmd->sg;
++ if (sg != NULL) {
++ TRACE(TRACE_SND_BOT, "Xmitting data for cmd %p "
++ "(sg_cnt %d, sg %p, sg[0].page %p, buf %p, "
++ "resp len %d)", cmd, cmd->tgt_sg_cnt,
++ sg, (void *)sg_page(&sg[0]), sg_virt(sg),
++ cmd->resp_data_len);
++ for (i = 0, j = 0; i < cmd->tgt_sg_cnt; ++i, ++j) {
++ if (unlikely(sg_is_chain(&sg[j]))) {
++ sg = sg_chain_ptr(&sg[j]);
++ j = 0;
++ }
++ TRACE(TRACE_SND_BOT, "sg %d", j);
++ PRINT_BUFF_FLAG(TRACE_SND_BOT,
++ "Xmitting sg", sg_virt(&sg[j]),
++ sg[j].length);
++ }
++ }
++ }
++#endif
++
++ if (tgtt->on_hw_pending_cmd_timeout != NULL) {
++ struct scst_session *sess = cmd->sess;
++ cmd->hw_pending_start = jiffies;
++ cmd->cmd_hw_pending = 1;
++ if (!test_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags)) {
++ TRACE_DBG("Sched HW pending work for sess %p "
++ "(max time %d)", sess,
++ tgtt->max_hw_pending_time);
++ set_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED,
++ &sess->sess_aflags);
++ schedule_delayed_work(&sess->hw_pending_work,
++ tgtt->max_hw_pending_time * HZ);
++ }
++ }
++
++ scst_set_cur_start(cmd);
++
++#ifdef CONFIG_SCST_DEBUG_RETRY
++ if (((scst_random() % 100) == 77))
++ rc = SCST_TGT_RES_QUEUE_FULL;
++ else
++#endif
++ rc = tgtt->xmit_response(cmd);
++ TRACE_DBG("xmit_response() returned %d", rc);
++
++ if (likely(rc == SCST_TGT_RES_SUCCESS))
++ goto out;
++
++ scst_set_xmit_time(cmd);
++
++ cmd->cmd_hw_pending = 0;
++
++ /* Restore the previous state */
++ cmd->state = SCST_CMD_STATE_XMIT_RESP;
++
++ switch (rc) {
++ case SCST_TGT_RES_QUEUE_FULL:
++ if (scst_queue_retry_cmd(cmd, finished_cmds) == 0)
++ break;
++ else
++ continue;
++
++ case SCST_TGT_RES_NEED_THREAD_CTX:
++ TRACE_DBG("Target driver %s xmit_response() "
++ "requested thread context, rescheduling",
++ tgtt->name);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++
++ default:
++ goto out_error;
++ }
++ break;
++ }
++
++out:
++ /* Caution: cmd can be already dead here */
++ TRACE_EXIT_HRES(res);
++ return res;
++
++out_error:
++ if (rc == SCST_TGT_RES_FATAL_ERROR) {
++ PRINT_ERROR("Target driver %s xmit_response() returned "
++ "fatal error", tgtt->name);
++ } else {
++ PRINT_ERROR("Target driver %s xmit_response() returned "
++ "invalid value %d", tgtt->name, rc);
++ }
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ cmd->state = SCST_CMD_STATE_FINISHED;
++ res = SCST_CMD_STATE_RES_CONT_SAME;
++ goto out;
++}
++
++/**
++ * scst_tgt_cmd_done() - the command's processing done
++ * @cmd: SCST command
++ * @pref_context: preferred command execution context
++ *
++ * Description:
++ * Notifies SCST that the driver sent the response and the command
++ * can be freed now. Don't forget to set the delivery status, if it
++ * isn't success, using scst_set_delivery_status() before calling
++ * this function. The third argument sets preferred command execution
++ * context (see SCST_CONTEXT_* constants for details)
++ */
++void scst_tgt_cmd_done(struct scst_cmd *cmd,
++ enum scst_exec_context pref_context)
++{
++ TRACE_ENTRY();
++
++ BUG_ON(cmd->state != SCST_CMD_STATE_XMIT_WAIT);
++
++ scst_set_xmit_time(cmd);
++
++ cmd->cmd_hw_pending = 0;
++
++ if (unlikely(cmd->tgt_dev == NULL))
++ pref_context = SCST_CONTEXT_THREAD;
++
++ cmd->state = SCST_CMD_STATE_FINISHED;
++
++ scst_process_redirect_cmd(cmd, pref_context, 1);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_tgt_cmd_done);
++
++static int scst_finish_cmd(struct scst_cmd *cmd)
++{
++ int res;
++ struct scst_session *sess = cmd->sess;
++ struct scst_io_stat_entry *stat;
++
++ TRACE_ENTRY();
++
++ scst_update_lat_stats(cmd);
++
++ if (unlikely(cmd->delivery_status != SCST_CMD_DELIVERY_SUCCESS)) {
++ if ((cmd->tgt_dev != NULL) &&
++ scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)) {
++ /* This UA delivery failed, so we need to requeue it */
++ if (scst_cmd_atomic(cmd) &&
++ scst_is_ua_global(cmd->sense, cmd->sense_valid_len)) {
++ TRACE_MGMT_DBG("Requeuing of global UA for "
++ "failed cmd %p needs a thread", cmd);
++ res = SCST_CMD_STATE_RES_NEED_THREAD;
++ goto out;
++ }
++ scst_requeue_ua(cmd);
++ }
++ }
++
++ atomic_dec(&sess->sess_cmd_count);
++
++ spin_lock_irq(&sess->sess_list_lock);
++
++ stat = &sess->io_stats[cmd->data_direction];
++ stat->cmd_count++;
++ stat->io_byte_count += cmd->bufflen + cmd->out_bufflen;
++
++ list_del(&cmd->sess_cmd_list_entry);
++
++ /*
++ * Done under sess_list_lock to sync with scst_abort_cmd() without
++ * using extra barrier.
++ */
++ cmd->finished = 1;
++
++ spin_unlock_irq(&sess->sess_list_lock);
++
++ if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) {
++ TRACE_MGMT_DBG("Aborted cmd %p finished (cmd_ref %d)",
++ cmd, atomic_read(&cmd->cmd_ref));
++
++ scst_finish_cmd_mgmt(cmd);
++ }
++
++ __scst_cmd_put(cmd);
++
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/*
++ * No locks, but it must be externally serialized (see comment for
++ * scst_cmd_init_done() in scst.h)
++ */
++static void scst_cmd_set_sn(struct scst_cmd *cmd)
++{
++ struct scst_order_data *order_data = cmd->cur_order_data;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ if (scst_is_implicit_hq_cmd(cmd) &&
++ likely(cmd->queue_type == SCST_CMD_QUEUE_SIMPLE)) {
++ TRACE_SN("Implicit HQ cmd %p", cmd);
++ cmd->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
++ }
++
++ EXTRACHECKS_BUG_ON(cmd->sn_set || cmd->hq_cmd_inced);
++
++ /* Optimized for lockless fast path */
++
++ scst_check_debug_sn(cmd);
++
++#ifdef CONFIG_SCST_STRICT_SERIALIZING
++ cmd->queue_type = SCST_CMD_QUEUE_ORDERED;
++#endif
++
++ if (cmd->dev->queue_alg == SCST_CONTR_MODE_QUEUE_ALG_RESTRICTED_REORDER) {
++ /*
++ * Not the best way, but good enough until there is a
++ * possibility to specify queue type during pass-through
++ * commands submission.
++ */
++ cmd->queue_type = SCST_CMD_QUEUE_ORDERED;
++ }
++
++ switch (cmd->queue_type) {
++ case SCST_CMD_QUEUE_SIMPLE:
++ case SCST_CMD_QUEUE_UNTAGGED:
++ if (likely(order_data->num_free_sn_slots >= 0)) {
++ /*
++ * atomic_inc_return() implies memory barrier to sync
++ * with scst_inc_expected_sn()
++ */
++ if (atomic_inc_return(order_data->cur_sn_slot) == 1) {
++ order_data->curr_sn++;
++ TRACE_SN("Incremented curr_sn %d",
++ order_data->curr_sn);
++ }
++ cmd->sn_slot = order_data->cur_sn_slot;
++ cmd->sn = order_data->curr_sn;
++
++ order_data->prev_cmd_ordered = 0;
++ } else {
++ TRACE(TRACE_MINOR, "***WARNING*** Not enough SN slots "
++ "%zd", ARRAY_SIZE(order_data->sn_slots));
++ goto ordered;
++ }
++ break;
++
++ case SCST_CMD_QUEUE_ORDERED:
++ TRACE_SN("ORDERED cmd %p (op %x)", cmd, cmd->cdb[0]);
++ordered:
++ if (!order_data->prev_cmd_ordered) {
++ spin_lock_irqsave(&order_data->sn_lock, flags);
++ if (order_data->num_free_sn_slots >= 0) {
++ order_data->num_free_sn_slots--;
++ if (order_data->num_free_sn_slots >= 0) {
++ int i = 0;
++ /* Commands can finish in any order, so
++ * we don't know which slot is empty.
++ */
++ while (1) {
++ order_data->cur_sn_slot++;
++ if (order_data->cur_sn_slot ==
++ order_data->sn_slots + ARRAY_SIZE(order_data->sn_slots))
++ order_data->cur_sn_slot = order_data->sn_slots;
++
++ if (atomic_read(order_data->cur_sn_slot) == 0)
++ break;
++
++ i++;
++ BUG_ON(i == ARRAY_SIZE(order_data->sn_slots));
++ }
++ TRACE_SN("New cur SN slot %zd",
++ order_data->cur_sn_slot -
++ order_data->sn_slots);
++ }
++ }
++ spin_unlock_irqrestore(&order_data->sn_lock, flags);
++ }
++ order_data->prev_cmd_ordered = 1;
++ order_data->curr_sn++;
++ cmd->sn = order_data->curr_sn;
++ break;
++
++ case SCST_CMD_QUEUE_HEAD_OF_QUEUE:
++ TRACE_SN("HQ cmd %p (op %x)", cmd, cmd->cdb[0]);
++ spin_lock_irqsave(&order_data->sn_lock, flags);
++ order_data->hq_cmd_count++;
++ spin_unlock_irqrestore(&order_data->sn_lock, flags);
++ cmd->hq_cmd_inced = 1;
++ goto out;
++
++ default:
++ BUG();
++ }
++
++ TRACE_SN("cmd(%p)->sn: %d (order_data %p, *cur_sn_slot %d, "
++ "num_free_sn_slots %d, prev_cmd_ordered %ld, "
++ "cur_sn_slot %zd)", cmd, cmd->sn, order_data,
++ atomic_read(order_data->cur_sn_slot),
++ order_data->num_free_sn_slots, order_data->prev_cmd_ordered,
++ order_data->cur_sn_slot - order_data->sn_slots);
++
++ cmd->sn_set = 1;
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * Returns 0 on success, > 0 when we need to wait for unblock,
++ * < 0 if there is no device (lun) or device type handler.
++ *
++ * No locks, but might be on IRQ, protection is done by the
++ * suspended activity.
++ */
++static int scst_translate_lun(struct scst_cmd *cmd)
++{
++ struct scst_tgt_dev *tgt_dev = NULL;
++ int res;
++
++ TRACE_ENTRY();
++
++ cmd->cpu_cmd_counter = scst_get();
++
++ if (likely(!test_bit(SCST_FLAG_SUSPENDED, &scst_flags))) {
++ struct list_head *head =
++ &cmd->sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(cmd->lun)];
++ TRACE_DBG("Finding tgt_dev for cmd %p (lun %lld)", cmd,
++ (long long unsigned int)cmd->lun);
++ res = -1;
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ if (tgt_dev->lun == cmd->lun) {
++ TRACE_DBG("tgt_dev %p found", tgt_dev);
++
++ if (unlikely(tgt_dev->dev->handler ==
++ &scst_null_devtype)) {
++ PRINT_INFO("Dev handler for device "
++ "%lld is NULL, the device will not "
++ "be visible remotely",
++ (long long unsigned int)cmd->lun);
++ break;
++ }
++
++ cmd->cmd_threads = tgt_dev->active_cmd_threads;
++ cmd->tgt_dev = tgt_dev;
++ cmd->cur_order_data = tgt_dev->curr_order_data;
++ cmd->dev = tgt_dev->dev;
++
++ res = 0;
++ break;
++ }
++ }
++ if (res != 0) {
++ TRACE(TRACE_MINOR,
++ "tgt_dev for LUN %lld not found, command to "
++ "unexisting LU (initiator %s, target %s)?",
++ (long long unsigned int)cmd->lun,
++ cmd->sess->initiator_name, cmd->tgt->tgt_name);
++ scst_put(cmd->cpu_cmd_counter);
++ }
++ } else {
++ TRACE_MGMT_DBG("%s", "FLAG SUSPENDED set, skipping");
++ scst_put(cmd->cpu_cmd_counter);
++ res = 1;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * No locks, but might be on IRQ.
++ *
++ * Returns 0 on success, > 0 when we need to wait for unblock,
++ * < 0 if there is no device (lun) or device type handler.
++ */
++static int __scst_init_cmd(struct scst_cmd *cmd)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ res = scst_translate_lun(cmd);
++ if (likely(res == 0)) {
++ int cnt;
++ bool failure = false;
++
++ cmd->state = SCST_CMD_STATE_PARSE;
++
++ cnt = atomic_inc_return(&cmd->tgt_dev->tgt_dev_cmd_count);
++ if (unlikely(cnt > SCST_MAX_TGT_DEV_COMMANDS)) {
++ TRACE(TRACE_FLOW_CONTROL,
++ "Too many pending commands (%d) in "
++ "session, returning BUSY to initiator \"%s\"",
++ cnt, (cmd->sess->initiator_name[0] == '\0') ?
++ "Anonymous" : cmd->sess->initiator_name);
++ failure = true;
++ }
++
++#ifdef CONFIG_SCST_PER_DEVICE_CMD_COUNT_LIMIT
++ cnt = atomic_inc_return(&cmd->dev->dev_cmd_count);
++ if (unlikely(cnt > SCST_MAX_DEV_COMMANDS)) {
++ if (!failure) {
++ TRACE(TRACE_FLOW_CONTROL,
++ "Too many pending device "
++ "commands (%d), returning BUSY to "
++ "initiator \"%s\"", cnt,
++ (cmd->sess->initiator_name[0] == '\0') ?
++ "Anonymous" :
++ cmd->sess->initiator_name);
++ failure = true;
++ }
++ }
++#endif
++
++ if (unlikely(failure))
++ goto out_busy;
++
++ /*
++ * SCST_IMPLICIT_HQ for unknown commands not implemented for
++ * case when set_sn_on_restart_cmd not set, because custom parse
++ * can reorder commands due to multithreaded processing. To
++ * implement it we need to implement all unknown commands as
++ * ORDERED in the beginning and post parse reprocess of
++ * queue_type to change it if needed. ToDo.
++ */
++ scst_pre_parse(cmd);
++
++ if (!cmd->set_sn_on_restart_cmd)
++ scst_cmd_set_sn(cmd);
++ } else if (res < 0) {
++ TRACE_DBG("Finishing cmd %p", cmd);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_lun_not_supported));
++ scst_set_cmd_abnormal_done_state(cmd);
++ } else
++ goto out;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_busy:
++ scst_set_busy(cmd);
++ scst_set_cmd_abnormal_done_state(cmd);
++ goto out;
++}
++
++/* Called under scst_init_lock and IRQs disabled */
++static void scst_do_job_init(void)
++ __releases(&scst_init_lock)
++ __acquires(&scst_init_lock)
++{
++ struct scst_cmd *cmd;
++ int susp;
++
++ TRACE_ENTRY();
++
++restart:
++ /*
++ * There is no need for read barrier here, because we don't care where
++ * this check will be done.
++ */
++ susp = test_bit(SCST_FLAG_SUSPENDED, &scst_flags);
++ if (scst_init_poll_cnt > 0)
++ scst_init_poll_cnt--;
++
++ list_for_each_entry(cmd, &scst_init_cmd_list, cmd_list_entry) {
++ int rc;
++ if (susp && !test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))
++ continue;
++ if (!test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) {
++ spin_unlock_irq(&scst_init_lock);
++ rc = __scst_init_cmd(cmd);
++ spin_lock_irq(&scst_init_lock);
++ if (rc > 0) {
++ TRACE_MGMT_DBG("%s",
++ "FLAG SUSPENDED set, restarting");
++ goto restart;
++ }
++ } else {
++ TRACE_MGMT_DBG("Aborting not inited cmd %p (tag %llu)",
++ cmd, (long long unsigned int)cmd->tag);
++ scst_set_cmd_abnormal_done_state(cmd);
++ }
++
++ /*
++ * Deleting cmd from init cmd list after __scst_init_cmd()
++ * is necessary to keep the check in scst_init_cmd() correct
++ * to preserve the commands order.
++ *
++ * We don't care about the race, when init cmd list is empty
++ * and one command detected that it just was not empty, so
++ * it's inserting to it, but another command at the same time
++ * seeing init cmd list empty and goes directly, because it
++ * could affect only commands from the same initiator to the
++ * same tgt_dev, but scst_cmd_init_done*() doesn't guarantee
++ * the order in case of simultaneous such calls anyway.
++ */
++ TRACE_MGMT_DBG("Deleting cmd %p from init cmd list", cmd);
++ smp_wmb(); /* enforce the required order */
++ list_del(&cmd->cmd_list_entry);
++ spin_unlock(&scst_init_lock);
++
++ spin_lock(&cmd->cmd_threads->cmd_list_lock);
++ TRACE_MGMT_DBG("Adding cmd %p to active cmd list", cmd);
++ if (unlikely(cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE))
++ list_add(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ else
++ list_add_tail(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock(&cmd->cmd_threads->cmd_list_lock);
++
++ spin_lock(&scst_init_lock);
++ goto restart;
++ }
++
++ /* It isn't really needed, but let's keep it */
++ if (susp != test_bit(SCST_FLAG_SUSPENDED, &scst_flags))
++ goto restart;
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline int test_init_cmd_list(void)
++{
++ int res = (!list_empty(&scst_init_cmd_list) &&
++ !test_bit(SCST_FLAG_SUSPENDED, &scst_flags)) ||
++ unlikely(kthread_should_stop()) ||
++ (scst_init_poll_cnt > 0);
++ return res;
++}
++
++int scst_init_thread(void *arg)
++{
++ TRACE_ENTRY();
++
++ PRINT_INFO("Init thread started, PID %d", current->pid);
++
++ current->flags |= PF_NOFREEZE;
++
++ set_user_nice(current, -10);
++
++ spin_lock_irq(&scst_init_lock);
++ while (!kthread_should_stop()) {
++ wait_queue_t wait;
++ init_waitqueue_entry(&wait, current);
++
++ if (!test_init_cmd_list()) {
++ add_wait_queue_exclusive(&scst_init_cmd_list_waitQ,
++ &wait);
++ for (;;) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (test_init_cmd_list())
++ break;
++ spin_unlock_irq(&scst_init_lock);
++ schedule();
++ spin_lock_irq(&scst_init_lock);
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&scst_init_cmd_list_waitQ, &wait);
++ }
++ scst_do_job_init();
++ }
++ spin_unlock_irq(&scst_init_lock);
++
++ /*
++ * If kthread_should_stop() is true, we are guaranteed to be
++ * on the module unload, so scst_init_cmd_list must be empty.
++ */
++ BUG_ON(!list_empty(&scst_init_cmd_list));
++
++ PRINT_INFO("Init thread PID %d finished", current->pid);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++/**
++ * scst_process_active_cmd() - process active command
++ *
++ * Description:
++ * Main SCST commands processing routing. Must be used only by dev handlers.
++ *
++ * Argument atomic is true, if function called in atomic context.
++ *
++ * Must be called with no locks held.
++ */
++void scst_process_active_cmd(struct scst_cmd *cmd, bool atomic)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ /*
++ * Checkpatch will complain on the use of in_atomic() below. You
++ * can safely ignore this warning since in_atomic() is used here only
++ * for debugging purposes.
++ */
++ EXTRACHECKS_BUG_ON(in_irq() || irqs_disabled());
++ EXTRACHECKS_WARN_ON((in_atomic() || in_interrupt()) && !atomic);
++
++ cmd->atomic = atomic;
++
++ TRACE_DBG("cmd %p, atomic %d", cmd, atomic);
++
++ do {
++ switch (cmd->state) {
++ case SCST_CMD_STATE_PARSE:
++ res = scst_parse_cmd(cmd);
++ break;
++
++ case SCST_CMD_STATE_PREPARE_SPACE:
++ res = scst_prepare_space(cmd);
++ break;
++
++ case SCST_CMD_STATE_PREPROCESSING_DONE:
++ res = scst_preprocessing_done(cmd);
++ break;
++
++ case SCST_CMD_STATE_RDY_TO_XFER:
++ res = scst_rdy_to_xfer(cmd);
++ break;
++
++ case SCST_CMD_STATE_TGT_PRE_EXEC:
++ res = scst_tgt_pre_exec(cmd);
++ break;
++
++ case SCST_CMD_STATE_SEND_FOR_EXEC:
++ if (tm_dbg_check_cmd(cmd) != 0) {
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ TRACE_MGMT_DBG("Skipping cmd %p (tag %llu), "
++ "because of TM DBG delay", cmd,
++ (long long unsigned int)cmd->tag);
++ break;
++ }
++ res = scst_send_for_exec(&cmd);
++ EXTRACHECKS_BUG_ON(res == SCST_CMD_STATE_RES_NEED_THREAD);
++ /*
++ * !! At this point cmd, sess & tgt_dev can already be
++ * freed !!
++ */
++ break;
++
++ case SCST_CMD_STATE_START_EXEC:
++ res = scst_exec(&cmd);
++ EXTRACHECKS_BUG_ON(res == SCST_CMD_STATE_RES_NEED_THREAD);
++ /*
++ * !! At this point cmd, sess & tgt_dev can already be
++ * freed !!
++ */
++ break;
++
++ case SCST_CMD_STATE_LOCAL_EXEC:
++ res = scst_local_exec(cmd);
++ EXTRACHECKS_BUG_ON(res == SCST_CMD_STATE_RES_NEED_THREAD);
++ /*
++ * !! At this point cmd, sess & tgt_dev can already be
++ * freed !!
++ */
++ break;
++
++ case SCST_CMD_STATE_REAL_EXEC:
++ res = scst_real_exec(cmd);
++ EXTRACHECKS_BUG_ON(res == SCST_CMD_STATE_RES_NEED_THREAD);
++ /*
++ * !! At this point cmd, sess & tgt_dev can already be
++ * freed !!
++ */
++ break;
++
++ case SCST_CMD_STATE_PRE_DEV_DONE:
++ res = scst_pre_dev_done(cmd);
++ EXTRACHECKS_BUG_ON((res == SCST_CMD_STATE_RES_NEED_THREAD) &&
++ (cmd->state == SCST_CMD_STATE_PRE_DEV_DONE));
++ break;
++
++ case SCST_CMD_STATE_MODE_SELECT_CHECKS:
++ res = scst_mode_select_checks(cmd);
++ break;
++
++ case SCST_CMD_STATE_DEV_DONE:
++ res = scst_dev_done(cmd);
++ break;
++
++ case SCST_CMD_STATE_PRE_XMIT_RESP:
++ res = scst_pre_xmit_response(cmd);
++ EXTRACHECKS_BUG_ON(res ==
++ SCST_CMD_STATE_RES_NEED_THREAD);
++ break;
++
++ case SCST_CMD_STATE_XMIT_RESP:
++ res = scst_xmit_response(cmd);
++ break;
++
++ case SCST_CMD_STATE_FINISHED:
++ res = scst_finish_cmd(cmd);
++ break;
++
++ case SCST_CMD_STATE_FINISHED_INTERNAL:
++ res = scst_finish_internal_cmd(cmd);
++ EXTRACHECKS_BUG_ON(res ==
++ SCST_CMD_STATE_RES_NEED_THREAD);
++ break;
++
++ default:
++ PRINT_CRIT_ERROR("cmd (%p) in state %d, but shouldn't "
++ "be", cmd, cmd->state);
++ BUG();
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++ break;
++ }
++ } while (res == SCST_CMD_STATE_RES_CONT_SAME);
++
++ if (res == SCST_CMD_STATE_RES_CONT_NEXT) {
++ /* None */
++ } else if (res == SCST_CMD_STATE_RES_NEED_THREAD) {
++ spin_lock_irq(&cmd->cmd_threads->cmd_list_lock);
++#ifdef CONFIG_SCST_EXTRACHECKS
++ switch (cmd->state) {
++ case SCST_CMD_STATE_PARSE:
++ case SCST_CMD_STATE_PREPARE_SPACE:
++ case SCST_CMD_STATE_RDY_TO_XFER:
++ case SCST_CMD_STATE_TGT_PRE_EXEC:
++ case SCST_CMD_STATE_SEND_FOR_EXEC:
++ case SCST_CMD_STATE_START_EXEC:
++ case SCST_CMD_STATE_LOCAL_EXEC:
++ case SCST_CMD_STATE_REAL_EXEC:
++ case SCST_CMD_STATE_DEV_DONE:
++ case SCST_CMD_STATE_XMIT_RESP:
++#endif
++ TRACE_DBG("Adding cmd %p to head of active cmd list",
++ cmd);
++ list_add(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++#ifdef CONFIG_SCST_EXTRACHECKS
++ break;
++ default:
++ PRINT_CRIT_ERROR("cmd %p is in invalid state %d)", cmd,
++ cmd->state);
++ spin_unlock_irq(&cmd->cmd_threads->cmd_list_lock);
++ BUG();
++ spin_lock_irq(&cmd->cmd_threads->cmd_list_lock);
++ break;
++ }
++#endif
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock_irq(&cmd->cmd_threads->cmd_list_lock);
++ } else
++ BUG();
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_process_active_cmd);
++
++/* Called under cmd_list_lock and IRQs disabled */
++static void scst_do_job_active(struct list_head *cmd_list,
++ spinlock_t *cmd_list_lock, bool atomic)
++ __releases(cmd_list_lock)
++ __acquires(cmd_list_lock)
++{
++ TRACE_ENTRY();
++
++ while (!list_empty(cmd_list)) {
++ struct scst_cmd *cmd = list_entry(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(cmd_list_lock);
++ scst_process_active_cmd(cmd, atomic);
++ spin_lock_irq(cmd_list_lock);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline int test_cmd_threads(struct scst_cmd_threads *p_cmd_threads)
++{
++ int res = !list_empty(&p_cmd_threads->active_cmd_list) ||
++ unlikely(kthread_should_stop()) ||
++ tm_dbg_is_release();
++ return res;
++}
++
++int scst_cmd_thread(void *arg)
++{
++ struct scst_cmd_threads *p_cmd_threads = arg;
++
++ TRACE_ENTRY();
++
++ PRINT_INFO("Processing thread %s (PID %d) started", current->comm,
++ current->pid);
++
++#if 0
++ set_user_nice(current, 10);
++#endif
++ current->flags |= PF_NOFREEZE;
++
++ mutex_lock(&p_cmd_threads->io_context_mutex);
++
++ WARN_ON(current->io_context);
++
++ if (p_cmd_threads != &scst_main_cmd_threads) {
++ /*
++ * For linked IO contexts io_context might be not NULL while
++ * io_context 0.
++ */
++ if (p_cmd_threads->io_context == NULL) {
++ p_cmd_threads->io_context = get_io_context(GFP_KERNEL, -1);
++ TRACE_MGMT_DBG("Alloced new IO context %p "
++ "(p_cmd_threads %p)",
++ p_cmd_threads->io_context,
++ p_cmd_threads);
++ /*
++ * Put the extra reference created by get_io_context()
++ * because we don't need it.
++ */
++ put_io_context(p_cmd_threads->io_context);
++ } else {
++ current->io_context = ioc_task_link(p_cmd_threads->io_context);
++ TRACE_MGMT_DBG("Linked IO context %p "
++ "(p_cmd_threads %p)", p_cmd_threads->io_context,
++ p_cmd_threads);
++ }
++ p_cmd_threads->io_context_refcnt++;
++ }
++
++ mutex_unlock(&p_cmd_threads->io_context_mutex);
++
++ smp_wmb();
++ p_cmd_threads->io_context_ready = true;
++
++ spin_lock_irq(&p_cmd_threads->cmd_list_lock);
++ while (!kthread_should_stop()) {
++ wait_queue_t wait;
++ init_waitqueue_entry(&wait, current);
++
++ if (!test_cmd_threads(p_cmd_threads)) {
++ add_wait_queue_exclusive_head(
++ &p_cmd_threads->cmd_list_waitQ,
++ &wait);
++ for (;;) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (test_cmd_threads(p_cmd_threads))
++ break;
++ spin_unlock_irq(&p_cmd_threads->cmd_list_lock);
++ schedule();
++ spin_lock_irq(&p_cmd_threads->cmd_list_lock);
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&p_cmd_threads->cmd_list_waitQ, &wait);
++ }
++
++ if (tm_dbg_is_release()) {
++ spin_unlock_irq(&p_cmd_threads->cmd_list_lock);
++ tm_dbg_check_released_cmds();
++ spin_lock_irq(&p_cmd_threads->cmd_list_lock);
++ }
++
++ scst_do_job_active(&p_cmd_threads->active_cmd_list,
++ &p_cmd_threads->cmd_list_lock, false);
++ }
++ spin_unlock_irq(&p_cmd_threads->cmd_list_lock);
++
++ if (p_cmd_threads != &scst_main_cmd_threads) {
++ mutex_lock(&p_cmd_threads->io_context_mutex);
++ if (--p_cmd_threads->io_context_refcnt == 0)
++ p_cmd_threads->io_context = NULL;
++ mutex_unlock(&p_cmd_threads->io_context_mutex);
++ }
++
++ PRINT_INFO("Processing thread %s (PID %d) finished", current->comm,
++ current->pid);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++void scst_cmd_tasklet(long p)
++{
++ struct scst_percpu_info *i = (struct scst_percpu_info *)p;
++
++ TRACE_ENTRY();
++
++ spin_lock_irq(&i->tasklet_lock);
++ scst_do_job_active(&i->tasklet_cmd_list, &i->tasklet_lock, true);
++ spin_unlock_irq(&i->tasklet_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * Returns 0 on success, < 0 if there is no device handler or
++ * > 0 if SCST_FLAG_SUSPENDED set and SCST_FLAG_SUSPENDING - not.
++ * No locks, protection is done by the suspended activity.
++ */
++static int scst_mgmt_translate_lun(struct scst_mgmt_cmd *mcmd)
++{
++ struct scst_tgt_dev *tgt_dev = NULL;
++ struct list_head *head;
++ int res = -1;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Finding tgt_dev for mgmt cmd %p (lun %lld)", mcmd,
++ (long long unsigned int)mcmd->lun);
++
++ mcmd->cpu_cmd_counter = scst_get();
++
++ if (unlikely(test_bit(SCST_FLAG_SUSPENDED, &scst_flags) &&
++ !test_bit(SCST_FLAG_SUSPENDING, &scst_flags))) {
++ TRACE_MGMT_DBG("%s", "FLAG SUSPENDED set, skipping");
++ scst_put(mcmd->cpu_cmd_counter);
++ res = 1;
++ goto out;
++ }
++
++ head = &mcmd->sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(mcmd->lun)];
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ if (tgt_dev->lun == mcmd->lun) {
++ TRACE_DBG("tgt_dev %p found", tgt_dev);
++ mcmd->mcmd_tgt_dev = tgt_dev;
++ res = 0;
++ break;
++ }
++ }
++ if (mcmd->mcmd_tgt_dev == NULL)
++ scst_put(mcmd->cpu_cmd_counter);
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/* No locks */
++void scst_done_cmd_mgmt(struct scst_cmd *cmd)
++{
++ struct scst_mgmt_cmd_stub *mstb, *t;
++ bool wake = 0;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("cmd %p done (tag %llu)",
++ cmd, (long long unsigned int)cmd->tag);
++
++ spin_lock_irqsave(&scst_mcmd_lock, flags);
++
++ list_for_each_entry_safe(mstb, t, &cmd->mgmt_cmd_list,
++ cmd_mgmt_cmd_list_entry) {
++ struct scst_mgmt_cmd *mcmd;
++
++ if (!mstb->done_counted)
++ continue;
++
++ mcmd = mstb->mcmd;
++ TRACE_MGMT_DBG("mcmd %p, mcmd->cmd_done_wait_count %d",
++ mcmd, mcmd->cmd_done_wait_count);
++
++ mcmd->cmd_done_wait_count--;
++
++ BUG_ON(mcmd->cmd_done_wait_count < 0);
++
++ if (mcmd->cmd_done_wait_count > 0) {
++ TRACE_MGMT_DBG("cmd_done_wait_count(%d) not 0, "
++ "skipping", mcmd->cmd_done_wait_count);
++ goto check_free;
++ }
++
++ if (mcmd->state == SCST_MCMD_STATE_WAITING_AFFECTED_CMDS_DONE) {
++ mcmd->state = SCST_MCMD_STATE_AFFECTED_CMDS_DONE;
++ TRACE_MGMT_DBG("Adding mgmt cmd %p to active mgmt cmd "
++ "list", mcmd);
++ list_add_tail(&mcmd->mgmt_cmd_list_entry,
++ &scst_active_mgmt_cmd_list);
++ wake = 1;
++ }
++
++check_free:
++ if (!mstb->finish_counted) {
++ TRACE_DBG("Releasing mstb %p", mstb);
++ list_del(&mstb->cmd_mgmt_cmd_list_entry);
++ mempool_free(mstb, scst_mgmt_stub_mempool);
++ }
++ }
++
++ spin_unlock_irqrestore(&scst_mcmd_lock, flags);
++
++ if (wake)
++ wake_up(&scst_mgmt_cmd_list_waitQ);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called under scst_mcmd_lock and IRQs disabled */
++static void __scst_dec_finish_wait_count(struct scst_mgmt_cmd *mcmd, bool *wake)
++{
++ TRACE_ENTRY();
++
++ mcmd->cmd_finish_wait_count--;
++
++ BUG_ON(mcmd->cmd_finish_wait_count < 0);
++
++ if (mcmd->cmd_finish_wait_count > 0) {
++ TRACE_MGMT_DBG("cmd_finish_wait_count(%d) not 0, "
++ "skipping", mcmd->cmd_finish_wait_count);
++ goto out;
++ }
++
++ if (mcmd->cmd_done_wait_count > 0) {
++ TRACE_MGMT_DBG("cmd_done_wait_count(%d) not 0, "
++ "skipping", mcmd->cmd_done_wait_count);
++ goto out;
++ }
++
++ if (mcmd->state == SCST_MCMD_STATE_WAITING_AFFECTED_CMDS_FINISHED) {
++ mcmd->state = SCST_MCMD_STATE_DONE;
++ TRACE_MGMT_DBG("Adding mgmt cmd %p to active mgmt cmd "
++ "list", mcmd);
++ list_add_tail(&mcmd->mgmt_cmd_list_entry,
++ &scst_active_mgmt_cmd_list);
++ *wake = true;
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_prepare_async_mcmd() - prepare async management command
++ *
++ * Notifies SCST that management command is going to be async, i.e.
++ * will be completed in another context.
++ *
++ * No SCST locks supposed to be held on entrance.
++ */
++void scst_prepare_async_mcmd(struct scst_mgmt_cmd *mcmd)
++{
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Preparing mcmd %p for async execution "
++ "(cmd_finish_wait_count %d)", mcmd,
++ mcmd->cmd_finish_wait_count);
++
++ spin_lock_irqsave(&scst_mcmd_lock, flags);
++ mcmd->cmd_finish_wait_count++;
++ spin_unlock_irqrestore(&scst_mcmd_lock, flags);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_prepare_async_mcmd);
++
++/**
++ * scst_async_mcmd_completed() - async management command completed
++ *
++ * Notifies SCST that async management command, prepared by
++ * scst_prepare_async_mcmd(), completed.
++ *
++ * No SCST locks supposed to be held on entrance.
++ */
++void scst_async_mcmd_completed(struct scst_mgmt_cmd *mcmd, int status)
++{
++ unsigned long flags;
++ bool wake = false;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Async mcmd %p completed (status %d)", mcmd, status);
++
++ spin_lock_irqsave(&scst_mcmd_lock, flags);
++
++ if (status != SCST_MGMT_STATUS_SUCCESS)
++ mcmd->status = status;
++
++ __scst_dec_finish_wait_count(mcmd, &wake);
++
++ spin_unlock_irqrestore(&scst_mcmd_lock, flags);
++
++ if (wake)
++ wake_up(&scst_mgmt_cmd_list_waitQ);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_async_mcmd_completed);
++
++/* No locks */
++static void scst_finish_cmd_mgmt(struct scst_cmd *cmd)
++{
++ struct scst_mgmt_cmd_stub *mstb, *t;
++ bool wake = false;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("cmd %p finished (tag %llu)",
++ cmd, (long long unsigned int)cmd->tag);
++
++ spin_lock_irqsave(&scst_mcmd_lock, flags);
++
++ list_for_each_entry_safe(mstb, t, &cmd->mgmt_cmd_list,
++ cmd_mgmt_cmd_list_entry) {
++ struct scst_mgmt_cmd *mcmd = mstb->mcmd;
++
++ TRACE_MGMT_DBG("mcmd %p, mcmd->cmd_finish_wait_count %d", mcmd,
++ mcmd->cmd_finish_wait_count);
++
++ BUG_ON(!mstb->finish_counted);
++
++ if (cmd->completed)
++ mcmd->completed_cmd_count++;
++
++ __scst_dec_finish_wait_count(mcmd, &wake);
++
++ TRACE_DBG("Releasing mstb %p", mstb);
++ list_del(&mstb->cmd_mgmt_cmd_list_entry);
++ mempool_free(mstb, scst_mgmt_stub_mempool);
++ }
++
++ spin_unlock_irqrestore(&scst_mcmd_lock, flags);
++
++ if (wake)
++ wake_up(&scst_mgmt_cmd_list_waitQ);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int scst_call_dev_task_mgmt_fn(struct scst_mgmt_cmd *mcmd,
++ struct scst_tgt_dev *tgt_dev, int set_status)
++{
++ int res = SCST_DEV_TM_NOT_COMPLETED;
++ struct scst_dev_type *h = tgt_dev->dev->handler;
++
++ if (h->task_mgmt_fn) {
++ TRACE_MGMT_DBG("Calling dev handler %s task_mgmt_fn(fn=%d)",
++ h->name, mcmd->fn);
++ res = h->task_mgmt_fn(mcmd, tgt_dev);
++ TRACE_MGMT_DBG("Dev handler %s task_mgmt_fn() returned %d",
++ h->name, res);
++ if (set_status && (res != SCST_DEV_TM_NOT_COMPLETED))
++ mcmd->status = res;
++ }
++ return res;
++}
++
++static inline int scst_is_strict_mgmt_fn(int mgmt_fn)
++{
++ switch (mgmt_fn) {
++#ifdef CONFIG_SCST_ABORT_CONSIDER_FINISHED_TASKS_AS_NOT_EXISTING
++ case SCST_ABORT_TASK:
++#endif
++#if 0
++ case SCST_ABORT_TASK_SET:
++ case SCST_CLEAR_TASK_SET:
++#endif
++ return 1;
++ default:
++ return 0;
++ }
++}
++
++/*
++ * Must be called under sess_list_lock to sync with finished flag assignment in
++ * scst_finish_cmd()
++ */
++void scst_abort_cmd(struct scst_cmd *cmd, struct scst_mgmt_cmd *mcmd,
++ bool other_ini, bool call_dev_task_mgmt_fn)
++{
++ unsigned long flags;
++ static DEFINE_SPINLOCK(other_ini_lock);
++
++ TRACE_ENTRY();
++
++ TRACE(TRACE_SCSI|TRACE_MGMT_DEBUG, "Aborting cmd %p (tag %llu, op %x)",
++ cmd, (long long unsigned int)cmd->tag, cmd->cdb[0]);
++
++ /* To protect from concurrent aborts */
++ spin_lock_irqsave(&other_ini_lock, flags);
++
++ if (other_ini) {
++ struct scst_device *dev = NULL;
++
++ /* Might be necessary if command aborted several times */
++ if (!test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))
++ set_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags);
++
++ /* Necessary for scst_xmit_process_aborted_cmd */
++ if (cmd->dev != NULL)
++ dev = cmd->dev;
++ else if ((mcmd != NULL) && (mcmd->mcmd_tgt_dev != NULL))
++ dev = mcmd->mcmd_tgt_dev->dev;
++
++ if (dev != NULL) {
++ if (dev->tas)
++ set_bit(SCST_CMD_DEVICE_TAS, &cmd->cmd_flags);
++ } else
++ PRINT_WARNING("Abort cmd %p from other initiator, but "
++ "neither cmd, nor mcmd %p have tgt_dev set, so "
++ "TAS information can be lost", cmd, mcmd);
++ } else {
++ /* Might be necessary if command aborted several times */
++ clear_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags);
++ }
++
++ set_bit(SCST_CMD_ABORTED, &cmd->cmd_flags);
++
++ spin_unlock_irqrestore(&other_ini_lock, flags);
++
++ /*
++ * To sync with setting cmd->done in scst_pre_xmit_response() (with
++ * scst_finish_cmd() we synced by using sess_list_lock) and with
++ * setting UA for aborted cmd in scst_set_pending_UA().
++ */
++ smp_mb__after_set_bit();
++
++ if (cmd->tgt_dev == NULL) {
++ spin_lock_irqsave(&scst_init_lock, flags);
++ scst_init_poll_cnt++;
++ spin_unlock_irqrestore(&scst_init_lock, flags);
++ wake_up(&scst_init_cmd_list_waitQ);
++ }
++
++ if (!cmd->finished && call_dev_task_mgmt_fn && (cmd->tgt_dev != NULL))
++ scst_call_dev_task_mgmt_fn(mcmd, cmd->tgt_dev, 1);
++
++ spin_lock_irqsave(&scst_mcmd_lock, flags);
++ if ((mcmd != NULL) && !cmd->finished) {
++ struct scst_mgmt_cmd_stub *mstb;
++
++ mstb = mempool_alloc(scst_mgmt_stub_mempool, GFP_ATOMIC);
++ if (mstb == NULL) {
++ PRINT_CRIT_ERROR("Allocation of management command "
++ "stub failed (mcmd %p, cmd %p)", mcmd, cmd);
++ goto unlock;
++ }
++ memset(mstb, 0, sizeof(*mstb));
++
++ TRACE_DBG("mstb %p, mcmd %p", mstb, mcmd);
++
++ mstb->mcmd = mcmd;
++
++ /*
++ * Delay the response until the command's finish in order to
++ * guarantee that "no further responses from the task are sent
++ * to the SCSI initiator port" after response from the TM
++ * function is sent (SAM). Plus, we must wait here to be sure
++ * that we won't receive double commands with the same tag.
++ * Moreover, if we don't wait here, we might have a possibility
++ * for data corruption, when aborted and reported as completed
++ * command actually gets executed *after* new commands sent
++ * after this TM command completed.
++ */
++
++ if (cmd->sent_for_exec && !cmd->done) {
++ TRACE_MGMT_DBG("cmd %p (tag %llu) is being executed",
++ cmd, (long long unsigned int)cmd->tag);
++ mstb->done_counted = 1;
++ mcmd->cmd_done_wait_count++;
++ }
++
++ /*
++ * We don't have to wait the command's status delivery finish
++ * to other initiators + it can affect MPIO failover.
++ */
++ if (!other_ini) {
++ mstb->finish_counted = 1;
++ mcmd->cmd_finish_wait_count++;
++ }
++
++ if (mstb->done_counted || mstb->finish_counted) {
++ TRACE(TRACE_SCSI|TRACE_MGMT_DEBUG, "cmd %p (tag %llu, "
++ "sn %u) being executed/xmitted (state %d, "
++ "op %x, proc time %ld sec., timeout %d sec.), "
++ "deferring ABORT (cmd_done_wait_count %d, "
++ "cmd_finish_wait_count %d)", cmd,
++ (long long unsigned int)cmd->tag,
++ cmd->sn, cmd->state, cmd->cdb[0],
++ (long)(jiffies - cmd->start_time) / HZ,
++ cmd->timeout / HZ, mcmd->cmd_done_wait_count,
++ mcmd->cmd_finish_wait_count);
++ /*
++ * cmd can't die here or sess_list_lock already taken
++ * and cmd is in the sess list
++ */
++ list_add_tail(&mstb->cmd_mgmt_cmd_list_entry,
++ &cmd->mgmt_cmd_list);
++ } else {
++ /* We don't need to wait for this cmd */
++ mempool_free(mstb, scst_mgmt_stub_mempool);
++ }
++
++ if (cmd->tgtt->on_abort_cmd)
++ cmd->tgtt->on_abort_cmd(cmd);
++ }
++
++unlock:
++ spin_unlock_irqrestore(&scst_mcmd_lock, flags);
++
++ tm_dbg_release_cmd(cmd);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* No locks. Returns 0, if mcmd should be processed further. */
++static int scst_set_mcmd_next_state(struct scst_mgmt_cmd *mcmd)
++{
++ int res;
++
++ spin_lock_irq(&scst_mcmd_lock);
++
++ switch (mcmd->state) {
++ case SCST_MCMD_STATE_INIT:
++ case SCST_MCMD_STATE_EXEC:
++ if (mcmd->cmd_done_wait_count == 0) {
++ mcmd->state = SCST_MCMD_STATE_AFFECTED_CMDS_DONE;
++ res = 0;
++ } else {
++ TRACE(TRACE_SCSI|TRACE_MGMT_DEBUG,
++ "cmd_done_wait_count(%d) not 0, "
++ "preparing to wait", mcmd->cmd_done_wait_count);
++ mcmd->state = SCST_MCMD_STATE_WAITING_AFFECTED_CMDS_DONE;
++ res = -1;
++ }
++ break;
++
++ case SCST_MCMD_STATE_AFFECTED_CMDS_DONE:
++ if (mcmd->cmd_finish_wait_count == 0) {
++ mcmd->state = SCST_MCMD_STATE_DONE;
++ res = 0;
++ } else {
++ TRACE(TRACE_SCSI|TRACE_MGMT_DEBUG,
++ "cmd_finish_wait_count(%d) not 0, "
++ "preparing to wait",
++ mcmd->cmd_finish_wait_count);
++ mcmd->state = SCST_MCMD_STATE_WAITING_AFFECTED_CMDS_FINISHED;
++ res = -1;
++ }
++ break;
++
++ case SCST_MCMD_STATE_DONE:
++ mcmd->state = SCST_MCMD_STATE_FINISHED;
++ res = 0;
++ break;
++
++ default:
++ PRINT_CRIT_ERROR("Wrong mcmd %p state %d (fn %d, "
++ "cmd_finish_wait_count %d, cmd_done_wait_count %d)",
++ mcmd, mcmd->state, mcmd->fn,
++ mcmd->cmd_finish_wait_count, mcmd->cmd_done_wait_count);
++ spin_unlock_irq(&scst_mcmd_lock);
++ res = -1;
++ BUG();
++ goto out;
++ }
++
++ spin_unlock_irq(&scst_mcmd_lock);
++
++out:
++ return res;
++}
++
++/* IRQs supposed to be disabled */
++static bool __scst_check_unblock_aborted_cmd(struct scst_cmd *cmd,
++ struct list_head *list_entry)
++{
++ bool res;
++ if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) {
++ list_del(list_entry);
++ spin_lock(&cmd->cmd_threads->cmd_list_lock);
++ list_add_tail(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock(&cmd->cmd_threads->cmd_list_lock);
++ res = 1;
++ } else
++ res = 0;
++ return res;
++}
++
++static void scst_unblock_aborted_cmds(int scst_mutex_held)
++{
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ if (!scst_mutex_held)
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
++ struct scst_cmd *cmd, *tcmd;
++ struct scst_tgt_dev *tgt_dev;
++ spin_lock_bh(&dev->dev_lock);
++ local_irq_disable();
++ list_for_each_entry_safe(cmd, tcmd, &dev->blocked_cmd_list,
++ blocked_cmd_list_entry) {
++ if (__scst_check_unblock_aborted_cmd(cmd,
++ &cmd->blocked_cmd_list_entry)) {
++ TRACE_MGMT_DBG("Unblock aborted blocked cmd %p",
++ cmd);
++ }
++ }
++ local_irq_enable();
++ spin_unlock_bh(&dev->dev_lock);
++
++ local_irq_disable();
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ struct scst_order_data *order_data = tgt_dev->curr_order_data;
++ spin_lock(&order_data->sn_lock);
++ list_for_each_entry_safe(cmd, tcmd,
++ &order_data->deferred_cmd_list,
++ sn_cmd_list_entry) {
++ if (__scst_check_unblock_aborted_cmd(cmd,
++ &cmd->sn_cmd_list_entry)) {
++ TRACE_MGMT_DBG("Unblocked aborted SN "
++ "cmd %p (sn %u)",
++ cmd, cmd->sn);
++ order_data->def_cmd_count--;
++ }
++ }
++ spin_unlock(&order_data->sn_lock);
++ }
++ local_irq_enable();
++ }
++
++ if (!scst_mutex_held)
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void __scst_abort_task_set(struct scst_mgmt_cmd *mcmd,
++ struct scst_tgt_dev *tgt_dev)
++{
++ struct scst_cmd *cmd;
++ struct scst_session *sess = tgt_dev->sess;
++ bool other_ini;
++
++ TRACE_ENTRY();
++
++ if ((mcmd->fn == SCST_PR_ABORT_ALL) &&
++ (mcmd->origin_pr_cmd->sess != sess))
++ other_ini = true;
++ else
++ other_ini = false;
++
++ spin_lock_irq(&sess->sess_list_lock);
++
++ TRACE_DBG("Searching in sess cmd list (sess=%p)", sess);
++ list_for_each_entry(cmd, &sess->sess_cmd_list,
++ sess_cmd_list_entry) {
++ if ((mcmd->fn == SCST_PR_ABORT_ALL) &&
++ (mcmd->origin_pr_cmd == cmd))
++ continue;
++ if ((cmd->tgt_dev == tgt_dev) ||
++ ((cmd->tgt_dev == NULL) &&
++ (cmd->lun == tgt_dev->lun))) {
++ if (mcmd->cmd_sn_set) {
++ BUG_ON(!cmd->tgt_sn_set);
++ if (scst_sn_before(mcmd->cmd_sn, cmd->tgt_sn) ||
++ (mcmd->cmd_sn == cmd->tgt_sn))
++ continue;
++ }
++ scst_abort_cmd(cmd, mcmd, other_ini, 0);
++ }
++ }
++ spin_unlock_irq(&sess->sess_list_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Returns 0 if the command processing should be continued, <0 otherwise */
++static int scst_abort_task_set(struct scst_mgmt_cmd *mcmd)
++{
++ int res;
++ struct scst_tgt_dev *tgt_dev = mcmd->mcmd_tgt_dev;
++
++ TRACE(TRACE_MGMT, "Aborting task set (lun=%lld, mcmd=%p)",
++ (long long unsigned int)tgt_dev->lun, mcmd);
++
++ __scst_abort_task_set(mcmd, tgt_dev);
++
++ if (mcmd->fn == SCST_PR_ABORT_ALL) {
++ struct scst_pr_abort_all_pending_mgmt_cmds_counter *pr_cnt =
++ mcmd->origin_pr_cmd->pr_abort_counter;
++ if (atomic_dec_and_test(&pr_cnt->pr_aborting_cnt))
++ complete_all(&pr_cnt->pr_aborting_cmpl);
++ }
++
++ tm_dbg_task_mgmt(mcmd->mcmd_tgt_dev->dev, "ABORT TASK SET/PR ABORT", 0);
++
++ scst_unblock_aborted_cmds(0);
++
++ scst_call_dev_task_mgmt_fn(mcmd, tgt_dev, 0);
++
++ res = scst_set_mcmd_next_state(mcmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_is_cmd_belongs_to_dev(struct scst_cmd *cmd,
++ struct scst_device *dev)
++{
++ struct scst_tgt_dev *tgt_dev = NULL;
++ struct list_head *head;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Finding match for dev %s and cmd %p (lun %lld)", dev->virt_name,
++ cmd, (long long unsigned int)cmd->lun);
++
++ head = &cmd->sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(cmd->lun)];
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ if (tgt_dev->lun == cmd->lun) {
++ TRACE_DBG("dev %s found", tgt_dev->dev->virt_name);
++ res = (tgt_dev->dev == dev);
++ goto out;
++ }
++ }
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/* Returns 0 if the command processing should be continued, <0 otherwise */
++static int scst_clear_task_set(struct scst_mgmt_cmd *mcmd)
++{
++ int res;
++ struct scst_device *dev = mcmd->mcmd_tgt_dev->dev;
++ struct scst_tgt_dev *tgt_dev;
++ LIST_HEAD(UA_tgt_devs);
++
++ TRACE_ENTRY();
++
++ TRACE(TRACE_MGMT, "Clearing task set (lun=%lld, mcmd=%p)",
++ (long long unsigned int)mcmd->lun, mcmd);
++
++#if 0 /* we are SAM-3 */
++ /*
++ * When a logical unit is aborting one or more tasks from a SCSI
++ * initiator port with the TASK ABORTED status it should complete all
++ * of those tasks before entering additional tasks from that SCSI
++ * initiator port into the task set - SAM2
++ */
++ mcmd->needs_unblocking = 1;
++ spin_lock_bh(&dev->dev_lock);
++ scst_block_dev(dev);
++ spin_unlock_bh(&dev->dev_lock);
++#endif
++
++ __scst_abort_task_set(mcmd, mcmd->mcmd_tgt_dev);
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ struct scst_session *sess = tgt_dev->sess;
++ struct scst_cmd *cmd;
++ int aborted = 0;
++
++ if (tgt_dev == mcmd->mcmd_tgt_dev)
++ continue;
++
++ spin_lock_irq(&sess->sess_list_lock);
++
++ TRACE_DBG("Searching in sess cmd list (sess=%p)", sess);
++ list_for_each_entry(cmd, &sess->sess_cmd_list,
++ sess_cmd_list_entry) {
++ if ((cmd->dev == dev) ||
++ ((cmd->dev == NULL) &&
++ scst_is_cmd_belongs_to_dev(cmd, dev))) {
++ scst_abort_cmd(cmd, mcmd, 1, 0);
++ aborted = 1;
++ }
++ }
++ spin_unlock_irq(&sess->sess_list_lock);
++
++ if (aborted)
++ list_add_tail(&tgt_dev->extra_tgt_dev_list_entry,
++ &UA_tgt_devs);
++ }
++
++ tm_dbg_task_mgmt(mcmd->mcmd_tgt_dev->dev, "CLEAR TASK SET", 0);
++
++ scst_unblock_aborted_cmds(1);
++
++ mutex_unlock(&scst_mutex);
++
++ if (!dev->tas) {
++ uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
++ int sl;
++
++ sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
++ dev->d_sense,
++ SCST_LOAD_SENSE(scst_sense_cleared_by_another_ini_UA));
++
++ list_for_each_entry(tgt_dev, &UA_tgt_devs,
++ extra_tgt_dev_list_entry) {
++ scst_check_set_UA(tgt_dev, sense_buffer, sl, 0);
++ }
++ }
++
++ scst_call_dev_task_mgmt_fn(mcmd, mcmd->mcmd_tgt_dev, 0);
++
++ res = scst_set_mcmd_next_state(mcmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Returns 0 if the command processing should be continued,
++ * >0, if it should be requeued, <0 otherwise */
++static int scst_mgmt_cmd_init(struct scst_mgmt_cmd *mcmd)
++{
++ int res = 0, rc;
++
++ TRACE_ENTRY();
++
++ switch (mcmd->fn) {
++ case SCST_ABORT_TASK:
++ {
++ struct scst_session *sess = mcmd->sess;
++ struct scst_cmd *cmd;
++
++ spin_lock_irq(&sess->sess_list_lock);
++ cmd = __scst_find_cmd_by_tag(sess, mcmd->tag, true);
++ if (cmd == NULL) {
++ TRACE_MGMT_DBG("ABORT TASK: command "
++ "for tag %llu not found",
++ (long long unsigned int)mcmd->tag);
++ mcmd->status = SCST_MGMT_STATUS_TASK_NOT_EXIST;
++ spin_unlock_irq(&sess->sess_list_lock);
++ res = scst_set_mcmd_next_state(mcmd);
++ goto out;
++ }
++ __scst_cmd_get(cmd);
++ spin_unlock_irq(&sess->sess_list_lock);
++ TRACE_DBG("Cmd to abort %p for tag %llu found",
++ cmd, (long long unsigned int)mcmd->tag);
++ mcmd->cmd_to_abort = cmd;
++ mcmd->state = SCST_MCMD_STATE_EXEC;
++ break;
++ }
++
++ case SCST_TARGET_RESET:
++ case SCST_NEXUS_LOSS_SESS:
++ case SCST_ABORT_ALL_TASKS_SESS:
++ case SCST_NEXUS_LOSS:
++ case SCST_ABORT_ALL_TASKS:
++ case SCST_UNREG_SESS_TM:
++ mcmd->state = SCST_MCMD_STATE_EXEC;
++ break;
++
++ case SCST_ABORT_TASK_SET:
++ case SCST_CLEAR_ACA:
++ case SCST_CLEAR_TASK_SET:
++ case SCST_LUN_RESET:
++ case SCST_PR_ABORT_ALL:
++ rc = scst_mgmt_translate_lun(mcmd);
++ if (rc == 0)
++ mcmd->state = SCST_MCMD_STATE_EXEC;
++ else if (rc < 0) {
++ PRINT_ERROR("Corresponding device for LUN %lld not "
++ "found", (long long unsigned int)mcmd->lun);
++ mcmd->status = SCST_MGMT_STATUS_LUN_NOT_EXIST;
++ res = scst_set_mcmd_next_state(mcmd);
++ } else
++ res = rc;
++ break;
++
++ default:
++ BUG();
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Returns 0 if the command processing should be continued, <0 otherwise */
++static int scst_target_reset(struct scst_mgmt_cmd *mcmd)
++{
++ int res, rc;
++ struct scst_device *dev;
++ struct scst_acg *acg = mcmd->sess->acg;
++ struct scst_acg_dev *acg_dev;
++ int cont, c;
++ LIST_HEAD(host_devs);
++
++ TRACE_ENTRY();
++
++ TRACE(TRACE_MGMT, "Target reset (mcmd %p, cmd count %d)",
++ mcmd, atomic_read(&mcmd->sess->sess_cmd_count));
++
++ mcmd->needs_unblocking = 1;
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) {
++ struct scst_device *d;
++ struct scst_tgt_dev *tgt_dev;
++ int found = 0;
++
++ dev = acg_dev->dev;
++
++ spin_lock_bh(&dev->dev_lock);
++ scst_block_dev(dev);
++ scst_process_reset(dev, mcmd->sess, NULL, mcmd, true);
++ spin_unlock_bh(&dev->dev_lock);
++
++ cont = 0;
++ c = 0;
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ cont = 1;
++ if (mcmd->sess == tgt_dev->sess) {
++ rc = scst_call_dev_task_mgmt_fn(mcmd,
++ tgt_dev, 0);
++ if (rc == SCST_DEV_TM_NOT_COMPLETED)
++ c = 1;
++ else if ((rc < 0) &&
++ (mcmd->status == SCST_MGMT_STATUS_SUCCESS))
++ mcmd->status = rc;
++ break;
++ }
++ }
++ if (cont && !c)
++ continue;
++
++ if (dev->scsi_dev == NULL)
++ continue;
++
++ list_for_each_entry(d, &host_devs, tm_dev_list_entry) {
++ if (dev->scsi_dev->host->host_no ==
++ d->scsi_dev->host->host_no) {
++ found = 1;
++ break;
++ }
++ }
++ if (!found)
++ list_add_tail(&dev->tm_dev_list_entry, &host_devs);
++
++ tm_dbg_task_mgmt(dev, "TARGET RESET", 0);
++ }
++
++ scst_unblock_aborted_cmds(1);
++
++ /*
++ * We suppose here that for all commands that already on devices
++ * on/after scsi_reset_provider() completion callbacks will be called.
++ */
++
++ list_for_each_entry(dev, &host_devs, tm_dev_list_entry) {
++ /* dev->scsi_dev must be non-NULL here */
++ TRACE(TRACE_MGMT, "Resetting host %d bus ",
++ dev->scsi_dev->host->host_no);
++ rc = scsi_reset_provider(dev->scsi_dev, SCSI_TRY_RESET_TARGET);
++ TRACE(TRACE_MGMT, "Result of host %d target reset: %s",
++ dev->scsi_dev->host->host_no,
++ (rc == SUCCESS) ? "SUCCESS" : "FAILED");
++#if 0
++ if ((rc != SUCCESS) &&
++ (mcmd->status == SCST_MGMT_STATUS_SUCCESS)) {
++ /*
++ * SCSI_TRY_RESET_BUS is also done by
++ * scsi_reset_provider()
++ */
++ mcmd->status = SCST_MGMT_STATUS_FAILED;
++ }
++#else
++ /*
++ * scsi_reset_provider() returns very weird status, so let's
++ * always succeed
++ */
++#endif
++ }
++
++ list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) {
++ dev = acg_dev->dev;
++ if (dev->scsi_dev != NULL)
++ dev->scsi_dev->was_reset = 0;
++ }
++
++ mutex_unlock(&scst_mutex);
++
++ res = scst_set_mcmd_next_state(mcmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Returns 0 if the command processing should be continued, <0 otherwise */
++static int scst_lun_reset(struct scst_mgmt_cmd *mcmd)
++{
++ int res, rc;
++ struct scst_tgt_dev *tgt_dev = mcmd->mcmd_tgt_dev;
++ struct scst_device *dev = tgt_dev->dev;
++
++ TRACE_ENTRY();
++
++ TRACE(TRACE_MGMT, "Resetting LUN %lld (mcmd %p)",
++ (long long unsigned int)tgt_dev->lun, mcmd);
++
++ mcmd->needs_unblocking = 1;
++
++ spin_lock_bh(&dev->dev_lock);
++ scst_block_dev(dev);
++ scst_process_reset(dev, mcmd->sess, NULL, mcmd, true);
++ spin_unlock_bh(&dev->dev_lock);
++
++ rc = scst_call_dev_task_mgmt_fn(mcmd, tgt_dev, 1);
++ if (rc != SCST_DEV_TM_NOT_COMPLETED)
++ goto out_tm_dbg;
++
++ if (dev->scsi_dev != NULL) {
++ TRACE(TRACE_MGMT, "Resetting host %d bus ",
++ dev->scsi_dev->host->host_no);
++ rc = scsi_reset_provider(dev->scsi_dev, SCSI_TRY_RESET_DEVICE);
++#if 0
++ if (rc != SUCCESS && mcmd->status == SCST_MGMT_STATUS_SUCCESS)
++ mcmd->status = SCST_MGMT_STATUS_FAILED;
++#else
++ /*
++ * scsi_reset_provider() returns very weird status, so let's
++ * always succeed
++ */
++#endif
++ dev->scsi_dev->was_reset = 0;
++ }
++
++ scst_unblock_aborted_cmds(0);
++
++out_tm_dbg:
++ tm_dbg_task_mgmt(mcmd->mcmd_tgt_dev->dev, "LUN RESET", 0);
++
++ res = scst_set_mcmd_next_state(mcmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_mutex supposed to be held */
++static void scst_do_nexus_loss_sess(struct scst_mgmt_cmd *mcmd)
++{
++ int i;
++ struct scst_session *sess = mcmd->sess;
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ scst_nexus_loss(tgt_dev,
++ (mcmd->fn != SCST_UNREG_SESS_TM));
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Returns 0 if the command processing should be continued, <0 otherwise */
++static int scst_abort_all_nexus_loss_sess(struct scst_mgmt_cmd *mcmd,
++ int nexus_loss)
++{
++ int res;
++ int i;
++ struct scst_session *sess = mcmd->sess;
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ if (nexus_loss) {
++ TRACE_MGMT_DBG("Nexus loss for sess %p (mcmd %p)",
++ sess, mcmd);
++ } else {
++ TRACE_MGMT_DBG("Aborting all from sess %p (mcmd %p)",
++ sess, mcmd);
++ }
++
++ mutex_lock(&scst_mutex);
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ int rc;
++
++ __scst_abort_task_set(mcmd, tgt_dev);
++
++ rc = scst_call_dev_task_mgmt_fn(mcmd, tgt_dev, 0);
++ if (rc < 0 && mcmd->status == SCST_MGMT_STATUS_SUCCESS)
++ mcmd->status = rc;
++
++ tm_dbg_task_mgmt(tgt_dev->dev, "NEXUS LOSS SESS or "
++ "ABORT ALL SESS or UNREG SESS",
++ (mcmd->fn == SCST_UNREG_SESS_TM));
++ }
++ }
++
++ scst_unblock_aborted_cmds(1);
++
++ mutex_unlock(&scst_mutex);
++
++ res = scst_set_mcmd_next_state(mcmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_mutex supposed to be held */
++static void scst_do_nexus_loss_tgt(struct scst_mgmt_cmd *mcmd)
++{
++ int i;
++ struct scst_tgt *tgt = mcmd->sess->tgt;
++ struct scst_session *sess;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ scst_nexus_loss(tgt_dev, true);
++ }
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static int scst_abort_all_nexus_loss_tgt(struct scst_mgmt_cmd *mcmd,
++ int nexus_loss)
++{
++ int res;
++ int i;
++ struct scst_tgt *tgt = mcmd->sess->tgt;
++ struct scst_session *sess;
++
++ TRACE_ENTRY();
++
++ if (nexus_loss) {
++ TRACE_MGMT_DBG("I_T Nexus loss (tgt %p, mcmd %p)",
++ tgt, mcmd);
++ } else {
++ TRACE_MGMT_DBG("Aborting all from tgt %p (mcmd %p)",
++ tgt, mcmd);
++ }
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ int rc;
++
++ __scst_abort_task_set(mcmd, tgt_dev);
++
++ if (mcmd->sess == tgt_dev->sess) {
++ rc = scst_call_dev_task_mgmt_fn(
++ mcmd, tgt_dev, 0);
++ if ((rc < 0) &&
++ (mcmd->status == SCST_MGMT_STATUS_SUCCESS))
++ mcmd->status = rc;
++ }
++
++ tm_dbg_task_mgmt(tgt_dev->dev, "NEXUS LOSS or "
++ "ABORT ALL", 0);
++ }
++ }
++ }
++
++ scst_unblock_aborted_cmds(1);
++
++ mutex_unlock(&scst_mutex);
++
++ res = scst_set_mcmd_next_state(mcmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_abort_task(struct scst_mgmt_cmd *mcmd)
++{
++ int res;
++ struct scst_cmd *cmd = mcmd->cmd_to_abort;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Aborting task (cmd %p, sn %d, set %d, tag %llu, "
++ "queue_type %x)", cmd, cmd->sn, cmd->sn_set,
++ (long long unsigned int)mcmd->tag, cmd->queue_type);
++
++ if (mcmd->lun_set && (mcmd->lun != cmd->lun)) {
++ PRINT_ERROR("ABORT TASK: LUN mismatch: mcmd LUN %llx, "
++ "cmd LUN %llx, cmd tag %llu",
++ (long long unsigned int)mcmd->lun,
++ (long long unsigned int)cmd->lun,
++ (long long unsigned int)mcmd->tag);
++ mcmd->status = SCST_MGMT_STATUS_REJECTED;
++ } else if (mcmd->cmd_sn_set &&
++ (scst_sn_before(mcmd->cmd_sn, cmd->tgt_sn) ||
++ (mcmd->cmd_sn == cmd->tgt_sn))) {
++ PRINT_ERROR("ABORT TASK: SN mismatch: mcmd SN %x, "
++ "cmd SN %x, cmd tag %llu", mcmd->cmd_sn,
++ cmd->tgt_sn, (long long unsigned int)mcmd->tag);
++ mcmd->status = SCST_MGMT_STATUS_REJECTED;
++ } else {
++ spin_lock_irq(&cmd->sess->sess_list_lock);
++ scst_abort_cmd(cmd, mcmd, 0, 1);
++ spin_unlock_irq(&cmd->sess->sess_list_lock);
++
++ scst_unblock_aborted_cmds(0);
++ }
++
++ res = scst_set_mcmd_next_state(mcmd);
++
++ mcmd->cmd_to_abort = NULL; /* just in case */
++
++ __scst_cmd_put(cmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Returns 0 if the command processing should be continued, <0 otherwise */
++static int scst_mgmt_cmd_exec(struct scst_mgmt_cmd *mcmd)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ mcmd->status = SCST_MGMT_STATUS_SUCCESS;
++
++ switch (mcmd->fn) {
++ case SCST_ABORT_TASK:
++ res = scst_abort_task(mcmd);
++ break;
++
++ case SCST_ABORT_TASK_SET:
++ case SCST_PR_ABORT_ALL:
++ res = scst_abort_task_set(mcmd);
++ break;
++
++ case SCST_CLEAR_TASK_SET:
++ if (mcmd->mcmd_tgt_dev->dev->tst ==
++ SCST_CONTR_MODE_SEP_TASK_SETS)
++ res = scst_abort_task_set(mcmd);
++ else
++ res = scst_clear_task_set(mcmd);
++ break;
++
++ case SCST_LUN_RESET:
++ res = scst_lun_reset(mcmd);
++ break;
++
++ case SCST_TARGET_RESET:
++ res = scst_target_reset(mcmd);
++ break;
++
++ case SCST_ABORT_ALL_TASKS_SESS:
++ res = scst_abort_all_nexus_loss_sess(mcmd, 0);
++ break;
++
++ case SCST_NEXUS_LOSS_SESS:
++ case SCST_UNREG_SESS_TM:
++ res = scst_abort_all_nexus_loss_sess(mcmd, 1);
++ break;
++
++ case SCST_ABORT_ALL_TASKS:
++ res = scst_abort_all_nexus_loss_tgt(mcmd, 0);
++ break;
++
++ case SCST_NEXUS_LOSS:
++ res = scst_abort_all_nexus_loss_tgt(mcmd, 1);
++ break;
++
++ case SCST_CLEAR_ACA:
++ if (scst_call_dev_task_mgmt_fn(mcmd, mcmd->mcmd_tgt_dev, 1) ==
++ SCST_DEV_TM_NOT_COMPLETED) {
++ mcmd->status = SCST_MGMT_STATUS_FN_NOT_SUPPORTED;
++ /* Nothing to do (yet) */
++ }
++ goto out_done;
++
++ default:
++ PRINT_ERROR("Unknown task management function %d", mcmd->fn);
++ mcmd->status = SCST_MGMT_STATUS_REJECTED;
++ goto out_done;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_done:
++ res = scst_set_mcmd_next_state(mcmd);
++ goto out;
++}
++
++static void scst_call_task_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd)
++{
++ struct scst_session *sess = mcmd->sess;
++
++ if ((sess->tgt->tgtt->task_mgmt_affected_cmds_done != NULL) &&
++ (mcmd->fn != SCST_UNREG_SESS_TM) &&
++ (mcmd->fn != SCST_PR_ABORT_ALL)) {
++ TRACE_DBG("Calling target %s task_mgmt_affected_cmds_done(%p)",
++ sess->tgt->tgtt->name, sess);
++ sess->tgt->tgtt->task_mgmt_affected_cmds_done(mcmd);
++ TRACE_MGMT_DBG("Target's %s task_mgmt_affected_cmds_done() "
++ "returned", sess->tgt->tgtt->name);
++ }
++ return;
++}
++
++static int scst_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ switch (mcmd->fn) {
++ case SCST_NEXUS_LOSS_SESS:
++ case SCST_UNREG_SESS_TM:
++ scst_do_nexus_loss_sess(mcmd);
++ break;
++
++ case SCST_NEXUS_LOSS:
++ scst_do_nexus_loss_tgt(mcmd);
++ break;
++ }
++
++ mutex_unlock(&scst_mutex);
++
++ scst_call_task_mgmt_affected_cmds_done(mcmd);
++
++ res = scst_set_mcmd_next_state(mcmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void scst_mgmt_cmd_send_done(struct scst_mgmt_cmd *mcmd)
++{
++ struct scst_device *dev;
++ struct scst_session *sess = mcmd->sess;
++
++ TRACE_ENTRY();
++
++ mcmd->state = SCST_MCMD_STATE_FINISHED;
++ if (scst_is_strict_mgmt_fn(mcmd->fn) && (mcmd->completed_cmd_count > 0))
++ mcmd->status = SCST_MGMT_STATUS_TASK_NOT_EXIST;
++
++ if (mcmd->fn < SCST_UNREG_SESS_TM)
++ TRACE(TRACE_MGMT, "TM fn %d (%p) finished, "
++ "status %d", mcmd->fn, mcmd, mcmd->status);
++ else
++ TRACE_MGMT_DBG("TM fn %d (%p) finished, "
++ "status %d", mcmd->fn, mcmd, mcmd->status);
++
++ if (mcmd->fn == SCST_PR_ABORT_ALL) {
++ mcmd->origin_pr_cmd->scst_cmd_done(mcmd->origin_pr_cmd,
++ SCST_CMD_STATE_DEFAULT,
++ SCST_CONTEXT_THREAD);
++ } else if ((sess->tgt->tgtt->task_mgmt_fn_done != NULL) &&
++ (mcmd->fn != SCST_UNREG_SESS_TM)) {
++ TRACE_DBG("Calling target %s task_mgmt_fn_done(%p)",
++ sess->tgt->tgtt->name, sess);
++ sess->tgt->tgtt->task_mgmt_fn_done(mcmd);
++ TRACE_MGMT_DBG("Target's %s task_mgmt_fn_done() "
++ "returned", sess->tgt->tgtt->name);
++ }
++
++ if (mcmd->needs_unblocking) {
++ switch (mcmd->fn) {
++ case SCST_LUN_RESET:
++ case SCST_CLEAR_TASK_SET:
++ dev = mcmd->mcmd_tgt_dev->dev;
++ spin_lock_bh(&dev->dev_lock);
++ scst_unblock_dev(dev);
++ spin_unlock_bh(&dev->dev_lock);
++ break;
++
++ case SCST_TARGET_RESET:
++ {
++ struct scst_acg *acg = mcmd->sess->acg;
++ struct scst_acg_dev *acg_dev;
++
++ mutex_lock(&scst_mutex);
++ list_for_each_entry(acg_dev, &acg->acg_dev_list,
++ acg_dev_list_entry) {
++ dev = acg_dev->dev;
++ spin_lock_bh(&dev->dev_lock);
++ scst_unblock_dev(dev);
++ spin_unlock_bh(&dev->dev_lock);
++ }
++ mutex_unlock(&scst_mutex);
++ break;
++ }
++
++ default:
++ BUG();
++ break;
++ }
++ }
++
++ mcmd->tgt_priv = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Returns >0, if cmd should be requeued */
++static int scst_process_mgmt_cmd(struct scst_mgmt_cmd *mcmd)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /*
++ * We are in the TM thread and mcmd->state guaranteed to not be
++ * changed behind us.
++ */
++
++ TRACE_DBG("mcmd %p, state %d", mcmd, mcmd->state);
++
++ while (1) {
++ switch (mcmd->state) {
++ case SCST_MCMD_STATE_INIT:
++ res = scst_mgmt_cmd_init(mcmd);
++ if (res != 0)
++ goto out;
++ break;
++
++ case SCST_MCMD_STATE_EXEC:
++ if (scst_mgmt_cmd_exec(mcmd))
++ goto out;
++ break;
++
++ case SCST_MCMD_STATE_AFFECTED_CMDS_DONE:
++ if (scst_mgmt_affected_cmds_done(mcmd))
++ goto out;
++ break;
++
++ case SCST_MCMD_STATE_DONE:
++ scst_mgmt_cmd_send_done(mcmd);
++ break;
++
++ case SCST_MCMD_STATE_FINISHED:
++ scst_free_mgmt_cmd(mcmd);
++ /* mcmd is dead */
++ goto out;
++
++ default:
++ PRINT_CRIT_ERROR("Wrong mcmd %p state %d (fn %d, "
++ "cmd_finish_wait_count %d, cmd_done_wait_count "
++ "%d)", mcmd, mcmd->state, mcmd->fn,
++ mcmd->cmd_finish_wait_count,
++ mcmd->cmd_done_wait_count);
++ BUG();
++ res = -1;
++ goto out;
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static inline int test_mgmt_cmd_list(void)
++{
++ int res = !list_empty(&scst_active_mgmt_cmd_list) ||
++ unlikely(kthread_should_stop());
++ return res;
++}
++
++int scst_tm_thread(void *arg)
++{
++ TRACE_ENTRY();
++
++ PRINT_INFO("Task management thread started, PID %d", current->pid);
++
++ current->flags |= PF_NOFREEZE;
++
++ set_user_nice(current, -10);
++
++ spin_lock_irq(&scst_mcmd_lock);
++ while (!kthread_should_stop()) {
++ wait_queue_t wait;
++ init_waitqueue_entry(&wait, current);
++
++ if (!test_mgmt_cmd_list()) {
++ add_wait_queue_exclusive(&scst_mgmt_cmd_list_waitQ,
++ &wait);
++ for (;;) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (test_mgmt_cmd_list())
++ break;
++ spin_unlock_irq(&scst_mcmd_lock);
++ schedule();
++ spin_lock_irq(&scst_mcmd_lock);
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&scst_mgmt_cmd_list_waitQ, &wait);
++ }
++
++ while (!list_empty(&scst_active_mgmt_cmd_list)) {
++ int rc;
++ struct scst_mgmt_cmd *mcmd;
++ mcmd = list_entry(scst_active_mgmt_cmd_list.next,
++ typeof(*mcmd), mgmt_cmd_list_entry);
++ TRACE_MGMT_DBG("Deleting mgmt cmd %p from active cmd "
++ "list", mcmd);
++ list_del(&mcmd->mgmt_cmd_list_entry);
++ spin_unlock_irq(&scst_mcmd_lock);
++ rc = scst_process_mgmt_cmd(mcmd);
++ spin_lock_irq(&scst_mcmd_lock);
++ if (rc > 0) {
++ if (test_bit(SCST_FLAG_SUSPENDED, &scst_flags) &&
++ !test_bit(SCST_FLAG_SUSPENDING,
++ &scst_flags)) {
++ TRACE_MGMT_DBG("Adding mgmt cmd %p to "
++ "head of delayed mgmt cmd list",
++ mcmd);
++ list_add(&mcmd->mgmt_cmd_list_entry,
++ &scst_delayed_mgmt_cmd_list);
++ } else {
++ TRACE_MGMT_DBG("Adding mgmt cmd %p to "
++ "head of active mgmt cmd list",
++ mcmd);
++ list_add(&mcmd->mgmt_cmd_list_entry,
++ &scst_active_mgmt_cmd_list);
++ }
++ }
++ }
++ }
++ spin_unlock_irq(&scst_mcmd_lock);
++
++ /*
++ * If kthread_should_stop() is true, we are guaranteed to be
++ * on the module unload, so scst_active_mgmt_cmd_list must be empty.
++ */
++ BUG_ON(!list_empty(&scst_active_mgmt_cmd_list));
++
++ PRINT_INFO("Task management thread PID %d finished", current->pid);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static struct scst_mgmt_cmd *scst_pre_rx_mgmt_cmd(struct scst_session
++ *sess, int fn, int atomic, void *tgt_priv)
++{
++ struct scst_mgmt_cmd *mcmd = NULL;
++
++ TRACE_ENTRY();
++
++ if (unlikely(sess->tgt->tgtt->task_mgmt_fn_done == NULL)) {
++ PRINT_ERROR("New mgmt cmd, but task_mgmt_fn_done() is NULL "
++ "(target %s)", sess->tgt->tgtt->name);
++ goto out;
++ }
++
++ mcmd = scst_alloc_mgmt_cmd(atomic ? GFP_ATOMIC : GFP_KERNEL);
++ if (mcmd == NULL) {
++ PRINT_CRIT_ERROR("Lost TM fn %d, initiator %s", fn,
++ sess->initiator_name);
++ goto out;
++ }
++
++ mcmd->sess = sess;
++ mcmd->fn = fn;
++ mcmd->state = SCST_MCMD_STATE_INIT;
++ mcmd->tgt_priv = tgt_priv;
++
++ if (fn == SCST_PR_ABORT_ALL) {
++ atomic_inc(&mcmd->origin_pr_cmd->pr_abort_counter->pr_abort_pending_cnt);
++ atomic_inc(&mcmd->origin_pr_cmd->pr_abort_counter->pr_aborting_cnt);
++ }
++
++out:
++ TRACE_EXIT();
++ return mcmd;
++}
++
++static int scst_post_rx_mgmt_cmd(struct scst_session *sess,
++ struct scst_mgmt_cmd *mcmd)
++{
++ unsigned long flags;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ scst_sess_get(sess);
++
++ if (unlikely(sess->shut_phase != SCST_SESS_SPH_READY)) {
++ PRINT_CRIT_ERROR("New mgmt cmd while shutting down the "
++ "session %p shut_phase %ld", sess, sess->shut_phase);
++ BUG();
++ }
++
++ local_irq_save(flags);
++
++ spin_lock(&sess->sess_list_lock);
++ atomic_inc(&sess->sess_cmd_count);
++
++ if (unlikely(sess->init_phase != SCST_SESS_IPH_READY)) {
++ switch (sess->init_phase) {
++ case SCST_SESS_IPH_INITING:
++ TRACE_DBG("Adding mcmd %p to init deferred mcmd list",
++ mcmd);
++ list_add_tail(&mcmd->mgmt_cmd_list_entry,
++ &sess->init_deferred_mcmd_list);
++ goto out_unlock;
++ case SCST_SESS_IPH_SUCCESS:
++ break;
++ case SCST_SESS_IPH_FAILED:
++ res = -1;
++ goto out_unlock;
++ default:
++ BUG();
++ }
++ }
++
++ spin_unlock(&sess->sess_list_lock);
++
++ TRACE_MGMT_DBG("Adding mgmt cmd %p to active mgmt cmd list", mcmd);
++ spin_lock(&scst_mcmd_lock);
++ list_add_tail(&mcmd->mgmt_cmd_list_entry, &scst_active_mgmt_cmd_list);
++ spin_unlock(&scst_mcmd_lock);
++
++ local_irq_restore(flags);
++
++ wake_up(&scst_mgmt_cmd_list_waitQ);
++
++out:
++ TRACE_EXIT();
++ return res;
++
++out_unlock:
++ spin_unlock(&sess->sess_list_lock);
++ local_irq_restore(flags);
++ goto out;
++}
++
++/**
++ * scst_rx_mgmt_fn() - create new management command and send it for execution
++ *
++ * Description:
++ * Creates new management command and sends it for execution.
++ *
++ * Returns 0 for success, error code otherwise.
++ *
++ * Must not be called in parallel with scst_unregister_session() for the
++ * same sess.
++ */
++int scst_rx_mgmt_fn(struct scst_session *sess,
++ const struct scst_rx_mgmt_params *params)
++{
++ int res = -EFAULT;
++ struct scst_mgmt_cmd *mcmd = NULL;
++
++ TRACE_ENTRY();
++
++ switch (params->fn) {
++ case SCST_ABORT_TASK:
++ BUG_ON(!params->tag_set);
++ break;
++ case SCST_TARGET_RESET:
++ case SCST_ABORT_ALL_TASKS:
++ case SCST_NEXUS_LOSS:
++ break;
++ default:
++ BUG_ON(!params->lun_set);
++ }
++
++ mcmd = scst_pre_rx_mgmt_cmd(sess, params->fn, params->atomic,
++ params->tgt_priv);
++ if (mcmd == NULL)
++ goto out;
++
++ if (params->lun_set) {
++ mcmd->lun = scst_unpack_lun(params->lun, params->lun_len);
++ if (mcmd->lun == NO_SUCH_LUN)
++ goto out_free;
++ mcmd->lun_set = 1;
++ }
++
++ if (params->tag_set)
++ mcmd->tag = params->tag;
++
++ mcmd->cmd_sn_set = params->cmd_sn_set;
++ mcmd->cmd_sn = params->cmd_sn;
++
++ if (params->fn < SCST_UNREG_SESS_TM)
++ TRACE(TRACE_MGMT, "TM fn %d (%p)", params->fn, mcmd);
++ else
++ TRACE_MGMT_DBG("TM fn %d (%p)", params->fn, mcmd);
++
++ TRACE_MGMT_DBG("sess=%p, tag_set %d, tag %lld, lun_set %d, "
++ "lun=%lld, cmd_sn_set %d, cmd_sn %d, priv %p", sess,
++ params->tag_set,
++ (long long unsigned int)params->tag,
++ params->lun_set,
++ (long long unsigned int)mcmd->lun,
++ params->cmd_sn_set,
++ params->cmd_sn,
++ params->tgt_priv);
++
++ if (scst_post_rx_mgmt_cmd(sess, mcmd) != 0)
++ goto out_free;
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ scst_free_mgmt_cmd(mcmd);
++ mcmd = NULL;
++ goto out;
++}
++EXPORT_SYMBOL(scst_rx_mgmt_fn);
++
++/*
++ * Written by Jack Handy - jakkhandy@hotmail.com
++ * Taken by Gennadiy Nerubayev <parakie@gmail.com> from
++ * http://www.codeproject.com/KB/string/wildcmp.aspx. No license attached
++ * to it, and it's posted on a free site; assumed to be free for use.
++ *
++ * Added the negative sign support - VLNB
++ *
++ * Also see comment for wildcmp().
++ *
++ * User space part of iSCSI-SCST also has a copy of this code, so fixing a bug
++ * here, don't forget to fix the copy too!
++ */
++static bool __wildcmp(const char *wild, const char *string, int recursion_level)
++{
++ const char *cp = NULL, *mp = NULL;
++
++ while ((*string) && (*wild != '*')) {
++ if ((*wild == '!') && (recursion_level == 0))
++ return !__wildcmp(++wild, string, ++recursion_level);
++
++ if ((*wild != *string) && (*wild != '?'))
++ return false;
++
++ wild++;
++ string++;
++ }
++
++ while (*string) {
++ if ((*wild == '!') && (recursion_level == 0))
++ return !__wildcmp(++wild, string, ++recursion_level);
++
++ if (*wild == '*') {
++ if (!*++wild)
++ return true;
++
++ mp = wild;
++ cp = string+1;
++ } else if ((*wild == *string) || (*wild == '?')) {
++ wild++;
++ string++;
++ } else {
++ wild = mp;
++ string = cp++;
++ }
++ }
++
++ while (*wild == '*')
++ wild++;
++
++ return !*wild;
++}
++
++/*
++ * Returns true if string "string" matches pattern "wild", false otherwise.
++ * Pattern is a regular DOS-type pattern, containing '*' and '?' symbols.
++ * '*' means match all any symbols, '?' means match only any single symbol.
++ *
++ * For instance:
++ * if (wildcmp("bl?h.*", "blah.jpg")) {
++ * // match
++ * } else {
++ * // no match
++ * }
++ *
++ * Also it supports boolean inversion sign '!', which does boolean inversion of
++ * the value of the rest of the string. Only one '!' allowed in the pattern,
++ * other '!' are treated as regular symbols. For instance:
++ * if (wildcmp("bl!?h.*", "blah.jpg")) {
++ * // no match
++ * } else {
++ * // match
++ * }
++ *
++ * Also see comment for __wildcmp().
++ */
++static bool wildcmp(const char *wild, const char *string)
++{
++ return __wildcmp(wild, string, 0);
++}
++
++/* scst_mutex supposed to be held */
++static struct scst_acg *scst_find_tgt_acg_by_name_wild(struct scst_tgt *tgt,
++ const char *initiator_name)
++{
++ struct scst_acg *acg, *res = NULL;
++ struct scst_acn *n;
++
++ TRACE_ENTRY();
++
++ if (initiator_name == NULL)
++ goto out;
++
++ list_for_each_entry(acg, &tgt->tgt_acg_list, acg_list_entry) {
++ list_for_each_entry(n, &acg->acn_list, acn_list_entry) {
++ if (wildcmp(n->name, initiator_name)) {
++ TRACE_DBG("Access control group %s found",
++ acg->acg_name);
++ res = acg;
++ goto out;
++ }
++ }
++ }
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/* Must be called under scst_mutex */
++static struct scst_acg *__scst_find_acg(struct scst_tgt *tgt,
++ const char *initiator_name)
++{
++ struct scst_acg *acg = NULL;
++
++ TRACE_ENTRY();
++
++ acg = scst_find_tgt_acg_by_name_wild(tgt, initiator_name);
++ if (acg == NULL)
++ acg = tgt->default_acg;
++
++ TRACE_EXIT_HRES((unsigned long)acg);
++ return acg;
++}
++
++/* Must be called under scst_mutex */
++struct scst_acg *scst_find_acg(const struct scst_session *sess)
++{
++ return __scst_find_acg(sess->tgt, sess->initiator_name);
++}
++
++/**
++ * scst_initiator_has_luns() - check if this initiator will see any LUNs
++ *
++ * Checks if this initiator will see any LUNs upon connect to this target.
++ * Returns true if yes and false otherwise.
++ */
++bool scst_initiator_has_luns(struct scst_tgt *tgt, const char *initiator_name)
++{
++ bool res;
++ struct scst_acg *acg;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ acg = __scst_find_acg(tgt, initiator_name);
++
++ res = !list_empty(&acg->acg_dev_list);
++
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_initiator_has_luns);
++
++static int scst_init_session(struct scst_session *sess)
++{
++ int res = 0;
++ struct scst_cmd *cmd;
++ struct scst_mgmt_cmd *mcmd, *tm;
++ int mwake = 0;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ sess->acg = scst_find_acg(sess);
++
++ PRINT_INFO("Using security group \"%s\" for initiator \"%s\" "
++ "(target %s)", sess->acg->acg_name, sess->initiator_name,
++ sess->tgt->tgt_name);
++
++ list_add_tail(&sess->acg_sess_list_entry, &sess->acg->acg_sess_list);
++
++ TRACE_DBG("Adding sess %p to tgt->sess_list", sess);
++ list_add_tail(&sess->sess_list_entry, &sess->tgt->sess_list);
++
++ if (sess->tgt->tgtt->get_initiator_port_transport_id != NULL) {
++ res = sess->tgt->tgtt->get_initiator_port_transport_id(
++ sess->tgt, sess, &sess->transport_id);
++ if (res != 0) {
++ PRINT_ERROR("Unable to make initiator %s port "
++ "transport id", sess->initiator_name);
++ goto failed;
++ }
++ TRACE_PR("sess %p (ini %s), transport id %s/%d", sess,
++ sess->initiator_name,
++ debug_transport_id_to_initiator_name(
++ sess->transport_id), sess->tgt->rel_tgt_id);
++ }
++
++ res = scst_sess_sysfs_create(sess);
++ if (res != 0)
++ goto failed;
++
++ /*
++ * scst_sess_alloc_tgt_devs() must be called after session added in the
++ * sess_list to not race with scst_check_reassign_sess()!
++ */
++ res = scst_sess_alloc_tgt_devs(sess);
++
++failed:
++ mutex_unlock(&scst_mutex);
++
++ if (sess->init_result_fn) {
++ TRACE_DBG("Calling init_result_fn(%p)", sess);
++ sess->init_result_fn(sess, sess->reg_sess_data, res);
++ TRACE_DBG("%s", "init_result_fn() returned");
++ }
++
++ spin_lock_irq(&sess->sess_list_lock);
++
++ if (res == 0)
++ sess->init_phase = SCST_SESS_IPH_SUCCESS;
++ else
++ sess->init_phase = SCST_SESS_IPH_FAILED;
++
++restart:
++ list_for_each_entry(cmd, &sess->init_deferred_cmd_list,
++ cmd_list_entry) {
++ TRACE_DBG("Deleting cmd %p from init deferred cmd list", cmd);
++ list_del(&cmd->cmd_list_entry);
++ atomic_dec(&sess->sess_cmd_count);
++ spin_unlock_irq(&sess->sess_list_lock);
++ scst_cmd_init_done(cmd, SCST_CONTEXT_THREAD);
++ spin_lock_irq(&sess->sess_list_lock);
++ goto restart;
++ }
++
++ spin_lock(&scst_mcmd_lock);
++ list_for_each_entry_safe(mcmd, tm, &sess->init_deferred_mcmd_list,
++ mgmt_cmd_list_entry) {
++ TRACE_DBG("Moving mgmt command %p from init deferred mcmd list",
++ mcmd);
++ list_move_tail(&mcmd->mgmt_cmd_list_entry,
++ &scst_active_mgmt_cmd_list);
++ mwake = 1;
++ }
++
++ spin_unlock(&scst_mcmd_lock);
++ /*
++ * In case of an error at this point the caller target driver supposed
++ * to already call this sess's unregistration.
++ */
++ sess->init_phase = SCST_SESS_IPH_READY;
++ spin_unlock_irq(&sess->sess_list_lock);
++
++ if (mwake)
++ wake_up(&scst_mgmt_cmd_list_waitQ);
++
++ scst_sess_put(sess);
++
++ TRACE_EXIT();
++ return res;
++}
++
++/**
++ * scst_register_session() - register session
++ * @tgt: target
++ * @atomic: true, if the function called in the atomic context. If false,
++ * this function will block until the session registration is
++ * completed.
++ * @initiator_name: remote initiator's name, any NULL-terminated string,
++ * e.g. iSCSI name, which used as the key to found appropriate
++ * access control group. Could be NULL, then the default
++ * target's LUNs are used.
++ * @tgt_priv: pointer to target driver's private data
++ * @result_fn_data: any target driver supplied data
++ * @result_fn: pointer to the function that will be asynchronously called
++ * when session initialization finishes.
++ * Can be NULL. Parameters:
++ * - sess - session
++ * - data - target driver supplied to scst_register_session()
++ * data
++ * - result - session initialization result, 0 on success or
++ * appropriate error code otherwise
++ *
++ * Description:
++ * Registers new session. Returns new session on success or NULL otherwise.
++ *
++ * Note: A session creation and initialization is a complex task,
++ * which requires sleeping state, so it can't be fully done
++ * in interrupt context. Therefore the "bottom half" of it, if
++ * scst_register_session() is called from atomic context, will be
++ * done in SCST thread context. In this case scst_register_session()
++ * will return not completely initialized session, but the target
++ * driver can supply commands to this session via scst_rx_cmd().
++ * Those commands processing will be delayed inside SCST until
++ * the session initialization is finished, then their processing
++ * will be restarted. The target driver will be notified about
++ * finish of the session initialization by function result_fn().
++ * On success the target driver could do nothing, but if the
++ * initialization fails, the target driver must ensure that
++ * no more new commands being sent or will be sent to SCST after
++ * result_fn() returns. All already sent to SCST commands for
++ * failed session will be returned in xmit_response() with BUSY status.
++ * In case of failure the driver shall call scst_unregister_session()
++ * inside result_fn(), it will NOT be called automatically.
++ */
++struct scst_session *scst_register_session(struct scst_tgt *tgt, int atomic,
++ const char *initiator_name, void *tgt_priv, void *result_fn_data,
++ void (*result_fn) (struct scst_session *sess, void *data, int result))
++{
++ struct scst_session *sess;
++ int res;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ sess = scst_alloc_session(tgt, atomic ? GFP_ATOMIC : GFP_KERNEL,
++ initiator_name);
++ if (sess == NULL)
++ goto out;
++
++ scst_sess_set_tgt_priv(sess, tgt_priv);
++
++ scst_sess_get(sess); /* one for registered session */
++ scst_sess_get(sess); /* one held until sess is inited */
++
++ if (atomic) {
++ sess->reg_sess_data = result_fn_data;
++ sess->init_result_fn = result_fn;
++ spin_lock_irqsave(&scst_mgmt_lock, flags);
++ TRACE_DBG("Adding sess %p to scst_sess_init_list", sess);
++ list_add_tail(&sess->sess_init_list_entry,
++ &scst_sess_init_list);
++ spin_unlock_irqrestore(&scst_mgmt_lock, flags);
++ wake_up(&scst_mgmt_waitQ);
++ } else {
++ res = scst_init_session(sess);
++ if (res != 0)
++ goto out_free;
++ }
++
++out:
++ TRACE_EXIT();
++ return sess;
++
++out_free:
++ scst_free_session(sess);
++ sess = NULL;
++ goto out;
++}
++EXPORT_SYMBOL_GPL(scst_register_session);
++
++/**
++ * scst_register_session_non_gpl() - register session (non-GPL version)
++ * @tgt: target
++ * @initiator_name: remote initiator's name, any NULL-terminated string,
++ * e.g. iSCSI name, which used as the key to found appropriate
++ * access control group. Could be NULL, then the default
++ * target's LUNs are used.
++ * @tgt_priv: pointer to target driver's private data
++ *
++ * Description:
++ * Registers new session. Returns new session on success or NULL otherwise.
++ */
++struct scst_session *scst_register_session_non_gpl(struct scst_tgt *tgt,
++ const char *initiator_name, void *tgt_priv)
++{
++ return scst_register_session(tgt, 0, initiator_name, tgt_priv,
++ NULL, NULL);
++}
++EXPORT_SYMBOL(scst_register_session_non_gpl);
++
++/**
++ * scst_unregister_session() - unregister session
++ * @sess: session to be unregistered
++ * @wait: if true, instructs to wait until all commands, which
++ * currently is being executed and belonged to the session,
++ * finished. Otherwise, target driver should be prepared to
++ * receive xmit_response() for the session's command after
++ * scst_unregister_session() returns.
++ * @unreg_done_fn: pointer to the function that will be asynchronously called
++ * when the last session's command finishes and
++ * the session is about to be completely freed. Can be NULL.
++ * Parameter:
++ * - sess - session
++ *
++ * Unregisters session.
++ *
++ * Notes:
++ * - All outstanding commands will be finished regularly. After
++ * scst_unregister_session() returned, no new commands must be sent to
++ * SCST via scst_rx_cmd().
++ *
++ * - The caller must ensure that no scst_rx_cmd() or scst_rx_mgmt_fn_*() is
++ * called in parallel with scst_unregister_session().
++ *
++ * - Can be called before result_fn() of scst_register_session() called,
++ * i.e. during the session registration/initialization.
++ *
++ * - It is highly recommended to call scst_unregister_session() as soon as it
++ * gets clear that session will be unregistered and not to wait until all
++ * related commands finished. This function provides the wait functionality,
++ * but it also starts recovering stuck commands, if there are any.
++ * Otherwise, your target driver could wait for those commands forever.
++ */
++void scst_unregister_session(struct scst_session *sess, int wait,
++ void (*unreg_done_fn) (struct scst_session *sess))
++{
++ unsigned long flags;
++ DECLARE_COMPLETION_ONSTACK(c);
++ int rc, lun;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Unregistering session %p (wait %d)", sess, wait);
++
++ sess->unreg_done_fn = unreg_done_fn;
++
++ /* Abort all outstanding commands and clear reservation, if necessary */
++ lun = 0;
++ rc = scst_rx_mgmt_fn_lun(sess, SCST_UNREG_SESS_TM,
++ (uint8_t *)&lun, sizeof(lun), SCST_ATOMIC, NULL);
++ if (rc != 0) {
++ PRINT_ERROR("SCST_UNREG_SESS_TM failed %d (sess %p)",
++ rc, sess);
++ }
++
++ sess->shut_phase = SCST_SESS_SPH_SHUTDOWN;
++
++ spin_lock_irqsave(&scst_mgmt_lock, flags);
++
++ if (wait)
++ sess->shutdown_compl = &c;
++
++ spin_unlock_irqrestore(&scst_mgmt_lock, flags);
++
++ scst_sess_put(sess);
++
++ if (wait) {
++ TRACE_DBG("Waiting for session %p to complete", sess);
++ wait_for_completion(&c);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_unregister_session);
++
++/**
++ * scst_unregister_session_non_gpl() - unregister session, non-GPL version
++ * @sess: session to be unregistered
++ *
++ * Unregisters session.
++ *
++ * See notes for scst_unregister_session() above.
++ */
++void scst_unregister_session_non_gpl(struct scst_session *sess)
++{
++ TRACE_ENTRY();
++
++ scst_unregister_session(sess, 1, NULL);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_unregister_session_non_gpl);
++
++static inline int test_mgmt_list(void)
++{
++ int res = !list_empty(&scst_sess_init_list) ||
++ !list_empty(&scst_sess_shut_list) ||
++ unlikely(kthread_should_stop());
++ return res;
++}
++
++int scst_global_mgmt_thread(void *arg)
++{
++ struct scst_session *sess;
++
++ TRACE_ENTRY();
++
++ PRINT_INFO("Management thread started, PID %d", current->pid);
++
++ current->flags |= PF_NOFREEZE;
++
++ set_user_nice(current, -10);
++
++ spin_lock_irq(&scst_mgmt_lock);
++ while (!kthread_should_stop()) {
++ wait_queue_t wait;
++ init_waitqueue_entry(&wait, current);
++
++ if (!test_mgmt_list()) {
++ add_wait_queue_exclusive(&scst_mgmt_waitQ, &wait);
++ for (;;) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (test_mgmt_list())
++ break;
++ spin_unlock_irq(&scst_mgmt_lock);
++ schedule();
++ spin_lock_irq(&scst_mgmt_lock);
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&scst_mgmt_waitQ, &wait);
++ }
++
++ while (!list_empty(&scst_sess_init_list)) {
++ sess = list_entry(scst_sess_init_list.next,
++ typeof(*sess), sess_init_list_entry);
++ TRACE_DBG("Removing sess %p from scst_sess_init_list",
++ sess);
++ list_del(&sess->sess_init_list_entry);
++ spin_unlock_irq(&scst_mgmt_lock);
++
++ if (sess->init_phase == SCST_SESS_IPH_INITING)
++ scst_init_session(sess);
++ else {
++ PRINT_CRIT_ERROR("session %p is in "
++ "scst_sess_init_list, but in unknown "
++ "init phase %x", sess,
++ sess->init_phase);
++ BUG();
++ }
++
++ spin_lock_irq(&scst_mgmt_lock);
++ }
++
++ while (!list_empty(&scst_sess_shut_list)) {
++ sess = list_entry(scst_sess_shut_list.next,
++ typeof(*sess), sess_shut_list_entry);
++ TRACE_DBG("Removing sess %p from scst_sess_shut_list",
++ sess);
++ list_del(&sess->sess_shut_list_entry);
++ spin_unlock_irq(&scst_mgmt_lock);
++
++ switch (sess->shut_phase) {
++ case SCST_SESS_SPH_SHUTDOWN:
++ BUG_ON(atomic_read(&sess->refcnt) != 0);
++ scst_free_session_callback(sess);
++ break;
++ default:
++ PRINT_CRIT_ERROR("session %p is in "
++ "scst_sess_shut_list, but in unknown "
++ "shut phase %lx", sess,
++ sess->shut_phase);
++ BUG();
++ break;
++ }
++
++ spin_lock_irq(&scst_mgmt_lock);
++ }
++ }
++ spin_unlock_irq(&scst_mgmt_lock);
++
++ /*
++ * If kthread_should_stop() is true, we are guaranteed to be
++ * on the module unload, so both lists must be empty.
++ */
++ BUG_ON(!list_empty(&scst_sess_init_list));
++ BUG_ON(!list_empty(&scst_sess_shut_list));
++
++ PRINT_INFO("Management thread PID %d finished", current->pid);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++/* Called under sess->sess_list_lock */
++static struct scst_cmd *__scst_find_cmd_by_tag(struct scst_session *sess,
++ uint64_t tag, bool to_abort)
++{
++ struct scst_cmd *cmd, *res = NULL;
++
++ TRACE_ENTRY();
++
++ /* ToDo: hash list */
++
++ TRACE_DBG("%s (sess=%p, tag=%llu)", "Searching in sess cmd list",
++ sess, (long long unsigned int)tag);
++
++ list_for_each_entry(cmd, &sess->sess_cmd_list,
++ sess_cmd_list_entry) {
++ if (cmd->tag == tag) {
++ /*
++ * We must not count done commands, because
++ * they were submitted for transmission.
++ * Otherwise we can have a race, when for
++ * some reason cmd's release delayed
++ * after transmission and initiator sends
++ * cmd with the same tag => it can be possible
++ * that a wrong cmd will be returned.
++ */
++ if (cmd->done) {
++ if (to_abort) {
++ /*
++ * We should return the latest not
++ * aborted cmd with this tag.
++ */
++ if (res == NULL)
++ res = cmd;
++ else {
++ if (test_bit(SCST_CMD_ABORTED,
++ &res->cmd_flags)) {
++ res = cmd;
++ } else if (!test_bit(SCST_CMD_ABORTED,
++ &cmd->cmd_flags))
++ res = cmd;
++ }
++ }
++ continue;
++ } else {
++ res = cmd;
++ break;
++ }
++ }
++ }
++
++ TRACE_EXIT();
++ return res;
++}
++
++/**
++ * scst_find_cmd() - find command by custom comparison function
++ *
++ * Finds a command based on user supplied data and comparison
++ * callback function, that should return true, if the command is found.
++ * Returns the command on success or NULL otherwise.
++ */
++struct scst_cmd *scst_find_cmd(struct scst_session *sess, void *data,
++ int (*cmp_fn) (struct scst_cmd *cmd,
++ void *data))
++{
++ struct scst_cmd *cmd = NULL;
++ unsigned long flags = 0;
++
++ TRACE_ENTRY();
++
++ if (cmp_fn == NULL)
++ goto out;
++
++ spin_lock_irqsave(&sess->sess_list_lock, flags);
++
++ TRACE_DBG("Searching in sess cmd list (sess=%p)", sess);
++ list_for_each_entry(cmd, &sess->sess_cmd_list, sess_cmd_list_entry) {
++ /*
++ * We must not count done commands, because they were
++ * submitted for transmission. Otherwise we can have a race,
++ * when for some reason cmd's release delayed after
++ * transmission and initiator sends cmd with the same tag =>
++ * it can be possible that a wrong cmd will be returned.
++ */
++ if (cmd->done)
++ continue;
++ if (cmp_fn(cmd, data))
++ goto out_unlock;
++ }
++
++ cmd = NULL;
++
++out_unlock:
++ spin_unlock_irqrestore(&sess->sess_list_lock, flags);
++
++out:
++ TRACE_EXIT();
++ return cmd;
++}
++EXPORT_SYMBOL(scst_find_cmd);
++
++/**
++ * scst_find_cmd_by_tag() - find command by tag
++ *
++ * Finds a command based on the supplied tag comparing it with one
++ * that previously set by scst_cmd_set_tag(). Returns the found command on
++ * success or NULL otherwise.
++ */
++struct scst_cmd *scst_find_cmd_by_tag(struct scst_session *sess,
++ uint64_t tag)
++{
++ unsigned long flags;
++ struct scst_cmd *cmd;
++ spin_lock_irqsave(&sess->sess_list_lock, flags);
++ cmd = __scst_find_cmd_by_tag(sess, tag, false);
++ spin_unlock_irqrestore(&sess->sess_list_lock, flags);
++ return cmd;
++}
++EXPORT_SYMBOL(scst_find_cmd_by_tag);
+diff -uprN orig/linux-3.2/drivers/scst/scst_lib.c linux-3.2/drivers/scst/scst_lib.c
+--- orig/linux-3.2/drivers/scst/scst_lib.c
++++ linux-3.2/drivers/scst/scst_lib.c
+@@ -0,0 +1,7480 @@
++/*
++ * scst_lib.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/kthread.h>
++#include <linux/cdrom.h>
++#include <linux/unistd.h>
++#include <linux/string.h>
++#include <linux/ctype.h>
++#include <linux/delay.h>
++#include <linux/vmalloc.h>
++#include <asm/kmap_types.h>
++#include <asm/unaligned.h>
++
++#include <scst/scst.h>
++#include "scst_priv.h"
++#include "scst_mem.h"
++#include "scst_pres.h"
++
++struct scsi_io_context {
++ void *data;
++ void (*done)(void *data, char *sense, int result, int resid);
++ char sense[SCST_SENSE_BUFFERSIZE];
++};
++static struct kmem_cache *scsi_io_context_cache;
++
++/* get_trans_len_x extract x bytes from cdb as length starting from off */
++static int get_trans_len_1(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_1_256(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_2(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_3(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_4(struct scst_cmd *cmd, uint8_t off);
++
++static int get_bidi_trans_len_2(struct scst_cmd *cmd, uint8_t off);
++
++/* for special commands */
++static int get_trans_len_block_limit(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_read_capacity(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_serv_act_in(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_single(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_none(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_read_pos(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_cdb_len_10(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_prevent_allow_medium_removal(struct scst_cmd *cmd,
++ uint8_t off);
++static int get_trans_len_3_read_elem_stat(struct scst_cmd *cmd, uint8_t off);
++static int get_trans_len_start_stop(struct scst_cmd *cmd, uint8_t off);
++
++/*
+++=====================================-============-======-
++| Command name | Operation | Type |
++| | code | |
++|-------------------------------------+------------+------+
++
+++=========================================================+
++|Key: M = command implementation is mandatory. |
++| O = command implementation is optional. |
++| V = Vendor-specific |
++| R = Reserved |
++| ' '= DON'T use for this device |
+++=========================================================+
++*/
++
++#define SCST_CDB_MANDATORY 'M' /* mandatory */
++#define SCST_CDB_OPTIONAL 'O' /* optional */
++#define SCST_CDB_VENDOR 'V' /* vendor */
++#define SCST_CDB_RESERVED 'R' /* reserved */
++#define SCST_CDB_NOTSUPP ' ' /* don't use */
++
++struct scst_sdbops {
++ uint8_t ops; /* SCSI-2 op codes */
++ uint8_t devkey[16]; /* Key for every device type M,O,V,R
++ * type_disk devkey[0]
++ * type_tape devkey[1]
++ * type_printer devkey[2]
++ * type_processor devkey[3]
++ * type_worm devkey[4]
++ * type_cdrom devkey[5]
++ * type_scanner devkey[6]
++ * type_mod devkey[7]
++ * type_changer devkey[8]
++ * type_commdev devkey[9]
++ * type_reserv devkey[A]
++ * type_reserv devkey[B]
++ * type_raid devkey[C]
++ * type_enclosure devkey[D]
++ * type_reserv devkey[E]
++ * type_reserv devkey[F]
++ */
++ const char *op_name; /* SCSI-2 op codes full name */
++ uint8_t direction; /* init --> target: SCST_DATA_WRITE
++ * target --> init: SCST_DATA_READ
++ */
++ uint32_t flags; /* opcode -- various flags */
++ uint8_t off; /* length offset in cdb */
++ int (*get_trans_len)(struct scst_cmd *cmd, uint8_t off);
++};
++
++static int scst_scsi_op_list[256];
++
++#define FLAG_NONE 0
++
++static const struct scst_sdbops scst_scsi_op_table[] = {
++ /*
++ * +-------------------> TYPE_IS_DISK (0)
++ * |
++ * |+------------------> TYPE_IS_TAPE (1)
++ * ||
++ * || +----------------> TYPE_IS_PROCESSOR (3)
++ * || |
++ * || | +--------------> TYPE_IS_CDROM (5)
++ * || | |
++ * || | | +------------> TYPE_IS_MOD (7)
++ * || | | |
++ * || | | |+-----------> TYPE_IS_CHANGER (8)
++ * || | | ||
++ * || | | || +-------> TYPE_IS_RAID (C)
++ * || | | || |
++ * || | | || |
++ * 0123456789ABCDEF ---> TYPE_IS_???? */
++
++ /* 6-bytes length CDB */
++ {0x00, "MMMMMMMMMMMMMMMM", "TEST UNIT READY",
++ /* let's be HQ to don't look dead under high load */
++ SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_none},
++ {0x01, " M ", "REWIND",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x01, "O V OO OO ", "REZERO UNIT",
++ SCST_DATA_NONE, SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x02, "VVVVVV V ", "REQUEST BLOCK ADDR",
++ SCST_DATA_NONE, SCST_SMALL_TIMEOUT, 0, get_trans_len_none},
++ {0x03, "MMMMMMMMMMMMMMMM", "REQUEST SENSE",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_SKIP_UA|SCST_LOCAL_CMD|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 4, get_trans_len_1},
++ {0x04, "M O O ", "FORMAT UNIT",
++ SCST_DATA_WRITE, SCST_LONG_TIMEOUT|SCST_UNKNOWN_LENGTH|SCST_WRITE_MEDIUM,
++ 0, get_trans_len_none},
++ {0x04, " O ", "FORMAT",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0x05, "VMVVVV V ", "READ BLOCK LIMITS",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_block_limit},
++ {0x07, " O ", "INITIALIZE ELEMENT STATUS",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0x07, "OVV O OV ", "REASSIGN BLOCKS",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0x08, "O ", "READ(6)",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_WRITE_EXCL_ALLOWED,
++ 4, get_trans_len_1_256},
++ {0x08, " MV OO OV ", "READ(6)",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
++ SCST_WRITE_EXCL_ALLOWED,
++ 2, get_trans_len_3},
++ {0x08, " M ", "GET MESSAGE(6)",
++ SCST_DATA_READ, FLAG_NONE, 2, get_trans_len_3},
++ {0x08, " O ", "RECEIVE",
++ SCST_DATA_READ, FLAG_NONE, 2, get_trans_len_3},
++ {0x0A, "O ", "WRITE(6)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_WRITE_MEDIUM,
++ 4, get_trans_len_1_256},
++ {0x0A, " M O OV ", "WRITE(6)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
++ 2, get_trans_len_3},
++ {0x0A, " M ", "PRINT",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x0A, " M ", "SEND MESSAGE(6)",
++ SCST_DATA_WRITE, FLAG_NONE, 2, get_trans_len_3},
++ {0x0A, " M ", "SEND(6)",
++ SCST_DATA_WRITE, FLAG_NONE, 2, get_trans_len_3},
++ {0x0B, "O OO OV ", "SEEK(6)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x0B, " ", "TRACK SELECT",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x0B, " O ", "SLEW AND PRINT",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x0C, "VVVVVV V ", "SEEK BLOCK",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0x0D, "VVVVVV V ", "PARTITION",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM,
++ 0, get_trans_len_none},
++ {0x0F, "VOVVVV V ", "READ REVERSE",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
++ SCST_WRITE_EXCL_ALLOWED,
++ 2, get_trans_len_3},
++ {0x10, "VM V V ", "WRITE FILEMARKS",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0x10, " O O ", "SYNCHRONIZE BUFFER",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x11, "VMVVVV ", "SPACE",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|
++ SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x12, "MMMMMMMMMMMMMMMM", "INQUIRY",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ|SCST_SKIP_UA|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
++ 3, get_trans_len_2},
++ {0x13, "VOVVVV ", "VERIFY(6)",
++ SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED|
++ SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED,
++ 2, get_trans_len_3},
++ {0x14, "VOOVVV ", "RECOVER BUFFERED DATA",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
++ SCST_WRITE_EXCL_ALLOWED,
++ 2, get_trans_len_3},
++ {0x15, "OMOOOOOOOOOOOOOO", "MODE SELECT(6)",
++ SCST_DATA_WRITE, SCST_STRICTLY_SERIALIZED, 4, get_trans_len_1},
++ {0x16, "MMMMMMMMMMMMMMMM", "RESERVE",
++ SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_SERIALIZED|
++ SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_none},
++ {0x17, "MMMMMMMMMMMMMMMM", "RELEASE",
++ SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_SERIALIZED|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_none},
++ {0x18, "OOOOOOOO ", "COPY",
++ SCST_DATA_WRITE, SCST_LONG_TIMEOUT, 2, get_trans_len_3},
++ {0x19, "VMVVVV ", "ERASE",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM,
++ 0, get_trans_len_none},
++ {0x1A, "OMOOOOOOOOOOOOOO", "MODE SENSE(6)",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT, 4, get_trans_len_1},
++ {0x1B, " O ", "SCAN",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x1B, " O ", "LOAD UNLOAD",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0x1B, " O ", "STOP PRINT",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x1B, "O OO O O ", "START STOP UNIT",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_start_stop},
++ {0x1C, "OOOOOOOOOOOOOOOO", "RECEIVE DIAGNOSTIC RESULTS",
++ SCST_DATA_READ, FLAG_NONE, 3, get_trans_len_2},
++ {0x1D, "MMMMMMMMMMMMMMMM", "SEND DIAGNOSTIC",
++ SCST_DATA_WRITE, FLAG_NONE, 4, get_trans_len_1},
++ {0x1E, "OOOOOOOOOOOOOOOO", "PREVENT ALLOW MEDIUM REMOVAL",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0,
++ get_trans_len_prevent_allow_medium_removal},
++ {0x1F, " O ", "PORT STATUS",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++
++ /* 10-bytes length CDB */
++ {0x23, "V VV V ", "READ FORMAT CAPACITY",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x24, "V VVM ", "SET WINDOW",
++ SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_3},
++ {0x25, "M MM M ", "READ CAPACITY",
++ SCST_DATA_READ, SCST_IMPLICIT_HQ|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_read_capacity},
++ {0x25, " O ", "GET WINDOW",
++ SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_3},
++ {0x28, "M MMMM ", "READ(10)",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_WRITE_EXCL_ALLOWED,
++ 7, get_trans_len_2},
++ {0x28, " O ", "GET MESSAGE(10)",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x29, "V VV O ", "READ GENERATION",
++ SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_1},
++ {0x2A, "O MO M ", "WRITE(10)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_WRITE_MEDIUM,
++ 7, get_trans_len_2},
++ {0x2A, " O ", "SEND MESSAGE(10)",
++ SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2},
++ {0x2A, " O ", "SEND(10)",
++ SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2},
++ {0x2B, " O ", "LOCATE",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|
++ SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x2B, " O ", "POSITION TO ELEMENT",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0x2B, "O OO O ", "SEEK(10)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x2C, "V O O ", "ERASE(10)",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM,
++ 0, get_trans_len_none},
++ {0x2D, "V O O ", "READ UPDATED BLOCK",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 0, get_trans_len_single},
++ {0x2E, "O OO O ", "WRITE AND VERIFY(10)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
++ 7, get_trans_len_2},
++ {0x2F, "O OO O ", "VERIFY(10)",
++ SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED|
++ SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED,
++ 7, get_trans_len_2},
++ {0x33, "O OO O ", "SET LIMITS(10)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x34, " O ", "READ POSITION",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT|
++ SCST_WRITE_EXCL_ALLOWED,
++ 7, get_trans_len_read_pos},
++ {0x34, " O ", "GET DATA BUFFER STATUS",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x34, "O OO O ", "PRE-FETCH",
++ SCST_DATA_NONE, SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x35, "O OO O ", "SYNCHRONIZE CACHE",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x36, "O OO O ", "LOCK UNLOCK CACHE",
++ SCST_DATA_NONE, SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x37, "O O ", "READ DEFECT DATA(10)",
++ SCST_DATA_READ, SCST_WRITE_EXCL_ALLOWED,
++ 8, get_trans_len_1},
++ {0x37, " O ", "INIT ELEMENT STATUS WRANGE",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0x38, " O O ", "MEDIUM SCAN",
++ SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_1},
++ {0x39, "OOOOOOOO ", "COMPARE",
++ SCST_DATA_WRITE, FLAG_NONE, 3, get_trans_len_3},
++ {0x3A, "OOOOOOOO ", "COPY AND VERIFY",
++ SCST_DATA_WRITE, FLAG_NONE, 3, get_trans_len_3},
++ {0x3B, "OOOOOOOOOOOOOOOO", "WRITE BUFFER",
++ SCST_DATA_WRITE, SCST_SMALL_TIMEOUT, 6, get_trans_len_3},
++ {0x3C, "OOOOOOOOOOOOOOOO", "READ BUFFER",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT, 6, get_trans_len_3},
++ {0x3D, " O O ", "UPDATE BLOCK",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED,
++ 0, get_trans_len_single},
++ {0x3E, "O OO O ", "READ LONG",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x3F, "O O O ", "WRITE LONG",
++ SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 7, get_trans_len_2},
++ {0x40, "OOOOOOOOOO ", "CHANGE DEFINITION",
++ SCST_DATA_WRITE, SCST_SMALL_TIMEOUT, 8, get_trans_len_1},
++ {0x41, "O O ", "WRITE SAME",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
++ 0, get_trans_len_single},
++ {0x42, " O ", "READ SUB-CHANNEL",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x42, "O ", "UNMAP",
++ SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 7, get_trans_len_2},
++ {0x43, " O ", "READ TOC/PMA/ATIP",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x44, " M ", "REPORT DENSITY SUPPORT",
++ SCST_DATA_READ, SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 7, get_trans_len_2},
++ {0x44, " O ", "READ HEADER",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x45, " O ", "PLAY AUDIO(10)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x46, " O ", "GET CONFIGURATION",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x47, " O ", "PLAY AUDIO MSF",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x48, " O ", "PLAY AUDIO TRACK INDEX",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x49, " O ", "PLAY TRACK RELATIVE(10)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x4A, " O ", "GET EVENT STATUS NOTIFICATION",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x4B, " O ", "PAUSE/RESUME",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x4C, "OOOOOOOOOOOOOOOO", "LOG SELECT",
++ SCST_DATA_WRITE, SCST_STRICTLY_SERIALIZED, 7, get_trans_len_2},
++ {0x4D, "OOOOOOOOOOOOOOOO", "LOG SENSE",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 7, get_trans_len_2},
++ {0x4E, " O ", "STOP PLAY/SCAN",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x50, " ", "XDWRITE",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0x51, " O ", "READ DISC INFORMATION",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x51, " ", "XPWRITE",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0x52, " O ", "READ TRACK INFORMATION",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x53, "O ", "XDWRITEREAD(10)",
++ SCST_DATA_READ|SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
++ SCST_WRITE_MEDIUM,
++ 7, get_bidi_trans_len_2},
++ {0x53, " O ", "RESERVE TRACK",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x54, " O ", "SEND OPC INFORMATION",
++ SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2},
++ {0x55, "OOOOOOOOOOOOOOOO", "MODE SELECT(10)",
++ SCST_DATA_WRITE, SCST_STRICTLY_SERIALIZED, 7, get_trans_len_2},
++ {0x56, "OOOOOOOOOOOOOOOO", "RESERVE(10)",
++ SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_SERIALIZED|
++ SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_none},
++ {0x57, "OOOOOOOOOOOOOOOO", "RELEASE(10)",
++ SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_SERIALIZED|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_none},
++ {0x58, " O ", "REPAIR TRACK",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0x5A, "OOOOOOOOOOOOOOOO", "MODE SENSE(10)",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT, 7, get_trans_len_2},
++ {0x5B, " O ", "CLOSE TRACK/SESSION",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x5C, " O ", "READ BUFFER CAPACITY",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2},
++ {0x5D, " O ", "SEND CUE SHEET",
++ SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_3},
++ {0x5E, "OOOOO OOOO ", "PERSISTENT RESERVE IN",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT|
++ SCST_LOCAL_CMD|SCST_SERIALIZED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 5, get_trans_len_4},
++ {0x5F, "OOOOO OOOO ", "PERSISTENT RESERVE OUT",
++ SCST_DATA_WRITE, SCST_SMALL_TIMEOUT|
++ SCST_LOCAL_CMD|SCST_SERIALIZED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 5, get_trans_len_4},
++
++ /* 16-bytes length CDB */
++ {0x80, "O OO O ", "XDWRITE EXTENDED",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0x80, " M ", "WRITE FILEMARKS",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0x81, "O OO O ", "REBUILD",
++ SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4},
++ {0x82, "O OO O ", "REGENERATE",
++ SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4},
++ {0x83, "OOOOOOOOOOOOOOOO", "EXTENDED COPY",
++ SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4},
++ {0x84, "OOOOOOOOOOOOOOOO", "RECEIVE COPY RESULT",
++ SCST_DATA_WRITE, FLAG_NONE, 10, get_trans_len_4},
++ {0x86, "OOOOOOOOOO ", "ACCESS CONTROL IN",
++ SCST_DATA_NONE, SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_none},
++ {0x87, "OOOOOOOOOO ", "ACCESS CONTROL OUT",
++ SCST_DATA_NONE, SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|
++ SCST_EXCL_ACCESS_ALLOWED,
++ 0, get_trans_len_none},
++ {0x88, "M MMMM ", "READ(16)",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_WRITE_EXCL_ALLOWED,
++ 10, get_trans_len_4},
++ {0x8A, "O OO O ", "WRITE(16)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_WRITE_MEDIUM,
++ 10, get_trans_len_4},
++ {0x8C, "OOOOOOOOOO ", "READ ATTRIBUTE",
++ SCST_DATA_READ, FLAG_NONE, 10, get_trans_len_4},
++ {0x8D, "OOOOOOOOOO ", "WRITE ATTRIBUTE",
++ SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4},
++ {0x8E, "O OO O ", "WRITE AND VERIFY(16)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
++ 10, get_trans_len_4},
++ {0x8F, "O OO O ", "VERIFY(16)",
++ SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED|
++ SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED,
++ 10, get_trans_len_4},
++ {0x90, "O OO O ", "PRE-FETCH(16)",
++ SCST_DATA_NONE, SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x91, "O OO O ", "SYNCHRONIZE CACHE(16)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x91, " M ", "SPACE(16)",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|
++ SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x92, "O OO O ", "LOCK UNLOCK CACHE(16)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0x92, " O ", "LOCATE(16)",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|
++ SCST_WRITE_EXCL_ALLOWED,
++ 0, get_trans_len_none},
++ {0x93, "O O ", "WRITE SAME(16)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
++ 10, get_trans_len_4},
++ {0x93, " M ", "ERASE(16)",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM,
++ 0, get_trans_len_none},
++ {0x9E, "O ", "SERVICE ACTION IN",
++ SCST_DATA_READ, FLAG_NONE, 0, get_trans_len_serv_act_in},
++
++ /* 12-bytes length CDB */
++ {0xA0, "VVVVVVVVVV M ", "REPORT LUNS",
++ SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ|SCST_SKIP_UA|
++ SCST_FULLY_LOCAL_CMD|SCST_LOCAL_CMD|
++ SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
++ 6, get_trans_len_4},
++ {0xA1, " O ", "BLANK",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0xA3, " O ", "SEND KEY",
++ SCST_DATA_WRITE, FLAG_NONE, 8, get_trans_len_2},
++ {0xA3, "OOOOO OOOO ", "REPORT DEVICE IDENTIDIER",
++ SCST_DATA_READ, SCST_REG_RESERVE_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED,
++ 6, get_trans_len_4},
++ {0xA3, " M ", "MAINTENANCE(IN)",
++ SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
++ {0xA4, " O ", "REPORT KEY",
++ SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2},
++ {0xA4, " O ", "MAINTENANCE(OUT)",
++ SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
++ {0xA5, " M ", "MOVE MEDIUM",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0xA5, " O ", "PLAY AUDIO(12)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0xA6, " O O ", "EXCHANGE/LOAD/UNLOAD MEDIUM",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0xA7, " O ", "SET READ AHEAD",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0xA8, " O ", "GET MESSAGE(12)",
++ SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
++ {0xA8, "O OO O ", "READ(12)",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_WRITE_EXCL_ALLOWED,
++ 6, get_trans_len_4},
++ {0xA9, " O ", "PLAY TRACK RELATIVE(12)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0xAA, "O OO O ", "WRITE(12)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ SCST_TEST_IO_IN_SIRQ_ALLOWED|
++#endif
++ SCST_WRITE_MEDIUM,
++ 6, get_trans_len_4},
++ {0xAA, " O ", "SEND MESSAGE(12)",
++ SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
++ {0xAC, " O ", "ERASE(12)",
++ SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none},
++ {0xAC, " M ", "GET PERFORMANCE",
++ SCST_DATA_READ, SCST_UNKNOWN_LENGTH, 0, get_trans_len_none},
++ {0xAD, " O ", "READ DVD STRUCTURE",
++ SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2},
++ {0xAE, "O OO O ", "WRITE AND VERIFY(12)",
++ SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM,
++ 6, get_trans_len_4},
++ {0xAF, "O OO O ", "VERIFY(12)",
++ SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED|
++ SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED|
++ SCST_WRITE_EXCL_ALLOWED,
++ 6, get_trans_len_4},
++#if 0 /* No need to support at all */
++ {0xB0, " OO O ", "SEARCH DATA HIGH(12)",
++ SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1},
++ {0xB1, " OO O ", "SEARCH DATA EQUAL(12)",
++ SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1},
++ {0xB2, " OO O ", "SEARCH DATA LOW(12)",
++ SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1},
++#endif
++ {0xB3, " OO O ", "SET LIMITS(12)",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0xB5, " O ", "REQUEST VOLUME ELEMENT ADDRESS",
++ SCST_DATA_READ, FLAG_NONE, 9, get_trans_len_1},
++ {0xB6, " O ", "SEND VOLUME TAG",
++ SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1},
++ {0xB6, " M ", "SET STREAMING",
++ SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_2},
++ {0xB7, " O ", "READ DEFECT DATA(12)",
++ SCST_DATA_READ, SCST_WRITE_EXCL_ALLOWED,
++ 9, get_trans_len_1},
++ {0xB8, " O ", "READ ELEMENT STATUS",
++ SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_3_read_elem_stat},
++ {0xB9, " O ", "READ CD MSF",
++ SCST_DATA_READ, SCST_UNKNOWN_LENGTH, 0, get_trans_len_none},
++ {0xBA, " O ", "SCAN",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none},
++ {0xBA, " O ", "REDUNDANCY GROUP(IN)",
++ SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
++ {0xBB, " O ", "SET SPEED",
++ SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none},
++ {0xBB, " O ", "REDUNDANCY GROUP(OUT)",
++ SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
++ {0xBC, " O ", "SPARE(IN)",
++ SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
++ {0xBD, " O ", "MECHANISM STATUS",
++ SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2},
++ {0xBD, " O ", "SPARE(OUT)",
++ SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
++ {0xBE, " O ", "READ CD",
++ SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 6, get_trans_len_3},
++ {0xBE, " O ", "VOLUME SET(IN)",
++ SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4},
++ {0xBF, " O ", "SEND DVD STRUCTUE",
++ SCST_DATA_WRITE, FLAG_NONE, 8, get_trans_len_2},
++ {0xBF, " O ", "VOLUME SET(OUT)",
++ SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4},
++ {0xE7, " V ", "INIT ELEMENT STATUS WRANGE",
++ SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_cdb_len_10}
++};
++
++#define SCST_CDB_TBL_SIZE ((int)ARRAY_SIZE(scst_scsi_op_table))
++
++static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev);
++static void scst_check_internal_sense(struct scst_device *dev, int result,
++ uint8_t *sense, int sense_len);
++static void scst_queue_report_luns_changed_UA(struct scst_session *sess,
++ int flags);
++static void __scst_check_set_UA(struct scst_tgt_dev *tgt_dev,
++ const uint8_t *sense, int sense_len, int flags);
++static void scst_alloc_set_UA(struct scst_tgt_dev *tgt_dev,
++ const uint8_t *sense, int sense_len, int flags);
++static void scst_free_all_UA(struct scst_tgt_dev *tgt_dev);
++static void scst_release_space(struct scst_cmd *cmd);
++static void scst_clear_reservation(struct scst_tgt_dev *tgt_dev);
++static int scst_alloc_add_tgt_dev(struct scst_session *sess,
++ struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev);
++static void scst_tgt_retry_timer_fn(unsigned long arg);
++
++#ifdef CONFIG_SCST_DEBUG_TM
++static void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev);
++static void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev);
++#else
++static inline void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev) {}
++static inline void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev) {}
++#endif /* CONFIG_SCST_DEBUG_TM */
++
++/**
++ * scst_alloc_sense() - allocate sense buffer for command
++ *
++ * Allocates, if necessary, sense buffer for command. Returns 0 on success
++ * and error code otherwise. Parameter "atomic" should be non-0 if the
++ * function called in atomic context.
++ */
++int scst_alloc_sense(struct scst_cmd *cmd, int atomic)
++{
++ int res = 0;
++ gfp_t gfp_mask = atomic ? GFP_ATOMIC : (GFP_KERNEL|__GFP_NOFAIL);
++
++ TRACE_ENTRY();
++
++ if (cmd->sense != NULL)
++ goto memzero;
++
++ cmd->sense = mempool_alloc(scst_sense_mempool, gfp_mask);
++ if (cmd->sense == NULL) {
++ PRINT_CRIT_ERROR("Sense memory allocation failed (op %x). "
++ "The sense data will be lost!!", cmd->cdb[0]);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ cmd->sense_buflen = SCST_SENSE_BUFFERSIZE;
++
++memzero:
++ cmd->sense_valid_len = 0;
++ memset(cmd->sense, 0, cmd->sense_buflen);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(scst_alloc_sense);
++
++/**
++ * scst_alloc_set_sense() - allocate and fill sense buffer for command
++ *
++ * Allocates, if necessary, sense buffer for command and copies in
++ * it data from the supplied sense buffer. Returns 0 on success
++ * and error code otherwise.
++ */
++int scst_alloc_set_sense(struct scst_cmd *cmd, int atomic,
++ const uint8_t *sense, unsigned int len)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ /*
++ * We don't check here if the existing sense is valid or not, because
++ * we suppose the caller did it based on cmd->status.
++ */
++
++ res = scst_alloc_sense(cmd, atomic);
++ if (res != 0) {
++ PRINT_BUFFER("Lost sense", sense, len);
++ goto out;
++ }
++
++ cmd->sense_valid_len = len;
++ if (cmd->sense_buflen < len) {
++ PRINT_WARNING("Sense truncated (needed %d), shall you increase "
++ "SCST_SENSE_BUFFERSIZE? Op: %x", len, cmd->cdb[0]);
++ cmd->sense_valid_len = cmd->sense_buflen;
++ }
++
++ memcpy(cmd->sense, sense, cmd->sense_valid_len);
++ TRACE_BUFFER("Sense set", cmd->sense, cmd->sense_valid_len);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(scst_alloc_set_sense);
++
++/**
++ * scst_set_cmd_error_status() - set error SCSI status
++ * @cmd: SCST command
++ * @status: SCSI status to set
++ *
++ * Description:
++ * Sets error SCSI status in the command and prepares it for returning it.
++ * Returns 0 on success, error code otherwise.
++ */
++int scst_set_cmd_error_status(struct scst_cmd *cmd, int status)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (status == SAM_STAT_RESERVATION_CONFLICT) {
++ TRACE(TRACE_SCSI|TRACE_MINOR, "Reservation conflict (dev %s, "
++ "initiator %s, tgt_id %d)",
++ cmd->dev ? cmd->dev->virt_name : NULL,
++ cmd->sess->initiator_name, cmd->tgt->rel_tgt_id);
++ }
++
++ if (cmd->status != 0) {
++ TRACE_MGMT_DBG("cmd %p already has status %x set", cmd,
++ cmd->status);
++ res = -EEXIST;
++ goto out;
++ }
++
++ cmd->status = status;
++ cmd->host_status = DID_OK;
++
++ cmd->dbl_ua_orig_resp_data_len = cmd->resp_data_len;
++ cmd->dbl_ua_orig_data_direction = cmd->data_direction;
++
++ cmd->data_direction = SCST_DATA_NONE;
++ cmd->resp_data_len = 0;
++ cmd->resid_possible = 1;
++ cmd->is_send_status = 1;
++
++ cmd->completed = 1;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(scst_set_cmd_error_status);
++
++static int scst_set_lun_not_supported_request_sense(struct scst_cmd *cmd,
++ int key, int asc, int ascq)
++{
++ int res;
++ int sense_len, len;
++ struct scatterlist *sg;
++
++ TRACE_ENTRY();
++
++ if (cmd->status != 0) {
++ TRACE_MGMT_DBG("cmd %p already has status %x set", cmd,
++ cmd->status);
++ res = -EEXIST;
++ goto out;
++ }
++
++ if ((cmd->sg != NULL) && SCST_SENSE_VALID(sg_virt(cmd->sg))) {
++ TRACE_MGMT_DBG("cmd %p already has sense set", cmd);
++ res = -EEXIST;
++ goto out;
++ }
++
++ if (cmd->sg == NULL) {
++ /*
++ * If target driver preparing data buffer using alloc_data_buf()
++ * callback, it is responsible to copy the sense to its buffer
++ * in xmit_response().
++ */
++ if (cmd->tgt_data_buf_alloced && (cmd->tgt_sg != NULL)) {
++ cmd->sg = cmd->tgt_sg;
++ cmd->sg_cnt = cmd->tgt_sg_cnt;
++ TRACE_MEM("Tgt sg used for sense for cmd %p", cmd);
++ goto go;
++ }
++
++ if (cmd->bufflen == 0)
++ cmd->bufflen = cmd->cdb[4];
++
++ cmd->sg = scst_alloc(cmd->bufflen, GFP_ATOMIC, &cmd->sg_cnt);
++ if (cmd->sg == NULL) {
++ PRINT_ERROR("Unable to alloc sg for REQUEST SENSE"
++ "(sense %x/%x/%x)", key, asc, ascq);
++ res = 1;
++ goto out;
++ }
++
++ TRACE_MEM("sg %p alloced for sense for cmd %p (cnt %d, "
++ "len %d)", cmd->sg, cmd, cmd->sg_cnt, cmd->bufflen);
++ }
++
++go:
++ sg = cmd->sg;
++ len = sg->length;
++
++ TRACE_MEM("sg %p (len %d) for sense for cmd %p", sg, len, cmd);
++
++ sense_len = scst_set_sense(sg_virt(sg), len, cmd->cdb[1] & 1,
++ key, asc, ascq);
++
++ TRACE_BUFFER("Sense set", sg_virt(sg), sense_len);
++
++ cmd->data_direction = SCST_DATA_READ;
++ scst_set_resp_data_len(cmd, sense_len);
++
++ res = 0;
++ cmd->completed = 1;
++ cmd->resid_possible = 1;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_set_lun_not_supported_inquiry(struct scst_cmd *cmd)
++{
++ int res;
++ uint8_t *buf;
++ struct scatterlist *sg;
++ int len;
++
++ TRACE_ENTRY();
++
++ if (cmd->status != 0) {
++ TRACE_MGMT_DBG("cmd %p already has status %x set", cmd,
++ cmd->status);
++ res = -EEXIST;
++ goto out;
++ }
++
++ if (cmd->sg == NULL) {
++ /*
++ * If target driver preparing data buffer using alloc_data_buf()
++ * callback, it is responsible to copy the sense to its buffer
++ * in xmit_response().
++ */
++ if (cmd->tgt_data_buf_alloced && (cmd->tgt_sg != NULL)) {
++ cmd->sg = cmd->tgt_sg;
++ cmd->sg_cnt = cmd->tgt_sg_cnt;
++ TRACE_MEM("Tgt used for INQUIRY for not supported "
++ "LUN for cmd %p", cmd);
++ goto go;
++ }
++
++ if (cmd->bufflen == 0)
++ cmd->bufflen = min_t(int, 36, (cmd->cdb[3] << 8) | cmd->cdb[4]);
++
++ cmd->sg = scst_alloc(cmd->bufflen, GFP_ATOMIC, &cmd->sg_cnt);
++ if (cmd->sg == NULL) {
++ PRINT_ERROR("%s", "Unable to alloc sg for INQUIRY "
++ "for not supported LUN");
++ res = 1;
++ goto out;
++ }
++
++ TRACE_MEM("sg %p alloced for INQUIRY for not supported LUN for "
++ "cmd %p (cnt %d, len %d)", cmd->sg, cmd, cmd->sg_cnt,
++ cmd->bufflen);
++ }
++
++go:
++ sg = cmd->sg;
++ len = sg->length;
++
++ TRACE_MEM("sg %p (len %d) for INQUIRY for cmd %p", sg, len, cmd);
++
++ buf = sg_virt(sg);
++ len = min_t(int, 36, len);
++
++ memset(buf, 0, len);
++ buf[0] = 0x7F; /* Peripheral qualifier 011b, Peripheral device type 1Fh */
++ buf[4] = len - 4;
++
++ TRACE_BUFFER("INQUIRY for not supported LUN set", buf, len);
++
++ cmd->data_direction = SCST_DATA_READ;
++ scst_set_resp_data_len(cmd, len);
++
++ res = 0;
++ cmd->completed = 1;
++ cmd->resid_possible = 1;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_set_cmd_error() - set error in the command and fill the sense buffer.
++ *
++ * Sets error in the command and fill the sense buffer. Returns 0 on success,
++ * error code otherwise.
++ */
++int scst_set_cmd_error(struct scst_cmd *cmd, int key, int asc, int ascq)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ /*
++ * We need for LOGICAL UNIT NOT SUPPORTED special handling for
++ * REQUEST SENSE and INQUIRY.
++ */
++ if ((key == ILLEGAL_REQUEST) && (asc == 0x25) && (ascq == 0)) {
++ if (cmd->cdb[0] == REQUEST_SENSE)
++ res = scst_set_lun_not_supported_request_sense(cmd,
++ key, asc, ascq);
++ else if (cmd->cdb[0] == INQUIRY)
++ res = scst_set_lun_not_supported_inquiry(cmd);
++ else
++ goto do_sense;
++
++ if (res > 0)
++ goto do_sense;
++ else
++ goto out;
++ }
++
++do_sense:
++ res = scst_set_cmd_error_status(cmd, SAM_STAT_CHECK_CONDITION);
++ if (res != 0)
++ goto out;
++
++ res = scst_alloc_sense(cmd, 1);
++ if (res != 0) {
++ PRINT_ERROR("Lost sense data (key %x, asc %x, ascq %x)",
++ key, asc, ascq);
++ goto out;
++ }
++
++ cmd->sense_valid_len = scst_set_sense(cmd->sense, cmd->sense_buflen,
++ scst_get_cmd_dev_d_sense(cmd), key, asc, ascq);
++ TRACE_BUFFER("Sense set", cmd->sense, cmd->sense_valid_len);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(scst_set_cmd_error);
++
++/**
++ * scst_set_sense() - set sense from KEY/ASC/ASCQ numbers
++ *
++ * Sets the corresponding fields in the sense buffer taking sense type
++ * into account. Returns resulting sense length.
++ */
++int scst_set_sense(uint8_t *buffer, int len, bool d_sense,
++ int key, int asc, int ascq)
++{
++ int res;
++
++ BUG_ON(len == 0);
++
++ memset(buffer, 0, len);
++
++ if (d_sense) {
++ /* Descriptor format */
++ if (len < 8) {
++ PRINT_ERROR("Length %d of sense buffer too small to "
++ "fit sense %x:%x:%x", len, key, asc, ascq);
++ }
++
++ buffer[0] = 0x72; /* Response Code */
++ if (len > 1)
++ buffer[1] = key; /* Sense Key */
++ if (len > 2)
++ buffer[2] = asc; /* ASC */
++ if (len > 3)
++ buffer[3] = ascq; /* ASCQ */
++ res = 8;
++ } else {
++ /* Fixed format */
++ if (len < 18) {
++ PRINT_ERROR("Length %d of sense buffer too small to "
++ "fit sense %x:%x:%x", len, key, asc, ascq);
++ }
++
++ buffer[0] = 0x70; /* Response Code */
++ if (len > 2)
++ buffer[2] = key; /* Sense Key */
++ if (len > 7)
++ buffer[7] = 0x0a; /* Additional Sense Length */
++ if (len > 12)
++ buffer[12] = asc; /* ASC */
++ if (len > 13)
++ buffer[13] = ascq; /* ASCQ */
++ res = 18;
++ }
++
++ TRACE_BUFFER("Sense set", buffer, res);
++ return res;
++}
++EXPORT_SYMBOL(scst_set_sense);
++
++/**
++ * scst_analyze_sense() - analyze sense
++ *
++ * Returns true if sense matches to (key, asc, ascq) and false otherwise.
++ * Valid_mask is one or several SCST_SENSE_*_VALID constants setting valid
++ * (key, asc, ascq) values.
++ */
++bool scst_analyze_sense(const uint8_t *sense, int len, unsigned int valid_mask,
++ int key, int asc, int ascq)
++{
++ bool res = false;
++
++ /* Response Code */
++ if ((sense[0] == 0x70) || (sense[0] == 0x71)) {
++ /* Fixed format */
++
++ /* Sense Key */
++ if (valid_mask & SCST_SENSE_KEY_VALID) {
++ if (len < 3)
++ goto out;
++ if (sense[2] != key)
++ goto out;
++ }
++
++ /* ASC */
++ if (valid_mask & SCST_SENSE_ASC_VALID) {
++ if (len < 13)
++ goto out;
++ if (sense[12] != asc)
++ goto out;
++ }
++
++ /* ASCQ */
++ if (valid_mask & SCST_SENSE_ASCQ_VALID) {
++ if (len < 14)
++ goto out;
++ if (sense[13] != ascq)
++ goto out;
++ }
++ } else if ((sense[0] == 0x72) || (sense[0] == 0x73)) {
++ /* Descriptor format */
++
++ /* Sense Key */
++ if (valid_mask & SCST_SENSE_KEY_VALID) {
++ if (len < 2)
++ goto out;
++ if (sense[1] != key)
++ goto out;
++ }
++
++ /* ASC */
++ if (valid_mask & SCST_SENSE_ASC_VALID) {
++ if (len < 3)
++ goto out;
++ if (sense[2] != asc)
++ goto out;
++ }
++
++ /* ASCQ */
++ if (valid_mask & SCST_SENSE_ASCQ_VALID) {
++ if (len < 4)
++ goto out;
++ if (sense[3] != ascq)
++ goto out;
++ }
++ } else
++ goto out;
++
++ res = true;
++
++out:
++ TRACE_EXIT_RES((int)res);
++ return res;
++}
++EXPORT_SYMBOL(scst_analyze_sense);
++
++/**
++ * scst_is_ua_sense() - determine if the sense is UA sense
++ *
++ * Returns true if the sense is valid and carrying a Unit
++ * Attention or false otherwise.
++ */
++bool scst_is_ua_sense(const uint8_t *sense, int len)
++{
++ if (SCST_SENSE_VALID(sense))
++ return scst_analyze_sense(sense, len,
++ SCST_SENSE_KEY_VALID, UNIT_ATTENTION, 0, 0);
++ else
++ return false;
++}
++EXPORT_SYMBOL(scst_is_ua_sense);
++
++bool scst_is_ua_global(const uint8_t *sense, int len)
++{
++ bool res;
++
++ /* Changing it don't forget to change scst_requeue_ua() as well!! */
++
++ if (scst_analyze_sense(sense, len, SCST_SENSE_ALL_VALID,
++ SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed)))
++ res = true;
++ else
++ res = false;
++
++ return res;
++}
++
++/**
++ * scst_check_convert_sense() - check sense type and convert it if needed
++ *
++ * Checks if sense in the sense buffer, if any, is in the correct format.
++ * If not, converts it in the correct format.
++ */
++void scst_check_convert_sense(struct scst_cmd *cmd)
++{
++ bool d_sense;
++
++ TRACE_ENTRY();
++
++ if ((cmd->sense == NULL) || (cmd->status != SAM_STAT_CHECK_CONDITION))
++ goto out;
++
++ d_sense = scst_get_cmd_dev_d_sense(cmd);
++ if (d_sense && ((cmd->sense[0] == 0x70) || (cmd->sense[0] == 0x71))) {
++ TRACE_MGMT_DBG("Converting fixed sense to descriptor (cmd %p)",
++ cmd);
++ if ((cmd->sense_valid_len < 18)) {
++ PRINT_ERROR("Sense too small to convert (%d, "
++ "type: fixed)", cmd->sense_buflen);
++ goto out;
++ }
++ cmd->sense_valid_len = scst_set_sense(cmd->sense, cmd->sense_buflen,
++ d_sense, cmd->sense[2], cmd->sense[12], cmd->sense[13]);
++ } else if (!d_sense && ((cmd->sense[0] == 0x72) ||
++ (cmd->sense[0] == 0x73))) {
++ TRACE_MGMT_DBG("Converting descriptor sense to fixed (cmd %p)",
++ cmd);
++ if ((cmd->sense_buflen < 18) || (cmd->sense_valid_len < 8)) {
++ PRINT_ERROR("Sense too small to convert (%d, "
++ "type: descriptor, valid %d)",
++ cmd->sense_buflen, cmd->sense_valid_len);
++ goto out;
++ }
++ cmd->sense_valid_len = scst_set_sense(cmd->sense,
++ cmd->sense_buflen, d_sense,
++ cmd->sense[1], cmd->sense[2], cmd->sense[3]);
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_check_convert_sense);
++
++static int scst_set_cmd_error_sense(struct scst_cmd *cmd, uint8_t *sense,
++ unsigned int len)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = scst_set_cmd_error_status(cmd, SAM_STAT_CHECK_CONDITION);
++ if (res != 0)
++ goto out;
++
++ res = scst_alloc_set_sense(cmd, 1, sense, len);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_set_busy() - set BUSY or TASK QUEUE FULL status
++ *
++ * Sets BUSY or TASK QUEUE FULL status depending on if this session has other
++ * outstanding commands or not.
++ */
++void scst_set_busy(struct scst_cmd *cmd)
++{
++ int c = atomic_read(&cmd->sess->sess_cmd_count);
++
++ TRACE_ENTRY();
++
++ if ((c <= 1) || (cmd->sess->init_phase != SCST_SESS_IPH_READY)) {
++ scst_set_cmd_error_status(cmd, SAM_STAT_BUSY);
++ TRACE(TRACE_FLOW_CONTROL, "Sending BUSY status to initiator %s "
++ "(cmds count %d, queue_type %x, sess->init_phase %d)",
++ cmd->sess->initiator_name, c,
++ cmd->queue_type, cmd->sess->init_phase);
++ } else {
++ scst_set_cmd_error_status(cmd, SAM_STAT_TASK_SET_FULL);
++ TRACE(TRACE_FLOW_CONTROL, "Sending QUEUE_FULL status to "
++ "initiator %s (cmds count %d, queue_type %x, "
++ "sess->init_phase %d)", cmd->sess->initiator_name, c,
++ cmd->queue_type, cmd->sess->init_phase);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_set_busy);
++
++/**
++ * scst_set_initial_UA() - set initial Unit Attention
++ *
++ * Sets initial Unit Attention on all devices of the session,
++ * replacing default scst_sense_reset_UA
++ */
++void scst_set_initial_UA(struct scst_session *sess, int key, int asc, int ascq)
++{
++ int i;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Setting for sess %p initial UA %x/%x/%x", sess, key,
++ asc, ascq);
++
++ /* To protect sess_tgt_dev_list */
++ mutex_lock(&scst_mutex);
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ struct scst_tgt_dev *tgt_dev;
++
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ spin_lock_bh(&tgt_dev->tgt_dev_lock);
++ if (!list_empty(&tgt_dev->UA_list)) {
++ struct scst_tgt_dev_UA *ua;
++
++ ua = list_entry(tgt_dev->UA_list.next,
++ typeof(*ua), UA_list_entry);
++ if (scst_analyze_sense(ua->UA_sense_buffer,
++ ua->UA_valid_sense_len,
++ SCST_SENSE_ALL_VALID,
++ SCST_LOAD_SENSE(scst_sense_reset_UA))) {
++ ua->UA_valid_sense_len = scst_set_sense(
++ ua->UA_sense_buffer,
++ sizeof(ua->UA_sense_buffer),
++ tgt_dev->dev->d_sense,
++ key, asc, ascq);
++ } else
++ PRINT_ERROR("%s",
++ "The first UA isn't RESET UA");
++ } else
++ PRINT_ERROR("%s", "There's no RESET UA to "
++ "replace");
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++ }
++ }
++
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_set_initial_UA);
++
++struct scst_aen *scst_alloc_aen(struct scst_session *sess,
++ uint64_t unpacked_lun)
++{
++ struct scst_aen *aen;
++
++ TRACE_ENTRY();
++
++ aen = mempool_alloc(scst_aen_mempool, GFP_KERNEL);
++ if (aen == NULL) {
++ PRINT_ERROR("AEN memory allocation failed. Corresponding "
++ "event notification will not be performed (initiator "
++ "%s)", sess->initiator_name);
++ goto out;
++ }
++ memset(aen, 0, sizeof(*aen));
++
++ aen->sess = sess;
++ scst_sess_get(sess);
++
++ aen->lun = scst_pack_lun(unpacked_lun, sess->acg->addr_method);
++
++out:
++ TRACE_EXIT_HRES((unsigned long)aen);
++ return aen;
++}
++
++void scst_free_aen(struct scst_aen *aen)
++{
++ TRACE_ENTRY();
++
++ scst_sess_put(aen->sess);
++ mempool_free(aen, scst_aen_mempool);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under scst_mutex */
++void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev,
++ int key, int asc, int ascq)
++{
++ struct scst_tgt_template *tgtt = tgt_dev->sess->tgt->tgtt;
++ uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
++ int sl;
++
++ TRACE_ENTRY();
++
++ if ((tgt_dev->sess->init_phase != SCST_SESS_IPH_READY) ||
++ (tgt_dev->sess->shut_phase != SCST_SESS_SPH_READY))
++ goto out;
++
++ if (tgtt->report_aen != NULL) {
++ struct scst_aen *aen;
++ int rc;
++
++ aen = scst_alloc_aen(tgt_dev->sess, tgt_dev->lun);
++ if (aen == NULL)
++ goto queue_ua;
++
++ aen->event_fn = SCST_AEN_SCSI;
++ aen->aen_sense_len = scst_set_sense(aen->aen_sense,
++ sizeof(aen->aen_sense), tgt_dev->dev->d_sense,
++ key, asc, ascq);
++
++ TRACE_DBG("Calling target's %s report_aen(%p)",
++ tgtt->name, aen);
++ rc = tgtt->report_aen(aen);
++ TRACE_DBG("Target's %s report_aen(%p) returned %d",
++ tgtt->name, aen, rc);
++ if (rc == SCST_AEN_RES_SUCCESS)
++ goto out;
++
++ scst_free_aen(aen);
++ }
++
++queue_ua:
++ TRACE_MGMT_DBG("AEN not supported, queueing plain UA (tgt_dev %p)",
++ tgt_dev);
++ sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
++ tgt_dev->dev->d_sense, key, asc, ascq);
++ scst_check_set_UA(tgt_dev, sense_buffer, sl, 0);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_capacity_data_changed() - notify SCST about device capacity change
++ *
++ * Notifies SCST core that dev has changed its capacity. Called under no locks.
++ */
++void scst_capacity_data_changed(struct scst_device *dev)
++{
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ if (dev->type != TYPE_DISK) {
++ TRACE_MGMT_DBG("Device type %d isn't for CAPACITY DATA "
++ "CHANGED UA", dev->type);
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("CAPACITY DATA CHANGED (dev %p)", dev);
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ scst_gen_aen_or_ua(tgt_dev,
++ SCST_LOAD_SENSE(scst_sense_capacity_data_changed));
++ }
++
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_capacity_data_changed);
++
++static inline bool scst_is_report_luns_changed_type(int type)
++{
++ switch (type) {
++ case TYPE_DISK:
++ case TYPE_TAPE:
++ case TYPE_PRINTER:
++ case TYPE_PROCESSOR:
++ case TYPE_WORM:
++ case TYPE_ROM:
++ case TYPE_SCANNER:
++ case TYPE_MOD:
++ case TYPE_MEDIUM_CHANGER:
++ case TYPE_RAID:
++ case TYPE_ENCLOSURE:
++ return true;
++ default:
++ return false;
++ }
++}
++
++/* scst_mutex supposed to be held */
++static void scst_queue_report_luns_changed_UA(struct scst_session *sess,
++ int flags)
++{
++ uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
++ struct list_head *head;
++ struct scst_tgt_dev *tgt_dev;
++ int i;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Queueing REPORTED LUNS DATA CHANGED UA "
++ "(sess %p)", sess);
++
++ local_bh_disable();
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ head = &sess->sess_tgt_dev_list[i];
++
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ /* Lockdep triggers here a false positive.. */
++ spin_lock(&tgt_dev->tgt_dev_lock);
++ }
++ }
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ head = &sess->sess_tgt_dev_list[i];
++
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ int sl;
++
++ if (!scst_is_report_luns_changed_type(
++ tgt_dev->dev->type))
++ continue;
++
++ sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
++ tgt_dev->dev->d_sense,
++ SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed));
++
++ __scst_check_set_UA(tgt_dev, sense_buffer,
++ sl, flags | SCST_SET_UA_FLAG_GLOBAL);
++ }
++ }
++
++ for (i = SESS_TGT_DEV_LIST_HASH_SIZE-1; i >= 0; i--) {
++ head = &sess->sess_tgt_dev_list[i];
++
++ list_for_each_entry_reverse(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ spin_unlock(&tgt_dev->tgt_dev_lock);
++ }
++ }
++
++ local_bh_enable();
++
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++static void scst_report_luns_changed_sess(struct scst_session *sess)
++{
++ int i;
++ struct scst_tgt_template *tgtt = sess->tgt->tgtt;
++ int d_sense = 0;
++ uint64_t lun = 0;
++
++ TRACE_ENTRY();
++
++ if ((sess->init_phase != SCST_SESS_IPH_READY) ||
++ (sess->shut_phase != SCST_SESS_SPH_READY))
++ goto out;
++
++ TRACE_DBG("REPORTED LUNS DATA CHANGED (sess %p)", sess);
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head;
++ struct scst_tgt_dev *tgt_dev;
++
++ head = &sess->sess_tgt_dev_list[i];
++
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ if (scst_is_report_luns_changed_type(
++ tgt_dev->dev->type)) {
++ lun = tgt_dev->lun;
++ d_sense = tgt_dev->dev->d_sense;
++ goto found;
++ }
++ }
++ }
++
++found:
++ if (tgtt->report_aen != NULL) {
++ struct scst_aen *aen;
++ int rc;
++
++ aen = scst_alloc_aen(sess, lun);
++ if (aen == NULL)
++ goto queue_ua;
++
++ aen->event_fn = SCST_AEN_SCSI;
++ aen->aen_sense_len = scst_set_sense(aen->aen_sense,
++ sizeof(aen->aen_sense), d_sense,
++ SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed));
++
++ TRACE_DBG("Calling target's %s report_aen(%p)",
++ tgtt->name, aen);
++ rc = tgtt->report_aen(aen);
++ TRACE_DBG("Target's %s report_aen(%p) returned %d",
++ tgtt->name, aen, rc);
++ if (rc == SCST_AEN_RES_SUCCESS)
++ goto out;
++
++ scst_free_aen(aen);
++ }
++
++queue_ua:
++ scst_queue_report_luns_changed_UA(sess, 0);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++void scst_report_luns_changed(struct scst_acg *acg)
++{
++ struct scst_session *sess;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("REPORTED LUNS DATA CHANGED (acg %s)", acg->acg_name);
++
++ list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) {
++ scst_report_luns_changed_sess(sess);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_aen_done() - AEN processing done
++ *
++ * Notifies SCST that the driver has sent the AEN and it
++ * can be freed now. Don't forget to set the delivery status, if it
++ * isn't success, using scst_set_aen_delivery_status() before calling
++ * this function.
++ */
++void scst_aen_done(struct scst_aen *aen)
++{
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("AEN %p (fn %d) done (initiator %s)", aen,
++ aen->event_fn, aen->sess->initiator_name);
++
++ if (aen->delivery_status == SCST_AEN_RES_SUCCESS)
++ goto out_free;
++
++ if (aen->event_fn != SCST_AEN_SCSI)
++ goto out_free;
++
++ TRACE_MGMT_DBG("Delivery of SCSI AEN failed (initiator %s)",
++ aen->sess->initiator_name);
++
++ if (scst_analyze_sense(aen->aen_sense, aen->aen_sense_len,
++ SCST_SENSE_ALL_VALID, SCST_LOAD_SENSE(
++ scst_sense_reported_luns_data_changed))) {
++ mutex_lock(&scst_mutex);
++ scst_queue_report_luns_changed_UA(aen->sess,
++ SCST_SET_UA_FLAG_AT_HEAD);
++ mutex_unlock(&scst_mutex);
++ } else {
++ struct list_head *head;
++ struct scst_tgt_dev *tgt_dev;
++ uint64_t lun;
++
++ lun = scst_unpack_lun((uint8_t *)&aen->lun, sizeof(aen->lun));
++
++ mutex_lock(&scst_mutex);
++
++ /* tgt_dev might get dead, so we need to reseek it */
++ head = &aen->sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(lun)];
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ if (tgt_dev->lun == lun) {
++ TRACE_MGMT_DBG("Requeuing failed AEN UA for "
++ "tgt_dev %p", tgt_dev);
++ scst_check_set_UA(tgt_dev, aen->aen_sense,
++ aen->aen_sense_len,
++ SCST_SET_UA_FLAG_AT_HEAD);
++ break;
++ }
++ }
++
++ mutex_unlock(&scst_mutex);
++ }
++
++out_free:
++ scst_free_aen(aen);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_aen_done);
++
++void scst_requeue_ua(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++ if (scst_analyze_sense(cmd->sense, cmd->sense_valid_len,
++ SCST_SENSE_ALL_VALID,
++ SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed))) {
++ TRACE_MGMT_DBG("Requeuing REPORTED LUNS DATA CHANGED UA "
++ "for delivery failed cmd %p", cmd);
++ mutex_lock(&scst_mutex);
++ scst_queue_report_luns_changed_UA(cmd->sess,
++ SCST_SET_UA_FLAG_AT_HEAD);
++ mutex_unlock(&scst_mutex);
++ } else {
++ TRACE_MGMT_DBG("Requeuing UA for delivery failed cmd %p", cmd);
++ scst_check_set_UA(cmd->tgt_dev, cmd->sense,
++ cmd->sense_valid_len, SCST_SET_UA_FLAG_AT_HEAD);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++static void scst_check_reassign_sess(struct scst_session *sess)
++{
++ struct scst_acg *acg, *old_acg;
++ struct scst_acg_dev *acg_dev;
++ int i, rc;
++ struct list_head *head;
++ struct scst_tgt_dev *tgt_dev;
++ bool luns_changed = false;
++ bool add_failed, something_freed, not_needed_freed = false;
++
++ TRACE_ENTRY();
++
++ if (sess->shut_phase != SCST_SESS_SPH_READY)
++ goto out;
++
++ TRACE_MGMT_DBG("Checking reassignment for sess %p (initiator %s)",
++ sess, sess->initiator_name);
++
++ acg = scst_find_acg(sess);
++ if (acg == sess->acg) {
++ TRACE_MGMT_DBG("No reassignment for sess %p", sess);
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("sess %p will be reassigned from acg %s to acg %s",
++ sess, sess->acg->acg_name, acg->acg_name);
++
++ old_acg = sess->acg;
++ sess->acg = NULL; /* to catch implicit dependencies earlier */
++
++retry_add:
++ add_failed = false;
++ list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) {
++ unsigned int inq_changed_ua_needed = 0;
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ head = &sess->sess_tgt_dev_list[i];
++
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ if ((tgt_dev->dev == acg_dev->dev) &&
++ (tgt_dev->lun == acg_dev->lun) &&
++ (tgt_dev->acg_dev->rd_only == acg_dev->rd_only)) {
++ TRACE_MGMT_DBG("sess %p: tgt_dev %p for "
++ "LUN %lld stays the same",
++ sess, tgt_dev,
++ (unsigned long long)tgt_dev->lun);
++ tgt_dev->acg_dev = acg_dev;
++ goto next;
++ } else if (tgt_dev->lun == acg_dev->lun)
++ inq_changed_ua_needed = 1;
++ }
++ }
++
++ luns_changed = true;
++
++ TRACE_MGMT_DBG("sess %p: Allocing new tgt_dev for LUN %lld",
++ sess, (unsigned long long)acg_dev->lun);
++
++ rc = scst_alloc_add_tgt_dev(sess, acg_dev, &tgt_dev);
++ if (rc == -EPERM)
++ continue;
++ else if (rc != 0) {
++ add_failed = true;
++ break;
++ }
++
++ tgt_dev->inq_changed_ua_needed = inq_changed_ua_needed ||
++ not_needed_freed;
++next:
++ continue;
++ }
++
++ something_freed = false;
++ not_needed_freed = true;
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct scst_tgt_dev *t;
++ head = &sess->sess_tgt_dev_list[i];
++
++ list_for_each_entry_safe(tgt_dev, t, head,
++ sess_tgt_dev_list_entry) {
++ if (tgt_dev->acg_dev->acg != acg) {
++ TRACE_MGMT_DBG("sess %p: Deleting not used "
++ "tgt_dev %p for LUN %lld",
++ sess, tgt_dev,
++ (unsigned long long)tgt_dev->lun);
++ luns_changed = true;
++ something_freed = true;
++ scst_free_tgt_dev(tgt_dev);
++ }
++ }
++ }
++
++ if (add_failed && something_freed) {
++ TRACE_MGMT_DBG("sess %p: Retrying adding new tgt_devs", sess);
++ goto retry_add;
++ }
++
++ sess->acg = acg;
++
++ TRACE_DBG("Moving sess %p from acg %s to acg %s", sess,
++ old_acg->acg_name, acg->acg_name);
++ list_move_tail(&sess->acg_sess_list_entry, &acg->acg_sess_list);
++
++ scst_recreate_sess_luns_link(sess);
++ /* Ignore possible error, since we can't do anything on it */
++
++ if (luns_changed) {
++ scst_report_luns_changed_sess(sess);
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ head = &sess->sess_tgt_dev_list[i];
++
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ if (tgt_dev->inq_changed_ua_needed) {
++ TRACE_MGMT_DBG("sess %p: Setting "
++ "INQUIRY DATA HAS CHANGED UA "
++ "(tgt_dev %p)", sess, tgt_dev);
++
++ tgt_dev->inq_changed_ua_needed = 0;
++
++ scst_gen_aen_or_ua(tgt_dev,
++ SCST_LOAD_SENSE(scst_sense_inquery_data_changed));
++ }
++ }
++ }
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++void scst_check_reassign_sessions(void)
++{
++ struct scst_tgt_template *tgtt;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) {
++ struct scst_tgt *tgt;
++ list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
++ struct scst_session *sess;
++ list_for_each_entry(sess, &tgt->sess_list,
++ sess_list_entry) {
++ scst_check_reassign_sess(sess);
++ }
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static int scst_get_cmd_abnormal_done_state(const struct scst_cmd *cmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ switch (cmd->state) {
++ case SCST_CMD_STATE_INIT_WAIT:
++ case SCST_CMD_STATE_INIT:
++ case SCST_CMD_STATE_PARSE:
++ if (cmd->preprocessing_only) {
++ res = SCST_CMD_STATE_PREPROCESSING_DONE;
++ break;
++ } /* else go through */
++ case SCST_CMD_STATE_DEV_DONE:
++ if (cmd->internal)
++ res = SCST_CMD_STATE_FINISHED_INTERNAL;
++ else
++ res = SCST_CMD_STATE_PRE_XMIT_RESP;
++ break;
++
++ case SCST_CMD_STATE_PRE_DEV_DONE:
++ case SCST_CMD_STATE_MODE_SELECT_CHECKS:
++ res = SCST_CMD_STATE_DEV_DONE;
++ break;
++
++ case SCST_CMD_STATE_PRE_XMIT_RESP:
++ res = SCST_CMD_STATE_XMIT_RESP;
++ break;
++
++ case SCST_CMD_STATE_PREPROCESSING_DONE:
++ case SCST_CMD_STATE_PREPROCESSING_DONE_CALLED:
++ if (cmd->tgt_dev == NULL)
++ res = SCST_CMD_STATE_PRE_XMIT_RESP;
++ else
++ res = SCST_CMD_STATE_PRE_DEV_DONE;
++ break;
++
++ case SCST_CMD_STATE_PREPARE_SPACE:
++ if (cmd->preprocessing_only) {
++ res = SCST_CMD_STATE_PREPROCESSING_DONE;
++ break;
++ } /* else go through */
++ case SCST_CMD_STATE_RDY_TO_XFER:
++ case SCST_CMD_STATE_DATA_WAIT:
++ case SCST_CMD_STATE_TGT_PRE_EXEC:
++ case SCST_CMD_STATE_START_EXEC:
++ case SCST_CMD_STATE_SEND_FOR_EXEC:
++ case SCST_CMD_STATE_LOCAL_EXEC:
++ case SCST_CMD_STATE_REAL_EXEC:
++ case SCST_CMD_STATE_REAL_EXECUTING:
++ res = SCST_CMD_STATE_PRE_DEV_DONE;
++ break;
++
++ default:
++ PRINT_CRIT_ERROR("Wrong cmd state %d (cmd %p, op %x)",
++ cmd->state, cmd, cmd->cdb[0]);
++ BUG();
++ /* Invalid state to suppress a compiler warning */
++ res = SCST_CMD_STATE_LAST_ACTIVE;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_set_cmd_abnormal_done_state() - set command's next abnormal done state
++ *
++ * Sets state of the SCSI target state machine to abnormally complete command
++ * ASAP.
++ *
++ * Returns the new state.
++ */
++int scst_set_cmd_abnormal_done_state(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ switch (cmd->state) {
++ case SCST_CMD_STATE_XMIT_RESP:
++ case SCST_CMD_STATE_FINISHED:
++ case SCST_CMD_STATE_FINISHED_INTERNAL:
++ case SCST_CMD_STATE_XMIT_WAIT:
++ PRINT_CRIT_ERROR("Wrong cmd state %d (cmd %p, op %x)",
++ cmd->state, cmd, cmd->cdb[0]);
++ BUG();
++ }
++#endif
++
++ cmd->state = scst_get_cmd_abnormal_done_state(cmd);
++
++ switch (cmd->state) {
++ case SCST_CMD_STATE_INIT_WAIT:
++ case SCST_CMD_STATE_INIT:
++ case SCST_CMD_STATE_PARSE:
++ case SCST_CMD_STATE_PREPROCESSING_DONE:
++ case SCST_CMD_STATE_PREPROCESSING_DONE_CALLED:
++ case SCST_CMD_STATE_PREPARE_SPACE:
++ case SCST_CMD_STATE_RDY_TO_XFER:
++ case SCST_CMD_STATE_DATA_WAIT:
++ cmd->write_len = 0;
++ cmd->resid_possible = 1;
++ break;
++ case SCST_CMD_STATE_TGT_PRE_EXEC:
++ case SCST_CMD_STATE_SEND_FOR_EXEC:
++ case SCST_CMD_STATE_START_EXEC:
++ case SCST_CMD_STATE_LOCAL_EXEC:
++ case SCST_CMD_STATE_REAL_EXEC:
++ case SCST_CMD_STATE_REAL_EXECUTING:
++ case SCST_CMD_STATE_DEV_DONE:
++ case SCST_CMD_STATE_PRE_DEV_DONE:
++ case SCST_CMD_STATE_MODE_SELECT_CHECKS:
++ case SCST_CMD_STATE_PRE_XMIT_RESP:
++ case SCST_CMD_STATE_FINISHED_INTERNAL:
++ break;
++ default:
++ PRINT_CRIT_ERROR("Wrong cmd state %d (cmd %p, op %x)",
++ cmd->state, cmd, cmd->cdb[0]);
++ BUG();
++ break;
++ }
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (((cmd->state != SCST_CMD_STATE_PRE_XMIT_RESP) &&
++ (cmd->state != SCST_CMD_STATE_PREPROCESSING_DONE)) &&
++ (cmd->tgt_dev == NULL) && !cmd->internal) {
++ PRINT_CRIT_ERROR("Wrong not inited cmd state %d (cmd %p, "
++ "op %x)", cmd->state, cmd, cmd->cdb[0]);
++ BUG();
++ }
++#endif
++
++ TRACE_EXIT_RES(cmd->state);
++ return cmd->state;
++}
++EXPORT_SYMBOL_GPL(scst_set_cmd_abnormal_done_state);
++
++void scst_zero_write_rest(struct scst_cmd *cmd)
++{
++ int len, offs = 0;
++ uint8_t *buf;
++
++ TRACE_ENTRY();
++
++ len = scst_get_sg_buf_first(cmd, &buf, *cmd->write_sg,
++ *cmd->write_sg_cnt);
++ while (len > 0) {
++ int cur_offs;
++
++ if (offs + len <= cmd->write_len)
++ goto next;
++ else if (offs >= cmd->write_len)
++ cur_offs = 0;
++ else
++ cur_offs = cmd->write_len - offs;
++
++ memset(&buf[cur_offs], 0, len - cur_offs);
++
++next:
++ offs += len;
++ scst_put_sg_buf(cmd, buf, *cmd->write_sg, *cmd->write_sg_cnt);
++ len = scst_get_sg_buf_next(cmd, &buf, *cmd->write_sg,
++ *cmd->write_sg_cnt);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static void scst_adjust_sg(struct scst_cmd *cmd, struct scatterlist *sg,
++ int *sg_cnt, int adjust_len)
++{
++ int i, j, l;
++
++ TRACE_ENTRY();
++
++ l = 0;
++ for (i = 0, j = 0; i < *sg_cnt; i++, j++) {
++ TRACE_DBG("i %d, j %d, sg_cnt %d, sg %p, page_link %lx", i, j,
++ *sg_cnt, sg, sg[j].page_link);
++ if (unlikely(sg_is_chain(&sg[j]))) {
++ sg = sg_chain_ptr(&sg[j]);
++ j = 0;
++ }
++ l += sg[j].length;
++ if (l >= adjust_len) {
++ int left = adjust_len - (l - sg[j].length);
++#ifdef CONFIG_SCST_DEBUG
++ TRACE(TRACE_SG_OP|TRACE_MEMORY, "cmd %p (tag %llu), "
++ "sg %p, sg_cnt %d, adjust_len %d, i %d, j %d, "
++ "sg[j].length %d, left %d",
++ cmd, (long long unsigned int)cmd->tag,
++ sg, *sg_cnt, adjust_len, i, j,
++ sg[j].length, left);
++#endif
++ cmd->orig_sg = sg;
++ cmd->p_orig_sg_cnt = sg_cnt;
++ cmd->orig_sg_cnt = *sg_cnt;
++ cmd->orig_sg_entry = j;
++ cmd->orig_entry_len = sg[j].length;
++ *sg_cnt = (left > 0) ? j+1 : j;
++ sg[j].length = left;
++ cmd->sg_buff_modified = 1;
++ break;
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_restore_sg_buff() - restores modified sg buffer
++ *
++ * Restores modified sg buffer in the original state.
++ */
++void scst_restore_sg_buff(struct scst_cmd *cmd)
++{
++ TRACE_MEM("cmd %p, sg %p, orig_sg_entry %d, "
++ "orig_entry_len %d, orig_sg_cnt %d", cmd, cmd->orig_sg,
++ cmd->orig_sg_entry, cmd->orig_entry_len,
++ cmd->orig_sg_cnt);
++ cmd->orig_sg[cmd->orig_sg_entry].length = cmd->orig_entry_len;
++ *cmd->p_orig_sg_cnt = cmd->orig_sg_cnt;
++ cmd->sg_buff_modified = 0;
++}
++EXPORT_SYMBOL(scst_restore_sg_buff);
++
++/**
++ * scst_set_resp_data_len() - set response data length
++ *
++ * Sets response data length for cmd and truncates its SG vector accordingly.
++ *
++ * The cmd->resp_data_len must not be set directly, it must be set only
++ * using this function. Value of resp_data_len must be <= cmd->bufflen.
++ */
++void scst_set_resp_data_len(struct scst_cmd *cmd, int resp_data_len)
++{
++ TRACE_ENTRY();
++
++ scst_check_restore_sg_buff(cmd);
++ cmd->resp_data_len = resp_data_len;
++
++ if (resp_data_len == cmd->bufflen)
++ goto out;
++
++ TRACE_DBG("cmd %p, resp_data_len %d", cmd, resp_data_len);
++
++ scst_adjust_sg(cmd, cmd->sg, &cmd->sg_cnt, resp_data_len);
++
++ cmd->resid_possible = 1;
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_set_resp_data_len);
++
++void scst_limit_sg_write_len(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++ TRACE_MEM("Limiting sg write len to %d (cmd %p, sg %p, sg_cnt %d)",
++ cmd->write_len, cmd, *cmd->write_sg, *cmd->write_sg_cnt);
++
++ scst_check_restore_sg_buff(cmd);
++ scst_adjust_sg(cmd, *cmd->write_sg, cmd->write_sg_cnt, cmd->write_len);
++
++ TRACE_EXIT();
++ return;
++}
++
++void scst_adjust_resp_data_len(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++ if (!cmd->expected_values_set) {
++ cmd->adjusted_resp_data_len = cmd->resp_data_len;
++ goto out;
++ }
++
++ cmd->adjusted_resp_data_len = min(cmd->resp_data_len,
++ cmd->expected_transfer_len);
++
++ if (cmd->adjusted_resp_data_len != cmd->resp_data_len) {
++ TRACE_MEM("Adjusting resp_data_len to %d (cmd %p, sg %p, "
++ "sg_cnt %d)", cmd->adjusted_resp_data_len, cmd, cmd->sg,
++ cmd->sg_cnt);
++ scst_check_restore_sg_buff(cmd);
++ scst_adjust_sg(cmd, cmd->sg, &cmd->sg_cnt,
++ cmd->adjusted_resp_data_len);
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_cmd_set_write_not_received_data_len() - sets cmd's not received len
++ *
++ * Sets cmd's not received data length. Also automatically sets resid_possible.
++ */
++void scst_cmd_set_write_not_received_data_len(struct scst_cmd *cmd,
++ int not_received)
++{
++ TRACE_ENTRY();
++
++ if (!cmd->expected_values_set) {
++ /*
++ * No expected values set, so no residuals processing.
++ * It can happen if a command preliminary completed before
++ * target driver had a chance to set expected values.
++ */
++ TRACE_MGMT_DBG("No expected values set, ignoring (cmd %p)", cmd);
++ goto out;
++ }
++
++ cmd->resid_possible = 1;
++
++ if ((cmd->expected_data_direction & SCST_DATA_READ) &&
++ (cmd->expected_data_direction & SCST_DATA_WRITE)) {
++ cmd->write_len = cmd->expected_out_transfer_len - not_received;
++ if (cmd->write_len == cmd->out_bufflen)
++ goto out;
++ } else if (cmd->expected_data_direction & SCST_DATA_WRITE) {
++ cmd->write_len = cmd->expected_transfer_len - not_received;
++ if (cmd->write_len == cmd->bufflen)
++ goto out;
++ }
++
++ /*
++ * Write len now can be bigger cmd->(out_)bufflen, but that's OK,
++ * because it will be used to only calculate write residuals.
++ */
++
++ TRACE_DBG("cmd %p, not_received %d, write_len %d", cmd, not_received,
++ cmd->write_len);
++
++ if (cmd->data_direction & SCST_DATA_WRITE)
++ scst_limit_sg_write_len(cmd);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_cmd_set_write_not_received_data_len);
++
++/**
++ * __scst_get_resid() - returns residuals for cmd
++ *
++ * Returns residuals for command. Must not be called directly, use
++ * scst_get_resid() instead.
++ */
++bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid)
++{
++ bool res;
++
++ TRACE_ENTRY();
++
++ *resid = 0;
++ if (bidi_out_resid != NULL)
++ *bidi_out_resid = 0;
++
++ if (!cmd->expected_values_set) {
++ /*
++ * No expected values set, so no residuals processing.
++ * It can happen if a command preliminary completed before
++ * target driver had a chance to set expected values.
++ */
++ TRACE_MGMT_DBG("No expected values set, returning no residual "
++ "(cmd %p)", cmd);
++ res = false;
++ goto out;
++ }
++
++ if (cmd->expected_data_direction & SCST_DATA_READ) {
++ *resid = cmd->expected_transfer_len - cmd->resp_data_len;
++ if ((cmd->expected_data_direction & SCST_DATA_WRITE) && bidi_out_resid) {
++ if (cmd->write_len < cmd->expected_out_transfer_len)
++ *bidi_out_resid = cmd->expected_out_transfer_len -
++ cmd->write_len;
++ else
++ *bidi_out_resid = cmd->write_len - cmd->out_bufflen;
++ }
++ } else if (cmd->expected_data_direction & SCST_DATA_WRITE) {
++ if (cmd->write_len < cmd->expected_transfer_len)
++ *resid = cmd->expected_transfer_len - cmd->write_len;
++ else
++ *resid = cmd->write_len - cmd->bufflen;
++ }
++
++ res = true;
++
++ TRACE_DBG("cmd %p, resid %d, bidi_out_resid %d (resp_data_len %d, "
++ "expected_data_direction %d, write_len %d, bufflen %d)", cmd,
++ *resid, bidi_out_resid ? *bidi_out_resid : 0, cmd->resp_data_len,
++ cmd->expected_data_direction, cmd->write_len, cmd->bufflen);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(__scst_get_resid);
++
++/* No locks */
++int scst_queue_retry_cmd(struct scst_cmd *cmd, int finished_cmds)
++{
++ struct scst_tgt *tgt = cmd->tgt;
++ int res = 0;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ spin_lock_irqsave(&tgt->tgt_lock, flags);
++ tgt->retry_cmds++;
++ /*
++ * Memory barrier is needed here, because we need the exact order
++ * between the read and write between retry_cmds and finished_cmds to
++ * not miss the case when a command finished while we queueing it for
++ * retry after the finished_cmds check.
++ */
++ smp_mb();
++ TRACE_RETRY("TGT QUEUE FULL: incrementing retry_cmds %d",
++ tgt->retry_cmds);
++ if (finished_cmds != atomic_read(&tgt->finished_cmds)) {
++ /* At least one cmd finished, so try again */
++ tgt->retry_cmds--;
++ TRACE_RETRY("Some command(s) finished, direct retry "
++ "(finished_cmds=%d, tgt->finished_cmds=%d, "
++ "retry_cmds=%d)", finished_cmds,
++ atomic_read(&tgt->finished_cmds), tgt->retry_cmds);
++ res = -1;
++ goto out_unlock_tgt;
++ }
++
++ TRACE_RETRY("Adding cmd %p to retry cmd list", cmd);
++ list_add_tail(&cmd->cmd_list_entry, &tgt->retry_cmd_list);
++
++ if (!tgt->retry_timer_active) {
++ tgt->retry_timer.expires = jiffies + SCST_TGT_RETRY_TIMEOUT;
++ add_timer(&tgt->retry_timer);
++ tgt->retry_timer_active = 1;
++ }
++
++out_unlock_tgt:
++ spin_unlock_irqrestore(&tgt->tgt_lock, flags);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_update_hw_pending_start() - update commands pending start
++ *
++ * Updates the command's hw_pending_start as if it's just started hw pending.
++ * Target drivers should call it if they received reply from this pending
++ * command, but SCST core won't see it.
++ */
++void scst_update_hw_pending_start(struct scst_cmd *cmd)
++{
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ /* To sync with scst_check_hw_pending_cmd() */
++ spin_lock_irqsave(&cmd->sess->sess_list_lock, flags);
++ cmd->hw_pending_start = jiffies;
++ TRACE_MGMT_DBG("Updated hw_pending_start to %ld (cmd %p)",
++ cmd->hw_pending_start, cmd);
++ spin_unlock_irqrestore(&cmd->sess->sess_list_lock, flags);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_update_hw_pending_start);
++
++/*
++ * Supposed to be called under sess_list_lock, but can release/reacquire it.
++ * Returns 0 to continue, >0 to restart, <0 to break.
++ */
++static int scst_check_hw_pending_cmd(struct scst_cmd *cmd,
++ unsigned long cur_time, unsigned long max_time,
++ struct scst_session *sess, unsigned long *flags,
++ struct scst_tgt_template *tgtt)
++{
++ int res = -1; /* break */
++
++ TRACE_DBG("cmd %p, hw_pending %d, proc time %ld, "
++ "pending time %ld", cmd, cmd->cmd_hw_pending,
++ (long)(cur_time - cmd->start_time) / HZ,
++ (long)(cur_time - cmd->hw_pending_start) / HZ);
++
++ if (time_before(cur_time, cmd->start_time + max_time)) {
++ /* Cmds are ordered, so no need to check more */
++ goto out;
++ }
++
++ if (!cmd->cmd_hw_pending) {
++ res = 0; /* continue */
++ goto out;
++ }
++
++ if (time_before(cur_time, cmd->hw_pending_start + max_time)) {
++ res = 0; /* continue */
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("Cmd %p HW pending for too long %ld (state %x)",
++ cmd, (cur_time - cmd->hw_pending_start) / HZ,
++ cmd->state);
++
++ cmd->cmd_hw_pending = 0;
++
++ spin_unlock_irqrestore(&sess->sess_list_lock, *flags);
++ tgtt->on_hw_pending_cmd_timeout(cmd);
++ spin_lock_irqsave(&sess->sess_list_lock, *flags);
++
++ res = 1; /* restart */
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void scst_hw_pending_work_fn(struct delayed_work *work)
++{
++ struct scst_session *sess = container_of(work, struct scst_session,
++ hw_pending_work);
++ struct scst_tgt_template *tgtt = sess->tgt->tgtt;
++ struct scst_cmd *cmd;
++ unsigned long cur_time = jiffies;
++ unsigned long flags;
++ unsigned long max_time = tgtt->max_hw_pending_time * HZ;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("HW pending work (sess %p, max time %ld)", sess, max_time/HZ);
++
++ clear_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags);
++
++ spin_lock_irqsave(&sess->sess_list_lock, flags);
++
++restart:
++ list_for_each_entry(cmd, &sess->sess_cmd_list, sess_cmd_list_entry) {
++ int rc;
++
++ rc = scst_check_hw_pending_cmd(cmd, cur_time, max_time, sess,
++ &flags, tgtt);
++ if (rc < 0)
++ break;
++ else if (rc == 0)
++ continue;
++ else
++ goto restart;
++ }
++
++ if (!list_empty(&sess->sess_cmd_list)) {
++ /*
++ * For stuck cmds if there is no activity we might need to have
++ * one more run to release them, so reschedule once again.
++ */
++ TRACE_DBG("Sched HW pending work for sess %p (max time %d)",
++ sess, tgtt->max_hw_pending_time);
++ set_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags);
++ schedule_delayed_work(&sess->hw_pending_work,
++ tgtt->max_hw_pending_time * HZ);
++ }
++
++ spin_unlock_irqrestore(&sess->sess_list_lock, flags);
++
++ TRACE_EXIT();
++ return;
++}
++
++static bool __scst_is_relative_target_port_id_unique(uint16_t id,
++ const struct scst_tgt *t)
++{
++ bool res = true;
++ struct scst_tgt_template *tgtt;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(tgtt, &scst_template_list,
++ scst_template_list_entry) {
++ struct scst_tgt *tgt;
++ list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
++ if (tgt == t)
++ continue;
++ if ((tgt->tgtt->is_target_enabled != NULL) &&
++ !tgt->tgtt->is_target_enabled(tgt))
++ continue;
++ if (id == tgt->rel_tgt_id) {
++ res = false;
++ break;
++ }
++ }
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_mutex supposed to be locked */
++bool scst_is_relative_target_port_id_unique(uint16_t id,
++ const struct scst_tgt *t)
++{
++ bool res;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++ res = __scst_is_relative_target_port_id_unique(id, t);
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++int gen_relative_target_port_id(uint16_t *id)
++{
++ int res = -EOVERFLOW;
++ static unsigned long rti = SCST_MIN_REL_TGT_ID, rti_prev;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ rti_prev = rti;
++ do {
++ if (__scst_is_relative_target_port_id_unique(rti, NULL)) {
++ *id = (uint16_t)rti++;
++ res = 0;
++ goto out_unlock;
++ }
++ rti++;
++ if (rti > SCST_MAX_REL_TGT_ID)
++ rti = SCST_MIN_REL_TGT_ID;
++ } while (rti != rti_prev);
++
++ PRINT_ERROR("%s", "Unable to create unique relative target port id");
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* No locks */
++int scst_alloc_tgt(struct scst_tgt_template *tgtt, struct scst_tgt **tgt)
++{
++ struct scst_tgt *t;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ t = kzalloc(sizeof(*t), GFP_KERNEL);
++ if (t == NULL) {
++ PRINT_ERROR("%s", "Allocation of tgt failed");
++ res = -ENOMEM;
++ goto out;
++ }
++
++ INIT_LIST_HEAD(&t->sess_list);
++ init_waitqueue_head(&t->unreg_waitQ);
++ t->tgtt = tgtt;
++ t->sg_tablesize = tgtt->sg_tablesize;
++ spin_lock_init(&t->tgt_lock);
++ INIT_LIST_HEAD(&t->retry_cmd_list);
++ atomic_set(&t->finished_cmds, 0);
++ init_timer(&t->retry_timer);
++ t->retry_timer.data = (unsigned long)t;
++ t->retry_timer.function = scst_tgt_retry_timer_fn;
++
++ INIT_LIST_HEAD(&t->tgt_acg_list);
++
++ *tgt = t;
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/* No locks */
++void scst_free_tgt(struct scst_tgt *tgt)
++{
++ TRACE_ENTRY();
++
++ kfree(tgt->tgt_name);
++ kfree(tgt->tgt_comment);
++
++ kfree(tgt);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void scst_init_order_data(struct scst_order_data *order_data)
++{
++ int i;
++ spin_lock_init(&order_data->sn_lock);
++ INIT_LIST_HEAD(&order_data->deferred_cmd_list);
++ INIT_LIST_HEAD(&order_data->skipped_sn_list);
++ order_data->curr_sn = (typeof(order_data->curr_sn))(-300);
++ order_data->expected_sn = order_data->curr_sn + 1;
++ order_data->num_free_sn_slots = ARRAY_SIZE(order_data->sn_slots)-1;
++ order_data->cur_sn_slot = &order_data->sn_slots[0];
++ for (i = 0; i < (int)ARRAY_SIZE(order_data->sn_slots); i++)
++ atomic_set(&order_data->sn_slots[i], 0);
++ return;
++}
++
++/* Called under scst_mutex and suspended activity */
++int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev)
++{
++ struct scst_device *dev;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ dev = kzalloc(sizeof(*dev), gfp_mask);
++ if (dev == NULL) {
++ PRINT_ERROR("%s", "Allocation of scst_device failed");
++ res = -ENOMEM;
++ goto out;
++ }
++
++ dev->handler = &scst_null_devtype;
++ atomic_set(&dev->dev_cmd_count, 0);
++ scst_init_mem_lim(&dev->dev_mem_lim);
++ spin_lock_init(&dev->dev_lock);
++ INIT_LIST_HEAD(&dev->blocked_cmd_list);
++ INIT_LIST_HEAD(&dev->dev_tgt_dev_list);
++ INIT_LIST_HEAD(&dev->dev_acg_dev_list);
++ dev->dev_double_ua_possible = 1;
++ dev->queue_alg = SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER;
++
++ mutex_init(&dev->dev_pr_mutex);
++ dev->pr_generation = 0;
++ dev->pr_is_set = 0;
++ dev->pr_holder = NULL;
++ dev->pr_scope = SCOPE_LU;
++ dev->pr_type = TYPE_UNSPECIFIED;
++ INIT_LIST_HEAD(&dev->dev_registrants_list);
++
++ scst_init_order_data(&dev->dev_order_data);
++
++ scst_init_threads(&dev->dev_cmd_threads);
++
++ *out_dev = dev;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++void scst_free_device(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (!list_empty(&dev->dev_tgt_dev_list) ||
++ !list_empty(&dev->dev_acg_dev_list)) {
++ PRINT_CRIT_ERROR("%s: dev_tgt_dev_list or dev_acg_dev_list "
++ "is not empty!", __func__);
++ BUG();
++ }
++#endif
++
++ scst_deinit_threads(&dev->dev_cmd_threads);
++
++ kfree(dev->virt_name);
++ kfree(dev);
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_init_mem_lim - initialize memory limits structure
++ *
++ * Initializes memory limits structure mem_lim according to
++ * the current system configuration. This structure should be latter used
++ * to track and limit allocated by one or more SGV pools memory.
++ */
++void scst_init_mem_lim(struct scst_mem_lim *mem_lim)
++{
++ atomic_set(&mem_lim->alloced_pages, 0);
++ mem_lim->max_allowed_pages =
++ ((uint64_t)scst_max_dev_cmd_mem << 10) >> (PAGE_SHIFT - 10);
++}
++EXPORT_SYMBOL_GPL(scst_init_mem_lim);
++
++static struct scst_acg_dev *scst_alloc_acg_dev(struct scst_acg *acg,
++ struct scst_device *dev, uint64_t lun)
++{
++ struct scst_acg_dev *res;
++
++ TRACE_ENTRY();
++
++ res = kmem_cache_zalloc(scst_acgd_cachep, GFP_KERNEL);
++ if (res == NULL) {
++ PRINT_ERROR("%s", "Allocation of scst_acg_dev failed");
++ goto out;
++ }
++
++ res->dev = dev;
++ res->acg = acg;
++ res->lun = lun;
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/*
++ * The activity supposed to be suspended and scst_mutex held or the
++ * corresponding target supposed to be stopped.
++ */
++static void scst_del_free_acg_dev(struct scst_acg_dev *acg_dev, bool del_sysfs)
++{
++ TRACE_ENTRY();
++
++ TRACE_DBG("Removing acg_dev %p from acg_dev_list and dev_acg_dev_list",
++ acg_dev);
++ list_del(&acg_dev->acg_dev_list_entry);
++ list_del(&acg_dev->dev_acg_dev_list_entry);
++
++ if (del_sysfs)
++ scst_acg_dev_sysfs_del(acg_dev);
++
++ kmem_cache_free(scst_acgd_cachep, acg_dev);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++int scst_acg_add_lun(struct scst_acg *acg, struct kobject *parent,
++ struct scst_device *dev, uint64_t lun, int read_only,
++ bool gen_scst_report_luns_changed, struct scst_acg_dev **out_acg_dev)
++{
++ int res = 0;
++ struct scst_acg_dev *acg_dev;
++ struct scst_tgt_dev *tgt_dev;
++ struct scst_session *sess;
++ LIST_HEAD(tmp_tgt_dev_list);
++
++ TRACE_ENTRY();
++
++ INIT_LIST_HEAD(&tmp_tgt_dev_list);
++
++ acg_dev = scst_alloc_acg_dev(acg, dev, lun);
++ if (acg_dev == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++ acg_dev->rd_only = read_only;
++
++ TRACE_DBG("Adding acg_dev %p to acg_dev_list and dev_acg_dev_list",
++ acg_dev);
++ list_add_tail(&acg_dev->acg_dev_list_entry, &acg->acg_dev_list);
++ list_add_tail(&acg_dev->dev_acg_dev_list_entry, &dev->dev_acg_dev_list);
++
++ list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) {
++ res = scst_alloc_add_tgt_dev(sess, acg_dev, &tgt_dev);
++ if (res == -EPERM)
++ continue;
++ else if (res != 0)
++ goto out_free;
++
++ list_add_tail(&tgt_dev->extra_tgt_dev_list_entry,
++ &tmp_tgt_dev_list);
++ }
++
++ res = scst_acg_dev_sysfs_create(acg_dev, parent);
++ if (res != 0)
++ goto out_free;
++
++ if (gen_scst_report_luns_changed)
++ scst_report_luns_changed(acg);
++
++ PRINT_INFO("Added device %s to group %s (LUN %lld, "
++ "rd_only %d)", dev->virt_name, acg->acg_name,
++ (long long unsigned int)lun, read_only);
++
++ if (out_acg_dev != NULL)
++ *out_acg_dev = acg_dev;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ list_for_each_entry(tgt_dev, &tmp_tgt_dev_list,
++ extra_tgt_dev_list_entry) {
++ scst_free_tgt_dev(tgt_dev);
++ }
++ scst_del_free_acg_dev(acg_dev, false);
++ goto out;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++int scst_acg_del_lun(struct scst_acg *acg, uint64_t lun,
++ bool gen_scst_report_luns_changed)
++{
++ int res = 0;
++ struct scst_acg_dev *acg_dev = NULL, *a;
++ struct scst_tgt_dev *tgt_dev, *tt;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(a, &acg->acg_dev_list, acg_dev_list_entry) {
++ if (a->lun == lun) {
++ acg_dev = a;
++ break;
++ }
++ }
++ if (acg_dev == NULL) {
++ PRINT_ERROR("Device is not found in group %s", acg->acg_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ list_for_each_entry_safe(tgt_dev, tt, &acg_dev->dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if (tgt_dev->acg_dev == acg_dev)
++ scst_free_tgt_dev(tgt_dev);
++ }
++
++ scst_del_free_acg_dev(acg_dev, true);
++
++ if (gen_scst_report_luns_changed)
++ scst_report_luns_changed(acg);
++
++ PRINT_INFO("Removed LUN %lld from group %s", (unsigned long long)lun,
++ acg->acg_name);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++struct scst_acg *scst_alloc_add_acg(struct scst_tgt *tgt,
++ const char *acg_name, bool tgt_acg)
++{
++ struct scst_acg *acg;
++
++ TRACE_ENTRY();
++
++ acg = kzalloc(sizeof(*acg), GFP_KERNEL);
++ if (acg == NULL) {
++ PRINT_ERROR("%s", "Allocation of acg failed");
++ goto out;
++ }
++
++ acg->tgt = tgt;
++ INIT_LIST_HEAD(&acg->acg_dev_list);
++ INIT_LIST_HEAD(&acg->acg_sess_list);
++ INIT_LIST_HEAD(&acg->acn_list);
++ cpumask_copy(&acg->acg_cpu_mask, &default_cpu_mask);
++ acg->acg_name = kstrdup(acg_name, GFP_KERNEL);
++ if (acg->acg_name == NULL) {
++ PRINT_ERROR("%s", "Allocation of acg_name failed");
++ goto out_free;
++ }
++
++ acg->addr_method = tgt->tgtt->preferred_addr_method;
++
++ if (tgt_acg) {
++ int rc;
++
++ TRACE_DBG("Adding acg '%s' to device '%s' acg_list", acg_name,
++ tgt->tgt_name);
++ list_add_tail(&acg->acg_list_entry, &tgt->tgt_acg_list);
++ acg->tgt_acg = 1;
++
++ rc = scst_acg_sysfs_create(tgt, acg);
++ if (rc != 0)
++ goto out_del;
++ }
++
++out:
++ TRACE_EXIT_HRES(acg);
++ return acg;
++
++out_del:
++ list_del(&acg->acg_list_entry);
++
++out_free:
++ kfree(acg);
++ acg = NULL;
++ goto out;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++void scst_del_free_acg(struct scst_acg *acg)
++{
++ struct scst_acn *acn, *acnt;
++ struct scst_acg_dev *acg_dev, *acg_dev_tmp;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Clearing acg %s from list", acg->acg_name);
++
++ BUG_ON(!list_empty(&acg->acg_sess_list));
++
++ /* Freeing acg_devs */
++ list_for_each_entry_safe(acg_dev, acg_dev_tmp, &acg->acg_dev_list,
++ acg_dev_list_entry) {
++ struct scst_tgt_dev *tgt_dev, *tt;
++ list_for_each_entry_safe(tgt_dev, tt,
++ &acg_dev->dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if (tgt_dev->acg_dev == acg_dev)
++ scst_free_tgt_dev(tgt_dev);
++ }
++ scst_del_free_acg_dev(acg_dev, true);
++ }
++
++ /* Freeing names */
++ list_for_each_entry_safe(acn, acnt, &acg->acn_list, acn_list_entry) {
++ scst_del_free_acn(acn,
++ list_is_last(&acn->acn_list_entry, &acg->acn_list));
++ }
++ INIT_LIST_HEAD(&acg->acn_list);
++
++ if (acg->tgt_acg) {
++ TRACE_DBG("Removing acg %s from list", acg->acg_name);
++ list_del(&acg->acg_list_entry);
++
++ scst_acg_sysfs_del(acg);
++ } else
++ acg->tgt->default_acg = NULL;
++
++ BUG_ON(!list_empty(&acg->acg_sess_list));
++ BUG_ON(!list_empty(&acg->acg_dev_list));
++ BUG_ON(!list_empty(&acg->acn_list));
++
++ kfree(acg->acg_name);
++ kfree(acg);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++struct scst_acg *scst_tgt_find_acg(struct scst_tgt *tgt, const char *name)
++{
++ struct scst_acg *acg, *acg_ret = NULL;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(acg, &tgt->tgt_acg_list, acg_list_entry) {
++ if (strcmp(acg->acg_name, name) == 0) {
++ acg_ret = acg;
++ break;
++ }
++ }
++
++ TRACE_EXIT();
++ return acg_ret;
++}
++
++/* scst_mutex supposed to be held */
++static struct scst_tgt_dev *scst_find_shared_io_tgt_dev(
++ struct scst_tgt_dev *tgt_dev)
++{
++ struct scst_tgt_dev *res = NULL;
++ struct scst_acg *acg = tgt_dev->acg_dev->acg;
++ struct scst_tgt_dev *t;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("tgt_dev %s (acg %p, io_grouping_type %d)",
++ tgt_dev->sess->initiator_name, acg, acg->acg_io_grouping_type);
++
++ switch (acg->acg_io_grouping_type) {
++ case SCST_IO_GROUPING_AUTO:
++ if (tgt_dev->sess->initiator_name == NULL)
++ goto out;
++
++ list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if ((t == tgt_dev) ||
++ (t->sess->initiator_name == NULL) ||
++ (t->active_cmd_threads == NULL))
++ continue;
++
++ TRACE_DBG("t %s", t->sess->initiator_name);
++
++ /* We check other ACG's as well */
++
++ if (strcmp(t->sess->initiator_name,
++ tgt_dev->sess->initiator_name) == 0)
++ goto found;
++ }
++ break;
++
++ case SCST_IO_GROUPING_THIS_GROUP_ONLY:
++ list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if ((t == tgt_dev) || (t->active_cmd_threads == NULL))
++ continue;
++
++ TRACE_DBG("t %s (acg %p)", t->sess->initiator_name,
++ t->acg_dev->acg);
++
++ if (t->acg_dev->acg == acg)
++ goto found;
++ }
++ break;
++
++ case SCST_IO_GROUPING_NEVER:
++ goto out;
++
++ default:
++ list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if ((t == tgt_dev) || (t->active_cmd_threads == NULL))
++ continue;
++
++ TRACE_DBG("t %s (acg %p, io_grouping_type %d)",
++ t->sess->initiator_name, t->acg_dev->acg,
++ t->acg_dev->acg->acg_io_grouping_type);
++
++ if (t->acg_dev->acg->acg_io_grouping_type ==
++ acg->acg_io_grouping_type)
++ goto found;
++ }
++ break;
++ }
++
++out:
++ TRACE_EXIT_HRES((unsigned long)res);
++ return res;
++
++found:
++ if (t->active_cmd_threads == &scst_main_cmd_threads) {
++ res = t;
++ TRACE_MGMT_DBG("Going to share async IO context %p (res %p, "
++ "ini %s, dev %s, grouping type %d)",
++ t->aic_keeper->aic, res, t->sess->initiator_name,
++ t->dev->virt_name,
++ t->acg_dev->acg->acg_io_grouping_type);
++ } else {
++ res = t;
++ if (!*(volatile bool*)&res->active_cmd_threads->io_context_ready) {
++ TRACE_MGMT_DBG("IO context for t %p not yet "
++ "initialized, waiting...", t);
++ msleep(100);
++ goto found;
++ }
++ smp_rmb();
++ TRACE_MGMT_DBG("Going to share IO context %p (res %p, ini %s, "
++ "dev %s, cmd_threads %p, grouping type %d)",
++ res->active_cmd_threads->io_context, res,
++ t->sess->initiator_name, t->dev->virt_name,
++ t->active_cmd_threads,
++ t->acg_dev->acg->acg_io_grouping_type);
++ }
++ goto out;
++}
++
++enum scst_dev_type_threads_pool_type scst_parse_threads_pool_type(const char *p,
++ int len)
++{
++ enum scst_dev_type_threads_pool_type res;
++
++ if (strncasecmp(p, SCST_THREADS_POOL_PER_INITIATOR_STR,
++ min_t(int, strlen(SCST_THREADS_POOL_PER_INITIATOR_STR),
++ len)) == 0)
++ res = SCST_THREADS_POOL_PER_INITIATOR;
++ else if (strncasecmp(p, SCST_THREADS_POOL_SHARED_STR,
++ min_t(int, strlen(SCST_THREADS_POOL_SHARED_STR),
++ len)) == 0)
++ res = SCST_THREADS_POOL_SHARED;
++ else {
++ PRINT_ERROR("Unknown threads pool type %s", p);
++ res = SCST_THREADS_POOL_TYPE_INVALID;
++ }
++
++ return res;
++}
++
++static int scst_ioc_keeper_thread(void *arg)
++{
++ struct scst_async_io_context_keeper *aic_keeper =
++ (struct scst_async_io_context_keeper *)arg;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("AIC %p keeper thread %s (PID %d) started", aic_keeper,
++ current->comm, current->pid);
++
++ current->flags |= PF_NOFREEZE;
++
++ BUG_ON(aic_keeper->aic != NULL);
++
++ aic_keeper->aic = get_io_context(GFP_KERNEL, -1);
++ TRACE_MGMT_DBG("Alloced new async IO context %p (aic %p)",
++ aic_keeper->aic, aic_keeper);
++
++ /* We have our own ref counting */
++ put_io_context(aic_keeper->aic);
++
++ /* We are ready */
++ aic_keeper->aic_ready = true;
++ wake_up_all(&aic_keeper->aic_keeper_waitQ);
++
++ wait_event_interruptible(aic_keeper->aic_keeper_waitQ,
++ kthread_should_stop());
++
++ TRACE_MGMT_DBG("AIC %p keeper thread %s (PID %d) finished", aic_keeper,
++ current->comm, current->pid);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++/* scst_mutex supposed to be held */
++int scst_tgt_dev_setup_threads(struct scst_tgt_dev *tgt_dev)
++{
++ int res = 0;
++ struct scst_device *dev = tgt_dev->dev;
++ struct scst_async_io_context_keeper *aic_keeper;
++
++ TRACE_ENTRY();
++
++ if (dev->threads_num < 0)
++ goto out;
++
++ if (dev->threads_num == 0) {
++ struct scst_tgt_dev *shared_io_tgt_dev;
++ tgt_dev->active_cmd_threads = &scst_main_cmd_threads;
++
++ shared_io_tgt_dev = scst_find_shared_io_tgt_dev(tgt_dev);
++ if (shared_io_tgt_dev != NULL) {
++ aic_keeper = shared_io_tgt_dev->aic_keeper;
++ kref_get(&aic_keeper->aic_keeper_kref);
++
++ TRACE_MGMT_DBG("Linking async io context %p "
++ "for shared tgt_dev %p (dev %s)",
++ aic_keeper->aic, tgt_dev,
++ tgt_dev->dev->virt_name);
++ } else {
++ /* Create new context */
++ aic_keeper = kzalloc(sizeof(*aic_keeper), GFP_KERNEL);
++ if (aic_keeper == NULL) {
++ PRINT_ERROR("Unable to alloc aic_keeper "
++ "(size %zd)", sizeof(*aic_keeper));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ kref_init(&aic_keeper->aic_keeper_kref);
++ init_waitqueue_head(&aic_keeper->aic_keeper_waitQ);
++
++ aic_keeper->aic_keeper_thr =
++ kthread_run(scst_ioc_keeper_thread,
++ aic_keeper, "aic_keeper");
++ if (IS_ERR(aic_keeper->aic_keeper_thr)) {
++ PRINT_ERROR("Error running ioc_keeper "
++ "thread (tgt_dev %p)", tgt_dev);
++ res = PTR_ERR(aic_keeper->aic_keeper_thr);
++ goto out_free_keeper;
++ }
++
++ wait_event(aic_keeper->aic_keeper_waitQ,
++ aic_keeper->aic_ready);
++
++ TRACE_MGMT_DBG("Created async io context %p "
++ "for not shared tgt_dev %p (dev %s)",
++ aic_keeper->aic, tgt_dev,
++ tgt_dev->dev->virt_name);
++ }
++
++ tgt_dev->async_io_context = aic_keeper->aic;
++ tgt_dev->aic_keeper = aic_keeper;
++
++ res = scst_add_threads(tgt_dev->active_cmd_threads, NULL, NULL,
++ tgt_dev->sess->tgt->tgtt->threads_num);
++ goto out;
++ }
++
++ switch (dev->threads_pool_type) {
++ case SCST_THREADS_POOL_PER_INITIATOR:
++ {
++ struct scst_tgt_dev *shared_io_tgt_dev;
++
++ scst_init_threads(&tgt_dev->tgt_dev_cmd_threads);
++
++ tgt_dev->active_cmd_threads = &tgt_dev->tgt_dev_cmd_threads;
++
++ shared_io_tgt_dev = scst_find_shared_io_tgt_dev(tgt_dev);
++ if (shared_io_tgt_dev != NULL) {
++ TRACE_MGMT_DBG("Linking io context %p for "
++ "shared tgt_dev %p (cmd_threads %p)",
++ shared_io_tgt_dev->active_cmd_threads->io_context,
++ tgt_dev, tgt_dev->active_cmd_threads);
++ /* It's ref counted via threads */
++ tgt_dev->active_cmd_threads->io_context =
++ shared_io_tgt_dev->active_cmd_threads->io_context;
++ }
++
++ res = scst_add_threads(tgt_dev->active_cmd_threads, NULL,
++ tgt_dev,
++ dev->threads_num + tgt_dev->sess->tgt->tgtt->threads_num);
++ if (res != 0) {
++ /* Let's clear here, because no threads could be run */
++ tgt_dev->active_cmd_threads->io_context = NULL;
++ }
++ break;
++ }
++ case SCST_THREADS_POOL_SHARED:
++ {
++ tgt_dev->active_cmd_threads = &dev->dev_cmd_threads;
++
++ res = scst_add_threads(tgt_dev->active_cmd_threads, dev, NULL,
++ tgt_dev->sess->tgt->tgtt->threads_num);
++ break;
++ }
++ default:
++ PRINT_CRIT_ERROR("Unknown threads pool type %d (dev %s)",
++ dev->threads_pool_type, dev->virt_name);
++ BUG();
++ break;
++ }
++
++out:
++ if (res == 0)
++ tm_dbg_init_tgt_dev(tgt_dev);
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free_keeper:
++ kfree(aic_keeper);
++ goto out;
++}
++
++static void scst_aic_keeper_release(struct kref *kref)
++{
++ struct scst_async_io_context_keeper *aic_keeper;
++
++ TRACE_ENTRY();
++
++ aic_keeper = container_of(kref, struct scst_async_io_context_keeper,
++ aic_keeper_kref);
++
++ kthread_stop(aic_keeper->aic_keeper_thr);
++
++ kfree(aic_keeper);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* scst_mutex supposed to be held */
++void scst_tgt_dev_stop_threads(struct scst_tgt_dev *tgt_dev)
++{
++ TRACE_ENTRY();
++
++ if (tgt_dev->dev->threads_num < 0)
++ goto out_deinit;
++
++ if (tgt_dev->active_cmd_threads == &scst_main_cmd_threads) {
++ /* Global async threads */
++ kref_put(&tgt_dev->aic_keeper->aic_keeper_kref,
++ scst_aic_keeper_release);
++ tgt_dev->async_io_context = NULL;
++ tgt_dev->aic_keeper = NULL;
++ } else if (tgt_dev->active_cmd_threads == &tgt_dev->dev->dev_cmd_threads) {
++ /* Per device shared threads */
++ scst_del_threads(tgt_dev->active_cmd_threads,
++ tgt_dev->sess->tgt->tgtt->threads_num);
++ } else if (tgt_dev->active_cmd_threads == &tgt_dev->tgt_dev_cmd_threads) {
++ /* Per tgt_dev threads */
++ scst_del_threads(tgt_dev->active_cmd_threads, -1);
++ scst_deinit_threads(&tgt_dev->tgt_dev_cmd_threads);
++ } /* else no threads (not yet initialized, e.g.) */
++
++out_deinit:
++ tm_dbg_deinit_tgt_dev(tgt_dev);
++ tgt_dev->active_cmd_threads = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * scst_mutex supposed to be held, there must not be parallel activity in this
++ * session.
++ */
++static int scst_alloc_add_tgt_dev(struct scst_session *sess,
++ struct scst_acg_dev *acg_dev, struct scst_tgt_dev **out_tgt_dev)
++{
++ int res = 0;
++ int ini_sg, ini_unchecked_isa_dma, ini_use_clustering;
++ struct scst_tgt_dev *tgt_dev;
++ struct scst_device *dev = acg_dev->dev;
++ struct list_head *head;
++ int sl;
++ uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
++
++ TRACE_ENTRY();
++
++ tgt_dev = kmem_cache_zalloc(scst_tgtd_cachep, GFP_KERNEL);
++ if (tgt_dev == NULL) {
++ PRINT_ERROR("%s", "Allocation of scst_tgt_dev failed");
++ res = -ENOMEM;
++ goto out;
++ }
++
++ tgt_dev->dev = dev;
++ tgt_dev->lun = acg_dev->lun;
++ tgt_dev->acg_dev = acg_dev;
++ tgt_dev->sess = sess;
++ atomic_set(&tgt_dev->tgt_dev_cmd_count, 0);
++
++ scst_sgv_pool_use_norm(tgt_dev);
++
++ if (dev->scsi_dev != NULL) {
++ ini_sg = dev->scsi_dev->host->sg_tablesize;
++ ini_unchecked_isa_dma = dev->scsi_dev->host->unchecked_isa_dma;
++ ini_use_clustering = (dev->scsi_dev->host->use_clustering ==
++ ENABLE_CLUSTERING);
++ } else {
++ ini_sg = (1 << 15) /* infinite */;
++ ini_unchecked_isa_dma = 0;
++ ini_use_clustering = 0;
++ }
++ tgt_dev->max_sg_cnt = min(ini_sg, sess->tgt->sg_tablesize);
++
++ if ((sess->tgt->tgtt->use_clustering || ini_use_clustering) &&
++ !sess->tgt->tgtt->no_clustering)
++ scst_sgv_pool_use_norm_clust(tgt_dev);
++
++ if (sess->tgt->tgtt->unchecked_isa_dma || ini_unchecked_isa_dma)
++ scst_sgv_pool_use_dma(tgt_dev);
++
++ TRACE_MGMT_DBG("Device %s on SCST lun=%lld",
++ dev->virt_name, (long long unsigned int)tgt_dev->lun);
++
++ spin_lock_init(&tgt_dev->tgt_dev_lock);
++ INIT_LIST_HEAD(&tgt_dev->UA_list);
++ spin_lock_init(&tgt_dev->thr_data_lock);
++ INIT_LIST_HEAD(&tgt_dev->thr_data_list);
++
++ scst_init_order_data(&tgt_dev->tgt_dev_order_data);
++ if (dev->tst == SCST_CONTR_MODE_SEP_TASK_SETS)
++ tgt_dev->curr_order_data = &tgt_dev->tgt_dev_order_data;
++ else
++ tgt_dev->curr_order_data = &dev->dev_order_data;
++
++ if (dev->handler->parse_atomic &&
++ dev->handler->alloc_data_buf_atomic &&
++ (sess->tgt->tgtt->preprocessing_done == NULL)) {
++ if (sess->tgt->tgtt->rdy_to_xfer_atomic)
++ __set_bit(SCST_TGT_DEV_AFTER_INIT_WR_ATOMIC,
++ &tgt_dev->tgt_dev_flags);
++ }
++ if (dev->handler->dev_done_atomic &&
++ sess->tgt->tgtt->xmit_response_atomic) {
++ __set_bit(SCST_TGT_DEV_AFTER_EXEC_ATOMIC,
++ &tgt_dev->tgt_dev_flags);
++ }
++
++ sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
++ dev->d_sense, SCST_LOAD_SENSE(scst_sense_reset_UA));
++ scst_alloc_set_UA(tgt_dev, sense_buffer, sl, 0);
++
++ if (sess->tgt->tgtt->get_initiator_port_transport_id == NULL) {
++ if (!list_empty(&dev->dev_registrants_list)) {
++ PRINT_WARNING("Initiators from target %s can't connect "
++ "to device %s, because the device has PR "
++ "registrants and the target doesn't support "
++ "Persistent Reservations", sess->tgt->tgtt->name,
++ dev->virt_name);
++ res = -EPERM;
++ goto out_free;
++ }
++ dev->not_pr_supporting_tgt_devs_num++;
++ }
++
++ res = scst_pr_init_tgt_dev(tgt_dev);
++ if (res != 0)
++ goto out_dec_free;
++
++ res = scst_tgt_dev_setup_threads(tgt_dev);
++ if (res != 0)
++ goto out_pr_clear;
++
++ if (dev->handler && dev->handler->attach_tgt) {
++ TRACE_DBG("Calling dev handler's attach_tgt(%p)", tgt_dev);
++ res = dev->handler->attach_tgt(tgt_dev);
++ TRACE_DBG("%s", "Dev handler's attach_tgt() returned");
++ if (res != 0) {
++ PRINT_ERROR("Device handler's %s attach_tgt() "
++ "failed: %d", dev->handler->name, res);
++ goto out_stop_threads;
++ }
++ }
++
++ res = scst_tgt_dev_sysfs_create(tgt_dev);
++ if (res != 0)
++ goto out_detach;
++
++ spin_lock_bh(&dev->dev_lock);
++ list_add_tail(&tgt_dev->dev_tgt_dev_list_entry, &dev->dev_tgt_dev_list);
++ if (dev->dev_reserved)
++ __set_bit(SCST_TGT_DEV_RESERVED, &tgt_dev->tgt_dev_flags);
++ spin_unlock_bh(&dev->dev_lock);
++
++ head = &sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(tgt_dev->lun)];
++ list_add_tail(&tgt_dev->sess_tgt_dev_list_entry, head);
++
++ *out_tgt_dev = tgt_dev;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_detach:
++ if (dev->handler && dev->handler->detach_tgt) {
++ TRACE_DBG("Calling dev handler's detach_tgt(%p)",
++ tgt_dev);
++ dev->handler->detach_tgt(tgt_dev);
++ TRACE_DBG("%s", "Dev handler's detach_tgt() returned");
++ }
++
++out_stop_threads:
++ scst_tgt_dev_stop_threads(tgt_dev);
++
++out_pr_clear:
++ scst_pr_clear_tgt_dev(tgt_dev);
++
++out_dec_free:
++ if (tgt_dev->sess->tgt->tgtt->get_initiator_port_transport_id == NULL)
++ dev->not_pr_supporting_tgt_devs_num--;
++
++out_free:
++ scst_free_all_UA(tgt_dev);
++ kmem_cache_free(scst_tgtd_cachep, tgt_dev);
++ goto out;
++}
++
++/* No locks supposed to be held, scst_mutex - held */
++void scst_nexus_loss(struct scst_tgt_dev *tgt_dev, bool queue_UA)
++{
++ TRACE_ENTRY();
++
++ scst_clear_reservation(tgt_dev);
++
++#if 0 /* Clearing UAs and last sense isn't required by SAM and it looks to be
++ * better to not clear them to not loose important events, so let's
++ * disable it.
++ */
++ /* With activity suspended the lock isn't needed, but let's be safe */
++ spin_lock_bh(&tgt_dev->tgt_dev_lock);
++ scst_free_all_UA(tgt_dev);
++ memset(tgt_dev->tgt_dev_sense, 0, sizeof(tgt_dev->tgt_dev_sense));
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++#endif
++
++ if (queue_UA) {
++ uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
++ int sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
++ tgt_dev->dev->d_sense,
++ SCST_LOAD_SENSE(scst_sense_nexus_loss_UA));
++ scst_check_set_UA(tgt_dev, sense_buffer, sl, 0);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * scst_mutex supposed to be held, there must not be parallel activity in this
++ * session.
++ */
++static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev)
++{
++ struct scst_device *dev = tgt_dev->dev;
++
++ TRACE_ENTRY();
++
++ spin_lock_bh(&dev->dev_lock);
++ list_del(&tgt_dev->dev_tgt_dev_list_entry);
++ spin_unlock_bh(&dev->dev_lock);
++
++ list_del(&tgt_dev->sess_tgt_dev_list_entry);
++
++ scst_tgt_dev_sysfs_del(tgt_dev);
++
++ if (tgt_dev->sess->tgt->tgtt->get_initiator_port_transport_id == NULL)
++ dev->not_pr_supporting_tgt_devs_num--;
++
++ scst_clear_reservation(tgt_dev);
++ scst_pr_clear_tgt_dev(tgt_dev);
++ scst_free_all_UA(tgt_dev);
++
++ if (dev->handler && dev->handler->detach_tgt) {
++ TRACE_DBG("Calling dev handler's detach_tgt(%p)",
++ tgt_dev);
++ dev->handler->detach_tgt(tgt_dev);
++ TRACE_DBG("%s", "Dev handler's detach_tgt() returned");
++ }
++
++ scst_tgt_dev_stop_threads(tgt_dev);
++
++ BUG_ON(!list_empty(&tgt_dev->thr_data_list));
++
++ kmem_cache_free(scst_tgtd_cachep, tgt_dev);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* scst_mutex supposed to be held */
++int scst_sess_alloc_tgt_devs(struct scst_session *sess)
++{
++ int res = 0;
++ struct scst_acg_dev *acg_dev;
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(acg_dev, &sess->acg->acg_dev_list,
++ acg_dev_list_entry) {
++ res = scst_alloc_add_tgt_dev(sess, acg_dev, &tgt_dev);
++ if (res == -EPERM)
++ continue;
++ else if (res != 0)
++ goto out_free;
++ }
++
++out:
++ TRACE_EXIT();
++ return res;
++
++out_free:
++ scst_sess_free_tgt_devs(sess);
++ goto out;
++}
++
++/*
++ * scst_mutex supposed to be held, there must not be parallel activity in this
++ * session.
++ */
++void scst_sess_free_tgt_devs(struct scst_session *sess)
++{
++ int i;
++ struct scst_tgt_dev *tgt_dev, *t;
++
++ TRACE_ENTRY();
++
++ /* The session is going down, no users, so no locks */
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ list_for_each_entry_safe(tgt_dev, t, head,
++ sess_tgt_dev_list_entry) {
++ scst_free_tgt_dev(tgt_dev);
++ }
++ INIT_LIST_HEAD(head);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++int scst_acg_add_acn(struct scst_acg *acg, const char *name)
++{
++ int res = 0;
++ struct scst_acn *acn;
++ char *nm;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(acn, &acg->acn_list, acn_list_entry) {
++ if (strcmp(acn->name, name) == 0) {
++ PRINT_ERROR("Name %s already exists in group %s",
++ name, acg->acg_name);
++ res = -EEXIST;
++ goto out;
++ }
++ }
++
++ acn = kzalloc(sizeof(*acn), GFP_KERNEL);
++ if (acn == NULL) {
++ PRINT_ERROR("%s", "Unable to allocate scst_acn");
++ res = -ENOMEM;
++ goto out;
++ }
++
++ acn->acg = acg;
++
++ nm = kstrdup(name, GFP_KERNEL);
++ if (nm == NULL) {
++ PRINT_ERROR("%s", "Unable to allocate scst_acn->name");
++ res = -ENOMEM;
++ goto out_free;
++ }
++ acn->name = nm;
++
++ res = scst_acn_sysfs_create(acn);
++ if (res != 0)
++ goto out_free_nm;
++
++ list_add_tail(&acn->acn_list_entry, &acg->acn_list);
++
++out:
++ if (res == 0) {
++ PRINT_INFO("Added name %s to group %s", name, acg->acg_name);
++ scst_check_reassign_sessions();
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free_nm:
++ kfree(nm);
++
++out_free:
++ kfree(acn);
++ goto out;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++void scst_del_free_acn(struct scst_acn *acn, bool reassign)
++{
++ TRACE_ENTRY();
++
++ list_del(&acn->acn_list_entry);
++
++ scst_acn_sysfs_del(acn);
++
++ kfree(acn->name);
++ kfree(acn);
++
++ if (reassign)
++ scst_check_reassign_sessions();
++
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++struct scst_acn *scst_find_acn(struct scst_acg *acg, const char *name)
++{
++ struct scst_acn *acn;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Trying to find name '%s'", name);
++
++ list_for_each_entry(acn, &acg->acn_list, acn_list_entry) {
++ if (strcmp(acn->name, name) == 0) {
++ TRACE_DBG("%s", "Found");
++ goto out;
++ }
++ }
++ acn = NULL;
++out:
++ TRACE_EXIT();
++ return acn;
++}
++
++static struct scst_cmd *scst_create_prepare_internal_cmd(
++ struct scst_cmd *orig_cmd, const uint8_t *cdb,
++ unsigned int cdb_len, int bufsize)
++{
++ struct scst_cmd *res;
++ int rc;
++ gfp_t gfp_mask = scst_cmd_atomic(orig_cmd) ? GFP_ATOMIC : GFP_KERNEL;
++
++ TRACE_ENTRY();
++
++ res = scst_alloc_cmd(cdb, cdb_len, gfp_mask);
++ if (res == NULL)
++ goto out;
++
++ res->cmd_threads = orig_cmd->cmd_threads;
++ res->sess = orig_cmd->sess;
++ res->atomic = scst_cmd_atomic(orig_cmd);
++ res->internal = 1;
++ res->tgtt = orig_cmd->tgtt;
++ res->tgt = orig_cmd->tgt;
++ res->dev = orig_cmd->dev;
++ res->tgt_dev = orig_cmd->tgt_dev;
++ res->cur_order_data = orig_cmd->tgt_dev->curr_order_data;
++ res->lun = orig_cmd->lun;
++ res->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
++ res->data_direction = SCST_DATA_UNKNOWN;
++ res->orig_cmd = orig_cmd;
++ res->bufflen = bufsize;
++
++ scst_sess_get(res->sess);
++ if (res->tgt_dev != NULL)
++ res->cpu_cmd_counter = scst_get();
++
++ rc = scst_pre_parse(res);
++ BUG_ON(rc != 0);
++
++ res->state = SCST_CMD_STATE_PARSE;
++
++out:
++ TRACE_EXIT_HRES((unsigned long)res);
++ return res;
++}
++
++int scst_prepare_request_sense(struct scst_cmd *orig_cmd)
++{
++ int res = 0;
++ static const uint8_t request_sense[6] = {
++ REQUEST_SENSE, 0, 0, 0, SCST_SENSE_BUFFERSIZE, 0
++ };
++ struct scst_cmd *rs_cmd;
++
++ TRACE_ENTRY();
++
++ if (orig_cmd->sense != NULL) {
++ TRACE_MEM("Releasing sense %p (orig_cmd %p)",
++ orig_cmd->sense, orig_cmd);
++ mempool_free(orig_cmd->sense, scst_sense_mempool);
++ orig_cmd->sense = NULL;
++ }
++
++ rs_cmd = scst_create_prepare_internal_cmd(orig_cmd,
++ request_sense, sizeof(request_sense),
++ SCST_SENSE_BUFFERSIZE);
++ if (rs_cmd == NULL)
++ goto out_error;
++
++ rs_cmd->cdb[1] |= scst_get_cmd_dev_d_sense(orig_cmd);
++ rs_cmd->data_direction = SCST_DATA_READ;
++ rs_cmd->expected_data_direction = rs_cmd->data_direction;
++ rs_cmd->expected_transfer_len = SCST_SENSE_BUFFERSIZE;
++ rs_cmd->expected_values_set = 1;
++
++ TRACE_MGMT_DBG("Adding REQUEST SENSE cmd %p to head of active "
++ "cmd list", rs_cmd);
++ spin_lock_irq(&rs_cmd->cmd_threads->cmd_list_lock);
++ list_add(&rs_cmd->cmd_list_entry, &rs_cmd->cmd_threads->active_cmd_list);
++ wake_up(&rs_cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock_irq(&rs_cmd->cmd_threads->cmd_list_lock);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_error:
++ res = -1;
++ goto out;
++}
++
++static void scst_complete_request_sense(struct scst_cmd *req_cmd)
++{
++ struct scst_cmd *orig_cmd = req_cmd->orig_cmd;
++ uint8_t *buf;
++ int len;
++
++ TRACE_ENTRY();
++
++ BUG_ON(orig_cmd == NULL);
++
++ len = scst_get_buf_full(req_cmd, &buf);
++
++ if (scsi_status_is_good(req_cmd->status) && (len > 0) &&
++ SCST_SENSE_VALID(buf) && (!SCST_NO_SENSE(buf))) {
++ PRINT_BUFF_FLAG(TRACE_SCSI, "REQUEST SENSE returned",
++ buf, len);
++ scst_alloc_set_sense(orig_cmd, scst_cmd_atomic(req_cmd), buf,
++ len);
++ } else {
++ PRINT_ERROR("%s", "Unable to get the sense via "
++ "REQUEST SENSE, returning HARDWARE ERROR");
++ scst_set_cmd_error(orig_cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++
++ if (len > 0)
++ scst_put_buf_full(req_cmd, buf);
++
++ TRACE_MGMT_DBG("Adding orig cmd %p to head of active "
++ "cmd list", orig_cmd);
++ spin_lock_irq(&orig_cmd->cmd_threads->cmd_list_lock);
++ list_add(&orig_cmd->cmd_list_entry, &orig_cmd->cmd_threads->active_cmd_list);
++ wake_up(&orig_cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock_irq(&orig_cmd->cmd_threads->cmd_list_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++int scst_finish_internal_cmd(struct scst_cmd *cmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ BUG_ON(!cmd->internal);
++
++ if (cmd->cdb[0] == REQUEST_SENSE)
++ scst_complete_request_sense(cmd);
++
++ __scst_cmd_put(cmd);
++
++ res = SCST_CMD_STATE_RES_CONT_NEXT;
++
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++static void scst_send_release(struct scst_device *dev)
++{
++ struct scsi_device *scsi_dev;
++ unsigned char cdb[6];
++ uint8_t sense[SCSI_SENSE_BUFFERSIZE];
++ int rc, i;
++
++ TRACE_ENTRY();
++
++ if (dev->scsi_dev == NULL)
++ goto out;
++
++ scsi_dev = dev->scsi_dev;
++
++ for (i = 0; i < 5; i++) {
++ memset(cdb, 0, sizeof(cdb));
++ cdb[0] = RELEASE;
++ cdb[1] = (scsi_dev->scsi_level <= SCSI_2) ?
++ ((scsi_dev->lun << 5) & 0xe0) : 0;
++
++ memset(sense, 0, sizeof(sense));
++
++ TRACE(TRACE_DEBUG | TRACE_SCSI, "%s", "Sending RELEASE req to "
++ "SCSI mid-level");
++ rc = scsi_execute(scsi_dev, cdb, SCST_DATA_NONE, NULL, 0,
++ sense, 15, 0, 0
++ , NULL
++ );
++ TRACE_DBG("MODE_SENSE done: %x", rc);
++
++ if (scsi_status_is_good(rc)) {
++ break;
++ } else {
++ PRINT_ERROR("RELEASE failed: %d", rc);
++ PRINT_BUFFER("RELEASE sense", sense, sizeof(sense));
++ scst_check_internal_sense(dev, rc, sense,
++ sizeof(sense));
++ }
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* scst_mutex supposed to be held */
++static void scst_clear_reservation(struct scst_tgt_dev *tgt_dev)
++{
++ struct scst_device *dev = tgt_dev->dev;
++ int release = 0;
++
++ TRACE_ENTRY();
++
++ spin_lock_bh(&dev->dev_lock);
++ if (dev->dev_reserved &&
++ !test_bit(SCST_TGT_DEV_RESERVED, &tgt_dev->tgt_dev_flags)) {
++ /* This is one who holds the reservation */
++ struct scst_tgt_dev *tgt_dev_tmp;
++ list_for_each_entry(tgt_dev_tmp, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ clear_bit(SCST_TGT_DEV_RESERVED,
++ &tgt_dev_tmp->tgt_dev_flags);
++ }
++ dev->dev_reserved = 0;
++ release = 1;
++ }
++ spin_unlock_bh(&dev->dev_lock);
++
++ if (release)
++ scst_send_release(dev);
++
++ TRACE_EXIT();
++ return;
++}
++
++struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask,
++ const char *initiator_name)
++{
++ struct scst_session *sess;
++ int i;
++
++ TRACE_ENTRY();
++
++ sess = kmem_cache_zalloc(scst_sess_cachep, gfp_mask);
++ if (sess == NULL) {
++ PRINT_ERROR("%s", "Allocation of scst_session failed");
++ goto out;
++ }
++
++ sess->init_phase = SCST_SESS_IPH_INITING;
++ sess->shut_phase = SCST_SESS_SPH_READY;
++ atomic_set(&sess->refcnt, 0);
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ INIT_LIST_HEAD(head);
++ }
++ spin_lock_init(&sess->sess_list_lock);
++ INIT_LIST_HEAD(&sess->sess_cmd_list);
++ sess->tgt = tgt;
++ INIT_LIST_HEAD(&sess->init_deferred_cmd_list);
++ INIT_LIST_HEAD(&sess->init_deferred_mcmd_list);
++ INIT_DELAYED_WORK(&sess->hw_pending_work,
++ (void (*)(struct work_struct *))scst_hw_pending_work_fn);
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++ spin_lock_init(&sess->lat_lock);
++#endif
++
++ sess->initiator_name = kstrdup(initiator_name, gfp_mask);
++ if (sess->initiator_name == NULL) {
++ PRINT_ERROR("%s", "Unable to dup sess->initiator_name");
++ goto out_free;
++ }
++
++out:
++ TRACE_EXIT();
++ return sess;
++
++out_free:
++ kmem_cache_free(scst_sess_cachep, sess);
++ sess = NULL;
++ goto out;
++}
++
++void scst_free_session(struct scst_session *sess)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ scst_sess_free_tgt_devs(sess);
++
++ mutex_unlock(&scst_mutex);
++
++ scst_sess_sysfs_del(sess);
++ if (sess->unreg_done_fn) {
++ TRACE_DBG("Calling unreg_done_fn(%p)", sess);
++ sess->unreg_done_fn(sess);
++ TRACE_DBG("%s", "unreg_done_fn() returned");
++ }
++
++ mutex_lock(&scst_mutex);
++
++ /*
++ * The lists delete must be after sysfs del. Otherwise it would break
++ * logic in scst_sess_sysfs_create() to avoid duplicate sysfs names.
++ */
++
++ TRACE_DBG("Removing sess %p from the list", sess);
++ list_del(&sess->sess_list_entry);
++ TRACE_DBG("Removing session %p from acg %s", sess, sess->acg->acg_name);
++ list_del(&sess->acg_sess_list_entry);
++
++ /* Called under lock to protect from too early tgt release */
++ wake_up_all(&sess->tgt->unreg_waitQ);
++
++ /*
++ * NOTE: do not dereference the sess->tgt pointer after scst_mutex
++ * has been unlocked, because it can be already dead!!
++ */
++ mutex_unlock(&scst_mutex);
++
++ kfree(sess->transport_id);
++ kfree(sess->initiator_name);
++
++ kmem_cache_free(scst_sess_cachep, sess);
++
++ TRACE_EXIT();
++ return;
++}
++
++void scst_free_session_callback(struct scst_session *sess)
++{
++ struct completion *c;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Freeing session %p", sess);
++
++ cancel_delayed_work_sync(&sess->hw_pending_work);
++
++ c = sess->shutdown_compl;
++
++ mutex_lock(&scst_mutex);
++ /*
++ * Necessary to sync with other threads trying to queue AEN, which
++ * the target driver will not be able to serve and crash, because after
++ * unreg_done_fn() called its internal session data will be destroyed.
++ */
++ sess->shut_phase = SCST_SESS_SPH_UNREG_DONE_CALLING;
++ mutex_unlock(&scst_mutex);
++
++ scst_free_session(sess);
++
++ if (c)
++ complete_all(c);
++
++ TRACE_EXIT();
++ return;
++}
++
++void scst_sched_session_free(struct scst_session *sess)
++{
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ if (sess->shut_phase != SCST_SESS_SPH_SHUTDOWN) {
++ PRINT_CRIT_ERROR("session %p is going to shutdown with unknown "
++ "shut phase %lx", sess, sess->shut_phase);
++ BUG();
++ }
++
++ spin_lock_irqsave(&scst_mgmt_lock, flags);
++ TRACE_DBG("Adding sess %p to scst_sess_shut_list", sess);
++ list_add_tail(&sess->sess_shut_list_entry, &scst_sess_shut_list);
++ spin_unlock_irqrestore(&scst_mgmt_lock, flags);
++
++ wake_up(&scst_mgmt_waitQ);
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_cmd_get() - increase command's reference counter
++ */
++void scst_cmd_get(struct scst_cmd *cmd)
++{
++ __scst_cmd_get(cmd);
++}
++EXPORT_SYMBOL(scst_cmd_get);
++
++/**
++ * scst_cmd_put() - decrease command's reference counter
++ */
++void scst_cmd_put(struct scst_cmd *cmd)
++{
++ __scst_cmd_put(cmd);
++}
++EXPORT_SYMBOL(scst_cmd_put);
++
++/**
++ * scst_cmd_set_ext_cdb() - sets cmd's extended CDB and its length
++ */
++void scst_cmd_set_ext_cdb(struct scst_cmd *cmd,
++ uint8_t *ext_cdb, unsigned int ext_cdb_len,
++ gfp_t gfp_mask)
++{
++ unsigned int len = cmd->cdb_len + ext_cdb_len;
++
++ TRACE_ENTRY();
++
++ if (len <= sizeof(cmd->cdb_buf))
++ goto copy;
++
++ if (unlikely(len > SCST_MAX_LONG_CDB_SIZE)) {
++ PRINT_ERROR("Too big CDB (%d)", len);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out;
++ }
++
++ cmd->cdb = kmalloc(len, gfp_mask);
++ if (unlikely(cmd->cdb == NULL)) {
++ PRINT_ERROR("Unable to alloc extended CDB (size %d)", len);
++ goto out_err;
++ }
++
++ memcpy(cmd->cdb, cmd->cdb_buf, cmd->cdb_len);
++
++copy:
++ memcpy(&cmd->cdb[cmd->cdb_len], ext_cdb, ext_cdb_len);
++
++ cmd->cdb_len = cmd->cdb_len + ext_cdb_len;
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_err:
++ cmd->cdb = cmd->cdb_buf;
++ scst_set_busy(cmd);
++ goto out;
++}
++EXPORT_SYMBOL(scst_cmd_set_ext_cdb);
++
++struct scst_cmd *scst_alloc_cmd(const uint8_t *cdb,
++ unsigned int cdb_len, gfp_t gfp_mask)
++{
++ struct scst_cmd *cmd;
++
++ TRACE_ENTRY();
++
++ cmd = kmem_cache_zalloc(scst_cmd_cachep, gfp_mask);
++ if (cmd == NULL) {
++ TRACE(TRACE_OUT_OF_MEM, "%s", "Allocation of scst_cmd failed");
++ goto out;
++ }
++
++ cmd->state = SCST_CMD_STATE_INIT_WAIT;
++ cmd->start_time = jiffies;
++ atomic_set(&cmd->cmd_ref, 1);
++ cmd->cmd_threads = &scst_main_cmd_threads;
++ INIT_LIST_HEAD(&cmd->mgmt_cmd_list);
++ cmd->cdb = cmd->cdb_buf;
++ cmd->queue_type = SCST_CMD_QUEUE_SIMPLE;
++ cmd->timeout = SCST_DEFAULT_TIMEOUT;
++ cmd->retries = 0;
++ cmd->data_len = -1;
++ cmd->is_send_status = 1;
++ cmd->resp_data_len = -1;
++ cmd->write_sg = &cmd->sg;
++ cmd->write_sg_cnt = &cmd->sg_cnt;
++
++ cmd->dbl_ua_orig_data_direction = SCST_DATA_UNKNOWN;
++ cmd->dbl_ua_orig_resp_data_len = -1;
++
++ if (unlikely(cdb_len == 0)) {
++ PRINT_ERROR("%s", "Wrong CDB len 0, finishing cmd");
++ goto out_free;
++ } else if (cdb_len <= SCST_MAX_CDB_SIZE) {
++ /* Duplicate memcpy to save a branch on the most common path */
++ memcpy(cmd->cdb, cdb, cdb_len);
++ } else {
++ if (unlikely(cdb_len > SCST_MAX_LONG_CDB_SIZE)) {
++ PRINT_ERROR("Too big CDB (%d), finishing cmd", cdb_len);
++ goto out_free;
++ }
++ cmd->cdb = kmalloc(cdb_len, gfp_mask);
++ if (unlikely(cmd->cdb == NULL)) {
++ PRINT_ERROR("Unable to alloc extended CDB (size %d)",
++ cdb_len);
++ goto out_free;
++ }
++ memcpy(cmd->cdb, cdb, cdb_len);
++ }
++
++ cmd->cdb_len = cdb_len;
++
++out:
++ TRACE_EXIT();
++ return cmd;
++
++out_free:
++ kmem_cache_free(scst_cmd_cachep, cmd);
++ cmd = NULL;
++ goto out;
++}
++
++static void scst_destroy_put_cmd(struct scst_cmd *cmd)
++{
++ scst_sess_put(cmd->sess);
++
++ /*
++ * At this point tgt_dev can be dead, but the pointer remains non-NULL
++ */
++ if (likely(cmd->tgt_dev != NULL))
++ scst_put(cmd->cpu_cmd_counter);
++
++ scst_destroy_cmd(cmd);
++ return;
++}
++
++/* No locks supposed to be held */
++void scst_free_cmd(struct scst_cmd *cmd)
++{
++ int destroy = 1;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Freeing cmd %p (tag %llu)",
++ cmd, (long long unsigned int)cmd->tag);
++
++ if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)))
++ TRACE_MGMT_DBG("Freeing aborted cmd %p", cmd);
++
++ EXTRACHECKS_BUG_ON(cmd->unblock_dev || cmd->dec_on_dev_needed ||
++ cmd->dec_pr_readers_count_needed);
++
++ /*
++ * Target driver can already free sg buffer before calling
++ * scst_tgt_cmd_done(). E.g., scst_local has to do that.
++ */
++ if (!cmd->tgt_data_buf_alloced)
++ scst_check_restore_sg_buff(cmd);
++
++ if ((cmd->tgtt->on_free_cmd != NULL) && likely(!cmd->internal)) {
++ TRACE_DBG("Calling target's on_free_cmd(%p)", cmd);
++ scst_set_cur_start(cmd);
++ cmd->tgtt->on_free_cmd(cmd);
++ scst_set_tgt_on_free_time(cmd);
++ TRACE_DBG("%s", "Target's on_free_cmd() returned");
++ }
++
++ if (likely(cmd->dev != NULL)) {
++ struct scst_dev_type *handler = cmd->dev->handler;
++ if (handler->on_free_cmd != NULL) {
++ TRACE_DBG("Calling dev handler %s on_free_cmd(%p)",
++ handler->name, cmd);
++ scst_set_cur_start(cmd);
++ handler->on_free_cmd(cmd);
++ scst_set_dev_on_free_time(cmd);
++ TRACE_DBG("Dev handler %s on_free_cmd() returned",
++ handler->name);
++ }
++ }
++
++ scst_release_space(cmd);
++
++ if (unlikely(cmd->sense != NULL)) {
++ TRACE_MEM("Releasing sense %p (cmd %p)", cmd->sense, cmd);
++ mempool_free(cmd->sense, scst_sense_mempool);
++ cmd->sense = NULL;
++ }
++
++ if (likely(cmd->tgt_dev != NULL)) {
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (unlikely(!cmd->sent_for_exec) && !cmd->internal) {
++ PRINT_ERROR("Finishing not executed cmd %p (opcode "
++ "%d, target %s, LUN %lld, sn %d, expected_sn %d)",
++ cmd, cmd->cdb[0], cmd->tgtt->name,
++ (long long unsigned int)cmd->lun,
++ cmd->sn, cmd->cur_order_data->expected_sn);
++ scst_unblock_deferred(cmd->cur_order_data, cmd);
++ }
++#endif
++
++ if (unlikely(cmd->out_of_sn)) {
++ TRACE_SN("Out of SN cmd %p (tag %llu, sn %d), "
++ "destroy=%d", cmd,
++ (long long unsigned int)cmd->tag,
++ cmd->sn, destroy);
++ destroy = test_and_set_bit(SCST_CMD_CAN_BE_DESTROYED,
++ &cmd->cmd_flags);
++ }
++ }
++
++ if (cmd->cdb != cmd->cdb_buf)
++ kfree(cmd->cdb);
++
++ if (likely(destroy))
++ scst_destroy_put_cmd(cmd);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* No locks supposed to be held. */
++void scst_check_retries(struct scst_tgt *tgt)
++{
++ int need_wake_up = 0;
++
++ TRACE_ENTRY();
++
++ /*
++ * We don't worry about overflow of finished_cmds, because we check
++ * only for its change.
++ */
++ atomic_inc(&tgt->finished_cmds);
++ /* See comment in scst_queue_retry_cmd() */
++ smp_mb__after_atomic_inc();
++ if (unlikely(tgt->retry_cmds > 0)) {
++ struct scst_cmd *c, *tc;
++ unsigned long flags;
++
++ TRACE_RETRY("Checking retry cmd list (retry_cmds %d)",
++ tgt->retry_cmds);
++
++ spin_lock_irqsave(&tgt->tgt_lock, flags);
++ list_for_each_entry_safe(c, tc, &tgt->retry_cmd_list,
++ cmd_list_entry) {
++ tgt->retry_cmds--;
++
++ TRACE_RETRY("Moving retry cmd %p to head of active "
++ "cmd list (retry_cmds left %d)",
++ c, tgt->retry_cmds);
++ spin_lock(&c->cmd_threads->cmd_list_lock);
++ list_move(&c->cmd_list_entry,
++ &c->cmd_threads->active_cmd_list);
++ wake_up(&c->cmd_threads->cmd_list_waitQ);
++ spin_unlock(&c->cmd_threads->cmd_list_lock);
++
++ need_wake_up++;
++ if (need_wake_up >= 2) /* "slow start" */
++ break;
++ }
++ spin_unlock_irqrestore(&tgt->tgt_lock, flags);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static void scst_tgt_retry_timer_fn(unsigned long arg)
++{
++ struct scst_tgt *tgt = (struct scst_tgt *)arg;
++ unsigned long flags;
++
++ TRACE_RETRY("Retry timer expired (retry_cmds %d)", tgt->retry_cmds);
++
++ spin_lock_irqsave(&tgt->tgt_lock, flags);
++ tgt->retry_timer_active = 0;
++ spin_unlock_irqrestore(&tgt->tgt_lock, flags);
++
++ scst_check_retries(tgt);
++
++ TRACE_EXIT();
++ return;
++}
++
++struct scst_mgmt_cmd *scst_alloc_mgmt_cmd(gfp_t gfp_mask)
++{
++ struct scst_mgmt_cmd *mcmd;
++
++ TRACE_ENTRY();
++
++ mcmd = mempool_alloc(scst_mgmt_mempool, gfp_mask);
++ if (mcmd == NULL) {
++ PRINT_CRIT_ERROR("%s", "Allocation of management command "
++ "failed, some commands and their data could leak");
++ goto out;
++ }
++ memset(mcmd, 0, sizeof(*mcmd));
++
++out:
++ TRACE_EXIT();
++ return mcmd;
++}
++
++void scst_free_mgmt_cmd(struct scst_mgmt_cmd *mcmd)
++{
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ spin_lock_irqsave(&mcmd->sess->sess_list_lock, flags);
++ atomic_dec(&mcmd->sess->sess_cmd_count);
++ spin_unlock_irqrestore(&mcmd->sess->sess_list_lock, flags);
++
++ scst_sess_put(mcmd->sess);
++
++ if (mcmd->mcmd_tgt_dev != NULL)
++ scst_put(mcmd->cpu_cmd_counter);
++
++ mempool_free(mcmd, scst_mgmt_mempool);
++
++ TRACE_EXIT();
++ return;
++}
++
++static bool scst_on_sg_tablesize_low(struct scst_cmd *cmd, bool out)
++{
++ bool res;
++ int sg_cnt = out ? cmd->out_sg_cnt : cmd->sg_cnt;
++ static int ll;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++
++ TRACE_ENTRY();
++
++ if (sg_cnt > cmd->tgt->sg_tablesize) {
++ /* It's the target's side business */
++ goto failed;
++ }
++
++ if (tgt_dev->dev->handler->on_sg_tablesize_low == NULL)
++ goto failed;
++
++ res = tgt_dev->dev->handler->on_sg_tablesize_low(cmd);
++
++ TRACE_DBG("on_sg_tablesize_low(%p) returned %d", cmd, res);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++failed:
++ res = false;
++ if ((ll < 10) || TRACING_MINOR()) {
++ PRINT_INFO("Unable to complete command due to SG IO count "
++ "limitation (%srequested %d, available %d, tgt lim %d)",
++ out ? "OUT buffer, " : "", cmd->sg_cnt,
++ tgt_dev->max_sg_cnt, cmd->tgt->sg_tablesize);
++ ll++;
++ }
++ goto out;
++}
++
++int scst_alloc_space(struct scst_cmd *cmd)
++{
++ gfp_t gfp_mask;
++ int res = -ENOMEM;
++ int atomic = scst_cmd_atomic(cmd);
++ int flags;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++
++ TRACE_ENTRY();
++
++ gfp_mask = tgt_dev->gfp_mask | (atomic ? GFP_ATOMIC : GFP_KERNEL);
++
++ flags = atomic ? SGV_POOL_NO_ALLOC_ON_CACHE_MISS : 0;
++ if (cmd->no_sgv)
++ flags |= SGV_POOL_ALLOC_NO_CACHED;
++
++ cmd->sg = sgv_pool_alloc(tgt_dev->pool, cmd->bufflen, gfp_mask, flags,
++ &cmd->sg_cnt, &cmd->sgv, &cmd->dev->dev_mem_lim, NULL);
++ if (cmd->sg == NULL)
++ goto out;
++
++ if (unlikely(cmd->sg_cnt > tgt_dev->max_sg_cnt))
++ if (!scst_on_sg_tablesize_low(cmd, false))
++ goto out_sg_free;
++
++ if (cmd->data_direction != SCST_DATA_BIDI)
++ goto success;
++
++ cmd->out_sg = sgv_pool_alloc(tgt_dev->pool, cmd->out_bufflen, gfp_mask,
++ flags, &cmd->out_sg_cnt, &cmd->out_sgv,
++ &cmd->dev->dev_mem_lim, NULL);
++ if (cmd->out_sg == NULL)
++ goto out_sg_free;
++
++ if (unlikely(cmd->out_sg_cnt > tgt_dev->max_sg_cnt))
++ if (!scst_on_sg_tablesize_low(cmd, true))
++ goto out_out_sg_free;
++
++success:
++ res = 0;
++
++out:
++ TRACE_EXIT();
++ return res;
++
++out_out_sg_free:
++ sgv_pool_free(cmd->out_sgv, &cmd->dev->dev_mem_lim);
++ cmd->out_sgv = NULL;
++ cmd->out_sg = NULL;
++ cmd->out_sg_cnt = 0;
++
++out_sg_free:
++ sgv_pool_free(cmd->sgv, &cmd->dev->dev_mem_lim);
++ cmd->sgv = NULL;
++ cmd->sg = NULL;
++ cmd->sg_cnt = 0;
++ goto out;
++}
++
++static void scst_release_space(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++ if (cmd->sgv == NULL) {
++ if ((cmd->sg != NULL) &&
++ !(cmd->tgt_data_buf_alloced || cmd->dh_data_buf_alloced)) {
++ TRACE_MEM("Freeing sg %p for cmd %p (cnt %d)", cmd->sg,
++ cmd, cmd->sg_cnt);
++ scst_free(cmd->sg, cmd->sg_cnt);
++ goto out_zero;
++ } else
++ goto out;
++ }
++
++ if (cmd->tgt_data_buf_alloced || cmd->dh_data_buf_alloced) {
++ TRACE_MEM("%s", "*data_buf_alloced set, returning");
++ goto out;
++ }
++
++ if (cmd->out_sgv != NULL) {
++ sgv_pool_free(cmd->out_sgv, &cmd->dev->dev_mem_lim);
++ cmd->out_sgv = NULL;
++ cmd->out_sg_cnt = 0;
++ cmd->out_sg = NULL;
++ cmd->out_bufflen = 0;
++ }
++
++ sgv_pool_free(cmd->sgv, &cmd->dev->dev_mem_lim);
++
++out_zero:
++ cmd->sgv = NULL;
++ cmd->sg_cnt = 0;
++ cmd->sg = NULL;
++ cmd->bufflen = 0;
++ cmd->data_len = 0;
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void scsi_end_async(struct request *req, int error)
++{
++ struct scsi_io_context *sioc = req->end_io_data;
++
++ TRACE_DBG("sioc %p, cmd %p", sioc, sioc->data);
++
++ if (sioc->done)
++ sioc->done(sioc->data, sioc->sense, req->errors, req->resid_len);
++
++ kmem_cache_free(scsi_io_context_cache, sioc);
++
++ __blk_put_request(req->q, req);
++ return;
++}
++
++/**
++ * scst_scsi_exec_async - executes a SCSI command in pass-through mode
++ * @cmd: scst command
++ * @data: pointer passed to done() as "data"
++ * @done: callback function when done
++ */
++int scst_scsi_exec_async(struct scst_cmd *cmd, void *data,
++ void (*done)(void *data, char *sense, int result, int resid))
++{
++ int res = 0;
++ struct request_queue *q = cmd->dev->scsi_dev->request_queue;
++ struct request *rq;
++ struct scsi_io_context *sioc;
++ int write = (cmd->data_direction & SCST_DATA_WRITE) ? WRITE : READ;
++ gfp_t gfp = cmd->noio_mem_alloc ? GFP_NOIO : GFP_KERNEL;
++ int cmd_len = cmd->cdb_len;
++
++ sioc = kmem_cache_zalloc(scsi_io_context_cache, gfp);
++ if (sioc == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ rq = blk_get_request(q, write, gfp);
++ if (rq == NULL) {
++ res = -ENOMEM;
++ goto out_free_sioc;
++ }
++
++ rq->cmd_type = REQ_TYPE_BLOCK_PC;
++ rq->cmd_flags |= REQ_QUIET;
++
++ if (cmd->sg == NULL)
++ goto done;
++
++ if (cmd->data_direction == SCST_DATA_BIDI) {
++ struct request *next_rq;
++
++ if (!test_bit(QUEUE_FLAG_BIDI, &q->queue_flags)) {
++ res = -EOPNOTSUPP;
++ goto out_free_rq;
++ }
++
++ res = blk_rq_map_kern_sg(rq, cmd->out_sg, cmd->out_sg_cnt, gfp);
++ if (res != 0) {
++ TRACE_DBG("blk_rq_map_kern_sg() failed: %d", res);
++ goto out_free_rq;
++ }
++
++ next_rq = blk_get_request(q, READ, gfp);
++ if (next_rq == NULL) {
++ res = -ENOMEM;
++ goto out_free_unmap;
++ }
++ rq->next_rq = next_rq;
++ next_rq->cmd_type = rq->cmd_type;
++
++ res = blk_rq_map_kern_sg(next_rq, cmd->sg, cmd->sg_cnt, gfp);
++ if (res != 0) {
++ TRACE_DBG("blk_rq_map_kern_sg() failed: %d", res);
++ goto out_free_unmap;
++ }
++ } else {
++ res = blk_rq_map_kern_sg(rq, cmd->sg, cmd->sg_cnt, gfp);
++ if (res != 0) {
++ TRACE_DBG("blk_rq_map_kern_sg() failed: %d", res);
++ goto out_free_rq;
++ }
++ }
++
++done:
++ TRACE_DBG("sioc %p, cmd %p", sioc, cmd);
++
++ sioc->data = data;
++ sioc->done = done;
++
++ rq->cmd_len = cmd_len;
++ if (rq->cmd_len <= BLK_MAX_CDB) {
++ memset(rq->cmd, 0, BLK_MAX_CDB); /* ATAPI hates garbage after CDB */
++ memcpy(rq->cmd, cmd->cdb, cmd->cdb_len);
++ } else
++ rq->cmd = cmd->cdb;
++
++ rq->sense = sioc->sense;
++ rq->sense_len = sizeof(sioc->sense);
++ rq->timeout = cmd->timeout;
++ rq->retries = cmd->retries;
++ rq->end_io_data = sioc;
++
++ blk_execute_rq_nowait(rq->q, NULL, rq,
++ (cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE), scsi_end_async);
++out:
++ return res;
++
++out_free_unmap:
++ if (rq->next_rq != NULL) {
++ blk_put_request(rq->next_rq);
++ rq->next_rq = NULL;
++ }
++ blk_rq_unmap_kern_sg(rq, res);
++
++out_free_rq:
++ blk_put_request(rq);
++
++out_free_sioc:
++ kmem_cache_free(scsi_io_context_cache, sioc);
++ goto out;
++}
++EXPORT_SYMBOL(scst_scsi_exec_async);
++
++/**
++ * scst_copy_sg() - copy data between the command's SGs
++ *
++ * Copies data between cmd->tgt_sg and cmd->sg in direction defined by
++ * copy_dir parameter.
++ */
++void scst_copy_sg(struct scst_cmd *cmd, enum scst_sg_copy_dir copy_dir)
++{
++ struct scatterlist *src_sg, *dst_sg;
++ unsigned int to_copy;
++ int atomic = scst_cmd_atomic(cmd);
++
++ TRACE_ENTRY();
++
++ if (copy_dir == SCST_SG_COPY_FROM_TARGET) {
++ if (cmd->data_direction != SCST_DATA_BIDI) {
++ src_sg = cmd->tgt_sg;
++ dst_sg = cmd->sg;
++ to_copy = cmd->bufflen;
++ } else {
++ TRACE_MEM("BIDI cmd %p", cmd);
++ src_sg = cmd->tgt_out_sg;
++ dst_sg = cmd->out_sg;
++ to_copy = cmd->out_bufflen;
++ }
++ } else {
++ src_sg = cmd->sg;
++ dst_sg = cmd->tgt_sg;
++ to_copy = cmd->resp_data_len;
++ }
++
++ TRACE_MEM("cmd %p, copy_dir %d, src_sg %p, dst_sg %p, to_copy %lld",
++ cmd, copy_dir, src_sg, dst_sg, (long long)to_copy);
++
++ if (unlikely(src_sg == NULL) || unlikely(dst_sg == NULL)) {
++ /*
++ * It can happened, e.g., with scst_user for cmd with delay
++ * alloc, which failed with Check Condition.
++ */
++ goto out;
++ }
++
++ sg_copy(dst_sg, src_sg, 0, to_copy,
++ atomic ? KM_SOFTIRQ0 : KM_USER0,
++ atomic ? KM_SOFTIRQ1 : KM_USER1);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_copy_sg);
++
++/**
++ * scst_get_buf_full - return linear buffer for command
++ * @cmd: scst command
++ * @buf: pointer on the resulting pointer
++ *
++ * If the command's buffer >single page, it vmalloc() the needed area
++ * and copies the buffer there. Returns length of the buffer or negative
++ * error code otherwise.
++ */
++int scst_get_buf_full(struct scst_cmd *cmd, uint8_t **buf)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(cmd->sg_buff_vmallocated);
++
++ if (scst_get_buf_count(cmd) > 1) {
++ int len;
++ uint8_t *tmp_buf;
++ int full_size;
++
++ full_size = 0;
++ len = scst_get_buf_first(cmd, &tmp_buf);
++ while (len > 0) {
++ full_size += len;
++ scst_put_buf(cmd, tmp_buf);
++ len = scst_get_buf_next(cmd, &tmp_buf);
++ }
++
++ *buf = vmalloc(full_size);
++ if (*buf == NULL) {
++ TRACE(TRACE_OUT_OF_MEM, "vmalloc() failed for opcode "
++ "%x", cmd->cdb[0]);
++ res = -ENOMEM;
++ goto out;
++ }
++ cmd->sg_buff_vmallocated = 1;
++
++ if (scst_cmd_get_data_direction(cmd) == SCST_DATA_WRITE) {
++ uint8_t *buf_ptr;
++
++ buf_ptr = *buf;
++
++ len = scst_get_buf_first(cmd, &tmp_buf);
++ while (len > 0) {
++ memcpy(buf_ptr, tmp_buf, len);
++ buf_ptr += len;
++
++ scst_put_buf(cmd, tmp_buf);
++ len = scst_get_buf_next(cmd, &tmp_buf);
++ }
++ }
++ res = full_size;
++ } else
++ res = scst_get_buf_first(cmd, buf);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(scst_get_buf_full);
++
++/**
++ * scst_put_buf_full - unmaps linear buffer for command
++ * @cmd: scst command
++ * @buf: pointer on the buffer to unmap
++ *
++ * Reverse operation for scst_get_buf_full. If the buffer was vmalloced(),
++ * it vfree() the buffer.
++ */
++void scst_put_buf_full(struct scst_cmd *cmd, uint8_t *buf)
++{
++ TRACE_ENTRY();
++
++ if (buf == NULL)
++ goto out;
++
++ if (cmd->sg_buff_vmallocated) {
++ if (scst_cmd_get_data_direction(cmd) == SCST_DATA_READ) {
++ int len;
++ uint8_t *tmp_buf, *buf_p;
++
++ buf_p = buf;
++
++ len = scst_get_buf_first(cmd, &tmp_buf);
++ while (len > 0) {
++ memcpy(tmp_buf, buf_p, len);
++ buf_p += len;
++
++ scst_put_buf(cmd, tmp_buf);
++ len = scst_get_buf_next(cmd, &tmp_buf);
++ }
++
++ }
++
++ cmd->sg_buff_vmallocated = 0;
++
++ vfree(buf);
++ } else
++ scst_put_buf(cmd, buf);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_put_buf_full);
++
++static const int SCST_CDB_LENGTH[8] = { 6, 10, 10, 0, 16, 12, 0, 0 };
++
++#define SCST_CDB_GROUP(opcode) ((opcode >> 5) & 0x7)
++#define SCST_GET_CDB_LEN(opcode) SCST_CDB_LENGTH[SCST_CDB_GROUP(opcode)]
++
++/* get_trans_len_x extract x bytes from cdb as length starting from off */
++
++static int get_trans_cdb_len_10(struct scst_cmd *cmd, uint8_t off)
++{
++ cmd->cdb_len = 10;
++ cmd->bufflen = 0;
++ return 0;
++}
++
++static int get_trans_len_block_limit(struct scst_cmd *cmd, uint8_t off)
++{
++ cmd->bufflen = 6;
++ return 0;
++}
++
++static int get_trans_len_read_capacity(struct scst_cmd *cmd, uint8_t off)
++{
++ cmd->bufflen = 8;
++ return 0;
++}
++
++static int get_trans_len_serv_act_in(struct scst_cmd *cmd, uint8_t off)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16) {
++ cmd->op_name = "READ CAPACITY(16)";
++ cmd->bufflen = be32_to_cpu(get_unaligned((__be32 *)&cmd->cdb[10]));
++ cmd->op_flags |= SCST_IMPLICIT_HQ | SCST_REG_RESERVE_ALLOWED |
++ SCST_WRITE_EXCL_ALLOWED | SCST_EXCL_ACCESS_ALLOWED;
++ } else
++ cmd->op_flags |= SCST_UNKNOWN_LENGTH;
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int get_trans_len_single(struct scst_cmd *cmd, uint8_t off)
++{
++ cmd->bufflen = 1;
++ return 0;
++}
++
++static int get_trans_len_read_pos(struct scst_cmd *cmd, uint8_t off)
++{
++ uint8_t *p = (uint8_t *)cmd->cdb + off;
++ int res = 0;
++
++ cmd->bufflen = 0;
++ cmd->bufflen |= ((u32)p[0]) << 8;
++ cmd->bufflen |= ((u32)p[1]);
++
++ switch (cmd->cdb[1] & 0x1f) {
++ case 0:
++ case 1:
++ case 6:
++ if (cmd->bufflen != 0) {
++ PRINT_ERROR("READ POSITION: Invalid non-zero (%d) "
++ "allocation length for service action %x",
++ cmd->bufflen, cmd->cdb[1] & 0x1f);
++ goto out_inval;
++ }
++ break;
++ }
++
++ switch (cmd->cdb[1] & 0x1f) {
++ case 0:
++ case 1:
++ cmd->bufflen = 20;
++ break;
++ case 6:
++ cmd->bufflen = 32;
++ break;
++ case 8:
++ cmd->bufflen = max(28, cmd->bufflen);
++ break;
++ default:
++ PRINT_ERROR("READ POSITION: Invalid service action %x",
++ cmd->cdb[1] & 0x1f);
++ goto out_inval;
++ }
++
++out:
++ return res;
++
++out_inval:
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ res = 1;
++ goto out;
++}
++
++static int get_trans_len_prevent_allow_medium_removal(struct scst_cmd *cmd,
++ uint8_t off)
++{
++ if ((cmd->cdb[4] & 3) == 0)
++ cmd->op_flags |= SCST_REG_RESERVE_ALLOWED |
++ SCST_WRITE_EXCL_ALLOWED | SCST_EXCL_ACCESS_ALLOWED;
++ return 0;
++}
++
++static int get_trans_len_start_stop(struct scst_cmd *cmd, uint8_t off)
++{
++ if ((cmd->cdb[4] & 0xF1) == 0x1)
++ cmd->op_flags |= SCST_REG_RESERVE_ALLOWED |
++ SCST_WRITE_EXCL_ALLOWED | SCST_EXCL_ACCESS_ALLOWED;
++ return 0;
++}
++
++static int get_trans_len_3_read_elem_stat(struct scst_cmd *cmd, uint8_t off)
++{
++ const uint8_t *p = cmd->cdb + off;
++
++ cmd->bufflen = 0;
++ cmd->bufflen |= ((u32)p[0]) << 16;
++ cmd->bufflen |= ((u32)p[1]) << 8;
++ cmd->bufflen |= ((u32)p[2]);
++
++ if ((cmd->cdb[6] & 0x2) == 0x2)
++ cmd->op_flags |= SCST_REG_RESERVE_ALLOWED |
++ SCST_WRITE_EXCL_ALLOWED | SCST_EXCL_ACCESS_ALLOWED;
++ return 0;
++}
++
++static int get_trans_len_1(struct scst_cmd *cmd, uint8_t off)
++{
++ cmd->bufflen = (u32)cmd->cdb[off];
++ return 0;
++}
++
++static int get_trans_len_1_256(struct scst_cmd *cmd, uint8_t off)
++{
++ cmd->bufflen = (u32)cmd->cdb[off];
++ if (cmd->bufflen == 0)
++ cmd->bufflen = 256;
++ return 0;
++}
++
++static int get_trans_len_2(struct scst_cmd *cmd, uint8_t off)
++{
++ const uint8_t *p = cmd->cdb + off;
++
++ cmd->bufflen = 0;
++ cmd->bufflen |= ((u32)p[0]) << 8;
++ cmd->bufflen |= ((u32)p[1]);
++
++ return 0;
++}
++
++static int get_trans_len_3(struct scst_cmd *cmd, uint8_t off)
++{
++ const uint8_t *p = cmd->cdb + off;
++
++ cmd->bufflen = 0;
++ cmd->bufflen |= ((u32)p[0]) << 16;
++ cmd->bufflen |= ((u32)p[1]) << 8;
++ cmd->bufflen |= ((u32)p[2]);
++
++ return 0;
++}
++
++static int get_trans_len_4(struct scst_cmd *cmd, uint8_t off)
++{
++ const uint8_t *p = cmd->cdb + off;
++
++ cmd->bufflen = 0;
++ cmd->bufflen |= ((u32)p[0]) << 24;
++ cmd->bufflen |= ((u32)p[1]) << 16;
++ cmd->bufflen |= ((u32)p[2]) << 8;
++ cmd->bufflen |= ((u32)p[3]);
++
++ return 0;
++}
++
++static int get_trans_len_none(struct scst_cmd *cmd, uint8_t off)
++{
++ cmd->bufflen = 0;
++ return 0;
++}
++
++static int get_bidi_trans_len_2(struct scst_cmd *cmd, uint8_t off)
++{
++ const uint8_t *p = cmd->cdb + off;
++
++ cmd->bufflen = 0;
++ cmd->bufflen |= ((u32)p[0]) << 8;
++ cmd->bufflen |= ((u32)p[1]);
++
++ cmd->out_bufflen = cmd->bufflen;
++
++ return 0;
++}
++
++/**
++ * scst_get_cdb_info() - fill various info about the command's CDB
++ *
++ * Description:
++ * Fills various info about the command's CDB in the corresponding fields
++ * in the command.
++ *
++ * Returns: 0 on success, <0 if command is unknown, >0 if command
++ * is invalid.
++ */
++int scst_get_cdb_info(struct scst_cmd *cmd)
++{
++ int dev_type = cmd->dev->type;
++ int i, res = 0;
++ uint8_t op;
++ const struct scst_sdbops *ptr = NULL;
++
++ TRACE_ENTRY();
++
++ op = cmd->cdb[0]; /* get clear opcode */
++
++ TRACE_DBG("opcode=%02x, cdblen=%d bytes, tblsize=%d, "
++ "dev_type=%d", op, SCST_GET_CDB_LEN(op), SCST_CDB_TBL_SIZE,
++ dev_type);
++
++ i = scst_scsi_op_list[op];
++ while (i < SCST_CDB_TBL_SIZE && scst_scsi_op_table[i].ops == op) {
++ if (scst_scsi_op_table[i].devkey[dev_type] != SCST_CDB_NOTSUPP) {
++ ptr = &scst_scsi_op_table[i];
++ TRACE_DBG("op = 0x%02x+'%c%c%c%c%c%c%c%c%c%c'+<%s>",
++ ptr->ops, ptr->devkey[0], /* disk */
++ ptr->devkey[1], /* tape */
++ ptr->devkey[2], /* printer */
++ ptr->devkey[3], /* cpu */
++ ptr->devkey[4], /* cdr */
++ ptr->devkey[5], /* cdrom */
++ ptr->devkey[6], /* scanner */
++ ptr->devkey[7], /* worm */
++ ptr->devkey[8], /* changer */
++ ptr->devkey[9], /* commdev */
++ ptr->op_name);
++ TRACE_DBG("direction=%d flags=%d off=%d",
++ ptr->direction,
++ ptr->flags,
++ ptr->off);
++ break;
++ }
++ i++;
++ }
++
++ if (unlikely(ptr == NULL)) {
++ /* opcode not found or now not used */
++ TRACE(TRACE_MINOR, "Unknown opcode 0x%x for type %d", op,
++ dev_type);
++ res = -1;
++ goto out;
++ }
++
++ cmd->cdb_len = SCST_GET_CDB_LEN(op);
++ cmd->op_name = ptr->op_name;
++ cmd->data_direction = ptr->direction;
++ cmd->op_flags = ptr->flags | SCST_INFO_VALID;
++ res = (*ptr->get_trans_len)(cmd, ptr->off);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_get_cdb_info);
++
++/* Packs SCST LUN back to SCSI form */
++__be64 scst_pack_lun(const uint64_t lun, enum scst_lun_addr_method addr_method)
++{
++ uint64_t res = 0;
++
++ if (lun) {
++ res = (addr_method << 14) | (lun & 0x3fff);
++ res = res << 48;
++ }
++
++ TRACE_EXIT_HRES(res >> 48);
++ return cpu_to_be64(res);
++}
++
++/*
++ * Function to extract a LUN number from an 8-byte LUN structure in network byte
++ * order (big endian). Supports three LUN addressing methods: peripheral, flat
++ * and logical unit. See also SAM-2, section 4.9.4 (page 40).
++ */
++uint64_t scst_unpack_lun(const uint8_t *lun, int len)
++{
++ uint64_t res = NO_SUCH_LUN;
++ int address_method;
++
++ TRACE_ENTRY();
++
++ TRACE_BUFF_FLAG(TRACE_DEBUG, "Raw LUN", lun, len);
++
++ if (unlikely(len < 2)) {
++ PRINT_ERROR("Illegal lun length %d, expected 2 bytes or "
++ "more", len);
++ goto out;
++ }
++
++ if (len > 2) {
++ switch (len) {
++ case 8:
++ if ((*((__be64 *)lun) &
++ __constant_cpu_to_be64(0x0000FFFFFFFFFFFFLL)) != 0)
++ goto out_err;
++ break;
++ case 4:
++ if (*((__be16 *)&lun[2]) != 0)
++ goto out_err;
++ break;
++ case 6:
++ if (*((__be32 *)&lun[2]) != 0)
++ goto out_err;
++ break;
++ default:
++ goto out_err;
++ }
++ }
++
++ address_method = (*lun) >> 6; /* high 2 bits of byte 0 */
++ switch (address_method) {
++ case SCST_LUN_ADDR_METHOD_PERIPHERAL:
++ case SCST_LUN_ADDR_METHOD_FLAT:
++ case SCST_LUN_ADDR_METHOD_LUN:
++ res = *(lun + 1) | (((*lun) & 0x3f) << 8);
++ break;
++
++ case SCST_LUN_ADDR_METHOD_EXTENDED_LUN:
++ default:
++ PRINT_ERROR("Unimplemented LUN addressing method %u",
++ address_method);
++ break;
++ }
++
++out:
++ TRACE_EXIT_RES((int)res);
++ return res;
++
++out_err:
++ PRINT_ERROR("%s", "Multi-level LUN unimplemented");
++ goto out;
++}
++
++/**
++ ** Generic parse() support routines.
++ ** Done via pointer on functions to avoid unneeded dereferences on
++ ** the fast path.
++ **/
++
++/**
++ * scst_calc_block_shift() - calculate block shift
++ *
++ * Calculates and returns block shift for the given sector size
++ */
++int scst_calc_block_shift(int sector_size)
++{
++ int block_shift = 0;
++ int t;
++
++ if (sector_size == 0)
++ sector_size = 512;
++
++ t = sector_size;
++ while (1) {
++ if ((t & 1) != 0)
++ break;
++ t >>= 1;
++ block_shift++;
++ }
++ if (block_shift < 9) {
++ PRINT_ERROR("Wrong sector size %d", sector_size);
++ block_shift = -1;
++ }
++
++ TRACE_EXIT_RES(block_shift);
++ return block_shift;
++}
++EXPORT_SYMBOL_GPL(scst_calc_block_shift);
++
++/**
++ * scst_sbc_generic_parse() - generic SBC parsing
++ *
++ * Generic parse() for SBC (disk) devices
++ */
++int scst_sbc_generic_parse(struct scst_cmd *cmd,
++ int (*get_block_shift)(struct scst_cmd *cmd))
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
++ * therefore change them only if necessary
++ */
++
++ TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
++ cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
++
++ switch (cmd->cdb[0]) {
++ case VERIFY_6:
++ case VERIFY:
++ case VERIFY_12:
++ case VERIFY_16:
++ if ((cmd->cdb[1] & BYTCHK) == 0) {
++ cmd->data_len = cmd->bufflen << get_block_shift(cmd);
++ cmd->bufflen = 0;
++ goto set_timeout;
++ } else
++ cmd->data_len = 0;
++ break;
++ default:
++ /* It's all good */
++ break;
++ }
++
++ if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED) {
++ int block_shift = get_block_shift(cmd);
++ /*
++ * No need for locks here, since *_detach() can not be
++ * called, when there are existing commands.
++ */
++ cmd->bufflen = cmd->bufflen << block_shift;
++ cmd->out_bufflen = cmd->out_bufflen << block_shift;
++ }
++
++set_timeout:
++ if ((cmd->op_flags & (SCST_SMALL_TIMEOUT | SCST_LONG_TIMEOUT)) == 0)
++ cmd->timeout = SCST_GENERIC_DISK_REG_TIMEOUT;
++ else if (cmd->op_flags & SCST_SMALL_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_DISK_SMALL_TIMEOUT;
++ else if (cmd->op_flags & SCST_LONG_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_DISK_LONG_TIMEOUT;
++
++ TRACE_DBG("res %d, bufflen %d, data_len %d, direct %d",
++ res, cmd->bufflen, cmd->data_len, cmd->data_direction);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_sbc_generic_parse);
++
++/**
++ * scst_cdrom_generic_parse() - generic MMC parse
++ *
++ * Generic parse() for MMC (cdrom) devices
++ */
++int scst_cdrom_generic_parse(struct scst_cmd *cmd,
++ int (*get_block_shift)(struct scst_cmd *cmd))
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
++ * therefore change them only if necessary
++ */
++
++ TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
++ cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
++
++ cmd->cdb[1] &= 0x1f;
++
++ switch (cmd->cdb[0]) {
++ case VERIFY_6:
++ case VERIFY:
++ case VERIFY_12:
++ case VERIFY_16:
++ if ((cmd->cdb[1] & BYTCHK) == 0) {
++ cmd->data_len = cmd->bufflen << get_block_shift(cmd);
++ cmd->bufflen = 0;
++ goto set_timeout;
++ }
++ break;
++ default:
++ /* It's all good */
++ break;
++ }
++
++ if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED) {
++ int block_shift = get_block_shift(cmd);
++ cmd->bufflen = cmd->bufflen << block_shift;
++ cmd->out_bufflen = cmd->out_bufflen << block_shift;
++ }
++
++set_timeout:
++ if ((cmd->op_flags & (SCST_SMALL_TIMEOUT | SCST_LONG_TIMEOUT)) == 0)
++ cmd->timeout = SCST_GENERIC_CDROM_REG_TIMEOUT;
++ else if (cmd->op_flags & SCST_SMALL_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_CDROM_SMALL_TIMEOUT;
++ else if (cmd->op_flags & SCST_LONG_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_CDROM_LONG_TIMEOUT;
++
++ TRACE_DBG("res=%d, bufflen=%d, direct=%d", res, cmd->bufflen,
++ cmd->data_direction);
++
++ TRACE_EXIT();
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_cdrom_generic_parse);
++
++/**
++ * scst_modisk_generic_parse() - generic MO parse
++ *
++ * Generic parse() for MO disk devices
++ */
++int scst_modisk_generic_parse(struct scst_cmd *cmd,
++ int (*get_block_shift)(struct scst_cmd *cmd))
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
++ * therefore change them only if necessary
++ */
++
++ TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
++ cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
++
++ cmd->cdb[1] &= 0x1f;
++
++ switch (cmd->cdb[0]) {
++ case VERIFY_6:
++ case VERIFY:
++ case VERIFY_12:
++ case VERIFY_16:
++ if ((cmd->cdb[1] & BYTCHK) == 0) {
++ cmd->data_len = cmd->bufflen << get_block_shift(cmd);
++ cmd->bufflen = 0;
++ goto set_timeout;
++ }
++ break;
++ default:
++ /* It's all good */
++ break;
++ }
++
++ if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED) {
++ int block_shift = get_block_shift(cmd);
++ cmd->bufflen = cmd->bufflen << block_shift;
++ cmd->out_bufflen = cmd->out_bufflen << block_shift;
++ }
++
++set_timeout:
++ if ((cmd->op_flags & (SCST_SMALL_TIMEOUT | SCST_LONG_TIMEOUT)) == 0)
++ cmd->timeout = SCST_GENERIC_MODISK_REG_TIMEOUT;
++ else if (cmd->op_flags & SCST_SMALL_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_MODISK_SMALL_TIMEOUT;
++ else if (cmd->op_flags & SCST_LONG_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_MODISK_LONG_TIMEOUT;
++
++ TRACE_DBG("res=%d, bufflen=%d, direct=%d", res, cmd->bufflen,
++ cmd->data_direction);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_modisk_generic_parse);
++
++/**
++ * scst_tape_generic_parse() - generic tape parse
++ *
++ * Generic parse() for tape devices
++ */
++int scst_tape_generic_parse(struct scst_cmd *cmd,
++ int (*get_block_size)(struct scst_cmd *cmd))
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
++ * therefore change them only if necessary
++ */
++
++ TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
++ cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
++
++ if (cmd->cdb[0] == READ_POSITION) {
++ int tclp = cmd->cdb[1] & 4;
++ int long_bit = cmd->cdb[1] & 2;
++ int bt = cmd->cdb[1] & 1;
++
++ if ((tclp == long_bit) && (!bt || !long_bit)) {
++ cmd->bufflen =
++ tclp ? POSITION_LEN_LONG : POSITION_LEN_SHORT;
++ cmd->data_direction = SCST_DATA_READ;
++ } else {
++ cmd->bufflen = 0;
++ cmd->data_direction = SCST_DATA_NONE;
++ }
++ }
++
++ if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED & cmd->cdb[1]) {
++ int block_size = get_block_size(cmd);
++ cmd->bufflen = cmd->bufflen * block_size;
++ cmd->out_bufflen = cmd->out_bufflen * block_size;
++ }
++
++ if ((cmd->op_flags & (SCST_SMALL_TIMEOUT | SCST_LONG_TIMEOUT)) == 0)
++ cmd->timeout = SCST_GENERIC_TAPE_REG_TIMEOUT;
++ else if (cmd->op_flags & SCST_SMALL_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_TAPE_SMALL_TIMEOUT;
++ else if (cmd->op_flags & SCST_LONG_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_TAPE_LONG_TIMEOUT;
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_tape_generic_parse);
++
++static int scst_null_parse(struct scst_cmd *cmd)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->data_direction and cmd->bufflen,
++ * therefore change them only if necessary
++ */
++
++ TRACE_DBG("op_name <%s> direct %d flags %d transfer_len %d",
++ cmd->op_name, cmd->data_direction, cmd->op_flags, cmd->bufflen);
++#if 0
++ switch (cmd->cdb[0]) {
++ default:
++ /* It's all good */
++ break;
++ }
++#endif
++ TRACE_DBG("res %d bufflen %d direct %d",
++ res, cmd->bufflen, cmd->data_direction);
++
++ TRACE_EXIT();
++ return res;
++}
++
++/**
++ * scst_changer_generic_parse() - generic changer parse
++ *
++ * Generic parse() for changer devices
++ */
++int scst_changer_generic_parse(struct scst_cmd *cmd,
++ int (*nothing)(struct scst_cmd *cmd))
++{
++ int res = scst_null_parse(cmd);
++
++ if (cmd->op_flags & SCST_LONG_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_CHANGER_LONG_TIMEOUT;
++ else
++ cmd->timeout = SCST_GENERIC_CHANGER_TIMEOUT;
++
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_changer_generic_parse);
++
++/**
++ * scst_processor_generic_parse - generic SCSI processor parse
++ *
++ * Generic parse() for SCSI processor devices
++ */
++int scst_processor_generic_parse(struct scst_cmd *cmd,
++ int (*nothing)(struct scst_cmd *cmd))
++{
++ int res = scst_null_parse(cmd);
++
++ if (cmd->op_flags & SCST_LONG_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_PROCESSOR_LONG_TIMEOUT;
++ else
++ cmd->timeout = SCST_GENERIC_PROCESSOR_TIMEOUT;
++
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_processor_generic_parse);
++
++/**
++ * scst_raid_generic_parse() - generic RAID parse
++ *
++ * Generic parse() for RAID devices
++ */
++int scst_raid_generic_parse(struct scst_cmd *cmd,
++ int (*nothing)(struct scst_cmd *cmd))
++{
++ int res = scst_null_parse(cmd);
++
++ if (cmd->op_flags & SCST_LONG_TIMEOUT)
++ cmd->timeout = SCST_GENERIC_RAID_LONG_TIMEOUT;
++ else
++ cmd->timeout = SCST_GENERIC_RAID_TIMEOUT;
++
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_raid_generic_parse);
++
++/**
++ ** Generic dev_done() support routines.
++ ** Done via pointer on functions to avoid unneeded dereferences on
++ ** the fast path.
++ **/
++
++/**
++ * scst_block_generic_dev_done() - generic SBC dev_done
++ *
++ * Generic dev_done() for block (SBC) devices
++ */
++int scst_block_generic_dev_done(struct scst_cmd *cmd,
++ void (*set_block_shift)(struct scst_cmd *cmd, int block_shift))
++{
++ int opcode = cmd->cdb[0];
++ int status = cmd->status;
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->is_send_status and
++ * cmd->resp_data_len based on cmd->status and cmd->data_direction,
++ * therefore change them only if necessary
++ */
++
++ if ((status == SAM_STAT_GOOD) || (status == SAM_STAT_CONDITION_MET)) {
++ switch (opcode) {
++ case READ_CAPACITY:
++ {
++ /* Always keep track of disk capacity */
++ int buffer_size, sector_size, sh;
++ uint8_t *buffer;
++
++ buffer_size = scst_get_buf_full(cmd, &buffer);
++ if (unlikely(buffer_size <= 0)) {
++ if (buffer_size < 0) {
++ PRINT_ERROR("%s: Unable to get the"
++ " buffer (%d)", __func__, buffer_size);
++ }
++ goto out;
++ }
++
++ sector_size =
++ ((buffer[4] << 24) | (buffer[5] << 16) |
++ (buffer[6] << 8) | (buffer[7] << 0));
++ scst_put_buf_full(cmd, buffer);
++ if (sector_size != 0)
++ sh = scst_calc_block_shift(sector_size);
++ else
++ sh = 0;
++ set_block_shift(cmd, sh);
++ TRACE_DBG("block_shift %d", sh);
++ break;
++ }
++ default:
++ /* It's all good */
++ break;
++ }
++ }
++
++ TRACE_DBG("cmd->is_send_status=%x, cmd->resp_data_len=%d, "
++ "res=%d", cmd->is_send_status, cmd->resp_data_len, res);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_block_generic_dev_done);
++
++/**
++ * scst_tape_generic_dev_done() - generic tape dev done
++ *
++ * Generic dev_done() for tape devices
++ */
++int scst_tape_generic_dev_done(struct scst_cmd *cmd,
++ void (*set_block_size)(struct scst_cmd *cmd, int block_shift))
++{
++ int opcode = cmd->cdb[0];
++ int res = SCST_CMD_STATE_DEFAULT;
++ int buffer_size, bs;
++ uint8_t *buffer = NULL;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->is_send_status and
++ * cmd->resp_data_len based on cmd->status and cmd->data_direction,
++ * therefore change them only if necessary
++ */
++
++ if (cmd->status != SAM_STAT_GOOD)
++ goto out;
++
++ switch (opcode) {
++ case MODE_SENSE:
++ case MODE_SELECT:
++ buffer_size = scst_get_buf_full(cmd, &buffer);
++ if (unlikely(buffer_size <= 0)) {
++ if (buffer_size < 0) {
++ PRINT_ERROR("%s: Unable to get the buffer (%d)",
++ __func__, buffer_size);
++ }
++ goto out;
++ }
++ break;
++ }
++
++ switch (opcode) {
++ case MODE_SENSE:
++ TRACE_DBG("%s", "MODE_SENSE");
++ if ((cmd->cdb[2] & 0xC0) == 0) {
++ if (buffer[3] == 8) {
++ bs = (buffer[9] << 16) |
++ (buffer[10] << 8) | buffer[11];
++ set_block_size(cmd, bs);
++ }
++ }
++ break;
++ case MODE_SELECT:
++ TRACE_DBG("%s", "MODE_SELECT");
++ if (buffer[3] == 8) {
++ bs = (buffer[9] << 16) | (buffer[10] << 8) |
++ (buffer[11]);
++ set_block_size(cmd, bs);
++ }
++ break;
++ default:
++ /* It's all good */
++ break;
++ }
++
++ switch (opcode) {
++ case MODE_SENSE:
++ case MODE_SELECT:
++ scst_put_buf_full(cmd, buffer);
++ break;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_tape_generic_dev_done);
++
++static void scst_check_internal_sense(struct scst_device *dev, int result,
++ uint8_t *sense, int sense_len)
++{
++ TRACE_ENTRY();
++
++ if (host_byte(result) == DID_RESET) {
++ int sl;
++ TRACE(TRACE_MGMT, "DID_RESET received for device %s, "
++ "triggering reset UA", dev->virt_name);
++ sl = scst_set_sense(sense, sense_len, dev->d_sense,
++ SCST_LOAD_SENSE(scst_sense_reset_UA));
++ scst_dev_check_set_UA(dev, NULL, sense, sl);
++ } else if ((status_byte(result) == CHECK_CONDITION) &&
++ scst_is_ua_sense(sense, sense_len))
++ scst_dev_check_set_UA(dev, NULL, sense, sense_len);
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_to_dma_dir() - translate SCST's data direction to DMA direction
++ *
++ * Translates SCST's data direction to DMA one from backend storage
++ * perspective.
++ */
++enum dma_data_direction scst_to_dma_dir(int scst_dir)
++{
++ static const enum dma_data_direction tr_tbl[] = { DMA_NONE,
++ DMA_TO_DEVICE, DMA_FROM_DEVICE, DMA_BIDIRECTIONAL, DMA_NONE };
++
++ return tr_tbl[scst_dir];
++}
++EXPORT_SYMBOL(scst_to_dma_dir);
++
++/*
++ * scst_to_tgt_dma_dir() - translate SCST data direction to DMA direction
++ *
++ * Translates SCST data direction to DMA data direction from the perspective
++ * of a target.
++ */
++enum dma_data_direction scst_to_tgt_dma_dir(int scst_dir)
++{
++ static const enum dma_data_direction tr_tbl[] = { DMA_NONE,
++ DMA_FROM_DEVICE, DMA_TO_DEVICE, DMA_BIDIRECTIONAL, DMA_NONE };
++
++ return tr_tbl[scst_dir];
++}
++EXPORT_SYMBOL(scst_to_tgt_dma_dir);
++
++/**
++ * scst_obtain_device_parameters() - obtain device control parameters
++ *
++ * Issues a MODE SENSE for control mode page data and sets the corresponding
++ * dev's parameter from it. Returns 0 on success and not 0 otherwise.
++ */
++int scst_obtain_device_parameters(struct scst_device *dev)
++{
++ int rc, i;
++ uint8_t cmd[16];
++ uint8_t buffer[4+0x0A];
++ uint8_t sense_buffer[SCSI_SENSE_BUFFERSIZE];
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(dev->scsi_dev == NULL);
++
++ for (i = 0; i < 5; i++) {
++ /* Get control mode page */
++ memset(cmd, 0, sizeof(cmd));
++#if 0
++ cmd[0] = MODE_SENSE_10;
++ cmd[1] = 0;
++ cmd[2] = 0x0A;
++ cmd[8] = sizeof(buffer); /* it's < 256 */
++#else
++ cmd[0] = MODE_SENSE;
++ cmd[1] = 8; /* DBD */
++ cmd[2] = 0x0A;
++ cmd[4] = sizeof(buffer);
++#endif
++
++ memset(buffer, 0, sizeof(buffer));
++ memset(sense_buffer, 0, sizeof(sense_buffer));
++
++ TRACE(TRACE_SCSI, "%s", "Doing internal MODE_SENSE");
++ rc = scsi_execute(dev->scsi_dev, cmd, SCST_DATA_READ, buffer,
++ sizeof(buffer), sense_buffer, 15, 0, 0
++ , NULL
++ );
++
++ TRACE_DBG("MODE_SENSE done: %x", rc);
++
++ if (scsi_status_is_good(rc)) {
++ int q;
++
++ PRINT_BUFF_FLAG(TRACE_SCSI, "Returned control mode "
++ "page data", buffer, sizeof(buffer));
++
++ dev->tst = buffer[4+2] >> 5;
++ q = buffer[4+3] >> 4;
++ if (q > SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER) {
++ PRINT_ERROR("Too big QUEUE ALG %x, dev %s",
++ dev->queue_alg, dev->virt_name);
++ }
++ dev->queue_alg = q;
++ dev->swp = (buffer[4+4] & 0x8) >> 3;
++ dev->tas = (buffer[4+5] & 0x40) >> 6;
++ dev->d_sense = (buffer[4+2] & 0x4) >> 2;
++
++ /*
++ * Unfortunately, SCSI ML doesn't provide a way to
++ * specify commands task attribute, so we can rely on
++ * device's restricted reordering only. Linux I/O
++ * subsystem doesn't reorder pass-through (PC) requests.
++ */
++ dev->has_own_order_mgmt = !dev->queue_alg;
++
++ PRINT_INFO("Device %s: TST %x, QUEUE ALG %x, SWP %x, "
++ "TAS %x, D_SENSE %d, has_own_order_mgmt %d",
++ dev->virt_name, dev->tst, dev->queue_alg,
++ dev->swp, dev->tas, dev->d_sense,
++ dev->has_own_order_mgmt);
++
++ goto out;
++ } else {
++ scst_check_internal_sense(dev, rc, sense_buffer,
++ sizeof(sense_buffer));
++#if 0
++ if ((status_byte(rc) == CHECK_CONDITION) &&
++ SCST_SENSE_VALID(sense_buffer)) {
++#else
++ /*
++ * 3ware controller is buggy and returns CONDITION_GOOD
++ * instead of CHECK_CONDITION
++ */
++ if (SCST_SENSE_VALID(sense_buffer)) {
++#endif
++ PRINT_BUFF_FLAG(TRACE_SCSI, "Returned sense "
++ "data", sense_buffer,
++ sizeof(sense_buffer));
++ if (scst_analyze_sense(sense_buffer,
++ sizeof(sense_buffer),
++ SCST_SENSE_KEY_VALID,
++ ILLEGAL_REQUEST, 0, 0)) {
++ PRINT_INFO("Device %s doesn't support "
++ "MODE SENSE", dev->virt_name);
++ break;
++ } else if (scst_analyze_sense(sense_buffer,
++ sizeof(sense_buffer),
++ SCST_SENSE_KEY_VALID,
++ NOT_READY, 0, 0)) {
++ PRINT_ERROR("Device %s not ready",
++ dev->virt_name);
++ break;
++ }
++ } else {
++ PRINT_INFO("Internal MODE SENSE to "
++ "device %s failed: %x",
++ dev->virt_name, rc);
++ PRINT_BUFF_FLAG(TRACE_SCSI, "MODE SENSE sense",
++ sense_buffer, sizeof(sense_buffer));
++ switch (host_byte(rc)) {
++ case DID_RESET:
++ case DID_ABORT:
++ case DID_SOFT_ERROR:
++ break;
++ default:
++ goto brk;
++ }
++ switch (driver_byte(rc)) {
++ case DRIVER_BUSY:
++ case DRIVER_SOFT:
++ break;
++ default:
++ goto brk;
++ }
++ }
++ }
++ }
++brk:
++ PRINT_WARNING("Unable to get device's %s control mode page, using "
++ "existing values/defaults: TST %x, QUEUE ALG %x, SWP %x, "
++ "TAS %x, D_SENSE %d, has_own_order_mgmt %d", dev->virt_name,
++ dev->tst, dev->queue_alg, dev->swp, dev->tas, dev->d_sense,
++ dev->has_own_order_mgmt);
++
++out:
++ TRACE_EXIT();
++ return 0;
++}
++EXPORT_SYMBOL_GPL(scst_obtain_device_parameters);
++
++/* Called under dev_lock and BH off */
++void scst_process_reset(struct scst_device *dev,
++ struct scst_session *originator, struct scst_cmd *exclude_cmd,
++ struct scst_mgmt_cmd *mcmd, bool setUA)
++{
++ struct scst_tgt_dev *tgt_dev;
++ struct scst_cmd *cmd, *tcmd;
++
++ TRACE_ENTRY();
++
++ /* Clear RESERVE'ation, if necessary */
++ if (dev->dev_reserved) {
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ TRACE_MGMT_DBG("Clearing RESERVE'ation for "
++ "tgt_dev LUN %lld",
++ (long long unsigned int)tgt_dev->lun);
++ clear_bit(SCST_TGT_DEV_RESERVED,
++ &tgt_dev->tgt_dev_flags);
++ }
++ dev->dev_reserved = 0;
++ /*
++ * There is no need to send RELEASE, since the device is going
++ * to be reset. Actually, since we can be in RESET TM
++ * function, it might be dangerous.
++ */
++ }
++
++ dev->dev_double_ua_possible = 1;
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ struct scst_session *sess = tgt_dev->sess;
++
++#if 0 /* Clearing UAs and last sense isn't required by SAM and it
++ * looks to be better to not clear them to not loose important
++ * events, so let's disable it.
++ */
++ spin_lock_bh(&tgt_dev->tgt_dev_lock);
++ scst_free_all_UA(tgt_dev);
++ memset(tgt_dev->tgt_dev_sense, 0,
++ sizeof(tgt_dev->tgt_dev_sense));
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++#endif
++
++ spin_lock_irq(&sess->sess_list_lock);
++
++ TRACE_DBG("Searching in sess cmd list (sess=%p)", sess);
++ list_for_each_entry(cmd, &sess->sess_cmd_list,
++ sess_cmd_list_entry) {
++ if (cmd == exclude_cmd)
++ continue;
++ if ((cmd->tgt_dev == tgt_dev) ||
++ ((cmd->tgt_dev == NULL) &&
++ (cmd->lun == tgt_dev->lun))) {
++ scst_abort_cmd(cmd, mcmd,
++ (tgt_dev->sess != originator), 0);
++ }
++ }
++ spin_unlock_irq(&sess->sess_list_lock);
++ }
++
++ list_for_each_entry_safe(cmd, tcmd, &dev->blocked_cmd_list,
++ blocked_cmd_list_entry) {
++ if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) {
++ list_del(&cmd->blocked_cmd_list_entry);
++ TRACE_MGMT_DBG("Adding aborted blocked cmd %p "
++ "to active cmd list", cmd);
++ spin_lock_irq(&cmd->cmd_threads->cmd_list_lock);
++ list_add_tail(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock_irq(&cmd->cmd_threads->cmd_list_lock);
++ }
++ }
++
++ if (setUA) {
++ uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
++ int sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
++ dev->d_sense, SCST_LOAD_SENSE(scst_sense_reset_UA));
++ scst_dev_check_set_local_UA(dev, exclude_cmd, sense_buffer, sl);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Caller must hold tgt_dev->tgt_dev_lock. */
++void scst_tgt_dev_del_free_UA(struct scst_tgt_dev *tgt_dev,
++ struct scst_tgt_dev_UA *ua)
++{
++ list_del(&ua->UA_list_entry);
++ if (list_empty(&tgt_dev->UA_list))
++ clear_bit(SCST_TGT_DEV_UA_PENDING, &tgt_dev->tgt_dev_flags);
++ mempool_free(ua, scst_ua_mempool);
++}
++
++/* No locks, no IRQ or IRQ-disabled context allowed */
++int scst_set_pending_UA(struct scst_cmd *cmd)
++{
++ int res = 0, i;
++ struct scst_tgt_dev_UA *UA_entry;
++ bool first = true, global_unlock = false;
++ struct scst_session *sess = cmd->sess;
++
++ TRACE_ENTRY();
++
++ /*
++ * RMB and recheck to sync with setting SCST_CMD_ABORTED in
++ * scst_abort_cmd() to not set UA for the being aborted cmd, hence
++ * possibly miss its delivery by a legitimate command while the UA is
++ * being requeued.
++ */
++ smp_rmb();
++ if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) {
++ TRACE_MGMT_DBG("Not set pending UA for aborted cmd %p", cmd);
++ res = -1;
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("Setting pending UA cmd %p", cmd);
++
++ spin_lock_bh(&cmd->tgt_dev->tgt_dev_lock);
++
++again:
++ /* UA list could be cleared behind us, so retest */
++ if (list_empty(&cmd->tgt_dev->UA_list)) {
++ TRACE_DBG("%s",
++ "SCST_TGT_DEV_UA_PENDING set, but UA_list empty");
++ res = -1;
++ goto out_unlock;
++ }
++
++ UA_entry = list_entry(cmd->tgt_dev->UA_list.next, typeof(*UA_entry),
++ UA_list_entry);
++
++ TRACE_DBG("next %p UA_entry %p",
++ cmd->tgt_dev->UA_list.next, UA_entry);
++
++ if (UA_entry->global_UA && first) {
++ TRACE_MGMT_DBG("Global UA %p detected", UA_entry);
++
++ spin_unlock_bh(&cmd->tgt_dev->tgt_dev_lock);
++
++ /*
++ * cmd won't allow to suspend activities, so we can access
++ * sess->sess_tgt_dev_list without any additional
++ * protection.
++ */
++
++ local_bh_disable();
++
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ /* Lockdep triggers here a false positive.. */
++ spin_lock(&tgt_dev->tgt_dev_lock);
++ }
++ }
++
++ first = false;
++ global_unlock = true;
++ goto again;
++ }
++
++ if (scst_set_cmd_error_sense(cmd, UA_entry->UA_sense_buffer,
++ UA_entry->UA_valid_sense_len) != 0)
++ goto out_unlock;
++
++ cmd->ua_ignore = 1;
++
++ list_del(&UA_entry->UA_list_entry);
++
++ if (UA_entry->global_UA) {
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ struct scst_tgt_dev *tgt_dev;
++
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ struct scst_tgt_dev_UA *ua;
++ list_for_each_entry(ua, &tgt_dev->UA_list,
++ UA_list_entry) {
++ if (ua->global_UA &&
++ memcmp(ua->UA_sense_buffer,
++ UA_entry->UA_sense_buffer,
++ sizeof(ua->UA_sense_buffer)) == 0) {
++ TRACE_MGMT_DBG("Freeing not "
++ "needed global UA %p",
++ ua);
++ scst_tgt_dev_del_free_UA(tgt_dev,
++ ua);
++ break;
++ }
++ }
++ }
++ }
++ }
++
++ mempool_free(UA_entry, scst_ua_mempool);
++
++ if (list_empty(&cmd->tgt_dev->UA_list)) {
++ clear_bit(SCST_TGT_DEV_UA_PENDING,
++ &cmd->tgt_dev->tgt_dev_flags);
++ }
++
++out_unlock:
++ if (global_unlock) {
++ for (i = SESS_TGT_DEV_LIST_HASH_SIZE-1; i >= 0; i--) {
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry_reverse(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ spin_unlock(&tgt_dev->tgt_dev_lock);
++ }
++ }
++
++ local_bh_enable();
++ spin_lock_bh(&cmd->tgt_dev->tgt_dev_lock);
++ }
++
++ spin_unlock_bh(&cmd->tgt_dev->tgt_dev_lock);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Called under tgt_dev_lock and BH off */
++static void scst_alloc_set_UA(struct scst_tgt_dev *tgt_dev,
++ const uint8_t *sense, int sense_len, int flags)
++{
++ struct scst_tgt_dev_UA *UA_entry = NULL;
++
++ TRACE_ENTRY();
++
++ UA_entry = mempool_alloc(scst_ua_mempool, GFP_ATOMIC);
++ if (UA_entry == NULL) {
++ PRINT_CRIT_ERROR("%s", "UNIT ATTENTION memory "
++ "allocation failed. The UNIT ATTENTION "
++ "on some sessions will be missed");
++ PRINT_BUFFER("Lost UA", sense, sense_len);
++ goto out;
++ }
++ memset(UA_entry, 0, sizeof(*UA_entry));
++
++ UA_entry->global_UA = (flags & SCST_SET_UA_FLAG_GLOBAL) != 0;
++ if (UA_entry->global_UA)
++ TRACE_MGMT_DBG("Queueing global UA %p", UA_entry);
++
++ if (sense_len > (int)sizeof(UA_entry->UA_sense_buffer)) {
++ PRINT_WARNING("Sense truncated (needed %d), shall you increase "
++ "SCST_SENSE_BUFFERSIZE?", sense_len);
++ sense_len = sizeof(UA_entry->UA_sense_buffer);
++ }
++ memcpy(UA_entry->UA_sense_buffer, sense, sense_len);
++ UA_entry->UA_valid_sense_len = sense_len;
++
++ set_bit(SCST_TGT_DEV_UA_PENDING, &tgt_dev->tgt_dev_flags);
++
++ TRACE_MGMT_DBG("Adding new UA to tgt_dev %p", tgt_dev);
++
++ if (flags & SCST_SET_UA_FLAG_AT_HEAD)
++ list_add(&UA_entry->UA_list_entry, &tgt_dev->UA_list);
++ else
++ list_add_tail(&UA_entry->UA_list_entry, &tgt_dev->UA_list);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* tgt_dev_lock supposed to be held and BH off */
++static void __scst_check_set_UA(struct scst_tgt_dev *tgt_dev,
++ const uint8_t *sense, int sense_len, int flags)
++{
++ int skip_UA = 0;
++ struct scst_tgt_dev_UA *UA_entry_tmp;
++ int len = min_t(int, sizeof(UA_entry_tmp->UA_sense_buffer), sense_len);
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(UA_entry_tmp, &tgt_dev->UA_list,
++ UA_list_entry) {
++ if (memcmp(sense, UA_entry_tmp->UA_sense_buffer, len) == 0) {
++ TRACE_MGMT_DBG("%s", "UA already exists");
++ skip_UA = 1;
++ break;
++ }
++ }
++
++ if (skip_UA == 0)
++ scst_alloc_set_UA(tgt_dev, sense, len, flags);
++
++ TRACE_EXIT();
++ return;
++}
++
++void scst_check_set_UA(struct scst_tgt_dev *tgt_dev,
++ const uint8_t *sense, int sense_len, int flags)
++{
++ TRACE_ENTRY();
++
++ spin_lock_bh(&tgt_dev->tgt_dev_lock);
++ __scst_check_set_UA(tgt_dev, sense, sense_len, flags);
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called under dev_lock and BH off */
++void scst_dev_check_set_local_UA(struct scst_device *dev,
++ struct scst_cmd *exclude, const uint8_t *sense, int sense_len)
++{
++ struct scst_tgt_dev *tgt_dev, *exclude_tgt_dev = NULL;
++
++ TRACE_ENTRY();
++
++ if (exclude != NULL)
++ exclude_tgt_dev = exclude->tgt_dev;
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if (tgt_dev != exclude_tgt_dev)
++ scst_check_set_UA(tgt_dev, sense, sense_len, 0);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called under dev_lock and BH off */
++void __scst_dev_check_set_UA(struct scst_device *dev,
++ struct scst_cmd *exclude, const uint8_t *sense, int sense_len)
++{
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Processing UA dev %s", dev->virt_name);
++
++ /* Check for reset UA */
++ if (scst_analyze_sense(sense, sense_len, SCST_SENSE_ASC_VALID,
++ 0, SCST_SENSE_ASC_UA_RESET, 0))
++ scst_process_reset(dev,
++ (exclude != NULL) ? exclude->sess : NULL,
++ exclude, NULL, false);
++
++ scst_dev_check_set_local_UA(dev, exclude, sense, sense_len);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called under tgt_dev_lock or when tgt_dev is unused */
++static void scst_free_all_UA(struct scst_tgt_dev *tgt_dev)
++{
++ struct scst_tgt_dev_UA *UA_entry, *t;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry_safe(UA_entry, t,
++ &tgt_dev->UA_list, UA_list_entry) {
++ TRACE_MGMT_DBG("Clearing UA for tgt_dev LUN %lld",
++ (long long unsigned int)tgt_dev->lun);
++ list_del(&UA_entry->UA_list_entry);
++ mempool_free(UA_entry, scst_ua_mempool);
++ }
++ INIT_LIST_HEAD(&tgt_dev->UA_list);
++ clear_bit(SCST_TGT_DEV_UA_PENDING, &tgt_dev->tgt_dev_flags);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* No locks */
++struct scst_cmd *__scst_check_deferred_commands(struct scst_order_data *order_data)
++{
++ struct scst_cmd *res = NULL, *cmd, *t;
++ typeof(order_data->expected_sn) expected_sn = order_data->expected_sn;
++
++ spin_lock_irq(&order_data->sn_lock);
++
++ if (unlikely(order_data->hq_cmd_count != 0))
++ goto out_unlock;
++
++restart:
++ list_for_each_entry_safe(cmd, t, &order_data->deferred_cmd_list,
++ sn_cmd_list_entry) {
++ EXTRACHECKS_BUG_ON(cmd->queue_type ==
++ SCST_CMD_QUEUE_HEAD_OF_QUEUE);
++ if (cmd->sn == expected_sn) {
++ TRACE_SN("Deferred command %p (sn %d, set %d) found",
++ cmd, cmd->sn, cmd->sn_set);
++ order_data->def_cmd_count--;
++ list_del(&cmd->sn_cmd_list_entry);
++ if (res == NULL)
++ res = cmd;
++ else {
++ spin_lock(&cmd->cmd_threads->cmd_list_lock);
++ TRACE_SN("Adding cmd %p to active cmd list",
++ cmd);
++ list_add_tail(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock(&cmd->cmd_threads->cmd_list_lock);
++ }
++ }
++ }
++ if (res != NULL)
++ goto out_unlock;
++
++ list_for_each_entry(cmd, &order_data->skipped_sn_list,
++ sn_cmd_list_entry) {
++ EXTRACHECKS_BUG_ON(cmd->queue_type ==
++ SCST_CMD_QUEUE_HEAD_OF_QUEUE);
++ if (cmd->sn == expected_sn) {
++ atomic_t *slot = cmd->sn_slot;
++ /*
++ * !! At this point any pointer in cmd, except !!
++ * !! sn_slot and sn_cmd_list_entry, could be !!
++ * !! already destroyed !!
++ */
++ TRACE_SN("cmd %p (tag %llu) with skipped sn %d found",
++ cmd,
++ (long long unsigned int)cmd->tag,
++ cmd->sn);
++ order_data->def_cmd_count--;
++ list_del(&cmd->sn_cmd_list_entry);
++ spin_unlock_irq(&order_data->sn_lock);
++ if (test_and_set_bit(SCST_CMD_CAN_BE_DESTROYED,
++ &cmd->cmd_flags))
++ scst_destroy_put_cmd(cmd);
++ scst_inc_expected_sn(order_data, slot);
++ expected_sn = order_data->expected_sn;
++ spin_lock_irq(&order_data->sn_lock);
++ goto restart;
++ }
++ }
++
++out_unlock:
++ spin_unlock_irq(&order_data->sn_lock);
++ return res;
++}
++
++/*****************************************************************
++ ** The following thr_data functions are necessary, because the
++ ** kernel doesn't provide a better way to have threads local
++ ** storage
++ *****************************************************************/
++
++/**
++ * scst_add_thr_data() - add the current thread's local data
++ *
++ * Adds local to the current thread data to tgt_dev
++ * (they will be local for the tgt_dev and current thread).
++ */
++void scst_add_thr_data(struct scst_tgt_dev *tgt_dev,
++ struct scst_thr_data_hdr *data,
++ void (*free_fn) (struct scst_thr_data_hdr *data))
++{
++ data->owner_thr = current;
++ atomic_set(&data->ref, 1);
++ EXTRACHECKS_BUG_ON(free_fn == NULL);
++ data->free_fn = free_fn;
++ spin_lock(&tgt_dev->thr_data_lock);
++ list_add_tail(&data->thr_data_list_entry, &tgt_dev->thr_data_list);
++ spin_unlock(&tgt_dev->thr_data_lock);
++}
++EXPORT_SYMBOL_GPL(scst_add_thr_data);
++
++/**
++ * scst_del_all_thr_data() - delete all thread's local data
++ *
++ * Deletes all local to threads data from tgt_dev
++ */
++void scst_del_all_thr_data(struct scst_tgt_dev *tgt_dev)
++{
++ spin_lock(&tgt_dev->thr_data_lock);
++ while (!list_empty(&tgt_dev->thr_data_list)) {
++ struct scst_thr_data_hdr *d = list_entry(
++ tgt_dev->thr_data_list.next, typeof(*d),
++ thr_data_list_entry);
++ list_del(&d->thr_data_list_entry);
++ spin_unlock(&tgt_dev->thr_data_lock);
++ scst_thr_data_put(d);
++ spin_lock(&tgt_dev->thr_data_lock);
++ }
++ spin_unlock(&tgt_dev->thr_data_lock);
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_del_all_thr_data);
++
++/**
++ * scst_dev_del_all_thr_data() - delete all thread's local data from device
++ *
++ * Deletes all local to threads data from all tgt_dev's of the device
++ */
++void scst_dev_del_all_thr_data(struct scst_device *dev)
++{
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ scst_del_all_thr_data(tgt_dev);
++ }
++
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_dev_del_all_thr_data);
++
++/* thr_data_lock supposed to be held */
++static struct scst_thr_data_hdr *__scst_find_thr_data_locked(
++ struct scst_tgt_dev *tgt_dev, struct task_struct *tsk)
++{
++ struct scst_thr_data_hdr *res = NULL, *d;
++
++ list_for_each_entry(d, &tgt_dev->thr_data_list, thr_data_list_entry) {
++ if (d->owner_thr == tsk) {
++ res = d;
++ scst_thr_data_get(res);
++ break;
++ }
++ }
++ return res;
++}
++
++/**
++ * __scst_find_thr_data() - find local to the thread data
++ *
++ * Finds local to the thread data. Returns NULL, if they not found.
++ */
++struct scst_thr_data_hdr *__scst_find_thr_data(struct scst_tgt_dev *tgt_dev,
++ struct task_struct *tsk)
++{
++ struct scst_thr_data_hdr *res;
++
++ spin_lock(&tgt_dev->thr_data_lock);
++ res = __scst_find_thr_data_locked(tgt_dev, tsk);
++ spin_unlock(&tgt_dev->thr_data_lock);
++
++ return res;
++}
++EXPORT_SYMBOL_GPL(__scst_find_thr_data);
++
++bool scst_del_thr_data(struct scst_tgt_dev *tgt_dev, struct task_struct *tsk)
++{
++ bool res;
++ struct scst_thr_data_hdr *td;
++
++ spin_lock(&tgt_dev->thr_data_lock);
++
++ td = __scst_find_thr_data_locked(tgt_dev, tsk);
++ if (td != NULL) {
++ list_del(&td->thr_data_list_entry);
++ res = true;
++ } else
++ res = false;
++
++ spin_unlock(&tgt_dev->thr_data_lock);
++
++ if (td != NULL) {
++ /* the find() fn also gets it */
++ scst_thr_data_put(td);
++ scst_thr_data_put(td);
++ }
++
++ return res;
++}
++
++static void __scst_unblock_deferred(struct scst_order_data *order_data,
++ struct scst_cmd *out_of_sn_cmd)
++{
++ EXTRACHECKS_BUG_ON(!out_of_sn_cmd->sn_set);
++
++ if (out_of_sn_cmd->sn == order_data->expected_sn) {
++ scst_inc_expected_sn(order_data, out_of_sn_cmd->sn_slot);
++ scst_make_deferred_commands_active(order_data);
++ } else {
++ out_of_sn_cmd->out_of_sn = 1;
++ spin_lock_irq(&order_data->sn_lock);
++ order_data->def_cmd_count++;
++ list_add_tail(&out_of_sn_cmd->sn_cmd_list_entry,
++ &order_data->skipped_sn_list);
++ TRACE_SN("out_of_sn_cmd %p with sn %d added to skipped_sn_list"
++ " (expected_sn %d)", out_of_sn_cmd, out_of_sn_cmd->sn,
++ order_data->expected_sn);
++ spin_unlock_irq(&order_data->sn_lock);
++ }
++
++ return;
++}
++
++void scst_unblock_deferred(struct scst_order_data *order_data,
++ struct scst_cmd *out_of_sn_cmd)
++{
++ TRACE_ENTRY();
++
++ if (!out_of_sn_cmd->sn_set) {
++ TRACE_SN("cmd %p without sn", out_of_sn_cmd);
++ goto out;
++ }
++
++ __scst_unblock_deferred(order_data, out_of_sn_cmd);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* dev_lock supposed to be held and BH disabled */
++void scst_block_dev(struct scst_device *dev)
++{
++ dev->block_count++;
++ TRACE_MGMT_DBG("Device BLOCK (new count %d), dev %s", dev->block_count,
++ dev->virt_name);
++}
++
++/* dev_lock supposed to be held and BH disabled */
++bool __scst_check_blocked_dev(struct scst_cmd *cmd)
++{
++ int res = false;
++ struct scst_device *dev = cmd->dev;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(cmd->unblock_dev);
++
++ if (unlikely(cmd->internal) && (cmd->cdb[0] == REQUEST_SENSE)) {
++ /*
++ * The original command can already block the device, so
++ * REQUEST SENSE command should always pass.
++ */
++ goto out;
++ }
++
++ if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)))
++ goto out;
++
++ if (dev->block_count > 0) {
++ TRACE_MGMT_DBG("Delaying cmd %p due to blocking "
++ "(tag %llu, op %x, dev %s)", cmd,
++ (long long unsigned int)cmd->tag, cmd->cdb[0],
++ dev->virt_name);
++ goto out_block;
++ } else if (scst_is_strictly_serialized_cmd(cmd)) {
++ TRACE_MGMT_DBG("cmd %p (tag %llu, op %x): blocking further "
++ "cmds on dev %s due to strict serialization", cmd,
++ (long long unsigned int)cmd->tag, cmd->cdb[0],
++ dev->virt_name);
++ scst_block_dev(dev);
++ if (dev->on_dev_cmd_count > 1) {
++ TRACE_MGMT_DBG("Delaying strictly serialized cmd %p "
++ "(dev %s, on_dev_cmds to wait %d)", cmd,
++ dev->virt_name, dev->on_dev_cmd_count-1);
++ EXTRACHECKS_BUG_ON(dev->strictly_serialized_cmd_waiting);
++ dev->strictly_serialized_cmd_waiting = 1;
++ goto out_block;
++ } else
++ cmd->unblock_dev = 1;
++ } else if ((dev->dev_double_ua_possible) || scst_is_serialized_cmd(cmd)) {
++ TRACE_MGMT_DBG("cmd %p (tag %llu, op %x): blocking further cmds "
++ "on dev %s due to %s", cmd, (long long unsigned int)cmd->tag,
++ cmd->cdb[0], dev->virt_name,
++ dev->dev_double_ua_possible ? "possible double reset UA" :
++ "serialized cmd");
++ scst_block_dev(dev);
++ cmd->unblock_dev = 1;
++ } else
++ TRACE_MGMT_DBG("No blocks for device %s", dev->virt_name);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_block:
++ if (cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE)
++ list_add(&cmd->blocked_cmd_list_entry,
++ &dev->blocked_cmd_list);
++ else
++ list_add_tail(&cmd->blocked_cmd_list_entry,
++ &dev->blocked_cmd_list);
++ res = true;
++ goto out;
++}
++
++/* dev_lock supposed to be held and BH disabled */
++void scst_unblock_dev(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Device UNBLOCK(new %d), dev %s",
++ dev->block_count-1, dev->virt_name);
++
++#ifdef CONFIG_SMP
++ EXTRACHECKS_BUG_ON(!spin_is_locked(&dev->dev_lock));
++#endif
++
++ if (--dev->block_count == 0) {
++ struct scst_cmd *cmd, *tcmd;
++ unsigned long flags;
++
++ local_irq_save(flags);
++ list_for_each_entry_safe(cmd, tcmd, &dev->blocked_cmd_list,
++ blocked_cmd_list_entry) {
++ bool strictly_serialized;
++ list_del(&cmd->blocked_cmd_list_entry);
++ TRACE_MGMT_DBG("Adding blocked cmd %p to active cmd "
++ "list", cmd);
++ spin_lock(&cmd->cmd_threads->cmd_list_lock);
++ if (cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE)
++ list_add(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ else
++ list_add_tail(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ strictly_serialized = scst_is_strictly_serialized_cmd(cmd);
++ wake_up(&cmd->cmd_threads->cmd_list_waitQ);
++ spin_unlock(&cmd->cmd_threads->cmd_list_lock);
++ if (dev->strictly_serialized_cmd_waiting && strictly_serialized)
++ break;
++ }
++ local_irq_restore(flags);
++
++ dev->strictly_serialized_cmd_waiting = 0;
++ }
++
++ BUG_ON(dev->block_count < 0);
++
++ TRACE_EXIT();
++ return;
++}
++
++void scst_on_hq_cmd_response(struct scst_cmd *cmd)
++{
++ struct scst_order_data *order_data = cmd->cur_order_data;
++
++ TRACE_ENTRY();
++
++ if (!cmd->hq_cmd_inced)
++ goto out;
++
++ spin_lock_irq(&order_data->sn_lock);
++ order_data->hq_cmd_count--;
++ spin_unlock_irq(&order_data->sn_lock);
++
++ EXTRACHECKS_BUG_ON(order_data->hq_cmd_count < 0);
++
++ /*
++ * There is no problem in checking hq_cmd_count in the
++ * non-locked state. In the worst case we will only have
++ * unneeded run of the deferred commands.
++ */
++ if (order_data->hq_cmd_count == 0)
++ scst_make_deferred_commands_active(order_data);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++void scst_store_sense(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++ if (SCST_SENSE_VALID(cmd->sense) &&
++ !test_bit(SCST_CMD_NO_RESP, &cmd->cmd_flags) &&
++ (cmd->tgt_dev != NULL)) {
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++
++ TRACE_DBG("Storing sense (cmd %p)", cmd);
++
++ spin_lock_bh(&tgt_dev->tgt_dev_lock);
++
++ if (cmd->sense_valid_len <= sizeof(tgt_dev->tgt_dev_sense))
++ tgt_dev->tgt_dev_valid_sense_len = cmd->sense_valid_len;
++ else {
++ tgt_dev->tgt_dev_valid_sense_len = sizeof(tgt_dev->tgt_dev_sense);
++ PRINT_ERROR("Stored sense truncated to size %d "
++ "(needed %d)", tgt_dev->tgt_dev_valid_sense_len,
++ cmd->sense_valid_len);
++ }
++ memcpy(tgt_dev->tgt_dev_sense, cmd->sense,
++ tgt_dev->tgt_dev_valid_sense_len);
++
++ spin_unlock_bh(&tgt_dev->tgt_dev_lock);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++void scst_xmit_process_aborted_cmd(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Aborted cmd %p done (cmd_ref %d)", cmd,
++ atomic_read(&cmd->cmd_ref));
++
++ scst_done_cmd_mgmt(cmd);
++
++ if (test_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags)) {
++ if (cmd->completed) {
++ /* It's completed and it's OK to return its result */
++ goto out;
++ }
++
++ /* For not yet inited commands cmd->dev can be NULL here */
++ if (test_bit(SCST_CMD_DEVICE_TAS, &cmd->cmd_flags)) {
++ TRACE_MGMT_DBG("Flag ABORTED OTHER set for cmd %p "
++ "(tag %llu), returning TASK ABORTED ", cmd,
++ (long long unsigned int)cmd->tag);
++ scst_set_cmd_error_status(cmd, SAM_STAT_TASK_ABORTED);
++ } else {
++ TRACE_MGMT_DBG("Flag ABORTED OTHER set for cmd %p "
++ "(tag %llu), aborting without delivery or "
++ "notification",
++ cmd, (long long unsigned int)cmd->tag);
++ /*
++ * There is no need to check/requeue possible UA,
++ * because, if it exists, it will be delivered
++ * by the "completed" branch above.
++ */
++ clear_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags);
++ }
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_get_max_lun_commands() - return maximum supported commands count
++ *
++ * Returns maximum commands count which can be queued to this LUN in this
++ * session.
++ *
++ * If lun is NO_SUCH_LUN, returns minimum of maximum commands count which
++ * can be queued to any LUN in this session.
++ *
++ * If sess is NULL, returns minimum of maximum commands count which can be
++ * queued to any SCST device.
++ */
++int scst_get_max_lun_commands(struct scst_session *sess, uint64_t lun)
++{
++ return SCST_MAX_TGT_DEV_COMMANDS;
++}
++EXPORT_SYMBOL(scst_get_max_lun_commands);
++
++/**
++ * scst_reassign_persistent_sess_states() - reassigns persistent states
++ *
++ * Reassigns persistent states from old_sess to new_sess.
++ */
++void scst_reassign_persistent_sess_states(struct scst_session *new_sess,
++ struct scst_session *old_sess)
++{
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ TRACE_PR("Reassigning persistent states from old_sess %p to "
++ "new_sess %p", old_sess, new_sess);
++
++ if ((new_sess == NULL) || (old_sess == NULL)) {
++ TRACE_DBG("%s", "new_sess or old_sess is NULL");
++ goto out;
++ }
++
++ if (new_sess == old_sess) {
++ TRACE_DBG("%s", "new_sess or old_sess are the same");
++ goto out;
++ }
++
++ if ((new_sess->transport_id == NULL) ||
++ (old_sess->transport_id == NULL)) {
++ TRACE_DBG("%s", "new_sess or old_sess doesn't support PRs");
++ goto out;
++ }
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
++ struct scst_tgt_dev *tgt_dev;
++ struct scst_tgt_dev *new_tgt_dev = NULL, *old_tgt_dev = NULL;
++
++ TRACE_DBG("Processing dev %s", dev->virt_name);
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if (tgt_dev->sess == new_sess) {
++ new_tgt_dev = tgt_dev;
++ if (old_tgt_dev != NULL)
++ break;
++ }
++ if (tgt_dev->sess == old_sess) {
++ old_tgt_dev = tgt_dev;
++ if (new_tgt_dev != NULL)
++ break;
++ }
++ }
++
++ if ((new_tgt_dev == NULL) || (old_tgt_dev == NULL)) {
++ TRACE_DBG("new_tgt_dev %p or old_sess %p is NULL, "
++ "skipping (dev %s)", new_tgt_dev, old_tgt_dev,
++ dev->virt_name);
++ continue;
++ }
++
++ scst_pr_write_lock(dev);
++
++ if (old_tgt_dev->registrant != NULL) {
++ TRACE_PR("Reassigning reg %p from tgt_dev %p to %p",
++ old_tgt_dev->registrant, old_tgt_dev,
++ new_tgt_dev);
++
++ if (new_tgt_dev->registrant != NULL)
++ new_tgt_dev->registrant->tgt_dev = NULL;
++
++ new_tgt_dev->registrant = old_tgt_dev->registrant;
++ new_tgt_dev->registrant->tgt_dev = new_tgt_dev;
++
++ old_tgt_dev->registrant = NULL;
++ }
++
++ scst_pr_write_unlock(dev);
++ }
++
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL(scst_reassign_persistent_sess_states);
++
++/**
++ * scst_get_next_lexem() - parse and return next lexem in the string
++ *
++ * Returns pointer to the next lexem from token_str skipping
++ * spaces and '=' character and using them then as a delimeter. Content
++ * of token_str is modified by setting '\0' at the delimeter's position.
++ */
++char *scst_get_next_lexem(char **token_str)
++{
++ char *p = *token_str;
++ char *q;
++ static const char blank = '\0';
++
++ if ((token_str == NULL) || (*token_str == NULL))
++ return (char *)&blank;
++
++ for (p = *token_str; (*p != '\0') && (isspace(*p) || (*p == '=')); p++)
++ ;
++
++ for (q = p; (*q != '\0') && !isspace(*q) && (*q != '='); q++)
++ ;
++
++ if (*q != '\0')
++ *q++ = '\0';
++
++ *token_str = q;
++ return p;
++}
++EXPORT_SYMBOL_GPL(scst_get_next_lexem);
++
++/**
++ * scst_restore_token_str() - restore string modified by scst_get_next_lexem()
++ *
++ * Restores token_str modified by scst_get_next_lexem() to the
++ * previous value before scst_get_next_lexem() was called. Prev_lexem is
++ * a pointer to lexem returned by scst_get_next_lexem().
++ */
++void scst_restore_token_str(char *prev_lexem, char *token_str)
++{
++ if (&prev_lexem[strlen(prev_lexem)] != token_str)
++ prev_lexem[strlen(prev_lexem)] = ' ';
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_restore_token_str);
++
++/**
++ * scst_get_next_token_str() - parse and return next token
++ *
++ * This function returns pointer to the next token strings from input_str
++ * using '\n', ';' and '\0' as a delimeter. Content of input_str is
++ * modified by setting '\0' at the delimeter's position.
++ */
++char *scst_get_next_token_str(char **input_str)
++{
++ char *p = *input_str;
++ int i = 0;
++
++ while ((p[i] != '\n') && (p[i] != ';') && (p[i] != '\0'))
++ i++;
++
++ if (i == 0)
++ return NULL;
++
++ if (p[i] == '\0')
++ *input_str = &p[i];
++ else
++ *input_str = &p[i+1];
++
++ p[i] = '\0';
++
++ return p;
++}
++EXPORT_SYMBOL_GPL(scst_get_next_token_str);
++
++static void __init scst_scsi_op_list_init(void)
++{
++ int i;
++ uint8_t op = 0xff;
++
++ TRACE_ENTRY();
++
++ for (i = 0; i < 256; i++)
++ scst_scsi_op_list[i] = SCST_CDB_TBL_SIZE;
++
++ for (i = 0; i < SCST_CDB_TBL_SIZE; i++) {
++ if (scst_scsi_op_table[i].ops != op) {
++ op = scst_scsi_op_table[i].ops;
++ scst_scsi_op_list[op] = i;
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++int __init scst_lib_init(void)
++{
++ int res = 0;
++
++ scst_scsi_op_list_init();
++
++ scsi_io_context_cache = kmem_cache_create("scst_scsi_io_context",
++ sizeof(struct scsi_io_context),
++ 0, 0, NULL);
++ if (!scsi_io_context_cache) {
++ PRINT_ERROR("%s", "Can't init scsi io context cache");
++ res = -ENOMEM;
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++void scst_lib_exit(void)
++{
++ BUILD_BUG_ON(SCST_MAX_CDB_SIZE != BLK_MAX_CDB);
++ BUILD_BUG_ON(SCST_SENSE_BUFFERSIZE < SCSI_SENSE_BUFFERSIZE);
++
++ kmem_cache_destroy(scsi_io_context_cache);
++}
++
++#ifdef CONFIG_SCST_DEBUG
++
++/**
++ * scst_random() - return a pseudo-random number for debugging purposes.
++ *
++ * Returns a pseudo-random number for debugging purposes. Available only in
++ * the DEBUG build.
++ *
++ * Original taken from the XFS code
++ */
++unsigned long scst_random(void)
++{
++ static int Inited;
++ static unsigned long RandomValue;
++ static DEFINE_SPINLOCK(lock);
++ /* cycles pseudo-randomly through all values between 1 and 2^31 - 2 */
++ register long rv;
++ register long lo;
++ register long hi;
++ unsigned long flags;
++
++ spin_lock_irqsave(&lock, flags);
++ if (!Inited) {
++ RandomValue = jiffies;
++ Inited = 1;
++ }
++ rv = RandomValue;
++ hi = rv / 127773;
++ lo = rv % 127773;
++ rv = 16807 * lo - 2836 * hi;
++ if (rv <= 0)
++ rv += 2147483647;
++ RandomValue = rv;
++ spin_unlock_irqrestore(&lock, flags);
++ return rv;
++}
++EXPORT_SYMBOL_GPL(scst_random);
++#endif /* CONFIG_SCST_DEBUG */
++
++#ifdef CONFIG_SCST_DEBUG_TM
++
++#define TM_DBG_STATE_ABORT 0
++#define TM_DBG_STATE_RESET 1
++#define TM_DBG_STATE_OFFLINE 2
++
++#define INIT_TM_DBG_STATE TM_DBG_STATE_ABORT
++
++static void tm_dbg_timer_fn(unsigned long arg);
++
++static DEFINE_SPINLOCK(scst_tm_dbg_lock);
++/* All serialized by scst_tm_dbg_lock */
++static struct {
++ unsigned int tm_dbg_release:1;
++ unsigned int tm_dbg_blocked:1;
++} tm_dbg_flags;
++static LIST_HEAD(tm_dbg_delayed_cmd_list);
++static int tm_dbg_delayed_cmds_count;
++static int tm_dbg_passed_cmds_count;
++static int tm_dbg_state;
++static int tm_dbg_on_state_passes;
++static DEFINE_TIMER(tm_dbg_timer, tm_dbg_timer_fn, 0, 0);
++static struct scst_tgt_dev *tm_dbg_tgt_dev;
++
++static const int tm_dbg_on_state_num_passes[] = { 5, 1, 0x7ffffff };
++
++static void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev)
++{
++ if (tgt_dev->lun == 6) {
++ unsigned long flags;
++
++ if (tm_dbg_tgt_dev != NULL)
++ tm_dbg_deinit_tgt_dev(tm_dbg_tgt_dev);
++
++ spin_lock_irqsave(&scst_tm_dbg_lock, flags);
++ tm_dbg_state = INIT_TM_DBG_STATE;
++ tm_dbg_on_state_passes =
++ tm_dbg_on_state_num_passes[tm_dbg_state];
++ tm_dbg_tgt_dev = tgt_dev;
++ PRINT_INFO("LUN %lld connected from initiator %s is under "
++ "TM debugging (tgt_dev %p)",
++ (unsigned long long)tgt_dev->lun,
++ tgt_dev->sess->initiator_name, tgt_dev);
++ spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
++ }
++ return;
++}
++
++static void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev)
++{
++ if (tm_dbg_tgt_dev == tgt_dev) {
++ unsigned long flags;
++ TRACE_MGMT_DBG("Deinit TM debugging tgt_dev %p", tgt_dev);
++ del_timer_sync(&tm_dbg_timer);
++ spin_lock_irqsave(&scst_tm_dbg_lock, flags);
++ tm_dbg_tgt_dev = NULL;
++ spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
++ }
++ return;
++}
++
++static void tm_dbg_timer_fn(unsigned long arg)
++{
++ TRACE_MGMT_DBG("%s", "delayed cmd timer expired");
++ tm_dbg_flags.tm_dbg_release = 1;
++ /* Used to make sure that all woken up threads see the new value */
++ smp_wmb();
++ wake_up_all(&tm_dbg_tgt_dev->active_cmd_threads->cmd_list_waitQ);
++ return;
++}
++
++/* Called under scst_tm_dbg_lock and IRQs off */
++static void tm_dbg_delay_cmd(struct scst_cmd *cmd)
++{
++ switch (tm_dbg_state) {
++ case TM_DBG_STATE_ABORT:
++ if (tm_dbg_delayed_cmds_count == 0) {
++ unsigned long d = 58*HZ + (scst_random() % (4*HZ));
++ TRACE_MGMT_DBG("STATE ABORT: delaying cmd %p (tag %llu)"
++ " for %ld.%ld seconds (%ld HZ), "
++ "tm_dbg_on_state_passes=%d", cmd, cmd->tag,
++ d/HZ, (d%HZ)*100/HZ, d, tm_dbg_on_state_passes);
++ mod_timer(&tm_dbg_timer, jiffies + d);
++#if 0
++ tm_dbg_flags.tm_dbg_blocked = 1;
++#endif
++ } else {
++ TRACE_MGMT_DBG("Delaying another timed cmd %p "
++ "(tag %llu), delayed_cmds_count=%d, "
++ "tm_dbg_on_state_passes=%d", cmd, cmd->tag,
++ tm_dbg_delayed_cmds_count,
++ tm_dbg_on_state_passes);
++ if (tm_dbg_delayed_cmds_count == 2)
++ tm_dbg_flags.tm_dbg_blocked = 0;
++ }
++ break;
++
++ case TM_DBG_STATE_RESET:
++ case TM_DBG_STATE_OFFLINE:
++ TRACE_MGMT_DBG("STATE RESET/OFFLINE: delaying cmd %p "
++ "(tag %llu), delayed_cmds_count=%d, "
++ "tm_dbg_on_state_passes=%d", cmd, cmd->tag,
++ tm_dbg_delayed_cmds_count, tm_dbg_on_state_passes);
++ tm_dbg_flags.tm_dbg_blocked = 1;
++ break;
++
++ default:
++ BUG();
++ }
++ /* IRQs already off */
++ spin_lock(&cmd->cmd_threads->cmd_list_lock);
++ list_add_tail(&cmd->cmd_list_entry, &tm_dbg_delayed_cmd_list);
++ spin_unlock(&cmd->cmd_threads->cmd_list_lock);
++ cmd->tm_dbg_delayed = 1;
++ tm_dbg_delayed_cmds_count++;
++ return;
++}
++
++/* No locks */
++void tm_dbg_check_released_cmds(void)
++{
++ if (tm_dbg_flags.tm_dbg_release) {
++ struct scst_cmd *cmd, *tc;
++ spin_lock_irq(&scst_tm_dbg_lock);
++ list_for_each_entry_safe_reverse(cmd, tc,
++ &tm_dbg_delayed_cmd_list, cmd_list_entry) {
++ TRACE_MGMT_DBG("Releasing timed cmd %p (tag %llu), "
++ "delayed_cmds_count=%d", cmd, cmd->tag,
++ tm_dbg_delayed_cmds_count);
++ spin_lock(&cmd->cmd_threads->cmd_list_lock);
++ list_move(&cmd->cmd_list_entry,
++ &cmd->cmd_threads->active_cmd_list);
++ spin_unlock(&cmd->cmd_threads->cmd_list_lock);
++ }
++ tm_dbg_flags.tm_dbg_release = 0;
++ spin_unlock_irq(&scst_tm_dbg_lock);
++ }
++}
++
++/* Called under scst_tm_dbg_lock */
++static void tm_dbg_change_state(void)
++{
++ tm_dbg_flags.tm_dbg_blocked = 0;
++ if (--tm_dbg_on_state_passes == 0) {
++ switch (tm_dbg_state) {
++ case TM_DBG_STATE_ABORT:
++ TRACE_MGMT_DBG("%s", "Changing "
++ "tm_dbg_state to RESET");
++ tm_dbg_state = TM_DBG_STATE_RESET;
++ tm_dbg_flags.tm_dbg_blocked = 0;
++ break;
++ case TM_DBG_STATE_RESET:
++ case TM_DBG_STATE_OFFLINE:
++#ifdef CONFIG_SCST_TM_DBG_GO_OFFLINE
++ TRACE_MGMT_DBG("%s", "Changing "
++ "tm_dbg_state to OFFLINE");
++ tm_dbg_state = TM_DBG_STATE_OFFLINE;
++#else
++ TRACE_MGMT_DBG("%s", "Changing "
++ "tm_dbg_state to ABORT");
++ tm_dbg_state = TM_DBG_STATE_ABORT;
++#endif
++ break;
++ default:
++ BUG();
++ }
++ tm_dbg_on_state_passes =
++ tm_dbg_on_state_num_passes[tm_dbg_state];
++ }
++
++ TRACE_MGMT_DBG("%s", "Deleting timer");
++ del_timer_sync(&tm_dbg_timer);
++ return;
++}
++
++/* No locks */
++int tm_dbg_check_cmd(struct scst_cmd *cmd)
++{
++ int res = 0;
++ unsigned long flags;
++
++ if (cmd->tm_dbg_immut)
++ goto out;
++
++ if (cmd->tm_dbg_delayed) {
++ spin_lock_irqsave(&scst_tm_dbg_lock, flags);
++ TRACE_MGMT_DBG("Processing delayed cmd %p (tag %llu), "
++ "delayed_cmds_count=%d", cmd, cmd->tag,
++ tm_dbg_delayed_cmds_count);
++
++ cmd->tm_dbg_immut = 1;
++ tm_dbg_delayed_cmds_count--;
++ if ((tm_dbg_delayed_cmds_count == 0) &&
++ (tm_dbg_state == TM_DBG_STATE_ABORT))
++ tm_dbg_change_state();
++ spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
++ } else if (cmd->tgt_dev && (tm_dbg_tgt_dev == cmd->tgt_dev)) {
++ /* Delay 50th command */
++ spin_lock_irqsave(&scst_tm_dbg_lock, flags);
++ if (tm_dbg_flags.tm_dbg_blocked ||
++ (++tm_dbg_passed_cmds_count % 50) == 0) {
++ tm_dbg_delay_cmd(cmd);
++ res = 1;
++ } else
++ cmd->tm_dbg_immut = 1;
++ spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
++ }
++
++out:
++ return res;
++}
++
++/* No locks */
++void tm_dbg_release_cmd(struct scst_cmd *cmd)
++{
++ struct scst_cmd *c;
++ unsigned long flags;
++
++ spin_lock_irqsave(&scst_tm_dbg_lock, flags);
++ list_for_each_entry(c, &tm_dbg_delayed_cmd_list,
++ cmd_list_entry) {
++ if (c == cmd) {
++ TRACE_MGMT_DBG("Abort request for "
++ "delayed cmd %p (tag=%llu), moving it to "
++ "active cmd list (delayed_cmds_count=%d)",
++ c, c->tag, tm_dbg_delayed_cmds_count);
++
++ if (!test_bit(SCST_CMD_ABORTED_OTHER,
++ &cmd->cmd_flags)) {
++ /* Test how completed commands handled */
++ if (((scst_random() % 10) == 5)) {
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(
++ scst_sense_hardw_error));
++ /* It's completed now */
++ }
++ }
++
++ spin_lock(&cmd->cmd_threads->cmd_list_lock);
++ list_move(&c->cmd_list_entry,
++ &c->cmd_threads->active_cmd_list);
++ wake_up(&c->cmd_threads->cmd_list_waitQ);
++ spin_unlock(&cmd->cmd_threads->cmd_list_lock);
++ break;
++ }
++ }
++ spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
++ return;
++}
++
++/* Might be called under scst_mutex */
++void tm_dbg_task_mgmt(struct scst_device *dev, const char *fn, int force)
++{
++ unsigned long flags;
++
++ if (dev != NULL) {
++ if (tm_dbg_tgt_dev == NULL)
++ goto out;
++
++ if (tm_dbg_tgt_dev->dev != dev)
++ goto out;
++ }
++
++ spin_lock_irqsave(&scst_tm_dbg_lock, flags);
++ if ((tm_dbg_state != TM_DBG_STATE_OFFLINE) || force) {
++ TRACE_MGMT_DBG("%s: freeing %d delayed cmds", fn,
++ tm_dbg_delayed_cmds_count);
++ tm_dbg_change_state();
++ tm_dbg_flags.tm_dbg_release = 1;
++ /*
++ * Used to make sure that all woken up threads see the new
++ * value.
++ */
++ smp_wmb();
++ if (tm_dbg_tgt_dev != NULL)
++ wake_up_all(&tm_dbg_tgt_dev->active_cmd_threads->cmd_list_waitQ);
++ } else {
++ TRACE_MGMT_DBG("%s: while OFFLINE state, doing nothing", fn);
++ }
++ spin_unlock_irqrestore(&scst_tm_dbg_lock, flags);
++
++out:
++ return;
++}
++
++int tm_dbg_is_release(void)
++{
++ return tm_dbg_flags.tm_dbg_release;
++}
++#endif /* CONFIG_SCST_DEBUG_TM */
++
++#ifdef CONFIG_SCST_DEBUG_SN
++void scst_check_debug_sn(struct scst_cmd *cmd)
++{
++ static DEFINE_SPINLOCK(lock);
++ static int type;
++ static int cnt;
++ unsigned long flags;
++ int old = cmd->queue_type;
++
++ spin_lock_irqsave(&lock, flags);
++
++ if (cnt == 0) {
++ if ((scst_random() % 1000) == 500) {
++ if ((scst_random() % 3) == 1)
++ type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
++ else
++ type = SCST_CMD_QUEUE_ORDERED;
++ do {
++ cnt = scst_random() % 10;
++ } while (cnt == 0);
++ } else
++ goto out_unlock;
++ }
++
++ cmd->queue_type = type;
++ cnt--;
++
++ if (((scst_random() % 1000) == 750))
++ cmd->queue_type = SCST_CMD_QUEUE_ORDERED;
++ else if (((scst_random() % 1000) == 751))
++ cmd->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
++ else if (((scst_random() % 1000) == 752))
++ cmd->queue_type = SCST_CMD_QUEUE_SIMPLE;
++
++ TRACE_SN("DbgSN changed cmd %p: %d/%d (cnt %d)", cmd, old,
++ cmd->queue_type, cnt);
++
++out_unlock:
++ spin_unlock_irqrestore(&lock, flags);
++ return;
++}
++#endif /* CONFIG_SCST_DEBUG_SN */
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++
++static uint64_t scst_get_nsec(void)
++{
++ struct timespec ts;
++ ktime_get_ts(&ts);
++ return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
++}
++
++void scst_set_start_time(struct scst_cmd *cmd)
++{
++ cmd->start = scst_get_nsec();
++ TRACE_DBG("cmd %p: start %lld", cmd, cmd->start);
++}
++
++void scst_set_cur_start(struct scst_cmd *cmd)
++{
++ cmd->curr_start = scst_get_nsec();
++ TRACE_DBG("cmd %p: cur_start %lld", cmd, cmd->curr_start);
++}
++
++void scst_set_parse_time(struct scst_cmd *cmd)
++{
++ cmd->parse_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: parse_time %lld", cmd, cmd->parse_time);
++}
++
++void scst_set_alloc_buf_time(struct scst_cmd *cmd)
++{
++ cmd->alloc_buf_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: alloc_buf_time %lld", cmd, cmd->alloc_buf_time);
++}
++
++void scst_set_restart_waiting_time(struct scst_cmd *cmd)
++{
++ cmd->restart_waiting_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: restart_waiting_time %lld", cmd,
++ cmd->restart_waiting_time);
++}
++
++void scst_set_rdy_to_xfer_time(struct scst_cmd *cmd)
++{
++ cmd->rdy_to_xfer_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: rdy_to_xfer_time %lld", cmd, cmd->rdy_to_xfer_time);
++}
++
++void scst_set_pre_exec_time(struct scst_cmd *cmd)
++{
++ cmd->pre_exec_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: pre_exec_time %lld", cmd, cmd->pre_exec_time);
++}
++
++void scst_set_exec_time(struct scst_cmd *cmd)
++{
++ cmd->exec_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: exec_time %lld", cmd, cmd->exec_time);
++}
++
++void scst_set_dev_done_time(struct scst_cmd *cmd)
++{
++ cmd->dev_done_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: dev_done_time %lld", cmd, cmd->dev_done_time);
++}
++
++void scst_set_xmit_time(struct scst_cmd *cmd)
++{
++ cmd->xmit_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: xmit_time %lld", cmd, cmd->xmit_time);
++}
++
++void scst_set_tgt_on_free_time(struct scst_cmd *cmd)
++{
++ cmd->tgt_on_free_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: tgt_on_free_time %lld", cmd, cmd->tgt_on_free_time);
++}
++
++void scst_set_dev_on_free_time(struct scst_cmd *cmd)
++{
++ cmd->dev_on_free_time += scst_get_nsec() - cmd->curr_start;
++ TRACE_DBG("cmd %p: dev_on_free_time %lld", cmd, cmd->dev_on_free_time);
++}
++
++void scst_update_lat_stats(struct scst_cmd *cmd)
++{
++ uint64_t finish, scst_time, tgt_time, dev_time;
++ struct scst_session *sess = cmd->sess;
++ int data_len;
++ int i;
++ struct scst_ext_latency_stat *latency_stat, *dev_latency_stat;
++
++ finish = scst_get_nsec();
++
++ /* Determine the IO size for extended latency statistics */
++ data_len = cmd->bufflen;
++ i = SCST_LATENCY_STAT_INDEX_OTHER;
++ if (data_len <= SCST_IO_SIZE_THRESHOLD_SMALL)
++ i = SCST_LATENCY_STAT_INDEX_SMALL;
++ else if (data_len <= SCST_IO_SIZE_THRESHOLD_MEDIUM)
++ i = SCST_LATENCY_STAT_INDEX_MEDIUM;
++ else if (data_len <= SCST_IO_SIZE_THRESHOLD_LARGE)
++ i = SCST_LATENCY_STAT_INDEX_LARGE;
++ else if (data_len <= SCST_IO_SIZE_THRESHOLD_VERY_LARGE)
++ i = SCST_LATENCY_STAT_INDEX_VERY_LARGE;
++ latency_stat = &sess->sess_latency_stat[i];
++ if (cmd->tgt_dev != NULL)
++ dev_latency_stat = &cmd->tgt_dev->dev_latency_stat[i];
++ else
++ dev_latency_stat = NULL;
++
++ /* Calculate the latencies */
++ scst_time = finish - cmd->start - (cmd->parse_time +
++ cmd->alloc_buf_time + cmd->restart_waiting_time +
++ cmd->rdy_to_xfer_time + cmd->pre_exec_time +
++ cmd->exec_time + cmd->dev_done_time + cmd->xmit_time +
++ cmd->tgt_on_free_time + cmd->dev_on_free_time);
++ tgt_time = cmd->alloc_buf_time + cmd->restart_waiting_time +
++ cmd->rdy_to_xfer_time + cmd->pre_exec_time +
++ cmd->xmit_time + cmd->tgt_on_free_time;
++ dev_time = cmd->parse_time + cmd->exec_time + cmd->dev_done_time +
++ cmd->dev_on_free_time;
++
++ spin_lock_bh(&sess->lat_lock);
++
++ /* Save the basic latency information */
++ sess->scst_time += scst_time;
++ sess->tgt_time += tgt_time;
++ sess->dev_time += dev_time;
++ sess->processed_cmds++;
++
++ if ((sess->min_scst_time == 0) ||
++ (sess->min_scst_time > scst_time))
++ sess->min_scst_time = scst_time;
++ if ((sess->min_tgt_time == 0) ||
++ (sess->min_tgt_time > tgt_time))
++ sess->min_tgt_time = tgt_time;
++ if ((sess->min_dev_time == 0) ||
++ (sess->min_dev_time > dev_time))
++ sess->min_dev_time = dev_time;
++
++ if (sess->max_scst_time < scst_time)
++ sess->max_scst_time = scst_time;
++ if (sess->max_tgt_time < tgt_time)
++ sess->max_tgt_time = tgt_time;
++ if (sess->max_dev_time < dev_time)
++ sess->max_dev_time = dev_time;
++
++ /* Save the extended latency information */
++ if (cmd->data_direction & SCST_DATA_READ) {
++ latency_stat->scst_time_rd += scst_time;
++ latency_stat->tgt_time_rd += tgt_time;
++ latency_stat->dev_time_rd += dev_time;
++ latency_stat->processed_cmds_rd++;
++
++ if ((latency_stat->min_scst_time_rd == 0) ||
++ (latency_stat->min_scst_time_rd > scst_time))
++ latency_stat->min_scst_time_rd = scst_time;
++ if ((latency_stat->min_tgt_time_rd == 0) ||
++ (latency_stat->min_tgt_time_rd > tgt_time))
++ latency_stat->min_tgt_time_rd = tgt_time;
++ if ((latency_stat->min_dev_time_rd == 0) ||
++ (latency_stat->min_dev_time_rd > dev_time))
++ latency_stat->min_dev_time_rd = dev_time;
++
++ if (latency_stat->max_scst_time_rd < scst_time)
++ latency_stat->max_scst_time_rd = scst_time;
++ if (latency_stat->max_tgt_time_rd < tgt_time)
++ latency_stat->max_tgt_time_rd = tgt_time;
++ if (latency_stat->max_dev_time_rd < dev_time)
++ latency_stat->max_dev_time_rd = dev_time;
++
++ if (dev_latency_stat != NULL) {
++ dev_latency_stat->scst_time_rd += scst_time;
++ dev_latency_stat->tgt_time_rd += tgt_time;
++ dev_latency_stat->dev_time_rd += dev_time;
++ dev_latency_stat->processed_cmds_rd++;
++
++ if ((dev_latency_stat->min_scst_time_rd == 0) ||
++ (dev_latency_stat->min_scst_time_rd > scst_time))
++ dev_latency_stat->min_scst_time_rd = scst_time;
++ if ((dev_latency_stat->min_tgt_time_rd == 0) ||
++ (dev_latency_stat->min_tgt_time_rd > tgt_time))
++ dev_latency_stat->min_tgt_time_rd = tgt_time;
++ if ((dev_latency_stat->min_dev_time_rd == 0) ||
++ (dev_latency_stat->min_dev_time_rd > dev_time))
++ dev_latency_stat->min_dev_time_rd = dev_time;
++
++ if (dev_latency_stat->max_scst_time_rd < scst_time)
++ dev_latency_stat->max_scst_time_rd = scst_time;
++ if (dev_latency_stat->max_tgt_time_rd < tgt_time)
++ dev_latency_stat->max_tgt_time_rd = tgt_time;
++ if (dev_latency_stat->max_dev_time_rd < dev_time)
++ dev_latency_stat->max_dev_time_rd = dev_time;
++ }
++ } else if (cmd->data_direction & SCST_DATA_WRITE) {
++ latency_stat->scst_time_wr += scst_time;
++ latency_stat->tgt_time_wr += tgt_time;
++ latency_stat->dev_time_wr += dev_time;
++ latency_stat->processed_cmds_wr++;
++
++ if ((latency_stat->min_scst_time_wr == 0) ||
++ (latency_stat->min_scst_time_wr > scst_time))
++ latency_stat->min_scst_time_wr = scst_time;
++ if ((latency_stat->min_tgt_time_wr == 0) ||
++ (latency_stat->min_tgt_time_wr > tgt_time))
++ latency_stat->min_tgt_time_wr = tgt_time;
++ if ((latency_stat->min_dev_time_wr == 0) ||
++ (latency_stat->min_dev_time_wr > dev_time))
++ latency_stat->min_dev_time_wr = dev_time;
++
++ if (latency_stat->max_scst_time_wr < scst_time)
++ latency_stat->max_scst_time_wr = scst_time;
++ if (latency_stat->max_tgt_time_wr < tgt_time)
++ latency_stat->max_tgt_time_wr = tgt_time;
++ if (latency_stat->max_dev_time_wr < dev_time)
++ latency_stat->max_dev_time_wr = dev_time;
++
++ if (dev_latency_stat != NULL) {
++ dev_latency_stat->scst_time_wr += scst_time;
++ dev_latency_stat->tgt_time_wr += tgt_time;
++ dev_latency_stat->dev_time_wr += dev_time;
++ dev_latency_stat->processed_cmds_wr++;
++
++ if ((dev_latency_stat->min_scst_time_wr == 0) ||
++ (dev_latency_stat->min_scst_time_wr > scst_time))
++ dev_latency_stat->min_scst_time_wr = scst_time;
++ if ((dev_latency_stat->min_tgt_time_wr == 0) ||
++ (dev_latency_stat->min_tgt_time_wr > tgt_time))
++ dev_latency_stat->min_tgt_time_wr = tgt_time;
++ if ((dev_latency_stat->min_dev_time_wr == 0) ||
++ (dev_latency_stat->min_dev_time_wr > dev_time))
++ dev_latency_stat->min_dev_time_wr = dev_time;
++
++ if (dev_latency_stat->max_scst_time_wr < scst_time)
++ dev_latency_stat->max_scst_time_wr = scst_time;
++ if (dev_latency_stat->max_tgt_time_wr < tgt_time)
++ dev_latency_stat->max_tgt_time_wr = tgt_time;
++ if (dev_latency_stat->max_dev_time_wr < dev_time)
++ dev_latency_stat->max_dev_time_wr = dev_time;
++ }
++ }
++
++ spin_unlock_bh(&sess->lat_lock);
++
++ TRACE_DBG("cmd %p: finish %lld, scst_time %lld, "
++ "tgt_time %lld, dev_time %lld", cmd, finish, scst_time,
++ tgt_time, dev_time);
++ return;
++}
++
++#endif /* CONFIG_SCST_MEASURE_LATENCY */
+diff -uprN orig/linux-3.2/drivers/scst/scst_pres.h linux-3.2/drivers/scst/scst_pres.h
+--- orig/linux-3.2/drivers/scst/scst_pres.h
++++ linux-3.2/drivers/scst/scst_pres.h
+@@ -0,0 +1,234 @@
++/*
++ * scst_pres.c
++ *
++ * Copyright (C) 2009 - 2010 Alexey Obitotskiy <alexeyo1@open-e.com>
++ * Copyright (C) 2009 - 2010 Open-E, Inc.
++ * Copyright (C) 2009 - 2011 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.
++ */
++
++#ifndef SCST_PRES_H_
++#define SCST_PRES_H_
++
++#include <linux/delay.h>
++
++#define PR_REGISTER 0x00
++#define PR_RESERVE 0x01
++#define PR_RELEASE 0x02
++#define PR_CLEAR 0x03
++#define PR_PREEMPT 0x04
++#define PR_PREEMPT_AND_ABORT 0x05
++#define PR_REGISTER_AND_IGNORE 0x06
++#define PR_REGISTER_AND_MOVE 0x07
++
++#define PR_READ_KEYS 0x00
++#define PR_READ_RESERVATION 0x01
++#define PR_REPORT_CAPS 0x02
++#define PR_READ_FULL_STATUS 0x03
++
++#define TYPE_UNSPECIFIED (-1)
++#define TYPE_WRITE_EXCLUSIVE 0x01
++#define TYPE_EXCLUSIVE_ACCESS 0x03
++#define TYPE_WRITE_EXCLUSIVE_REGONLY 0x05
++#define TYPE_EXCLUSIVE_ACCESS_REGONLY 0x06
++#define TYPE_WRITE_EXCLUSIVE_ALL_REG 0x07
++#define TYPE_EXCLUSIVE_ACCESS_ALL_REG 0x08
++
++#define SCOPE_LU 0x00
++
++static inline void scst_inc_pr_readers_count(struct scst_cmd *cmd,
++ bool locked)
++{
++ struct scst_device *dev = cmd->dev;
++
++ EXTRACHECKS_BUG_ON(cmd->dec_pr_readers_count_needed);
++
++ if (!locked)
++ spin_lock_bh(&dev->dev_lock);
++
++#ifdef CONFIG_SMP
++ EXTRACHECKS_BUG_ON(!spin_is_locked(&dev->dev_lock));
++#endif
++
++ dev->pr_readers_count++;
++ cmd->dec_pr_readers_count_needed = 1;
++ TRACE_DBG("New inc pr_readers_count %d (cmd %p)", dev->pr_readers_count,
++ cmd);
++
++ if (!locked)
++ spin_unlock_bh(&dev->dev_lock);
++ return;
++}
++
++static inline void scst_dec_pr_readers_count(struct scst_cmd *cmd,
++ bool locked)
++{
++ struct scst_device *dev = cmd->dev;
++
++ if (unlikely(!cmd->dec_pr_readers_count_needed)) {
++ PRINT_ERROR("scst_check_local_events() should not be called "
++ "twice (cmd %p, op %x)! Use "
++ "scst_pre_check_local_events() instead.", cmd,
++ cmd->cdb[0]);
++ WARN_ON(1);
++ goto out;
++ }
++
++ if (!locked)
++ spin_lock_bh(&dev->dev_lock);
++
++#ifdef CONFIG_SMP
++ EXTRACHECKS_BUG_ON(!spin_is_locked(&dev->dev_lock));
++#endif
++
++ dev->pr_readers_count--;
++ cmd->dec_pr_readers_count_needed = 0;
++ TRACE_DBG("New dec pr_readers_count %d (cmd %p)", dev->pr_readers_count,
++ cmd);
++
++ if (!locked)
++ spin_unlock_bh(&dev->dev_lock);
++
++out:
++ EXTRACHECKS_BUG_ON(dev->pr_readers_count < 0);
++ return;
++}
++
++static inline void scst_reset_requeued_cmd(struct scst_cmd *cmd)
++{
++ TRACE_DBG("Reset requeued cmd %p (op %x)", cmd, cmd->cdb[0]);
++ scst_inc_pr_readers_count(cmd, false);
++ cmd->check_local_events_once_done = 0;
++ return;
++}
++
++static inline bool scst_pr_type_valid(uint8_t type)
++{
++ switch (type) {
++ case TYPE_WRITE_EXCLUSIVE:
++ case TYPE_EXCLUSIVE_ACCESS:
++ case TYPE_WRITE_EXCLUSIVE_REGONLY:
++ case TYPE_EXCLUSIVE_ACCESS_REGONLY:
++ case TYPE_WRITE_EXCLUSIVE_ALL_REG:
++ case TYPE_EXCLUSIVE_ACCESS_ALL_REG:
++ return true;
++ default:
++ return false;
++ }
++}
++
++static inline bool scst_pr_read_lock(struct scst_cmd *cmd)
++{
++ struct scst_device *dev = cmd->dev;
++ bool unlock = false;
++
++ TRACE_ENTRY();
++
++ smp_mb(); /* to sync with scst_pr_write_lock() */
++ if (unlikely(dev->pr_writer_active)) {
++ unlock = true;
++ scst_dec_pr_readers_count(cmd, false);
++ mutex_lock(&dev->dev_pr_mutex);
++ }
++
++ TRACE_EXIT_RES(unlock);
++ return unlock;
++}
++
++static inline void scst_pr_read_unlock(struct scst_cmd *cmd, bool unlock)
++{
++ struct scst_device *dev = cmd->dev;
++
++ TRACE_ENTRY();
++
++ if (unlikely(unlock))
++ mutex_unlock(&dev->dev_pr_mutex);
++ else
++ scst_dec_pr_readers_count(cmd, false);
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline void scst_pr_write_lock(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&dev->dev_pr_mutex);
++
++ dev->pr_writer_active = 1;
++ /* to sync with scst_pr_read_lock() and unlock() */
++ smp_mb();
++
++ while (true) {
++ int readers;
++ spin_lock_bh(&dev->dev_lock);
++ readers = dev->pr_readers_count;
++ spin_unlock_bh(&dev->dev_lock);
++ if (readers == 0)
++ break;
++ TRACE_DBG("Waiting for %d readers (dev %p)", readers, dev);
++ msleep(1);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline void scst_pr_write_unlock(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ dev->pr_writer_active = 0;
++ mutex_unlock(&dev->dev_pr_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++int scst_pr_init_dev(struct scst_device *dev);
++void scst_pr_clear_dev(struct scst_device *dev);
++
++int scst_pr_init_tgt_dev(struct scst_tgt_dev *tgt_dev);
++void scst_pr_clear_tgt_dev(struct scst_tgt_dev *tgt_dev);
++
++bool scst_pr_crh_case(struct scst_cmd *cmd);
++bool scst_pr_is_cmd_allowed(struct scst_cmd *cmd);
++
++void scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size);
++void scst_pr_register_and_ignore(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size);
++void scst_pr_register_and_move(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size);
++void scst_pr_reserve(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size);
++void scst_pr_release(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size);
++void scst_pr_clear(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size);
++void scst_pr_preempt(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size);
++void scst_pr_preempt_and_abort(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size);
++
++void scst_pr_read_keys(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size);
++void scst_pr_read_reservation(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size);
++void scst_pr_report_caps(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size);
++void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size);
++
++void scst_pr_sync_device_file(struct scst_tgt_dev *tgt_dev, struct scst_cmd *cmd);
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++void scst_pr_dump_prs(struct scst_device *dev, bool force);
++#else
++static inline void scst_pr_dump_prs(struct scst_device *dev, bool force) {}
++#endif
++
++#endif /* SCST_PRES_H_ */
+diff -uprN orig/linux-3.2/drivers/scst/scst_pres.c linux-3.2/drivers/scst/scst_pres.c
+--- orig/linux-3.2/drivers/scst/scst_pres.c
++++ linux-3.2/drivers/scst/scst_pres.c
+@@ -0,0 +1,2636 @@
++/*
++ * scst_pres.c
++ *
++ * Copyright (C) 2009 - 2010 Alexey Obitotskiy <alexeyo1@open-e.com>
++ * Copyright (C) 2009 - 2010 Open-E, Inc.
++ * Copyright (C) 2009 - 2011 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 <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/unistd.h>
++#include <linux/string.h>
++#include <linux/kthread.h>
++#include <linux/delay.h>
++#include <linux/time.h>
++#include <linux/ctype.h>
++#include <asm/byteorder.h>
++#include <linux/syscalls.h>
++#include <linux/file.h>
++#include <linux/fs.h>
++#include <linux/fcntl.h>
++#include <linux/uaccess.h>
++#include <linux/namei.h>
++#include <linux/vmalloc.h>
++#include <asm/unaligned.h>
++
++#include <scst/scst.h>
++#include <scst/scst_const.h>
++#include "scst_priv.h"
++#include "scst_pres.h"
++
++#define SCST_PR_ROOT_ENTRY "pr"
++#define SCST_PR_FILE_SIGN 0xBBEEEEAAEEBBDD77LLU
++#define SCST_PR_FILE_VERSION 1LLU
++
++#define FILE_BUFFER_SIZE 512
++
++#ifndef isblank
++#define isblank(c) ((c) == ' ' || (c) == '\t')
++#endif
++
++static inline int tid_size(const uint8_t *tid)
++{
++ BUG_ON(tid == NULL);
++
++ if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI)
++ return be16_to_cpu(get_unaligned((__be16 *)&tid[2])) + 4;
++ else
++ return TID_COMMON_SIZE;
++}
++
++/* Secures tid by setting 0 in the last byte of NULL-terminated tid's */
++static inline void tid_secure(uint8_t *tid)
++{
++ if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) {
++ int size = tid_size(tid);
++ tid[size - 1] = '\0';
++ }
++
++ return;
++}
++
++/* Returns false if tid's are not equal, true otherwise */
++static bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b)
++{
++ int len;
++
++ if (tid_a == NULL || tid_b == NULL)
++ return false;
++
++ if ((tid_a[0] & 0x0f) != (tid_b[0] & 0x0f)) {
++ TRACE_DBG("%s", "Different protocol IDs");
++ return false;
++ }
++
++ if ((tid_a[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) {
++ const uint8_t tid_a_fmt = tid_a[0] & 0xc0;
++ const uint8_t tid_b_fmt = tid_b[0] & 0xc0;
++ int tid_a_len, tid_a_max = tid_size(tid_a) - 4;
++ int tid_b_len, tid_b_max = tid_size(tid_b) - 4;
++ int i;
++
++ tid_a += 4;
++ tid_b += 4;
++
++ if (tid_a_fmt == 0x00)
++ tid_a_len = strnlen(tid_a, tid_a_max);
++ else if (tid_a_fmt == 0x40) {
++ if (tid_a_fmt != tid_b_fmt) {
++ uint8_t *p = strnchr(tid_a, tid_a_max, ',');
++ if (p == NULL)
++ goto out_error;
++ tid_a_len = p - tid_a;
++
++ BUG_ON(tid_a_len > tid_a_max);
++ BUG_ON(tid_a_len < 0);
++ } else
++ tid_a_len = strnlen(tid_a, tid_a_max);
++ } else
++ goto out_error;
++
++ if (tid_b_fmt == 0x00)
++ tid_b_len = strnlen(tid_b, tid_b_max);
++ else if (tid_b_fmt == 0x40) {
++ if (tid_a_fmt != tid_b_fmt) {
++ uint8_t *p = strnchr(tid_b, tid_b_max, ',');
++ if (p == NULL)
++ goto out_error;
++ tid_b_len = p - tid_b;
++
++ BUG_ON(tid_b_len > tid_b_max);
++ BUG_ON(tid_b_len < 0);
++ } else
++ tid_b_len = strnlen(tid_b, tid_b_max);
++ } else
++ goto out_error;
++
++ if (tid_a_len != tid_b_len)
++ return false;
++
++ len = tid_a_len;
++
++ /* ISCSI names are case insensitive */
++ for (i = 0; i < len; i++)
++ if (tolower(tid_a[i]) != tolower(tid_b[i]))
++ return false;
++ return true;
++ } else
++ len = TID_COMMON_SIZE;
++
++ return memcmp(tid_a, tid_b, len) == 0;
++
++out_error:
++ PRINT_ERROR("%s", "Invalid initiator port transport id");
++ return false;
++}
++
++/* Must be called under dev_pr_mutex */
++static inline void scst_pr_set_holder(struct scst_device *dev,
++ struct scst_dev_registrant *holder, uint8_t scope, uint8_t type)
++{
++ dev->pr_is_set = 1;
++ dev->pr_scope = scope;
++ dev->pr_type = type;
++ if (dev->pr_type != TYPE_EXCLUSIVE_ACCESS_ALL_REG &&
++ dev->pr_type != TYPE_WRITE_EXCLUSIVE_ALL_REG)
++ dev->pr_holder = holder;
++}
++
++/* Must be called under dev_pr_mutex */
++static bool scst_pr_is_holder(struct scst_device *dev,
++ struct scst_dev_registrant *reg)
++{
++ bool res = false;
++
++ TRACE_ENTRY();
++
++ if (!dev->pr_is_set)
++ goto out;
++
++ if (dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG ||
++ dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG) {
++ res = (reg != NULL);
++ } else
++ res = (dev->pr_holder == reg);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++/* Must be called under dev_pr_mutex */
++void scst_pr_dump_prs(struct scst_device *dev, bool force)
++{
++ if (!force) {
++#if defined(CONFIG_SCST_DEBUG)
++ if ((trace_flag & TRACE_PRES) == 0)
++#endif
++ goto out;
++ }
++
++ PRINT_INFO("Persistent reservations for device %s:", dev->virt_name);
++
++ if (list_empty(&dev->dev_registrants_list))
++ PRINT_INFO("%s", " No registrants");
++ else {
++ struct scst_dev_registrant *reg;
++ int i = 0;
++ list_for_each_entry(reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ PRINT_INFO(" [%d] registrant %s/%d, key %016llx "
++ "(reg %p, tgt_dev %p)", i++,
++ debug_transport_id_to_initiator_name(
++ reg->transport_id),
++ reg->rel_tgt_id, reg->key, reg, reg->tgt_dev);
++ }
++ }
++
++ if (dev->pr_is_set) {
++ struct scst_dev_registrant *holder = dev->pr_holder;
++ if (holder != NULL)
++ PRINT_INFO("Reservation holder is %s/%d (key %016llx, "
++ "scope %x, type %x, reg %p, tgt_dev %p)",
++ debug_transport_id_to_initiator_name(
++ holder->transport_id),
++ holder->rel_tgt_id, holder->key, dev->pr_scope,
++ dev->pr_type, holder, holder->tgt_dev);
++ else
++ PRINT_INFO("All registrants are reservation holders "
++ "(scope %x, type %x)", dev->pr_scope,
++ dev->pr_type);
++ } else
++ PRINT_INFO("%s", "Not reserved");
++
++out:
++ return;
++}
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++/* dev_pr_mutex must be locked */
++static void scst_pr_find_registrants_list_all(struct scst_device *dev,
++ struct scst_dev_registrant *exclude_reg, struct list_head *list)
++{
++ struct scst_dev_registrant *reg;
++
++ TRACE_ENTRY();
++
++ TRACE_PR("Finding all registered records for device '%s' "
++ "with exclude reg key %016llx",
++ dev->virt_name, exclude_reg->key);
++
++ list_for_each_entry(reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ if (reg == exclude_reg)
++ continue;
++ TRACE_PR("Adding registrant %s/%d (%p) to find list (key %016llx)",
++ debug_transport_id_to_initiator_name(reg->transport_id),
++ reg->rel_tgt_id, reg, reg->key);
++ list_add_tail(&reg->aux_list_entry, list);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* dev_pr_mutex must be locked */
++static void scst_pr_find_registrants_list_key(struct scst_device *dev,
++ __be64 key, struct list_head *list)
++{
++ struct scst_dev_registrant *reg;
++
++ TRACE_ENTRY();
++
++ TRACE_PR("Finding registrants for device '%s' with key %016llx",
++ dev->virt_name, key);
++
++ list_for_each_entry(reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ if (reg->key == key) {
++ TRACE_PR("Adding registrant %s/%d (%p) to the find "
++ "list (key %016llx)",
++ debug_transport_id_to_initiator_name(
++ reg->transport_id),
++ reg->rel_tgt_id, reg->tgt_dev, key);
++ list_add_tail(&reg->aux_list_entry, list);
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* dev_pr_mutex must be locked */
++static struct scst_dev_registrant *scst_pr_find_reg(
++ struct scst_device *dev, const uint8_t *transport_id,
++ const uint16_t rel_tgt_id)
++{
++ struct scst_dev_registrant *reg, *res = NULL;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ if ((reg->rel_tgt_id == rel_tgt_id) &&
++ tid_equal(reg->transport_id, transport_id)) {
++ res = reg;
++ break;
++ }
++ }
++
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/* Must be called under dev_pr_mutex */
++static void scst_pr_clear_reservation(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ WARN_ON(!dev->pr_is_set);
++
++ dev->pr_is_set = 0;
++ dev->pr_scope = SCOPE_LU;
++ dev->pr_type = TYPE_UNSPECIFIED;
++
++ dev->pr_holder = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under dev_pr_mutex */
++static void scst_pr_clear_holder(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ WARN_ON(!dev->pr_is_set);
++
++ if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG ||
++ dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) {
++ if (list_empty(&dev->dev_registrants_list))
++ scst_pr_clear_reservation(dev);
++ } else
++ scst_pr_clear_reservation(dev);
++
++ dev->pr_holder = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under dev_pr_mutex */
++static struct scst_dev_registrant *scst_pr_add_registrant(
++ struct scst_device *dev, const uint8_t *transport_id,
++ const uint16_t rel_tgt_id, __be64 key,
++ bool dev_lock_locked)
++{
++ struct scst_dev_registrant *reg;
++ struct scst_tgt_dev *t;
++ gfp_t gfp_flags = dev_lock_locked ? GFP_ATOMIC : GFP_KERNEL;
++
++ TRACE_ENTRY();
++
++ BUG_ON(dev == NULL);
++ BUG_ON(transport_id == NULL);
++
++ TRACE_PR("Registering %s/%d (dev %s)",
++ debug_transport_id_to_initiator_name(transport_id),
++ rel_tgt_id, dev->virt_name);
++
++ reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id);
++ if (reg != NULL) {
++ /*
++ * It might happen when a target driver would make >1 session
++ * from the same initiator to the same target.
++ */
++ PRINT_ERROR("Registrant %p/%d (dev %s) already exists!", reg,
++ rel_tgt_id, dev->virt_name);
++ PRINT_BUFFER("TransportID", transport_id, 24);
++ WARN_ON(1);
++ reg = NULL;
++ goto out;
++ }
++
++ reg = kzalloc(sizeof(*reg), gfp_flags);
++ if (reg == NULL) {
++ PRINT_ERROR("%s", "Unable to allocate registration record");
++ goto out;
++ }
++
++ reg->transport_id = kmalloc(tid_size(transport_id), gfp_flags);
++ if (reg->transport_id == NULL) {
++ PRINT_ERROR("%s", "Unable to allocate initiator port "
++ "transport id");
++ goto out_free;
++ }
++ memcpy(reg->transport_id, transport_id, tid_size(transport_id));
++
++ reg->rel_tgt_id = rel_tgt_id;
++ reg->key = key;
++
++ /*
++ * We can't use scst_mutex here, because of the circular
++ * locking dependency with dev_pr_mutex.
++ */
++ if (!dev_lock_locked)
++ spin_lock_bh(&dev->dev_lock);
++ list_for_each_entry(t, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) {
++ if (tid_equal(t->sess->transport_id, transport_id) &&
++ (t->sess->tgt->rel_tgt_id == rel_tgt_id) &&
++ (t->registrant == NULL)) {
++ /*
++ * We must assign here, because t can die
++ * immediately after we release dev_lock.
++ */
++ TRACE_PR("Found tgt_dev %p", t);
++ reg->tgt_dev = t;
++ t->registrant = reg;
++ break;
++ }
++ }
++ if (!dev_lock_locked)
++ spin_unlock_bh(&dev->dev_lock);
++
++ list_add_tail(&reg->dev_registrants_list_entry,
++ &dev->dev_registrants_list);
++
++ TRACE_PR("Reg %p registered (dev %s, tgt_dev %p)", reg,
++ dev->virt_name, reg->tgt_dev);
++
++out:
++ TRACE_EXIT_HRES((unsigned long)reg);
++ return reg;
++
++out_free:
++ kfree(reg);
++ reg = NULL;
++ goto out;
++}
++
++/* Must be called under dev_pr_mutex */
++static void scst_pr_remove_registrant(struct scst_device *dev,
++ struct scst_dev_registrant *reg)
++{
++ TRACE_ENTRY();
++
++ TRACE_PR("Removing registrant %s/%d (reg %p, tgt_dev %p, key %016llx, "
++ "dev %s)", debug_transport_id_to_initiator_name(reg->transport_id),
++ reg->rel_tgt_id, reg, reg->tgt_dev, reg->key, dev->virt_name);
++
++ list_del(&reg->dev_registrants_list_entry);
++
++ if (scst_pr_is_holder(dev, reg))
++ scst_pr_clear_holder(dev);
++
++ if (reg->tgt_dev)
++ reg->tgt_dev->registrant = NULL;
++
++ kfree(reg->transport_id);
++ kfree(reg);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under dev_pr_mutex */
++static void scst_pr_send_ua_reg(struct scst_device *dev,
++ struct scst_dev_registrant *reg,
++ int key, int asc, int ascq)
++{
++ static uint8_t ua[SCST_STANDARD_SENSE_LEN];
++
++ TRACE_ENTRY();
++
++ scst_set_sense(ua, sizeof(ua), dev->d_sense, key, asc, ascq);
++
++ TRACE_PR("Queueing UA [%x %x %x]: registrant %s/%d (%p), tgt_dev %p, "
++ "key %016llx", ua[2], ua[12], ua[13],
++ debug_transport_id_to_initiator_name(reg->transport_id),
++ reg->rel_tgt_id, reg, reg->tgt_dev, reg->key);
++
++ if (reg->tgt_dev)
++ scst_check_set_UA(reg->tgt_dev, ua, sizeof(ua), 0);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under dev_pr_mutex */
++static void scst_pr_send_ua_all(struct scst_device *dev,
++ struct scst_dev_registrant *exclude_reg,
++ int key, int asc, int ascq)
++{
++ struct scst_dev_registrant *reg;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ if (reg != exclude_reg)
++ scst_pr_send_ua_reg(dev, reg, key, asc, ascq);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under dev_pr_mutex */
++static void scst_pr_abort_reg(struct scst_device *dev,
++ struct scst_cmd *pr_cmd, struct scst_dev_registrant *reg)
++{
++ struct scst_session *sess;
++ __be64 packed_lun;
++ int rc;
++
++ TRACE_ENTRY();
++
++ if (reg->tgt_dev == NULL) {
++ TRACE_PR("Registrant %s/%d (%p, key 0x%016llx) has no session",
++ debug_transport_id_to_initiator_name(reg->transport_id),
++ reg->rel_tgt_id, reg, reg->key);
++ goto out;
++ }
++
++ sess = reg->tgt_dev->sess;
++
++ TRACE_PR("Aborting %d commands for %s/%d (reg %p, key 0x%016llx, "
++ "tgt_dev %p, sess %p)",
++ atomic_read(&reg->tgt_dev->tgt_dev_cmd_count),
++ debug_transport_id_to_initiator_name(reg->transport_id),
++ reg->rel_tgt_id, reg, reg->key, reg->tgt_dev, sess);
++
++ packed_lun = scst_pack_lun(reg->tgt_dev->lun, sess->acg->addr_method);
++
++ rc = scst_rx_mgmt_fn_lun(sess, SCST_PR_ABORT_ALL,
++ (uint8_t *)&packed_lun, sizeof(packed_lun), SCST_NON_ATOMIC,
++ pr_cmd);
++ if (rc != 0) {
++ /*
++ * There's nothing more we can do here... Hopefully, it would
++ * never happen.
++ */
++ PRINT_ERROR("SCST_PR_ABORT_ALL failed %d (sess %p)",
++ rc, sess);
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Abstract vfs_unlink() for different kernel versions (as possible) */
++static inline void scst_pr_vfs_unlink_and_put(struct path *path)
++{
++ vfs_unlink(path->dentry->d_parent->d_inode, path->dentry);
++ path_put(path);
++}
++
++/* Called under scst_mutex */
++static int scst_pr_do_load_device_file(struct scst_device *dev,
++ const char *file_name)
++{
++ int res = 0, rc;
++ struct file *file = NULL;
++ struct inode *inode;
++ char *buf = NULL;
++ loff_t file_size, pos, data_size;
++ uint64_t sign, version;
++ mm_segment_t old_fs;
++ uint8_t pr_is_set, aptpl;
++ __be64 key;
++ uint16_t rel_tgt_id;
++
++ TRACE_ENTRY();
++
++ old_fs = get_fs();
++ set_fs(KERNEL_DS);
++
++ TRACE_PR("Loading persistent file '%s'", file_name);
++
++ file = filp_open(file_name, O_RDONLY, 0);
++ if (IS_ERR(file)) {
++ res = PTR_ERR(file);
++ TRACE_PR("Unable to open file '%s' - error %d", file_name, res);
++ goto out;
++ }
++
++ inode = file->f_dentry->d_inode;
++
++ if (S_ISREG(inode->i_mode))
++ /* Nothing to do */;
++ else if (S_ISBLK(inode->i_mode))
++ inode = inode->i_bdev->bd_inode;
++ else {
++ PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode);
++ goto out_close;
++ }
++
++ file_size = inode->i_size;
++
++ /* Let's limit the file size by some reasonable number */
++ if ((file_size == 0) || (file_size >= 15*1024*1024)) {
++ PRINT_ERROR("Invalid PR file size %d", (int)file_size);
++ res = -EINVAL;
++ goto out_close;
++ }
++
++ buf = vmalloc(file_size);
++ if (buf == NULL) {
++ res = -ENOMEM;
++ PRINT_ERROR("%s", "Unable to allocate buffer");
++ goto out_close;
++ }
++
++ pos = 0;
++ rc = vfs_read(file, (void __force __user *)buf, file_size, &pos);
++ if (rc != file_size) {
++ PRINT_ERROR("Unable to read file '%s' - error %d", file_name,
++ rc);
++ res = rc;
++ goto out_close;
++ }
++
++ data_size = 0;
++ data_size += sizeof(sign);
++ data_size += sizeof(version);
++ data_size += sizeof(aptpl);
++ data_size += sizeof(pr_is_set);
++ data_size += sizeof(dev->pr_type);
++ data_size += sizeof(dev->pr_scope);
++
++ if (file_size < data_size) {
++ res = -EINVAL;
++ PRINT_ERROR("Invalid file '%s' - size too small", file_name);
++ goto out_close;
++ }
++
++ pos = 0;
++
++ sign = get_unaligned((uint64_t *)&buf[pos]);
++ if (sign != SCST_PR_FILE_SIGN) {
++ res = -EINVAL;
++ PRINT_ERROR("Invalid persistent file signature %016llx "
++ "(expected %016llx)", sign, SCST_PR_FILE_SIGN);
++ goto out_close;
++ }
++ pos += sizeof(sign);
++
++ version = get_unaligned((uint64_t *)&buf[pos]);
++ if (version != SCST_PR_FILE_VERSION) {
++ res = -EINVAL;
++ PRINT_ERROR("Invalid persistent file version %016llx "
++ "(expected %016llx)", version, SCST_PR_FILE_VERSION);
++ goto out_close;
++ }
++ pos += sizeof(version);
++
++ while (data_size < file_size) {
++ uint8_t *tid;
++
++ data_size++;
++ tid = &buf[data_size];
++ data_size += tid_size(tid);
++ data_size += sizeof(key);
++ data_size += sizeof(rel_tgt_id);
++
++ if (data_size > file_size) {
++ res = -EINVAL;
++ PRINT_ERROR("Invalid file '%s' - size mismatch have "
++ "%lld expected %lld", file_name, file_size,
++ data_size);
++ goto out_close;
++ }
++ }
++
++ aptpl = buf[pos];
++ dev->pr_aptpl = aptpl ? 1 : 0;
++ pos += sizeof(aptpl);
++
++ pr_is_set = buf[pos];
++ dev->pr_is_set = pr_is_set ? 1 : 0;
++ pos += sizeof(pr_is_set);
++
++ dev->pr_type = buf[pos];
++ pos += sizeof(dev->pr_type);
++
++ dev->pr_scope = buf[pos];
++ pos += sizeof(dev->pr_scope);
++
++ while (pos < file_size) {
++ uint8_t is_holder;
++ uint8_t *tid;
++ struct scst_dev_registrant *reg = NULL;
++
++ is_holder = buf[pos++];
++
++ tid = &buf[pos];
++ pos += tid_size(tid);
++
++ key = get_unaligned((__be64 *)&buf[pos]);
++ pos += sizeof(key);
++
++ rel_tgt_id = get_unaligned((uint16_t *)&buf[pos]);
++ pos += sizeof(rel_tgt_id);
++
++ reg = scst_pr_add_registrant(dev, tid, rel_tgt_id, key, false);
++ if (reg == NULL) {
++ res = -ENOMEM;
++ goto out_close;
++ }
++
++ if (is_holder)
++ dev->pr_holder = reg;
++ }
++
++out_close:
++ filp_close(file, NULL);
++
++out:
++ if (buf != NULL)
++ vfree(buf);
++
++ set_fs(old_fs);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_pr_load_device_file(struct scst_device *dev)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (dev->pr_file_name == NULL || dev->pr_file_name1 == NULL) {
++ PRINT_ERROR("Invalid file paths for '%s'", dev->virt_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_pr_do_load_device_file(dev, dev->pr_file_name);
++ if (res == 0)
++ goto out;
++ else if (res == -ENOMEM)
++ goto out;
++
++ res = scst_pr_do_load_device_file(dev, dev->pr_file_name1);
++
++ scst_pr_dump_prs(dev, false);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_pr_copy_file(const char *src, const char *dest)
++{
++ int res = 0;
++ struct inode *inode;
++ loff_t file_size, pos;
++ uint8_t *buf = NULL;
++ struct file *file_src = NULL, *file_dest = NULL;
++ mm_segment_t old_fs = get_fs();
++
++ TRACE_ENTRY();
++
++ if (src == NULL || dest == NULL) {
++ res = -EINVAL;
++ PRINT_ERROR("%s", "Invalid persistent files path - backup "
++ "skipped");
++ goto out;
++ }
++
++ TRACE_PR("Copying '%s' into '%s'", src, dest);
++
++ set_fs(KERNEL_DS);
++
++ file_src = filp_open(src, O_RDONLY, 0);
++ if (IS_ERR(file_src)) {
++ res = PTR_ERR(file_src);
++ TRACE_PR("Unable to open file '%s' - error %d", src,
++ res);
++ goto out_free;
++ }
++
++ file_dest = filp_open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0644);
++ if (IS_ERR(file_dest)) {
++ res = PTR_ERR(file_dest);
++ TRACE_PR("Unable to open backup file '%s' - error %d", dest,
++ res);
++ goto out_close;
++ }
++
++ inode = file_src->f_dentry->d_inode;
++
++ if (S_ISREG(inode->i_mode))
++ /* Nothing to do */;
++ else if (S_ISBLK(inode->i_mode))
++ inode = inode->i_bdev->bd_inode;
++ else {
++ PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode);
++ res = -EINVAL;
++ set_fs(old_fs);
++ goto out_skip;
++ }
++
++ file_size = inode->i_size;
++
++ buf = vmalloc(file_size);
++ if (buf == NULL) {
++ res = -ENOMEM;
++ PRINT_ERROR("%s", "Unable to allocate temporary buffer");
++ goto out_skip;
++ }
++
++ pos = 0;
++ res = vfs_read(file_src, (void __force __user *)buf, file_size, &pos);
++ if (res != file_size) {
++ PRINT_ERROR("Unable to read file '%s' - error %d", src, res);
++ goto out_skip;
++ }
++
++ pos = 0;
++ res = vfs_write(file_dest, (void __force __user *)buf, file_size, &pos);
++ if (res != file_size) {
++ PRINT_ERROR("Unable to write to '%s' - error %d", dest, res);
++ goto out_skip;
++ }
++
++ res = vfs_fsync(file_dest, 0);
++ if (res != 0) {
++ PRINT_ERROR("fsync() of the backup PR file failed: %d", res);
++ goto out_skip;
++ }
++
++out_skip:
++ filp_close(file_dest, NULL);
++
++out_close:
++ filp_close(file_src, NULL);
++
++out_free:
++ if (buf != NULL)
++ vfree(buf);
++
++ set_fs(old_fs);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void scst_pr_remove_device_files(struct scst_tgt_dev *tgt_dev)
++{
++ int res = 0;
++ struct scst_device *dev = tgt_dev->dev;
++ struct path path;
++ mm_segment_t old_fs = get_fs();
++
++ TRACE_ENTRY();
++
++ set_fs(KERNEL_DS);
++
++ res = dev->pr_file_name ? kern_path(dev->pr_file_name, 0, &path) :
++ -ENOENT;
++ if (!res)
++ scst_pr_vfs_unlink_and_put(&path);
++ else
++ TRACE_DBG("Unable to lookup file '%s' - error %d",
++ dev->pr_file_name, res);
++
++ res = dev->pr_file_name1 ? kern_path(dev->pr_file_name1, 0, &path) :
++ -ENOENT;
++ if (!res)
++ scst_pr_vfs_unlink_and_put(&path);
++ else
++ TRACE_DBG("Unable to lookup file '%s' - error %d",
++ dev->pr_file_name1, res);
++
++ set_fs(old_fs);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under dev_pr_mutex */
++void scst_pr_sync_device_file(struct scst_tgt_dev *tgt_dev, struct scst_cmd *cmd)
++{
++ int res = 0;
++ struct scst_device *dev = tgt_dev->dev;
++ struct file *file;
++ mm_segment_t old_fs = get_fs();
++ loff_t pos = 0;
++ uint64_t sign;
++ uint64_t version;
++ uint8_t pr_is_set, aptpl;
++
++ TRACE_ENTRY();
++
++ if ((dev->pr_aptpl == 0) || list_empty(&dev->dev_registrants_list)) {
++ scst_pr_remove_device_files(tgt_dev);
++ goto out;
++ }
++
++ scst_pr_copy_file(dev->pr_file_name, dev->pr_file_name1);
++
++ set_fs(KERNEL_DS);
++
++ file = filp_open(dev->pr_file_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
++ if (IS_ERR(file)) {
++ res = PTR_ERR(file);
++ PRINT_ERROR("Unable to (re)create PR file '%s' - error %d",
++ dev->pr_file_name, res);
++ goto out_set_fs;
++ }
++
++ TRACE_PR("Updating pr file '%s'", dev->pr_file_name);
++
++ /*
++ * signature
++ */
++ sign = 0;
++ pos = 0;
++ res = vfs_write(file, (void __force __user *)&sign, sizeof(sign), &pos);
++ if (res != sizeof(sign))
++ goto write_error;
++
++ /*
++ * version
++ */
++ version = SCST_PR_FILE_VERSION;
++ res = vfs_write(file, (void __force __user *)&version, sizeof(version), &pos);
++ if (res != sizeof(version))
++ goto write_error;
++
++ /*
++ * APTPL
++ */
++ aptpl = dev->pr_aptpl;
++ res = vfs_write(file, (void __force __user *)&aptpl, sizeof(aptpl), &pos);
++ if (res != sizeof(aptpl))
++ goto write_error;
++
++ /*
++ * reservation
++ */
++ pr_is_set = dev->pr_is_set;
++ res = vfs_write(file, (void __force __user *)&pr_is_set, sizeof(pr_is_set), &pos);
++ if (res != sizeof(pr_is_set))
++ goto write_error;
++
++ res = vfs_write(file, (void __force __user *)&dev->pr_type, sizeof(dev->pr_type), &pos);
++ if (res != sizeof(dev->pr_type))
++ goto write_error;
++
++ res = vfs_write(file, (void __force __user *)&dev->pr_scope, sizeof(dev->pr_scope), &pos);
++ if (res != sizeof(dev->pr_scope))
++ goto write_error;
++
++ /*
++ * registration records
++ */
++ if (!list_empty(&dev->dev_registrants_list)) {
++ struct scst_dev_registrant *reg;
++
++ list_for_each_entry(reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ uint8_t is_holder = 0;
++ int size;
++
++ is_holder = (dev->pr_holder == reg);
++
++ res = vfs_write(file, (void __force __user *)&is_holder, sizeof(is_holder),
++ &pos);
++ if (res != sizeof(is_holder))
++ goto write_error;
++
++ size = tid_size(reg->transport_id);
++ res = vfs_write(file, (void __force __user *)reg->transport_id, size, &pos);
++ if (res != size)
++ goto write_error;
++
++ res = vfs_write(file, (void __force __user *)&reg->key,
++ sizeof(reg->key), &pos);
++ if (res != sizeof(reg->key))
++ goto write_error;
++
++ res = vfs_write(file, (void __force __user *)&reg->rel_tgt_id,
++ sizeof(reg->rel_tgt_id), &pos);
++ if (res != sizeof(reg->rel_tgt_id))
++ goto write_error;
++ }
++ }
++
++ res = vfs_fsync(file, 0);
++ if (res != 0) {
++ PRINT_ERROR("fsync() of the PR file failed: %d", res);
++ goto write_error_close;
++ }
++
++ sign = SCST_PR_FILE_SIGN;
++ pos = 0;
++ res = vfs_write(file, (void __force __user *)&sign, sizeof(sign), &pos);
++ if (res != sizeof(sign))
++ goto write_error;
++
++ res = vfs_fsync(file, 0);
++ if (res != 0) {
++ PRINT_ERROR("fsync() of the PR file failed: %d", res);
++ goto write_error_close;
++ }
++
++ res = 0;
++
++ filp_close(file, NULL);
++
++out_set_fs:
++ set_fs(old_fs);
++
++out:
++ if (res != 0) {
++ PRINT_CRIT_ERROR("Unable to save persistent information "
++ "(target %s, initiator %s, device %s)",
++ tgt_dev->sess->tgt->tgt_name,
++ tgt_dev->sess->initiator_name, dev->virt_name);
++#if 0 /*
++ * Looks like it's safer to return SUCCESS and expect operator's
++ * intervention to be able to save the PR's state next time, than
++ * to return HARDWARE ERROR and screw up all the interaction with
++ * the affected initiator.
++ */
++ if (cmd != NULL)
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++#endif
++ }
++
++ TRACE_EXIT_RES(res);
++ return;
++
++write_error:
++ PRINT_ERROR("Error writing to '%s' - error %d", dev->pr_file_name, res);
++
++write_error_close:
++ filp_close(file, NULL);
++ {
++ struct path path;
++ int rc;
++
++ rc = kern_path(dev->pr_file_name, 0, &path);
++ if (!rc)
++ scst_pr_vfs_unlink_and_put(&path);
++ else
++ TRACE_PR("Unable to lookup '%s' - error %d",
++ dev->pr_file_name, rc);
++ }
++ goto out_set_fs;
++}
++
++static int scst_pr_check_pr_path(void)
++{
++ int res;
++ struct path path;
++
++ mm_segment_t old_fs = get_fs();
++
++ TRACE_ENTRY();
++
++ set_fs(KERNEL_DS);
++
++ res = kern_path(SCST_PR_DIR, 0, &path);
++ if (res == 0)
++ path_put(&path);
++ if (res != 0) {
++ PRINT_ERROR("Unable to find %s (err %d), you should create "
++ "this directory manually or reinstall SCST",
++ SCST_PR_DIR, res);
++ goto out_setfs;
++ }
++
++out_setfs:
++ set_fs(old_fs);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Called under scst_mutex */
++int scst_pr_init_dev(struct scst_device *dev)
++{
++ int res = 0;
++ uint8_t q;
++ int name_len;
++
++ TRACE_ENTRY();
++
++ name_len = snprintf(&q, sizeof(q), "%s/%s", SCST_PR_DIR, dev->virt_name) + 1;
++ dev->pr_file_name = kmalloc(name_len, GFP_KERNEL);
++ if (dev->pr_file_name == NULL) {
++ PRINT_ERROR("Allocation of device '%s' file path failed",
++ dev->virt_name);
++ res = -ENOMEM;
++ goto out;
++ } else
++ snprintf(dev->pr_file_name, name_len, "%s/%s", SCST_PR_DIR,
++ dev->virt_name);
++
++ name_len = snprintf(&q, sizeof(q), "%s/%s.1", SCST_PR_DIR, dev->virt_name) + 1;
++ dev->pr_file_name1 = kmalloc(name_len, GFP_KERNEL);
++ if (dev->pr_file_name1 == NULL) {
++ PRINT_ERROR("Allocation of device '%s' backup file path failed",
++ dev->virt_name);
++ res = -ENOMEM;
++ goto out_free_name;
++ } else
++ snprintf(dev->pr_file_name1, name_len, "%s/%s.1", SCST_PR_DIR,
++ dev->virt_name);
++
++ res = scst_pr_check_pr_path();
++ if (res == 0) {
++ res = scst_pr_load_device_file(dev);
++ if (res == -ENOENT)
++ res = 0;
++ }
++
++ if (res != 0)
++ goto out_free_name1;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free_name1:
++ kfree(dev->pr_file_name1);
++ dev->pr_file_name1 = NULL;
++
++out_free_name:
++ kfree(dev->pr_file_name);
++ dev->pr_file_name = NULL;
++ goto out;
++}
++
++/* Called under scst_mutex */
++void scst_pr_clear_dev(struct scst_device *dev)
++{
++ struct scst_dev_registrant *reg, *tmp_reg;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry_safe(reg, tmp_reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ scst_pr_remove_registrant(dev, reg);
++ }
++
++ kfree(dev->pr_file_name);
++ kfree(dev->pr_file_name1);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called under scst_mutex */
++int scst_pr_init_tgt_dev(struct scst_tgt_dev *tgt_dev)
++{
++ int res = 0;
++ struct scst_dev_registrant *reg;
++ struct scst_device *dev = tgt_dev->dev;
++ const uint8_t *transport_id = tgt_dev->sess->transport_id;
++ const uint16_t rel_tgt_id = tgt_dev->sess->tgt->rel_tgt_id;
++
++ TRACE_ENTRY();
++
++ if (tgt_dev->sess->transport_id == NULL)
++ goto out;
++
++ scst_pr_write_lock(dev);
++
++ reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id);
++ if ((reg != NULL) && (reg->tgt_dev == NULL)) {
++ TRACE_PR("Assigning reg %s/%d (%p) to tgt_dev %p (dev %s)",
++ debug_transport_id_to_initiator_name(transport_id),
++ rel_tgt_id, reg, tgt_dev, dev->virt_name);
++ tgt_dev->registrant = reg;
++ reg->tgt_dev = tgt_dev;
++ }
++
++ scst_pr_write_unlock(dev);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Called under scst_mutex */
++void scst_pr_clear_tgt_dev(struct scst_tgt_dev *tgt_dev)
++{
++ TRACE_ENTRY();
++
++ if (tgt_dev->registrant != NULL) {
++ struct scst_dev_registrant *reg = tgt_dev->registrant;
++ struct scst_device *dev = tgt_dev->dev;
++ struct scst_tgt_dev *t;
++
++ scst_pr_write_lock(dev);
++
++ tgt_dev->registrant = NULL;
++ reg->tgt_dev = NULL;
++
++ /* Just in case, actually. It should never happen. */
++ list_for_each_entry(t, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if (t == tgt_dev)
++ continue;
++ if ((t->sess->tgt->rel_tgt_id == reg->rel_tgt_id) &&
++ tid_equal(t->sess->transport_id, reg->transport_id)) {
++ TRACE_PR("Reassigning reg %s/%d (%p) to tgt_dev "
++ "%p (being cleared tgt_dev %p)",
++ debug_transport_id_to_initiator_name(
++ reg->transport_id),
++ reg->rel_tgt_id, reg, t, tgt_dev);
++ t->registrant = reg;
++ reg->tgt_dev = t;
++ break;
++ }
++ }
++
++ scst_pr_write_unlock(dev);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked. Might also be called under scst_mutex2. */
++static int scst_pr_register_with_spec_i_pt(struct scst_cmd *cmd,
++ const uint16_t rel_tgt_id, uint8_t *buffer, int buffer_size,
++ struct list_head *rollback_list)
++{
++ int res = 0;
++ int offset, ext_size;
++ __be64 action_key;
++ struct scst_device *dev = cmd->dev;
++ struct scst_dev_registrant *reg;
++ uint8_t *transport_id;
++
++ action_key = get_unaligned((__be64 *)&buffer[8]);
++
++ ext_size = be32_to_cpu(get_unaligned((__be32 *)&buffer[24]));
++ if ((ext_size + 28) > buffer_size) {
++ TRACE_PR("Invalid buffer size %d (max %d)", buffer_size,
++ ext_size + 28);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
++ res = -EINVAL;
++ goto out;
++ }
++
++ offset = 0;
++ while (offset < ext_size) {
++ transport_id = &buffer[28 + offset];
++
++ if ((offset + tid_size(transport_id)) > ext_size) {
++ TRACE_PR("Invalid transport_id size %d (max %d)",
++ tid_size(transport_id), ext_size - offset);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
++ res = -EINVAL;
++ goto out;
++ }
++ tid_secure(transport_id);
++ offset += tid_size(transport_id);
++ }
++
++ offset = 0;
++ while (offset < ext_size) {
++ struct scst_tgt_dev *t;
++
++ transport_id = &buffer[28 + offset];
++
++ TRACE_PR("rel_tgt_id %d, transport_id %s", rel_tgt_id,
++ debug_transport_id_to_initiator_name(transport_id));
++
++ if ((transport_id[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI &&
++ (transport_id[0] & 0xc0) == 0) {
++ TRACE_PR("Wildcard iSCSI TransportID %s",
++ &transport_id[4]);
++ /*
++ * We can't use scst_mutex here, because of the
++ * circular locking dependency with dev_pr_mutex.
++ */
++ spin_lock_bh(&dev->dev_lock);
++ list_for_each_entry(t, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ /*
++ * We must go over all matching tgt_devs and
++ * register them on the requested rel_tgt_id
++ */
++ if (!tid_equal(t->sess->transport_id,
++ transport_id))
++ continue;
++
++ reg = scst_pr_find_reg(dev,
++ t->sess->transport_id, rel_tgt_id);
++ if (reg == NULL) {
++ reg = scst_pr_add_registrant(dev,
++ t->sess->transport_id,
++ rel_tgt_id, action_key, true);
++ if (reg == NULL) {
++ spin_unlock_bh(&dev->dev_lock);
++ scst_set_busy(cmd);
++ res = -ENOMEM;
++ goto out;
++ }
++ } else if (reg->key != action_key) {
++ TRACE_PR("Changing key of reg %p "
++ "(tgt_dev %p)", reg, t);
++ reg->rollback_key = reg->key;
++ reg->key = action_key;
++ } else
++ continue;
++
++ list_add_tail(&reg->aux_list_entry,
++ rollback_list);
++ }
++ spin_unlock_bh(&dev->dev_lock);
++ } else {
++ reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id);
++ if (reg != NULL) {
++ if (reg->key == action_key)
++ goto next;
++ TRACE_PR("Changing key of reg %p (tgt_dev %p)",
++ reg, reg->tgt_dev);
++ reg->rollback_key = reg->key;
++ reg->key = action_key;
++ } else {
++ reg = scst_pr_add_registrant(dev, transport_id,
++ rel_tgt_id, action_key, false);
++ if (reg == NULL) {
++ scst_set_busy(cmd);
++ res = -ENOMEM;
++ goto out;
++ }
++ }
++
++ list_add_tail(&reg->aux_list_entry,
++ rollback_list);
++ }
++next:
++ offset += tid_size(transport_id);
++ }
++out:
++ return res;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++static void scst_pr_unregister(struct scst_device *dev,
++ struct scst_dev_registrant *reg)
++{
++ bool is_holder;
++ uint8_t pr_type;
++
++ TRACE_ENTRY();
++
++ TRACE_PR("Unregistering key %0llx", reg->key);
++
++ is_holder = scst_pr_is_holder(dev, reg);
++ pr_type = dev->pr_type;
++
++ scst_pr_remove_registrant(dev, reg);
++
++ if (is_holder && !dev->pr_is_set) {
++ /* A registration just released */
++ switch (pr_type) {
++ case TYPE_WRITE_EXCLUSIVE_REGONLY:
++ case TYPE_EXCLUSIVE_ACCESS_REGONLY:
++ scst_pr_send_ua_all(dev, NULL,
++ SCST_LOAD_SENSE(scst_sense_reservation_released));
++ break;
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++static void scst_pr_unregister_all_tg_pt(struct scst_device *dev,
++ const uint8_t *transport_id)
++{
++ struct scst_tgt_template *tgtt;
++ uint8_t proto_id = transport_id[0] & 0x0f;
++
++ TRACE_ENTRY();
++
++ /*
++ * We can't use scst_mutex here, because of the circular locking
++ * dependency with dev_pr_mutex.
++ */
++ mutex_lock(&scst_mutex2);
++
++ list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) {
++ struct scst_tgt *tgt;
++
++ if (tgtt->get_initiator_port_transport_id == NULL)
++ continue;
++
++ list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
++ struct scst_dev_registrant *reg;
++
++ if (tgtt->get_initiator_port_transport_id(tgt, NULL, NULL) != proto_id)
++ continue;
++
++ reg = scst_pr_find_reg(dev, transport_id,
++ tgt->rel_tgt_id);
++ if (reg == NULL)
++ continue;
++
++ scst_pr_unregister(dev, reg);
++ }
++ }
++
++ mutex_unlock(&scst_mutex2);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked. Might also be called under scst_mutex2. */
++static int scst_pr_register_on_tgt_id(struct scst_cmd *cmd,
++ const uint16_t rel_tgt_id, uint8_t *buffer, int buffer_size,
++ bool spec_i_pt, struct list_head *rollback_list)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ TRACE_PR("rel_tgt_id %d, spec_i_pt %d", rel_tgt_id, spec_i_pt);
++
++ if (spec_i_pt) {
++ res = scst_pr_register_with_spec_i_pt(cmd, rel_tgt_id, buffer,
++ buffer_size, rollback_list);
++ if (res != 0)
++ goto out;
++ }
++
++ /* tgt_dev can be among TIDs for scst_pr_register_with_spec_i_pt() */
++
++ if (scst_pr_find_reg(cmd->dev, cmd->sess->transport_id, rel_tgt_id) == NULL) {
++ __be64 action_key;
++ struct scst_dev_registrant *reg;
++
++ action_key = get_unaligned((__be64 *)&buffer[8]);
++
++ reg = scst_pr_add_registrant(cmd->dev, cmd->sess->transport_id,
++ rel_tgt_id, action_key, false);
++ if (reg == NULL) {
++ res = -ENOMEM;
++ scst_set_busy(cmd);
++ goto out;
++ }
++
++ list_add_tail(&reg->aux_list_entry, rollback_list);
++ }
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++static int scst_pr_register_all_tg_pt(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size, bool spec_i_pt, struct list_head *rollback_list)
++{
++ int res = 0;
++ struct scst_tgt_template *tgtt;
++ uint8_t proto_id = cmd->sess->transport_id[0] & 0x0f;
++
++ TRACE_ENTRY();
++
++ /*
++ * We can't use scst_mutex here, because of the circular locking
++ * dependency with dev_pr_mutex.
++ */
++ mutex_lock(&scst_mutex2);
++
++ list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) {
++ struct scst_tgt *tgt;
++
++ if (tgtt->get_initiator_port_transport_id == NULL)
++ continue;
++
++ TRACE_PR("tgtt %s, spec_i_pt %d", tgtt->name, spec_i_pt);
++
++ list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
++ if (tgtt->get_initiator_port_transport_id(tgt, NULL, NULL) != proto_id)
++ continue;
++ if (tgt->rel_tgt_id == 0)
++ continue;
++ TRACE_PR("tgt %s, rel_tgt_id %d", tgt->tgt_name,
++ tgt->rel_tgt_id);
++ res = scst_pr_register_on_tgt_id(cmd, tgt->rel_tgt_id,
++ buffer, buffer_size, spec_i_pt, rollback_list);
++ if (res != 0)
++ goto out_unlock;
++ }
++ }
++
++out_unlock:
++ mutex_unlock(&scst_mutex2);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++static int __scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size, bool spec_i_pt, bool all_tg_pt)
++{
++ int res;
++ struct scst_dev_registrant *reg, *treg;
++ LIST_HEAD(rollback_list);
++
++ TRACE_ENTRY();
++
++ if (all_tg_pt) {
++ res = scst_pr_register_all_tg_pt(cmd, buffer, buffer_size,
++ spec_i_pt, &rollback_list);
++ if (res != 0)
++ goto out_rollback;
++ } else {
++ res = scst_pr_register_on_tgt_id(cmd,
++ cmd->sess->tgt->rel_tgt_id, buffer, buffer_size,
++ spec_i_pt, &rollback_list);
++ if (res != 0)
++ goto out_rollback;
++ }
++
++ list_for_each_entry(reg, &rollback_list, aux_list_entry) {
++ reg->rollback_key = 0;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_rollback:
++ list_for_each_entry_safe(reg, treg, &rollback_list, aux_list_entry) {
++ list_del(&reg->aux_list_entry);
++ if (reg->rollback_key == 0)
++ scst_pr_remove_registrant(cmd->dev, reg);
++ else {
++ reg->key = reg->rollback_key;
++ reg->rollback_key = 0;
++ }
++ }
++ goto out;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
++{
++ int aptpl, spec_i_pt, all_tg_pt;
++ __be64 key, action_key;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_session *sess = cmd->sess;
++ struct scst_dev_registrant *reg;
++
++ TRACE_ENTRY();
++
++ aptpl = buffer[20] & 0x01;
++ spec_i_pt = (buffer[20] >> 3) & 0x01;
++ all_tg_pt = (buffer[20] >> 2) & 0x01;
++ key = get_unaligned((__be64 *)&buffer[0]);
++ action_key = get_unaligned((__be64 *)&buffer[8]);
++
++ if (spec_i_pt == 0 && buffer_size != 24) {
++ TRACE_PR("Invalid buffer size %d", buffer_size);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
++ goto out;
++ }
++
++ reg = tgt_dev->registrant;
++
++ TRACE_PR("Register: initiator %s/%d (%p), key %0llx, action_key %0llx "
++ "(tgt_dev %p)",
++ debug_transport_id_to_initiator_name(sess->transport_id),
++ sess->tgt->rel_tgt_id, reg, key, action_key, tgt_dev);
++
++ if (reg == NULL) {
++ TRACE_PR("tgt_dev %p is not registered yet - registering",
++ tgt_dev);
++ if (key) {
++ TRACE_PR("%s", "Key must be zero on new registration");
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++ if (action_key) {
++ int rc = __scst_pr_register(cmd, buffer, buffer_size,
++ spec_i_pt, all_tg_pt);
++ if (rc != 0)
++ goto out;
++ } else
++ TRACE_PR("%s", "Doing nothing - action_key is zero");
++ } else {
++ if (reg->key != key) {
++ TRACE_PR("tgt_dev %p already registered - reservation "
++ "key %0llx mismatch", tgt_dev, reg->key);
++ scst_set_cmd_error_status(cmd,
++ SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++ if (spec_i_pt) {
++ TRACE_PR("%s", "spec_i_pt must be zero in this case");
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++ if (action_key == 0) {
++ if (all_tg_pt)
++ scst_pr_unregister_all_tg_pt(dev,
++ sess->transport_id);
++ else
++ scst_pr_unregister(dev, reg);
++ } else
++ reg->key = action_key;
++ }
++
++ dev->pr_generation++;
++
++ dev->pr_aptpl = aptpl;
++
++ scst_pr_dump_prs(dev, false);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_register_and_ignore(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size)
++{
++ int aptpl, all_tg_pt;
++ __be64 action_key;
++ struct scst_dev_registrant *reg = NULL;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_session *sess = cmd->sess;
++
++ TRACE_ENTRY();
++
++ aptpl = buffer[20] & 0x01;
++ all_tg_pt = (buffer[20] >> 2) & 0x01;
++ action_key = get_unaligned((__be64 *)&buffer[8]);
++
++ if (buffer_size != 24) {
++ TRACE_PR("Invalid buffer size %d", buffer_size);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
++ goto out;
++ }
++
++ reg = tgt_dev->registrant;
++
++ TRACE_PR("Register and ignore: initiator %s/%d (%p), action_key "
++ "%016llx (tgt_dev %p)",
++ debug_transport_id_to_initiator_name(sess->transport_id),
++ sess->tgt->rel_tgt_id, reg, action_key, tgt_dev);
++
++ if (reg == NULL) {
++ TRACE_PR("Tgt_dev %p is not registered yet - trying to "
++ "register", tgt_dev);
++ if (action_key) {
++ int rc = __scst_pr_register(cmd, buffer, buffer_size,
++ false, all_tg_pt);
++ if (rc != 0)
++ goto out;
++ } else
++ TRACE_PR("%s", "Doing nothing, action_key is zero");
++ } else {
++ if (action_key == 0) {
++ if (all_tg_pt)
++ scst_pr_unregister_all_tg_pt(dev,
++ sess->transport_id);
++ else
++ scst_pr_unregister(dev, reg);
++ } else
++ reg->key = action_key;
++ }
++
++ dev->pr_generation++;
++
++ dev->pr_aptpl = aptpl;
++
++ scst_pr_dump_prs(dev, false);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_register_and_move(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size)
++{
++ int aptpl;
++ int unreg;
++ int tid_buffer_size;
++ __be64 key, action_key;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_session *sess = cmd->sess;
++ struct scst_dev_registrant *reg, *reg_move;
++ const uint8_t *transport_id = NULL;
++ uint8_t *transport_id_move = NULL;
++ uint16_t rel_tgt_id_move;
++
++ TRACE_ENTRY();
++
++ aptpl = buffer[17] & 0x01;
++ key = get_unaligned((__be64 *)&buffer[0]);
++ action_key = get_unaligned((__be64 *)&buffer[8]);
++ unreg = (buffer[17] >> 1) & 0x01;
++ tid_buffer_size = be32_to_cpu(get_unaligned((__be32 *)&buffer[20]));
++
++ if ((tid_buffer_size + 24) > buffer_size) {
++ TRACE_PR("Invalid buffer size %d (%d)",
++ buffer_size, tid_buffer_size + 24);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
++ goto out;
++ }
++
++ if (tid_buffer_size < 24) {
++ TRACE_PR("%s", "Transport id buffer too small");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
++ goto out;
++ }
++
++ reg = tgt_dev->registrant;
++ /* We already checked reg is not NULL */
++ if (reg->key != key) {
++ TRACE_PR("Registrant's %s/%d (%p) key %016llx mismatch with "
++ "%016llx (tgt_dev %p)",
++ debug_transport_id_to_initiator_name(reg->transport_id),
++ reg->rel_tgt_id, reg, reg->key, key, tgt_dev);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++
++ if (!dev->pr_is_set) {
++ TRACE_PR("%s", "There must be a PR");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++
++ /*
++ * This check also required by table "PERSISTENT RESERVE OUT service
++ * actions that are allowed in the presence of various reservations".
++ */
++ if (!scst_pr_is_holder(dev, reg)) {
++ TRACE_PR("Registrant %s/%d (%p) is not a holder (tgt_dev %p)",
++ debug_transport_id_to_initiator_name(
++ reg->transport_id), reg->rel_tgt_id,
++ reg, tgt_dev);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++
++ if (action_key == 0) {
++ TRACE_PR("%s", "Action key must be non-zero");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++
++ transport_id = sess->transport_id;
++ transport_id_move = (uint8_t *)&buffer[24];
++ rel_tgt_id_move = be16_to_cpu(get_unaligned((__be16 *)&buffer[18]));
++
++ if ((tid_size(transport_id_move) + 24) > buffer_size) {
++ TRACE_PR("Invalid buffer size %d (%d)",
++ buffer_size, tid_size(transport_id_move) + 24);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
++ goto out;
++ }
++
++ tid_secure(transport_id_move);
++
++ if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG ||
++ dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) {
++ TRACE_PR("Unable to finish operation due to wrong reservation "
++ "type %02x", dev->pr_type);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++
++ if (tid_equal(transport_id, transport_id_move)) {
++ TRACE_PR("%s", "Equal transport id's");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
++ goto out;
++ }
++
++ reg_move = scst_pr_find_reg(dev, transport_id_move, rel_tgt_id_move);
++ if (reg_move == NULL) {
++ reg_move = scst_pr_add_registrant(dev, transport_id_move,
++ rel_tgt_id_move, action_key, false);
++ if (reg_move == NULL) {
++ scst_set_busy(cmd);
++ goto out;
++ }
++ } else if (reg_move->key != action_key) {
++ TRACE_PR("Changing key for reg %p", reg);
++ reg_move->key = action_key;
++ }
++
++ TRACE_PR("Register and move: from initiator %s/%d (%p, tgt_dev %p) to "
++ "initiator %s/%d (%p, tgt_dev %p), key %016llx (unreg %d)",
++ debug_transport_id_to_initiator_name(reg->transport_id),
++ reg->rel_tgt_id, reg, reg->tgt_dev,
++ debug_transport_id_to_initiator_name(transport_id_move),
++ rel_tgt_id_move, reg_move, reg_move->tgt_dev, action_key,
++ unreg);
++
++ /* Move the holder */
++ scst_pr_set_holder(dev, reg_move, dev->pr_scope, dev->pr_type);
++
++ if (unreg)
++ scst_pr_remove_registrant(dev, reg);
++
++ dev->pr_generation++;
++
++ dev->pr_aptpl = aptpl;
++
++ scst_pr_dump_prs(dev, false);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_reserve(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
++{
++ uint8_t scope, type;
++ __be64 key;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_dev_registrant *reg;
++
++ TRACE_ENTRY();
++
++ key = get_unaligned((__be64 *)&buffer[0]);
++ scope = (cmd->cdb[2] & 0x0f) >> 4;
++ type = cmd->cdb[2] & 0x0f;
++
++ if (buffer_size != 24) {
++ TRACE_PR("Invalid buffer size %d", buffer_size);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
++ goto out;
++ }
++
++ if (!scst_pr_type_valid(type)) {
++ TRACE_PR("Invalid reservation type %d", type);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++
++ if (((cmd->cdb[2] & 0x0f) >> 4) != SCOPE_LU) {
++ TRACE_PR("Invalid reservation scope %d", scope);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++
++ reg = tgt_dev->registrant;
++
++ TRACE_PR("Reserve: initiator %s/%d (%p), key %016llx, scope %d, "
++ "type %d (tgt_dev %p)",
++ debug_transport_id_to_initiator_name(cmd->sess->transport_id),
++ cmd->sess->tgt->rel_tgt_id, reg, key, scope, type, tgt_dev);
++
++ /* We already checked reg is not NULL */
++ if (reg->key != key) {
++ TRACE_PR("Registrant's %p key %016llx mismatch with %016llx",
++ reg, reg->key, key);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++
++ if (!dev->pr_is_set)
++ scst_pr_set_holder(dev, reg, scope, type);
++ else {
++ if (!scst_pr_is_holder(dev, reg)) {
++ /*
++ * This check also required by table "PERSISTENT
++ * RESERVE OUT service actions that are allowed in the
++ * presence of various reservations".
++ */
++ TRACE_PR("Only holder can override - reg %p is not a "
++ "holder", reg);
++ scst_set_cmd_error_status(cmd,
++ SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ } else {
++ if (dev->pr_scope != scope || dev->pr_type != type) {
++ TRACE_PR("Error overriding scope or type for "
++ "reg %p", reg);
++ scst_set_cmd_error_status(cmd,
++ SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ } else
++ TRACE_PR("Do nothing: reservation of reg %p "
++ "is the same", reg);
++ }
++ }
++
++ scst_pr_dump_prs(dev, false);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_release(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
++{
++ int scope, type;
++ __be64 key;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_dev_registrant *reg;
++ uint8_t cur_pr_type;
++
++ TRACE_ENTRY();
++
++ key = get_unaligned((__be64 *)&buffer[0]);
++ scope = (cmd->cdb[2] & 0x0f) >> 4;
++ type = cmd->cdb[2] & 0x0f;
++
++ if (buffer_size != 24) {
++ TRACE_PR("Invalid buffer size %d", buffer_size);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
++ goto out;
++ }
++
++ if (!dev->pr_is_set) {
++ TRACE_PR("%s", "There is no PR - do nothing");
++ goto out;
++ }
++
++ reg = tgt_dev->registrant;
++
++ TRACE_PR("Release: initiator %s/%d (%p), key %016llx, scope %d, type "
++ "%d (tgt_dev %p)", debug_transport_id_to_initiator_name(
++ cmd->sess->transport_id),
++ cmd->sess->tgt->rel_tgt_id, reg, key, scope, type, tgt_dev);
++
++ /* We already checked reg is not NULL */
++ if (reg->key != key) {
++ TRACE_PR("Registrant's %p key %016llx mismatch with %016llx",
++ reg, reg->key, key);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++
++ if (!scst_pr_is_holder(dev, reg)) {
++ TRACE_PR("Registrant %p is not a holder - do nothing", reg);
++ goto out;
++ }
++
++ if (dev->pr_scope != scope || dev->pr_type != type) {
++ TRACE_PR("%s", "Released scope or type do not match with "
++ "holder");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_release));
++ goto out;
++ }
++
++ cur_pr_type = dev->pr_type; /* it will be cleared */
++
++ scst_pr_clear_reservation(dev);
++
++ switch (cur_pr_type) {
++ case TYPE_WRITE_EXCLUSIVE_REGONLY:
++ case TYPE_EXCLUSIVE_ACCESS_REGONLY:
++ case TYPE_WRITE_EXCLUSIVE_ALL_REG:
++ case TYPE_EXCLUSIVE_ACCESS_ALL_REG:
++ scst_pr_send_ua_all(dev, reg,
++ SCST_LOAD_SENSE(scst_sense_reservation_released));
++ }
++
++ scst_pr_dump_prs(dev, false);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_clear(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
++{
++ __be64 key;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_dev_registrant *reg, *r, *t;
++
++ TRACE_ENTRY();
++
++ key = get_unaligned((__be64 *)&buffer[0]);
++
++ if (buffer_size != 24) {
++ TRACE_PR("Invalid buffer size %d", buffer_size);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
++ goto out;
++ }
++
++ reg = tgt_dev->registrant;
++
++ TRACE_PR("Clear: initiator %s/%d (%p), key %016llx (tgt_dev %p)",
++ debug_transport_id_to_initiator_name(cmd->sess->transport_id),
++ cmd->sess->tgt->rel_tgt_id, reg, key, tgt_dev);
++
++ /* We already checked reg is not NULL */
++ if (reg->key != key) {
++ TRACE_PR("Registrant's %p key %016llx mismatch with %016llx",
++ reg, reg->key, key);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++
++ scst_pr_send_ua_all(dev, reg,
++ SCST_LOAD_SENSE(scst_sense_reservation_preempted));
++
++ list_for_each_entry_safe(r, t, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ scst_pr_remove_registrant(dev, r);
++ }
++
++ dev->pr_generation++;
++
++ scst_pr_dump_prs(dev, false);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void scst_pr_do_preempt(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size, bool abort)
++{
++ __be64 key, action_key;
++ int scope, type;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_dev_registrant *reg, *r, *rt;
++ int existing_pr_type = dev->pr_type;
++ int existing_pr_scope = dev->pr_scope;
++ LIST_HEAD(preempt_list);
++
++ TRACE_ENTRY();
++
++ key = get_unaligned((__be64 *)&buffer[0]);
++ action_key = get_unaligned((__be64 *)&buffer[8]);
++ scope = (cmd->cdb[2] & 0x0f) >> 4;
++ type = cmd->cdb[2] & 0x0f;
++
++ if (buffer_size != 24) {
++ TRACE_PR("Invalid buffer size %d", buffer_size);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid));
++ goto out;
++ }
++
++ if (!scst_pr_type_valid(type)) {
++ TRACE_PR("Invalid reservation type %d", type);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++
++ reg = tgt_dev->registrant;
++
++ TRACE_PR("Preempt%s: initiator %s/%d (%p), key %016llx, action_key "
++ "%016llx, scope %x type %x (tgt_dev %p)",
++ abort ? " and abort" : "",
++ debug_transport_id_to_initiator_name(cmd->sess->transport_id),
++ cmd->sess->tgt->rel_tgt_id, reg, key, action_key, scope, type,
++ tgt_dev);
++
++ /* We already checked reg is not NULL */
++ if (reg->key != key) {
++ TRACE_PR("Registrant's %p key %016llx mismatch with %016llx",
++ reg, reg->key, key);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++ }
++
++ if (!dev->pr_is_set) {
++ scst_pr_find_registrants_list_key(dev, action_key,
++ &preempt_list);
++ if (list_empty(&preempt_list))
++ goto out_error;
++ list_for_each_entry_safe(r, rt, &preempt_list, aux_list_entry) {
++ if (abort)
++ scst_pr_abort_reg(dev, cmd, r);
++ if (r != reg) {
++ scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE(
++ scst_sense_registrations_preempted));
++ scst_pr_remove_registrant(dev, r);
++ }
++ }
++ goto done;
++ }
++
++ if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG ||
++ dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) {
++ if (action_key == 0) {
++ scst_pr_find_registrants_list_all(dev, reg,
++ &preempt_list);
++ list_for_each_entry_safe(r, rt, &preempt_list,
++ aux_list_entry) {
++ BUG_ON(r == reg);
++ if (abort)
++ scst_pr_abort_reg(dev, cmd, r);
++ scst_pr_send_ua_reg(dev, r,
++ SCST_LOAD_SENSE(
++ scst_sense_registrations_preempted));
++ scst_pr_remove_registrant(dev, r);
++ }
++ scst_pr_set_holder(dev, reg, scope, type);
++ } else {
++ scst_pr_find_registrants_list_key(dev, action_key,
++ &preempt_list);
++ if (list_empty(&preempt_list))
++ goto out_error;
++ list_for_each_entry_safe(r, rt, &preempt_list,
++ aux_list_entry) {
++ if (abort)
++ scst_pr_abort_reg(dev, cmd, r);
++ if (r != reg) {
++ scst_pr_send_ua_reg(dev, r,
++ SCST_LOAD_SENSE(
++ scst_sense_registrations_preempted));
++ scst_pr_remove_registrant(dev, r);
++ }
++ }
++ }
++ goto done;
++ }
++
++ if (dev->pr_holder->key != action_key) {
++ if (action_key == 0) {
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_invalid_field_in_parm_list));
++ goto out;
++ } else {
++ scst_pr_find_registrants_list_key(dev, action_key,
++ &preempt_list);
++ if (list_empty(&preempt_list))
++ goto out_error;
++ list_for_each_entry_safe(r, rt, &preempt_list,
++ aux_list_entry) {
++ if (abort)
++ scst_pr_abort_reg(dev, cmd, r);
++ if (r != reg)
++ scst_pr_send_ua_reg(dev, r,
++ SCST_LOAD_SENSE(
++ scst_sense_registrations_preempted));
++ scst_pr_remove_registrant(dev, r);
++ }
++ goto done;
++ }
++ }
++
++ scst_pr_find_registrants_list_key(dev, action_key,
++ &preempt_list);
++
++ list_for_each_entry_safe(r, rt, &preempt_list, aux_list_entry) {
++ if (abort)
++ scst_pr_abort_reg(dev, cmd, r);
++ if (r != reg) {
++ scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE(
++ scst_sense_registrations_preempted));
++ scst_pr_remove_registrant(dev, r);
++ }
++ }
++
++ scst_pr_set_holder(dev, reg, scope, type);
++
++ if (existing_pr_type != type || existing_pr_scope != scope) {
++ list_for_each_entry(r, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ if (r != reg)
++ scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE(
++ scst_sense_reservation_released));
++ }
++ }
++
++done:
++ dev->pr_generation++;
++
++ scst_pr_dump_prs(dev, false);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_error:
++ TRACE_PR("Invalid key %016llx", action_key);
++ scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT);
++ goto out;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_preempt(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
++{
++ TRACE_ENTRY();
++
++ scst_pr_do_preempt(cmd, buffer, buffer_size, false);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void scst_cmd_done_pr_preempt(struct scst_cmd *cmd, int next_state,
++ enum scst_exec_context pref_context)
++{
++ void (*saved_cmd_done) (struct scst_cmd *cmd, int next_state,
++ enum scst_exec_context pref_context);
++
++ TRACE_ENTRY();
++
++ if (!atomic_dec_and_test(&cmd->pr_abort_counter->pr_abort_pending_cnt))
++ goto out;
++
++ saved_cmd_done = cmd->pr_abort_counter->saved_cmd_done;
++ kfree(cmd->pr_abort_counter);
++ cmd->pr_abort_counter = NULL;
++
++ saved_cmd_done(cmd, next_state, pref_context);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * Called with dev_pr_mutex locked, no IRQ. Expects session_list_lock
++ * not locked
++ */
++void scst_pr_preempt_and_abort(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size)
++{
++ TRACE_ENTRY();
++
++ cmd->pr_abort_counter = kzalloc(sizeof(*cmd->pr_abort_counter),
++ GFP_KERNEL);
++ if (cmd->pr_abort_counter == NULL) {
++ PRINT_ERROR("Unable to allocate PR abort counter (size %zd)",
++ sizeof(*cmd->pr_abort_counter));
++ scst_set_busy(cmd);
++ goto out;
++ }
++
++ /* 1 to protect cmd from be done by the TM thread too early */
++ atomic_set(&cmd->pr_abort_counter->pr_abort_pending_cnt, 1);
++ atomic_set(&cmd->pr_abort_counter->pr_aborting_cnt, 1);
++ init_completion(&cmd->pr_abort_counter->pr_aborting_cmpl);
++
++ cmd->pr_abort_counter->saved_cmd_done = cmd->scst_cmd_done;
++ cmd->scst_cmd_done = scst_cmd_done_pr_preempt;
++
++ scst_pr_do_preempt(cmd, buffer, buffer_size, true);
++
++ if (!atomic_dec_and_test(&cmd->pr_abort_counter->pr_aborting_cnt))
++ wait_for_completion(&cmd->pr_abort_counter->pr_aborting_cmpl);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Checks if this is a Compatible Reservation Handling (CRH) case */
++bool scst_pr_crh_case(struct scst_cmd *cmd)
++{
++ bool allowed;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_dev_registrant *reg;
++ uint8_t type;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Test if there is a CRH case for command %s (0x%x) from "
++ "%s", cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name);
++
++ if (!dev->pr_is_set) {
++ TRACE_PR("%s", "PR not set");
++ allowed = false;
++ goto out;
++ }
++
++ reg = tgt_dev->registrant;
++ type = dev->pr_type;
++
++ switch (type) {
++ case TYPE_WRITE_EXCLUSIVE:
++ case TYPE_EXCLUSIVE_ACCESS:
++ WARN_ON(dev->pr_holder == NULL);
++ if (reg == dev->pr_holder)
++ allowed = true;
++ else
++ allowed = false;
++ break;
++
++ case TYPE_WRITE_EXCLUSIVE_REGONLY:
++ case TYPE_EXCLUSIVE_ACCESS_REGONLY:
++ case TYPE_WRITE_EXCLUSIVE_ALL_REG:
++ case TYPE_EXCLUSIVE_ACCESS_ALL_REG:
++ allowed = (reg != NULL);
++ break;
++
++ default:
++ PRINT_ERROR("Invalid PR type %x", type);
++ allowed = false;
++ break;
++ }
++
++ if (!allowed)
++ TRACE_PR("Command %s (0x%x) from %s rejected due to not CRH "
++ "reservation", cmd->op_name, cmd->cdb[0],
++ cmd->sess->initiator_name);
++ else
++ TRACE_DBG("Command %s (0x%x) from %s is allowed to execute "
++ "due to CRH", cmd->op_name, cmd->cdb[0],
++ cmd->sess->initiator_name);
++
++out:
++ TRACE_EXIT_RES(allowed);
++ return allowed;
++
++}
++
++/* Check if command allowed in presence of reservation */
++bool scst_pr_is_cmd_allowed(struct scst_cmd *cmd)
++{
++ bool allowed;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_dev_registrant *reg;
++ uint8_t type;
++ bool unlock;
++
++ TRACE_ENTRY();
++
++ unlock = scst_pr_read_lock(cmd);
++
++ TRACE_DBG("Testing if command %s (0x%x) from %s allowed to execute",
++ cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name);
++
++ /* Recheck, because it can change while we were waiting for the lock */
++ if (unlikely(!dev->pr_is_set)) {
++ allowed = true;
++ goto out_unlock;
++ }
++
++ reg = tgt_dev->registrant;
++ type = dev->pr_type;
++
++ switch (type) {
++ case TYPE_WRITE_EXCLUSIVE:
++ if (reg && reg == dev->pr_holder)
++ allowed = true;
++ else
++ allowed = (cmd->op_flags & SCST_WRITE_EXCL_ALLOWED) != 0;
++ break;
++
++ case TYPE_EXCLUSIVE_ACCESS:
++ if (reg && reg == dev->pr_holder)
++ allowed = true;
++ else
++ allowed = (cmd->op_flags & SCST_EXCL_ACCESS_ALLOWED) != 0;
++ break;
++
++ case TYPE_WRITE_EXCLUSIVE_REGONLY:
++ case TYPE_WRITE_EXCLUSIVE_ALL_REG:
++ if (reg)
++ allowed = true;
++ else
++ allowed = (cmd->op_flags & SCST_WRITE_EXCL_ALLOWED) != 0;
++ break;
++
++ case TYPE_EXCLUSIVE_ACCESS_REGONLY:
++ case TYPE_EXCLUSIVE_ACCESS_ALL_REG:
++ if (reg)
++ allowed = true;
++ else
++ allowed = (cmd->op_flags & SCST_EXCL_ACCESS_ALLOWED) != 0;
++ break;
++
++ default:
++ PRINT_ERROR("Invalid PR type %x", type);
++ allowed = false;
++ break;
++ }
++
++ if (!allowed)
++ TRACE_PR("Command %s (0x%x) from %s rejected due "
++ "to PR", cmd->op_name, cmd->cdb[0],
++ cmd->sess->initiator_name);
++ else
++ TRACE_DBG("Command %s (0x%x) from %s is allowed to execute",
++ cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name);
++
++out_unlock:
++ scst_pr_read_unlock(cmd, unlock);
++
++ TRACE_EXIT_RES(allowed);
++ return allowed;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_read_keys(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
++{
++ int i, offset = 0, size, size_max;
++ struct scst_device *dev = cmd->dev;
++ struct scst_dev_registrant *reg;
++
++ TRACE_ENTRY();
++
++ if (buffer_size < 8) {
++ TRACE_PR("buffer_size too small: %d. expected >= 8 "
++ "(buffer %p)", buffer_size, buffer);
++ goto skip;
++ }
++
++ TRACE_PR("Read Keys (dev %s): PRGen %d", dev->virt_name,
++ dev->pr_generation);
++
++ put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&buffer[0]);
++
++ offset = 8;
++ size = 0;
++ size_max = buffer_size - 8;
++
++ i = 0;
++ list_for_each_entry(reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ if (size_max - size >= 8) {
++ TRACE_PR("Read Keys (dev %s): key 0x%llx",
++ dev->virt_name, reg->key);
++
++ WARN_ON(reg->key == 0);
++
++ put_unaligned(reg->key,
++ (__be64 *)&buffer[offset + 8 * i]);
++
++ offset += 8;
++ }
++ size += 8;
++ }
++
++ put_unaligned(cpu_to_be32(size), (__be32 *)&buffer[4]);
++
++skip:
++ scst_set_resp_data_len(cmd, offset);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_read_reservation(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size)
++{
++ struct scst_device *dev = cmd->dev;
++ uint8_t b[24];
++ int size = 0;
++
++ TRACE_ENTRY();
++
++ if (buffer_size < 8) {
++ TRACE_PR("buffer_size too small: %d. expected >= 8 "
++ "(buffer %p)", buffer_size, buffer);
++ goto skip;
++ }
++
++ memset(b, 0, sizeof(b));
++
++ put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&b[0]);
++
++ if (!dev->pr_is_set) {
++ TRACE_PR("Read Reservation: no reservations for dev %s",
++ dev->virt_name);
++ b[4] =
++ b[5] =
++ b[6] =
++ b[7] = 0;
++
++ size = 8;
++ } else {
++ __be64 key = dev->pr_holder ? dev->pr_holder->key : 0;
++
++ TRACE_PR("Read Reservation: dev %s, holder %p, key 0x%llx, "
++ "scope %d, type %d", dev->virt_name, dev->pr_holder,
++ key, dev->pr_scope, dev->pr_type);
++
++ b[4] =
++ b[5] =
++ b[6] = 0;
++ b[7] = 0x10;
++
++ put_unaligned(key, (__be64 *)&b[8]);
++ b[21] = dev->pr_scope << 4 | dev->pr_type;
++
++ size = 24;
++ }
++
++ memset(buffer, 0, buffer_size);
++ memcpy(buffer, b, min(size, buffer_size));
++
++skip:
++ scst_set_resp_data_len(cmd, size);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_report_caps(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
++{
++ int offset = 0;
++ unsigned int crh = 1;
++ unsigned int atp_c = 1;
++ unsigned int sip_c = 1;
++ unsigned int ptpl_c = 1;
++ struct scst_device *dev = cmd->dev;
++
++ TRACE_ENTRY();
++
++ if (buffer_size < 8) {
++ TRACE_PR("buffer_size too small: %d. expected >= 8 "
++ "(buffer %p)", buffer_size, buffer);
++ goto skip;
++ }
++
++ TRACE_PR("Reporting capabilities (dev %s): crh %x, sip_c %x, "
++ "atp_c %x, ptpl_c %x, pr_aptpl %x", dev->virt_name,
++ crh, sip_c, atp_c, ptpl_c, dev->pr_aptpl);
++
++ buffer[0] = 0;
++ buffer[1] = 8;
++
++ buffer[2] = crh << 4 | sip_c << 3 | atp_c << 2 | ptpl_c;
++ buffer[3] = (1 << 7) | (dev->pr_aptpl > 0 ? 1 : 0);
++
++ /* All commands supported */
++ buffer[4] = 0xEA;
++ buffer[5] = 0x1;
++
++ offset += 8;
++
++skip:
++ scst_set_resp_data_len(cmd, offset);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called with dev_pr_mutex locked, no IRQ */
++void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer,
++ int buffer_size)
++{
++ int offset = 0, size, size_max;
++ struct scst_device *dev = cmd->dev;
++ struct scst_dev_registrant *reg;
++
++ TRACE_ENTRY();
++
++ if (buffer_size < 8)
++ goto skip;
++
++ put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&buffer[0]);
++ offset += 8;
++
++ size = 0;
++ size_max = buffer_size - 8;
++
++ list_for_each_entry(reg, &dev->dev_registrants_list,
++ dev_registrants_list_entry) {
++ int ts;
++ int rec_len;
++
++ ts = tid_size(reg->transport_id);
++ rec_len = 24 + ts;
++
++ if (size_max - size > rec_len) {
++ memset(&buffer[offset], 0, rec_len);
++
++ put_unaligned(reg->key, (__be64 *)(&buffer[offset]));
++
++ if (dev->pr_is_set && scst_pr_is_holder(dev, reg)) {
++ buffer[offset + 12] = 1;
++ buffer[offset + 13] = (dev->pr_scope << 4) | dev->pr_type;
++ }
++
++ put_unaligned(cpu_to_be16(reg->rel_tgt_id),
++ (__be16 *)&buffer[offset + 18]);
++ put_unaligned(cpu_to_be32(ts),
++ (__be32 *)&buffer[offset + 20]);
++
++ memcpy(&buffer[offset + 24], reg->transport_id, ts);
++
++ offset += rec_len;
++ }
++ size += rec_len;
++ }
++
++ put_unaligned(cpu_to_be32(size), (__be32 *)&buffer[4]);
++
++skip:
++ scst_set_resp_data_len(cmd, offset);
++
++ TRACE_EXIT();
++ return;
++}
+diff -uprN orig/linux-3.2/drivers/scst/scst_sysfs.c linux-3.2/drivers/scst/scst_sysfs.c
+--- orig/linux-3.2/drivers/scst/scst_sysfs.c
++++ linux-3.2/drivers/scst/scst_sysfs.c
+@@ -0,0 +1,6224 @@
++/*
++ * scst_sysfs.c
++ *
++ * Copyright (C) 2009 Daniel Henrique Debonzi <debonzi@linux.vnet.ibm.com>
++ * Copyright (C) 2009 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2009 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/kobject.h>
++#include <linux/string.h>
++#include <linux/sysfs.h>
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/ctype.h>
++#include <linux/slab.h>
++#include <linux/kthread.h>
++
++#include <scst/scst.h>
++#include "scst_priv.h"
++#include "scst_pres.h"
++
++static DECLARE_COMPLETION(scst_sysfs_root_release_completion);
++
++static struct kobject *scst_targets_kobj;
++static struct kobject *scst_devices_kobj;
++static struct kobject *scst_handlers_kobj;
++static struct kobject *scst_device_groups_kobj;
++
++static const char *const scst_dev_handler_types[] = {
++ "Direct-access device (e.g., magnetic disk)",
++ "Sequential-access device (e.g., magnetic tape)",
++ "Printer device",
++ "Processor device",
++ "Write-once device (e.g., some optical disks)",
++ "CD-ROM device",
++ "Scanner device (obsolete)",
++ "Optical memory device (e.g., some optical disks)",
++ "Medium changer device (e.g., jukeboxes)",
++ "Communications device (obsolete)",
++ "Defined by ASC IT8 (Graphic arts pre-press devices)",
++ "Defined by ASC IT8 (Graphic arts pre-press devices)",
++ "Storage array controller device (e.g., RAID)",
++ "Enclosure services device",
++ "Simplified direct-access device (e.g., magnetic disk)",
++ "Optical card reader/writer device"
++};
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++static DEFINE_MUTEX(scst_log_mutex);
++
++static struct scst_trace_log scst_trace_tbl[] = {
++ { TRACE_OUT_OF_MEM, "out_of_mem" },
++ { TRACE_MINOR, "minor" },
++ { TRACE_SG_OP, "sg" },
++ { TRACE_MEMORY, "mem" },
++ { TRACE_BUFF, "buff" },
++#ifndef GENERATING_UPSTREAM_PATCH
++ { TRACE_ENTRYEXIT, "entryexit" },
++#endif
++ { TRACE_PID, "pid" },
++ { TRACE_LINE, "line" },
++ { TRACE_FUNCTION, "function" },
++ { TRACE_DEBUG, "debug" },
++ { TRACE_SPECIAL, "special" },
++ { TRACE_SCSI, "scsi" },
++ { TRACE_MGMT, "mgmt" },
++ { TRACE_MGMT_DEBUG, "mgmt_dbg" },
++ { TRACE_FLOW_CONTROL, "flow_control" },
++ { TRACE_PRES, "pr" },
++ { 0, NULL }
++};
++
++static struct scst_trace_log scst_local_trace_tbl[] = {
++ { TRACE_RTRY, "retry" },
++ { TRACE_SCSI_SERIALIZING, "scsi_serializing" },
++ { TRACE_RCV_BOT, "recv_bot" },
++ { TRACE_SND_BOT, "send_bot" },
++ { TRACE_RCV_TOP, "recv_top" },
++ { TRACE_SND_TOP, "send_top" },
++ { 0, NULL }
++};
++
++static void scst_read_trace_tbl(const struct scst_trace_log *tbl, char *buf,
++ unsigned long log_level, int *pos)
++{
++ const struct scst_trace_log *t = tbl;
++
++ if (t == NULL)
++ goto out;
++
++ while (t->token) {
++ if (log_level & t->val) {
++ *pos += sprintf(&buf[*pos], "%s%s",
++ (*pos == 0) ? "" : " | ",
++ t->token);
++ }
++ t++;
++ }
++out:
++ return;
++}
++
++static ssize_t scst_trace_level_show(const struct scst_trace_log *local_tbl,
++ unsigned long log_level, char *buf, const char *help)
++{
++ int pos = 0;
++
++ scst_read_trace_tbl(scst_trace_tbl, buf, log_level, &pos);
++ scst_read_trace_tbl(local_tbl, buf, log_level, &pos);
++
++ pos += sprintf(&buf[pos], "\n\n\nUsage:\n"
++ " echo \"all|none|default\" >trace_level\n"
++ " echo \"value DEC|0xHEX|0OCT\" >trace_level\n"
++ " echo \"add|del TOKEN\" >trace_level\n"
++ "\nwhere TOKEN is one of [debug, function, line, pid,\n"
++#ifndef GENERATING_UPSTREAM_PATCH
++ " entryexit, buff, mem, sg, out_of_mem,\n"
++#else
++ " buff, mem, sg, out_of_mem,\n"
++#endif
++ " special, scsi, mgmt, minor,\n"
++ " mgmt_dbg, scsi_serializing,\n"
++ " retry, recv_bot, send_bot, recv_top, pr,\n"
++ " send_top%s]\n", help != NULL ? help : "");
++
++ return pos;
++}
++
++static int scst_write_trace(const char *buf, size_t length,
++ unsigned long *log_level, unsigned long default_level,
++ const char *name, const struct scst_trace_log *tbl)
++{
++ int res = length;
++ int action;
++ unsigned long level = 0, oldlevel;
++ char *buffer, *p, *e;
++ const struct scst_trace_log *t;
++ enum {
++ SCST_TRACE_ACTION_ALL = 1,
++ SCST_TRACE_ACTION_NONE = 2,
++ SCST_TRACE_ACTION_DEFAULT = 3,
++ SCST_TRACE_ACTION_ADD = 4,
++ SCST_TRACE_ACTION_DEL = 5,
++ SCST_TRACE_ACTION_VALUE = 6,
++ };
++
++ TRACE_ENTRY();
++
++ if ((buf == NULL) || (length == 0)) {
++ res = -EINVAL;
++ goto out;
++ }
++
++ buffer = kasprintf(GFP_KERNEL, "%.*s", (int)length, buf);
++ if (buffer == NULL) {
++ PRINT_ERROR("Unable to alloc intermediate buffer (size %zd)",
++ length+1);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ TRACE_DBG("buffer %s", buffer);
++
++ p = buffer;
++ if (!strncasecmp("all", p, 3)) {
++ action = SCST_TRACE_ACTION_ALL;
++ } else if (!strncasecmp("none", p, 4) || !strncasecmp("null", p, 4)) {
++ action = SCST_TRACE_ACTION_NONE;
++ } else if (!strncasecmp("default", p, 7)) {
++ action = SCST_TRACE_ACTION_DEFAULT;
++ } else if (!strncasecmp("add", p, 3)) {
++ p += 3;
++ action = SCST_TRACE_ACTION_ADD;
++ } else if (!strncasecmp("del", p, 3)) {
++ p += 3;
++ action = SCST_TRACE_ACTION_DEL;
++ } else if (!strncasecmp("value", p, 5)) {
++ p += 5;
++ action = SCST_TRACE_ACTION_VALUE;
++ } else {
++ if (p[strlen(p) - 1] == '\n')
++ p[strlen(p) - 1] = '\0';
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ switch (action) {
++ case SCST_TRACE_ACTION_ADD:
++ case SCST_TRACE_ACTION_DEL:
++ case SCST_TRACE_ACTION_VALUE:
++ if (!isspace(*p)) {
++ PRINT_ERROR("%s", "Syntax error");
++ res = -EINVAL;
++ goto out_free;
++ }
++ }
++
++ switch (action) {
++ case SCST_TRACE_ACTION_ALL:
++ level = TRACE_ALL;
++ break;
++ case SCST_TRACE_ACTION_DEFAULT:
++ level = default_level;
++ break;
++ case SCST_TRACE_ACTION_NONE:
++ level = TRACE_NULL;
++ break;
++ case SCST_TRACE_ACTION_ADD:
++ case SCST_TRACE_ACTION_DEL:
++ while (isspace(*p) && *p != '\0')
++ p++;
++ e = p;
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = 0;
++ if (tbl) {
++ t = tbl;
++ while (t->token) {
++ if (!strcasecmp(p, t->token)) {
++ level = t->val;
++ break;
++ }
++ t++;
++ }
++ }
++ if (level == 0) {
++ t = scst_trace_tbl;
++ while (t->token) {
++ if (!strcasecmp(p, t->token)) {
++ level = t->val;
++ break;
++ }
++ t++;
++ }
++ }
++ if (level == 0) {
++ PRINT_ERROR("Unknown token \"%s\"", p);
++ res = -EINVAL;
++ goto out_free;
++ }
++ break;
++ case SCST_TRACE_ACTION_VALUE:
++ while (isspace(*p) && *p != '\0')
++ p++;
++ res = strict_strtoul(p, 0, &level);
++ if (res != 0) {
++ PRINT_ERROR("Invalid trace value \"%s\"", p);
++ res = -EINVAL;
++ goto out_free;
++ }
++ break;
++ }
++
++ oldlevel = *log_level;
++
++ switch (action) {
++ case SCST_TRACE_ACTION_ADD:
++ *log_level |= level;
++ break;
++ case SCST_TRACE_ACTION_DEL:
++ *log_level &= ~level;
++ break;
++ default:
++ *log_level = level;
++ break;
++ }
++
++ PRINT_INFO("Changed trace level for \"%s\": old 0x%08lx, new 0x%08lx",
++ name, oldlevel, *log_level);
++
++out_free:
++ kfree(buffer);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++/**
++ ** Sysfs work
++ **/
++
++static DEFINE_SPINLOCK(sysfs_work_lock);
++static LIST_HEAD(sysfs_work_list);
++static DECLARE_WAIT_QUEUE_HEAD(sysfs_work_waitQ);
++static int active_sysfs_works;
++static int last_sysfs_work_res;
++static struct task_struct *sysfs_work_thread;
++
++/**
++ * scst_alloc_sysfs_work() - allocates a sysfs work
++ */
++int scst_alloc_sysfs_work(int (*sysfs_work_fn)(struct scst_sysfs_work_item *),
++ bool read_only_action, struct scst_sysfs_work_item **res_work)
++{
++ int res = 0;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ if (sysfs_work_fn == NULL) {
++ PRINT_ERROR("%s", "sysfs_work_fn is NULL");
++ res = -EINVAL;
++ goto out;
++ }
++
++ *res_work = NULL;
++
++ work = kzalloc(sizeof(*work), GFP_KERNEL);
++ if (work == NULL) {
++ PRINT_ERROR("Unable to alloc sysfs work (size %zd)",
++ sizeof(*work));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ work->read_only_action = read_only_action;
++ kref_init(&work->sysfs_work_kref);
++ init_completion(&work->sysfs_work_done);
++ work->sysfs_work_fn = sysfs_work_fn;
++
++ *res_work = work;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(scst_alloc_sysfs_work);
++
++static void scst_sysfs_work_release(struct kref *kref)
++{
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ work = container_of(kref, struct scst_sysfs_work_item,
++ sysfs_work_kref);
++
++ TRACE_DBG("Freeing sysfs work %p (buf %p)", work, work->buf);
++
++ kfree(work->buf);
++ kfree(work->res_buf);
++ kfree(work);
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * scst_sysfs_work_get() - increases ref counter of the sysfs work
++ */
++void scst_sysfs_work_get(struct scst_sysfs_work_item *work)
++{
++ kref_get(&work->sysfs_work_kref);
++}
++EXPORT_SYMBOL(scst_sysfs_work_get);
++
++/**
++ * scst_sysfs_work_put() - decreases ref counter of the sysfs work
++ */
++void scst_sysfs_work_put(struct scst_sysfs_work_item *work)
++{
++ kref_put(&work->sysfs_work_kref, scst_sysfs_work_release);
++}
++EXPORT_SYMBOL(scst_sysfs_work_put);
++
++/* Called under sysfs_work_lock and drops/reacquire it inside */
++static void scst_process_sysfs_works(void)
++ __releases(&sysfs_work_lock)
++ __acquires(&sysfs_work_lock)
++{
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ while (!list_empty(&sysfs_work_list)) {
++ work = list_entry(sysfs_work_list.next,
++ struct scst_sysfs_work_item, sysfs_work_list_entry);
++ list_del(&work->sysfs_work_list_entry);
++ spin_unlock(&sysfs_work_lock);
++
++ TRACE_DBG("Sysfs work %p", work);
++
++ work->work_res = work->sysfs_work_fn(work);
++
++ spin_lock(&sysfs_work_lock);
++ if (!work->read_only_action)
++ last_sysfs_work_res = work->work_res;
++ active_sysfs_works--;
++ spin_unlock(&sysfs_work_lock);
++
++ complete_all(&work->sysfs_work_done);
++ kref_put(&work->sysfs_work_kref, scst_sysfs_work_release);
++
++ spin_lock(&sysfs_work_lock);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline int test_sysfs_work_list(void)
++{
++ int res = !list_empty(&sysfs_work_list) ||
++ unlikely(kthread_should_stop());
++ return res;
++}
++
++static int sysfs_work_thread_fn(void *arg)
++{
++ bool one_time_only = (bool)arg;
++
++ TRACE_ENTRY();
++
++ if (!one_time_only)
++ PRINT_INFO("User interface thread started, PID %d", current->pid);
++
++ current->flags |= PF_NOFREEZE;
++
++ set_user_nice(current, -10);
++
++ spin_lock(&sysfs_work_lock);
++ while (!kthread_should_stop()) {
++ wait_queue_t wait;
++ init_waitqueue_entry(&wait, current);
++
++ if (one_time_only && !test_sysfs_work_list())
++ break;
++
++ if (!test_sysfs_work_list()) {
++ add_wait_queue_exclusive(&sysfs_work_waitQ, &wait);
++ for (;;) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (test_sysfs_work_list())
++ break;
++ spin_unlock(&sysfs_work_lock);
++ schedule();
++ spin_lock(&sysfs_work_lock);
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&sysfs_work_waitQ, &wait);
++ }
++
++ scst_process_sysfs_works();
++ }
++ spin_unlock(&sysfs_work_lock);
++
++ if (!one_time_only) {
++ /*
++ * If kthread_should_stop() is true, we are guaranteed to be
++ * on the module unload, so both lists must be empty.
++ */
++ BUG_ON(!list_empty(&sysfs_work_list));
++
++ PRINT_INFO("User interface thread PID %d finished", current->pid);
++ }
++
++ TRACE_EXIT();
++ return 0;
++}
++
++/**
++ * scst_sysfs_queue_wait_work() - waits for the work to complete
++ *
++ * Returns status of the completed work or -EAGAIN if the work not
++ * completed before timeout. In the latter case a user should poll
++ * last_sysfs_mgmt_res until it returns the result of the processing.
++ */
++int scst_sysfs_queue_wait_work(struct scst_sysfs_work_item *work)
++{
++ int res = 0, rc;
++ unsigned long timeout = 15*HZ;
++ struct task_struct *t;
++ static atomic_t uid_thread_name = ATOMIC_INIT(0);
++
++ TRACE_ENTRY();
++
++ spin_lock(&sysfs_work_lock);
++
++ TRACE_DBG("Adding sysfs work %p to the list", work);
++ list_add_tail(&work->sysfs_work_list_entry, &sysfs_work_list);
++
++ active_sysfs_works++;
++
++ kref_get(&work->sysfs_work_kref);
++
++ spin_unlock(&sysfs_work_lock);
++
++ wake_up(&sysfs_work_waitQ);
++
++ /*
++ * We can have a dead lock possibility like: the sysfs thread is waiting
++ * for the last put during some object unregistration and at the same
++ * time another queued work is having reference on that object taken and
++ * waiting for attention from the sysfs thread. Generally, all sysfs
++ * functions calling kobject_get() and then queuing sysfs thread job
++ * affected by this. This is especially dangerous in read only cases,
++ * like vdev_sysfs_filename_show().
++ *
++ * So, to eliminate that deadlock we will create an extra sysfs thread
++ * for each queued sysfs work. This thread will quit as soon as it will
++ * see that there is not more queued works to process.
++ */
++
++ t = kthread_run(sysfs_work_thread_fn, (void *)true, "scst_uid%d",
++ atomic_inc_return(&uid_thread_name));
++ if (IS_ERR(t))
++ PRINT_ERROR("kthread_run() for user interface thread %d "
++ "failed: %d", atomic_read(&uid_thread_name),
++ (int)PTR_ERR(t));
++
++ while (1) {
++ rc = wait_for_completion_interruptible_timeout(
++ &work->sysfs_work_done, timeout);
++ if (rc == 0) {
++ if (!mutex_is_locked(&scst_mutex)) {
++ TRACE_DBG("scst_mutex not locked, continue "
++ "waiting (work %p)", work);
++ timeout = 5*HZ;
++ continue;
++ }
++ TRACE_MGMT_DBG("Time out waiting for work %p", work);
++ res = -EAGAIN;
++ goto out_put;
++ } else if (rc < 0) {
++ res = rc;
++ goto out_put;
++ }
++ break;
++ }
++
++ res = work->work_res;
++
++out_put:
++ kref_put(&work->sysfs_work_kref, scst_sysfs_work_release);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL(scst_sysfs_queue_wait_work);
++
++/* No locks */
++static int scst_check_grab_tgtt_ptr(struct scst_tgt_template *tgtt)
++{
++ int res = 0;
++ struct scst_tgt_template *tt;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(tt, &scst_template_list, scst_template_list_entry) {
++ if (tt == tgtt) {
++ tgtt->tgtt_active_sysfs_works_count++;
++ goto out_unlock;
++ }
++ }
++
++ TRACE_DBG("Tgtt %p not found", tgtt);
++ res = -ENOENT;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* No locks */
++static void scst_ungrab_tgtt_ptr(struct scst_tgt_template *tgtt)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++ tgtt->tgtt_active_sysfs_works_count--;
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* scst_mutex supposed to be locked */
++static int scst_check_tgt_acg_ptrs(struct scst_tgt *tgt, struct scst_acg *acg)
++{
++ int res = 0;
++ struct scst_tgt_template *tgtt;
++
++ list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) {
++ struct scst_tgt *t;
++ list_for_each_entry(t, &tgtt->tgt_list, tgt_list_entry) {
++ if (t == tgt) {
++ struct scst_acg *a;
++ if (acg == NULL)
++ goto out;
++ if (acg == tgt->default_acg)
++ goto out;
++ list_for_each_entry(a, &tgt->tgt_acg_list,
++ acg_list_entry) {
++ if (a == acg)
++ goto out;
++ }
++ }
++ }
++ }
++
++ TRACE_DBG("Tgt %p/ACG %p not found", tgt, acg);
++ res = -ENOENT;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_mutex supposed to be locked */
++static int scst_check_devt_ptr(struct scst_dev_type *devt,
++ struct list_head *list)
++{
++ int res = 0;
++ struct scst_dev_type *dt;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(dt, list, dev_type_list_entry) {
++ if (dt == devt)
++ goto out;
++ }
++
++ TRACE_DBG("Devt %p not found", devt);
++ res = -ENOENT;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_mutex supposed to be locked */
++static int scst_check_dev_ptr(struct scst_device *dev)
++{
++ int res = 0;
++ struct scst_device *d;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if (d == dev)
++ goto out;
++ }
++
++ TRACE_DBG("Dev %p not found", dev);
++ res = -ENOENT;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* No locks */
++static int scst_check_grab_devt_ptr(struct scst_dev_type *devt,
++ struct list_head *list)
++{
++ int res = 0;
++ struct scst_dev_type *dt;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++
++ list_for_each_entry(dt, list, dev_type_list_entry) {
++ if (dt == devt) {
++ devt->devt_active_sysfs_works_count++;
++ goto out_unlock;
++ }
++ }
++
++ TRACE_DBG("Devt %p not found", devt);
++ res = -ENOENT;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* No locks */
++static void scst_ungrab_devt_ptr(struct scst_dev_type *devt)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_mutex);
++ devt->devt_active_sysfs_works_count--;
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ ** Regular SCST sysfs ops
++ **/
++static ssize_t scst_show(struct kobject *kobj, struct attribute *attr,
++ char *buf)
++{
++ struct kobj_attribute *kobj_attr;
++ kobj_attr = container_of(attr, struct kobj_attribute, attr);
++
++ return kobj_attr->show(kobj, kobj_attr, buf);
++}
++
++static ssize_t scst_store(struct kobject *kobj, struct attribute *attr,
++ const char *buf, size_t count)
++{
++ struct kobj_attribute *kobj_attr;
++ kobj_attr = container_of(attr, struct kobj_attribute, attr);
++
++ if (kobj_attr->store)
++ return kobj_attr->store(kobj, kobj_attr, buf, count);
++ else
++ return -EIO;
++}
++
++const struct sysfs_ops scst_sysfs_ops = {
++ .show = scst_show,
++ .store = scst_store,
++};
++
++const struct sysfs_ops *scst_sysfs_get_sysfs_ops(void)
++{
++ return &scst_sysfs_ops;
++}
++EXPORT_SYMBOL_GPL(scst_sysfs_get_sysfs_ops);
++
++/**
++ ** Target Template
++ **/
++
++static void scst_tgtt_release(struct kobject *kobj)
++{
++ struct scst_tgt_template *tgtt;
++
++ TRACE_ENTRY();
++
++ tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
++ if (tgtt->tgtt_kobj_release_cmpl)
++ complete_all(tgtt->tgtt_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++static struct kobj_type tgtt_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_tgtt_release,
++};
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++static ssize_t scst_tgtt_trace_level_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt_template *tgtt;
++
++ tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
++
++ return scst_trace_level_show(tgtt->trace_tbl,
++ tgtt->trace_flags ? *tgtt->trace_flags : 0, buf,
++ tgtt->trace_tbl_help);
++}
++
++static ssize_t scst_tgtt_trace_level_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_tgt_template *tgtt;
++
++ TRACE_ENTRY();
++
++ tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
++
++ res = mutex_lock_interruptible(&scst_log_mutex);
++ if (res != 0)
++ goto out;
++
++ res = scst_write_trace(buf, count, tgtt->trace_flags,
++ tgtt->default_trace_flags, tgtt->name, tgtt->trace_tbl);
++
++ mutex_unlock(&scst_log_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute tgtt_trace_attr =
++ __ATTR(trace_level, S_IRUGO | S_IWUSR,
++ scst_tgtt_trace_level_show, scst_tgtt_trace_level_store);
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++static ssize_t scst_tgtt_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"add_target target_name [parameters]\" >mgmt\n"
++ " echo \"del_target target_name\" >mgmt\n"
++ "%s%s"
++ "%s"
++ "\n"
++ "where parameters are one or more "
++ "param_name=value pairs separated by ';'\n\n"
++ "%s%s%s%s%s%s%s%s\n";
++ struct scst_tgt_template *tgtt;
++
++ tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
++
++ return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, help,
++ (tgtt->tgtt_optional_attributes != NULL) ?
++ " echo \"add_attribute <attribute> <value>\" >mgmt\n"
++ " echo \"del_attribute <attribute> <value>\" >mgmt\n" : "",
++ (tgtt->tgt_optional_attributes != NULL) ?
++ " echo \"add_target_attribute target_name <attribute> <value>\" >mgmt\n"
++ " echo \"del_target_attribute target_name <attribute> <value>\" >mgmt\n" : "",
++ (tgtt->mgmt_cmd_help) ? tgtt->mgmt_cmd_help : "",
++ (tgtt->add_target_parameters != NULL) ?
++ "The following parameters available: " : "",
++ (tgtt->add_target_parameters != NULL) ?
++ tgtt->add_target_parameters : "",
++ (tgtt->tgtt_optional_attributes != NULL) ?
++ "The following target driver attributes available: " : "",
++ (tgtt->tgtt_optional_attributes != NULL) ?
++ tgtt->tgtt_optional_attributes : "",
++ (tgtt->tgtt_optional_attributes != NULL) ? "\n" : "",
++ (tgtt->tgt_optional_attributes != NULL) ?
++ "The following target attributes available: " : "",
++ (tgtt->tgt_optional_attributes != NULL) ?
++ tgtt->tgt_optional_attributes : "",
++ (tgtt->tgt_optional_attributes != NULL) ? "\n" : "");
++}
++
++static int scst_process_tgtt_mgmt_store(char *buffer,
++ struct scst_tgt_template *tgtt)
++{
++ int res = 0;
++ char *p, *pp, *target_name;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("buffer %s", buffer);
++
++ /* Check if our pointer is still alive and, if yes, grab it */
++ if (scst_check_grab_tgtt_ptr(tgtt) != 0)
++ goto out;
++
++ pp = buffer;
++ if (pp[strlen(pp) - 1] == '\n')
++ pp[strlen(pp) - 1] = '\0';
++
++ p = scst_get_next_lexem(&pp);
++
++ if (strcasecmp("add_target", p) == 0) {
++ target_name = scst_get_next_lexem(&pp);
++ if (*target_name == '\0') {
++ PRINT_ERROR("%s", "Target name required");
++ res = -EINVAL;
++ goto out_ungrab;
++ }
++ res = tgtt->add_target(target_name, pp);
++ } else if (strcasecmp("del_target", p) == 0) {
++ target_name = scst_get_next_lexem(&pp);
++ if (*target_name == '\0') {
++ PRINT_ERROR("%s", "Target name required");
++ res = -EINVAL;
++ goto out_ungrab;
++ }
++
++ p = scst_get_next_lexem(&pp);
++ if (*p != '\0')
++ goto out_syntax_err;
++
++ res = tgtt->del_target(target_name);
++ } else if (tgtt->mgmt_cmd != NULL) {
++ scst_restore_token_str(p, pp);
++ res = tgtt->mgmt_cmd(buffer);
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out_ungrab;
++ }
++
++out_ungrab:
++ scst_ungrab_tgtt_ptr(tgtt);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_syntax_err:
++ PRINT_ERROR("Syntax error on \"%s\"", p);
++ res = -EINVAL;
++ goto out_ungrab;
++}
++
++static int scst_tgtt_mgmt_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return scst_process_tgtt_mgmt_store(work->buf, work->tgtt);
++}
++
++static ssize_t scst_tgtt_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ char *buffer;
++ struct scst_sysfs_work_item *work;
++ struct scst_tgt_template *tgtt;
++
++ TRACE_ENTRY();
++
++ tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj);
++
++ buffer = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (buffer == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ res = scst_alloc_sysfs_work(scst_tgtt_mgmt_store_work_fn, false, &work);
++ if (res != 0)
++ goto out_free;
++
++ work->buf = buffer;
++ work->tgtt = tgtt;
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(buffer);
++ goto out;
++}
++
++static struct kobj_attribute scst_tgtt_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_tgtt_mgmt_show,
++ scst_tgtt_mgmt_store);
++
++int scst_tgtt_sysfs_create(struct scst_tgt_template *tgtt)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ res = kobject_init_and_add(&tgtt->tgtt_kobj, &tgtt_ktype,
++ scst_targets_kobj, tgtt->name);
++ if (res != 0) {
++ PRINT_ERROR("Can't add tgtt %s to sysfs", tgtt->name);
++ goto out;
++ }
++
++ if (tgtt->add_target != NULL) {
++ res = sysfs_create_file(&tgtt->tgtt_kobj,
++ &scst_tgtt_mgmt.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add mgmt attr for target driver %s",
++ tgtt->name);
++ goto out_del;
++ }
++ }
++
++ if (tgtt->tgtt_attrs) {
++ res = sysfs_create_files(&tgtt->tgtt_kobj, tgtt->tgtt_attrs);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attributes for target "
++ "driver %s", tgtt->name);
++ goto out_del;
++ }
++ }
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ if (tgtt->trace_flags != NULL) {
++ res = sysfs_create_file(&tgtt->tgtt_kobj,
++ &tgtt_trace_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add trace_flag for target "
++ "driver %s", tgtt->name);
++ goto out_del;
++ }
++ }
++#endif
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ scst_tgtt_sysfs_del(tgtt);
++ goto out;
++}
++
++/*
++ * Must not be called under scst_mutex, due to possible deadlock with
++ * sysfs ref counting in sysfs works (it is waiting for the last put, but
++ * the last ref counter holder is waiting for scst_mutex)
++ */
++void scst_tgtt_sysfs_del(struct scst_tgt_template *tgtt)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ tgtt->tgtt_kobj_release_cmpl = &c;
++
++ kobject_del(&tgtt->tgtt_kobj);
++ kobject_put(&tgtt->tgtt_kobj);
++
++ rc = wait_for_completion_timeout(tgtt->tgtt_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for target template %s (%d refs)...", tgtt->name,
++ atomic_read(&tgtt->tgtt_kobj.kref.refcount));
++ wait_for_completion(tgtt->tgtt_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs "
++ "entry for target template %s", tgtt->name);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ ** Target directory implementation
++ **/
++
++static void scst_tgt_release(struct kobject *kobj)
++{
++ struct scst_tgt *tgt;
++
++ TRACE_ENTRY();
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ if (tgt->tgt_kobj_release_cmpl)
++ complete_all(tgt->tgt_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++static struct kobj_type tgt_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_tgt_release,
++};
++
++static int __scst_process_luns_mgmt_store(char *buffer,
++ struct scst_tgt *tgt, struct scst_acg *acg, bool tgt_kobj)
++{
++ int res, read_only = 0, action;
++ char *p, *e = NULL;
++ unsigned int virt_lun;
++ struct scst_acg_dev *acg_dev = NULL, *acg_dev_tmp;
++ struct scst_device *d, *dev = NULL;
++ enum {
++ SCST_LUN_ACTION_ADD = 1,
++ SCST_LUN_ACTION_DEL = 2,
++ SCST_LUN_ACTION_REPLACE = 3,
++ SCST_LUN_ACTION_CLEAR = 4,
++ };
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("buffer %s", buffer);
++
++ p = buffer;
++ if (p[strlen(p) - 1] == '\n')
++ p[strlen(p) - 1] = '\0';
++ if (strncasecmp("add", p, 3) == 0) {
++ p += 3;
++ action = SCST_LUN_ACTION_ADD;
++ } else if (strncasecmp("del", p, 3) == 0) {
++ p += 3;
++ action = SCST_LUN_ACTION_DEL;
++ } else if (!strncasecmp("replace", p, 7)) {
++ p += 7;
++ action = SCST_LUN_ACTION_REPLACE;
++ } else if (!strncasecmp("clear", p, 5)) {
++ p += 5;
++ action = SCST_LUN_ACTION_CLEAR;
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out_resume;
++
++ /* Check if tgt and acg not already freed while we were coming here */
++ if (scst_check_tgt_acg_ptrs(tgt, acg) != 0)
++ goto out_unlock;
++
++ if ((action != SCST_LUN_ACTION_CLEAR) &&
++ (action != SCST_LUN_ACTION_DEL)) {
++ if (!isspace(*p)) {
++ PRINT_ERROR("%s", "Syntax error");
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ while (isspace(*p) && *p != '\0')
++ p++;
++ e = p; /* save p */
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = '\0';
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if (!strcmp(d->virt_name, p)) {
++ dev = d;
++ TRACE_DBG("Device %p (%s) found", dev, p);
++ break;
++ }
++ }
++ if (dev == NULL) {
++ PRINT_ERROR("Device '%s' not found", p);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ }
++
++ switch (action) {
++ case SCST_LUN_ACTION_ADD:
++ case SCST_LUN_ACTION_REPLACE:
++ {
++ bool dev_replaced = false;
++
++ e++;
++ while (isspace(*e) && *e != '\0')
++ e++;
++
++ virt_lun = simple_strtoul(e, &e, 0);
++ if (virt_lun > SCST_MAX_LUN) {
++ PRINT_ERROR("Too big LUN %d (max %d)", virt_lun,
++ SCST_MAX_LUN);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ while (isspace(*e) && *e != '\0')
++ e++;
++
++ while (1) {
++ char *pp;
++ unsigned long val;
++ char *param = scst_get_next_token_str(&e);
++ if (param == NULL)
++ break;
++
++ p = scst_get_next_lexem(&param);
++ if (*p == '\0') {
++ PRINT_ERROR("Syntax error at %s (device %s)",
++ param, dev->virt_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ pp = scst_get_next_lexem(&param);
++ if (*pp == '\0') {
++ PRINT_ERROR("Parameter %s value missed for device %s",
++ p, dev->virt_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ if (scst_get_next_lexem(&param)[0] != '\0') {
++ PRINT_ERROR("Too many parameter's %s values (device %s)",
++ p, dev->virt_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ res = strict_strtoul(pp, 0, &val);
++ if (res != 0) {
++ PRINT_ERROR("strict_strtoul() for %s failed: %d "
++ "(device %s)", pp, res, dev->virt_name);
++ goto out_unlock;
++ }
++
++ if (!strcasecmp("read_only", p)) {
++ read_only = val;
++ TRACE_DBG("READ ONLY %d", read_only);
++ } else {
++ PRINT_ERROR("Unknown parameter %s (device %s)",
++ p, dev->virt_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ }
++
++ acg_dev = NULL;
++ list_for_each_entry(acg_dev_tmp, &acg->acg_dev_list,
++ acg_dev_list_entry) {
++ if (acg_dev_tmp->lun == virt_lun) {
++ acg_dev = acg_dev_tmp;
++ break;
++ }
++ }
++
++ if (acg_dev != NULL) {
++ if (action == SCST_LUN_ACTION_ADD) {
++ PRINT_ERROR("virt lun %d already exists in "
++ "group %s", virt_lun, acg->acg_name);
++ res = -EEXIST;
++ goto out_unlock;
++ } else {
++ /* Replace */
++ res = scst_acg_del_lun(acg, acg_dev->lun,
++ false);
++ if (res != 0)
++ goto out_unlock;
++
++ dev_replaced = true;
++ }
++ }
++
++ res = scst_acg_add_lun(acg,
++ tgt_kobj ? tgt->tgt_luns_kobj : acg->luns_kobj,
++ dev, virt_lun, read_only, !dev_replaced, NULL);
++ if (res != 0)
++ goto out_unlock;
++
++ if (dev_replaced) {
++ struct scst_tgt_dev *tgt_dev;
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if ((tgt_dev->acg_dev->acg == acg) &&
++ (tgt_dev->lun == virt_lun)) {
++ TRACE_MGMT_DBG("INQUIRY DATA HAS CHANGED"
++ " on tgt_dev %p", tgt_dev);
++ scst_gen_aen_or_ua(tgt_dev,
++ SCST_LOAD_SENSE(scst_sense_inquery_data_changed));
++ }
++ }
++ }
++
++ break;
++ }
++ case SCST_LUN_ACTION_DEL:
++ while (isspace(*p) && *p != '\0')
++ p++;
++ virt_lun = simple_strtoul(p, &p, 0);
++
++ res = scst_acg_del_lun(acg, virt_lun, true);
++ if (res != 0)
++ goto out_unlock;
++ break;
++ case SCST_LUN_ACTION_CLEAR:
++ PRINT_INFO("Removed all devices from group %s",
++ acg->acg_name);
++ list_for_each_entry_safe(acg_dev, acg_dev_tmp,
++ &acg->acg_dev_list,
++ acg_dev_list_entry) {
++ res = scst_acg_del_lun(acg, acg_dev->lun,
++ list_is_last(&acg_dev->acg_dev_list_entry,
++ &acg->acg_dev_list));
++ if (res != 0)
++ goto out_unlock;
++ }
++ break;
++ }
++
++ res = 0;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out_resume:
++ scst_resume_activity();
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_luns_mgmt_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return __scst_process_luns_mgmt_store(work->buf, work->tgt, work->acg,
++ work->is_tgt_kobj);
++}
++
++static ssize_t __scst_acg_mgmt_store(struct scst_acg *acg,
++ const char *buf, size_t count, bool is_tgt_kobj,
++ int (*sysfs_work_fn)(struct scst_sysfs_work_item *))
++{
++ int res;
++ char *buffer;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ buffer = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (buffer == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ res = scst_alloc_sysfs_work(sysfs_work_fn, false, &work);
++ if (res != 0)
++ goto out_free;
++
++ work->buf = buffer;
++ work->tgt = acg->tgt;
++ work->acg = acg;
++ work->is_tgt_kobj = is_tgt_kobj;
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(buffer);
++ goto out;
++}
++
++static ssize_t __scst_luns_mgmt_store(struct scst_acg *acg,
++ bool tgt_kobj, const char *buf, size_t count)
++{
++ return __scst_acg_mgmt_store(acg, buf, count, tgt_kobj,
++ scst_luns_mgmt_store_work_fn);
++}
++
++static ssize_t scst_luns_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"add|del H:C:I:L lun [parameters]\" >mgmt\n"
++ " echo \"add VNAME lun [parameters]\" >mgmt\n"
++ " echo \"del lun\" >mgmt\n"
++ " echo \"replace H:C:I:L lun [parameters]\" >mgmt\n"
++ " echo \"replace VNAME lun [parameters]\" >mgmt\n"
++ " echo \"clear\" >mgmt\n"
++ "\n"
++ "where parameters are one or more "
++ "param_name=value pairs separated by ';'\n"
++ "\nThe following parameters available: read_only.\n";
++
++ return sprintf(buf, "%s", help);
++}
++
++static ssize_t scst_luns_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ int res;
++ struct scst_acg *acg;
++ struct scst_tgt *tgt;
++
++ tgt = container_of(kobj->parent, struct scst_tgt, tgt_kobj);
++ acg = tgt->default_acg;
++
++ res = __scst_luns_mgmt_store(acg, true, buf, count);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_luns_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_luns_mgmt_show,
++ scst_luns_mgmt_store);
++
++static ssize_t __scst_acg_addr_method_show(struct scst_acg *acg, char *buf)
++{
++ int res;
++
++ switch (acg->addr_method) {
++ case SCST_LUN_ADDR_METHOD_FLAT:
++ res = sprintf(buf, "FLAT\n");
++ break;
++ case SCST_LUN_ADDR_METHOD_PERIPHERAL:
++ res = sprintf(buf, "PERIPHERAL\n");
++ break;
++ case SCST_LUN_ADDR_METHOD_LUN:
++ res = sprintf(buf, "LUN\n");
++ break;
++ default:
++ res = sprintf(buf, "UNKNOWN\n");
++ break;
++ }
++
++ if (acg->addr_method != acg->tgt->tgtt->preferred_addr_method)
++ res += sprintf(&buf[res], "%s\n", SCST_SYSFS_KEY_MARK);
++
++ return res;
++}
++
++static ssize_t __scst_acg_addr_method_store(struct scst_acg *acg,
++ const char *buf, size_t count)
++{
++ int res = count;
++
++ if (strncasecmp(buf, "FLAT", min_t(int, 4, count)) == 0)
++ acg->addr_method = SCST_LUN_ADDR_METHOD_FLAT;
++ else if (strncasecmp(buf, "PERIPHERAL", min_t(int, 10, count)) == 0)
++ acg->addr_method = SCST_LUN_ADDR_METHOD_PERIPHERAL;
++ else if (strncasecmp(buf, "LUN", min_t(int, 3, count)) == 0)
++ acg->addr_method = SCST_LUN_ADDR_METHOD_LUN;
++ else {
++ PRINT_ERROR("Unknown address method %s", buf);
++ res = -EINVAL;
++ }
++
++ TRACE_DBG("acg %p, addr_method %d", acg, acg->addr_method);
++
++ return res;
++}
++
++static ssize_t scst_tgt_addr_method_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_acg *acg;
++ struct scst_tgt *tgt;
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ acg = tgt->default_acg;
++
++ return __scst_acg_addr_method_show(acg, buf);
++}
++
++static ssize_t scst_tgt_addr_method_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_acg *acg;
++ struct scst_tgt *tgt;
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ acg = tgt->default_acg;
++
++ res = __scst_acg_addr_method_store(acg, buf, count);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tgt_addr_method =
++ __ATTR(addr_method, S_IRUGO | S_IWUSR, scst_tgt_addr_method_show,
++ scst_tgt_addr_method_store);
++
++static ssize_t __scst_acg_io_grouping_type_show(struct scst_acg *acg, char *buf)
++{
++ int res;
++
++ switch (acg->acg_io_grouping_type) {
++ case SCST_IO_GROUPING_AUTO:
++ res = sprintf(buf, "%s\n", SCST_IO_GROUPING_AUTO_STR);
++ break;
++ case SCST_IO_GROUPING_THIS_GROUP_ONLY:
++ res = sprintf(buf, "%s\n%s\n",
++ SCST_IO_GROUPING_THIS_GROUP_ONLY_STR,
++ SCST_SYSFS_KEY_MARK);
++ break;
++ case SCST_IO_GROUPING_NEVER:
++ res = sprintf(buf, "%s\n%s\n", SCST_IO_GROUPING_NEVER_STR,
++ SCST_SYSFS_KEY_MARK);
++ break;
++ default:
++ res = sprintf(buf, "%d\n%s\n", acg->acg_io_grouping_type,
++ SCST_SYSFS_KEY_MARK);
++ break;
++ }
++
++ return res;
++}
++
++static int __scst_acg_process_io_grouping_type_store(struct scst_tgt *tgt,
++ struct scst_acg *acg, int io_grouping_type)
++{
++ int res = 0;
++ struct scst_acg_dev *acg_dev;
++
++ TRACE_DBG("tgt %p, acg %p, io_grouping_type %d", tgt, acg,
++ io_grouping_type);
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out_resume;
++
++ /* Check if tgt and acg not already freed while we were coming here */
++ if (scst_check_tgt_acg_ptrs(tgt, acg) != 0)
++ goto out_unlock;
++
++ acg->acg_io_grouping_type = io_grouping_type;
++
++ list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) {
++ int rc;
++
++ scst_stop_dev_threads(acg_dev->dev);
++
++ rc = scst_create_dev_threads(acg_dev->dev);
++ if (rc != 0)
++ res = rc;
++ }
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out_resume:
++ scst_resume_activity();
++
++out:
++ return res;
++}
++
++static int __scst_acg_io_grouping_type_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return __scst_acg_process_io_grouping_type_store(work->tgt, work->acg,
++ work->io_grouping_type);
++}
++
++static ssize_t __scst_acg_io_grouping_type_store(struct scst_acg *acg,
++ const char *buf, size_t count)
++{
++ int res = 0;
++ int prev = acg->acg_io_grouping_type;
++ long io_grouping_type;
++ struct scst_sysfs_work_item *work;
++
++ if (strncasecmp(buf, SCST_IO_GROUPING_AUTO_STR,
++ min_t(int, strlen(SCST_IO_GROUPING_AUTO_STR), count)) == 0)
++ io_grouping_type = SCST_IO_GROUPING_AUTO;
++ else if (strncasecmp(buf, SCST_IO_GROUPING_THIS_GROUP_ONLY_STR,
++ min_t(int, strlen(SCST_IO_GROUPING_THIS_GROUP_ONLY_STR), count)) == 0)
++ io_grouping_type = SCST_IO_GROUPING_THIS_GROUP_ONLY;
++ else if (strncasecmp(buf, SCST_IO_GROUPING_NEVER_STR,
++ min_t(int, strlen(SCST_IO_GROUPING_NEVER_STR), count)) == 0)
++ io_grouping_type = SCST_IO_GROUPING_NEVER;
++ else {
++ res = strict_strtol(buf, 0, &io_grouping_type);
++ if ((res != 0) || (io_grouping_type <= 0)) {
++ PRINT_ERROR("Unknown or not allowed I/O grouping type "
++ "%s", buf);
++ res = -EINVAL;
++ goto out;
++ }
++ }
++
++ if (prev == io_grouping_type)
++ goto out;
++
++ res = scst_alloc_sysfs_work(__scst_acg_io_grouping_type_store_work_fn,
++ false, &work);
++ if (res != 0)
++ goto out;
++
++ work->tgt = acg->tgt;
++ work->acg = acg;
++ work->io_grouping_type = io_grouping_type;
++
++ res = scst_sysfs_queue_wait_work(work);
++
++out:
++ return res;
++}
++
++static ssize_t scst_tgt_io_grouping_type_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_acg *acg;
++ struct scst_tgt *tgt;
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ acg = tgt->default_acg;
++
++ return __scst_acg_io_grouping_type_show(acg, buf);
++}
++
++static ssize_t scst_tgt_io_grouping_type_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_acg *acg;
++ struct scst_tgt *tgt;
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ acg = tgt->default_acg;
++
++ res = __scst_acg_io_grouping_type_store(acg, buf, count);
++ if (res != 0)
++ goto out;
++
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tgt_io_grouping_type =
++ __ATTR(io_grouping_type, S_IRUGO | S_IWUSR,
++ scst_tgt_io_grouping_type_show,
++ scst_tgt_io_grouping_type_store);
++
++static ssize_t __scst_acg_cpu_mask_show(struct scst_acg *acg, char *buf)
++{
++ int res;
++
++ res = cpumask_scnprintf(buf, SCST_SYSFS_BLOCK_SIZE,
++ &acg->acg_cpu_mask);
++ if (!cpus_equal(acg->acg_cpu_mask, default_cpu_mask))
++ res += sprintf(&buf[res], "\n%s\n", SCST_SYSFS_KEY_MARK);
++
++ return res;
++}
++
++static int __scst_acg_process_cpu_mask_store(struct scst_tgt *tgt,
++ struct scst_acg *acg, cpumask_t *cpu_mask)
++{
++ int res = 0;
++ struct scst_session *sess;
++
++ TRACE_DBG("tgt %p, acg %p", tgt, acg);
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out;
++
++ /* Check if tgt and acg not already freed while we were coming here */
++ if (scst_check_tgt_acg_ptrs(tgt, acg) != 0)
++ goto out_unlock;
++
++ cpumask_copy(&acg->acg_cpu_mask, cpu_mask);
++
++ list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) {
++ int i;
++ for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
++ struct scst_tgt_dev *tgt_dev;
++ struct list_head *head = &sess->sess_tgt_dev_list[i];
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ struct scst_cmd_thread_t *thr;
++ if (tgt_dev->active_cmd_threads != &tgt_dev->tgt_dev_cmd_threads)
++ continue;
++ list_for_each_entry(thr,
++ &tgt_dev->active_cmd_threads->threads_list,
++ thread_list_entry) {
++ int rc;
++ rc = set_cpus_allowed_ptr(thr->cmd_thread, cpu_mask);
++ if (rc != 0)
++ PRINT_ERROR("Setting CPU "
++ "affinity failed: %d", rc);
++ }
++ }
++ }
++ if (tgt->tgtt->report_aen != NULL) {
++ struct scst_aen *aen;
++ int rc;
++
++ aen = scst_alloc_aen(sess, 0);
++ if (aen == NULL) {
++ PRINT_ERROR("Unable to notify target driver %s "
++ "about cpu_mask change", tgt->tgt_name);
++ continue;
++ }
++
++ aen->event_fn = SCST_AEN_CPU_MASK_CHANGED;
++
++ TRACE_DBG("Calling target's %s report_aen(%p)",
++ tgt->tgtt->name, aen);
++ rc = tgt->tgtt->report_aen(aen);
++ TRACE_DBG("Target's %s report_aen(%p) returned %d",
++ tgt->tgtt->name, aen, rc);
++ if (rc != SCST_AEN_RES_SUCCESS)
++ scst_free_aen(aen);
++ }
++ }
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out:
++ return res;
++}
++
++static int __scst_acg_cpu_mask_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return __scst_acg_process_cpu_mask_store(work->tgt, work->acg,
++ &work->cpu_mask);
++}
++
++static ssize_t __scst_acg_cpu_mask_store(struct scst_acg *acg,
++ const char *buf, size_t count)
++{
++ int res;
++ struct scst_sysfs_work_item *work;
++
++ /* cpumask might be too big for stack */
++
++ res = scst_alloc_sysfs_work(__scst_acg_cpu_mask_store_work_fn,
++ false, &work);
++ if (res != 0)
++ goto out;
++
++ /*
++ * We can't use cpumask_parse_user() here, because it expects
++ * buffer in the user space.
++ */
++ res = __bitmap_parse(buf, count, 0, cpumask_bits(&work->cpu_mask),
++ nr_cpumask_bits);
++ if (res != 0) {
++ PRINT_ERROR("__bitmap_parse() failed: %d", res);
++ goto out_release;
++ }
++
++ if (cpus_equal(acg->acg_cpu_mask, work->cpu_mask))
++ goto out;
++
++ work->tgt = acg->tgt;
++ work->acg = acg;
++
++ res = scst_sysfs_queue_wait_work(work);
++
++out:
++ return res;
++
++out_release:
++ scst_sysfs_work_release(&work->sysfs_work_kref);
++ goto out;
++}
++
++static ssize_t scst_tgt_cpu_mask_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_acg *acg;
++ struct scst_tgt *tgt;
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ acg = tgt->default_acg;
++
++ return __scst_acg_cpu_mask_show(acg, buf);
++}
++
++static ssize_t scst_tgt_cpu_mask_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_acg *acg;
++ struct scst_tgt *tgt;
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ acg = tgt->default_acg;
++
++ res = __scst_acg_cpu_mask_store(acg, buf, count);
++ if (res != 0)
++ goto out;
++
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tgt_cpu_mask =
++ __ATTR(cpu_mask, S_IRUGO | S_IWUSR,
++ scst_tgt_cpu_mask_show,
++ scst_tgt_cpu_mask_store);
++
++static ssize_t scst_ini_group_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"create GROUP_NAME\" >mgmt\n"
++ " echo \"del GROUP_NAME\" >mgmt\n";
++
++ return sprintf(buf, "%s", help);
++}
++
++static int scst_process_ini_group_mgmt_store(char *buffer,
++ struct scst_tgt *tgt)
++{
++ int res, action;
++ char *p, *e = NULL;
++ struct scst_acg *a, *acg = NULL;
++ enum {
++ SCST_INI_GROUP_ACTION_CREATE = 1,
++ SCST_INI_GROUP_ACTION_DEL = 2,
++ };
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("tgt %p, buffer %s", tgt, buffer);
++
++ p = buffer;
++ if (p[strlen(p) - 1] == '\n')
++ p[strlen(p) - 1] = '\0';
++ if (strncasecmp("create ", p, 7) == 0) {
++ p += 7;
++ action = SCST_INI_GROUP_ACTION_CREATE;
++ } else if (strncasecmp("del ", p, 4) == 0) {
++ p += 4;
++ action = SCST_INI_GROUP_ACTION_DEL;
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out_resume;
++
++ /* Check if our pointer is still alive */
++ if (scst_check_tgt_acg_ptrs(tgt, NULL) != 0)
++ goto out_unlock;
++
++ while (isspace(*p) && *p != '\0')
++ p++;
++ e = p;
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = '\0';
++
++ if (p[0] == '\0') {
++ PRINT_ERROR("%s", "Group name required");
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ list_for_each_entry(a, &tgt->tgt_acg_list, acg_list_entry) {
++ if (strcmp(a->acg_name, p) == 0) {
++ TRACE_DBG("group (acg) %p %s found",
++ a, a->acg_name);
++ acg = a;
++ break;
++ }
++ }
++
++ switch (action) {
++ case SCST_INI_GROUP_ACTION_CREATE:
++ TRACE_DBG("Creating group '%s'", p);
++ if (acg != NULL) {
++ PRINT_ERROR("acg name %s exist", p);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ acg = scst_alloc_add_acg(tgt, p, true);
++ if (acg == NULL)
++ goto out_unlock;
++ break;
++ case SCST_INI_GROUP_ACTION_DEL:
++ TRACE_DBG("Deleting group '%s'", p);
++ if (acg == NULL) {
++ PRINT_ERROR("Group %s not found", p);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ if (!scst_acg_sess_is_empty(acg)) {
++ PRINT_ERROR("Group %s is not empty", acg->acg_name);
++ res = -EBUSY;
++ goto out_unlock;
++ }
++ scst_del_free_acg(acg);
++ break;
++ }
++
++ res = 0;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out_resume:
++ scst_resume_activity();
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_ini_group_mgmt_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return scst_process_ini_group_mgmt_store(work->buf, work->tgt);
++}
++
++static ssize_t scst_ini_group_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ char *buffer;
++ struct scst_tgt *tgt;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ tgt = container_of(kobj->parent, struct scst_tgt, tgt_kobj);
++
++ buffer = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (buffer == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ res = scst_alloc_sysfs_work(scst_ini_group_mgmt_store_work_fn, false,
++ &work);
++ if (res != 0)
++ goto out_free;
++
++ work->buf = buffer;
++ work->tgt = tgt;
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(buffer);
++ goto out;
++}
++
++static struct kobj_attribute scst_ini_group_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_ini_group_mgmt_show,
++ scst_ini_group_mgmt_store);
++
++static ssize_t scst_tgt_enable_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt *tgt;
++ int res;
++ bool enabled;
++
++ TRACE_ENTRY();
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++
++ enabled = tgt->tgtt->is_target_enabled(tgt);
++
++ res = sprintf(buf, "%d\n", enabled ? 1 : 0);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_process_tgt_enable_store(struct scst_tgt *tgt, bool enable)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ /* Tgt protected by kobject reference */
++
++ TRACE_DBG("tgt %s, enable %d", tgt->tgt_name, enable);
++
++ if (enable) {
++ if (tgt->rel_tgt_id == 0) {
++ res = gen_relative_target_port_id(&tgt->rel_tgt_id);
++ if (res != 0)
++ goto out_put;
++ PRINT_INFO("Using autogenerated rel ID %d for target "
++ "%s", tgt->rel_tgt_id, tgt->tgt_name);
++ } else {
++ if (!scst_is_relative_target_port_id_unique(
++ tgt->rel_tgt_id, tgt)) {
++ PRINT_ERROR("Relative port id %d is not unique",
++ tgt->rel_tgt_id);
++ res = -EBADSLT;
++ goto out_put;
++ }
++ }
++ }
++
++ res = tgt->tgtt->enable_target(tgt, enable);
++
++out_put:
++ kobject_put(&tgt->tgt_kobj);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_tgt_enable_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return scst_process_tgt_enable_store(work->tgt, work->enable);
++}
++
++static ssize_t scst_tgt_enable_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_tgt *tgt;
++ bool enable;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ if (buf == NULL) {
++ PRINT_ERROR("%s: NULL buffer?", __func__);
++ res = -EINVAL;
++ goto out;
++ }
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++
++ switch (buf[0]) {
++ case '0':
++ enable = false;
++ break;
++ case '1':
++ enable = true;
++ break;
++ default:
++ PRINT_ERROR("%s: Requested action not understood: %s",
++ __func__, buf);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_alloc_sysfs_work(scst_tgt_enable_store_work_fn, false,
++ &work);
++ if (res != 0)
++ goto out;
++
++ work->tgt = tgt;
++ work->enable = enable;
++
++ kobject_get(&tgt->tgt_kobj);
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute tgt_enable_attr =
++ __ATTR(enabled, S_IRUGO | S_IWUSR,
++ scst_tgt_enable_show, scst_tgt_enable_store);
++
++static ssize_t scst_rel_tgt_id_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt *tgt;
++ int res;
++
++ TRACE_ENTRY();
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++
++ res = sprintf(buf, "%d\n%s", tgt->rel_tgt_id,
++ (tgt->rel_tgt_id != 0) ? SCST_SYSFS_KEY_MARK "\n" : "");
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_process_rel_tgt_id_store(struct scst_sysfs_work_item *work)
++{
++ int res = 0;
++ struct scst_tgt *tgt = work->tgt_r;
++ unsigned long rel_tgt_id = work->rel_tgt_id;
++ bool enabled;
++
++ TRACE_ENTRY();
++
++ /* tgt protected by kobject_get() */
++
++ TRACE_DBG("Trying to set relative target port id %d",
++ (uint16_t)rel_tgt_id);
++
++ if (tgt->tgtt->is_target_enabled != NULL)
++ enabled = tgt->tgtt->is_target_enabled(tgt);
++ else
++ enabled = true;
++
++ if (enabled && rel_tgt_id != tgt->rel_tgt_id) {
++ if (!scst_is_relative_target_port_id_unique(rel_tgt_id, tgt)) {
++ PRINT_ERROR("Relative port id %d is not unique",
++ (uint16_t)rel_tgt_id);
++ res = -EBADSLT;
++ goto out_put;
++ }
++ }
++
++ if (rel_tgt_id < SCST_MIN_REL_TGT_ID ||
++ rel_tgt_id > SCST_MAX_REL_TGT_ID) {
++ if ((rel_tgt_id == 0) && !enabled)
++ goto set;
++
++ PRINT_ERROR("Invalid relative port id %d",
++ (uint16_t)rel_tgt_id);
++ res = -EINVAL;
++ goto out_put;
++ }
++
++set:
++ tgt->rel_tgt_id = (uint16_t)rel_tgt_id;
++
++out_put:
++ kobject_put(&tgt->tgt_kobj);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_rel_tgt_id_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res = 0;
++ struct scst_tgt *tgt;
++ unsigned long rel_tgt_id;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ if (buf == NULL)
++ goto out;
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++
++ res = strict_strtoul(buf, 0, &rel_tgt_id);
++ if (res != 0) {
++ PRINT_ERROR("%s", "Wrong rel_tgt_id");
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_alloc_sysfs_work(scst_process_rel_tgt_id_store, false,
++ &work);
++ if (res != 0)
++ goto out;
++
++ work->tgt_r = tgt;
++ work->rel_tgt_id = rel_tgt_id;
++
++ kobject_get(&tgt->tgt_kobj);
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_rel_tgt_id =
++ __ATTR(rel_tgt_id, S_IRUGO | S_IWUSR, scst_rel_tgt_id_show,
++ scst_rel_tgt_id_store);
++
++static ssize_t scst_tgt_comment_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt *tgt;
++ int res;
++
++ TRACE_ENTRY();
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++
++ if (tgt->tgt_comment != NULL)
++ res = sprintf(buf, "%s\n%s", tgt->tgt_comment,
++ SCST_SYSFS_KEY_MARK "\n");
++ else
++ res = 0;
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_tgt_comment_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_tgt *tgt;
++ char *p;
++ int len;
++
++ TRACE_ENTRY();
++
++ if ((buf == NULL) || (count == 0)) {
++ res = 0;
++ goto out;
++ }
++
++ tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++
++ len = strnlen(buf, count);
++ if (buf[count-1] == '\n')
++ len--;
++
++ if (len == 0) {
++ kfree(tgt->tgt_comment);
++ tgt->tgt_comment = NULL;
++ goto out_done;
++ }
++
++ p = kmalloc(len+1, GFP_KERNEL);
++ if (p == NULL) {
++ PRINT_ERROR("Unable to alloc tgt_comment string (len %d)",
++ len+1);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ memcpy(p, buf, len);
++ p[len] = '\0';
++
++ kfree(tgt->tgt_comment);
++
++ tgt->tgt_comment = p;
++
++out_done:
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tgt_comment =
++ __ATTR(comment, S_IRUGO | S_IWUSR, scst_tgt_comment_show,
++ scst_tgt_comment_store);
++
++/*
++ * Supposed to be called under scst_mutex. In case of error will drop,
++ * then reacquire it.
++ */
++int scst_tgt_sysfs_create(struct scst_tgt *tgt)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = kobject_init_and_add(&tgt->tgt_kobj, &tgt_ktype,
++ &tgt->tgtt->tgtt_kobj, tgt->tgt_name);
++ if (res != 0) {
++ PRINT_ERROR("Can't add tgt %s to sysfs", tgt->tgt_name);
++ goto out;
++ }
++
++ if ((tgt->tgtt->enable_target != NULL) &&
++ (tgt->tgtt->is_target_enabled != NULL)) {
++ res = sysfs_create_file(&tgt->tgt_kobj,
++ &tgt_enable_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attr %s to sysfs",
++ tgt_enable_attr.attr.name);
++ goto out_err;
++ }
++ }
++
++ tgt->tgt_sess_kobj = kobject_create_and_add("sessions", &tgt->tgt_kobj);
++ if (tgt->tgt_sess_kobj == NULL) {
++ PRINT_ERROR("Can't create sess kobj for tgt %s", tgt->tgt_name);
++ goto out_nomem;
++ }
++
++ tgt->tgt_luns_kobj = kobject_create_and_add("luns", &tgt->tgt_kobj);
++ if (tgt->tgt_luns_kobj == NULL) {
++ PRINT_ERROR("Can't create luns kobj for tgt %s", tgt->tgt_name);
++ goto out_nomem;
++ }
++
++ res = sysfs_create_file(tgt->tgt_luns_kobj, &scst_luns_mgmt.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attribute %s for tgt %s",
++ scst_luns_mgmt.attr.name, tgt->tgt_name);
++ goto out_err;
++ }
++
++ tgt->tgt_ini_grp_kobj = kobject_create_and_add("ini_groups",
++ &tgt->tgt_kobj);
++ if (tgt->tgt_ini_grp_kobj == NULL) {
++ PRINT_ERROR("Can't create ini_grp kobj for tgt %s",
++ tgt->tgt_name);
++ goto out_nomem;
++ }
++
++ res = sysfs_create_file(tgt->tgt_ini_grp_kobj,
++ &scst_ini_group_mgmt.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attribute %s for tgt %s",
++ scst_ini_group_mgmt.attr.name, tgt->tgt_name);
++ goto out_err;
++ }
++
++ res = sysfs_create_file(&tgt->tgt_kobj,
++ &scst_rel_tgt_id.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attribute %s for tgt %s",
++ scst_rel_tgt_id.attr.name, tgt->tgt_name);
++ goto out_err;
++ }
++
++ res = sysfs_create_file(&tgt->tgt_kobj,
++ &scst_tgt_comment.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attribute %s for tgt %s",
++ scst_tgt_comment.attr.name, tgt->tgt_name);
++ goto out_err;
++ }
++
++ res = sysfs_create_file(&tgt->tgt_kobj,
++ &scst_tgt_addr_method.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attribute %s for tgt %s",
++ scst_tgt_addr_method.attr.name, tgt->tgt_name);
++ goto out_err;
++ }
++
++ res = sysfs_create_file(&tgt->tgt_kobj,
++ &scst_tgt_io_grouping_type.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attribute %s for tgt %s",
++ scst_tgt_io_grouping_type.attr.name, tgt->tgt_name);
++ goto out_err;
++ }
++
++ res = sysfs_create_file(&tgt->tgt_kobj, &scst_tgt_cpu_mask.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attribute %s for tgt %s",
++ scst_tgt_cpu_mask.attr.name, tgt->tgt_name);
++ goto out_err;
++ }
++
++ if (tgt->tgtt->tgt_attrs) {
++ res = sysfs_create_files(&tgt->tgt_kobj, tgt->tgtt->tgt_attrs);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attributes for tgt %s",
++ tgt->tgt_name);
++ goto out_err;
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_nomem:
++ res = -ENOMEM;
++
++out_err:
++ mutex_unlock(&scst_mutex);
++ scst_tgt_sysfs_del(tgt);
++ mutex_lock(&scst_mutex);
++ goto out;
++}
++
++/*
++ * Must not be called under scst_mutex, due to possible deadlock with
++ * sysfs ref counting in sysfs works (it is waiting for the last put, but
++ * the last ref counter holder is waiting for scst_mutex)
++ */
++void scst_tgt_sysfs_del(struct scst_tgt *tgt)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ tgt->tgt_kobj_release_cmpl = &c;
++
++ kobject_del(tgt->tgt_sess_kobj);
++ kobject_del(tgt->tgt_luns_kobj);
++ kobject_del(tgt->tgt_ini_grp_kobj);
++ kobject_del(&tgt->tgt_kobj);
++
++ kobject_put(tgt->tgt_sess_kobj);
++ kobject_put(tgt->tgt_luns_kobj);
++ kobject_put(tgt->tgt_ini_grp_kobj);
++ kobject_put(&tgt->tgt_kobj);
++
++ rc = wait_for_completion_timeout(tgt->tgt_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for target %s (%d refs)...", tgt->tgt_name,
++ atomic_read(&tgt->tgt_kobj.kref.refcount));
++ wait_for_completion(tgt->tgt_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs "
++ "entry for target %s", tgt->tgt_name);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ ** Devices directory implementation
++ **/
++
++static ssize_t scst_dev_sysfs_type_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++
++ struct scst_device *dev;
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ pos = sprintf(buf, "%d - %s\n", dev->type,
++ (unsigned)dev->type >= ARRAY_SIZE(scst_dev_handler_types) ?
++ "unknown" : scst_dev_handler_types[dev->type]);
++
++ return pos;
++}
++
++static struct kobj_attribute dev_type_attr =
++ __ATTR(type, S_IRUGO, scst_dev_sysfs_type_show, NULL);
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++static ssize_t scst_dev_sysfs_dump_prs(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ scst_pr_dump_prs(dev, true);
++
++ TRACE_EXIT_RES(count);
++ return count;
++}
++
++static struct kobj_attribute dev_dump_prs_attr =
++ __ATTR(dump_prs, S_IWUSR, NULL, scst_dev_sysfs_dump_prs);
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++static int scst_process_dev_sysfs_threads_data_store(
++ struct scst_device *dev, int threads_num,
++ enum scst_dev_type_threads_pool_type threads_pool_type)
++{
++ int res = 0;
++ int oldtn = dev->threads_num;
++ enum scst_dev_type_threads_pool_type oldtt = dev->threads_pool_type;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("dev %p, threads_num %d, threads_pool_type %d", dev,
++ threads_num, threads_pool_type);
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out_resume;
++
++ /* Check if our pointer is still alive */
++ if (scst_check_dev_ptr(dev) != 0)
++ goto out_unlock;
++
++ scst_stop_dev_threads(dev);
++
++ dev->threads_num = threads_num;
++ dev->threads_pool_type = threads_pool_type;
++
++ res = scst_create_dev_threads(dev);
++ if (res != 0)
++ goto out_unlock;
++
++ if (oldtn != dev->threads_num)
++ PRINT_INFO("Changed cmd threads num to %d", dev->threads_num);
++ else if (oldtt != dev->threads_pool_type)
++ PRINT_INFO("Changed cmd threads pool type to %d",
++ dev->threads_pool_type);
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out_resume:
++ scst_resume_activity();
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_dev_sysfs_threads_data_store_work_fn(
++ struct scst_sysfs_work_item *work)
++{
++ return scst_process_dev_sysfs_threads_data_store(work->dev,
++ work->new_threads_num, work->new_threads_pool_type);
++}
++
++static ssize_t scst_dev_sysfs_check_threads_data(
++ struct scst_device *dev, int threads_num,
++ enum scst_dev_type_threads_pool_type threads_pool_type, bool *stop)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ *stop = false;
++
++ if (dev->threads_num < 0) {
++ PRINT_ERROR("Threads pool disabled for device %s",
++ dev->virt_name);
++ res = -EPERM;
++ goto out;
++ }
++
++ if ((threads_num == dev->threads_num) &&
++ (threads_pool_type == dev->threads_pool_type)) {
++ *stop = true;
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_dev_sysfs_threads_num_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ pos = sprintf(buf, "%d\n%s", dev->threads_num,
++ (dev->threads_num != dev->handler->threads_num) ?
++ SCST_SYSFS_KEY_MARK "\n" : "");
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t scst_dev_sysfs_threads_num_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_device *dev;
++ long newtn;
++ bool stop;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ res = strict_strtol(buf, 0, &newtn);
++ if (res != 0) {
++ PRINT_ERROR("strict_strtol() for %s failed: %d ", buf, res);
++ goto out;
++ }
++ if (newtn < 0) {
++ PRINT_ERROR("Illegal threads num value %ld", newtn);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_dev_sysfs_check_threads_data(dev, newtn,
++ dev->threads_pool_type, &stop);
++ if ((res != 0) || stop)
++ goto out;
++
++ res = scst_alloc_sysfs_work(scst_dev_sysfs_threads_data_store_work_fn,
++ false, &work);
++ if (res != 0)
++ goto out;
++
++ work->dev = dev;
++ work->new_threads_num = newtn;
++ work->new_threads_pool_type = dev->threads_pool_type;
++
++ res = scst_sysfs_queue_wait_work(work);
++
++out:
++ if (res == 0)
++ res = count;
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute dev_threads_num_attr =
++ __ATTR(threads_num, S_IRUGO | S_IWUSR,
++ scst_dev_sysfs_threads_num_show,
++ scst_dev_sysfs_threads_num_store);
++
++static ssize_t scst_dev_sysfs_threads_pool_type_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ if (dev->threads_num == 0) {
++ pos = sprintf(buf, "Async\n");
++ goto out;
++ } else if (dev->threads_num < 0) {
++ pos = sprintf(buf, "Not valid\n");
++ goto out;
++ }
++
++ switch (dev->threads_pool_type) {
++ case SCST_THREADS_POOL_PER_INITIATOR:
++ pos = sprintf(buf, "%s\n%s", SCST_THREADS_POOL_PER_INITIATOR_STR,
++ (dev->threads_pool_type != dev->handler->threads_pool_type) ?
++ SCST_SYSFS_KEY_MARK "\n" : "");
++ break;
++ case SCST_THREADS_POOL_SHARED:
++ pos = sprintf(buf, "%s\n%s", SCST_THREADS_POOL_SHARED_STR,
++ (dev->threads_pool_type != dev->handler->threads_pool_type) ?
++ SCST_SYSFS_KEY_MARK "\n" : "");
++ break;
++ default:
++ pos = sprintf(buf, "Unknown\n");
++ break;
++ }
++
++out:
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t scst_dev_sysfs_threads_pool_type_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_device *dev;
++ enum scst_dev_type_threads_pool_type newtpt;
++ struct scst_sysfs_work_item *work;
++ bool stop;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ newtpt = scst_parse_threads_pool_type(buf, count);
++ if (newtpt == SCST_THREADS_POOL_TYPE_INVALID) {
++ PRINT_ERROR("Illegal threads pool type %s", buf);
++ res = -EINVAL;
++ goto out;
++ }
++
++ TRACE_DBG("buf %s, count %zd, newtpt %d", buf, count, newtpt);
++
++ res = scst_dev_sysfs_check_threads_data(dev, dev->threads_num,
++ newtpt, &stop);
++ if ((res != 0) || stop)
++ goto out;
++
++ res = scst_alloc_sysfs_work(scst_dev_sysfs_threads_data_store_work_fn,
++ false, &work);
++ if (res != 0)
++ goto out;
++
++ work->dev = dev;
++ work->new_threads_num = dev->threads_num;
++ work->new_threads_pool_type = newtpt;
++
++ res = scst_sysfs_queue_wait_work(work);
++
++out:
++ if (res == 0)
++ res = count;
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute dev_threads_pool_type_attr =
++ __ATTR(threads_pool_type, S_IRUGO | S_IWUSR,
++ scst_dev_sysfs_threads_pool_type_show,
++ scst_dev_sysfs_threads_pool_type_store);
++
++static struct attribute *scst_dev_attrs[] = {
++ &dev_type_attr.attr,
++ NULL,
++};
++
++static void scst_sysfs_dev_release(struct kobject *kobj)
++{
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ if (dev->dev_kobj_release_cmpl)
++ complete_all(dev->dev_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++int scst_devt_dev_sysfs_create(struct scst_device *dev)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (dev->handler == &scst_null_devtype)
++ goto out;
++
++ res = sysfs_create_link(&dev->dev_kobj,
++ &dev->handler->devt_kobj, "handler");
++ if (res != 0) {
++ PRINT_ERROR("Can't create handler link for dev %s",
++ dev->virt_name);
++ goto out;
++ }
++
++ res = sysfs_create_link(&dev->handler->devt_kobj,
++ &dev->dev_kobj, dev->virt_name);
++ if (res != 0) {
++ PRINT_ERROR("Can't create handler link for dev %s",
++ dev->virt_name);
++ goto out_err;
++ }
++
++ if (dev->handler->threads_num >= 0) {
++ res = sysfs_create_file(&dev->dev_kobj,
++ &dev_threads_num_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add dev attr %s for dev %s",
++ dev_threads_num_attr.attr.name,
++ dev->virt_name);
++ goto out_err;
++ }
++ res = sysfs_create_file(&dev->dev_kobj,
++ &dev_threads_pool_type_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add dev attr %s for dev %s",
++ dev_threads_pool_type_attr.attr.name,
++ dev->virt_name);
++ goto out_err;
++ }
++ }
++
++ if (dev->handler->dev_attrs) {
++ res = sysfs_create_files(&dev->dev_kobj,
++ dev->handler->dev_attrs);
++ if (res != 0) {
++ PRINT_ERROR("Can't add dev attributes for dev %s",
++ dev->virt_name);
++ goto out_err;
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_err:
++ scst_devt_dev_sysfs_del(dev);
++ goto out;
++}
++
++void scst_devt_dev_sysfs_del(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ if (dev->handler == &scst_null_devtype)
++ goto out;
++
++ if (dev->handler->dev_attrs)
++ sysfs_remove_files(&dev->dev_kobj, dev->handler->dev_attrs);
++
++ sysfs_remove_link(&dev->dev_kobj, "handler");
++ sysfs_remove_link(&dev->handler->devt_kobj, dev->virt_name);
++
++ if (dev->handler->threads_num >= 0) {
++ sysfs_remove_file(&dev->dev_kobj,
++ &dev_threads_num_attr.attr);
++ sysfs_remove_file(&dev->dev_kobj,
++ &dev_threads_pool_type_attr.attr);
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static struct kobj_type scst_dev_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_sysfs_dev_release,
++ .default_attrs = scst_dev_attrs,
++};
++
++/*
++ * Must not be called under scst_mutex, because it can call
++ * scst_dev_sysfs_del()
++ */
++int scst_dev_sysfs_create(struct scst_device *dev)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ res = kobject_init_and_add(&dev->dev_kobj, &scst_dev_ktype,
++ scst_devices_kobj, dev->virt_name);
++ if (res != 0) {
++ PRINT_ERROR("Can't add device %s to sysfs", dev->virt_name);
++ goto out;
++ }
++
++ dev->dev_exp_kobj = kobject_create_and_add("exported",
++ &dev->dev_kobj);
++ if (dev->dev_exp_kobj == NULL) {
++ PRINT_ERROR("Can't create exported link for device %s",
++ dev->virt_name);
++ res = -ENOMEM;
++ goto out_del;
++ }
++
++ if (dev->scsi_dev != NULL) {
++ res = sysfs_create_link(&dev->dev_kobj,
++ &dev->scsi_dev->sdev_dev.kobj, "scsi_device");
++ if (res != 0) {
++ PRINT_ERROR("Can't create scsi_device link for dev %s",
++ dev->virt_name);
++ goto out_del;
++ }
++ }
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ if (dev->scsi_dev == NULL) {
++ res = sysfs_create_file(&dev->dev_kobj,
++ &dev_dump_prs_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't create attr %s for dev %s",
++ dev_dump_prs_attr.attr.name, dev->virt_name);
++ goto out_del;
++ }
++ }
++#endif
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ scst_dev_sysfs_del(dev);
++ goto out;
++}
++
++/*
++ * Must not be called under scst_mutex, due to possible deadlock with
++ * sysfs ref counting in sysfs works (it is waiting for the last put, but
++ * the last ref counter holder is waiting for scst_mutex)
++ */
++void scst_dev_sysfs_del(struct scst_device *dev)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ dev->dev_kobj_release_cmpl = &c;
++
++ kobject_del(dev->dev_exp_kobj);
++ kobject_del(&dev->dev_kobj);
++
++ kobject_put(dev->dev_exp_kobj);
++ kobject_put(&dev->dev_kobj);
++
++ rc = wait_for_completion_timeout(dev->dev_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for device %s (%d refs)...", dev->virt_name,
++ atomic_read(&dev->dev_kobj.kref.refcount));
++ wait_for_completion(dev->dev_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs "
++ "entry for device %s", dev->virt_name);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ ** Tgt_dev implementation
++ **/
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++
++static char *scst_io_size_names[] = {
++ "<=8K ",
++ "<=32K ",
++ "<=128K",
++ "<=512K",
++ ">512K "
++};
++
++static ssize_t scst_tgt_dev_latency_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buffer)
++{
++ int res = 0, i;
++ char buf[50];
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj);
++
++ for (i = 0; i < SCST_LATENCY_STATS_NUM; i++) {
++ uint64_t scst_time_wr, tgt_time_wr, dev_time_wr;
++ unsigned int processed_cmds_wr;
++ uint64_t scst_time_rd, tgt_time_rd, dev_time_rd;
++ unsigned int processed_cmds_rd;
++ struct scst_ext_latency_stat *latency_stat;
++
++ latency_stat = &tgt_dev->dev_latency_stat[i];
++ scst_time_wr = latency_stat->scst_time_wr;
++ scst_time_rd = latency_stat->scst_time_rd;
++ tgt_time_wr = latency_stat->tgt_time_wr;
++ tgt_time_rd = latency_stat->tgt_time_rd;
++ dev_time_wr = latency_stat->dev_time_wr;
++ dev_time_rd = latency_stat->dev_time_rd;
++ processed_cmds_wr = latency_stat->processed_cmds_wr;
++ processed_cmds_rd = latency_stat->processed_cmds_rd;
++
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-5s %-9s %-15lu ", "Write", scst_io_size_names[i],
++ (unsigned long)processed_cmds_wr);
++ if (processed_cmds_wr == 0)
++ processed_cmds_wr = 1;
++
++ do_div(scst_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_scst_time_wr,
++ (unsigned long)scst_time_wr,
++ (unsigned long)latency_stat->max_scst_time_wr,
++ (unsigned long)latency_stat->scst_time_wr);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(tgt_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_tgt_time_wr,
++ (unsigned long)tgt_time_wr,
++ (unsigned long)latency_stat->max_tgt_time_wr,
++ (unsigned long)latency_stat->tgt_time_wr);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(dev_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_dev_time_wr,
++ (unsigned long)dev_time_wr,
++ (unsigned long)latency_stat->max_dev_time_wr,
++ (unsigned long)latency_stat->dev_time_wr);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s\n", buf);
++
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-5s %-9s %-15lu ", "Read", scst_io_size_names[i],
++ (unsigned long)processed_cmds_rd);
++ if (processed_cmds_rd == 0)
++ processed_cmds_rd = 1;
++
++ do_div(scst_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_scst_time_rd,
++ (unsigned long)scst_time_rd,
++ (unsigned long)latency_stat->max_scst_time_rd,
++ (unsigned long)latency_stat->scst_time_rd);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(tgt_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_tgt_time_rd,
++ (unsigned long)tgt_time_rd,
++ (unsigned long)latency_stat->max_tgt_time_rd,
++ (unsigned long)latency_stat->tgt_time_rd);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(dev_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_dev_time_rd,
++ (unsigned long)dev_time_rd,
++ (unsigned long)latency_stat->max_dev_time_rd,
++ (unsigned long)latency_stat->dev_time_rd);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s\n", buf);
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute tgt_dev_latency_attr =
++ __ATTR(latency, S_IRUGO,
++ scst_tgt_dev_latency_show, NULL);
++
++#endif /* CONFIG_SCST_MEASURE_LATENCY */
++
++static ssize_t scst_tgt_dev_active_commands_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_tgt_dev *tgt_dev;
++
++ tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj);
++
++ pos = sprintf(buf, "%d\n", atomic_read(&tgt_dev->tgt_dev_cmd_count));
++
++ return pos;
++}
++
++static struct kobj_attribute tgt_dev_active_commands_attr =
++ __ATTR(active_commands, S_IRUGO,
++ scst_tgt_dev_active_commands_show, NULL);
++
++static struct attribute *scst_tgt_dev_attrs[] = {
++ &tgt_dev_active_commands_attr.attr,
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++ &tgt_dev_latency_attr.attr,
++#endif
++ NULL,
++};
++
++static void scst_sysfs_tgt_dev_release(struct kobject *kobj)
++{
++ struct scst_tgt_dev *tgt_dev;
++
++ TRACE_ENTRY();
++
++ tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj);
++ if (tgt_dev->tgt_dev_kobj_release_cmpl)
++ complete_all(tgt_dev->tgt_dev_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++static struct kobj_type scst_tgt_dev_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_sysfs_tgt_dev_release,
++ .default_attrs = scst_tgt_dev_attrs,
++};
++
++int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ res = kobject_init_and_add(&tgt_dev->tgt_dev_kobj, &scst_tgt_dev_ktype,
++ &tgt_dev->sess->sess_kobj, "lun%lld",
++ (unsigned long long)tgt_dev->lun);
++ if (res != 0) {
++ PRINT_ERROR("Can't add tgt_dev %lld to sysfs",
++ (unsigned long long)tgt_dev->lun);
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * Called with scst_mutex held.
++ *
++ * !! No sysfs works must use kobject_get() to protect tgt_dev, due to possible
++ * !! deadlock with scst_mutex (it is waiting for the last put, but
++ * !! the last ref counter holder is waiting for scst_mutex)
++ */
++void scst_tgt_dev_sysfs_del(struct scst_tgt_dev *tgt_dev)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ tgt_dev->tgt_dev_kobj_release_cmpl = &c;
++
++ kobject_del(&tgt_dev->tgt_dev_kobj);
++ kobject_put(&tgt_dev->tgt_dev_kobj);
++
++ rc = wait_for_completion_timeout(
++ tgt_dev->tgt_dev_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for tgt_dev %lld (%d refs)...",
++ (unsigned long long)tgt_dev->lun,
++ atomic_read(&tgt_dev->tgt_dev_kobj.kref.refcount));
++ wait_for_completion(tgt_dev->tgt_dev_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs entry for "
++ "tgt_dev %lld", (unsigned long long)tgt_dev->lun);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ ** Sessions subdirectory implementation
++ **/
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++
++static ssize_t scst_sess_latency_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buffer)
++{
++ ssize_t res = 0;
++ struct scst_session *sess;
++ int i;
++ char buf[50];
++ uint64_t scst_time, tgt_time, dev_time;
++ unsigned int processed_cmds;
++
++ TRACE_ENTRY();
++
++ sess = container_of(kobj, struct scst_session, sess_kobj);
++
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-15s %-15s %-46s %-46s %-46s\n",
++ "T-L names", "Total commands", "SCST latency",
++ "Target latency", "Dev latency (min/avg/max/all ns)");
++
++ spin_lock_bh(&sess->lat_lock);
++
++ for (i = 0; i < SCST_LATENCY_STATS_NUM ; i++) {
++ uint64_t scst_time_wr, tgt_time_wr, dev_time_wr;
++ unsigned int processed_cmds_wr;
++ uint64_t scst_time_rd, tgt_time_rd, dev_time_rd;
++ unsigned int processed_cmds_rd;
++ struct scst_ext_latency_stat *latency_stat;
++
++ latency_stat = &sess->sess_latency_stat[i];
++ scst_time_wr = latency_stat->scst_time_wr;
++ scst_time_rd = latency_stat->scst_time_rd;
++ tgt_time_wr = latency_stat->tgt_time_wr;
++ tgt_time_rd = latency_stat->tgt_time_rd;
++ dev_time_wr = latency_stat->dev_time_wr;
++ dev_time_rd = latency_stat->dev_time_rd;
++ processed_cmds_wr = latency_stat->processed_cmds_wr;
++ processed_cmds_rd = latency_stat->processed_cmds_rd;
++
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-5s %-9s %-15lu ",
++ "Write", scst_io_size_names[i],
++ (unsigned long)processed_cmds_wr);
++ if (processed_cmds_wr == 0)
++ processed_cmds_wr = 1;
++
++ do_div(scst_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_scst_time_wr,
++ (unsigned long)scst_time_wr,
++ (unsigned long)latency_stat->max_scst_time_wr,
++ (unsigned long)latency_stat->scst_time_wr);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(tgt_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_tgt_time_wr,
++ (unsigned long)tgt_time_wr,
++ (unsigned long)latency_stat->max_tgt_time_wr,
++ (unsigned long)latency_stat->tgt_time_wr);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(dev_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_dev_time_wr,
++ (unsigned long)dev_time_wr,
++ (unsigned long)latency_stat->max_dev_time_wr,
++ (unsigned long)latency_stat->dev_time_wr);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s\n", buf);
++
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-5s %-9s %-15lu ",
++ "Read", scst_io_size_names[i],
++ (unsigned long)processed_cmds_rd);
++ if (processed_cmds_rd == 0)
++ processed_cmds_rd = 1;
++
++ do_div(scst_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_scst_time_rd,
++ (unsigned long)scst_time_rd,
++ (unsigned long)latency_stat->max_scst_time_rd,
++ (unsigned long)latency_stat->scst_time_rd);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(tgt_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_tgt_time_rd,
++ (unsigned long)tgt_time_rd,
++ (unsigned long)latency_stat->max_tgt_time_rd,
++ (unsigned long)latency_stat->tgt_time_rd);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(dev_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_dev_time_rd,
++ (unsigned long)dev_time_rd,
++ (unsigned long)latency_stat->max_dev_time_rd,
++ (unsigned long)latency_stat->dev_time_rd);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s\n", buf);
++ }
++
++ scst_time = sess->scst_time;
++ tgt_time = sess->tgt_time;
++ dev_time = sess->dev_time;
++ processed_cmds = sess->processed_cmds;
++
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "\n%-15s %-16d", "Overall ", processed_cmds);
++
++ if (processed_cmds == 0)
++ processed_cmds = 1;
++
++ do_div(scst_time, processed_cmds);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)sess->min_scst_time,
++ (unsigned long)scst_time,
++ (unsigned long)sess->max_scst_time,
++ (unsigned long)sess->scst_time);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(tgt_time, processed_cmds);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)sess->min_tgt_time,
++ (unsigned long)tgt_time,
++ (unsigned long)sess->max_tgt_time,
++ (unsigned long)sess->tgt_time);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s", buf);
++
++ do_div(dev_time, processed_cmds);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)sess->min_dev_time,
++ (unsigned long)dev_time,
++ (unsigned long)sess->max_dev_time,
++ (unsigned long)sess->dev_time);
++ res += scnprintf(&buffer[res], SCST_SYSFS_BLOCK_SIZE - res,
++ "%-47s\n\n", buf);
++
++ spin_unlock_bh(&sess->lat_lock);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_sess_zero_latency(struct scst_sysfs_work_item *work)
++{
++ int res = 0, t;
++ struct scst_session *sess = work->sess;
++
++ TRACE_ENTRY();
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out_put;
++
++ PRINT_INFO("Zeroing latency statistics for initiator "
++ "%s", sess->initiator_name);
++
++ spin_lock_bh(&sess->lat_lock);
++
++ sess->scst_time = 0;
++ sess->tgt_time = 0;
++ sess->dev_time = 0;
++ sess->min_scst_time = 0;
++ sess->min_tgt_time = 0;
++ sess->min_dev_time = 0;
++ sess->max_scst_time = 0;
++ sess->max_tgt_time = 0;
++ sess->max_dev_time = 0;
++ sess->processed_cmds = 0;
++ memset(sess->sess_latency_stat, 0,
++ sizeof(sess->sess_latency_stat));
++
++ for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) {
++ struct list_head *head = &sess->sess_tgt_dev_list[t];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ tgt_dev->scst_time = 0;
++ tgt_dev->tgt_time = 0;
++ tgt_dev->dev_time = 0;
++ tgt_dev->processed_cmds = 0;
++ memset(tgt_dev->dev_latency_stat, 0,
++ sizeof(tgt_dev->dev_latency_stat));
++ }
++ }
++
++ spin_unlock_bh(&sess->lat_lock);
++
++ mutex_unlock(&scst_mutex);
++
++out_put:
++ kobject_put(&sess->sess_kobj);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_sess_latency_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_session *sess;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ sess = container_of(kobj, struct scst_session, sess_kobj);
++
++ res = scst_alloc_sysfs_work(scst_sess_zero_latency, false, &work);
++ if (res != 0)
++ goto out;
++
++ work->sess = sess;
++
++ kobject_get(&sess->sess_kobj);
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute session_latency_attr =
++ __ATTR(latency, S_IRUGO | S_IWUSR, scst_sess_latency_show,
++ scst_sess_latency_store);
++
++#endif /* CONFIG_SCST_MEASURE_LATENCY */
++
++static ssize_t scst_sess_sysfs_commands_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_session *sess;
++
++ sess = container_of(kobj, struct scst_session, sess_kobj);
++
++ return sprintf(buf, "%i\n", atomic_read(&sess->sess_cmd_count));
++}
++
++static struct kobj_attribute session_commands_attr =
++ __ATTR(commands, S_IRUGO, scst_sess_sysfs_commands_show, NULL);
++
++static int scst_sysfs_sess_get_active_commands(struct scst_session *sess)
++{
++ int res;
++ int active_cmds = 0, t;
++
++ TRACE_ENTRY();
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out_put;
++
++ for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) {
++ struct list_head *head = &sess->sess_tgt_dev_list[t];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) {
++ active_cmds += atomic_read(&tgt_dev->tgt_dev_cmd_count);
++ }
++ }
++
++ mutex_unlock(&scst_mutex);
++
++ res = active_cmds;
++
++out_put:
++ kobject_put(&sess->sess_kobj);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_sysfs_sess_get_active_commands_work_fn(struct scst_sysfs_work_item *work)
++{
++ return scst_sysfs_sess_get_active_commands(work->sess);
++}
++
++static ssize_t scst_sess_sysfs_active_commands_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int res;
++ struct scst_session *sess;
++ struct scst_sysfs_work_item *work;
++
++ sess = container_of(kobj, struct scst_session, sess_kobj);
++
++ res = scst_alloc_sysfs_work(scst_sysfs_sess_get_active_commands_work_fn,
++ true, &work);
++ if (res != 0)
++ goto out;
++
++ work->sess = sess;
++
++ kobject_get(&sess->sess_kobj);
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res != -EAGAIN)
++ res = sprintf(buf, "%i\n", res);
++
++out:
++ return res;
++}
++
++static struct kobj_attribute session_active_commands_attr =
++ __ATTR(active_commands, S_IRUGO, scst_sess_sysfs_active_commands_show,
++ NULL);
++
++static ssize_t scst_sess_sysfs_initiator_name_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_session *sess;
++
++ sess = container_of(kobj, struct scst_session, sess_kobj);
++
++ return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n",
++ sess->initiator_name);
++}
++
++static struct kobj_attribute session_initiator_name_attr =
++ __ATTR(initiator_name, S_IRUGO, scst_sess_sysfs_initiator_name_show,
++ NULL);
++
++#define SCST_SESS_SYSFS_STAT_ATTR(name, exported_name, dir, kb) \
++static ssize_t scst_sess_sysfs_##exported_name##_show(struct kobject *kobj, \
++ struct kobj_attribute *attr, char *buf) \
++{ \
++ struct scst_session *sess; \
++ int res; \
++ uint64_t v; \
++ \
++ BUILD_BUG_ON(SCST_DATA_UNKNOWN != 0); \
++ BUILD_BUG_ON(SCST_DATA_WRITE != 1); \
++ BUILD_BUG_ON(SCST_DATA_READ != 2); \
++ BUILD_BUG_ON(SCST_DATA_BIDI != 3); \
++ BUILD_BUG_ON(SCST_DATA_NONE != 4); \
++ \
++ BUILD_BUG_ON(dir >= SCST_DATA_DIR_MAX); \
++ \
++ sess = container_of(kobj, struct scst_session, sess_kobj); \
++ v = sess->io_stats[dir].name; \
++ if (kb) \
++ v >>= 10; \
++ res = sprintf(buf, "%llu\n", (unsigned long long)v); \
++ return res; \
++} \
++ \
++static ssize_t scst_sess_sysfs_##exported_name##_store(struct kobject *kobj, \
++ struct kobj_attribute *attr, const char *buf, size_t count) \
++{ \
++ struct scst_session *sess; \
++ sess = container_of(kobj, struct scst_session, sess_kobj); \
++ spin_lock_irq(&sess->sess_list_lock); \
++ BUILD_BUG_ON(dir >= SCST_DATA_DIR_MAX); \
++ sess->io_stats[dir].cmd_count = 0; \
++ sess->io_stats[dir].io_byte_count = 0; \
++ spin_unlock_irq(&sess->sess_list_lock); \
++ return count; \
++} \
++ \
++static struct kobj_attribute session_##exported_name##_attr = \
++ __ATTR(exported_name, S_IRUGO | S_IWUSR, \
++ scst_sess_sysfs_##exported_name##_show, \
++ scst_sess_sysfs_##exported_name##_store);
++
++SCST_SESS_SYSFS_STAT_ATTR(cmd_count, unknown_cmd_count, SCST_DATA_UNKNOWN, 0);
++SCST_SESS_SYSFS_STAT_ATTR(cmd_count, write_cmd_count, SCST_DATA_WRITE, 0);
++SCST_SESS_SYSFS_STAT_ATTR(io_byte_count, write_io_count_kb, SCST_DATA_WRITE, 1);
++SCST_SESS_SYSFS_STAT_ATTR(cmd_count, read_cmd_count, SCST_DATA_READ, 0);
++SCST_SESS_SYSFS_STAT_ATTR(io_byte_count, read_io_count_kb, SCST_DATA_READ, 1);
++SCST_SESS_SYSFS_STAT_ATTR(cmd_count, bidi_cmd_count, SCST_DATA_BIDI, 0);
++SCST_SESS_SYSFS_STAT_ATTR(io_byte_count, bidi_io_count_kb, SCST_DATA_BIDI, 1);
++SCST_SESS_SYSFS_STAT_ATTR(cmd_count, none_cmd_count, SCST_DATA_NONE, 0);
++
++static struct attribute *scst_session_attrs[] = {
++ &session_commands_attr.attr,
++ &session_active_commands_attr.attr,
++ &session_initiator_name_attr.attr,
++ &session_unknown_cmd_count_attr.attr,
++ &session_write_cmd_count_attr.attr,
++ &session_write_io_count_kb_attr.attr,
++ &session_read_cmd_count_attr.attr,
++ &session_read_io_count_kb_attr.attr,
++ &session_bidi_cmd_count_attr.attr,
++ &session_bidi_io_count_kb_attr.attr,
++ &session_none_cmd_count_attr.attr,
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++ &session_latency_attr.attr,
++#endif /* CONFIG_SCST_MEASURE_LATENCY */
++ NULL,
++};
++
++static void scst_sysfs_session_release(struct kobject *kobj)
++{
++ struct scst_session *sess;
++
++ TRACE_ENTRY();
++
++ sess = container_of(kobj, struct scst_session, sess_kobj);
++ if (sess->sess_kobj_release_cmpl)
++ complete_all(sess->sess_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++static struct kobj_type scst_session_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_sysfs_session_release,
++ .default_attrs = scst_session_attrs,
++};
++
++static int scst_create_sess_luns_link(struct scst_session *sess)
++{
++ int res;
++
++ /*
++ * No locks are needed, because sess supposed to be in acg->acg_sess_list
++ * and tgt->sess_list, so blocking them from disappearing.
++ */
++
++ if (sess->acg == sess->tgt->default_acg)
++ res = sysfs_create_link(&sess->sess_kobj,
++ sess->tgt->tgt_luns_kobj, "luns");
++ else
++ res = sysfs_create_link(&sess->sess_kobj,
++ sess->acg->luns_kobj, "luns");
++
++ if (res != 0)
++ PRINT_ERROR("Can't create luns link for initiator %s",
++ sess->initiator_name);
++
++ return res;
++}
++
++int scst_recreate_sess_luns_link(struct scst_session *sess)
++{
++ sysfs_remove_link(&sess->sess_kobj, "luns");
++ return scst_create_sess_luns_link(sess);
++}
++
++/* Supposed to be called under scst_mutex */
++int scst_sess_sysfs_create(struct scst_session *sess)
++{
++ int res = 0;
++ struct scst_session *s;
++ char *name = (char *)sess->initiator_name;
++ int len = strlen(name) + 1, n = 1;
++
++ TRACE_ENTRY();
++
++restart:
++ list_for_each_entry(s, &sess->tgt->sess_list, sess_list_entry) {
++ if (!s->sess_kobj_ready)
++ continue;
++
++ if (strcmp(name, kobject_name(&s->sess_kobj)) == 0) {
++ if (s == sess)
++ continue;
++
++ TRACE_DBG("Duplicated session from the same initiator "
++ "%s found", name);
++
++ if (name == sess->initiator_name) {
++ len = strlen(sess->initiator_name);
++ len += 20;
++ name = kmalloc(len, GFP_KERNEL);
++ if (name == NULL) {
++ PRINT_ERROR("Unable to allocate a "
++ "replacement name (size %d)",
++ len);
++ }
++ }
++
++ snprintf(name, len, "%s_%d", sess->initiator_name, n);
++ n++;
++ goto restart;
++ }
++ }
++
++ TRACE_DBG("Adding session %s to sysfs", name);
++
++ res = kobject_init_and_add(&sess->sess_kobj, &scst_session_ktype,
++ sess->tgt->tgt_sess_kobj, name);
++ if (res != 0) {
++ PRINT_ERROR("Can't add session %s to sysfs", name);
++ goto out_free;
++ }
++
++ sess->sess_kobj_ready = 1;
++
++ if (sess->tgt->tgtt->sess_attrs) {
++ res = sysfs_create_files(&sess->sess_kobj,
++ sess->tgt->tgtt->sess_attrs);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attributes for session %s", name);
++ goto out_free;
++ }
++ }
++
++ res = scst_create_sess_luns_link(sess);
++
++out_free:
++ if (name != sess->initiator_name)
++ kfree(name);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * Must not be called under scst_mutex, due to possible deadlock with
++ * sysfs ref counting in sysfs works (it is waiting for the last put, but
++ * the last ref counter holder is waiting for scst_mutex)
++ */
++void scst_sess_sysfs_del(struct scst_session *sess)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ if (!sess->sess_kobj_ready)
++ goto out;
++
++ TRACE_DBG("Deleting session %s from sysfs",
++ kobject_name(&sess->sess_kobj));
++
++ sess->sess_kobj_release_cmpl = &c;
++
++ kobject_del(&sess->sess_kobj);
++ kobject_put(&sess->sess_kobj);
++
++ rc = wait_for_completion_timeout(sess->sess_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for session from %s (%d refs)...", sess->initiator_name,
++ atomic_read(&sess->sess_kobj.kref.refcount));
++ wait_for_completion(sess->sess_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs "
++ "entry for session %s", sess->initiator_name);
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ ** Target luns directory implementation
++ **/
++
++static void scst_acg_dev_release(struct kobject *kobj)
++{
++ struct scst_acg_dev *acg_dev;
++
++ TRACE_ENTRY();
++
++ acg_dev = container_of(kobj, struct scst_acg_dev, acg_dev_kobj);
++ if (acg_dev->acg_dev_kobj_release_cmpl)
++ complete_all(acg_dev->acg_dev_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++static ssize_t scst_lun_rd_only_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ struct scst_acg_dev *acg_dev;
++
++ acg_dev = container_of(kobj, struct scst_acg_dev, acg_dev_kobj);
++
++ if (acg_dev->rd_only || acg_dev->dev->rd_only)
++ return sprintf(buf, "%d\n%s\n", 1, SCST_SYSFS_KEY_MARK);
++ else
++ return sprintf(buf, "%d\n", 0);
++}
++
++static struct kobj_attribute lun_options_attr =
++ __ATTR(read_only, S_IRUGO, scst_lun_rd_only_show, NULL);
++
++static struct attribute *lun_attrs[] = {
++ &lun_options_attr.attr,
++ NULL,
++};
++
++static struct kobj_type acg_dev_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_acg_dev_release,
++ .default_attrs = lun_attrs,
++};
++
++/*
++ * Called with scst_mutex held.
++ *
++ * !! No sysfs works must use kobject_get() to protect acg_dev, due to possible
++ * !! deadlock with scst_mutex (it is waiting for the last put, but
++ * !! the last ref counter holder is waiting for scst_mutex)
++ */
++void scst_acg_dev_sysfs_del(struct scst_acg_dev *acg_dev)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ acg_dev->acg_dev_kobj_release_cmpl = &c;
++
++ if (acg_dev->dev != NULL) {
++ sysfs_remove_link(acg_dev->dev->dev_exp_kobj,
++ acg_dev->acg_dev_link_name);
++ kobject_put(&acg_dev->dev->dev_kobj);
++ }
++
++ kobject_del(&acg_dev->acg_dev_kobj);
++ kobject_put(&acg_dev->acg_dev_kobj);
++
++ rc = wait_for_completion_timeout(acg_dev->acg_dev_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for acg_dev %p (%d refs)...", acg_dev,
++ atomic_read(&acg_dev->acg_dev_kobj.kref.refcount));
++ wait_for_completion(acg_dev->acg_dev_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs "
++ "entry for acg_dev %p", acg_dev);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++int scst_acg_dev_sysfs_create(struct scst_acg_dev *acg_dev,
++ struct kobject *parent)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = kobject_init_and_add(&acg_dev->acg_dev_kobj, &acg_dev_ktype,
++ parent, "%llu", acg_dev->lun);
++ if (res != 0) {
++ PRINT_ERROR("Can't add acg_dev %p to sysfs", acg_dev);
++ goto out;
++ }
++
++ kobject_get(&acg_dev->dev->dev_kobj);
++
++ snprintf(acg_dev->acg_dev_link_name, sizeof(acg_dev->acg_dev_link_name),
++ "export%u", acg_dev->dev->dev_exported_lun_num++);
++
++ res = sysfs_create_link(acg_dev->dev->dev_exp_kobj,
++ &acg_dev->acg_dev_kobj, acg_dev->acg_dev_link_name);
++ if (res != 0) {
++ PRINT_ERROR("Can't create acg %s LUN link",
++ acg_dev->acg->acg_name);
++ goto out_del;
++ }
++
++ res = sysfs_create_link(&acg_dev->acg_dev_kobj,
++ &acg_dev->dev->dev_kobj, "device");
++ if (res != 0) {
++ PRINT_ERROR("Can't create acg %s device link",
++ acg_dev->acg->acg_name);
++ goto out_del;
++ }
++
++out:
++ return res;
++
++out_del:
++ scst_acg_dev_sysfs_del(acg_dev);
++ goto out;
++}
++
++/**
++ ** ini_groups directory implementation.
++ **/
++
++static void scst_acg_release(struct kobject *kobj)
++{
++ struct scst_acg *acg;
++
++ TRACE_ENTRY();
++
++ acg = container_of(kobj, struct scst_acg, acg_kobj);
++ if (acg->acg_kobj_release_cmpl)
++ complete_all(acg->acg_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++static struct kobj_type acg_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_acg_release,
++};
++
++static ssize_t scst_acg_ini_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"add INITIATOR_NAME\" >mgmt\n"
++ " echo \"del INITIATOR_NAME\" >mgmt\n"
++ " echo \"move INITIATOR_NAME DEST_GROUP_NAME\" >mgmt\n"
++ " echo \"clear\" >mgmt\n";
++
++ return sprintf(buf, "%s", help);
++}
++
++static int scst_process_acg_ini_mgmt_store(char *buffer,
++ struct scst_tgt *tgt, struct scst_acg *acg)
++{
++ int res, action;
++ char *p, *e = NULL;
++ char *name = NULL, *group = NULL;
++ struct scst_acg *acg_dest = NULL;
++ struct scst_acn *acn = NULL, *acn_tmp;
++ enum {
++ SCST_ACG_ACTION_INI_ADD = 1,
++ SCST_ACG_ACTION_INI_DEL = 2,
++ SCST_ACG_ACTION_INI_CLEAR = 3,
++ SCST_ACG_ACTION_INI_MOVE = 4,
++ };
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("tgt %p, acg %p, buffer %s", tgt, acg, buffer);
++
++ p = buffer;
++ if (p[strlen(p) - 1] == '\n')
++ p[strlen(p) - 1] = '\0';
++
++ if (strncasecmp("add", p, 3) == 0) {
++ p += 3;
++ action = SCST_ACG_ACTION_INI_ADD;
++ } else if (strncasecmp("del", p, 3) == 0) {
++ p += 3;
++ action = SCST_ACG_ACTION_INI_DEL;
++ } else if (strncasecmp("clear", p, 5) == 0) {
++ p += 5;
++ action = SCST_ACG_ACTION_INI_CLEAR;
++ } else if (strncasecmp("move", p, 4) == 0) {
++ p += 4;
++ action = SCST_ACG_ACTION_INI_MOVE;
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (action != SCST_ACG_ACTION_INI_CLEAR)
++ if (!isspace(*p)) {
++ PRINT_ERROR("%s", "Syntax error");
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out_resume;
++
++ /* Check if tgt and acg not already freed while we were coming here */
++ if (scst_check_tgt_acg_ptrs(tgt, acg) != 0)
++ goto out_unlock;
++
++ if (action != SCST_ACG_ACTION_INI_CLEAR)
++ while (isspace(*p) && *p != '\0')
++ p++;
++
++ switch (action) {
++ case SCST_ACG_ACTION_INI_ADD:
++ e = p;
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = '\0';
++ name = p;
++
++ if (name[0] == '\0') {
++ PRINT_ERROR("%s", "Invalid initiator name");
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ res = scst_acg_add_acn(acg, name);
++ if (res != 0)
++ goto out_unlock;
++ break;
++ case SCST_ACG_ACTION_INI_DEL:
++ e = p;
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = '\0';
++ name = p;
++
++ if (name[0] == '\0') {
++ PRINT_ERROR("%s", "Invalid initiator name");
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ acn = scst_find_acn(acg, name);
++ if (acn == NULL) {
++ PRINT_ERROR("Unable to find "
++ "initiator '%s' in group '%s'",
++ name, acg->acg_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ scst_del_free_acn(acn, true);
++ break;
++ case SCST_ACG_ACTION_INI_CLEAR:
++ list_for_each_entry_safe(acn, acn_tmp, &acg->acn_list,
++ acn_list_entry) {
++ scst_del_free_acn(acn, false);
++ }
++ scst_check_reassign_sessions();
++ break;
++ case SCST_ACG_ACTION_INI_MOVE:
++ e = p;
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ if (*e == '\0') {
++ PRINT_ERROR("%s", "Too few parameters");
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ *e = '\0';
++ name = p;
++
++ if (name[0] == '\0') {
++ PRINT_ERROR("%s", "Invalid initiator name");
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ e++;
++ p = e;
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = '\0';
++ group = p;
++
++ if (group[0] == '\0') {
++ PRINT_ERROR("%s", "Invalid group name");
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ TRACE_DBG("Move initiator '%s' to group '%s'",
++ name, group);
++
++ acn = scst_find_acn(acg, name);
++ if (acn == NULL) {
++ PRINT_ERROR("Unable to find "
++ "initiator '%s' in group '%s'",
++ name, acg->acg_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ acg_dest = scst_tgt_find_acg(tgt, group);
++ if (acg_dest == NULL) {
++ PRINT_ERROR("Unable to find group '%s' in target '%s'",
++ group, tgt->tgt_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ if (scst_find_acn(acg_dest, name) != NULL) {
++ PRINT_ERROR("Initiator '%s' already exists in group '%s'",
++ name, acg_dest->acg_name);
++ res = -EEXIST;
++ goto out_unlock;
++ }
++ scst_del_free_acn(acn, false);
++
++ res = scst_acg_add_acn(acg_dest, name);
++ if (res != 0)
++ goto out_unlock;
++ break;
++ }
++
++ res = 0;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out_resume:
++ scst_resume_activity();
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_acg_ini_mgmt_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return scst_process_acg_ini_mgmt_store(work->buf, work->tgt, work->acg);
++}
++
++static ssize_t scst_acg_ini_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ struct scst_acg *acg;
++
++ acg = container_of(kobj->parent, struct scst_acg, acg_kobj);
++
++ return __scst_acg_mgmt_store(acg, buf, count, false,
++ scst_acg_ini_mgmt_store_work_fn);
++}
++
++static struct kobj_attribute scst_acg_ini_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_acg_ini_mgmt_show,
++ scst_acg_ini_mgmt_store);
++
++static ssize_t scst_acg_luns_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ int res;
++ struct scst_acg *acg;
++
++ acg = container_of(kobj->parent, struct scst_acg, acg_kobj);
++ res = __scst_luns_mgmt_store(acg, false, buf, count);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_acg_luns_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_luns_mgmt_show,
++ scst_acg_luns_mgmt_store);
++
++static ssize_t scst_acg_addr_method_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_acg *acg;
++
++ acg = container_of(kobj, struct scst_acg, acg_kobj);
++
++ return __scst_acg_addr_method_show(acg, buf);
++}
++
++static ssize_t scst_acg_addr_method_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_acg *acg;
++
++ acg = container_of(kobj, struct scst_acg, acg_kobj);
++
++ res = __scst_acg_addr_method_store(acg, buf, count);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_acg_addr_method =
++ __ATTR(addr_method, S_IRUGO | S_IWUSR, scst_acg_addr_method_show,
++ scst_acg_addr_method_store);
++
++static ssize_t scst_acg_io_grouping_type_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_acg *acg;
++
++ acg = container_of(kobj, struct scst_acg, acg_kobj);
++
++ return __scst_acg_io_grouping_type_show(acg, buf);
++}
++
++static ssize_t scst_acg_io_grouping_type_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_acg *acg;
++
++ acg = container_of(kobj, struct scst_acg, acg_kobj);
++
++ res = __scst_acg_io_grouping_type_store(acg, buf, count);
++ if (res != 0)
++ goto out;
++
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_acg_io_grouping_type =
++ __ATTR(io_grouping_type, S_IRUGO | S_IWUSR,
++ scst_acg_io_grouping_type_show,
++ scst_acg_io_grouping_type_store);
++
++static ssize_t scst_acg_cpu_mask_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_acg *acg;
++
++ acg = container_of(kobj, struct scst_acg, acg_kobj);
++
++ return __scst_acg_cpu_mask_show(acg, buf);
++}
++
++static ssize_t scst_acg_cpu_mask_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_acg *acg;
++
++ acg = container_of(kobj, struct scst_acg, acg_kobj);
++
++ res = __scst_acg_cpu_mask_store(acg, buf, count);
++ if (res != 0)
++ goto out;
++
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_acg_cpu_mask =
++ __ATTR(cpu_mask, S_IRUGO | S_IWUSR,
++ scst_acg_cpu_mask_show,
++ scst_acg_cpu_mask_store);
++
++/*
++ * Called with scst_mutex held.
++ *
++ * !! No sysfs works must use kobject_get() to protect acg, due to possible
++ * !! deadlock with scst_mutex (it is waiting for the last put, but
++ * !! the last ref counter holder is waiting for scst_mutex)
++ */
++void scst_acg_sysfs_del(struct scst_acg *acg)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ acg->acg_kobj_release_cmpl = &c;
++
++ kobject_del(acg->luns_kobj);
++ kobject_del(acg->initiators_kobj);
++ kobject_del(&acg->acg_kobj);
++
++ kobject_put(acg->luns_kobj);
++ kobject_put(acg->initiators_kobj);
++ kobject_put(&acg->acg_kobj);
++
++ rc = wait_for_completion_timeout(acg->acg_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for acg %s (%d refs)...", acg->acg_name,
++ atomic_read(&acg->acg_kobj.kref.refcount));
++ wait_for_completion(acg->acg_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs "
++ "entry for acg %s", acg->acg_name);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++int scst_acg_sysfs_create(struct scst_tgt *tgt,
++ struct scst_acg *acg)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ res = kobject_init_and_add(&acg->acg_kobj, &acg_ktype,
++ tgt->tgt_ini_grp_kobj, acg->acg_name);
++ if (res != 0) {
++ PRINT_ERROR("Can't add acg '%s' to sysfs", acg->acg_name);
++ goto out;
++ }
++
++ acg->luns_kobj = kobject_create_and_add("luns", &acg->acg_kobj);
++ if (acg->luns_kobj == NULL) {
++ PRINT_ERROR("Can't create luns kobj for tgt %s",
++ tgt->tgt_name);
++ res = -ENOMEM;
++ goto out_del;
++ }
++
++ res = sysfs_create_file(acg->luns_kobj, &scst_acg_luns_mgmt.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add tgt attr %s for tgt %s",
++ scst_acg_luns_mgmt.attr.name, tgt->tgt_name);
++ goto out_del;
++ }
++
++ acg->initiators_kobj = kobject_create_and_add("initiators",
++ &acg->acg_kobj);
++ if (acg->initiators_kobj == NULL) {
++ PRINT_ERROR("Can't create initiators kobj for tgt %s",
++ tgt->tgt_name);
++ res = -ENOMEM;
++ goto out_del;
++ }
++
++ res = sysfs_create_file(acg->initiators_kobj,
++ &scst_acg_ini_mgmt.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add tgt attr %s for tgt %s",
++ scst_acg_ini_mgmt.attr.name, tgt->tgt_name);
++ goto out_del;
++ }
++
++ res = sysfs_create_file(&acg->acg_kobj, &scst_acg_addr_method.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add tgt attr %s for tgt %s",
++ scst_acg_addr_method.attr.name, tgt->tgt_name);
++ goto out_del;
++ }
++
++ res = sysfs_create_file(&acg->acg_kobj, &scst_acg_io_grouping_type.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add tgt attr %s for tgt %s",
++ scst_acg_io_grouping_type.attr.name, tgt->tgt_name);
++ goto out_del;
++ }
++
++ res = sysfs_create_file(&acg->acg_kobj, &scst_acg_cpu_mask.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add tgt attr %s for tgt %s",
++ scst_acg_cpu_mask.attr.name, tgt->tgt_name);
++ goto out_del;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ scst_acg_sysfs_del(acg);
++ goto out;
++}
++
++/**
++ ** acn
++ **/
++
++static ssize_t scst_acn_file_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n",
++ attr->attr.name);
++}
++
++int scst_acn_sysfs_create(struct scst_acn *acn)
++{
++ int res = 0;
++ struct scst_acg *acg = acn->acg;
++ struct kobj_attribute *attr = NULL;
++#ifdef CONFIG_DEBUG_LOCK_ALLOC
++ static struct lock_class_key __key;
++#endif
++
++ TRACE_ENTRY();
++
++ acn->acn_attr = NULL;
++
++ attr = kzalloc(sizeof(struct kobj_attribute), GFP_KERNEL);
++ if (attr == NULL) {
++ PRINT_ERROR("Unable to allocate attributes for initiator '%s'",
++ acn->name);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ attr->attr.name = kstrdup(acn->name, GFP_KERNEL);
++ if (attr->attr.name == NULL) {
++ PRINT_ERROR("Unable to allocate attributes for initiator '%s'",
++ acn->name);
++ res = -ENOMEM;
++ goto out_free;
++ }
++
++#ifdef CONFIG_DEBUG_LOCK_ALLOC
++ attr->attr.key = &__key;
++#endif
++
++ attr->attr.mode = S_IRUGO;
++ attr->show = scst_acn_file_show;
++ attr->store = NULL;
++
++ res = sysfs_create_file(acg->initiators_kobj, &attr->attr);
++ if (res != 0) {
++ PRINT_ERROR("Unable to create acn '%s' for group '%s'",
++ acn->name, acg->acg_name);
++ kfree(attr->attr.name);
++ goto out_free;
++ }
++
++ acn->acn_attr = attr;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(attr);
++ goto out;
++}
++
++void scst_acn_sysfs_del(struct scst_acn *acn)
++{
++ struct scst_acg *acg = acn->acg;
++
++ TRACE_ENTRY();
++
++ if (acn->acn_attr != NULL) {
++ sysfs_remove_file(acg->initiators_kobj,
++ &acn->acn_attr->attr);
++ kfree(acn->acn_attr->attr.name);
++ kfree(acn->acn_attr);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ ** Dev handlers
++ **/
++
++static void scst_devt_release(struct kobject *kobj)
++{
++ struct scst_dev_type *devt;
++
++ TRACE_ENTRY();
++
++ devt = container_of(kobj, struct scst_dev_type, devt_kobj);
++ if (devt->devt_kobj_release_compl)
++ complete_all(devt->devt_kobj_release_compl);
++
++ TRACE_EXIT();
++ return;
++}
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++static ssize_t scst_devt_trace_level_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_dev_type *devt;
++
++ devt = container_of(kobj, struct scst_dev_type, devt_kobj);
++
++ return scst_trace_level_show(devt->trace_tbl,
++ devt->trace_flags ? *devt->trace_flags : 0, buf,
++ devt->trace_tbl_help);
++}
++
++static ssize_t scst_devt_trace_level_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_dev_type *devt;
++
++ TRACE_ENTRY();
++
++ devt = container_of(kobj, struct scst_dev_type, devt_kobj);
++
++ res = mutex_lock_interruptible(&scst_log_mutex);
++ if (res != 0)
++ goto out;
++
++ res = scst_write_trace(buf, count, devt->trace_flags,
++ devt->default_trace_flags, devt->name, devt->trace_tbl);
++
++ mutex_unlock(&scst_log_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute devt_trace_attr =
++ __ATTR(trace_level, S_IRUGO | S_IWUSR,
++ scst_devt_trace_level_show, scst_devt_trace_level_store);
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++static ssize_t scst_devt_type_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos;
++ struct scst_dev_type *devt;
++
++ devt = container_of(kobj, struct scst_dev_type, devt_kobj);
++
++ pos = sprintf(buf, "%d - %s\n", devt->type,
++ (unsigned)devt->type >= ARRAY_SIZE(scst_dev_handler_types) ?
++ "unknown" : scst_dev_handler_types[devt->type]);
++
++ return pos;
++}
++
++static struct kobj_attribute scst_devt_type_attr =
++ __ATTR(type, S_IRUGO, scst_devt_type_show, NULL);
++
++static struct attribute *scst_devt_default_attrs[] = {
++ &scst_devt_type_attr.attr,
++ NULL,
++};
++
++static struct kobj_type scst_devt_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_devt_release,
++ .default_attrs = scst_devt_default_attrs,
++};
++
++static ssize_t scst_devt_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"add_device device_name [parameters]\" >mgmt\n"
++ " echo \"del_device device_name\" >mgmt\n"
++ "%s%s"
++ "%s"
++ "\n"
++ "where parameters are one or more "
++ "param_name=value pairs separated by ';'\n\n"
++ "%s%s%s%s%s%s%s%s\n";
++ struct scst_dev_type *devt;
++
++ devt = container_of(kobj, struct scst_dev_type, devt_kobj);
++
++ return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, help,
++ (devt->devt_optional_attributes != NULL) ?
++ " echo \"add_attribute <attribute> <value>\" >mgmt\n"
++ " echo \"del_attribute <attribute> <value>\" >mgmt\n" : "",
++ (devt->dev_optional_attributes != NULL) ?
++ " echo \"add_device_attribute device_name <attribute> <value>\" >mgmt"
++ " echo \"del_device_attribute device_name <attribute> <value>\" >mgmt\n" : "",
++ (devt->mgmt_cmd_help) ? devt->mgmt_cmd_help : "",
++ (devt->add_device_parameters != NULL) ?
++ "The following parameters available: " : "",
++ (devt->add_device_parameters != NULL) ?
++ devt->add_device_parameters : "",
++ (devt->devt_optional_attributes != NULL) ?
++ "The following dev handler attributes available: " : "",
++ (devt->devt_optional_attributes != NULL) ?
++ devt->devt_optional_attributes : "",
++ (devt->devt_optional_attributes != NULL) ? "\n" : "",
++ (devt->dev_optional_attributes != NULL) ?
++ "The following device attributes available: " : "",
++ (devt->dev_optional_attributes != NULL) ?
++ devt->dev_optional_attributes : "",
++ (devt->dev_optional_attributes != NULL) ? "\n" : "");
++}
++
++static int scst_process_devt_mgmt_store(char *buffer,
++ struct scst_dev_type *devt)
++{
++ int res = 0;
++ char *p, *pp, *dev_name;
++
++ TRACE_ENTRY();
++
++ /* Check if our pointer is still alive and, if yes, grab it */
++ if (scst_check_grab_devt_ptr(devt, &scst_virtual_dev_type_list) != 0)
++ goto out;
++
++ TRACE_DBG("devt %p, buffer %s", devt, buffer);
++
++ pp = buffer;
++ if (pp[strlen(pp) - 1] == '\n')
++ pp[strlen(pp) - 1] = '\0';
++
++ p = scst_get_next_lexem(&pp);
++
++ if (strcasecmp("add_device", p) == 0) {
++ dev_name = scst_get_next_lexem(&pp);
++ if (*dev_name == '\0') {
++ PRINT_ERROR("%s", "Device name required");
++ res = -EINVAL;
++ goto out_ungrab;
++ }
++ res = devt->add_device(dev_name, pp);
++ } else if (strcasecmp("del_device", p) == 0) {
++ dev_name = scst_get_next_lexem(&pp);
++ if (*dev_name == '\0') {
++ PRINT_ERROR("%s", "Device name required");
++ res = -EINVAL;
++ goto out_ungrab;
++ }
++
++ p = scst_get_next_lexem(&pp);
++ if (*p != '\0')
++ goto out_syntax_err;
++
++ res = devt->del_device(dev_name);
++ } else if (devt->mgmt_cmd != NULL) {
++ scst_restore_token_str(p, pp);
++ res = devt->mgmt_cmd(buffer);
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out_ungrab;
++ }
++
++out_ungrab:
++ scst_ungrab_devt_ptr(devt);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_syntax_err:
++ PRINT_ERROR("Syntax error on \"%s\"", p);
++ res = -EINVAL;
++ goto out_ungrab;
++}
++
++static int scst_devt_mgmt_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return scst_process_devt_mgmt_store(work->buf, work->devt);
++}
++
++static ssize_t __scst_devt_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count,
++ int (*sysfs_work_fn)(struct scst_sysfs_work_item *work))
++{
++ int res;
++ char *buffer;
++ struct scst_dev_type *devt;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ devt = container_of(kobj, struct scst_dev_type, devt_kobj);
++
++ buffer = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (buffer == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ res = scst_alloc_sysfs_work(sysfs_work_fn, false, &work);
++ if (res != 0)
++ goto out_free;
++
++ work->buf = buffer;
++ work->devt = devt;
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(buffer);
++ goto out;
++}
++
++static ssize_t scst_devt_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ return __scst_devt_mgmt_store(kobj, attr, buf, count,
++ scst_devt_mgmt_store_work_fn);
++}
++
++static struct kobj_attribute scst_devt_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_devt_mgmt_show,
++ scst_devt_mgmt_store);
++
++static ssize_t scst_devt_pass_through_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"add_device H:C:I:L\" >mgmt\n"
++ " echo \"del_device H:C:I:L\" >mgmt\n";
++ return sprintf(buf, "%s", help);
++}
++
++static int scst_process_devt_pass_through_mgmt_store(char *buffer,
++ struct scst_dev_type *devt)
++{
++ int res = 0;
++ char *p, *pp, *action;
++ unsigned long host, channel, id, lun;
++ struct scst_device *d, *dev = NULL;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("devt %p, buffer %s", devt, buffer);
++
++ pp = buffer;
++ if (pp[strlen(pp) - 1] == '\n')
++ pp[strlen(pp) - 1] = '\0';
++
++ action = scst_get_next_lexem(&pp);
++ p = scst_get_next_lexem(&pp);
++ if (*p == '\0') {
++ PRINT_ERROR("%s", "Device required");
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (*scst_get_next_lexem(&pp) != '\0') {
++ PRINT_ERROR("%s", "Too many parameters");
++ res = -EINVAL;
++ goto out_syntax_err;
++ }
++
++ host = simple_strtoul(p, &p, 0);
++ if ((host == ULONG_MAX) || (*p != ':'))
++ goto out_syntax_err;
++ p++;
++ channel = simple_strtoul(p, &p, 0);
++ if ((channel == ULONG_MAX) || (*p != ':'))
++ goto out_syntax_err;
++ p++;
++ id = simple_strtoul(p, &p, 0);
++ if ((channel == ULONG_MAX) || (*p != ':'))
++ goto out_syntax_err;
++ p++;
++ lun = simple_strtoul(p, &p, 0);
++ if (lun == ULONG_MAX)
++ goto out_syntax_err;
++
++ TRACE_DBG("Dev %ld:%ld:%ld:%ld", host, channel, id, lun);
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out;
++
++ /* Check if devt not be already freed while we were coming here */
++ if (scst_check_devt_ptr(devt, &scst_dev_type_list) != 0)
++ goto out_unlock;
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if ((d->virt_id == 0) &&
++ d->scsi_dev->host->host_no == host &&
++ d->scsi_dev->channel == channel &&
++ d->scsi_dev->id == id &&
++ d->scsi_dev->lun == lun) {
++ dev = d;
++ TRACE_DBG("Dev %p (%ld:%ld:%ld:%ld) found",
++ dev, host, channel, id, lun);
++ break;
++ }
++ }
++ if (dev == NULL) {
++ PRINT_ERROR("Device %ld:%ld:%ld:%ld not found",
++ host, channel, id, lun);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ if (dev->scsi_dev->type != devt->type) {
++ PRINT_ERROR("Type %d of device %s differs from type "
++ "%d of dev handler %s", dev->type,
++ dev->virt_name, devt->type, devt->name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ if (strcasecmp("add_device", action) == 0) {
++ res = scst_assign_dev_handler(dev, devt);
++ if (res == 0)
++ PRINT_INFO("Device %s assigned to dev handler %s",
++ dev->virt_name, devt->name);
++ } else if (strcasecmp("del_device", action) == 0) {
++ if (dev->handler != devt) {
++ PRINT_ERROR("Device %s is not assigned to handler %s",
++ dev->virt_name, devt->name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ res = scst_assign_dev_handler(dev, &scst_null_devtype);
++ if (res == 0)
++ PRINT_INFO("Device %s unassigned from dev handler %s",
++ dev->virt_name, devt->name);
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", action);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_syntax_err:
++ PRINT_ERROR("Syntax error on \"%s\"", p);
++ res = -EINVAL;
++ goto out;
++}
++
++static int scst_devt_pass_through_mgmt_store_work_fn(
++ struct scst_sysfs_work_item *work)
++{
++ return scst_process_devt_pass_through_mgmt_store(work->buf, work->devt);
++}
++
++static ssize_t scst_devt_pass_through_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ return __scst_devt_mgmt_store(kobj, attr, buf, count,
++ scst_devt_pass_through_mgmt_store_work_fn);
++}
++
++static struct kobj_attribute scst_devt_pass_through_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_devt_pass_through_mgmt_show,
++ scst_devt_pass_through_mgmt_store);
++
++int scst_devt_sysfs_create(struct scst_dev_type *devt)
++{
++ int res;
++ struct kobject *parent;
++
++ TRACE_ENTRY();
++
++ if (devt->parent != NULL)
++ parent = &devt->parent->devt_kobj;
++ else
++ parent = scst_handlers_kobj;
++
++ res = kobject_init_and_add(&devt->devt_kobj, &scst_devt_ktype,
++ parent, devt->name);
++ if (res != 0) {
++ PRINT_ERROR("Can't add devt %s to sysfs", devt->name);
++ goto out;
++ }
++
++ if (devt->add_device != NULL) {
++ res = sysfs_create_file(&devt->devt_kobj,
++ &scst_devt_mgmt.attr);
++ } else {
++ res = sysfs_create_file(&devt->devt_kobj,
++ &scst_devt_pass_through_mgmt.attr);
++ }
++ if (res != 0) {
++ PRINT_ERROR("Can't add mgmt attr for dev handler %s",
++ devt->name);
++ goto out_err;
++ }
++
++ if (devt->devt_attrs) {
++ res = sysfs_create_files(&devt->devt_kobj, devt->devt_attrs);
++ if (res != 0) {
++ PRINT_ERROR("Can't add attributes for dev handler %s",
++ devt->name);
++ goto out_err;
++ }
++ }
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ if (devt->trace_flags != NULL) {
++ res = sysfs_create_file(&devt->devt_kobj,
++ &devt_trace_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Can't add devt trace_flag for dev "
++ "handler %s", devt->name);
++ goto out_err;
++ }
++ }
++#endif
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_err:
++ scst_devt_sysfs_del(devt);
++ goto out;
++}
++
++void scst_devt_sysfs_del(struct scst_dev_type *devt)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ devt->devt_kobj_release_compl = &c;
++
++ kobject_del(&devt->devt_kobj);
++ kobject_put(&devt->devt_kobj);
++
++ rc = wait_for_completion_timeout(devt->devt_kobj_release_compl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing of sysfs entry "
++ "for dev handler template %s (%d refs)...", devt->name,
++ atomic_read(&devt->devt_kobj.kref.refcount));
++ wait_for_completion(devt->devt_kobj_release_compl);
++ PRINT_INFO("Done waiting for releasing sysfs entry "
++ "for dev handler template %s", devt->name);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ ** SCST sysfs device_groups/<dg>/devices/<dev> implementation.
++ **/
++
++int scst_dg_dev_sysfs_add(struct scst_dev_group *dg, struct scst_dg_dev *dgdev)
++{
++ int res;
++
++ TRACE_ENTRY();
++ res = sysfs_create_link(dg->dev_kobj, &dgdev->dev->dev_kobj,
++ dgdev->dev->virt_name);
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++void scst_dg_dev_sysfs_del(struct scst_dev_group *dg, struct scst_dg_dev *dgdev)
++{
++ TRACE_ENTRY();
++ sysfs_remove_link(dg->dev_kobj, dgdev->dev->virt_name);
++ TRACE_EXIT();
++}
++
++/**
++ ** SCST sysfs device_groups/<dg>/devices directory implementation.
++ **/
++
++static ssize_t scst_dg_devs_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"add device\" >mgmt\n"
++ " echo \"del device\" >mgmt\n";
++
++ return scnprintf(buf, PAGE_SIZE, help);
++}
++
++static int scst_dg_devs_mgmt_store_work_fn(struct scst_sysfs_work_item *w)
++{
++ struct scst_dev_group *dg;
++ char *cmd, *p, *pp, *dev_name;
++ int res;
++
++ TRACE_ENTRY();
++
++ cmd = w->buf;
++ dg = scst_lookup_dg_by_kobj(w->kobj);
++ WARN_ON(!dg);
++
++ p = strchr(cmd, '\n');
++ if (p)
++ *p = '\0';
++
++ res = -EINVAL;
++ pp = cmd;
++ p = scst_get_next_lexem(&pp);
++ if (strcasecmp(p, "add") == 0) {
++ dev_name = scst_get_next_lexem(&pp);
++ if (!*dev_name)
++ goto out;
++ res = scst_dg_dev_add(dg, dev_name);
++ } else if (strcasecmp(p, "del") == 0) {
++ dev_name = scst_get_next_lexem(&pp);
++ if (!*dev_name)
++ goto out;
++ res = scst_dg_dev_remove_by_name(dg, dev_name);
++ }
++out:
++ kobject_put(w->kobj);
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_dg_devs_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ char *cmd;
++ struct scst_sysfs_work_item *work;
++ int res;
++
++ TRACE_ENTRY();
++
++ res = -ENOMEM;
++ cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (!cmd)
++ goto out;
++
++ res = scst_alloc_sysfs_work(scst_dg_devs_mgmt_store_work_fn, false,
++ &work);
++ if (res)
++ goto out;
++
++ work->buf = cmd;
++ work->kobj = kobj;
++ kobject_get(kobj);
++ res = scst_sysfs_queue_wait_work(work);
++
++out:
++ if (res == 0)
++ res = count;
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_dg_devs_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_dg_devs_mgmt_show,
++ scst_dg_devs_mgmt_store);
++
++static const struct attribute *scst_dg_devs_attrs[] = {
++ &scst_dg_devs_mgmt.attr,
++ NULL,
++};
++
++/**
++ ** SCST sysfs device_groups/<dg>/target_groups/<tg>/<tgt> implementation.
++ **/
++
++static ssize_t scst_tg_tgt_rel_tgt_id_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ struct scst_tg_tgt *tg_tgt;
++
++ tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj);
++ return scnprintf(buf, PAGE_SIZE, "%u\n" SCST_SYSFS_KEY_MARK "\n",
++ tg_tgt->rel_tgt_id);
++}
++
++static ssize_t scst_tg_tgt_rel_tgt_id_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ struct scst_tg_tgt *tg_tgt;
++ unsigned long rel_tgt_id;
++ char ch[8];
++ int res;
++
++ TRACE_ENTRY();
++ tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj);
++ snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf);
++ res = strict_strtoul(ch, 0, &rel_tgt_id);
++ if (res)
++ goto out;
++ res = -EINVAL;
++ if (rel_tgt_id == 0 || rel_tgt_id > 0xffff)
++ goto out;
++ tg_tgt->rel_tgt_id = rel_tgt_id;
++ res = count;
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tg_tgt_rel_tgt_id =
++ __ATTR(rel_tgt_id, S_IRUGO | S_IWUSR, scst_tg_tgt_rel_tgt_id_show,
++ scst_tg_tgt_rel_tgt_id_store);
++
++static const struct attribute *scst_tg_tgt_attrs[] = {
++ &scst_tg_tgt_rel_tgt_id.attr,
++ NULL,
++};
++
++int scst_tg_tgt_sysfs_add(struct scst_target_group *tg,
++ struct scst_tg_tgt *tg_tgt)
++{
++ int res;
++
++ TRACE_ENTRY();
++ BUG_ON(!tg);
++ BUG_ON(!tg_tgt);
++ BUG_ON(!tg_tgt->name);
++ if (tg_tgt->tgt)
++ res = sysfs_create_link(&tg->kobj, &tg_tgt->tgt->tgt_kobj,
++ tg_tgt->name);
++ else {
++ res = kobject_add(&tg_tgt->kobj, &tg->kobj, "%s", tg_tgt->name);
++ if (res)
++ goto err;
++ res = sysfs_create_files(&tg_tgt->kobj, scst_tg_tgt_attrs);
++ if (res)
++ goto err;
++ }
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++err:
++ scst_tg_tgt_sysfs_del(tg, tg_tgt);
++ goto out;
++}
++
++void scst_tg_tgt_sysfs_del(struct scst_target_group *tg,
++ struct scst_tg_tgt *tg_tgt)
++{
++ TRACE_ENTRY();
++ if (tg_tgt->tgt)
++ sysfs_remove_link(&tg->kobj, tg_tgt->name);
++ else {
++ sysfs_remove_files(&tg_tgt->kobj, scst_tg_tgt_attrs);
++ kobject_del(&tg_tgt->kobj);
++ }
++ TRACE_EXIT();
++}
++
++/**
++ ** SCST sysfs device_groups/<dg>/target_groups/<tg> directory implementation.
++ **/
++
++static ssize_t scst_tg_group_id_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ struct scst_target_group *tg;
++
++ tg = container_of(kobj, struct scst_target_group, kobj);
++ return scnprintf(buf, PAGE_SIZE, "%u\n" SCST_SYSFS_KEY_MARK "\n",
++ tg->group_id);
++}
++
++static ssize_t scst_tg_group_id_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ struct scst_target_group *tg;
++ unsigned long group_id;
++ char ch[8];
++ int res;
++
++ TRACE_ENTRY();
++ tg = container_of(kobj, struct scst_target_group, kobj);
++ snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf);
++ res = strict_strtoul(ch, 0, &group_id);
++ if (res)
++ goto out;
++ res = -EINVAL;
++ if (group_id == 0 || group_id > 0xffff)
++ goto out;
++ tg->group_id = group_id;
++ res = count;
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tg_group_id =
++ __ATTR(group_id, S_IRUGO | S_IWUSR, scst_tg_group_id_show,
++ scst_tg_group_id_store);
++
++static ssize_t scst_tg_preferred_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ struct scst_target_group *tg;
++
++ tg = container_of(kobj, struct scst_target_group, kobj);
++ return scnprintf(buf, PAGE_SIZE, "%u\n%s",
++ tg->preferred, SCST_SYSFS_KEY_MARK "\n");
++}
++
++static ssize_t scst_tg_preferred_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ struct scst_target_group *tg;
++ unsigned long preferred;
++ char ch[8];
++ int res;
++
++ TRACE_ENTRY();
++ tg = container_of(kobj, struct scst_target_group, kobj);
++ snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf);
++ res = strict_strtoul(ch, 0, &preferred);
++ if (res)
++ goto out;
++ res = -EINVAL;
++ if (preferred != 0 && preferred != 1)
++ goto out;
++ tg->preferred = preferred;
++ res = count;
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tg_preferred =
++ __ATTR(preferred, S_IRUGO | S_IWUSR, scst_tg_preferred_show,
++ scst_tg_preferred_store);
++
++static struct { enum scst_tg_state s; const char *n; } scst_tg_state_names[] = {
++ { SCST_TG_STATE_OPTIMIZED, "active" },
++ { SCST_TG_STATE_NONOPTIMIZED, "nonoptimized" },
++ { SCST_TG_STATE_STANDBY, "standby" },
++ { SCST_TG_STATE_UNAVAILABLE, "unavailable" },
++ { SCST_TG_STATE_OFFLINE, "offline" },
++ { SCST_TG_STATE_TRANSITIONING, "transitioning" },
++};
++
++static ssize_t scst_tg_state_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ struct scst_target_group *tg;
++ int i;
++
++ tg = container_of(kobj, struct scst_target_group, kobj);
++ for (i = ARRAY_SIZE(scst_tg_state_names) - 1; i >= 0; i--)
++ if (scst_tg_state_names[i].s == tg->state)
++ break;
++
++ return scnprintf(buf, PAGE_SIZE, "%s\n" SCST_SYSFS_KEY_MARK "\n",
++ i >= 0 ? scst_tg_state_names[i].n : "???");
++}
++
++static int scst_tg_state_store_work_fn(struct scst_sysfs_work_item *w)
++{
++ struct scst_target_group *tg;
++ char *cmd, *p;
++ int i, res;
++
++ TRACE_ENTRY();
++
++ cmd = w->buf;
++ tg = container_of(w->kobj, struct scst_target_group, kobj);
++
++ p = strchr(cmd, '\n');
++ if (p)
++ *p = '\0';
++
++ for (i = ARRAY_SIZE(scst_tg_state_names) - 1; i >= 0; i--)
++ if (strcmp(scst_tg_state_names[i].n, cmd) == 0)
++ break;
++
++ res = -EINVAL;
++ if (i < 0)
++ goto out;
++ res = scst_tg_set_state(tg, scst_tg_state_names[i].s);
++out:
++ kobject_put(w->kobj);
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_tg_state_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ char *cmd;
++ struct scst_sysfs_work_item *work;
++ int res;
++
++ TRACE_ENTRY();
++
++ res = -ENOMEM;
++ cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (!cmd)
++ goto out;
++
++ res = scst_alloc_sysfs_work(scst_tg_state_store_work_fn, false,
++ &work);
++ if (res)
++ goto out;
++
++ work->buf = cmd;
++ work->kobj = kobj;
++ kobject_get(kobj);
++ res = scst_sysfs_queue_wait_work(work);
++
++out:
++ if (res == 0)
++ res = count;
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tg_state =
++ __ATTR(state, S_IRUGO | S_IWUSR, scst_tg_state_show,
++ scst_tg_state_store);
++
++static ssize_t scst_tg_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"add target\" >mgmt\n"
++ " echo \"del target\" >mgmt\n";
++
++ return scnprintf(buf, PAGE_SIZE, help);
++}
++
++static int scst_tg_mgmt_store_work_fn(struct scst_sysfs_work_item *w)
++{
++ struct scst_target_group *tg;
++ char *cmd, *p, *pp, *target_name;
++ int res;
++
++ TRACE_ENTRY();
++
++ cmd = w->buf;
++ tg = container_of(w->kobj, struct scst_target_group, kobj);
++
++ p = strchr(cmd, '\n');
++ if (p)
++ *p = '\0';
++
++ res = -EINVAL;
++ pp = cmd;
++ p = scst_get_next_lexem(&pp);
++ if (strcasecmp(p, "add") == 0) {
++ target_name = scst_get_next_lexem(&pp);
++ if (!*target_name)
++ goto out;
++ res = scst_tg_tgt_add(tg, target_name);
++ } else if (strcasecmp(p, "del") == 0) {
++ target_name = scst_get_next_lexem(&pp);
++ if (!*target_name)
++ goto out;
++ res = scst_tg_tgt_remove_by_name(tg, target_name);
++ }
++out:
++ kobject_put(w->kobj);
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_tg_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ char *cmd;
++ struct scst_sysfs_work_item *work;
++ int res;
++
++ TRACE_ENTRY();
++
++ res = -ENOMEM;
++ cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (!cmd)
++ goto out;
++
++ res = scst_alloc_sysfs_work(scst_tg_mgmt_store_work_fn, false,
++ &work);
++ if (res)
++ goto out;
++
++ work->buf = cmd;
++ work->kobj = kobj;
++ kobject_get(kobj);
++ res = scst_sysfs_queue_wait_work(work);
++
++out:
++ if (res == 0)
++ res = count;
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_tg_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_tg_mgmt_show,
++ scst_tg_mgmt_store);
++
++static const struct attribute *scst_tg_attrs[] = {
++ &scst_tg_mgmt.attr,
++ &scst_tg_group_id.attr,
++ &scst_tg_preferred.attr,
++ &scst_tg_state.attr,
++ NULL,
++};
++
++int scst_tg_sysfs_add(struct scst_dev_group *dg, struct scst_target_group *tg)
++{
++ int res;
++
++ TRACE_ENTRY();
++ res = kobject_add(&tg->kobj, dg->tg_kobj, "%s", tg->name);
++ if (res)
++ goto err;
++ res = sysfs_create_files(&tg->kobj, scst_tg_attrs);
++ if (res)
++ goto err;
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++err:
++ scst_tg_sysfs_del(tg);
++ goto out;
++}
++
++void scst_tg_sysfs_del(struct scst_target_group *tg)
++{
++ TRACE_ENTRY();
++ sysfs_remove_files(&tg->kobj, scst_tg_attrs);
++ kobject_del(&tg->kobj);
++ TRACE_EXIT();
++}
++
++/**
++ ** SCST sysfs device_groups/<dg>/target_groups directory implementation.
++ **/
++
++static ssize_t scst_dg_tgs_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"create group_name\" >mgmt\n"
++ " echo \"del group_name\" >mgmt\n";
++
++ return scnprintf(buf, PAGE_SIZE, help);
++}
++
++static int scst_dg_tgs_mgmt_store_work_fn(struct scst_sysfs_work_item *w)
++{
++ struct scst_dev_group *dg;
++ char *cmd, *p, *pp, *dev_name;
++ int res;
++
++ TRACE_ENTRY();
++
++ cmd = w->buf;
++ dg = scst_lookup_dg_by_kobj(w->kobj);
++ WARN_ON(!dg);
++
++ p = strchr(cmd, '\n');
++ if (p)
++ *p = '\0';
++
++ res = -EINVAL;
++ pp = cmd;
++ p = scst_get_next_lexem(&pp);
++ if (strcasecmp(p, "create") == 0 || strcasecmp(p, "add") == 0) {
++ dev_name = scst_get_next_lexem(&pp);
++ if (!*dev_name)
++ goto out;
++ res = scst_tg_add(dg, dev_name);
++ } else if (strcasecmp(p, "del") == 0) {
++ dev_name = scst_get_next_lexem(&pp);
++ if (!*dev_name)
++ goto out;
++ res = scst_tg_remove_by_name(dg, dev_name);
++ }
++out:
++ kobject_put(w->kobj);
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_dg_tgs_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ char *cmd;
++ struct scst_sysfs_work_item *work;
++ int res;
++
++ TRACE_ENTRY();
++
++ res = -ENOMEM;
++ cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (!cmd)
++ goto out;
++
++ res = scst_alloc_sysfs_work(scst_dg_tgs_mgmt_store_work_fn, false,
++ &work);
++ if (res)
++ goto out;
++
++ work->buf = cmd;
++ work->kobj = kobj;
++ kobject_get(kobj);
++ res = scst_sysfs_queue_wait_work(work);
++
++out:
++ if (res == 0)
++ res = count;
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_dg_tgs_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_dg_tgs_mgmt_show,
++ scst_dg_tgs_mgmt_store);
++
++static const struct attribute *scst_dg_tgs_attrs[] = {
++ &scst_dg_tgs_mgmt.attr,
++ NULL,
++};
++
++/**
++ ** SCST sysfs device_groups directory implementation.
++ **/
++
++int scst_dg_sysfs_add(struct kobject *parent, struct scst_dev_group *dg)
++{
++ int res;
++
++ dg->dev_kobj = NULL;
++ dg->tg_kobj = NULL;
++ res = kobject_add(&dg->kobj, parent, "%s", dg->name);
++ if (res)
++ goto err;
++ res = -EEXIST;
++ dg->dev_kobj = kobject_create_and_add("devices", &dg->kobj);
++ if (!dg->dev_kobj)
++ goto err;
++ res = sysfs_create_files(dg->dev_kobj, scst_dg_devs_attrs);
++ if (res)
++ goto err;
++ dg->tg_kobj = kobject_create_and_add("target_groups", &dg->kobj);
++ if (!dg->tg_kobj)
++ goto err;
++ res = sysfs_create_files(dg->tg_kobj, scst_dg_tgs_attrs);
++ if (res)
++ goto err;
++out:
++ return res;
++err:
++ scst_dg_sysfs_del(dg);
++ goto out;
++}
++
++void scst_dg_sysfs_del(struct scst_dev_group *dg)
++{
++ if (dg->tg_kobj) {
++ sysfs_remove_files(dg->tg_kobj, scst_dg_tgs_attrs);
++ kobject_del(dg->tg_kobj);
++ kobject_put(dg->tg_kobj);
++ dg->tg_kobj = NULL;
++ }
++ if (dg->dev_kobj) {
++ sysfs_remove_files(dg->dev_kobj, scst_dg_devs_attrs);
++ kobject_del(dg->dev_kobj);
++ kobject_put(dg->dev_kobj);
++ dg->dev_kobj = NULL;
++ }
++ kobject_del(&dg->kobj);
++}
++
++static ssize_t scst_device_groups_mgmt_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ static const char help[] =
++ "Usage: echo \"create group_name\" >mgmt\n"
++ " echo \"del group_name\" >mgmt\n";
++
++ return scnprintf(buf, PAGE_SIZE, help);
++}
++
++static ssize_t scst_device_groups_mgmt_store(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ const char *buf, size_t count)
++{
++ int res;
++ char *p, *pp, *input, *group_name;
++
++ TRACE_ENTRY();
++
++ input = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ pp = input;
++ p = strchr(input, '\n');
++ if (p)
++ *p = '\0';
++
++ res = -EINVAL;
++ p = scst_get_next_lexem(&pp);
++ if (strcasecmp(p, "create") == 0 || strcasecmp(p, "add") == 0) {
++ group_name = scst_get_next_lexem(&pp);
++ if (!*group_name)
++ goto out;
++ res = scst_dg_add(scst_device_groups_kobj, group_name);
++ } else if (strcasecmp(p, "del") == 0) {
++ group_name = scst_get_next_lexem(&pp);
++ if (!*group_name)
++ goto out;
++ res = scst_dg_remove(group_name);
++ }
++out:
++ kfree(input);
++ if (res == 0)
++ res = count;
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_device_groups_mgmt =
++ __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_device_groups_mgmt_show,
++ scst_device_groups_mgmt_store);
++
++static const struct attribute *scst_device_groups_attrs[] = {
++ &scst_device_groups_mgmt.attr,
++ NULL,
++};
++
++/**
++ ** SCST sysfs root directory implementation
++ **/
++
++static ssize_t scst_threads_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int count;
++
++ TRACE_ENTRY();
++
++ count = sprintf(buf, "%d\n%s", scst_main_cmd_threads.nr_threads,
++ (scst_main_cmd_threads.nr_threads != scst_threads) ?
++ SCST_SYSFS_KEY_MARK "\n" : "");
++
++ TRACE_EXIT();
++ return count;
++}
++
++static int scst_process_threads_store(int newtn)
++{
++ int res;
++ long oldtn, delta;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("newtn %d", newtn);
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res != 0)
++ goto out;
++
++ oldtn = scst_main_cmd_threads.nr_threads;
++
++ delta = newtn - oldtn;
++ if (delta < 0)
++ scst_del_threads(&scst_main_cmd_threads, -delta);
++ else {
++ res = scst_add_threads(&scst_main_cmd_threads, NULL, NULL, delta);
++ if (res != 0)
++ goto out_up;
++ }
++
++ PRINT_INFO("Changed cmd threads num: old %ld, new %d", oldtn, newtn);
++
++out_up:
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_threads_store_work_fn(struct scst_sysfs_work_item *work)
++{
++ return scst_process_threads_store(work->new_threads_num);
++}
++
++static ssize_t scst_threads_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ long newtn;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ res = strict_strtol(buf, 0, &newtn);
++ if (res != 0) {
++ PRINT_ERROR("strict_strtol() for %s failed: %d ", buf, res);
++ goto out;
++ }
++ if (newtn <= 0) {
++ PRINT_ERROR("Illegal threads num value %ld", newtn);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_alloc_sysfs_work(scst_threads_store_work_fn, false, &work);
++ if (res != 0)
++ goto out;
++
++ work->new_threads_num = newtn;
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_threads_attr =
++ __ATTR(threads, S_IRUGO | S_IWUSR, scst_threads_show,
++ scst_threads_store);
++
++static ssize_t scst_setup_id_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int count;
++
++ TRACE_ENTRY();
++
++ count = sprintf(buf, "0x%x\n%s\n", scst_setup_id,
++ (scst_setup_id == 0) ? "" : SCST_SYSFS_KEY_MARK);
++
++ TRACE_EXIT();
++ return count;
++}
++
++static ssize_t scst_setup_id_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ unsigned long val;
++
++ TRACE_ENTRY();
++
++ res = strict_strtoul(buf, 0, &val);
++ if (res != 0) {
++ PRINT_ERROR("strict_strtoul() for %s failed: %d ", buf, res);
++ goto out;
++ }
++
++ scst_setup_id = val;
++ PRINT_INFO("Changed scst_setup_id to %x", scst_setup_id);
++
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_setup_id_attr =
++ __ATTR(setup_id, S_IRUGO | S_IWUSR, scst_setup_id_show,
++ scst_setup_id_store);
++
++static ssize_t scst_max_tasklet_cmd_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int count;
++
++ TRACE_ENTRY();
++
++ count = sprintf(buf, "%d\n%s\n", scst_max_tasklet_cmd,
++ (scst_max_tasklet_cmd == SCST_DEF_MAX_TASKLET_CMD)
++ ? "" : SCST_SYSFS_KEY_MARK);
++
++ TRACE_EXIT();
++ return count;
++}
++
++static ssize_t scst_max_tasklet_cmd_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ unsigned long val;
++
++ TRACE_ENTRY();
++
++ res = strict_strtoul(buf, 0, &val);
++ if (res != 0) {
++ PRINT_ERROR("strict_strtoul() for %s failed: %d ", buf, res);
++ goto out;
++ }
++
++ scst_max_tasklet_cmd = val;
++ PRINT_INFO("Changed scst_max_tasklet_cmd to %d", scst_max_tasklet_cmd);
++
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_max_tasklet_cmd_attr =
++ __ATTR(max_tasklet_cmd, S_IRUGO | S_IWUSR, scst_max_tasklet_cmd_show,
++ scst_max_tasklet_cmd_store);
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++static ssize_t scst_main_trace_level_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ return scst_trace_level_show(scst_local_trace_tbl, trace_flag,
++ buf, NULL);
++}
++
++static ssize_t scst_main_trace_level_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = mutex_lock_interruptible(&scst_log_mutex);
++ if (res != 0)
++ goto out;
++
++ res = scst_write_trace(buf, count, &trace_flag,
++ SCST_DEFAULT_LOG_FLAGS, "scst", scst_local_trace_tbl);
++
++ mutex_unlock(&scst_log_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_main_trace_level_attr =
++ __ATTR(trace_level, S_IRUGO | S_IWUSR, scst_main_trace_level_show,
++ scst_main_trace_level_store);
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++static ssize_t scst_version_show(struct kobject *kobj,
++ struct kobj_attribute *attr,
++ char *buf)
++{
++ TRACE_ENTRY();
++
++ sprintf(buf, "%s\n", SCST_VERSION_STRING);
++
++#ifdef CONFIG_SCST_STRICT_SERIALIZING
++ strcat(buf, "STRICT_SERIALIZING\n");
++#endif
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ strcat(buf, "EXTRACHECKS\n");
++#endif
++
++#ifdef CONFIG_SCST_TRACING
++ strcat(buf, "TRACING\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++ strcat(buf, "DEBUG\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_TM
++ strcat(buf, "DEBUG_TM\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_RETRY
++ strcat(buf, "DEBUG_RETRY\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_OOM
++ strcat(buf, "DEBUG_OOM\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_SN
++ strcat(buf, "DEBUG_SN\n");
++#endif
++
++#ifdef CONFIG_SCST_USE_EXPECTED_VALUES
++ strcat(buf, "USE_EXPECTED_VALUES\n");
++#endif
++
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ strcat(buf, "TEST_IO_IN_SIRQ\n");
++#endif
++
++#ifdef CONFIG_SCST_STRICT_SECURITY
++ strcat(buf, "STRICT_SECURITY\n");
++#endif
++
++ TRACE_EXIT();
++ return strlen(buf);
++}
++
++static struct kobj_attribute scst_version_attr =
++ __ATTR(version, S_IRUGO, scst_version_show, NULL);
++
++static ssize_t scst_last_sysfs_mgmt_res_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ spin_lock(&sysfs_work_lock);
++ TRACE_DBG("active_sysfs_works %d", active_sysfs_works);
++ if (active_sysfs_works > 0)
++ res = -EAGAIN;
++ else
++ res = sprintf(buf, "%d\n", last_sysfs_work_res);
++ spin_unlock(&sysfs_work_lock);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute scst_last_sysfs_mgmt_res_attr =
++ __ATTR(last_sysfs_mgmt_res, S_IRUGO,
++ scst_last_sysfs_mgmt_res_show, NULL);
++
++static struct attribute *scst_sysfs_root_default_attrs[] = {
++ &scst_threads_attr.attr,
++ &scst_setup_id_attr.attr,
++ &scst_max_tasklet_cmd_attr.attr,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ &scst_main_trace_level_attr.attr,
++#endif
++ &scst_version_attr.attr,
++ &scst_last_sysfs_mgmt_res_attr.attr,
++ NULL,
++};
++
++static void scst_sysfs_root_release(struct kobject *kobj)
++{
++ complete_all(&scst_sysfs_root_release_completion);
++}
++
++static struct kobj_type scst_sysfs_root_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_sysfs_root_release,
++ .default_attrs = scst_sysfs_root_default_attrs,
++};
++
++/**
++ ** Sysfs user info
++ **/
++
++static DEFINE_MUTEX(scst_sysfs_user_info_mutex);
++
++/* All protected by scst_sysfs_user_info_mutex */
++static LIST_HEAD(scst_sysfs_user_info_list);
++static uint32_t scst_sysfs_info_cur_cookie;
++
++/* scst_sysfs_user_info_mutex supposed to be held */
++static struct scst_sysfs_user_info *scst_sysfs_user_find_info(uint32_t cookie)
++{
++ struct scst_sysfs_user_info *info, *res = NULL;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry(info, &scst_sysfs_user_info_list,
++ info_list_entry) {
++ if (info->info_cookie == cookie) {
++ res = info;
++ break;
++ }
++ }
++
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/**
++ * scst_sysfs_user_get_info() - get user_info
++ *
++ * Finds the user_info based on cookie and mark it as received the reply by
++ * setting for it flag info_being_executed.
++ *
++ * Returns found entry or NULL.
++ */
++struct scst_sysfs_user_info *scst_sysfs_user_get_info(uint32_t cookie)
++{
++ struct scst_sysfs_user_info *res = NULL;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_sysfs_user_info_mutex);
++
++ res = scst_sysfs_user_find_info(cookie);
++ if (res != NULL) {
++ if (!res->info_being_executed)
++ res->info_being_executed = 1;
++ }
++
++ mutex_unlock(&scst_sysfs_user_info_mutex);
++
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_sysfs_user_get_info);
++
++/**
++ ** Helper functionality to help target drivers and dev handlers support
++ ** sending events to user space and wait for their completion in a safe
++ ** manner. See samples how to use it in iscsi-scst or scst_user.
++ **/
++
++/**
++ * scst_sysfs_user_add_info() - create and add user_info in the global list
++ *
++ * Creates an info structure and adds it in the info_list.
++ * Returns 0 and out_info on success, error code otherwise.
++ */
++int scst_sysfs_user_add_info(struct scst_sysfs_user_info **out_info)
++{
++ int res = 0;
++ struct scst_sysfs_user_info *info;
++
++ TRACE_ENTRY();
++
++ info = kzalloc(sizeof(*info), GFP_KERNEL);
++ if (info == NULL) {
++ PRINT_ERROR("Unable to allocate sysfs user info (size %zd)",
++ sizeof(*info));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ mutex_lock(&scst_sysfs_user_info_mutex);
++
++ while ((info->info_cookie == 0) ||
++ (scst_sysfs_user_find_info(info->info_cookie) != NULL))
++ info->info_cookie = scst_sysfs_info_cur_cookie++;
++
++ init_completion(&info->info_completion);
++
++ list_add_tail(&info->info_list_entry, &scst_sysfs_user_info_list);
++ info->info_in_list = 1;
++
++ *out_info = info;
++
++ mutex_unlock(&scst_sysfs_user_info_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_sysfs_user_add_info);
++
++/**
++ * scst_sysfs_user_del_info - delete and frees user_info
++ */
++void scst_sysfs_user_del_info(struct scst_sysfs_user_info *info)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_sysfs_user_info_mutex);
++
++ if (info->info_in_list)
++ list_del(&info->info_list_entry);
++
++ mutex_unlock(&scst_sysfs_user_info_mutex);
++
++ kfree(info);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_sysfs_user_del_info);
++
++/*
++ * Returns true if the reply received and being processed by another part of
++ * the kernel, false otherwise. Also removes the user_info from the list to
++ * fix for the user space that it missed the timeout.
++ */
++static bool scst_sysfs_user_info_executing(struct scst_sysfs_user_info *info)
++{
++ bool res;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_sysfs_user_info_mutex);
++
++ res = info->info_being_executed;
++
++ if (info->info_in_list) {
++ list_del(&info->info_list_entry);
++ info->info_in_list = 0;
++ }
++
++ mutex_unlock(&scst_sysfs_user_info_mutex);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * scst_wait_info_completion() - wait an user space event's completion
++ *
++ * Waits for the info request been completed by user space at most timeout
++ * jiffies. If the reply received before timeout and being processed by
++ * another part of the kernel, i.e. scst_sysfs_user_info_executing()
++ * returned true, waits for it to complete indefinitely.
++ *
++ * Returns status of the request completion.
++ */
++int scst_wait_info_completion(struct scst_sysfs_user_info *info,
++ unsigned long timeout)
++{
++ int res, rc;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Waiting for info %p completion", info);
++
++ while (1) {
++ rc = wait_for_completion_interruptible_timeout(
++ &info->info_completion, timeout);
++ if (rc > 0) {
++ TRACE_DBG("Waiting for info %p finished with %d",
++ info, rc);
++ break;
++ } else if (rc == 0) {
++ if (!scst_sysfs_user_info_executing(info)) {
++ PRINT_ERROR("Timeout waiting for user "
++ "space event %p", info);
++ res = -EBUSY;
++ goto out;
++ } else {
++ /* Req is being executed in the kernel */
++ TRACE_DBG("Keep waiting for info %p completion",
++ info);
++ wait_for_completion(&info->info_completion);
++ break;
++ }
++ } else if (rc != -ERESTARTSYS) {
++ res = rc;
++ PRINT_ERROR("wait_for_completion() failed: %d",
++ res);
++ goto out;
++ } else {
++ TRACE_DBG("Waiting for info %p finished with %d, "
++ "retrying", info, rc);
++ }
++ }
++
++ TRACE_DBG("info %p, status %d", info, info->info_status);
++ res = info->info_status;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_wait_info_completion);
++
++static struct kobject scst_sysfs_root_kobj;
++
++int __init scst_sysfs_init(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ sysfs_work_thread = kthread_run(sysfs_work_thread_fn,
++ NULL, "scst_uid");
++ if (IS_ERR(sysfs_work_thread)) {
++ res = PTR_ERR(sysfs_work_thread);
++ PRINT_ERROR("kthread_run() for user interface thread "
++ "failed: %d", res);
++ sysfs_work_thread = NULL;
++ goto out;
++ }
++
++ res = kobject_init_and_add(&scst_sysfs_root_kobj,
++ &scst_sysfs_root_ktype, kernel_kobj, "%s", "scst_tgt");
++ if (res != 0)
++ goto sysfs_root_add_error;
++
++ scst_targets_kobj = kobject_create_and_add("targets",
++ &scst_sysfs_root_kobj);
++ if (scst_targets_kobj == NULL)
++ goto targets_kobj_error;
++
++ scst_devices_kobj = kobject_create_and_add("devices",
++ &scst_sysfs_root_kobj);
++ if (scst_devices_kobj == NULL)
++ goto devices_kobj_error;
++
++ res = scst_add_sgv_kobj(&scst_sysfs_root_kobj, "sgv");
++ if (res != 0)
++ goto sgv_kobj_error;
++
++ scst_handlers_kobj = kobject_create_and_add("handlers",
++ &scst_sysfs_root_kobj);
++ if (scst_handlers_kobj == NULL)
++ goto handlers_kobj_error;
++
++ scst_device_groups_kobj = kobject_create_and_add("device_groups",
++ &scst_sysfs_root_kobj);
++ if (scst_device_groups_kobj == NULL)
++ goto device_groups_kobj_error;
++
++ if (sysfs_create_files(scst_device_groups_kobj,
++ scst_device_groups_attrs))
++ goto device_groups_attrs_error;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++device_groups_attrs_error:
++ kobject_del(scst_device_groups_kobj);
++ kobject_put(scst_device_groups_kobj);
++
++device_groups_kobj_error:
++ kobject_del(scst_handlers_kobj);
++ kobject_put(scst_handlers_kobj);
++
++handlers_kobj_error:
++ scst_del_put_sgv_kobj();
++
++sgv_kobj_error:
++ kobject_del(scst_devices_kobj);
++ kobject_put(scst_devices_kobj);
++
++devices_kobj_error:
++ kobject_del(scst_targets_kobj);
++ kobject_put(scst_targets_kobj);
++
++targets_kobj_error:
++ kobject_del(&scst_sysfs_root_kobj);
++
++sysfs_root_add_error:
++ kobject_put(&scst_sysfs_root_kobj);
++
++ kthread_stop(sysfs_work_thread);
++
++ if (res == 0)
++ res = -EINVAL;
++
++ goto out;
++}
++
++void scst_sysfs_cleanup(void)
++{
++ TRACE_ENTRY();
++
++ PRINT_INFO("%s", "Exiting SCST sysfs hierarchy...");
++
++ scst_del_put_sgv_kobj();
++
++ kobject_del(scst_devices_kobj);
++ kobject_put(scst_devices_kobj);
++
++ kobject_del(scst_targets_kobj);
++ kobject_put(scst_targets_kobj);
++
++ kobject_del(scst_handlers_kobj);
++ kobject_put(scst_handlers_kobj);
++
++ sysfs_remove_files(scst_device_groups_kobj, scst_device_groups_attrs);
++
++ kobject_del(scst_device_groups_kobj);
++ kobject_put(scst_device_groups_kobj);
++
++ kobject_del(&scst_sysfs_root_kobj);
++ kobject_put(&scst_sysfs_root_kobj);
++
++ wait_for_completion(&scst_sysfs_root_release_completion);
++ /*
++ * There is a race, when in the release() schedule happens just after
++ * calling complete(), so if we exit and unload scst module immediately,
++ * there will be oops there. So let's give it a chance to quit
++ * gracefully. Unfortunately, current kobjects implementation
++ * doesn't allow better ways to handle it.
++ */
++ msleep(3000);
++
++ if (sysfs_work_thread)
++ kthread_stop(sysfs_work_thread);
++
++ PRINT_INFO("%s", "Exiting SCST sysfs hierarchy done");
++
++ TRACE_EXIT();
++ return;
++}
+diff -uprN orig/linux-3.2/include/scst/scst_debug.h linux-3.2/include/scst/scst_debug.h
+--- orig/linux-3.2/include/scst/scst_debug.h
++++ linux-3.2/include/scst/scst_debug.h
+@@ -0,0 +1,351 @@
++/*
++ * include/scst_debug.h
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * Contains macros 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_DEBUG_H
++#define __SCST_DEBUG_H
++
++#include <generated/autoconf.h> /* for CONFIG_* */
++
++#include <linux/bug.h> /* for WARN_ON_ONCE */
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++#define EXTRACHECKS_BUG_ON(a) BUG_ON(a)
++#define EXTRACHECKS_WARN_ON(a) WARN_ON(a)
++#define EXTRACHECKS_WARN_ON_ONCE(a) WARN_ON_ONCE(a)
++#else
++#define EXTRACHECKS_BUG_ON(a) do { } while (0)
++#define EXTRACHECKS_WARN_ON(a) do { } while (0)
++#define EXTRACHECKS_WARN_ON_ONCE(a) do { } while (0)
++#endif
++
++#define TRACE_NULL 0x00000000
++#define TRACE_DEBUG 0x00000001
++#define TRACE_FUNCTION 0x00000002
++#define TRACE_LINE 0x00000004
++#define TRACE_PID 0x00000008
++#ifndef GENERATING_UPSTREAM_PATCH
++#define TRACE_ENTRYEXIT 0x00000010
++#endif
++#define TRACE_BUFF 0x00000020
++#define TRACE_MEMORY 0x00000040
++#define TRACE_SG_OP 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_FLOW_CONTROL 0x00004000 /* flow control in action */
++#define TRACE_PRES 0x00008000
++#define TRACE_ALL 0xffffffff
++/* Flags 0xXXXX0000 are local for users */
++
++#define TRACE_MINOR_AND_MGMT_DBG (TRACE_MINOR|TRACE_MGMT_DEBUG)
++
++#ifndef KERN_CONT
++#define KERN_CONT ""
++#endif
++
++/*
++ * Note: in the next two printk() statements the KERN_CONT macro is only
++ * present to suppress a checkpatch warning (KERN_CONT is defined as "").
++ */
++#define PRINT(log_flag, format, args...) \
++ printk(log_flag format "\n", ## args)
++#define PRINTN(log_flag, format, args...) \
++ printk(log_flag format, ## args)
++
++#ifdef LOG_PREFIX
++#define __LOG_PREFIX LOG_PREFIX
++#else
++#define __LOG_PREFIX NULL
++#endif
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++#ifndef CONFIG_SCST_DEBUG
++#define ___unlikely(a) (a)
++#else
++#define ___unlikely(a) unlikely(a)
++#endif
++
++/*
++ * We don't print prefix for debug traces to not put additional pressure
++ * on the logging system in case of a lot of logging.
++ */
++
++int debug_print_prefix(unsigned long trace_flag,
++ const char *prefix, const char *func, int line);
++void debug_print_buffer(const void *data, int len);
++const char *debug_transport_id_to_initiator_name(const uint8_t *transport_id);
++
++#define TRACING_MINOR() (trace_flag & TRACE_MINOR)
++
++#define TRACE(trace, format, args...) \
++do { \
++ if (___unlikely(trace_flag & (trace))) { \
++ debug_print_prefix(trace_flag, __LOG_PREFIX, \
++ __func__, __LINE__); \
++ PRINT(KERN_CONT, format, args); \
++ } \
++} while (0)
++
++#ifdef CONFIG_SCST_DEBUG
++
++#define PRINT_BUFFER(message, buff, len) \
++do { \
++ PRINT(KERN_INFO, "%s:%s:", __func__, message); \
++ debug_print_buffer(buff, len); \
++} while (0)
++
++#else
++
++#define PRINT_BUFFER(message, buff, len) \
++do { \
++ PRINT(KERN_INFO, "%s:", message); \
++ debug_print_buffer(buff, len); \
++} while (0)
++
++#endif
++
++#define PRINT_BUFF_FLAG(flag, message, buff, len) \
++do { \
++ if (___unlikely(trace_flag & (flag))) { \
++ debug_print_prefix(trace_flag, NULL, __func__, __LINE__);\
++ PRINT(KERN_CONT, "%s:", message); \
++ debug_print_buffer(buff, len); \
++ } \
++} while (0)
++
++#else /* CONFIG_SCST_DEBUG || CONFIG_SCST_TRACING */
++
++#define TRACING_MINOR() (false)
++
++#define TRACE(trace, args...) do {} while (0)
++#define PRINT_BUFFER(message, buff, len) do {} while (0)
++#define PRINT_BUFF_FLAG(flag, message, buff, len) do {} while (0)
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++#ifdef CONFIG_SCST_DEBUG
++
++#define TRACE_DBG_FLAG(trace, format, args...) \
++do { \
++ if (trace_flag & (trace)) { \
++ debug_print_prefix(trace_flag, NULL, __func__, __LINE__);\
++ PRINT(KERN_CONT, format, args); \
++ } \
++} while (0)
++
++#define TRACE_MEM(args...) TRACE_DBG_FLAG(TRACE_MEMORY, args)
++#define TRACE_SG(args...) TRACE_DBG_FLAG(TRACE_SG_OP, args)
++#define TRACE_DBG(args...) TRACE_DBG_FLAG(TRACE_DEBUG, args)
++#define TRACE_DBG_SPECIAL(args...) TRACE_DBG_FLAG(TRACE_DEBUG|TRACE_SPECIAL, args)
++#define TRACE_MGMT_DBG(args...) TRACE_DBG_FLAG(TRACE_MGMT_DEBUG, args)
++#define TRACE_MGMT_DBG_SPECIAL(args...) \
++ TRACE_DBG_FLAG(TRACE_MGMT_DEBUG|TRACE_SPECIAL, args)
++#define TRACE_PR(args...) TRACE_DBG_FLAG(TRACE_PRES, args)
++
++#define TRACE_BUFFER(message, buff, len) \
++do { \
++ if (trace_flag & TRACE_BUFF) { \
++ debug_print_prefix(trace_flag, NULL, __func__, __LINE__);\
++ PRINT(KERN_CONT, "%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, NULL, __func__, __LINE__);\
++ PRINT(KERN_CONT, "%s:", message); \
++ debug_print_buffer(buff, len); \
++ } \
++} while (0)
++
++#define PRINT_LOG_FLAG(log_flag, format, args...) \
++do { \
++ debug_print_prefix(trace_flag, __LOG_PREFIX, __func__, __LINE__);\
++ PRINT(KERN_CONT, format, args); \
++} while (0)
++
++#define PRINT_WARNING(format, args...) \
++do { \
++ debug_print_prefix(trace_flag, __LOG_PREFIX, __func__, __LINE__);\
++ PRINT(KERN_CONT, "***WARNING***: " format, args); \
++} while (0)
++
++#define PRINT_ERROR(format, args...) \
++do { \
++ debug_print_prefix(trace_flag, __LOG_PREFIX, __func__, __LINE__);\
++ PRINT(KERN_CONT, "***ERROR***: " format, args); \
++} while (0)
++
++#define PRINT_CRIT_ERROR(format, args...) \
++do { \
++ debug_print_prefix(trace_flag, __LOG_PREFIX, __func__, __LINE__);\
++ PRINT(KERN_CONT, "***CRITICAL ERROR***: " format, args); \
++} while (0)
++
++#define PRINT_INFO(format, args...) \
++do { \
++ debug_print_prefix(trace_flag, __LOG_PREFIX, __func__, __LINE__);\
++ PRINT(KERN_CONT, format, args); \
++} while (0)
++
++#ifndef GENERATING_UPSTREAM_PATCH
++#define TRACE_ENTRY() \
++do { \
++ if (trace_flag & TRACE_ENTRYEXIT) { \
++ if (trace_flag & TRACE_PID) { \
++ PRINT(KERN_INFO, "[%d]: ENTRY %s", current->pid, \
++ __func__); \
++ } \
++ else { \
++ PRINT(KERN_INFO, "ENTRY %s", __func__); \
++ } \
++ } \
++} while (0)
++
++#define TRACE_EXIT() \
++do { \
++ if (trace_flag & TRACE_ENTRYEXIT) { \
++ if (trace_flag & TRACE_PID) { \
++ PRINT(KERN_INFO, "[%d]: EXIT %s", current->pid, \
++ __func__); \
++ } \
++ else { \
++ PRINT(KERN_INFO, "EXIT %s", __func__); \
++ } \
++ } \
++} while (0)
++
++#define TRACE_EXIT_RES(res) \
++do { \
++ if (trace_flag & TRACE_ENTRYEXIT) { \
++ if (trace_flag & TRACE_PID) { \
++ PRINT(KERN_INFO, "[%d]: EXIT %s: %ld", current->pid, \
++ __func__, (long)(res)); \
++ } \
++ else { \
++ PRINT(KERN_INFO, "EXIT %s: %ld", \
++ __func__, (long)(res)); \
++ } \
++ } \
++} while (0)
++
++#define TRACE_EXIT_HRES(res) \
++do { \
++ if (trace_flag & TRACE_ENTRYEXIT) { \
++ if (trace_flag & TRACE_PID) { \
++ PRINT(KERN_INFO, "[%d]: EXIT %s: 0x%lx", current->pid, \
++ __func__, (long)(res)); \
++ } \
++ else { \
++ PRINT(KERN_INFO, "EXIT %s: %lx", \
++ __func__, (long)(res)); \
++ } \
++ } \
++} while (0)
++#endif
++
++#else /* CONFIG_SCST_DEBUG */
++
++#define TRACE_MEM(format, args...) do {} while (0)
++#define TRACE_SG(format, args...) do {} while (0)
++#define TRACE_DBG(format, args...) do {} while (0)
++#define TRACE_DBG_FLAG(format, args...) do {} while (0)
++#define TRACE_DBG_SPECIAL(format, args...) do {} while (0)
++#define TRACE_MGMT_DBG(format, args...) do {} while (0)
++#define TRACE_MGMT_DBG_SPECIAL(format, args...) do {} while (0)
++#define TRACE_PR(format, args...) do {} while (0)
++#define TRACE_BUFFER(message, buff, len) do {} while (0)
++#define TRACE_BUFF_FLAG(flag, message, buff, len) do {} while (0)
++
++#ifndef GENERATING_UPSTREAM_PATCH
++#define TRACE_ENTRY() do {} while (0)
++#define TRACE_EXIT() do {} while (0)
++#define TRACE_EXIT_RES(res) do {} while (0)
++#define TRACE_EXIT_HRES(res) do {} while (0)
++#endif
++
++#ifdef LOG_PREFIX
++
++#define PRINT_INFO(format, args...) \
++do { \
++ PRINT(KERN_INFO, "%s: " format, LOG_PREFIX, args); \
++} while (0)
++
++#define PRINT_WARNING(format, args...) \
++do { \
++ PRINT(KERN_INFO, "%s: ***WARNING***: " \
++ format, LOG_PREFIX, args); \
++} while (0)
++
++#define PRINT_ERROR(format, args...) \
++do { \
++ PRINT(KERN_INFO, "%s: ***ERROR***: " \
++ format, LOG_PREFIX, args); \
++} while (0)
++
++#define PRINT_CRIT_ERROR(format, args...) \
++do { \
++ PRINT(KERN_INFO, "%s: ***CRITICAL ERROR***: " \
++ format, LOG_PREFIX, args); \
++} while (0)
++
++#else
++
++#define PRINT_INFO(format, args...) \
++do { \
++ PRINT(KERN_INFO, format, args); \
++} while (0)
++
++#define PRINT_WARNING(format, args...) \
++do { \
++ PRINT(KERN_INFO, "***WARNING***: " \
++ format, args); \
++} while (0)
++
++#define PRINT_ERROR(format, args...) \
++do { \
++ PRINT(KERN_ERR, "***ERROR***: " \
++ format, args); \
++} while (0)
++
++#define PRINT_CRIT_ERROR(format, args...) \
++do { \
++ PRINT(KERN_CRIT, "***CRITICAL ERROR***: " \
++ format, args); \
++} while (0)
++
++#endif /* LOG_PREFIX */
++
++#endif /* CONFIG_SCST_DEBUG */
++
++#if defined(CONFIG_SCST_DEBUG) && defined(CONFIG_DEBUG_SLAB)
++#define SCST_SLAB_FLAGS (SLAB_RED_ZONE | SLAB_POISON)
++#else
++#define SCST_SLAB_FLAGS 0L
++#endif
++
++#endif /* __SCST_DEBUG_H */
+diff -uprN orig/linux-3.2/drivers/scst/scst_debug.c linux-3.2/drivers/scst/scst_debug.c
+--- orig/linux-3.2/drivers/scst/scst_debug.c
++++ linux-3.2/drivers/scst/scst_debug.c
+@@ -0,0 +1,228 @@
++/*
++ * scst_debug.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * 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 <linux/version.h>
++
++#include <linux/export.h>
++
++#include <scst/scst.h>
++#include <scst/scst_debug.h>
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++#define TRACE_BUF_SIZE 512
++
++static char trace_buf[TRACE_BUF_SIZE];
++static DEFINE_SPINLOCK(trace_buf_lock);
++
++static inline int get_current_tid(void)
++{
++ /* Code should be the same as in sys_gettid() */
++ if (in_interrupt()) {
++ /*
++ * Unfortunately, task_pid_vnr() isn't IRQ-safe, so otherwise
++ * it can oops. ToDo.
++ */
++ return 0;
++ }
++ return task_pid_vnr(current);
++}
++
++/**
++ * debug_print_prefix() - print debug prefix for a log line
++ *
++ * Prints, if requested by trace_flag, debug prefix for each log line
++ */
++int debug_print_prefix(unsigned long trace_flag,
++ const char *prefix, const char *func, int line)
++{
++ int i = 0;
++ unsigned long flags;
++ int pid = get_current_tid();
++
++ spin_lock_irqsave(&trace_buf_lock, flags);
++
++ trace_buf[0] = '\0';
++
++ if (trace_flag & TRACE_PID)
++ i += snprintf(&trace_buf[i], TRACE_BUF_SIZE, "[%d]: ", pid);
++ if (prefix != NULL)
++ i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s: ",
++ prefix);
++ 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);
++
++ PRINTN(KERN_INFO, "%s", trace_buf);
++
++ spin_unlock_irqrestore(&trace_buf_lock, flags);
++
++ return i;
++}
++EXPORT_SYMBOL(debug_print_prefix);
++
++/**
++ * debug_print_buffer() - print a buffer
++ *
++ * Prints in the log data from the buffer
++ */
++void debug_print_buffer(const void *data, int len)
++{
++ int z, z1, i;
++ const unsigned char *buf = (const unsigned char *) data;
++ unsigned long flags;
++
++ if (buf == NULL)
++ return;
++
++ spin_lock_irqsave(&trace_buf_lock, flags);
++
++ PRINT(KERN_INFO, " (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(KERN_INFO, "%s", trace_buf);
++ i = 0;
++ }
++ 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';
++
++ PRINT(KERN_INFO, "%s", trace_buf);
++
++ spin_unlock_irqrestore(&trace_buf_lock, flags);
++ return;
++}
++EXPORT_SYMBOL(debug_print_buffer);
++
++/*
++ * This function converts transport_id in a string form into internal per-CPU
++ * static buffer. This buffer isn't anyhow protected, because it's acceptable
++ * if the name corrupted in the debug logs because of the race for this buffer.
++ *
++ * Note! You can't call this function 2 or more times in a single logging
++ * (printk) statement, because then each new call of this function will override
++ * data written in this buffer by the previous call. You should instead split
++ * that logging statement on smaller statements each calling
++ * debug_transport_id_to_initiator_name() only once.
++ */
++const char *debug_transport_id_to_initiator_name(const uint8_t *transport_id)
++{
++ /*
++ * No external protection, because it's acceptable if the name
++ * corrupted in the debug logs because of the race for this
++ * buffer.
++ */
++#define SIZEOF_NAME_BUF 256
++ static char name_bufs[NR_CPUS][SIZEOF_NAME_BUF];
++ char *name_buf;
++ unsigned long flags;
++
++ BUG_ON(transport_id == NULL); /* better to catch it not under lock */
++
++ spin_lock_irqsave(&trace_buf_lock, flags);
++
++ name_buf = name_bufs[smp_processor_id()];
++
++ /*
++ * To prevent external racing with us users from accidentally
++ * missing their NULL terminator.
++ */
++ memset(name_buf, 0, SIZEOF_NAME_BUF);
++ smp_mb();
++
++ switch (transport_id[0] & 0x0f) {
++ case SCSI_TRANSPORTID_PROTOCOLID_ISCSI:
++ scnprintf(name_buf, SIZEOF_NAME_BUF, "%s",
++ &transport_id[4]);
++ break;
++ case SCSI_TRANSPORTID_PROTOCOLID_FCP2:
++ scnprintf(name_buf, SIZEOF_NAME_BUF,
++ "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
++ transport_id[8], transport_id[9],
++ transport_id[10], transport_id[11],
++ transport_id[12], transport_id[13],
++ transport_id[14], transport_id[15]);
++ break;
++ case SCSI_TRANSPORTID_PROTOCOLID_SPI5:
++ scnprintf(name_buf, SIZEOF_NAME_BUF,
++ "%x:%x", be16_to_cpu((__force __be16)transport_id[2]),
++ be16_to_cpu((__force __be16)transport_id[6]));
++ break;
++ case SCSI_TRANSPORTID_PROTOCOLID_SRP:
++ scnprintf(name_buf, SIZEOF_NAME_BUF,
++ "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x"
++ ":%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
++ transport_id[8], transport_id[9],
++ transport_id[10], transport_id[11],
++ transport_id[12], transport_id[13],
++ transport_id[14], transport_id[15],
++ transport_id[16], transport_id[17],
++ transport_id[18], transport_id[19],
++ transport_id[20], transport_id[21],
++ transport_id[22], transport_id[23]);
++ break;
++ case SCSI_TRANSPORTID_PROTOCOLID_SAS:
++ scnprintf(name_buf, SIZEOF_NAME_BUF,
++ "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
++ transport_id[4], transport_id[5],
++ transport_id[6], transport_id[7],
++ transport_id[8], transport_id[9],
++ transport_id[10], transport_id[11]);
++ break;
++ default:
++ scnprintf(name_buf, SIZEOF_NAME_BUF,
++ "(Not known protocol ID %x)", transport_id[0] & 0x0f);
++ break;
++ }
++
++ spin_unlock_irqrestore(&trace_buf_lock, flags);
++
++ return name_buf;
++#undef SIZEOF_NAME_BUF
++}
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
+diff -uprN orig/linux-3.2/include/scst/scst_sgv.h linux-3.2/include/scst/scst_sgv.h
+--- orig/linux-3.2/include/scst/scst_sgv.h
++++ linux-3.2/include/scst/scst_sgv.h
+@@ -0,0 +1,98 @@
++/*
++ * include/scst_sgv.h
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * Include file for SCST SGV cache.
++ *
++ * 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_SGV_H
++#define __SCST_SGV_H
++
++/** SGV pool routines and flag bits **/
++
++/* Set if the allocated object must be not from the cache */
++#define SGV_POOL_ALLOC_NO_CACHED 1
++
++/* Set if there should not be any memory allocations on a cache miss */
++#define SGV_POOL_NO_ALLOC_ON_CACHE_MISS 2
++
++/* Set an object should be returned even if it doesn't have SG vector built */
++#define SGV_POOL_RETURN_OBJ_ON_ALLOC_FAIL 4
++
++/*
++ * Set if the allocated object must be a new one, i.e. from the cache,
++ * but not cached
++ */
++#define SGV_POOL_ALLOC_GET_NEW 8
++
++struct sgv_pool_obj;
++struct sgv_pool;
++
++/*
++ * Structure to keep a memory limit for an SCST object
++ */
++struct scst_mem_lim {
++ /* How much memory allocated under this object */
++ atomic_t alloced_pages;
++
++ /*
++ * How much memory allowed to allocated under this object. Put here
++ * mostly to save a possible cache miss accessing scst_max_dev_cmd_mem.
++ */
++ int max_allowed_pages;
++};
++
++/* Types of clustering */
++enum sgv_clustering_types {
++ /* No clustering performed */
++ sgv_no_clustering = 0,
++
++ /*
++ * A page will only be merged with the latest previously allocated
++ * page, so the order of pages in the SG will be preserved.
++ */
++ sgv_tail_clustering,
++
++ /*
++ * Free merging of pages at any place in the SG is allowed. This mode
++ * usually provides the best merging rate.
++ */
++ sgv_full_clustering,
++};
++
++struct sgv_pool *sgv_pool_create(const char *name,
++ enum sgv_clustering_types clustered, int single_alloc_pages,
++ bool shared, int purge_interval);
++void sgv_pool_del(struct sgv_pool *pool);
++
++void sgv_pool_get(struct sgv_pool *pool);
++void sgv_pool_put(struct sgv_pool *pool);
++
++void sgv_pool_flush(struct sgv_pool *pool);
++
++void sgv_pool_set_allocator(struct sgv_pool *pool,
++ struct page *(*alloc_pages_fn)(struct scatterlist *, gfp_t, void *),
++ void (*free_pages_fn)(struct scatterlist *, int, void *));
++
++struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size,
++ gfp_t gfp_mask, int flags, int *count,
++ struct sgv_pool_obj **sgv, struct scst_mem_lim *mem_lim, void *priv);
++void sgv_pool_free(struct sgv_pool_obj *sgv, struct scst_mem_lim *mem_lim);
++
++void *sgv_get_priv(struct sgv_pool_obj *sgv);
++
++void scst_init_mem_lim(struct scst_mem_lim *mem_lim);
++
++#endif /* __SCST_SGV_H */
+diff -uprN orig/linux-3.2/drivers/scst/scst_mem.h linux-3.2/drivers/scst/scst_mem.h
+--- orig/linux-3.2/drivers/scst/scst_mem.h
++++ linux-3.2/drivers/scst/scst_mem.h
+@@ -0,0 +1,142 @@
++/*
++ * scst_mem.h
++ *
++ * Copyright (C) 2006 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/scatterlist.h>
++#include <linux/workqueue.h>
++
++#define SGV_POOL_ELEMENTS 11
++
++/*
++ * sg_num is indexed by the page number, pg_count is indexed by the sg number.
++ * Made in one entry to simplify the code (eg all sizeof(*) parts) and save
++ * some CPU cache for non-clustered case.
++ */
++struct trans_tbl_ent {
++ unsigned short sg_num;
++ unsigned short pg_count;
++};
++
++/*
++ * SGV pool object
++ */
++struct sgv_pool_obj {
++ int cache_num;
++ int pages;
++
++ /* jiffies, protected by sgv_pool_lock */
++ unsigned long time_stamp;
++
++ struct list_head recycling_list_entry;
++ struct list_head sorted_recycling_list_entry;
++
++ struct sgv_pool *owner_pool;
++ int orig_sg;
++ int orig_length;
++ int sg_count;
++ void *allocator_priv;
++ struct trans_tbl_ent *trans_tbl;
++ struct scatterlist *sg_entries;
++ struct scatterlist sg_entries_data[0];
++};
++
++/*
++ * SGV pool statistics accounting structure
++ */
++struct sgv_pool_cache_acc {
++ atomic_t total_alloc, hit_alloc;
++ atomic_t merged;
++};
++
++/*
++ * SGV pool allocation functions
++ */
++struct sgv_pool_alloc_fns {
++ struct page *(*alloc_pages_fn)(struct scatterlist *sg, gfp_t gfp_mask,
++ void *priv);
++ void (*free_pages_fn)(struct scatterlist *sg, int sg_count,
++ void *priv);
++};
++
++/*
++ * SGV pool
++ */
++struct sgv_pool {
++ enum sgv_clustering_types clustering_type;
++ int single_alloc_pages;
++ int max_cached_pages;
++
++ struct sgv_pool_alloc_fns alloc_fns;
++
++ /* <=4K, <=8, <=16, <=32, <=64, <=128, <=256, <=512, <=1024, <=2048 */
++ struct kmem_cache *caches[SGV_POOL_ELEMENTS];
++
++ spinlock_t sgv_pool_lock; /* outer lock for sgv_pools_lock! */
++
++ int purge_interval;
++
++ /* Protected by sgv_pool_lock, if necessary */
++ unsigned int purge_work_scheduled:1;
++
++ /* Protected by sgv_pool_lock */
++ struct list_head sorted_recycling_list;
++
++ int inactive_cached_pages; /* protected by sgv_pool_lock */
++
++ /* Protected by sgv_pool_lock */
++ struct list_head recycling_lists[SGV_POOL_ELEMENTS];
++
++ int cached_pages, cached_entries; /* protected by sgv_pool_lock */
++
++ struct sgv_pool_cache_acc cache_acc[SGV_POOL_ELEMENTS];
++
++ struct delayed_work sgv_purge_work;
++
++ struct list_head sgv_active_pools_list_entry;
++
++ atomic_t big_alloc, big_pages, big_merged;
++ atomic_t other_alloc, other_pages, other_merged;
++
++ atomic_t sgv_pool_ref;
++
++ int max_caches;
++
++ /* SCST_MAX_NAME + few more bytes to match scst_user expectations */
++ char cache_names[SGV_POOL_ELEMENTS][SCST_MAX_NAME + 10];
++ char name[SCST_MAX_NAME + 10];
++
++ struct mm_struct *owner_mm;
++
++ struct list_head sgv_pools_list_entry;
++
++ struct kobject sgv_kobj;
++
++ /* sysfs release completion */
++ struct completion *sgv_kobj_release_cmpl;
++};
++
++static inline struct scatterlist *sgv_pool_sg(struct sgv_pool_obj *obj)
++{
++ return obj->sg_entries;
++}
++
++int scst_sgv_pools_init(unsigned long mem_hwmark, unsigned long mem_lwmark);
++void scst_sgv_pools_deinit(void);
++
++void scst_sgv_pool_use_norm(struct scst_tgt_dev *tgt_dev);
++void scst_sgv_pool_use_norm_clust(struct scst_tgt_dev *tgt_dev);
++void scst_sgv_pool_use_dma(struct scst_tgt_dev *tgt_dev);
+diff -uprN orig/linux-3.2/drivers/scst/scst_mem.c linux-3.2/drivers/scst/scst_mem.c
+--- orig/linux-3.2/drivers/scst/scst_mem.c
++++ linux-3.2/drivers/scst/scst_mem.c
+@@ -0,0 +1,2002 @@
++/*
++ * scst_mem.c
++ *
++ * Copyright (C) 2006 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/mm.h>
++#include <linux/unistd.h>
++#include <linux/string.h>
++
++#include <scst/scst.h>
++#include "scst_priv.h"
++#include "scst_mem.h"
++
++#define SGV_DEFAULT_PURGE_INTERVAL (60 * HZ)
++#define SGV_MIN_SHRINK_INTERVAL (1 * HZ)
++
++/* Max pages freed from a pool per shrinking iteration */
++#define MAX_PAGES_PER_POOL 50
++
++static struct sgv_pool *sgv_norm_clust_pool, *sgv_norm_pool, *sgv_dma_pool;
++
++static atomic_t sgv_pages_total = ATOMIC_INIT(0);
++
++/* Both read-only */
++static int sgv_hi_wmk;
++static int sgv_lo_wmk;
++
++static int sgv_max_local_pages, sgv_max_trans_pages;
++
++static DEFINE_SPINLOCK(sgv_pools_lock); /* inner lock for sgv_pool_lock! */
++static DEFINE_MUTEX(sgv_pools_mutex);
++
++/* Both protected by sgv_pools_lock */
++static struct sgv_pool *sgv_cur_purge_pool;
++static LIST_HEAD(sgv_active_pools_list);
++
++static atomic_t sgv_releases_on_hiwmk = ATOMIC_INIT(0);
++static atomic_t sgv_releases_on_hiwmk_failed = ATOMIC_INIT(0);
++
++static atomic_t sgv_other_total_alloc = ATOMIC_INIT(0);
++
++static struct shrinker sgv_shrinker;
++
++/*
++ * Protected by sgv_pools_mutex AND sgv_pools_lock for writes,
++ * either one for reads.
++ */
++static LIST_HEAD(sgv_pools_list);
++
++static struct kobject *scst_sgv_kobj;
++static int scst_sgv_sysfs_create(struct sgv_pool *pool);
++static void scst_sgv_sysfs_del(struct sgv_pool *pool);
++
++static inline bool sgv_pool_clustered(const struct sgv_pool *pool)
++{
++ return pool->clustering_type != sgv_no_clustering;
++}
++
++void scst_sgv_pool_use_norm(struct scst_tgt_dev *tgt_dev)
++{
++ tgt_dev->gfp_mask = __GFP_NOWARN;
++ tgt_dev->pool = sgv_norm_pool;
++ clear_bit(SCST_TGT_DEV_CLUST_POOL, &tgt_dev->tgt_dev_flags);
++}
++
++void scst_sgv_pool_use_norm_clust(struct scst_tgt_dev *tgt_dev)
++{
++ TRACE_MEM("%s", "Use clustering");
++ tgt_dev->gfp_mask = __GFP_NOWARN;
++ tgt_dev->pool = sgv_norm_clust_pool;
++ set_bit(SCST_TGT_DEV_CLUST_POOL, &tgt_dev->tgt_dev_flags);
++}
++
++void scst_sgv_pool_use_dma(struct scst_tgt_dev *tgt_dev)
++{
++ TRACE_MEM("%s", "Use ISA DMA memory");
++ tgt_dev->gfp_mask = __GFP_NOWARN | GFP_DMA;
++ tgt_dev->pool = sgv_dma_pool;
++ clear_bit(SCST_TGT_DEV_CLUST_POOL, &tgt_dev->tgt_dev_flags);
++}
++
++/* Must be no locks */
++static void sgv_dtor_and_free(struct sgv_pool_obj *obj)
++{
++ struct sgv_pool *pool = obj->owner_pool;
++
++ TRACE_MEM("Destroying sgv obj %p", obj);
++
++ if (obj->sg_count != 0) {
++ pool->alloc_fns.free_pages_fn(obj->sg_entries,
++ obj->sg_count, obj->allocator_priv);
++ }
++ if (obj->sg_entries != obj->sg_entries_data) {
++ if (obj->trans_tbl !=
++ (struct trans_tbl_ent *)obj->sg_entries_data) {
++ /* kfree() handles NULL parameter */
++ kfree(obj->trans_tbl);
++ obj->trans_tbl = NULL;
++ }
++ kfree(obj->sg_entries);
++ }
++
++ kmem_cache_free(pool->caches[obj->cache_num], obj);
++ return;
++}
++
++/* Might be called under sgv_pool_lock */
++static inline void sgv_del_from_active(struct sgv_pool *pool)
++{
++ struct list_head *next;
++
++ TRACE_MEM("Deleting sgv pool %p from the active list", pool);
++
++ spin_lock_bh(&sgv_pools_lock);
++
++ next = pool->sgv_active_pools_list_entry.next;
++ list_del(&pool->sgv_active_pools_list_entry);
++
++ if (sgv_cur_purge_pool == pool) {
++ TRACE_MEM("Sgv pool %p is sgv cur purge pool", pool);
++
++ if (next == &sgv_active_pools_list)
++ next = next->next;
++
++ if (next == &sgv_active_pools_list) {
++ sgv_cur_purge_pool = NULL;
++ TRACE_MEM("%s", "Sgv active list now empty");
++ } else {
++ sgv_cur_purge_pool = list_entry(next, typeof(*pool),
++ sgv_active_pools_list_entry);
++ TRACE_MEM("New sgv cur purge pool %p",
++ sgv_cur_purge_pool);
++ }
++ }
++
++ spin_unlock_bh(&sgv_pools_lock);
++ return;
++}
++
++/* Must be called under sgv_pool_lock held */
++static void sgv_dec_cached_entries(struct sgv_pool *pool, int pages)
++{
++ pool->cached_entries--;
++ pool->cached_pages -= pages;
++
++ if (pool->cached_entries == 0)
++ sgv_del_from_active(pool);
++
++ return;
++}
++
++/* Must be called under sgv_pool_lock held */
++static void __sgv_purge_from_cache(struct sgv_pool_obj *obj)
++{
++ int pages = obj->pages;
++ struct sgv_pool *pool = obj->owner_pool;
++
++ TRACE_MEM("Purging sgv obj %p from pool %p (new cached_entries %d)",
++ obj, pool, pool->cached_entries-1);
++
++ list_del(&obj->sorted_recycling_list_entry);
++ list_del(&obj->recycling_list_entry);
++
++ pool->inactive_cached_pages -= pages;
++ sgv_dec_cached_entries(pool, pages);
++
++ atomic_sub(pages, &sgv_pages_total);
++
++ return;
++}
++
++/* Must be called under sgv_pool_lock held */
++static bool sgv_purge_from_cache(struct sgv_pool_obj *obj, int min_interval,
++ unsigned long cur_time)
++{
++ EXTRACHECKS_BUG_ON(min_interval < 0);
++
++ TRACE_MEM("Checking if sgv obj %p should be purged (cur time %ld, "
++ "obj time %ld, time to purge %ld)", obj, cur_time,
++ obj->time_stamp, obj->time_stamp + min_interval);
++
++ if (time_after_eq(cur_time, (obj->time_stamp + min_interval))) {
++ __sgv_purge_from_cache(obj);
++ return true;
++ }
++ return false;
++}
++
++/* No locks */
++static int sgv_shrink_pool(struct sgv_pool *pool, int nr, int min_interval,
++ unsigned long cur_time)
++{
++ int freed = 0;
++
++ TRACE_ENTRY();
++
++ TRACE_MEM("Trying to shrink pool %p (nr %d, min_interval %d)",
++ pool, nr, min_interval);
++
++ if (pool->purge_interval < 0) {
++ TRACE_MEM("Not shrinkable pool %p, skipping", pool);
++ goto out;
++ }
++
++ spin_lock_bh(&pool->sgv_pool_lock);
++
++ while (!list_empty(&pool->sorted_recycling_list) &&
++ (atomic_read(&sgv_pages_total) > sgv_lo_wmk)) {
++ struct sgv_pool_obj *obj = list_entry(
++ pool->sorted_recycling_list.next,
++ struct sgv_pool_obj, sorted_recycling_list_entry);
++
++ if (sgv_purge_from_cache(obj, min_interval, cur_time)) {
++ int pages = obj->pages;
++
++ freed += pages;
++ nr -= pages;
++
++ TRACE_MEM("%d pages purged from pool %p (nr left %d, "
++ "total freed %d)", pages, pool, nr, freed);
++
++ spin_unlock_bh(&pool->sgv_pool_lock);
++ sgv_dtor_and_free(obj);
++ spin_lock_bh(&pool->sgv_pool_lock);
++ } else
++ break;
++
++ if ((nr <= 0) || (freed >= MAX_PAGES_PER_POOL)) {
++ if (freed >= MAX_PAGES_PER_POOL)
++ TRACE_MEM("%d pages purged from pool %p, "
++ "leaving", freed, pool);
++ break;
++ }
++ }
++
++ spin_unlock_bh(&pool->sgv_pool_lock);
++
++out:
++ TRACE_EXIT_RES(nr);
++ return nr;
++}
++
++/* No locks */
++static int __sgv_shrink(int nr, int min_interval)
++{
++ struct sgv_pool *pool;
++ unsigned long cur_time = jiffies;
++ int prev_nr = nr;
++ bool circle = false;
++
++ TRACE_ENTRY();
++
++ TRACE_MEM("Trying to shrink %d pages from all sgv pools "
++ "(min_interval %d)", nr, min_interval);
++
++ while (nr > 0) {
++ struct list_head *next;
++
++ spin_lock_bh(&sgv_pools_lock);
++
++ pool = sgv_cur_purge_pool;
++ if (pool == NULL) {
++ if (list_empty(&sgv_active_pools_list)) {
++ TRACE_MEM("%s", "Active pools list is empty");
++ goto out_unlock;
++ }
++
++ pool = list_entry(sgv_active_pools_list.next,
++ typeof(*pool),
++ sgv_active_pools_list_entry);
++ }
++ sgv_pool_get(pool);
++
++ next = pool->sgv_active_pools_list_entry.next;
++ if (next == &sgv_active_pools_list) {
++ if (circle && (prev_nr == nr)) {
++ TRACE_MEM("Full circle done, but no progress, "
++ "leaving (nr %d)", nr);
++ goto out_unlock_put;
++ }
++ circle = true;
++ prev_nr = nr;
++
++ next = next->next;
++ }
++
++ sgv_cur_purge_pool = list_entry(next, typeof(*pool),
++ sgv_active_pools_list_entry);
++ TRACE_MEM("New cur purge pool %p", sgv_cur_purge_pool);
++
++ spin_unlock_bh(&sgv_pools_lock);
++
++ nr = sgv_shrink_pool(pool, nr, min_interval, cur_time);
++
++ sgv_pool_put(pool);
++ }
++
++out:
++ TRACE_EXIT_RES(nr);
++ return nr;
++
++out_unlock:
++ spin_unlock_bh(&sgv_pools_lock);
++ goto out;
++
++out_unlock_put:
++ spin_unlock_bh(&sgv_pools_lock);
++ sgv_pool_put(pool);
++ goto out;
++}
++
++static int sgv_shrink(struct shrinker *shrinker, struct shrink_control *sc)
++{
++ int nr = sc->nr_to_scan;
++
++ TRACE_ENTRY();
++
++ if (nr > 0) {
++ nr = __sgv_shrink(nr, SGV_MIN_SHRINK_INTERVAL);
++ TRACE_MEM("Left %d", nr);
++ } else {
++ struct sgv_pool *pool;
++ int inactive_pages = 0;
++
++ spin_lock_bh(&sgv_pools_lock);
++ list_for_each_entry(pool, &sgv_active_pools_list,
++ sgv_active_pools_list_entry) {
++ if (pool->purge_interval > 0)
++ inactive_pages += pool->inactive_cached_pages;
++ }
++ spin_unlock_bh(&sgv_pools_lock);
++
++ nr = max((int)0, inactive_pages - sgv_lo_wmk);
++ TRACE_MEM("Can free %d (total %d)", nr,
++ atomic_read(&sgv_pages_total));
++ }
++
++ TRACE_EXIT_RES(nr);
++ return nr;
++}
++
++static void sgv_purge_work_fn(struct delayed_work *work)
++{
++ unsigned long cur_time = jiffies;
++ struct sgv_pool *pool = container_of(work, struct sgv_pool,
++ sgv_purge_work);
++
++ TRACE_ENTRY();
++
++ TRACE_MEM("Purge work for pool %p", pool);
++
++ spin_lock_bh(&pool->sgv_pool_lock);
++
++ pool->purge_work_scheduled = false;
++
++ while (!list_empty(&pool->sorted_recycling_list)) {
++ struct sgv_pool_obj *obj = list_entry(
++ pool->sorted_recycling_list.next,
++ struct sgv_pool_obj, sorted_recycling_list_entry);
++
++ if (sgv_purge_from_cache(obj, pool->purge_interval, cur_time)) {
++ spin_unlock_bh(&pool->sgv_pool_lock);
++ sgv_dtor_and_free(obj);
++ spin_lock_bh(&pool->sgv_pool_lock);
++ } else {
++ /*
++ * Let's reschedule it for full period to not get here
++ * too often. In the worst case we have shrinker
++ * to reclaim buffers more quickly.
++ */
++ TRACE_MEM("Rescheduling purge work for pool %p (delay "
++ "%d HZ/%d sec)", pool, pool->purge_interval,
++ pool->purge_interval/HZ);
++ schedule_delayed_work(&pool->sgv_purge_work,
++ pool->purge_interval);
++ pool->purge_work_scheduled = true;
++ break;
++ }
++ }
++
++ spin_unlock_bh(&pool->sgv_pool_lock);
++
++ TRACE_MEM("Leaving purge work for pool %p", pool);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int sgv_check_full_clustering(struct scatterlist *sg, int cur, int hint)
++{
++ int res = -1;
++ int i = hint;
++ unsigned long pfn_cur = page_to_pfn(sg_page(&sg[cur]));
++ int len_cur = sg[cur].length;
++ unsigned long pfn_cur_next = pfn_cur + (len_cur >> PAGE_SHIFT);
++ int full_page_cur = (len_cur & (PAGE_SIZE - 1)) == 0;
++ unsigned long pfn, pfn_next;
++ bool full_page;
++
++#if 0
++ TRACE_MEM("pfn_cur %ld, pfn_cur_next %ld, len_cur %d, full_page_cur %d",
++ pfn_cur, pfn_cur_next, len_cur, full_page_cur);
++#endif
++
++ /* check the hint first */
++ if (i >= 0) {
++ pfn = page_to_pfn(sg_page(&sg[i]));
++ pfn_next = pfn + (sg[i].length >> PAGE_SHIFT);
++ full_page = (sg[i].length & (PAGE_SIZE - 1)) == 0;
++
++ if ((pfn == pfn_cur_next) && full_page_cur)
++ goto out_head;
++
++ if ((pfn_next == pfn_cur) && full_page)
++ goto out_tail;
++ }
++
++ /* ToDo: implement more intelligent search */
++ for (i = cur - 1; i >= 0; i--) {
++ pfn = page_to_pfn(sg_page(&sg[i]));
++ pfn_next = pfn + (sg[i].length >> PAGE_SHIFT);
++ full_page = (sg[i].length & (PAGE_SIZE - 1)) == 0;
++
++ if ((pfn == pfn_cur_next) && full_page_cur)
++ goto out_head;
++
++ if ((pfn_next == pfn_cur) && full_page)
++ goto out_tail;
++ }
++
++out:
++ return res;
++
++out_tail:
++ TRACE_MEM("SG segment %d will be tail merged with segment %d", cur, i);
++ sg[i].length += len_cur;
++ sg_clear(&sg[cur]);
++ res = i;
++ goto out;
++
++out_head:
++ TRACE_MEM("SG segment %d will be head merged with segment %d", cur, i);
++ sg_assign_page(&sg[i], sg_page(&sg[cur]));
++ sg[i].length += len_cur;
++ sg_clear(&sg[cur]);
++ res = i;
++ goto out;
++}
++
++static int sgv_check_tail_clustering(struct scatterlist *sg, int cur, int hint)
++{
++ int res = -1;
++ unsigned long pfn_cur = page_to_pfn(sg_page(&sg[cur]));
++ int len_cur = sg[cur].length;
++ int prev;
++ unsigned long pfn_prev;
++ bool full_page;
++
++#ifdef SCST_HIGHMEM
++ if (page >= highmem_start_page) {
++ TRACE_MEM("%s", "HIGHMEM page allocated, no clustering")
++ goto out;
++ }
++#endif
++
++#if 0
++ TRACE_MEM("pfn_cur %ld, pfn_cur_next %ld, len_cur %d, full_page_cur %d",
++ pfn_cur, pfn_cur_next, len_cur, full_page_cur);
++#endif
++
++ if (cur == 0)
++ goto out;
++
++ prev = cur - 1;
++ pfn_prev = page_to_pfn(sg_page(&sg[prev])) +
++ (sg[prev].length >> PAGE_SHIFT);
++ full_page = (sg[prev].length & (PAGE_SIZE - 1)) == 0;
++
++ if ((pfn_prev == pfn_cur) && full_page) {
++ TRACE_MEM("SG segment %d will be tail merged with segment %d",
++ cur, prev);
++ sg[prev].length += len_cur;
++ sg_clear(&sg[cur]);
++ res = prev;
++ }
++
++out:
++ return res;
++}
++
++static void sgv_free_sys_sg_entries(struct scatterlist *sg, int sg_count,
++ void *priv)
++{
++ int i;
++
++ TRACE_MEM("sg=%p, sg_count=%d", sg, sg_count);
++
++ for (i = 0; i < sg_count; i++) {
++ struct page *p = sg_page(&sg[i]);
++ int len = sg[i].length;
++ int pages =
++ (len >> PAGE_SHIFT) + ((len & ~PAGE_MASK) != 0);
++
++ TRACE_MEM("page %lx, len %d, pages %d",
++ (unsigned long)p, len, pages);
++
++ while (pages > 0) {
++ int order = 0;
++
++ TRACE_MEM("free_pages(): order %d, page %lx",
++ order, (unsigned long)p);
++
++ __free_pages(p, order);
++
++ pages -= 1 << order;
++ p += 1 << order;
++ }
++ }
++}
++
++static struct page *sgv_alloc_sys_pages(struct scatterlist *sg,
++ gfp_t gfp_mask, void *priv)
++{
++ struct page *page = alloc_pages(gfp_mask, 0);
++
++ sg_set_page(sg, page, PAGE_SIZE, 0);
++ TRACE_MEM("page=%p, sg=%p, priv=%p", page, sg, priv);
++ if (page == NULL) {
++ TRACE(TRACE_OUT_OF_MEM, "%s", "Allocation of "
++ "sg page failed");
++ }
++ return page;
++}
++
++static int sgv_alloc_sg_entries(struct scatterlist *sg, int pages,
++ gfp_t gfp_mask, enum sgv_clustering_types clustering_type,
++ struct trans_tbl_ent *trans_tbl,
++ const struct sgv_pool_alloc_fns *alloc_fns, void *priv)
++{
++ int sg_count = 0;
++ int pg, i, j;
++ int merged = -1;
++
++ TRACE_MEM("pages=%d, clustering_type=%d", pages, clustering_type);
++
++#if 0
++ gfp_mask |= __GFP_COLD;
++#endif
++#ifdef CONFIG_SCST_STRICT_SECURITY
++ gfp_mask |= __GFP_ZERO;
++#endif
++
++ for (pg = 0; pg < pages; pg++) {
++ void *rc;
++#ifdef CONFIG_SCST_DEBUG_OOM
++ if (((gfp_mask & __GFP_NOFAIL) != __GFP_NOFAIL) &&
++ ((scst_random() % 10000) == 55))
++ rc = NULL;
++ else
++#endif
++ rc = alloc_fns->alloc_pages_fn(&sg[sg_count], gfp_mask,
++ priv);
++ if (rc == NULL)
++ goto out_no_mem;
++
++ /*
++ * This code allows compiler to see full body of the clustering
++ * functions and gives it a chance to generate better code.
++ * At least, the resulting code is smaller, comparing to
++ * calling them using a function pointer.
++ */
++ if (clustering_type == sgv_full_clustering)
++ merged = sgv_check_full_clustering(sg, sg_count, merged);
++ else if (clustering_type == sgv_tail_clustering)
++ merged = sgv_check_tail_clustering(sg, sg_count, merged);
++ else
++ merged = -1;
++
++ if (merged == -1)
++ sg_count++;
++
++ TRACE_MEM("pg=%d, merged=%d, sg_count=%d", pg, merged,
++ sg_count);
++ }
++
++ if ((clustering_type != sgv_no_clustering) && (trans_tbl != NULL)) {
++ pg = 0;
++ for (i = 0; i < pages; i++) {
++ int n = (sg[i].length >> PAGE_SHIFT) +
++ ((sg[i].length & ~PAGE_MASK) != 0);
++ trans_tbl[i].pg_count = pg;
++ for (j = 0; j < n; j++)
++ trans_tbl[pg++].sg_num = i+1;
++ TRACE_MEM("i=%d, n=%d, pg_count=%d", i, n,
++ trans_tbl[i].pg_count);
++ }
++ }
++
++out:
++ TRACE_MEM("sg_count=%d", sg_count);
++ return sg_count;
++
++out_no_mem:
++ alloc_fns->free_pages_fn(sg, sg_count, priv);
++ sg_count = 0;
++ goto out;
++}
++
++static int sgv_alloc_arrays(struct sgv_pool_obj *obj,
++ int pages_to_alloc, gfp_t gfp_mask)
++{
++ int sz, tsz = 0;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ sz = pages_to_alloc * sizeof(obj->sg_entries[0]);
++
++ obj->sg_entries = kmalloc(sz, gfp_mask);
++ if (unlikely(obj->sg_entries == NULL)) {
++ TRACE(TRACE_OUT_OF_MEM, "Allocation of sgv_pool_obj "
++ "SG vector failed (size %d)", sz);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ sg_init_table(obj->sg_entries, pages_to_alloc);
++
++ if (sgv_pool_clustered(obj->owner_pool)) {
++ if (pages_to_alloc <= sgv_max_trans_pages) {
++ obj->trans_tbl =
++ (struct trans_tbl_ent *)obj->sg_entries_data;
++ /*
++ * No need to clear trans_tbl, if needed, it will be
++ * fully rewritten in sgv_alloc_sg_entries()
++ */
++ } else {
++ tsz = pages_to_alloc * sizeof(obj->trans_tbl[0]);
++ obj->trans_tbl = kzalloc(tsz, gfp_mask);
++ if (unlikely(obj->trans_tbl == NULL)) {
++ TRACE(TRACE_OUT_OF_MEM, "Allocation of "
++ "trans_tbl failed (size %d)", tsz);
++ res = -ENOMEM;
++ goto out_free;
++ }
++ }
++ }
++
++ TRACE_MEM("pages_to_alloc %d, sz %d, tsz %d, obj %p, sg_entries %p, "
++ "trans_tbl %p", pages_to_alloc, sz, tsz, obj, obj->sg_entries,
++ obj->trans_tbl);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(obj->sg_entries);
++ obj->sg_entries = NULL;
++ goto out;
++}
++
++static struct sgv_pool_obj *sgv_get_obj(struct sgv_pool *pool, int cache_num,
++ int pages, gfp_t gfp_mask, bool get_new)
++{
++ struct sgv_pool_obj *obj;
++
++ spin_lock_bh(&pool->sgv_pool_lock);
++
++ if (unlikely(get_new)) {
++ /* Used only for buffers preallocation */
++ goto get_new;
++ }
++
++ if (likely(!list_empty(&pool->recycling_lists[cache_num]))) {
++ obj = list_entry(pool->recycling_lists[cache_num].next,
++ struct sgv_pool_obj, recycling_list_entry);
++
++ list_del(&obj->sorted_recycling_list_entry);
++ list_del(&obj->recycling_list_entry);
++
++ pool->inactive_cached_pages -= pages;
++
++ spin_unlock_bh(&pool->sgv_pool_lock);
++ goto out;
++ }
++
++get_new:
++ if (pool->cached_entries == 0) {
++ TRACE_MEM("Adding pool %p to the active list", pool);
++ spin_lock_bh(&sgv_pools_lock);
++ list_add_tail(&pool->sgv_active_pools_list_entry,
++ &sgv_active_pools_list);
++ spin_unlock_bh(&sgv_pools_lock);
++ }
++
++ pool->cached_entries++;
++ pool->cached_pages += pages;
++
++ spin_unlock_bh(&pool->sgv_pool_lock);
++
++ TRACE_MEM("New cached entries %d (pool %p)", pool->cached_entries,
++ pool);
++
++ obj = kmem_cache_alloc(pool->caches[cache_num],
++ gfp_mask & ~(__GFP_HIGHMEM|GFP_DMA));
++ if (likely(obj)) {
++ memset(obj, 0, sizeof(*obj));
++ obj->cache_num = cache_num;
++ obj->pages = pages;
++ obj->owner_pool = pool;
++ } else {
++ spin_lock_bh(&pool->sgv_pool_lock);
++ sgv_dec_cached_entries(pool, pages);
++ spin_unlock_bh(&pool->sgv_pool_lock);
++ }
++
++out:
++ return obj;
++}
++
++static void sgv_put_obj(struct sgv_pool_obj *obj)
++{
++ struct sgv_pool *pool = obj->owner_pool;
++ struct list_head *entry;
++ struct list_head *list = &pool->recycling_lists[obj->cache_num];
++ int pages = obj->pages;
++
++ spin_lock_bh(&pool->sgv_pool_lock);
++
++ TRACE_MEM("sgv %p, cache num %d, pages %d, sg_count %d", obj,
++ obj->cache_num, pages, obj->sg_count);
++
++ if (sgv_pool_clustered(pool)) {
++ /* Make objects with less entries more preferred */
++ __list_for_each(entry, list) {
++ struct sgv_pool_obj *tmp = list_entry(entry,
++ struct sgv_pool_obj, recycling_list_entry);
++
++ TRACE_MEM("tmp %p, cache num %d, pages %d, sg_count %d",
++ tmp, tmp->cache_num, tmp->pages, tmp->sg_count);
++
++ if (obj->sg_count <= tmp->sg_count)
++ break;
++ }
++ entry = entry->prev;
++ } else
++ entry = list;
++
++ TRACE_MEM("Adding in %p (list %p)", entry, list);
++ list_add(&obj->recycling_list_entry, entry);
++
++ list_add_tail(&obj->sorted_recycling_list_entry,
++ &pool->sorted_recycling_list);
++
++ obj->time_stamp = jiffies;
++
++ pool->inactive_cached_pages += pages;
++
++ if (!pool->purge_work_scheduled) {
++ TRACE_MEM("Scheduling purge work for pool %p", pool);
++ pool->purge_work_scheduled = true;
++ schedule_delayed_work(&pool->sgv_purge_work,
++ pool->purge_interval);
++ }
++
++ spin_unlock_bh(&pool->sgv_pool_lock);
++ return;
++}
++
++/* No locks */
++static int sgv_hiwmk_check(int pages_to_alloc)
++{
++ int res = 0;
++ int pages = pages_to_alloc;
++
++ pages += atomic_read(&sgv_pages_total);
++
++ if (unlikely(pages > sgv_hi_wmk)) {
++ pages -= sgv_hi_wmk;
++ atomic_inc(&sgv_releases_on_hiwmk);
++
++ pages = __sgv_shrink(pages, 0);
++ if (pages > 0) {
++ TRACE(TRACE_OUT_OF_MEM, "Requested amount of "
++ "memory (%d pages) for being executed "
++ "commands together with the already "
++ "allocated memory exceeds the allowed "
++ "maximum %d. Should you increase "
++ "scst_max_cmd_mem?", pages_to_alloc,
++ sgv_hi_wmk);
++ atomic_inc(&sgv_releases_on_hiwmk_failed);
++ res = -ENOMEM;
++ goto out_unlock;
++ }
++ }
++
++ atomic_add(pages_to_alloc, &sgv_pages_total);
++
++out_unlock:
++ TRACE_MEM("pages_to_alloc %d, new total %d", pages_to_alloc,
++ atomic_read(&sgv_pages_total));
++
++ return res;
++}
++
++/* No locks */
++static void sgv_hiwmk_uncheck(int pages)
++{
++ atomic_sub(pages, &sgv_pages_total);
++ TRACE_MEM("pages %d, new total %d", pages,
++ atomic_read(&sgv_pages_total));
++ return;
++}
++
++/* No locks */
++static bool sgv_check_allowed_mem(struct scst_mem_lim *mem_lim, int pages)
++{
++ int alloced;
++ bool res = true;
++
++ alloced = atomic_add_return(pages, &mem_lim->alloced_pages);
++ if (unlikely(alloced > mem_lim->max_allowed_pages)) {
++ TRACE(TRACE_OUT_OF_MEM, "Requested amount of memory "
++ "(%d pages) for being executed commands on a device "
++ "together with the already allocated memory exceeds "
++ "the allowed maximum %d. Should you increase "
++ "scst_max_dev_cmd_mem?", pages,
++ mem_lim->max_allowed_pages);
++ atomic_sub(pages, &mem_lim->alloced_pages);
++ res = false;
++ }
++
++ TRACE_MEM("mem_lim %p, pages %d, res %d, new alloced %d", mem_lim,
++ pages, res, atomic_read(&mem_lim->alloced_pages));
++
++ return res;
++}
++
++/* No locks */
++static void sgv_uncheck_allowed_mem(struct scst_mem_lim *mem_lim, int pages)
++{
++ atomic_sub(pages, &mem_lim->alloced_pages);
++
++ TRACE_MEM("mem_lim %p, pages %d, new alloced %d", mem_lim,
++ pages, atomic_read(&mem_lim->alloced_pages));
++ return;
++}
++
++/**
++ * sgv_pool_alloc - allocate an SG vector from the SGV pool
++ * @pool: the cache to alloc from
++ * @size: size of the resulting SG vector in bytes
++ * @gfp_mask: the allocation mask
++ * @flags: the allocation flags
++ * @count: the resulting count of SG entries in the resulting SG vector
++ * @sgv: the resulting SGV object
++ * @mem_lim: memory limits
++ * @priv: pointer to private for this allocation data
++ *
++ * Description:
++ * Allocate an SG vector from the SGV pool and returns pointer to it or
++ * NULL in case of any error. See the SGV pool documentation for more details.
++ */
++struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size,
++ gfp_t gfp_mask, int flags, int *count,
++ struct sgv_pool_obj **sgv, struct scst_mem_lim *mem_lim, void *priv)
++{
++ struct sgv_pool_obj *obj;
++ int cache_num, pages, cnt;
++ struct scatterlist *res = NULL;
++ int pages_to_alloc;
++ int no_cached = flags & SGV_POOL_ALLOC_NO_CACHED;
++ bool allowed_mem_checked = false, hiwmk_checked = false;
++
++ TRACE_ENTRY();
++
++ if (unlikely(size == 0))
++ goto out;
++
++ EXTRACHECKS_BUG_ON((gfp_mask & __GFP_NOFAIL) == __GFP_NOFAIL);
++
++ pages = ((size + PAGE_SIZE - 1) >> PAGE_SHIFT);
++ if (pool->single_alloc_pages == 0) {
++ int pages_order = get_order(size);
++ cache_num = pages_order;
++ pages_to_alloc = (1 << pages_order);
++ } else {
++ cache_num = 0;
++ pages_to_alloc = max(pool->single_alloc_pages, pages);
++ }
++
++ TRACE_MEM("size=%d, pages=%d, pages_to_alloc=%d, cache num=%d, "
++ "flags=%x, no_cached=%d, *sgv=%p", size, pages,
++ pages_to_alloc, cache_num, flags, no_cached, *sgv);
++
++ if (*sgv != NULL) {
++ obj = *sgv;
++
++ TRACE_MEM("Supplied obj %p, cache num %d", obj, obj->cache_num);
++
++ EXTRACHECKS_BUG_ON(obj->sg_count != 0);
++
++ if (unlikely(!sgv_check_allowed_mem(mem_lim, pages_to_alloc)))
++ goto out_fail_free_sg_entries;
++ allowed_mem_checked = true;
++
++ if (unlikely(sgv_hiwmk_check(pages_to_alloc) != 0))
++ goto out_fail_free_sg_entries;
++ hiwmk_checked = true;
++ } else if ((pages_to_alloc <= pool->max_cached_pages) && !no_cached) {
++ if (unlikely(!sgv_check_allowed_mem(mem_lim, pages_to_alloc)))
++ goto out_fail;
++ allowed_mem_checked = true;
++
++ obj = sgv_get_obj(pool, cache_num, pages_to_alloc, gfp_mask,
++ flags & SGV_POOL_ALLOC_GET_NEW);
++ if (unlikely(obj == NULL)) {
++ TRACE(TRACE_OUT_OF_MEM, "Allocation of "
++ "sgv_pool_obj failed (size %d)", size);
++ goto out_fail;
++ }
++
++ if (obj->sg_count != 0) {
++ TRACE_MEM("Cached obj %p", obj);
++ atomic_inc(&pool->cache_acc[cache_num].hit_alloc);
++ goto success;
++ }
++
++ if (flags & SGV_POOL_NO_ALLOC_ON_CACHE_MISS) {
++ if (!(flags & SGV_POOL_RETURN_OBJ_ON_ALLOC_FAIL))
++ goto out_fail_free;
++ }
++
++ TRACE_MEM("Brand new obj %p", obj);
++
++ if (pages_to_alloc <= sgv_max_local_pages) {
++ obj->sg_entries = obj->sg_entries_data;
++ sg_init_table(obj->sg_entries, pages_to_alloc);
++ TRACE_MEM("sg_entries %p", obj->sg_entries);
++ if (sgv_pool_clustered(pool)) {
++ obj->trans_tbl = (struct trans_tbl_ent *)
++ (obj->sg_entries + pages_to_alloc);
++ TRACE_MEM("trans_tbl %p", obj->trans_tbl);
++ /*
++ * No need to clear trans_tbl, if needed, it
++ * will be fully rewritten in
++ * sgv_alloc_sg_entries().
++ */
++ }
++ } else {
++ if (unlikely(sgv_alloc_arrays(obj, pages_to_alloc,
++ gfp_mask) != 0))
++ goto out_fail_free;
++ }
++
++ if ((flags & SGV_POOL_NO_ALLOC_ON_CACHE_MISS) &&
++ (flags & SGV_POOL_RETURN_OBJ_ON_ALLOC_FAIL))
++ goto out_return;
++
++ obj->allocator_priv = priv;
++
++ if (unlikely(sgv_hiwmk_check(pages_to_alloc) != 0))
++ goto out_fail_free_sg_entries;
++ hiwmk_checked = true;
++ } else {
++ int sz;
++
++ pages_to_alloc = pages;
++
++ if (unlikely(!sgv_check_allowed_mem(mem_lim, pages_to_alloc)))
++ goto out_fail;
++ allowed_mem_checked = true;
++
++ if (flags & SGV_POOL_NO_ALLOC_ON_CACHE_MISS)
++ goto out_return2;
++
++ sz = sizeof(*obj) + pages * sizeof(obj->sg_entries[0]);
++
++ obj = kmalloc(sz, gfp_mask);
++ if (unlikely(obj == NULL)) {
++ TRACE(TRACE_OUT_OF_MEM, "Allocation of "
++ "sgv_pool_obj failed (size %d)", size);
++ goto out_fail;
++ }
++ memset(obj, 0, sizeof(*obj));
++
++ obj->owner_pool = pool;
++ cache_num = -1;
++ obj->cache_num = cache_num;
++ obj->pages = pages_to_alloc;
++ obj->allocator_priv = priv;
++
++ obj->sg_entries = obj->sg_entries_data;
++ sg_init_table(obj->sg_entries, pages);
++
++ if (unlikely(sgv_hiwmk_check(pages_to_alloc) != 0))
++ goto out_fail_free_sg_entries;
++ hiwmk_checked = true;
++
++ TRACE_MEM("Big or no_cached obj %p (size %d)", obj, sz);
++ }
++
++ obj->sg_count = sgv_alloc_sg_entries(obj->sg_entries,
++ pages_to_alloc, gfp_mask, pool->clustering_type,
++ obj->trans_tbl, &pool->alloc_fns, priv);
++ if (unlikely(obj->sg_count <= 0)) {
++ obj->sg_count = 0;
++ if ((flags & SGV_POOL_RETURN_OBJ_ON_ALLOC_FAIL) &&
++ (cache_num >= 0))
++ goto out_return1;
++ else
++ goto out_fail_free_sg_entries;
++ }
++
++ if (cache_num >= 0) {
++ atomic_add(pages_to_alloc - obj->sg_count,
++ &pool->cache_acc[cache_num].merged);
++ } else {
++ if (no_cached) {
++ atomic_add(pages_to_alloc,
++ &pool->other_pages);
++ atomic_add(pages_to_alloc - obj->sg_count,
++ &pool->other_merged);
++ } else {
++ atomic_add(pages_to_alloc,
++ &pool->big_pages);
++ atomic_add(pages_to_alloc - obj->sg_count,
++ &pool->big_merged);
++ }
++ }
++
++success:
++ if (cache_num >= 0) {
++ int sg;
++ atomic_inc(&pool->cache_acc[cache_num].total_alloc);
++ if (sgv_pool_clustered(pool))
++ cnt = obj->trans_tbl[pages-1].sg_num;
++ else
++ cnt = pages;
++ sg = cnt-1;
++ obj->orig_sg = sg;
++ obj->orig_length = obj->sg_entries[sg].length;
++ if (sgv_pool_clustered(pool)) {
++ obj->sg_entries[sg].length =
++ (pages - obj->trans_tbl[sg].pg_count) << PAGE_SHIFT;
++ }
++ } else {
++ cnt = obj->sg_count;
++ if (no_cached)
++ atomic_inc(&pool->other_alloc);
++ else
++ atomic_inc(&pool->big_alloc);
++ }
++
++ *count = cnt;
++ res = obj->sg_entries;
++ *sgv = obj;
++
++ if (size & ~PAGE_MASK)
++ obj->sg_entries[cnt-1].length -=
++ PAGE_SIZE - (size & ~PAGE_MASK);
++
++ TRACE_MEM("obj=%p, sg_entries %p (size=%d, pages=%d, sg_count=%d, "
++ "count=%d, last_len=%d)", obj, obj->sg_entries, size, pages,
++ obj->sg_count, *count, obj->sg_entries[obj->orig_sg].length);
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++
++out_return:
++ obj->allocator_priv = priv;
++ obj->owner_pool = pool;
++
++out_return1:
++ *sgv = obj;
++ TRACE_MEM("Returning failed obj %p (count %d)", obj, *count);
++
++out_return2:
++ *count = pages_to_alloc;
++ res = NULL;
++ goto out_uncheck;
++
++out_fail_free_sg_entries:
++ if (obj->sg_entries != obj->sg_entries_data) {
++ if (obj->trans_tbl !=
++ (struct trans_tbl_ent *)obj->sg_entries_data) {
++ /* kfree() handles NULL parameter */
++ kfree(obj->trans_tbl);
++ obj->trans_tbl = NULL;
++ }
++ kfree(obj->sg_entries);
++ obj->sg_entries = NULL;
++ }
++
++out_fail_free:
++ if (cache_num >= 0) {
++ spin_lock_bh(&pool->sgv_pool_lock);
++ sgv_dec_cached_entries(pool, pages_to_alloc);
++ spin_unlock_bh(&pool->sgv_pool_lock);
++
++ kmem_cache_free(pool->caches[obj->cache_num], obj);
++ } else
++ kfree(obj);
++
++out_fail:
++ res = NULL;
++ *count = 0;
++ *sgv = NULL;
++ TRACE_MEM("%s", "Allocation failed");
++
++out_uncheck:
++ if (hiwmk_checked)
++ sgv_hiwmk_uncheck(pages_to_alloc);
++ if (allowed_mem_checked)
++ sgv_uncheck_allowed_mem(mem_lim, pages_to_alloc);
++ goto out;
++}
++EXPORT_SYMBOL_GPL(sgv_pool_alloc);
++
++/**
++ * sgv_get_priv - return the private allocation data
++ *
++ * Allows to get the allocation private data for this SGV
++ * cache object. The private data supposed to be set by sgv_pool_alloc().
++ */
++void *sgv_get_priv(struct sgv_pool_obj *obj)
++{
++ return obj->allocator_priv;
++}
++EXPORT_SYMBOL_GPL(sgv_get_priv);
++
++/**
++ * sgv_pool_free - free previously allocated SG vector
++ * @sgv: the SGV object to free
++ * @mem_lim: memory limits
++ *
++ * Description:
++ * Frees previously allocated SG vector and updates memory limits
++ */
++void sgv_pool_free(struct sgv_pool_obj *obj, struct scst_mem_lim *mem_lim)
++{
++ int pages = (obj->sg_count != 0) ? obj->pages : 0;
++
++ TRACE_MEM("Freeing obj %p, cache num %d, pages %d, sg_entries %p, "
++ "sg_count %d, allocator_priv %p", obj, obj->cache_num, pages,
++ obj->sg_entries, obj->sg_count, obj->allocator_priv);
++
++/*
++ * Enable it if you are investigating a data corruption and want to make
++ * sure that target or dev handler didn't leave the pages mapped somewhere and,
++ * hence, provoked a data corruption.
++ *
++ * Make sure the check value for _count is set correctly. In most cases, 1 is
++ * correct, but, e.g., iSCSI-SCST can call it with value 2, because
++ * it frees the corresponding cmd before the last put_page() call from
++ * net_put_page() for the last page in the SG. Also, user space dev handlers
++ * usually have their memory mapped in their address space.
++ */
++#if 0
++ {
++ struct scatterlist *sg = obj->sg_entries;
++ int i;
++ for (i = 0; i < obj->sg_count; i++) {
++ struct page *p = sg_page(&sg[i]);
++ int len = sg[i].length;
++ int pages = (len >> PAGE_SHIFT) + ((len & ~PAGE_MASK) != 0);
++ while (pages > 0) {
++ if (atomic_read(&p->_count) != 1) {
++ PRINT_WARNING("Freeing page %p with "
++ "additional owners (_count %d). "
++ "Data corruption possible!",
++ p, atomic_read(&p->_count));
++ WARN_ON(1);
++ }
++ pages--;
++ p++;
++ }
++ }
++ }
++#endif
++
++ if (obj->cache_num >= 0) {
++ obj->sg_entries[obj->orig_sg].length = obj->orig_length;
++ sgv_put_obj(obj);
++ } else {
++ obj->owner_pool->alloc_fns.free_pages_fn(obj->sg_entries,
++ obj->sg_count, obj->allocator_priv);
++ kfree(obj);
++ sgv_hiwmk_uncheck(pages);
++ }
++
++ sgv_uncheck_allowed_mem(mem_lim, pages);
++ return;
++}
++EXPORT_SYMBOL_GPL(sgv_pool_free);
++
++/**
++ * scst_alloc() - allocates an SG vector
++ *
++ * Allocates and returns pointer to SG vector with data size "size".
++ * In *count returned the count of entries in the vector.
++ * Returns NULL for failure.
++ */
++struct scatterlist *scst_alloc(int size, gfp_t gfp_mask, int *count)
++{
++ struct scatterlist *res;
++ int pages = (size >> PAGE_SHIFT) + ((size & ~PAGE_MASK) != 0);
++ struct sgv_pool_alloc_fns sys_alloc_fns = {
++ sgv_alloc_sys_pages, sgv_free_sys_sg_entries };
++ int no_fail = ((gfp_mask & __GFP_NOFAIL) == __GFP_NOFAIL);
++ int cnt;
++
++ TRACE_ENTRY();
++
++ atomic_inc(&sgv_other_total_alloc);
++
++ if (unlikely(sgv_hiwmk_check(pages) != 0)) {
++ if (!no_fail) {
++ res = NULL;
++ goto out;
++ } else {
++ /*
++ * Update active_pages_total since alloc can't fail.
++ * If it wasn't updated then the counter would cross 0
++ * on free again.
++ */
++ sgv_hiwmk_uncheck(-pages);
++ }
++ }
++
++ res = kmalloc(pages*sizeof(*res), gfp_mask);
++ if (res == NULL) {
++ TRACE(TRACE_OUT_OF_MEM, "Unable to allocate sg for %d pages",
++ pages);
++ goto out_uncheck;
++ }
++
++ sg_init_table(res, pages);
++
++ /*
++ * If we allow use clustering here, we will have troubles in
++ * scst_free() to figure out how many pages are in the SG vector.
++ * So, let's always don't use clustering.
++ */
++ cnt = sgv_alloc_sg_entries(res, pages, gfp_mask, sgv_no_clustering,
++ NULL, &sys_alloc_fns, NULL);
++ if (cnt <= 0)
++ goto out_free;
++
++ if (size & ~PAGE_MASK)
++ res[cnt-1].length -= PAGE_SIZE - (size & ~PAGE_MASK);
++
++ *count = cnt;
++
++out:
++ TRACE_MEM("Alloced sg %p (count %d, no_fail %d)", res, *count, no_fail);
++
++ TRACE_EXIT_HRES(res);
++ return res;
++
++out_free:
++ kfree(res);
++ res = NULL;
++
++out_uncheck:
++ if (!no_fail)
++ sgv_hiwmk_uncheck(pages);
++ goto out;
++}
++EXPORT_SYMBOL_GPL(scst_alloc);
++
++/**
++ * scst_free() - frees SG vector
++ *
++ * Frees SG vector returned by scst_alloc().
++ */
++void scst_free(struct scatterlist *sg, int count)
++{
++ TRACE_MEM("Freeing sg=%p", sg);
++
++ sgv_hiwmk_uncheck(count);
++
++ sgv_free_sys_sg_entries(sg, count, NULL);
++ kfree(sg);
++ return;
++}
++EXPORT_SYMBOL_GPL(scst_free);
++
++/* Must be called under sgv_pools_mutex */
++static void sgv_pool_init_cache(struct sgv_pool *pool, int cache_num)
++{
++ int size;
++ int pages;
++ struct sgv_pool_obj *obj;
++
++ atomic_set(&pool->cache_acc[cache_num].total_alloc, 0);
++ atomic_set(&pool->cache_acc[cache_num].hit_alloc, 0);
++ atomic_set(&pool->cache_acc[cache_num].merged, 0);
++
++ if (pool->single_alloc_pages == 0)
++ pages = 1 << cache_num;
++ else
++ pages = pool->single_alloc_pages;
++
++ if (pages <= sgv_max_local_pages) {
++ size = sizeof(*obj) + pages *
++ (sizeof(obj->sg_entries[0]) +
++ ((pool->clustering_type != sgv_no_clustering) ?
++ sizeof(obj->trans_tbl[0]) : 0));
++ } else if (pages <= sgv_max_trans_pages) {
++ /*
++ * sg_entries is allocated outside object,
++ * but trans_tbl is still embedded.
++ */
++ size = sizeof(*obj) + pages *
++ (((pool->clustering_type != sgv_no_clustering) ?
++ sizeof(obj->trans_tbl[0]) : 0));
++ } else {
++ size = sizeof(*obj);
++ /* both sgv and trans_tbl are kmalloc'ed() */
++ }
++
++ TRACE_MEM("pages=%d, size=%d", pages, size);
++
++ scnprintf(pool->cache_names[cache_num],
++ sizeof(pool->cache_names[cache_num]),
++ "%s-%uK", pool->name, (pages << PAGE_SHIFT) >> 10);
++ pool->caches[cache_num] = kmem_cache_create(
++ pool->cache_names[cache_num], size, 0, SCST_SLAB_FLAGS, NULL
++ );
++ return;
++}
++
++/* Must be called under sgv_pools_mutex */
++static int sgv_pool_init(struct sgv_pool *pool, const char *name,
++ enum sgv_clustering_types clustering_type, int single_alloc_pages,
++ int purge_interval)
++{
++ int res = -ENOMEM;
++ int i;
++
++ TRACE_ENTRY();
++
++ if (single_alloc_pages < 0) {
++ PRINT_ERROR("Wrong single_alloc_pages value %d",
++ single_alloc_pages);
++ res = -EINVAL;
++ goto out;
++ }
++
++ memset(pool, 0, sizeof(*pool));
++
++ atomic_set(&pool->big_alloc, 0);
++ atomic_set(&pool->big_pages, 0);
++ atomic_set(&pool->big_merged, 0);
++ atomic_set(&pool->other_alloc, 0);
++ atomic_set(&pool->other_pages, 0);
++ atomic_set(&pool->other_merged, 0);
++
++ pool->clustering_type = clustering_type;
++ pool->single_alloc_pages = single_alloc_pages;
++ if (purge_interval != 0) {
++ pool->purge_interval = purge_interval;
++ if (purge_interval < 0) {
++ /* Let's pretend that it's always scheduled */
++ pool->purge_work_scheduled = 1;
++ }
++ } else
++ pool->purge_interval = SGV_DEFAULT_PURGE_INTERVAL;
++ if (single_alloc_pages == 0) {
++ pool->max_caches = SGV_POOL_ELEMENTS;
++ pool->max_cached_pages = 1 << (SGV_POOL_ELEMENTS - 1);
++ } else {
++ pool->max_caches = 1;
++ pool->max_cached_pages = single_alloc_pages;
++ }
++ pool->alloc_fns.alloc_pages_fn = sgv_alloc_sys_pages;
++ pool->alloc_fns.free_pages_fn = sgv_free_sys_sg_entries;
++
++ TRACE_MEM("name %s, sizeof(*obj)=%zd, clustering_type=%d, "
++ "single_alloc_pages=%d, max_caches=%d, max_cached_pages=%d",
++ name, sizeof(struct sgv_pool_obj), clustering_type,
++ single_alloc_pages, pool->max_caches, pool->max_cached_pages);
++
++ strlcpy(pool->name, name, sizeof(pool->name)-1);
++
++ pool->owner_mm = current->mm;
++
++ for (i = 0; i < pool->max_caches; i++) {
++ sgv_pool_init_cache(pool, i);
++ if (pool->caches[i] == NULL) {
++ PRINT_ERROR("Allocation of sgv_pool "
++ "cache %s(%d) failed", name, i);
++ goto out_free;
++ }
++ }
++
++ atomic_set(&pool->sgv_pool_ref, 1);
++ spin_lock_init(&pool->sgv_pool_lock);
++ INIT_LIST_HEAD(&pool->sorted_recycling_list);
++ for (i = 0; i < pool->max_caches; i++)
++ INIT_LIST_HEAD(&pool->recycling_lists[i]);
++
++ INIT_DELAYED_WORK(&pool->sgv_purge_work,
++ (void (*)(struct work_struct *))sgv_purge_work_fn);
++
++ spin_lock_bh(&sgv_pools_lock);
++ list_add_tail(&pool->sgv_pools_list_entry, &sgv_pools_list);
++ spin_unlock_bh(&sgv_pools_lock);
++
++ res = scst_sgv_sysfs_create(pool);
++ if (res != 0)
++ goto out_del;
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ spin_lock_bh(&sgv_pools_lock);
++ list_del(&pool->sgv_pools_list_entry);
++ spin_unlock_bh(&sgv_pools_lock);
++
++out_free:
++ for (i = 0; i < pool->max_caches; i++) {
++ if (pool->caches[i]) {
++ kmem_cache_destroy(pool->caches[i]);
++ pool->caches[i] = NULL;
++ } else
++ break;
++ }
++ goto out;
++}
++
++static void sgv_evaluate_local_max_pages(void)
++{
++ int space4sgv_ttbl = PAGE_SIZE - sizeof(struct sgv_pool_obj);
++
++ sgv_max_local_pages = space4sgv_ttbl /
++ (sizeof(struct trans_tbl_ent) + sizeof(struct scatterlist));
++
++ sgv_max_trans_pages = space4sgv_ttbl / sizeof(struct trans_tbl_ent);
++
++ TRACE_MEM("sgv_max_local_pages %d, sgv_max_trans_pages %d",
++ sgv_max_local_pages, sgv_max_trans_pages);
++ return;
++}
++
++/**
++ * sgv_pool_flush() - flushes the SGV pool.
++ *
++ * Flushes, i.e. frees, all the cached entries in the SGV pool.
++ */
++void sgv_pool_flush(struct sgv_pool *pool)
++{
++ int i;
++
++ TRACE_ENTRY();
++
++ for (i = 0; i < pool->max_caches; i++) {
++ struct sgv_pool_obj *obj;
++
++ spin_lock_bh(&pool->sgv_pool_lock);
++
++ while (!list_empty(&pool->recycling_lists[i])) {
++ obj = list_entry(pool->recycling_lists[i].next,
++ struct sgv_pool_obj, recycling_list_entry);
++
++ __sgv_purge_from_cache(obj);
++
++ spin_unlock_bh(&pool->sgv_pool_lock);
++
++ EXTRACHECKS_BUG_ON(obj->owner_pool != pool);
++ sgv_dtor_and_free(obj);
++
++ spin_lock_bh(&pool->sgv_pool_lock);
++ }
++ spin_unlock_bh(&pool->sgv_pool_lock);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(sgv_pool_flush);
++
++static void sgv_pool_destroy(struct sgv_pool *pool)
++{
++ int i;
++
++ TRACE_ENTRY();
++
++ cancel_delayed_work_sync(&pool->sgv_purge_work);
++
++ sgv_pool_flush(pool);
++
++ mutex_lock(&sgv_pools_mutex);
++ spin_lock_bh(&sgv_pools_lock);
++ list_del(&pool->sgv_pools_list_entry);
++ spin_unlock_bh(&sgv_pools_lock);
++ mutex_unlock(&sgv_pools_mutex);
++
++ scst_sgv_sysfs_del(pool);
++
++ for (i = 0; i < pool->max_caches; i++) {
++ if (pool->caches[i])
++ kmem_cache_destroy(pool->caches[i]);
++ pool->caches[i] = NULL;
++ }
++
++ kfree(pool);
++
++ TRACE_EXIT();
++ return;
++}
++
++/**
++ * sgv_pool_set_allocator - set custom pages allocator
++ * @pool: the cache
++ * @alloc_pages_fn: pages allocation function
++ * @free_pages_fn: pages freeing function
++ *
++ * Description:
++ * Allows to set custom pages allocator for the SGV pool.
++ * See the SGV pool documentation for more details.
++ */
++void sgv_pool_set_allocator(struct sgv_pool *pool,
++ struct page *(*alloc_pages_fn)(struct scatterlist *, gfp_t, void *),
++ void (*free_pages_fn)(struct scatterlist *, int, void *))
++{
++ pool->alloc_fns.alloc_pages_fn = alloc_pages_fn;
++ pool->alloc_fns.free_pages_fn = free_pages_fn;
++ return;
++}
++EXPORT_SYMBOL_GPL(sgv_pool_set_allocator);
++
++/**
++ * sgv_pool_create - creates and initializes an SGV pool
++ * @name: the name of the SGV pool
++ * @clustered: sets type of the pages clustering.
++ * @single_alloc_pages: if 0, then the SGV pool will work in the set of
++ * power 2 size buffers mode. If >0, then the SGV pool will
++ * work in the fixed size buffers mode. In this case
++ * single_alloc_pages sets the size of each buffer in pages.
++ * @shared: sets if the SGV pool can be shared between devices or not.
++ * The cache sharing allowed only between devices created inside
++ * the same address space. If an SGV pool is shared, each
++ * subsequent call of sgv_pool_create() with the same cache name
++ * will not create a new cache, but instead return a reference
++ * to it.
++ * @purge_interval: sets the cache purging interval. I.e., an SG buffer
++ * will be freed if it's unused for time t
++ * purge_interval <= t < 2*purge_interval. If purge_interval
++ * is 0, then the default interval will be used (60 seconds).
++ * If purge_interval <0, then the automatic purging will be
++ * disabled.
++ *
++ * Description:
++ * Returns the resulting SGV pool or NULL in case of any error.
++ */
++struct sgv_pool *sgv_pool_create(const char *name,
++ enum sgv_clustering_types clustering_type,
++ int single_alloc_pages, bool shared, int purge_interval)
++{
++ struct sgv_pool *pool;
++ int rc;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&sgv_pools_mutex);
++
++ list_for_each_entry(pool, &sgv_pools_list, sgv_pools_list_entry) {
++ if (strcmp(pool->name, name) == 0) {
++ if (shared) {
++ if (pool->owner_mm != current->mm) {
++ PRINT_ERROR("Attempt of a shared use "
++ "of SGV pool %s with "
++ "different MM", name);
++ goto out_unlock;
++ }
++ sgv_pool_get(pool);
++ goto out_unlock;
++ } else {
++ PRINT_ERROR("SGV pool %s already exists", name);
++ pool = NULL;
++ goto out_unlock;
++ }
++ }
++ }
++
++ pool = kzalloc(sizeof(*pool), GFP_KERNEL);
++ if (pool == NULL) {
++ PRINT_ERROR("Allocation of sgv_pool failed (size %zd)",
++ sizeof(*pool));
++ goto out_unlock;
++ }
++
++ rc = sgv_pool_init(pool, name, clustering_type, single_alloc_pages,
++ purge_interval);
++ if (rc != 0)
++ goto out_free;
++
++out_unlock:
++ mutex_unlock(&sgv_pools_mutex);
++
++ TRACE_EXIT_RES(pool != NULL);
++ return pool;
++
++out_free:
++ kfree(pool);
++ goto out_unlock;
++}
++EXPORT_SYMBOL_GPL(sgv_pool_create);
++
++/**
++ * sgv_pool_get - increase ref counter for the corresponding SGV pool
++ *
++ * Increases ref counter for the corresponding SGV pool
++ */
++void sgv_pool_get(struct sgv_pool *pool)
++{
++ atomic_inc(&pool->sgv_pool_ref);
++ TRACE_MEM("Incrementing sgv pool %p ref (new value %d)",
++ pool, atomic_read(&pool->sgv_pool_ref));
++ return;
++}
++EXPORT_SYMBOL_GPL(sgv_pool_get);
++
++/**
++ * sgv_pool_put - decrease ref counter for the corresponding SGV pool
++ *
++ * Decreases ref counter for the corresponding SGV pool. If the ref
++ * counter reaches 0, the cache will be destroyed.
++ */
++void sgv_pool_put(struct sgv_pool *pool)
++{
++ TRACE_MEM("Decrementing sgv pool %p ref (new value %d)",
++ pool, atomic_read(&pool->sgv_pool_ref)-1);
++ if (atomic_dec_and_test(&pool->sgv_pool_ref))
++ sgv_pool_destroy(pool);
++ return;
++}
++EXPORT_SYMBOL_GPL(sgv_pool_put);
++
++/**
++ * sgv_pool_del - deletes the corresponding SGV pool
++ * @pool: the cache to delete.
++ *
++ * Description:
++ * If the cache is shared, it will decrease its reference counter.
++ * If the reference counter reaches 0, the cache will be destroyed.
++ */
++void sgv_pool_del(struct sgv_pool *pool)
++{
++ TRACE_ENTRY();
++
++ sgv_pool_put(pool);
++
++ TRACE_EXIT();
++ return;
++}
++EXPORT_SYMBOL_GPL(sgv_pool_del);
++
++/* Both parameters in pages */
++int scst_sgv_pools_init(unsigned long mem_hwmark, unsigned long mem_lwmark)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ sgv_hi_wmk = mem_hwmark;
++ sgv_lo_wmk = mem_lwmark;
++
++ sgv_evaluate_local_max_pages();
++
++ sgv_norm_pool = sgv_pool_create("sgv", sgv_no_clustering, 0, false, 0);
++ if (sgv_norm_pool == NULL)
++ goto out_err;
++
++ sgv_norm_clust_pool = sgv_pool_create("sgv-clust",
++ sgv_full_clustering, 0, false, 0);
++ if (sgv_norm_clust_pool == NULL)
++ goto out_free_norm;
++
++ sgv_dma_pool = sgv_pool_create("sgv-dma", sgv_no_clustering, 0,
++ false, 0);
++ if (sgv_dma_pool == NULL)
++ goto out_free_clust;
++
++ sgv_shrinker.shrink = sgv_shrink;
++ sgv_shrinker.seeks = DEFAULT_SEEKS;
++ register_shrinker(&sgv_shrinker);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free_clust:
++ sgv_pool_destroy(sgv_norm_clust_pool);
++
++out_free_norm:
++ sgv_pool_destroy(sgv_norm_pool);
++
++out_err:
++ res = -ENOMEM;
++ goto out;
++}
++
++void scst_sgv_pools_deinit(void)
++{
++ TRACE_ENTRY();
++
++ unregister_shrinker(&sgv_shrinker);
++
++ sgv_pool_destroy(sgv_dma_pool);
++ sgv_pool_destroy(sgv_norm_pool);
++ sgv_pool_destroy(sgv_norm_clust_pool);
++
++ flush_scheduled_work();
++
++ TRACE_EXIT();
++ return;
++}
++
++static ssize_t sgv_sysfs_stat_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct sgv_pool *pool;
++ int i, total = 0, hit = 0, merged = 0, allocated = 0;
++ int oa, om, res;
++
++ pool = container_of(kobj, struct sgv_pool, sgv_kobj);
++
++ for (i = 0; i < SGV_POOL_ELEMENTS; i++) {
++ int t;
++
++ hit += atomic_read(&pool->cache_acc[i].hit_alloc);
++ total += atomic_read(&pool->cache_acc[i].total_alloc);
++
++ t = atomic_read(&pool->cache_acc[i].total_alloc) -
++ atomic_read(&pool->cache_acc[i].hit_alloc);
++ allocated += t * (1 << i);
++ merged += atomic_read(&pool->cache_acc[i].merged);
++ }
++
++ res = sprintf(buf, "%-30s %-11s %-11s %-11s %-11s", "Name", "Hit", "Total",
++ "% merged", "Cached (P/I/O)");
++
++ res += sprintf(&buf[res], "\n%-30s %-11d %-11d %-11d %d/%d/%d\n",
++ pool->name, hit, total,
++ (allocated != 0) ? merged*100/allocated : 0,
++ pool->cached_pages, pool->inactive_cached_pages,
++ pool->cached_entries);
++
++ for (i = 0; i < SGV_POOL_ELEMENTS; i++) {
++ int t = atomic_read(&pool->cache_acc[i].total_alloc) -
++ atomic_read(&pool->cache_acc[i].hit_alloc);
++ allocated = t * (1 << i);
++ merged = atomic_read(&pool->cache_acc[i].merged);
++
++ res += sprintf(&buf[res], " %-28s %-11d %-11d %d\n",
++ pool->cache_names[i],
++ atomic_read(&pool->cache_acc[i].hit_alloc),
++ atomic_read(&pool->cache_acc[i].total_alloc),
++ (allocated != 0) ? merged*100/allocated : 0);
++ }
++
++ allocated = atomic_read(&pool->big_pages);
++ merged = atomic_read(&pool->big_merged);
++ oa = atomic_read(&pool->other_pages);
++ om = atomic_read(&pool->other_merged);
++
++ res += sprintf(&buf[res], " %-40s %d/%-9d %d/%d\n", "big/other",
++ atomic_read(&pool->big_alloc), atomic_read(&pool->other_alloc),
++ (allocated != 0) ? merged*100/allocated : 0,
++ (oa != 0) ? om/oa : 0);
++
++ return res;
++}
++
++static ssize_t sgv_sysfs_stat_reset(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ struct sgv_pool *pool;
++ int i;
++
++ TRACE_ENTRY();
++
++ pool = container_of(kobj, struct sgv_pool, sgv_kobj);
++
++ for (i = 0; i < SGV_POOL_ELEMENTS; i++) {
++ atomic_set(&pool->cache_acc[i].hit_alloc, 0);
++ atomic_set(&pool->cache_acc[i].total_alloc, 0);
++ atomic_set(&pool->cache_acc[i].merged, 0);
++ }
++
++ atomic_set(&pool->big_pages, 0);
++ atomic_set(&pool->big_merged, 0);
++ atomic_set(&pool->big_alloc, 0);
++ atomic_set(&pool->other_pages, 0);
++ atomic_set(&pool->other_merged, 0);
++ atomic_set(&pool->other_alloc, 0);
++
++ PRINT_INFO("Statistics for SGV pool %s reset", pool->name);
++
++ TRACE_EXIT_RES(count);
++ return count;
++}
++
++static ssize_t sgv_sysfs_global_stat_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct sgv_pool *pool;
++ int inactive_pages = 0, res;
++
++ TRACE_ENTRY();
++
++ spin_lock_bh(&sgv_pools_lock);
++ list_for_each_entry(pool, &sgv_active_pools_list,
++ sgv_active_pools_list_entry) {
++ inactive_pages += pool->inactive_cached_pages;
++ }
++ spin_unlock_bh(&sgv_pools_lock);
++
++ res = sprintf(buf, "%-42s %d/%d\n%-42s %d/%d\n%-42s %d/%d\n"
++ "%-42s %-11d\n",
++ "Inactive/active pages", inactive_pages,
++ atomic_read(&sgv_pages_total) - inactive_pages,
++ "Hi/lo watermarks [pages]", sgv_hi_wmk, sgv_lo_wmk,
++ "Hi watermark releases/failures",
++ atomic_read(&sgv_releases_on_hiwmk),
++ atomic_read(&sgv_releases_on_hiwmk_failed),
++ "Other allocs", atomic_read(&sgv_other_total_alloc));
++
++ TRACE_EXIT();
++ return res;
++}
++
++static ssize_t sgv_sysfs_global_stat_reset(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ TRACE_ENTRY();
++
++ atomic_set(&sgv_releases_on_hiwmk, 0);
++ atomic_set(&sgv_releases_on_hiwmk_failed, 0);
++ atomic_set(&sgv_other_total_alloc, 0);
++
++ PRINT_INFO("%s", "Global SGV pool statistics reset");
++
++ TRACE_EXIT_RES(count);
++ return count;
++}
++
++static struct kobj_attribute sgv_stat_attr =
++ __ATTR(stats, S_IRUGO | S_IWUSR, sgv_sysfs_stat_show,
++ sgv_sysfs_stat_reset);
++
++static struct attribute *sgv_attrs[] = {
++ &sgv_stat_attr.attr,
++ NULL,
++};
++
++static void sgv_kobj_release(struct kobject *kobj)
++{
++ struct sgv_pool *pool;
++
++ TRACE_ENTRY();
++
++ pool = container_of(kobj, struct sgv_pool, sgv_kobj);
++ if (pool->sgv_kobj_release_cmpl != NULL)
++ complete_all(pool->sgv_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++static struct kobj_type sgv_pool_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = sgv_kobj_release,
++ .default_attrs = sgv_attrs,
++};
++
++static int scst_sgv_sysfs_create(struct sgv_pool *pool)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = kobject_init_and_add(&pool->sgv_kobj, &sgv_pool_ktype,
++ scst_sgv_kobj, pool->name);
++ if (res != 0) {
++ PRINT_ERROR("Can't add sgv pool %s to sysfs", pool->name);
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void scst_sgv_sysfs_del(struct sgv_pool *pool)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ pool->sgv_kobj_release_cmpl = &c;
++
++ kobject_del(&pool->sgv_kobj);
++ kobject_put(&pool->sgv_kobj);
++
++ rc = wait_for_completion_timeout(pool->sgv_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for SGV pool %s (%d refs)...", pool->name,
++ atomic_read(&pool->sgv_kobj.kref.refcount));
++ wait_for_completion(pool->sgv_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs "
++ "entry for SGV pool %s", pool->name);
++ }
++
++ TRACE_EXIT();
++}
++
++static struct kobj_attribute sgv_global_stat_attr =
++ __ATTR(global_stats, S_IRUGO | S_IWUSR, sgv_sysfs_global_stat_show,
++ sgv_sysfs_global_stat_reset);
++
++static struct attribute *sgv_default_attrs[] = {
++ &sgv_global_stat_attr.attr,
++ NULL,
++};
++
++static void scst_sysfs_release(struct kobject *kobj)
++{
++ kfree(kobj);
++}
++
++static struct kobj_type sgv_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_sysfs_release,
++ .default_attrs = sgv_default_attrs,
++};
++
++/**
++ * scst_add_sgv_kobj() - Initialize and add the root SGV kernel object.
++ */
++int scst_add_sgv_kobj(struct kobject *parent, const char *name)
++{
++ int res;
++
++ WARN_ON(scst_sgv_kobj);
++ res = -ENOMEM;
++ scst_sgv_kobj = kzalloc(sizeof(*scst_sgv_kobj), GFP_KERNEL);
++ if (!scst_sgv_kobj)
++ goto out;
++ res = kobject_init_and_add(scst_sgv_kobj, &sgv_ktype, parent, name);
++ if (res != 0)
++ goto out_free;
++out:
++ return res;
++out_free:
++ kobject_put(scst_sgv_kobj);
++ scst_sgv_kobj = NULL;
++ goto out;
++}
++
++/**
++ * scst_del_put_sgv_kobj() - Remove the root SGV kernel object.
++ */
++void scst_del_put_sgv_kobj(void)
++{
++ WARN_ON(!scst_sgv_kobj);
++ kobject_del(scst_sgv_kobj);
++ kobject_put(scst_sgv_kobj);
++ scst_sgv_kobj = NULL;
++}
++
+diff -uprN orig/linux-3.2/Documentation/scst/sgv_cache.sgml linux-3.2/Documentation/scst/sgv_cache.sgml
+--- orig/linux-3.2/Documentation/scst/sgv_cache.sgml
++++ linux-3.2/Documentation/scst/sgv_cache.sgml
+@@ -0,0 +1,335 @@
++<!doctype linuxdoc system>
++
++<article>
++
++<title>
++SCST SGV cache description
++</title>
++
++<author>
++ <name>Vladislav Bolkhovitin</name>
++</author>
++
++<date>Version 2.1.0</date>
++
++<toc>
++
++<sect>Introduction
++
++<p>
++SCST SGV cache is a memory management subsystem in SCST. One can call it
++a "memory pool", but Linux kernel already have a mempool interface,
++which serves different purposes. SGV cache provides to SCST core, target
++drivers and backend dev handlers facilities to allocate, build and cache
++SG vectors for data buffers. The main advantage of it is the caching
++facility, when it doesn't free to the system each vector, which is not
++used anymore, but keeps it for a while (possibly indefinitely) to let it
++be reused by the next consecutive command. This allows to:
++
++<itemize>
++
++<item> Reduce commands processing latencies and, hence, improve performance;
++
++<item> Make commands processing latencies predictable, which is essential
++ for RT applications.
++
++</itemize>
++
++The freed SG vectors are kept by the SGV cache either for some (possibly
++indefinite) time, or, optionally, until the system needs more memory and
++asks to free some using the set_shrinker() interface. Also the SGV cache
++allows to:
++
++<itemize>
++
++<item> Cluster pages together. "Cluster" means merging adjacent pages in a
++single SG entry. It allows to have less SG entries in the resulting SG
++vector, hence improve performance handling it as well as allow to
++work with bigger buffers on hardware with limited SG capabilities.
++
++<item> Set custom page allocator functions. For instance, scst_user device
++handler uses this facility to eliminate unneeded mapping/unmapping of
++user space pages and avoid unneeded IOCTL calls for buffers allocations.
++In fileio_tgt application, which uses a regular malloc() function to
++allocate data buffers, this facility allows ~30% less CPU load and
++considerable performance increase.
++
++<item> Prevent each initiator or all initiators altogether to allocate too
++much memory and DoS the target. Consider 10 initiators, which can have
++access to 10 devices each. Any of them can queue up to 64 commands, each
++can transfer up to 1MB of data. So, all of them in a peak can allocate
++up to 10*10*64 = ~6.5GB of memory for data buffers. This amount must be
++limited somehow and the SGV cache performs this function.
++
++</itemize>
++
++<sect> Implementation
++
++<p>
++From implementation POV the SGV cache is a simple extension of the kmem
++cache. It can work in 2 modes:
++
++<enum>
++
++<item> With fixed size buffers.
++
++<item> With a set of power 2 size buffers. In this mode each SGV cache
++(struct sgv_pool) has SGV_POOL_ELEMENTS (11 currently) of kmem caches.
++Each of those kmem caches keeps SGV cache objects (struct sgv_pool_obj)
++corresponding to SG vectors with size of order X pages. For instance,
++request to allocate 4 pages will be served from kmem cache&lsqb;2&rsqb, since the
++order of the of number of requested pages is 2. If later request to
++allocate 11KB comes, the same SG vector with 4 pages will be reused (see
++below). This mode is in average allows less memory overhead comparing
++with the fixed size buffers mode.
++
++</enum>
++
++Consider how the SGV cache works in the set of buffers mode. When a
++request to allocate new SG vector comes, sgv_pool_alloc() via
++sgv_get_obj() checks if there is already a cached vector with that
++order. If yes, then that vector will be reused and its length, if
++necessary, will be modified to match the requested size. In the above
++example request for 11KB buffer, 4 pages vector will be reused and
++modified using trans_tbl to contain 3 pages and the last entry will be
++modified to contain the requested length - 2*PAGE_SIZE. If there is no
++cached object, then a new sgv_pool_obj will be allocated from the
++corresponding kmem cache, chosen by the order of number of requested
++pages. Then that vector will be filled by pages and returned.
++
++In the fixed size buffers mode the SGV cache works similarly, except
++that it always allocate buffer with the predefined fixed size. I.e.
++even for 4K request the whole buffer with predefined size, say, 1MB,
++will be used.
++
++In both modes, if size of a request exceeds the maximum allowed for
++caching buffer size, the requested buffer will be allocated, but not
++cached.
++
++Freed cached sgv_pool_obj objects are actually freed to the system
++either by the purge work, which is scheduled once in 60 seconds, or in
++sgv_shrink() called by system, when it's asking for memory.
++
++<sect> Interface
++
++<sect1> sgv_pool *sgv_pool_create()
++
++<p>
++<verb>
++struct sgv_pool *sgv_pool_create(
++ const char *name,
++ enum sgv_clustering_types clustered, int single_alloc_pages,
++ bool shared, int purge_interval)
++</verb>
++
++This function creates and initializes an SGV cache. It has the following
++arguments:
++
++<itemize>
++
++<item> <bf/name/ - the name of the SGV cache
++
++<item> <bf/clustered/ - sets type of the pages clustering. The type can be:
++
++ <itemize>
++
++ <item> <bf/sgv_no_clustering/ - no clustering performed.
++
++ <item> <bf/sgv_tail_clustering/ - a page will only be merged with the latest
++ previously allocated page, so the order of pages in the SG will be
++ preserved
++
++ <item> <bf/sgv_full_clustering/ - free merging of pages at any place in
++ the SG is allowed. This mode usually provides the best merging
++ rate.
++
++ </itemize>
++
++<item> <bf/single_alloc_pages/ - if 0, then the SGV cache will work in the set of
++ power 2 size buffers mode. If >0, then the SGV cache will work in the
++ fixed size buffers mode. In this case single_alloc_pages sets the
++ size of each buffer in pages.
++
++<item> <bf/shared/ - sets if the SGV cache can be shared between devices or not.
++ The cache sharing allowed only between devices created inside the same
++ address space. If an SGV cache is shared, each subsequent call of
++ sgv_pool_create() with the same cache name will not create a new cache,
++ but instead return a reference to it.
++
++<item> <bf/purge_interval/ - sets the cache purging interval. I.e. an SG buffer
++ will be freed if it's unused for time t purge_interval <= t <
++ 2*purge_interval. If purge_interval is 0, then the default interval
++ will be used (60 seconds). If purge_interval <0, then the automatic
++ purging will be disabled. Shrinking by the system's demand will also
++ be disabled.
++
++</itemize>
++
++Returns the resulting SGV cache or NULL in case of any error.
++
++<sect1> void sgv_pool_del()
++
++<p>
++<verb>
++void sgv_pool_del(
++ struct sgv_pool *pool)
++</verb>
++
++This function deletes the corresponding SGV cache. If the cache is
++shared, it will decrease its reference counter. If the reference counter
++reaches 0, the cache will be destroyed.
++
++<sect1> void sgv_pool_flush()
++
++<p>
++<verb>
++void sgv_pool_flush(
++ struct sgv_pool *pool)
++</verb>
++
++This function flushes, i.e. frees, all the cached entries in the SGV
++cache.
++
++<sect1> void sgv_pool_set_allocator()
++
++<p>
++<verb>
++void sgv_pool_set_allocator(
++ struct sgv_pool *pool,
++ struct page *(*alloc_pages_fn)(struct scatterlist *sg, gfp_t gfp, void *priv),
++ void (*free_pages_fn)(struct scatterlist *sg, int sg_count, void *priv));
++</verb>
++
++This function allows to set for the SGV cache a custom pages allocator. For
++instance, scst_user uses such function to supply to the cache mapped from
++user space pages.
++
++<bf/alloc_pages_fn()/ has the following parameters:
++
++<itemize>
++
++<item> <bf/sg/ - SG entry, to which the allocated page should be added.
++
++<item> <bf/gfp/ - the allocation GFP flags
++
++<item> <bf/priv/ - pointer to a private data supplied to sgv_pool_alloc()
++
++</itemize>
++
++This function should return the allocated page or NULL, if no page was
++allocated.
++
++
++<bf/free_pages_fn()/ has the following parameters:
++
++<itemize>
++
++<item> <bf/sg/ - SG vector to free
++
++<item> <bf/sg_count/ - number of SG entries in the sg
++
++<item> <bf/priv/ - pointer to a private data supplied to the
++corresponding sgv_pool_alloc()
++
++</itemize>
++
++<sect1> struct scatterlist *sgv_pool_alloc()
++
++<p>
++<verb>
++struct scatterlist *sgv_pool_alloc(
++ struct sgv_pool *pool,
++ unsigned int size,
++ gfp_t gfp_mask,
++ int flags,
++ int *count,
++ struct sgv_pool_obj **sgv,
++ struct scst_mem_lim *mem_lim,
++ void *priv)
++</verb>
++
++This function allocates an SG vector from the SGV cache. It has the
++following parameters:
++
++<itemize>
++
++<item> <bf/pool/ - the cache to alloc from
++
++<item> <bf/size/ - size of the resulting SG vector in bytes
++
++<item> <bf/gfp_mask/ - the allocation mask
++
++<item> <bf/flags/ - the allocation flags. The following flags are possible and
++ can be set using OR operation:
++
++ <enum>
++
++ <item> <bf/SGV_POOL_ALLOC_NO_CACHED/ - the SG vector must not be cached.
++
++ <item> <bf/SGV_POOL_NO_ALLOC_ON_CACHE_MISS/ - don't do an allocation on a
++ cache miss.
++
++ <item> <bf/SGV_POOL_RETURN_OBJ_ON_ALLOC_FAIL/ - return an empty SGV object,
++ i.e. without the SG vector, if the allocation can't be completed.
++ For instance, because SGV_POOL_NO_ALLOC_ON_CACHE_MISS flag set.
++
++ </enum>
++
++<item> <bf/count/ - the resulting count of SG entries in the resulting SG vector.
++
++<item> <bf/sgv/ - the resulting SGV object. It should be used to free the
++ resulting SG vector.
++
++<item> <bf/mem_lim/ - memory limits, see below.
++
++<item> <bf/priv/ - pointer to private for this allocation data. This pointer will
++ be supplied to alloc_pages_fn() and free_pages_fn() and can be
++ retrieved by sgv_get_priv().
++
++</itemize>
++
++This function returns pointer to the resulting SG vector or NULL in case
++of any error.
++
++<sect1> void sgv_pool_free()
++
++<p>
++<verb>
++void sgv_pool_free(
++ struct sgv_pool_obj *sgv,
++ struct scst_mem_lim *mem_lim)
++</verb>
++
++This function frees previously allocated SG vector, referenced by SGV
++cache object sgv.
++
++<sect1> void *sgv_get_priv(struct sgv_pool_obj *sgv)
++
++<p>
++<verb>
++void *sgv_get_priv(
++ struct sgv_pool_obj *sgv)
++</verb>
++
++This function allows to get the allocation private data for this SGV
++cache object sgv. The private data are set by sgv_pool_alloc().
++
++<sect1> void scst_init_mem_lim()
++
++<p>
++<verb>
++void scst_init_mem_lim(
++ struct scst_mem_lim *mem_lim)
++</verb>
++
++This function initializes memory limits structure mem_lim according to
++the current system configuration. This structure should be latter used
++to track and limit allocated by one or more SGV caches memory.
++
++
++<sect> Runtime information and statistics.
++
++<p>
++Runtime information and statistics is available in /sys/kernel/scst_tgt/sgv.
++
++</article>
+diff -uprN orig/linux-3.2/include/scst/scst_user.h linux-3.2/include/scst/scst_user.h
+--- orig/linux-3.2/include/scst/scst_user.h
++++ linux-3.2/include/scst/scst_user.h
+@@ -0,0 +1,320 @@
++/*
++ * include/scst_user.h
++ *
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * Contains constants and data structures for scst_user module.
++ * See http://scst.sourceforge.net/doc/scst_user_spec.txt or
++ * scst_user_spec.txt for description.
++ *
++ * 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/scst_const.h>
++
++#define DEV_USER_NAME "scst_user"
++#define DEV_USER_PATH "/dev/"
++#define DEV_USER_VERSION_NAME SCST_VERSION_NAME
++#define DEV_USER_VERSION \
++ DEV_USER_VERSION_NAME "$Revision: 3281 $" SCST_CONST_VERSION
++
++#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_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
++
++struct scst_user_opt {
++ uint8_t parse_type;
++ uint8_t on_free_cmd_type;
++ uint8_t memory_reuse_type;
++ uint8_t partial_transfers_type;
++ int32_t partial_len;
++
++ /* SCSI control mode page parameters, see SPC */
++ uint8_t tst;
++ uint8_t queue_alg;
++ uint8_t tas;
++ uint8_t swp;
++ uint8_t d_sense;
++
++ uint8_t has_own_order_mgmt;
++};
++
++struct scst_user_dev_desc {
++ aligned_u64 version_str;
++ aligned_u64 license_str;
++ uint8_t type;
++ uint8_t sgv_shared;
++ uint8_t sgv_disable_clustered_pool;
++ int32_t sgv_single_alloc_pages;
++ int32_t sgv_purge_interval;
++ struct scst_user_opt opt;
++ uint32_t block_size;
++ uint8_t enable_pr_cmds_notifications;
++ char name[SCST_MAX_NAME];
++ char sgv_name[SCST_MAX_NAME];
++};
++
++struct scst_user_sess {
++ aligned_u64 sess_h;
++ aligned_u64 lun;
++ uint16_t threads_num;
++ uint8_t rd_only;
++ uint16_t scsi_transport_version;
++ uint16_t phys_transport_version;
++ char initiator_name[SCST_MAX_EXTERNAL_NAME];
++ char target_name[SCST_MAX_EXTERNAL_NAME];
++};
++
++struct scst_user_scsi_cmd_parse {
++ aligned_u64 sess_h;
++
++ uint8_t cdb[SCST_MAX_CDB_SIZE];
++ uint16_t cdb_len;
++
++ int32_t timeout;
++ int32_t bufflen;
++ int32_t out_bufflen;
++
++ uint32_t op_flags;
++
++ uint8_t queue_type;
++ uint8_t data_direction;
++
++ uint8_t expected_values_set;
++ uint8_t expected_data_direction;
++ int32_t expected_transfer_len;
++ int32_t expected_out_transfer_len;
++
++ uint32_t sn;
++};
++
++struct scst_user_scsi_cmd_alloc_mem {
++ aligned_u64 sess_h;
++
++ uint8_t cdb[SCST_MAX_CDB_SIZE];
++ uint16_t cdb_len;
++
++ int32_t alloc_len;
++
++ uint8_t queue_type;
++ uint8_t data_direction;
++
++ uint32_t sn;
++};
++
++struct scst_user_scsi_cmd_exec {
++ aligned_u64 sess_h;
++
++ uint8_t cdb[SCST_MAX_CDB_SIZE];
++ uint16_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;
++ int32_t timeout;
++
++ aligned_u64 p_out_buf;
++ int32_t out_bufflen;
++
++ uint32_t sn;
++
++ 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 aborted;
++ uint8_t status;
++ uint8_t delivery_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;
++ uint32_t cmd_sn;
++ uint8_t cmd_sn_set;
++};
++
++struct scst_user_get_cmd {
++ uint32_t cmd_h;
++ uint32_t subcode;
++ union {
++ aligned_u64 preply;
++ 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;
++ };
++};
++
++/* Be careful adding new members here, this structure is allocated on stack! */
++struct scst_user_scsi_cmd_reply_parse {
++ uint8_t status;
++ union {
++ struct {
++ uint8_t queue_type;
++ uint8_t data_direction;
++ uint16_t cdb_len;
++ uint32_t op_flags;
++ int32_t data_len;
++ int32_t bufflen;
++ int32_t out_bufflen;
++ };
++ struct {
++ uint8_t sense_len;
++ aligned_u64 psense_buffer;
++ };
++ };
++};
++
++/* Be careful adding new members here, this structure is allocated on stack! */
++struct scst_user_scsi_cmd_reply_alloc_mem {
++ aligned_u64 pbuf;
++};
++
++/* Be careful adding new members here, this structure is allocated on stack! */
++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;
++};
++
++/* Be careful adding new members here, this structure is allocated on stack! */
++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;
++ };
++};
++
++/* Be careful adding new members here, this structure is allocated on stack! */
++struct scst_user_get_ext_cdb {
++ uint32_t cmd_h;
++ aligned_u64 ext_cdb_buffer;
++};
++
++/* Be careful adding new members here, this structure is allocated on stack! */
++struct scst_user_prealloc_buffer_in {
++ aligned_u64 pbuf;
++ uint32_t bufflen;
++ uint8_t for_clust_pool;
++};
++
++/* Be careful adding new members here, this structure is allocated on stack! */
++struct scst_user_prealloc_buffer_out {
++ uint32_t cmd_h;
++};
++
++/* Be careful adding new members here, this structure is allocated on stack! */
++union scst_user_prealloc_buffer {
++ struct scst_user_prealloc_buffer_in in;
++ struct scst_user_prealloc_buffer_out out;
++};
++
++#define SCST_USER_REGISTER_DEVICE _IOW('u', 1, struct scst_user_dev_desc)
++#define SCST_USER_UNREGISTER_DEVICE _IO('u', 2)
++#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_CMD _IOW('u', 6, struct scst_user_reply_cmd)
++#define SCST_USER_FLUSH_CACHE _IO('u', 7)
++#define SCST_USER_DEVICE_CAPACITY_CHANGED _IO('u', 8)
++#define SCST_USER_GET_EXTENDED_CDB _IOWR('u', 9, struct scst_user_get_ext_cdb)
++#define SCST_USER_PREALLOC_BUFFER _IOWR('u', 10, union scst_user_prealloc_buffer)
++
++/* 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 */
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_user.c linux-3.2/drivers/scst/dev_handlers/scst_user.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_user.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_user.c
+@@ -0,0 +1,3751 @@
++/*
++ * scst_user.c
++ *
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * 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>
++#include <linux/stddef.h>
++#include <linux/slab.h>
++
++#define LOG_PREFIX DEV_USER_NAME
++
++#include <scst/scst.h>
++#include <scst/scst_user.h>
++#include "scst_dev_handler.h"
++
++#define DEV_USER_CMD_HASH_ORDER 6
++#define DEV_USER_ATTACH_TIMEOUT (5*HZ)
++
++struct scst_user_dev {
++ struct rw_semaphore dev_rwsem;
++
++ /*
++ * Must be kept here, because it's needed on the cleanup time,
++ * when corresponding scst_dev is already dead.
++ */
++ struct scst_cmd_threads udev_cmd_threads;
++
++ /* Protected by udev_cmd_threads.cmd_list_lock */
++ struct list_head ready_cmd_list;
++
++ /* Protected by dev_rwsem or don't need any protection */
++ unsigned int blocking:1;
++ unsigned int cleanup_done:1;
++ unsigned int tst:3;
++ unsigned int queue_alg:4;
++ unsigned int tas:1;
++ unsigned int swp:1;
++ unsigned int d_sense:1;
++ unsigned int has_own_order_mgmt:1;
++
++ int (*generic_parse)(struct scst_cmd *cmd,
++ int (*get_block)(struct scst_cmd *cmd));
++
++ int block;
++ int def_block;
++
++ struct scst_mem_lim udev_mem_lim;
++ struct sgv_pool *pool;
++ struct sgv_pool *pool_clust;
++
++ uint8_t parse_type;
++ uint8_t on_free_cmd_type;
++ uint8_t memory_reuse_type;
++ uint8_t partial_transfers_type;
++ uint32_t partial_len;
++
++ struct scst_dev_type devtype;
++
++ /* Both protected by udev_cmd_threads.cmd_list_lock */
++ unsigned int handle_counter;
++ struct list_head ucmd_hash[1 << DEV_USER_CMD_HASH_ORDER];
++
++ struct scst_device *sdev;
++
++ int virt_id;
++ struct list_head dev_list_entry;
++ char name[SCST_MAX_NAME];
++
++ struct list_head cleanup_list_entry;
++ struct completion cleanup_cmpl;
++};
++
++/* Most fields are unprotected, since only one thread at time can access them */
++struct scst_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 aborted:1;
++
++ struct scst_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;
++
++ /*
++ * Special flags, which can be accessed asynchronously (hence "long").
++ * Protected by udev_cmd_threads.cmd_list_lock.
++ */
++ unsigned long sent_to_user:1;
++ unsigned long jammed:1;
++ unsigned long this_state_unjammed:1;
++ unsigned long seen_by_user:1; /* here only as a small optimization */
++
++ unsigned int state;
++
++ struct list_head ready_cmd_list_entry;
++
++ unsigned int h;
++ struct list_head hash_list_entry;
++
++ int user_cmd_payload_len;
++ struct scst_user_get_cmd user_cmd;
++
++ /* cmpl used only by ATTACH_SESS, mcmd used only by TM */
++ union {
++ struct completion *cmpl;
++ struct scst_mgmt_cmd *mcmd;
++ };
++ int result;
++};
++
++static struct scst_user_cmd *dev_user_alloc_ucmd(struct scst_user_dev *dev,
++ gfp_t gfp_mask);
++static void dev_user_free_ucmd(struct scst_user_cmd *ucmd);
++
++static int dev_user_parse(struct scst_cmd *cmd);
++static int dev_user_alloc_data_buf(struct scst_cmd *cmd);
++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 scst_user_cmd *ucmd);
++
++static void dev_user_unjam_cmd(struct scst_user_cmd *ucmd, int busy,
++ unsigned long *flags);
++
++static int dev_user_process_reply_on_free(struct scst_user_cmd *ucmd);
++static int dev_user_process_reply_tm_exec(struct scst_user_cmd *ucmd,
++ int status);
++static int dev_user_process_reply_sess(struct scst_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_unregister_dev(struct file *file);
++static int dev_user_flush_cache(struct file *file);
++static int dev_user_capacity_changed(struct file *file);
++static int dev_user_prealloc_buffer(struct file *file, void __user *arg);
++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 __user *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);
++static int dev_user_exit_dev(struct scst_user_dev *dev);
++
++static ssize_t dev_user_sysfs_commands_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++
++static struct kobj_attribute dev_user_commands_attr =
++ __ATTR(commands, S_IRUGO, dev_user_sysfs_commands_show, NULL);
++
++static const struct attribute *dev_user_dev_attrs[] = {
++ &dev_user_commands_attr.attr,
++ NULL,
++};
++
++static int dev_usr_parse(struct scst_cmd *cmd);
++
++/** Data **/
++
++static struct kmem_cache *user_cmd_cachep;
++static struct kmem_cache *user_get_cmd_cachep;
++
++static DEFINE_MUTEX(dev_priv_mutex);
++
++static const 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 scst_dev_type dev_user_devtype = {
++ .name = DEV_USER_NAME,
++ .type = -1,
++ .parse = dev_usr_parse,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static int dev_user_major;
++
++static struct class *dev_user_sysfs_class;
++
++static DEFINE_SPINLOCK(dev_list_lock);
++static LIST_HEAD(dev_list);
++
++static DEFINE_SPINLOCK(cleanup_lock);
++static LIST_HEAD(cleanup_list);
++static DECLARE_WAIT_QUEUE_HEAD(cleanup_list_waitQ);
++static struct task_struct *cleanup_thread;
++
++/*
++ * Skip this command if result is not 0. Must be called under
++ * udev_cmd_threads.cmd_list_lock and IRQ off.
++ */
++static inline bool ucmd_get_check(struct scst_user_cmd *ucmd)
++{
++ int r = atomic_inc_return(&ucmd->ucmd_ref);
++ int res;
++ if (unlikely(r == 1)) {
++ TRACE_DBG("ucmd %p is being destroyed", ucmd);
++ atomic_dec(&ucmd->ucmd_ref);
++ res = true;
++ /*
++ * Necessary code is serialized by cmd_list_lock in
++ * cmd_remove_hash()
++ */
++ } else {
++ TRACE_DBG("ucmd %p, new ref_cnt %d", ucmd,
++ atomic_read(&ucmd->ucmd_ref));
++ res = false;
++ }
++ return res;
++}
++
++static inline void ucmd_get(struct scst_user_cmd *ucmd)
++{
++ TRACE_DBG("ucmd %p, ucmd_ref %d", ucmd, atomic_read(&ucmd->ucmd_ref));
++ atomic_inc(&ucmd->ucmd_ref);
++ /*
++ * For the same reason as in kref_get(). Let's be safe and
++ * always do it.
++ */
++ smp_mb__after_atomic_inc();
++}
++
++/* Must not be called under cmd_list_lock!! */
++static inline void ucmd_put(struct scst_user_cmd *ucmd)
++{
++ TRACE_DBG("ucmd %p, ucmd_ref %d", ucmd, atomic_read(&ucmd->ucmd_ref));
++
++ EXTRACHECKS_BUG_ON(atomic_read(&ucmd->ucmd_ref) == 0);
++
++ 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 void __dev_user_not_reg(void)
++{
++ TRACE_MGMT_DBG("%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 -ENODEV;
++ }
++ return 0;
++}
++
++static inline int scst_user_cmd_hashfn(int h)
++{
++ return h & ((1 << DEV_USER_CMD_HASH_ORDER) - 1);
++}
++
++static inline struct scst_user_cmd *__ucmd_find_hash(struct scst_user_dev *dev,
++ unsigned int h)
++{
++ struct list_head *head;
++ struct scst_user_cmd *ucmd;
++
++ head = &dev->ucmd_hash[scst_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 cmd_insert_hash(struct scst_user_cmd *ucmd)
++{
++ struct list_head *head;
++ struct scst_user_dev *dev = ucmd->dev;
++ struct scst_user_cmd *u;
++ unsigned long flags;
++
++ spin_lock_irqsave(&dev->udev_cmd_threads.cmd_list_lock, flags);
++ do {
++ ucmd->h = dev->handle_counter++;
++ u = __ucmd_find_hash(dev, ucmd->h);
++ } while (u != NULL);
++ head = &dev->ucmd_hash[scst_user_cmd_hashfn(ucmd->h)];
++ list_add_tail(&ucmd->hash_list_entry, head);
++ spin_unlock_irqrestore(&dev->udev_cmd_threads.cmd_list_lock, flags);
++
++ TRACE_DBG("Inserted ucmd %p, h=%d (dev %s)", ucmd, ucmd->h, dev->name);
++ return;
++}
++
++static inline void cmd_remove_hash(struct scst_user_cmd *ucmd)
++{
++ unsigned long flags;
++
++ spin_lock_irqsave(&ucmd->dev->udev_cmd_threads.cmd_list_lock, flags);
++ list_del(&ucmd->hash_list_entry);
++ spin_unlock_irqrestore(&ucmd->dev->udev_cmd_threads.cmd_list_lock, flags);
++
++ TRACE_DBG("Removed ucmd %p, h=%d", ucmd, ucmd->h);
++ return;
++}
++
++static void dev_user_free_ucmd(struct scst_user_cmd *ucmd)
++{
++ TRACE_ENTRY();
++
++ TRACE_MEM("Freeing ucmd %p", ucmd);
++
++ cmd_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 scst_user_cmd *ucmd = priv;
++ int offset = 0;
++
++ 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);
++ offset = ucmd->first_page_offset;
++ ucmd_get(ucmd);
++ }
++
++ if (ucmd->cur_data_page >= ucmd->num_data_pages)
++ goto out;
++
++ sg_set_page(sg, ucmd->data_pages[ucmd->cur_data_page],
++ PAGE_SIZE - offset, offset);
++ ucmd->cur_data_page++;
++
++ TRACE_MEM("page=%p, length=%d, offset=%d", sg_page(sg), sg->length,
++ sg->offset);
++ TRACE_BUFFER("Page data", sg_virt(sg), sg->length);
++
++out:
++ TRACE_EXIT();
++ return sg_page(sg);
++}
++
++static void dev_user_on_cached_mem_free(struct scst_user_cmd *ucmd)
++{
++ TRACE_ENTRY();
++
++ TRACE_MEM("Preparing ON_CACHED_MEM_FREE (ucmd %p, h %d, ubuff %lx)",
++ ucmd, ucmd->h, ucmd->ubuff);
++
++ ucmd->user_cmd_payload_len =
++ offsetof(struct scst_user_get_cmd, on_cached_mem_free) +
++ sizeof(ucmd->user_cmd.on_cached_mem_free);
++ 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 scst_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 scst_user_cmd *ucmd)
++{
++ TRACE_ENTRY();
++
++ BUG_ON(ucmd->data_pages == NULL);
++
++ TRACE_MEM("Freeing data pages (ucmd=%p, ubuff=%lx, buff_cached=%d)",
++ ucmd, ucmd->ubuff, 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 void dev_user_free_sg_entries(struct scatterlist *sg, int sg_count,
++ void *priv)
++{
++ struct scst_user_cmd *ucmd = priv;
++
++ TRACE_MEM("Freeing data pages (sg=%p, sg_count=%d, priv %p)", sg,
++ sg_count, ucmd);
++
++ __dev_user_free_sg_entries(ucmd);
++
++ return;
++}
++
++static inline int is_buff_cached(struct scst_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;
++}
++
++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));
++}
++
++/*
++ * 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 scst_user_cmd *ucmd, int cached_buff)
++{
++ int res = 0;
++ struct scst_cmd *cmd = ucmd->cmd;
++ struct scst_user_dev *dev = ucmd->dev;
++ struct sgv_pool *pool;
++ gfp_t gfp_mask;
++ int flags = 0;
++ int bufflen, orig_bufflen;
++ int last_len = 0;
++ int out_sg_pages = 0;
++
++ TRACE_ENTRY();
++
++ gfp_mask = __GFP_NOWARN;
++ gfp_mask |= (scst_cmd_atomic(cmd) ? GFP_ATOMIC : GFP_KERNEL);
++
++ if (cmd->data_direction != SCST_DATA_BIDI) {
++ orig_bufflen = cmd->bufflen;
++ pool = cmd->tgt_dev->dh_priv;
++ } else {
++ /* Make out_sg->offset 0 */
++ int len = cmd->bufflen + ucmd->first_page_offset;
++ out_sg_pages = (len >> PAGE_SHIFT) + ((len & ~PAGE_MASK) != 0);
++ orig_bufflen = (out_sg_pages << PAGE_SHIFT) + cmd->out_bufflen;
++ pool = dev->pool;
++ }
++ bufflen = orig_bufflen;
++
++ EXTRACHECKS_BUG_ON(bufflen == 0);
++
++ if (cached_buff) {
++ flags |= SGV_POOL_RETURN_OBJ_ON_ALLOC_FAIL;
++ if (ucmd->ubuff == 0)
++ flags |= SGV_POOL_NO_ALLOC_ON_CACHE_MISS;
++ } else {
++ TRACE_MEM("%s", "Not cached buff");
++ flags |= SGV_POOL_ALLOC_NO_CACHED;
++ if (ucmd->ubuff == 0) {
++ res = 1;
++ goto out;
++ }
++ bufflen += ucmd->first_page_offset;
++ if (is_need_offs_page(ucmd->ubuff, orig_bufflen))
++ last_len = bufflen & ~PAGE_MASK;
++ else
++ last_len = orig_bufflen & ~PAGE_MASK;
++ }
++ ucmd->buff_cached = cached_buff;
++
++ cmd->sg = sgv_pool_alloc(pool, bufflen, gfp_mask, flags, &cmd->sg_cnt,
++ &ucmd->sgv, &dev->udev_mem_lim, ucmd);
++ if (cmd->sg != NULL) {
++ struct scst_user_cmd *buf_ucmd = sgv_get_priv(ucmd->sgv);
++
++ TRACE_MEM("Buf ucmd %p (cmd->sg_cnt %d, last seg len %d, "
++ "last_len %d, bufflen %d)", buf_ucmd, cmd->sg_cnt,
++ cmd->sg[cmd->sg_cnt-1].length, last_len, bufflen);
++
++ ucmd->ubuff = buf_ucmd->ubuff;
++ ucmd->buf_ucmd = buf_ucmd;
++
++ EXTRACHECKS_BUG_ON((ucmd->data_pages != NULL) &&
++ (ucmd != buf_ucmd));
++
++ if (last_len != 0) {
++ cmd->sg[cmd->sg_cnt-1].length &= PAGE_MASK;
++ cmd->sg[cmd->sg_cnt-1].length += last_len;
++ }
++
++ TRACE_MEM("Buf alloced (ucmd %p, cached_buff %d, ubuff %lx, "
++ "last seg len %d)", ucmd, cached_buff, ucmd->ubuff,
++ cmd->sg[cmd->sg_cnt-1].length);
++
++ if (cmd->data_direction == SCST_DATA_BIDI) {
++ cmd->out_sg = &cmd->sg[out_sg_pages];
++ cmd->out_sg_cnt = cmd->sg_cnt - out_sg_pages;
++ cmd->sg_cnt = out_sg_pages;
++ TRACE_MEM("cmd %p, out_sg %p, out_sg_cnt %d, sg_cnt %d",
++ cmd, cmd->out_sg, cmd->out_sg_cnt, cmd->sg_cnt);
++ }
++
++ if (unlikely(cmd->sg_cnt > cmd->tgt_dev->max_sg_cnt)) {
++ static int ll;
++ if ((ll < 10) || TRACING_MINOR()) {
++ 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++;
++ }
++ cmd->sg = NULL;
++ /* sgv will be freed in dev_user_free_sgv() */
++ 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)) {
++ TRACE_MEM("Refused allocation (ucmd %p)", ucmd);
++ BUG_ON(ucmd->sgv != NULL);
++ res = -1;
++ } else {
++ switch (ucmd->state) {
++ case UCMD_STATE_BUF_ALLOCING:
++ res = 1;
++ break;
++ case UCMD_STATE_EXECING:
++ res = -1;
++ break;
++ default:
++ BUG();
++ break;
++ }
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int dev_user_alloc_space(struct scst_user_cmd *ucmd)
++{
++ int rc, res = SCST_CMD_STATE_DEFAULT;
++ struct scst_cmd *cmd = ucmd->cmd;
++
++ TRACE_ENTRY();
++
++ ucmd->state = UCMD_STATE_BUF_ALLOCING;
++ scst_cmd_set_dh_data_buff_alloced(cmd);
++
++ 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_set_cmd_abnormal_done_state(cmd);
++ 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_payload_len =
++ offsetof(struct scst_user_get_cmd, alloc_cmd) +
++ sizeof(ucmd->user_cmd.alloc_cmd);
++ 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_t(int, SCST_MAX_CDB_SIZE, cmd->cdb_len));
++ 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;
++ ucmd->user_cmd.alloc_cmd.sn = cmd->tgt_sn;
++
++ dev_user_add_to_ready(ucmd);
++
++ res = SCST_CMD_STATE_STOP;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_user_cmd *dev_user_alloc_ucmd(struct scst_user_dev *dev,
++ gfp_t gfp_mask)
++{
++ struct scst_user_cmd *ucmd = NULL;
++
++ TRACE_ENTRY();
++
++ ucmd = kmem_cache_zalloc(user_cmd_cachep, gfp_mask);
++ 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);
++
++ cmd_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 = 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)
++{
++ int rc, res = SCST_CMD_STATE_DEFAULT;
++ struct scst_user_cmd *ucmd;
++ int atomic = scst_cmd_atomic(cmd);
++ struct scst_user_dev *dev = cmd->dev->dh_priv;
++ gfp_t 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 = 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_PARSING) {
++ /* We've already done */
++ goto done;
++ }
++
++ EXTRACHECKS_BUG_ON(ucmd->state != UCMD_STATE_NEW);
++
++ switch (dev->parse_type) {
++ case SCST_USER_PARSE_STANDARD:
++ TRACE_DBG("PARSE STANDARD: ucmd %p", ucmd);
++ rc = dev->generic_parse(cmd, dev_user_get_block);
++ if (rc != 0)
++ goto out_invalid;
++ break;
++
++ case SCST_USER_PARSE_EXCEPTION:
++ TRACE_DBG("PARSE EXCEPTION: ucmd %p", ucmd);
++ rc = dev->generic_parse(cmd, dev_user_get_block);
++ if ((rc == 0) && (cmd->op_flags & SCST_INFO_VALID))
++ 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->user_cmd_payload_len =
++ offsetof(struct scst_user_get_cmd, parse_cmd) +
++ sizeof(ucmd->user_cmd.parse_cmd);
++ 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_t(int, SCST_MAX_CDB_SIZE, cmd->cdb_len));
++ ucmd->user_cmd.parse_cmd.cdb_len = cmd->cdb_len;
++ ucmd->user_cmd.parse_cmd.timeout = cmd->timeout / HZ;
++ ucmd->user_cmd.parse_cmd.bufflen = cmd->bufflen;
++ ucmd->user_cmd.parse_cmd.out_bufflen = cmd->out_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->user_cmd.parse_cmd.expected_out_transfer_len =
++ cmd->expected_out_transfer_len;
++ ucmd->user_cmd.parse_cmd.sn = cmd->tgt_sn;
++ ucmd->user_cmd.parse_cmd.op_flags = cmd->op_flags;
++ ucmd->state = UCMD_STATE_PARSING;
++ dev_user_add_to_ready(ucmd);
++ res = SCST_CMD_STATE_STOP;
++ goto out;
++
++ default:
++ BUG();
++ goto out;
++ }
++
++done:
++ if (cmd->bufflen == 0) {
++ /*
++ * According to SPC bufflen 0 for data transfer commands isn't
++ * an error, so we need to fix the transfer direction.
++ */
++ cmd->data_direction = SCST_DATA_NONE;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_invalid:
++ PRINT_ERROR("PARSE failed (ucmd %p, rc %d)", ucmd, rc);
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++
++out_error:
++ res = scst_set_cmd_abnormal_done_state(cmd);
++ goto out;
++}
++
++static int dev_user_alloc_data_buf(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++ struct scst_user_cmd *ucmd = cmd->dh_priv;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON((ucmd->state != UCMD_STATE_NEW) &&
++ (ucmd->state != UCMD_STATE_PARSING) &&
++ (ucmd->state != UCMD_STATE_BUF_ALLOCING));
++
++ res = dev_user_alloc_space(ucmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void dev_user_flush_dcache(struct scst_user_cmd *ucmd)
++{
++ struct scst_user_cmd *buf_ucmd = ucmd->buf_ucmd;
++ unsigned long start = buf_ucmd->ubuff;
++ int i, bufflen = ucmd->cmd->bufflen;
++
++ TRACE_ENTRY();
++
++ if (start == 0)
++ goto out;
++
++ /*
++ * Possibly, flushing of all the pages from ucmd->cmd->sg can be
++ * faster, since it should be cache hot, while ucmd->buf_ucmd and
++ * buf_ucmd->data_pages are cache cold. But, from other side,
++ * sizeof(buf_ucmd->data_pages[0]) is considerably smaller, than
++ * sizeof(ucmd->cmd->sg[0]), so on big buffers going over
++ * data_pages array can lead to less cache misses. So, real numbers are
++ * needed. ToDo.
++ */
++
++ for (i = 0; (bufflen > 0) && (i < buf_ucmd->num_data_pages); i++) {
++ struct page *page __attribute__((unused));
++ 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;
++ bufflen -= PAGE_SIZE;
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int dev_user_exec(struct scst_cmd *cmd)
++{
++ struct scst_user_cmd *ucmd = cmd->dh_priv;
++ int res = SCST_EXEC_COMPLETED;
++
++ 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_payload_len =
++ offsetof(struct scst_user_get_cmd, exec_cmd) +
++ sizeof(ucmd->user_cmd.exec_cmd);
++ 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_t(int, SCST_MAX_CDB_SIZE, cmd->cdb_len));
++ 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 / HZ;
++ ucmd->user_cmd.exec_cmd.p_out_buf = ucmd->ubuff +
++ (cmd->sg_cnt << PAGE_SHIFT);
++ ucmd->user_cmd.exec_cmd.out_bufflen = cmd->out_bufflen;
++ ucmd->user_cmd.exec_cmd.sn = cmd->tgt_sn;
++
++ ucmd->state = UCMD_STATE_EXECING;
++
++ dev_user_add_to_ready(ucmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void dev_user_free_sgv(struct scst_user_cmd *ucmd)
++{
++ if (ucmd->sgv != NULL) {
++ sgv_pool_free(ucmd->sgv, &ucmd->dev->udev_mem_lim);
++ ucmd->sgv = NULL;
++ } else if (ucmd->data_pages != NULL) {
++ /* We mapped pages, but for some reason didn't allocate them */
++ ucmd_get(ucmd);
++ __dev_user_free_sg_entries(ucmd);
++ }
++ return;
++}
++
++static void dev_user_on_free_cmd(struct scst_cmd *cmd)
++{
++ struct scst_user_cmd *ucmd = cmd->dh_priv;
++
++ TRACE_ENTRY();
++
++ if (unlikely(ucmd == NULL))
++ goto out;
++
++ TRACE_MEM("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! */
++ goto out_reply;
++ }
++
++ if (unlikely(!ucmd->seen_by_user)) {
++ TRACE_MGMT_DBG("Not seen by user ucmd %p", ucmd);
++ goto out_reply;
++ }
++
++ ucmd->user_cmd_payload_len =
++ offsetof(struct scst_user_get_cmd, on_free_cmd) +
++ sizeof(ucmd->user_cmd.on_free_cmd);
++ 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.aborted = ucmd->aborted;
++ ucmd->user_cmd.on_free_cmd.status = cmd->status;
++ ucmd->user_cmd.on_free_cmd.delivery_status = cmd->delivery_status;
++
++ ucmd->state = UCMD_STATE_ON_FREEING;
++
++ dev_user_add_to_ready(ucmd);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_reply:
++ dev_user_process_reply_on_free(ucmd);
++ goto out;
++}
++
++static void dev_user_set_block(struct scst_cmd *cmd, int block)
++{
++ struct scst_user_dev *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 scst_user_cmd *ucmd)
++{
++ struct scst_user_dev *dev = ucmd->dev;
++ unsigned long flags;
++ int do_wake = in_interrupt();
++
++ TRACE_ENTRY();
++
++ if (ucmd->cmd)
++ do_wake |= ucmd->cmd->preprocessing_only;
++
++ spin_lock_irqsave(&dev->udev_cmd_threads.cmd_list_lock, flags);
++
++ ucmd->this_state_unjammed = 0;
++
++ if ((ucmd->state == UCMD_STATE_PARSING) ||
++ (ucmd->state == UCMD_STATE_BUF_ALLOCING)) {
++ /*
++ * If we don't put such commands in the queue head, then under
++ * high load we might delay threads, waiting for memory
++ * allocations, for too long and start loosing NOPs, which
++ * would lead to consider us by remote initiators as
++ * unresponsive and stuck => broken connections, etc. If none
++ * of our commands completed in NOP timeout to allow the head
++ * commands to go, then we are really overloaded and/or stuck.
++ */
++ TRACE_DBG("Adding ucmd %p (state %d) to head of ready "
++ "cmd list", ucmd, ucmd->state);
++ list_add(&ucmd->ready_cmd_list_entry,
++ &dev->ready_cmd_list);
++ } else if (unlikely(ucmd->state == UCMD_STATE_TM_EXECING) ||
++ unlikely(ucmd->state == UCMD_STATE_ATTACH_SESS) ||
++ unlikely(ucmd->state == UCMD_STATE_DETACH_SESS)) {
++ TRACE_MGMT_DBG("Adding mgmt ucmd %p (state %d) to head of "
++ "ready cmd list", ucmd, ucmd->state);
++ list_add(&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 HQ ucmd %p to head of 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);
++ }
++ do_wake |= ((ucmd->state == UCMD_STATE_ON_CACHE_FREEING) ||
++ (ucmd->state == UCMD_STATE_ON_FREEING));
++ }
++
++ if (do_wake) {
++ TRACE_DBG("Waking up dev %p", dev);
++ wake_up(&dev->udev_cmd_threads.cmd_list_waitQ);
++ }
++
++ spin_unlock_irqrestore(&dev->udev_cmd_threads.cmd_list_lock, flags);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int dev_user_map_buf(struct scst_user_cmd *ucmd, unsigned long ubuff,
++ int num_pg)
++{
++ int res = 0, rc;
++ int i;
++ struct task_struct *tsk = current;
++
++ TRACE_ENTRY();
++
++ if (unlikely(ubuff == 0))
++ goto out_nomem;
++
++ BUG_ON(ucmd->data_pages != NULL);
++
++ ucmd->num_data_pages = num_pg;
++
++ ucmd->data_pages =
++ kmalloc(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 != NULL) ? ucmd->cmd->bufflen : -1);
++
++ down_read(&tsk->mm->mmap_sem);
++ rc = get_user_pages(tsk, tsk->mm, ubuff, ucmd->num_data_pages,
++ 1/*writable*/, 0/*don't force*/, ucmd->data_pages, NULL);
++ up_read(&tsk->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:
++ if (ucmd->cmd != NULL)
++ scst_set_busy(ucmd->cmd);
++ /* go through */
++
++out_err:
++ if (ucmd->cmd != NULL)
++ scst_set_cmd_abnormal_done_state(ucmd->cmd);
++ goto out;
++
++out_unmap:
++ PRINT_ERROR("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;
++ if (ucmd->cmd != NULL)
++ scst_set_cmd_error(ucmd->cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_err;
++}
++
++static int dev_user_process_reply_alloc(struct scst_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 %llx", 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("Supplied pbuf %llx 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);
++ scst_set_cmd_abnormal_done_state(ucmd->cmd);
++ }
++
++out_process:
++ scst_post_alloc_data_buf(cmd);
++ scst_process_active_cmd(cmd, false);
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_hwerr:
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ scst_set_cmd_abnormal_done_state(ucmd->cmd);
++ res = -EINVAL;
++ goto out_process;
++}
++
++static int dev_user_process_reply_parse(struct scst_user_cmd *ucmd,
++ struct scst_user_reply_cmd *reply)
++{
++ int res = 0, rc;
++ struct scst_user_scsi_cmd_reply_parse *preply =
++ &reply->parse_reply;
++ struct scst_cmd *cmd = ucmd->cmd;
++
++ TRACE_ENTRY();
++
++ if (preply->status != 0)
++ goto out_status;
++
++ 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_BIDI) &&
++ (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->out_bufflen < 0) ||
++ (preply->data_len < 0)))
++ goto out_inval;
++
++ if (unlikely(preply->cdb_len > cmd->cdb_len))
++ goto out_inval;
++
++ if (!(preply->op_flags & SCST_INFO_VALID))
++ goto out_inval;
++
++ TRACE_DBG("ucmd %p, queue_type %x, data_direction, %x, bufflen %d, "
++ "data_len %d, pbuf %llx, cdb_len %d, op_flags %x", ucmd,
++ preply->queue_type, preply->data_direction, preply->bufflen,
++ preply->data_len, reply->alloc_reply.pbuf, preply->cdb_len,
++ preply->op_flags);
++
++ cmd->queue_type = preply->queue_type;
++ cmd->data_direction = preply->data_direction;
++ cmd->bufflen = preply->bufflen;
++ cmd->out_bufflen = preply->out_bufflen;
++ cmd->data_len = preply->data_len;
++ if (preply->cdb_len > 0)
++ cmd->cdb_len = preply->cdb_len;
++ cmd->op_flags = preply->op_flags;
++
++out_process:
++ scst_post_parse(cmd);
++ scst_process_active_cmd(cmd, false);
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_inval:
++ PRINT_ERROR("Invalid parse_reply parameters (LUN %lld, op %x, cmd %p)",
++ (long long unsigned int)cmd->lun, cmd->cdb[0], cmd);
++ PRINT_BUFFER("Invalid parse_reply", reply, sizeof(*reply));
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ res = -EINVAL;
++ goto out_abnormal;
++
++out_hwerr_res_set:
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++
++out_abnormal:
++ scst_set_cmd_abnormal_done_state(cmd);
++ goto out_process;
++
++out_status:
++ TRACE_DBG("ucmd %p returned with error from user status %x",
++ ucmd, preply->status);
++
++ if (preply->sense_len != 0) {
++ int sense_len;
++
++ res = scst_alloc_sense(cmd, 0);
++ if (res != 0)
++ goto out_hwerr_res_set;
++
++ sense_len = min_t(int, cmd->sense_buflen, preply->sense_len);
++
++ rc = copy_from_user(cmd->sense,
++ (void __user *)(unsigned long)preply->psense_buffer,
++ sense_len);
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d sense's bytes", rc);
++ res = -EFAULT;
++ goto out_hwerr_res_set;
++ }
++ cmd->sense_valid_len = sense_len;
++ }
++ scst_set_cmd_error_status(cmd, preply->status);
++ goto out_abnormal;
++}
++
++static int dev_user_process_reply_on_free(struct scst_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 scst_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 scst_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;
++ /*
++ * background_exec assignment must be after ucmd get.
++ * Otherwise, due to reorder, in dev_user_process_reply()
++ * it is possible that ucmd is destroyed before it "got" here.
++ */
++ ucmd_get(ucmd);
++ 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);
++
++ cmd->atomic = 0;
++
++ 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("Supplied pbuf %llx 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) {
++ /*
++ * We have an empty SG, so can't call
++ * scst_set_resp_data_len()
++ */
++ cmd->resp_data_len = ereply->resp_data_len;
++ cmd->resid_possible = 1;
++ } else
++ scst_set_resp_data_len(cmd, ereply->resp_data_len);
++ }
++
++ cmd->status = ereply->status;
++ if (ereply->sense_len != 0) {
++ int sense_len, rc;
++
++ res = scst_alloc_sense(cmd, 0);
++ if (res != 0)
++ goto out_compl;
++
++ sense_len = min_t(int, cmd->sense_buflen, ereply->sense_len);
++
++ rc = copy_from_user(cmd->sense,
++ (void __user *)(unsigned long)ereply->psense_buffer,
++ sense_len);
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d sense's bytes", rc);
++ res = -EFAULT;
++ goto out_hwerr_res_set;
++ }
++ cmd->sense_valid_len = sense_len;
++ }
++
++out_compl:
++ cmd->completed = 1;
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_DIRECT);
++ /* !! At this point cmd can be already freed !! */
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_inval:
++ PRINT_ERROR("Invalid exec_reply parameters (LUN %lld, op %x, cmd %p)",
++ (long long unsigned int)cmd->lun, cmd->cdb[0], cmd);
++ PRINT_BUFFER("Invalid exec_reply", reply, sizeof(*reply));
++
++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 scst_user_cmd *ucmd;
++ int state;
++
++ TRACE_ENTRY();
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ ucmd = __ucmd_find_hash(dev, reply->cmd_h);
++ if (unlikely(ucmd == NULL)) {
++ TRACE_MGMT_DBG("cmd_h %d not found", reply->cmd_h);
++ res = -ESRCH;
++ goto out_unlock;
++ }
++
++ if (unlikely(ucmd_get_check(ucmd))) {
++ TRACE_MGMT_DBG("Found being destroyed cmd_h %d", reply->cmd_h);
++ res = -ESRCH;
++ goto out_unlock;
++ }
++
++ /* To sync. with dev_user_process_reply_exec(). See comment there. */
++ smp_mb();
++ if (ucmd->background_exec) {
++ state = UCMD_STATE_EXECING;
++ goto unlock_process;
++ }
++
++ if (unlikely(ucmd->this_state_unjammed)) {
++ TRACE_MGMT_DBG("Reply on unjammed ucmd %p, ignoring",
++ ucmd);
++ goto out_unlock_put;
++ }
++
++ if (unlikely(!ucmd->sent_to_user)) {
++ TRACE_MGMT_DBG("Ucmd %p isn't in the sent to user "
++ "state %x", ucmd, ucmd->state);
++ res = -EINVAL;
++ goto out_unlock_put;
++ }
++
++ if (unlikely(reply->subcode != ucmd->user_cmd.subcode))
++ goto out_wrong_state;
++
++ if (unlikely(_IOC_NR(reply->subcode) != ucmd->state))
++ goto out_wrong_state;
++
++ state = ucmd->state;
++ ucmd->sent_to_user = 0;
++
++unlock_process:
++ spin_unlock_irq(&dev->udev_cmd_threads.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:
++ BUG();
++ break;
++ }
++
++out_put:
++ ucmd_put(ucmd);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_wrong_state:
++ PRINT_ERROR("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_put:
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ goto out_put;
++
++out_unlock:
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ goto out;
++}
++
++static int dev_user_reply_cmd(struct file *file, void __user *arg)
++{
++ int res = 0, rc;
++ struct scst_user_dev *dev;
++ struct scst_user_reply_cmd reply;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (unlikely(res != 0)) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ rc = copy_from_user(&reply, arg, sizeof(reply));
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ res = -EFAULT;
++ goto out_up;
++ }
++
++ TRACE_DBG("Reply for dev %s", dev->name);
++
++ TRACE_BUFFER("Reply", &reply, sizeof(reply));
++
++ res = dev_user_process_reply(dev, &reply);
++ if (unlikely(res < 0))
++ goto out_up;
++
++out_up:
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int dev_user_get_ext_cdb(struct file *file, void __user *arg)
++{
++ int res = 0, rc;
++ struct scst_user_dev *dev;
++ struct scst_user_cmd *ucmd;
++ struct scst_cmd *cmd = NULL;
++ struct scst_user_get_ext_cdb get;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (unlikely(res != 0)) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ rc = copy_from_user(&get, arg, sizeof(get));
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ res = -EFAULT;
++ goto out_up;
++ }
++
++ TRACE_MGMT_DBG("Get ext cdb for dev %s", dev->name);
++
++ TRACE_BUFFER("Get ext cdb", &get, sizeof(get));
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ ucmd = __ucmd_find_hash(dev, get.cmd_h);
++ if (unlikely(ucmd == NULL)) {
++ TRACE_MGMT_DBG("cmd_h %d not found", get.cmd_h);
++ res = -ESRCH;
++ goto out_unlock;
++ }
++
++ if (unlikely(ucmd_get_check(ucmd))) {
++ TRACE_MGMT_DBG("Found being destroyed cmd_h %d", get.cmd_h);
++ res = -ESRCH;
++ goto out_unlock;
++ }
++
++ if ((ucmd->cmd != NULL) && (ucmd->state <= UCMD_STATE_EXECING) &&
++ (ucmd->sent_to_user || ucmd->background_exec)) {
++ cmd = ucmd->cmd;
++ scst_cmd_get(cmd);
++ } else {
++ TRACE_MGMT_DBG("Invalid ucmd state %d for cmd_h %d",
++ ucmd->state, get.cmd_h);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ if (cmd == NULL)
++ goto out_put;
++
++ BUILD_BUG_ON(sizeof(cmd->cdb_buf) != SCST_MAX_CDB_SIZE);
++
++ if (cmd->cdb_len <= SCST_MAX_CDB_SIZE)
++ goto out_cmd_put;
++
++ EXTRACHECKS_BUG_ON(cmd->cdb_buf == cmd->cdb_buf);
++
++ TRACE_BUFFER("EXT CDB", &cmd->cdb[sizeof(cmd->cdb_buf)],
++ cmd->cdb_len - sizeof(cmd->cdb_buf));
++ rc = copy_to_user((void __user *)(unsigned long)get.ext_cdb_buffer,
++ &cmd->cdb[sizeof(cmd->cdb_buf)],
++ cmd->cdb_len - sizeof(cmd->cdb_buf));
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Failed to copy to user %d bytes", rc);
++ res = -EFAULT;
++ goto out_cmd_put;
++ }
++
++out_cmd_put:
++ scst_cmd_put(cmd);
++
++out_put:
++ ucmd_put(ucmd);
++
++out_up:
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unlock:
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ goto out_up;
++}
++
++static int dev_user_process_scst_commands(struct scst_user_dev *dev)
++ __releases(&dev->udev_cmd_threads.cmd_list_lock)
++ __acquires(&dev->udev_cmd_threads.cmd_list_lock)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ while (!list_empty(&dev->udev_cmd_threads.active_cmd_list)) {
++ struct scst_cmd *cmd = list_entry(
++ dev->udev_cmd_threads.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->udev_cmd_threads.cmd_list_lock);
++ scst_process_active_cmd(cmd, false);
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ res++;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Called under udev_cmd_threads.cmd_list_lock and IRQ off */
++static struct scst_user_cmd *__dev_user_get_next_cmd(struct list_head *cmd_list)
++ __releases(&dev->udev_cmd_threads.cmd_list_lock)
++ __acquires(&dev->udev_cmd_threads.cmd_list_lock)
++{
++ struct scst_user_cmd *u;
++
++again:
++ u = NULL;
++ 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->this_state_unjammed);
++
++ if (u->cmd != NULL) {
++ if (u->state == UCMD_STATE_EXECING) {
++ struct scst_user_dev *dev = u->dev;
++ int rc;
++
++ EXTRACHECKS_BUG_ON(u->jammed);
++
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ rc = scst_check_local_events(u->cmd);
++ if (unlikely(rc != 0)) {
++ u->cmd->scst_cmd_done(u->cmd,
++ SCST_CMD_STATE_DEFAULT,
++ SCST_CONTEXT_DIRECT);
++ /*
++ * !! At this point cmd & u can be !!
++ * !! already freed !!
++ */
++ spin_lock_irq(
++ &dev->udev_cmd_threads.cmd_list_lock);
++ goto again;
++ }
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ } else if (unlikely(test_bit(SCST_CMD_ABORTED,
++ &u->cmd->cmd_flags))) {
++ switch (u->state) {
++ case UCMD_STATE_PARSING:
++ case UCMD_STATE_BUF_ALLOCING:
++ TRACE_MGMT_DBG("Aborting ucmd %p", u);
++ dev_user_unjam_cmd(u, 0, NULL);
++ goto again;
++ case UCMD_STATE_EXECING:
++ EXTRACHECKS_BUG_ON(1);
++ }
++ }
++ }
++ u->sent_to_user = 1;
++ u->seen_by_user = 1;
++ }
++ return u;
++}
++
++static inline int test_cmd_threads(struct scst_user_dev *dev)
++{
++ int res = !list_empty(&dev->udev_cmd_threads.active_cmd_list) ||
++ !list_empty(&dev->ready_cmd_list) ||
++ !dev->blocking || dev->cleanup_done ||
++ signal_pending(current);
++ return res;
++}
++
++/* Called under udev_cmd_threads.cmd_list_lock and IRQ off */
++static int dev_user_get_next_cmd(struct scst_user_dev *dev,
++ struct scst_user_cmd **ucmd)
++{
++ int res = 0;
++ wait_queue_t wait;
++
++ TRACE_ENTRY();
++
++ init_waitqueue_entry(&wait, current);
++
++ while (1) {
++ if (!test_cmd_threads(dev)) {
++ add_wait_queue_exclusive_head(
++ &dev->udev_cmd_threads.cmd_list_waitQ,
++ &wait);
++ for (;;) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (test_cmd_threads(dev))
++ break;
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ schedule();
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&dev->udev_cmd_threads.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 int dev_user_reply_get_cmd(struct file *file, void __user *arg)
++{
++ int res = 0, rc;
++ struct scst_user_dev *dev;
++ struct scst_user_get_cmd *cmd;
++ struct scst_user_reply_cmd *reply;
++ struct scst_user_cmd *ucmd;
++ uint64_t ureply;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (unlikely(res != 0)) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ /* get_user() can't be used with 64-bit values on x86_32 */
++ rc = copy_from_user(&ureply, (uint64_t __user *)
++ &((struct scst_user_get_cmd __user *)arg)->preply,
++ sizeof(ureply));
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ res = -EFAULT;
++ goto out_up;
++ }
++
++ TRACE_DBG("ureply %lld (dev %s)", (long long unsigned int)ureply,
++ dev->name);
++
++ cmd = kmem_cache_alloc(user_get_cmd_cachep, GFP_KERNEL);
++ if (unlikely(cmd == NULL)) {
++ res = -ENOMEM;
++ goto out_up;
++ }
++
++ if (ureply != 0) {
++ unsigned long u = (unsigned long)ureply;
++ reply = (struct scst_user_reply_cmd *)cmd;
++ rc = copy_from_user(reply, (void __user *)u, sizeof(*reply));
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ res = -EFAULT;
++ goto out_free;
++ }
++
++ TRACE_BUFFER("Reply", reply, sizeof(*reply));
++
++ res = dev_user_process_reply(dev, reply);
++ if (unlikely(res < 0))
++ goto out_free;
++ }
++
++ kmem_cache_free(user_get_cmd_cachep, cmd);
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++again:
++ res = dev_user_get_next_cmd(dev, &ucmd);
++ if (res == 0) {
++ int len;
++ /*
++ * A misbehaving user space handler can make ucmd to get dead
++ * immediately after we released the lock, which can lead to
++ * copy of dead data to the user space, which can lead to a
++ * leak of sensitive information.
++ */
++ if (unlikely(ucmd_get_check(ucmd))) {
++ /* Oops, this ucmd is already being destroyed. Retry. */
++ goto again;
++ }
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ EXTRACHECKS_BUG_ON(ucmd->user_cmd_payload_len == 0);
++
++ len = ucmd->user_cmd_payload_len;
++ TRACE_DBG("ucmd %p (user_cmd %p), payload_len %d (len %d)",
++ ucmd, &ucmd->user_cmd, ucmd->user_cmd_payload_len, len);
++ TRACE_BUFFER("UCMD", &ucmd->user_cmd, len);
++ rc = copy_to_user(arg, &ucmd->user_cmd, len);
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Copy to user failed (%d), requeuing ucmd "
++ "%p back to head of ready cmd list", rc, ucmd);
++ res = -EFAULT;
++ /* Requeue ucmd back */
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ list_add(&ucmd->ready_cmd_list_entry,
++ &dev->ready_cmd_list);
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ }
++#ifdef CONFIG_SCST_EXTRACHECKS
++ else
++ ucmd->user_cmd_payload_len = 0;
++#endif
++ ucmd_put(ucmd);
++ } else
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++out_up:
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kmem_cache_free(user_get_cmd_cachep, cmd);
++ goto out_up;
++}
++
++static long dev_user_ioctl(struct file *file, unsigned int cmd,
++ unsigned long arg)
++{
++ long res, rc;
++
++ 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, (void __user *)arg);
++ break;
++
++ case SCST_USER_REPLY_CMD:
++ TRACE_DBG("%s", "REPLY_CMD");
++ res = dev_user_reply_cmd(file, (void __user *)arg);
++ break;
++
++ case SCST_USER_GET_EXTENDED_CDB:
++ TRACE_DBG("%s", "GET_EXTENDED_CDB");
++ res = dev_user_get_ext_cdb(file, (void __user *)arg);
++ 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;
++ }
++ rc = copy_from_user(dev_desc, (void __user *)arg,
++ sizeof(*dev_desc));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %ld user's bytes", rc);
++ res = -EFAULT;
++ kfree(dev_desc);
++ goto out;
++ }
++ TRACE_BUFFER("dev_desc", dev_desc, sizeof(*dev_desc));
++ dev_desc->name[sizeof(dev_desc->name)-1] = '\0';
++ dev_desc->sgv_name[sizeof(dev_desc->sgv_name)-1] = '\0';
++ res = dev_user_register_dev(file, dev_desc);
++ kfree(dev_desc);
++ break;
++ }
++
++ case SCST_USER_UNREGISTER_DEVICE:
++ TRACE_DBG("%s", "UNREGISTER_DEVICE");
++ res = dev_user_unregister_dev(file);
++ break;
++
++ case SCST_USER_FLUSH_CACHE:
++ TRACE_DBG("%s", "FLUSH_CACHE");
++ res = dev_user_flush_cache(file);
++ break;
++
++ case SCST_USER_SET_OPTIONS:
++ {
++ struct scst_user_opt opt;
++ TRACE_DBG("%s", "SET_OPTIONS");
++ rc = copy_from_user(&opt, (void __user *)arg, sizeof(opt));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %ld user's bytes", rc);
++ res = -EFAULT;
++ 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 __user *)arg);
++ break;
++
++ case SCST_USER_DEVICE_CAPACITY_CHANGED:
++ TRACE_DBG("%s", "CAPACITY_CHANGED");
++ res = dev_user_capacity_changed(file);
++ break;
++
++ case SCST_USER_PREALLOC_BUFFER:
++ TRACE_DBG("%s", "PREALLOC_BUFFER");
++ res = dev_user_prealloc_buffer(file, (void __user *)arg);
++ break;
++
++ default:
++ PRINT_ERROR("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_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (unlikely(res != 0)) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ if (!list_empty(&dev->ready_cmd_list) ||
++ !list_empty(&dev->udev_cmd_threads.active_cmd_list)) {
++ res |= POLLIN | POLLRDNORM;
++ goto out_unlock;
++ }
++
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ TRACE_DBG("Before poll_wait() (dev %s)", dev->name);
++ poll_wait(file, &dev->udev_cmd_threads.cmd_list_waitQ, wait);
++ TRACE_DBG("After poll_wait() (dev %s)", dev->name);
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ if (!list_empty(&dev->ready_cmd_list) ||
++ !list_empty(&dev->udev_cmd_threads.active_cmd_list)) {
++ res |= POLLIN | POLLRDNORM;
++ goto out_unlock;
++ }
++
++out_unlock:
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++/*
++ * Called under udev_cmd_threads.cmd_list_lock, but can drop it inside,
++ * then reacquire.
++ */
++static void dev_user_unjam_cmd(struct scst_user_cmd *ucmd, int busy,
++ unsigned long *flags)
++ __releases(&dev->udev_cmd_threads.cmd_list_lock)
++ __acquires(&dev->udev_cmd_threads.cmd_list_lock)
++{
++ int state = ucmd->state;
++ struct scst_user_dev *dev = ucmd->dev;
++
++ TRACE_ENTRY();
++
++ if (ucmd->this_state_unjammed)
++ goto out;
++
++ TRACE_MGMT_DBG("Unjamming ucmd %p (busy %d, state %x)", ucmd, busy,
++ state);
++
++ ucmd->jammed = 1;
++ ucmd->this_state_unjammed = 1;
++ ucmd->sent_to_user = 0;
++
++ switch (state) {
++ case UCMD_STATE_PARSING:
++ case UCMD_STATE_BUF_ALLOCING:
++ if (test_bit(SCST_CMD_ABORTED, &ucmd->cmd->cmd_flags))
++ ucmd->aborted = 1;
++ else {
++ if (busy)
++ scst_set_busy(ucmd->cmd);
++ else
++ scst_set_cmd_error(ucmd->cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ scst_set_cmd_abnormal_done_state(ucmd->cmd);
++
++ if (state == UCMD_STATE_PARSING)
++ scst_post_parse(ucmd->cmd);
++ else
++ scst_post_alloc_data_buf(ucmd->cmd);
++
++ TRACE_MGMT_DBG("Adding ucmd %p to active list", ucmd);
++ list_add(&ucmd->cmd->cmd_list_entry,
++ &ucmd->cmd->cmd_threads->active_cmd_list);
++ wake_up(&ucmd->cmd->cmd_threads->cmd_list_waitQ);
++ break;
++
++ case UCMD_STATE_EXECING:
++ if (flags != NULL)
++ spin_unlock_irqrestore(&dev->udev_cmd_threads.cmd_list_lock,
++ *flags);
++ else
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ TRACE_MGMT_DBG("EXEC: unjamming ucmd %p", ucmd);
++
++ if (test_bit(SCST_CMD_ABORTED, &ucmd->cmd->cmd_flags))
++ ucmd->aborted = 1;
++ else {
++ if (busy)
++ scst_set_busy(ucmd->cmd);
++ else
++ scst_set_cmd_error(ucmd->cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++
++ ucmd->cmd->scst_cmd_done(ucmd->cmd, SCST_CMD_STATE_DEFAULT,
++ SCST_CONTEXT_THREAD);
++ /* !! At this point cmd and ucmd can be already freed !! */
++
++ if (flags != NULL)
++ spin_lock_irqsave(&dev->udev_cmd_threads.cmd_list_lock,
++ *flags);
++ else
++ spin_lock_irq(&dev->udev_cmd_threads.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->udev_cmd_threads.cmd_list_lock,
++ *flags);
++ else
++ spin_unlock_irq(&dev->udev_cmd_threads.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->udev_cmd_threads.cmd_list_lock,
++ *flags);
++ else
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ break;
++
++ default:
++ PRINT_CRIT_ERROR("Wrong ucmd state %x", state);
++ BUG();
++ break;
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int dev_user_unjam_dev(struct scst_user_dev *dev)
++ __releases(&dev->udev_cmd_threads.cmd_list_lock)
++ __acquires(&dev->udev_cmd_threads.cmd_list_lock)
++{
++ int i, res = 0;
++ struct scst_user_cmd *ucmd;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Unjamming dev %p", dev);
++
++ sgv_pool_flush(dev->pool);
++ sgv_pool_flush(dev->pool_clust);
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++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) {
++ res++;
++
++ if (!ucmd->sent_to_user)
++ continue;
++
++ if (ucmd_get_check(ucmd))
++ continue;
++
++ TRACE_MGMT_DBG("ucmd %p, state %x, scst_cmd %p", ucmd,
++ ucmd->state, ucmd->cmd);
++
++ dev_user_unjam_cmd(ucmd, 0, NULL);
++
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ ucmd_put(ucmd);
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ goto repeat;
++ }
++ }
++
++ if (dev_user_process_scst_commands(dev) != 0)
++ goto repeat;
++
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int dev_user_process_reply_tm_exec(struct scst_user_cmd *ucmd,
++ int status)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("TM reply (ucmd %p, fn %d, status %d)", ucmd,
++ ucmd->user_cmd.tm_cmd.fn, status);
++
++ if (status == SCST_MGMT_STATUS_TASK_NOT_EXIST) {
++ /*
++ * It is possible that user space seen TM cmd before cmd
++ * to abort or will never see it at all, because it was
++ * aborted on the way there. So, it is safe to return
++ * success instead, because, if there is the TM cmd at this
++ * point, then the cmd to abort apparrently does exist.
++ */
++ status = SCST_MGMT_STATUS_SUCCESS;
++ }
++
++ scst_async_mcmd_completed(ucmd->mcmd, status);
++
++ ucmd_put(ucmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void dev_user_abort_ready_commands(struct scst_user_dev *dev)
++{
++ struct scst_user_cmd *ucmd;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ spin_lock_irqsave(&dev->udev_cmd_threads.cmd_list_lock, flags);
++again:
++ list_for_each_entry(ucmd, &dev->ready_cmd_list, ready_cmd_list_entry) {
++ if ((ucmd->cmd != NULL) && !ucmd->seen_by_user &&
++ test_bit(SCST_CMD_ABORTED, &ucmd->cmd->cmd_flags)) {
++ switch (ucmd->state) {
++ case UCMD_STATE_PARSING:
++ case UCMD_STATE_BUF_ALLOCING:
++ case UCMD_STATE_EXECING:
++ TRACE_MGMT_DBG("Aborting ready ucmd %p", ucmd);
++ list_del(&ucmd->ready_cmd_list_entry);
++ dev_user_unjam_cmd(ucmd, 0, &flags);
++ goto again;
++ }
++ }
++ }
++
++ spin_unlock_irqrestore(&dev->udev_cmd_threads.cmd_list_lock, flags);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Can be called under some spinlock and IRQs off */
++static int dev_user_task_mgmt_fn(struct scst_mgmt_cmd *mcmd,
++ struct scst_tgt_dev *tgt_dev)
++{
++ struct scst_user_cmd *ucmd;
++ struct scst_user_dev *dev = tgt_dev->dev->dh_priv;
++ struct scst_user_cmd *ucmd_to_abort = NULL;
++
++ TRACE_ENTRY();
++
++ /*
++ * In the used approach we don't do anything with hung devices, which
++ * stopped responding and/or have stuck commands. We forcedly abort such
++ * commands only if they not yet sent to the user space or if the device
++ * is getting unloaded, e.g. if its handler program gets killed. This is
++ * because it's pretty hard to distinguish between stuck and temporary
++ * overloaded states of the device. There are several reasons for that:
++ *
++ * 1. Some commands need a lot of time to complete (several hours),
++ * so for an impatient user such command(s) will always look as
++ * stuck.
++ *
++ * 2. If we forcedly abort, i.e. abort before it's actually completed
++ * in the user space, just one command, we will have to put the whole
++ * device offline until we are sure that no more previously aborted
++ * commands will get executed. Otherwise, we might have a possibility
++ * for data corruption, when aborted and reported as completed
++ * command actually gets executed *after* new commands sent
++ * after the force abort was done. Many journaling file systems and
++ * databases use "provide required commands order via queue draining"
++ * approach and not putting the whole device offline after the forced
++ * abort will break it. This makes our decision, if a command stuck
++ * or not, cost a lot.
++ *
++ * So, we leave policy definition if a device stuck or not to
++ * the user space and simply let all commands live until they are
++ * completed or their devices get closed/killed. This approach is very
++ * much OK, but can affect management commands, which need activity
++ * suspending via scst_suspend_activity() function such as devices or
++ * targets registration/removal. But during normal life such commands
++ * should be rare. Plus, when possible, scst_suspend_activity() will
++ * return after timeout EBUSY status to allow caller to not stuck
++ * forever as well.
++ *
++ * But, anyway, ToDo, we should reimplement that in the SCST core, so
++ * stuck commands would affect only related devices.
++ */
++
++ dev_user_abort_ready_commands(dev);
++
++ /* We can't afford missing TM command due to memory shortage */
++ ucmd = dev_user_alloc_ucmd(dev, GFP_ATOMIC|__GFP_NOFAIL);
++ if (ucmd == NULL) {
++ PRINT_CRIT_ERROR("Unable to allocate TM %d message "
++ "(dev %s)", mcmd->fn, dev->name);
++ goto out;
++ }
++
++ ucmd->user_cmd_payload_len =
++ offsetof(struct scst_user_get_cmd, tm_cmd) +
++ sizeof(ucmd->user_cmd.tm_cmd);
++ 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;
++ ucmd->user_cmd.tm_cmd.cmd_sn = mcmd->cmd_sn;
++ ucmd->user_cmd.tm_cmd.cmd_sn_set = mcmd->cmd_sn_set;
++
++ if (mcmd->cmd_to_abort != NULL) {
++ ucmd_to_abort = 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 %p, cmd_h_to_abort %d, mcmd %p)", ucmd, ucmd->h,
++ mcmd->fn, mcmd->cmd_to_abort, ucmd_to_abort,
++ ucmd->user_cmd.tm_cmd.cmd_h_to_abort, mcmd);
++
++ ucmd->mcmd = mcmd;
++ ucmd->state = UCMD_STATE_TM_EXECING;
++
++ scst_prepare_async_mcmd(mcmd);
++
++ dev_user_add_to_ready(ucmd);
++
++out:
++ TRACE_EXIT();
++ return SCST_DEV_TM_NOT_COMPLETED;
++}
++
++static int dev_user_attach(struct scst_device *sdev)
++{
++ int res = 0;
++ struct scst_user_dev *dev = NULL, *d;
++
++ TRACE_ENTRY();
++
++ spin_lock(&dev_list_lock);
++ list_for_each_entry(d, &dev_list, dev_list_entry) {
++ if (strcmp(d->name, sdev->virt_name) == 0) {
++ dev = d;
++ break;
++ }
++ }
++ spin_unlock(&dev_list_lock);
++ if (dev == NULL) {
++ PRINT_ERROR("Device %s not found", sdev->virt_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ sdev->dh_priv = dev;
++ sdev->tst = dev->tst;
++ sdev->queue_alg = dev->queue_alg;
++ sdev->swp = dev->swp;
++ sdev->tas = dev->tas;
++ sdev->d_sense = dev->d_sense;
++ sdev->has_own_order_mgmt = dev->has_own_order_mgmt;
++
++ dev->sdev = sdev;
++
++ PRINT_INFO("Attached user space virtual device \"%s\"",
++ dev->name);
++
++out:
++ TRACE_EXIT();
++ return res;
++}
++
++static void dev_user_detach(struct scst_device *sdev)
++{
++ struct scst_user_dev *dev = sdev->dh_priv;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("virt_id %d", sdev->virt_id);
++
++ PRINT_INFO("Detached user space virtual device \"%s\"",
++ dev->name);
++
++ /* dev will be freed by the caller */
++ sdev->dh_priv = NULL;
++ dev->sdev = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++static int dev_user_process_reply_sess(struct scst_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->udev_cmd_threads.cmd_list_lock, flags);
++
++ if (ucmd->state == UCMD_STATE_ATTACH_SESS) {
++ TRACE_MGMT_DBG("%s", "ATTACH_SESS finished");
++ ucmd->result = status;
++ } else if (ucmd->state == UCMD_STATE_DETACH_SESS) {
++ TRACE_MGMT_DBG("%s", "DETACH_SESS finished");
++ } else
++ BUG();
++
++ if (ucmd->cmpl != NULL)
++ complete_all(ucmd->cmpl);
++
++ spin_unlock_irqrestore(&ucmd->dev->udev_cmd_threads.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 = tgt_dev->dev->dh_priv;
++ int res = 0, rc;
++ struct scst_user_cmd *ucmd;
++ DECLARE_COMPLETION_ONSTACK(cmpl);
++ struct scst_tgt_template *tgtt = tgt_dev->sess->tgt->tgtt;
++ struct scst_tgt *tgt = tgt_dev->sess->tgt;
++
++ TRACE_ENTRY();
++
++ tgt_dev->active_cmd_threads = &dev->udev_cmd_threads;
++
++ /*
++ * We can't replace tgt_dev->pool, because it can be used to allocate
++ * memory for SCST local commands, like REPORT LUNS, where there is no
++ * corresponding ucmd. Otherwise we will crash in dev_user_alloc_sg().
++ */
++ if (test_bit(SCST_TGT_DEV_CLUST_POOL, &tgt_dev->tgt_dev_flags))
++ tgt_dev->dh_priv = dev->pool_clust;
++ else
++ tgt_dev->dh_priv = dev->pool;
++
++ ucmd = dev_user_alloc_ucmd(dev, GFP_KERNEL);
++ if (ucmd == NULL)
++ goto out_nomem;
++
++ ucmd->cmpl = &cmpl;
++
++ ucmd->user_cmd_payload_len = offsetof(struct scst_user_get_cmd, sess) +
++ sizeof(ucmd->user_cmd.sess);
++ 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.threads_num = tgt_dev->sess->tgt->tgtt->threads_num;
++ ucmd->user_cmd.sess.rd_only = tgt_dev->acg_dev->rd_only;
++ if (tgtt->get_phys_transport_version != NULL)
++ ucmd->user_cmd.sess.phys_transport_version =
++ tgtt->get_phys_transport_version(tgt);
++ if (tgtt->get_scsi_transport_version != NULL)
++ ucmd->user_cmd.sess.scsi_transport_version =
++ tgtt->get_scsi_transport_version(tgt);
++ strlcpy(ucmd->user_cmd.sess.initiator_name,
++ tgt_dev->sess->initiator_name,
++ sizeof(ucmd->user_cmd.sess.initiator_name)-1);
++ strlcpy(ucmd->user_cmd.sess.target_name,
++ tgt_dev->sess->tgt->tgt_name,
++ sizeof(ucmd->user_cmd.sess.target_name)-1);
++
++ TRACE_MGMT_DBG("Preparing ATTACH_SESS %p (h %d, sess_h %llx, LUN %llx, "
++ "threads_num %d, rd_only %d, initiator %s, target %s)",
++ ucmd, ucmd->h, ucmd->user_cmd.sess.sess_h,
++ ucmd->user_cmd.sess.lun, ucmd->user_cmd.sess.threads_num,
++ ucmd->user_cmd.sess.rd_only, ucmd->user_cmd.sess.initiator_name,
++ ucmd->user_cmd.sess.target_name);
++
++ ucmd->state = UCMD_STATE_ATTACH_SESS;
++
++ ucmd_get(ucmd);
++
++ 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("%s", "ATTACH_SESS command timeout");
++ res = -EFAULT;
++ }
++
++ BUG_ON(irqs_disabled());
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++ ucmd->cmpl = NULL;
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ ucmd_put(ucmd);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_nomem:
++ res = -ENOMEM;
++ goto out;
++}
++
++static void dev_user_detach_tgt(struct scst_tgt_dev *tgt_dev)
++{
++ struct scst_user_dev *dev = tgt_dev->dev->dh_priv;
++ struct scst_user_cmd *ucmd;
++
++ TRACE_ENTRY();
++
++ /*
++ * We can't miss detach command due to memory shortage, because it might
++ * lead to a memory leak in the user space handler.
++ */
++ ucmd = dev_user_alloc_ucmd(dev, GFP_KERNEL|__GFP_NOFAIL);
++ if (ucmd == NULL) {
++ PRINT_CRIT_ERROR("Unable to allocate DETACH_SESS message "
++ "(dev %s)", dev->name);
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("Preparing DETACH_SESS %p (h %d, sess_h %llx)", ucmd,
++ ucmd->h, ucmd->user_cmd.sess.sess_h);
++
++ ucmd->user_cmd_payload_len = offsetof(struct scst_user_get_cmd, sess) +
++ sizeof(ucmd->user_cmd.sess);
++ 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;
++
++ 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.alloc_data_buf = dev_user_alloc_data_buf;
++ 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("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_check_version(const struct scst_user_dev_desc *dev_desc)
++{
++ char str[sizeof(DEV_USER_VERSION) > 20 ? sizeof(DEV_USER_VERSION) : 20];
++ int res = 0, rc;
++
++ rc = copy_from_user(str,
++ (void __user *)(unsigned long)dev_desc->license_str,
++ sizeof(str));
++ if (rc != 0) {
++ PRINT_ERROR("%s", "Unable to get license string");
++ res = -EFAULT;
++ goto out;
++ }
++ str[sizeof(str)-1] = '\0';
++
++ if ((strcmp(str, "GPL") != 0) &&
++ (strcmp(str, "GPL v2") != 0) &&
++ (strcmp(str, "Dual BSD/GPL") != 0) &&
++ (strcmp(str, "Dual MIT/GPL") != 0) &&
++ (strcmp(str, "Dual MPL/GPL") != 0)) {
++ /* ->name already 0-terminated in dev_user_ioctl() */
++ PRINT_ERROR("Unsupported license of user device %s (%s). "
++ "Ask license@scst-tgt.com for more info.",
++ dev_desc->name, str);
++ res = -EPERM;
++ goto out;
++ }
++
++ rc = copy_from_user(str,
++ (void __user *)(unsigned long)dev_desc->version_str,
++ sizeof(str));
++ if (rc != 0) {
++ PRINT_ERROR("%s", "Unable to get version string");
++ res = -EFAULT;
++ goto out;
++ }
++ str[sizeof(str)-1] = '\0';
++
++ if (strcmp(str, DEV_USER_VERSION) != 0) {
++ /* ->name already 0-terminated in dev_user_ioctl() */
++ PRINT_ERROR("Incorrect version of user device %s (%s). "
++ "Expected: %s", dev_desc->name, str,
++ DEV_USER_VERSION);
++ res = -EINVAL;
++ goto out;
++ }
++
++out:
++ return res;
++}
++
++static int dev_user_register_dev(struct file *file,
++ const struct scst_user_dev_desc *dev_desc)
++{
++ int res, i;
++ struct scst_user_dev *dev, *d;
++ int block;
++
++ TRACE_ENTRY();
++
++ res = dev_user_check_version(dev_desc);
++ if (res != 0)
++ goto out;
++
++ switch (dev_desc->type) {
++ case TYPE_DISK:
++ case TYPE_ROM:
++ case TYPE_MOD:
++ if (dev_desc->block_size == 0) {
++ PRINT_ERROR("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;
++ }
++ break;
++ default:
++ block = dev_desc->block_size;
++ break;
++ }
++
++ if (!try_module_get(THIS_MODULE)) {
++ PRINT_ERROR("%s", "Fail to get module");
++ res = -ETXTBSY;
++ goto out;
++ }
++
++ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
++ if (dev == NULL) {
++ res = -ENOMEM;
++ goto out_put;
++ }
++
++ init_rwsem(&dev->dev_rwsem);
++ INIT_LIST_HEAD(&dev->ready_cmd_list);
++ 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]);
++
++ scst_init_threads(&dev->udev_cmd_threads);
++
++ strlcpy(dev->name, dev_desc->name, sizeof(dev->name)-1);
++
++ scst_init_mem_lim(&dev->udev_mem_lim);
++
++ scnprintf(dev->devtype.name, sizeof(dev->devtype.name), "%s",
++ (dev_desc->sgv_name[0] == '\0') ? dev->name :
++ dev_desc->sgv_name);
++ dev->pool = sgv_pool_create(dev->devtype.name, sgv_no_clustering,
++ dev_desc->sgv_single_alloc_pages,
++ dev_desc->sgv_shared,
++ dev_desc->sgv_purge_interval);
++ if (dev->pool == NULL) {
++ res = -ENOMEM;
++ goto out_deinit_threads;
++ }
++ sgv_pool_set_allocator(dev->pool, dev_user_alloc_pages,
++ dev_user_free_sg_entries);
++
++ if (!dev_desc->sgv_disable_clustered_pool) {
++ scnprintf(dev->devtype.name, sizeof(dev->devtype.name),
++ "%s-clust",
++ (dev_desc->sgv_name[0] == '\0') ? dev->name :
++ dev_desc->sgv_name);
++ dev->pool_clust = sgv_pool_create(dev->devtype.name,
++ sgv_tail_clustering,
++ dev_desc->sgv_single_alloc_pages,
++ dev_desc->sgv_shared,
++ dev_desc->sgv_purge_interval);
++ if (dev->pool_clust == NULL) {
++ res = -ENOMEM;
++ goto out_free0;
++ }
++ sgv_pool_set_allocator(dev->pool_clust, dev_user_alloc_pages,
++ dev_user_free_sg_entries);
++ } else {
++ dev->pool_clust = dev->pool;
++ sgv_pool_get(dev->pool_clust);
++ }
++
++ scnprintf(dev->devtype.name, sizeof(dev->devtype.name), "%s",
++ dev->name);
++ dev->devtype.type = dev_desc->type;
++ dev->devtype.threads_num = -1;
++ dev->devtype.parse_atomic = 1;
++ dev->devtype.alloc_data_buf_atomic = 1;
++ dev->devtype.dev_done_atomic = 1;
++ dev->devtype.dev_attrs = dev_user_dev_attrs;
++ dev->devtype.attach = dev_user_attach;
++ dev->devtype.detach = dev_user_detach;
++ dev->devtype.attach_tgt = dev_user_attach_tgt;
++ 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;
++ if (dev_desc->enable_pr_cmds_notifications)
++ dev->devtype.pr_cmds_notifications = 1;
++
++ init_completion(&dev->cleanup_cmpl);
++ dev->block = block;
++ dev->def_block = block;
++
++ res = __dev_user_set_opt(dev, &dev_desc->opt);
++ if (res != 0)
++ goto out_free;
++
++ TRACE_MEM("dev %p, name %s", dev, dev->name);
++
++ spin_lock(&dev_list_lock);
++
++ list_for_each_entry(d, &dev_list, dev_list_entry) {
++ if (strcmp(d->name, dev->name) == 0) {
++ PRINT_ERROR("Device %s already exist",
++ dev->name);
++ res = -EEXIST;
++ spin_unlock(&dev_list_lock);
++ goto out_free;
++ }
++ }
++
++ list_add_tail(&dev->dev_list_entry, &dev_list);
++
++ spin_unlock(&dev_list_lock);
++
++ res = scst_register_virtual_dev_driver(&dev->devtype);
++ if (res < 0)
++ goto out_del_free;
++
++ 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_priv_mutex);
++ if (file->private_data != NULL) {
++ mutex_unlock(&dev_priv_mutex);
++ PRINT_ERROR("%s", "Device already registered");
++ res = -EINVAL;
++ goto out_unreg_drv;
++ }
++ file->private_data = dev;
++ mutex_unlock(&dev_priv_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_del_free:
++ spin_lock(&dev_list_lock);
++ list_del(&dev->dev_list_entry);
++ spin_unlock(&dev_list_lock);
++
++out_free:
++ sgv_pool_del(dev->pool_clust);
++
++out_free0:
++ sgv_pool_del(dev->pool);
++
++out_deinit_threads:
++ scst_deinit_threads(&dev->udev_cmd_threads);
++
++ kfree(dev);
++
++out_put:
++ module_put(THIS_MODULE);
++ goto out;
++}
++
++static int dev_user_unregister_dev(struct file *file)
++{
++ int res;
++ struct scst_user_dev *dev;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (res != 0) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out_up;
++
++ up_read(&dev->dev_rwsem);
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ if (dev == NULL) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out_resume;
++ }
++
++ dev->blocking = 0;
++ wake_up_all(&dev->udev_cmd_threads.cmd_list_waitQ);
++
++ down_write(&dev->dev_rwsem);
++ file->private_data = NULL;
++ mutex_unlock(&dev_priv_mutex);
++
++ dev_user_exit_dev(dev);
++
++ up_write(&dev->dev_rwsem); /* to make lockdep happy */
++
++ kfree(dev);
++
++out_resume:
++ scst_resume_activity();
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_up:
++ up_read(&dev->dev_rwsem);
++ goto out;
++}
++
++static int dev_user_flush_cache(struct file *file)
++{
++ int res;
++ struct scst_user_dev *dev;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (res != 0) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out_up;
++
++ sgv_pool_flush(dev->pool);
++ sgv_pool_flush(dev->pool_clust);
++
++ scst_resume_activity();
++
++out_up:
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int dev_user_capacity_changed(struct file *file)
++{
++ int res;
++ struct scst_user_dev *dev;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (res != 0) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ scst_capacity_data_changed(dev->sdev);
++
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int dev_user_prealloc_buffer(struct file *file, void __user *arg)
++{
++ int res = 0, rc;
++ struct scst_user_dev *dev;
++ union scst_user_prealloc_buffer pre;
++ aligned_u64 pbuf;
++ uint32_t bufflen;
++ struct scst_user_cmd *ucmd;
++ int pages, sg_cnt;
++ struct sgv_pool *pool;
++ struct scatterlist *sg;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (unlikely(res != 0)) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ rc = copy_from_user(&pre.in, arg, sizeof(pre.in));
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ res = -EFAULT;
++ goto out_up;
++ }
++
++ TRACE_MEM("Prealloc buffer with size %dKB for dev %s",
++ pre.in.bufflen / 1024, dev->name);
++ TRACE_BUFFER("Input param", &pre.in, sizeof(pre.in));
++
++ pbuf = pre.in.pbuf;
++ bufflen = pre.in.bufflen;
++
++ ucmd = dev_user_alloc_ucmd(dev, GFP_KERNEL);
++ if (ucmd == NULL) {
++ res = -ENOMEM;
++ goto out_up;
++ }
++
++ ucmd->buff_cached = 1;
++
++ TRACE_MEM("ucmd %p, pbuf %llx", ucmd, pbuf);
++
++ if (unlikely((pbuf & ~PAGE_MASK) != 0)) {
++ PRINT_ERROR("Supplied pbuf %llx isn't page aligned", pbuf);
++ res = -EINVAL;
++ goto out_put;
++ }
++
++ pages = calc_num_pg(pbuf, bufflen);
++ res = dev_user_map_buf(ucmd, pbuf, pages);
++ if (res != 0)
++ goto out_put;
++
++ if (pre.in.for_clust_pool)
++ pool = dev->pool_clust;
++ else
++ pool = dev->pool;
++
++ sg = sgv_pool_alloc(pool, bufflen, GFP_KERNEL, SGV_POOL_ALLOC_GET_NEW,
++ &sg_cnt, &ucmd->sgv, &dev->udev_mem_lim, ucmd);
++ if (sg != NULL) {
++ struct scst_user_cmd *buf_ucmd = sgv_get_priv(ucmd->sgv);
++
++ TRACE_MEM("Buf ucmd %p (sg_cnt %d, last seg len %d, "
++ "bufflen %d)", buf_ucmd, sg_cnt,
++ sg[sg_cnt-1].length, bufflen);
++
++ EXTRACHECKS_BUG_ON(ucmd != buf_ucmd);
++
++ ucmd->buf_ucmd = buf_ucmd;
++ } else {
++ res = -ENOMEM;
++ goto out_put;
++ }
++
++ dev_user_free_sgv(ucmd);
++
++ pre.out.cmd_h = ucmd->h;
++ rc = copy_to_user(arg, &pre.out, sizeof(pre.out));
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Failed to copy to user %d bytes", rc);
++ res = -EFAULT;
++ goto out_put;
++ }
++
++out_put:
++ ucmd_put(ucmd);
++
++out_up:
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int __dev_user_set_opt(struct scst_user_dev *dev,
++ const struct scst_user_opt *opt)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("dev %s, parse_type %x, on_free_cmd_type %x, "
++ "memory_reuse_type %x, partial_transfers_type %x, "
++ "partial_len %d", dev->name, 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->partial_transfers_type > SCST_USER_MAX_PARTIAL_TRANSFERS_OPT) {
++ PRINT_ERROR("%s", "Invalid option");
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (((opt->tst != SCST_CONTR_MODE_ONE_TASK_SET) &&
++ (opt->tst != SCST_CONTR_MODE_SEP_TASK_SETS)) ||
++ ((opt->queue_alg != SCST_CONTR_MODE_QUEUE_ALG_RESTRICTED_REORDER) &&
++ (opt->queue_alg != SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER)) ||
++ (opt->swp > 1) || (opt->tas > 1) || (opt->has_own_order_mgmt > 1) ||
++ (opt->d_sense > 1)) {
++ PRINT_ERROR("Invalid SCSI option (tst %x, queue_alg %x, swp %x,"
++ " tas %x, d_sense %d, has_own_order_mgmt %x)", opt->tst,
++ opt->queue_alg, opt->swp, opt->tas, opt->d_sense,
++ opt->has_own_order_mgmt);
++ res = -EINVAL;
++ goto out;
++ }
++
++ 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->tst = opt->tst;
++ dev->queue_alg = opt->queue_alg;
++ dev->swp = opt->swp;
++ dev->tas = opt->tas;
++ dev->tst = opt->tst;
++ dev->d_sense = opt->d_sense;
++ dev->has_own_order_mgmt = opt->has_own_order_mgmt;
++ if (dev->sdev != NULL) {
++ dev->sdev->tst = opt->tst;
++ dev->sdev->queue_alg = opt->queue_alg;
++ dev->sdev->swp = opt->swp;
++ dev->sdev->tas = opt->tas;
++ dev->sdev->d_sense = opt->d_sense;
++ dev->sdev->has_own_order_mgmt = opt->has_own_order_mgmt;
++ }
++
++ 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;
++ struct scst_user_dev *dev;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (res != 0) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_mutex);
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out_up;
++
++ res = __dev_user_set_opt(dev, opt);
++
++ scst_resume_activity();
++
++out_up:
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int dev_user_get_opt(struct file *file, void __user *arg)
++{
++ int res, rc;
++ struct scst_user_dev *dev;
++ struct scst_user_opt opt;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&dev_priv_mutex);
++ dev = file->private_data;
++ res = dev_user_check_reg(dev);
++ if (res != 0) {
++ mutex_unlock(&dev_priv_mutex);
++ goto out;
++ }
++ down_read(&dev->dev_rwsem);
++ mutex_unlock(&dev_priv_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.partial_transfers_type = dev->partial_transfers_type;
++ opt.partial_len = dev->partial_len;
++ opt.tst = dev->tst;
++ opt.queue_alg = dev->queue_alg;
++ opt.tas = dev->tas;
++ opt.swp = dev->swp;
++ opt.d_sense = dev->d_sense;
++ opt.has_own_order_mgmt = dev->has_own_order_mgmt;
++
++ TRACE_DBG("dev %s, parse_type %x, on_free_cmd_type %x, "
++ "memory_reuse_type %x, partial_transfers_type %x, "
++ "partial_len %d", dev->name, opt.parse_type,
++ opt.on_free_cmd_type, opt.memory_reuse_type,
++ opt.partial_transfers_type, opt.partial_len);
++
++ rc = copy_to_user(arg, &opt, sizeof(opt));
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("Failed to copy to user %d bytes", rc);
++ res = -EFAULT;
++ goto out_up;
++ }
++
++out_up:
++ up_read(&dev->dev_rwsem);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int dev_usr_parse(struct scst_cmd *cmd)
++{
++ BUG();
++ return SCST_CMD_STATE_DEFAULT;
++}
++
++static int dev_user_exit_dev(struct scst_user_dev *dev)
++{
++ TRACE_ENTRY();
++
++ TRACE(TRACE_MGMT, "Releasing dev %s", dev->name);
++
++ spin_lock(&dev_list_lock);
++ list_del(&dev->dev_list_entry);
++ spin_unlock(&dev_list_lock);
++
++ dev->blocking = 0;
++ wake_up_all(&dev->udev_cmd_threads.cmd_list_waitQ);
++
++ spin_lock(&cleanup_lock);
++ list_add_tail(&dev->cleanup_list_entry, &cleanup_list);
++ spin_unlock(&cleanup_lock);
++
++ wake_up(&cleanup_list_waitQ);
++
++ scst_unregister_virtual_device(dev->virt_id);
++ scst_unregister_virtual_dev_driver(&dev->devtype);
++
++ sgv_pool_flush(dev->pool_clust);
++ sgv_pool_flush(dev->pool);
++
++ TRACE_MGMT_DBG("Unregistering finished (dev %p)", dev);
++
++ dev->cleanup_done = 1;
++
++ wake_up(&cleanup_list_waitQ);
++ wake_up(&dev->udev_cmd_threads.cmd_list_waitQ);
++
++ wait_for_completion(&dev->cleanup_cmpl);
++
++ sgv_pool_del(dev->pool_clust);
++ sgv_pool_del(dev->pool);
++
++ scst_deinit_threads(&dev->udev_cmd_threads);
++
++ TRACE_MGMT_DBG("Releasing completed (dev %p)", dev);
++
++ module_put(THIS_MODULE);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static int __dev_user_release(void *arg)
++{
++ struct scst_user_dev *dev = arg;
++ dev_user_exit_dev(dev);
++ kfree(dev);
++ return 0;
++}
++
++static int dev_user_release(struct inode *inode, struct file *file)
++{
++ struct scst_user_dev *dev;
++ struct task_struct *t;
++
++ TRACE_ENTRY();
++
++ dev = file->private_data;
++ if (dev == NULL)
++ goto out;
++ file->private_data = NULL;
++
++ TRACE_MGMT_DBG("Going to release dev %s", dev->name);
++
++ t = kthread_run(__dev_user_release, dev, "scst_usr_released");
++ if (IS_ERR(t)) {
++ PRINT_CRIT_ERROR("kthread_run() failed (%ld), releasing device "
++ "%p directly. If you have several devices under load "
++ "it might deadlock!", PTR_ERR(t), dev);
++ __dev_user_release(dev);
++ }
++
++out:
++ TRACE_EXIT();
++ return 0;
++}
++
++static int dev_user_process_cleanup(struct scst_user_dev *dev)
++{
++ struct scst_user_cmd *ucmd;
++ int rc = 0, res = 1;
++
++ TRACE_ENTRY();
++
++ BUG_ON(dev->blocking);
++ wake_up_all(&dev->udev_cmd_threads.cmd_list_waitQ); /* just in case */
++
++ while (1) {
++ int rc1;
++
++ TRACE_DBG("Cleanuping dev %p", dev);
++
++ rc1 = dev_user_unjam_dev(dev);
++ if ((rc1 == 0) && (rc == -EAGAIN) && dev->cleanup_done)
++ break;
++
++ spin_lock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ rc = dev_user_get_next_cmd(dev, &ucmd);
++ if (rc == 0)
++ dev_user_unjam_cmd(ucmd, 1, NULL);
++
++ spin_unlock_irq(&dev->udev_cmd_threads.cmd_list_lock);
++
++ if (rc == -EAGAIN) {
++ if (!dev->cleanup_done) {
++ TRACE_DBG("No more commands (dev %p)", dev);
++ goto out;
++ }
++ }
++ }
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++{
++ int i;
++ for (i = 0; i < (int)ARRAY_SIZE(dev->ucmd_hash); i++) {
++ struct list_head *head = &dev->ucmd_hash[i];
++ struct scst_user_cmd *ucmd2;
++again:
++ list_for_each_entry(ucmd2, head, hash_list_entry) {
++ PRINT_ERROR("Lost ucmd %p (state %x, ref %d)", ucmd2,
++ ucmd2->state, atomic_read(&ucmd2->ucmd_ref));
++ ucmd_put(ucmd2);
++ goto again;
++ }
++ }
++}
++#endif
++
++ TRACE_DBG("Cleanuping done (dev %p)", dev);
++ complete_all(&dev->cleanup_cmpl);
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t dev_user_sysfs_commands_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0, ppos, i;
++ struct scst_device *dev;
++ struct scst_user_dev *udev;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ udev = dev->dh_priv;
++
++ spin_lock_irqsave(&udev->udev_cmd_threads.cmd_list_lock, flags);
++ for (i = 0; i < (int)ARRAY_SIZE(udev->ucmd_hash); i++) {
++ struct list_head *head = &udev->ucmd_hash[i];
++ struct scst_user_cmd *ucmd;
++ list_for_each_entry(ucmd, head, hash_list_entry) {
++ ppos = pos;
++ pos += scnprintf(&buf[pos],
++ SCST_SYSFS_BLOCK_SIZE - pos,
++ "ucmd %p (state %x, ref %d), "
++ "sent_to_user %d, seen_by_user %d, "
++ "aborted %d, jammed %d, scst_cmd %p\n",
++ ucmd, ucmd->state,
++ atomic_read(&ucmd->ucmd_ref),
++ ucmd->sent_to_user, ucmd->seen_by_user,
++ ucmd->aborted, ucmd->jammed, ucmd->cmd);
++ if (pos >= SCST_SYSFS_BLOCK_SIZE-1) {
++ ppos += scnprintf(&buf[ppos],
++ SCST_SYSFS_BLOCK_SIZE - ppos, "...\n");
++ pos = ppos;
++ break;
++ }
++ }
++ }
++ spin_unlock_irqrestore(&udev->udev_cmd_threads.cmd_list_lock, flags);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++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)
++{
++ TRACE_ENTRY();
++
++ PRINT_INFO("Cleanup thread started, PID %d", current->pid);
++
++ 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);
++ }
++
++ /*
++ * We have to poll devices, because commands can go from SCST
++ * core on cmd_list_waitQ and we have no practical way to
++ * detect them.
++ */
++
++ while (1) {
++ struct scst_user_dev *dev;
++ LIST_HEAD(cl_devs);
++
++ while (!list_empty(&cleanup_list)) {
++ int rc;
++
++ dev = list_entry(cleanup_list.next,
++ typeof(*dev), cleanup_list_entry);
++ list_del(&dev->cleanup_list_entry);
++
++ spin_unlock(&cleanup_lock);
++ rc = dev_user_process_cleanup(dev);
++ spin_lock(&cleanup_lock);
++
++ if (rc != 0)
++ list_add_tail(&dev->cleanup_list_entry,
++ &cl_devs);
++ }
++
++ if (list_empty(&cl_devs))
++ break;
++
++ spin_unlock(&cleanup_lock);
++ msleep(100);
++ spin_lock(&cleanup_lock);
++
++ while (!list_empty(&cl_devs)) {
++ dev = list_entry(cl_devs.next, typeof(*dev),
++ cleanup_list_entry);
++ list_move_tail(&dev->cleanup_list_entry,
++ &cleanup_list);
++ }
++ }
++ }
++ 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.
++ */
++ BUG_ON(!list_empty(&cleanup_list));
++
++ PRINT_INFO("Cleanup thread PID %d finished", current->pid);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static int __init init_scst_user(void)
++{
++ int res = 0;
++ struct max_get_reply {
++ union {
++ struct scst_user_get_cmd g;
++ struct scst_user_reply_cmd r;
++ };
++ };
++ struct device *dev;
++
++ TRACE_ENTRY();
++
++ user_cmd_cachep = KMEM_CACHE(scst_user_cmd, SCST_SLAB_FLAGS);
++ if (user_cmd_cachep == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ user_get_cmd_cachep = KMEM_CACHE(max_get_reply, SCST_SLAB_FLAGS);
++ if (user_get_cmd_cachep == NULL) {
++ res = -ENOMEM;
++ goto out_cache;
++ }
++
++ dev_user_devtype.module = THIS_MODULE;
++
++ res = scst_register_virtual_dev_driver(&dev_user_devtype);
++ if (res < 0)
++ goto out_cache1;
++
++ dev_user_sysfs_class = class_create(THIS_MODULE, DEV_USER_NAME);
++ if (IS_ERR(dev_user_sysfs_class)) {
++ PRINT_ERROR("%s", "Unable create sysfs class for SCST user "
++ "space handler");
++ res = PTR_ERR(dev_user_sysfs_class);
++ goto out_unreg;
++ }
++
++ dev_user_major = register_chrdev(0, DEV_USER_NAME, &dev_user_fops);
++ if (dev_user_major < 0) {
++ PRINT_ERROR("register_chrdev() failed: %d", res);
++ res = dev_user_major;
++ goto out_class;
++ }
++
++ dev = device_create(dev_user_sysfs_class, NULL,
++ MKDEV(dev_user_major, 0),
++ NULL,
++ DEV_USER_NAME);
++ if (IS_ERR(dev)) {
++ res = PTR_ERR(dev);
++ 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("kthread_create() failed: %d", res);
++ goto out_dev;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_dev:
++ 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_unreg:
++ scst_unregister_dev_driver(&dev_user_devtype);
++
++out_cache1:
++ kmem_cache_destroy(user_get_cmd_cachep);
++
++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);
++ device_destroy(dev_user_sysfs_class, MKDEV(dev_user_major, 0));
++ class_destroy(dev_user_sysfs_class);
++
++ scst_unregister_virtual_dev_driver(&dev_user_devtype);
++
++ kmem_cache_destroy(user_get_cmd_cachep);
++ 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("User space device handler for SCST");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_vdisk.c linux-3.2/drivers/scst/dev_handlers/scst_vdisk.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_vdisk.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_vdisk.c
+@@ -0,0 +1,4529 @@
++/*
++ * scst_vdisk.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 Ming Zhang <blackmagic02881 at gmail dot com>
++ * Copyright (C) 2007 Ross Walker <rswwalker at hotmail dot com>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * SCSI disk (type 0) and CDROM (type 5) dev handler using files
++ * on file systems or block devices (VDISK)
++ *
++ * 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/file.h>
++#include <linux/fs.h>
++#include <linux/string.h>
++#include <linux/types.h>
++#include <linux/unistd.h>
++#include <linux/spinlock.h>
++#include <linux/init.h>
++#include <linux/uio.h>
++#include <linux/list.h>
++#include <linux/ctype.h>
++#include <linux/writeback.h>
++#include <linux/vmalloc.h>
++#include <asm/atomic.h>
++#include <linux/kthread.h>
++#include <linux/sched.h>
++#include <linux/delay.h>
++#include <asm/div64.h>
++#include <asm/unaligned.h>
++#include <linux/slab.h>
++#include <linux/bio.h>
++#include <linux/crc32c.h>
++
++#define LOG_PREFIX "dev_vdisk"
++
++#include <scst/scst.h>
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++#define TRACE_ORDER 0x80000000
++
++static struct scst_trace_log vdisk_local_trace_tbl[] = {
++ { TRACE_ORDER, "order" },
++ { 0, NULL }
++};
++#define trace_log_tbl vdisk_local_trace_tbl
++
++#define VDISK_TRACE_TBL_HELP ", order"
++
++#endif
++
++#include "scst_dev_handler.h"
++
++/* 8 byte ASCII Vendor */
++#define SCST_FIO_VENDOR "SCST_FIO"
++#define SCST_BIO_VENDOR "SCST_BIO"
++/* 4 byte ASCII Product Revision Level - left aligned */
++#define SCST_FIO_REV " 220"
++
++#define MAX_USN_LEN (20+1) /* For '\0' */
++
++#define INQ_BUF_SZ 256
++#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_DISK_BLOCKSIZE_SHIFT 9
++#define DEF_DISK_BLOCKSIZE (1 << DEF_DISK_BLOCKSIZE_SHIFT)
++#define DEF_CDROM_BLOCKSIZE_SHIFT 11
++#define DEF_CDROM_BLOCKSIZE (1 << DEF_CDROM_BLOCKSIZE_SHIFT)
++#define DEF_SECTORS 56
++#define DEF_HEADS 255
++#define LEN_MEM (32 * 1024)
++#define DEF_RD_ONLY 0
++#define DEF_WRITE_THROUGH 0
++#define DEF_NV_CACHE 0
++#define DEF_O_DIRECT 0
++#define DEF_REMOVABLE 0
++#define DEF_THIN_PROVISIONED 0
++
++#define VDISK_NULLIO_SIZE (3LL*1024*1024*1024*1024/2)
++
++#define DEF_TST SCST_CONTR_MODE_SEP_TASK_SETS
++
++/*
++ * Since we can't control backstorage device's reordering, we have to always
++ * report unrestricted reordering.
++ */
++#define DEF_QUEUE_ALG_WT SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER
++#define DEF_QUEUE_ALG SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER
++#define DEF_SWP 0
++#define DEF_TAS 0
++
++#define DEF_DSENSE SCST_CONTR_MODE_FIXED_SENSE
++
++struct scst_vdisk_dev {
++ uint32_t block_size;
++ uint64_t nblocks;
++ int block_shift;
++ loff_t file_size; /* in bytes */
++
++ /*
++ * This lock can be taken on both SIRQ and thread context, but in
++ * all cases for each particular instance it's taken consistenly either
++ * on SIRQ or thread context. Mix of them is forbidden.
++ */
++ spinlock_t flags_lock;
++
++ /*
++ * Below flags are protected by flags_lock or suspended activity
++ * with scst_vdisk_mutex.
++ */
++ unsigned int rd_only: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 blockio:1;
++ unsigned int cdrom_empty:1;
++ unsigned int removable:1;
++ unsigned int thin_provisioned:1;
++
++ int virt_id;
++ char name[16+1]; /* Name of the virtual device,
++ must be <= SCSI Model + 1 */
++ char *filename; /* File name, protected by
++ scst_mutex and suspended activities */
++ uint16_t command_set_version;
++
++ /* All 4 protected by vdisk_serial_rwlock */
++ unsigned int t10_dev_id_set:1; /* true if t10_dev_id manually set */
++ unsigned int usn_set:1; /* true if usn manually set */
++ char t10_dev_id[16+8+2]; /* T10 device ID */
++ char usn[MAX_USN_LEN];
++
++ struct scst_device *dev;
++ struct list_head vdev_list_entry;
++
++ struct scst_dev_type *vdev_devt;
++};
++
++struct scst_vdisk_thr {
++ struct scst_thr_data_hdr hdr;
++ struct file *fd;
++ struct block_device *bdev;
++ struct iovec *iv;
++ int iv_count;
++};
++
++/* Context RA patch supposed to be applied on the kernel */
++#define DEF_NUM_THREADS 8
++static int num_threads = DEF_NUM_THREADS;
++
++module_param_named(num_threads, num_threads, int, S_IRUGO);
++MODULE_PARM_DESC(num_threads, "vdisk threads count");
++
++static int vdisk_attach(struct scst_device *dev);
++static void vdisk_detach(struct scst_device *dev);
++static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev);
++static void vdisk_detach_tgt(struct scst_tgt_dev *tgt_dev);
++static int vdisk_parse(struct scst_cmd *);
++static int vdisk_do_job(struct scst_cmd *cmd);
++static int vcdrom_parse(struct scst_cmd *);
++static int vcdrom_exec(struct scst_cmd *cmd);
++static void vdisk_exec_read(struct scst_cmd *cmd,
++ struct scst_vdisk_thr *thr, loff_t loff);
++static void vdisk_exec_write(struct scst_cmd *cmd,
++ struct scst_vdisk_thr *thr, loff_t loff);
++static void blockio_exec_rw(struct scst_cmd *cmd, struct scst_vdisk_thr *thr,
++ u64 lba_start, int write);
++static int vdisk_blockio_flush(struct block_device *bdev, gfp_t gfp_mask,
++ bool report_error);
++static void vdisk_exec_verify(struct scst_cmd *cmd,
++ struct scst_vdisk_thr *thr, loff_t loff);
++static void vdisk_exec_read_capacity(struct scst_cmd *cmd);
++static void vdisk_exec_read_capacity16(struct scst_cmd *cmd);
++static void vdisk_exec_report_tpgs(struct scst_cmd *cmd);
++static void vdisk_exec_inquiry(struct scst_cmd *cmd);
++static void vdisk_exec_request_sense(struct scst_cmd *cmd);
++static void vdisk_exec_mode_sense(struct scst_cmd *cmd);
++static void vdisk_exec_mode_select(struct scst_cmd *cmd);
++static void vdisk_exec_log(struct scst_cmd *cmd);
++static void vdisk_exec_read_toc(struct scst_cmd *cmd);
++static void vdisk_exec_prevent_allow_medium_removal(struct scst_cmd *cmd);
++static void vdisk_exec_unmap(struct scst_cmd *cmd, struct scst_vdisk_thr *thr);
++static int vdisk_fsync(struct scst_vdisk_thr *thr, loff_t loff,
++ loff_t len, struct scst_cmd *cmd, struct scst_device *dev);
++static ssize_t vdisk_add_fileio_device(const char *device_name, char *params);
++static ssize_t vdisk_add_blockio_device(const char *device_name, char *params);
++static ssize_t vdisk_add_nullio_device(const char *device_name, char *params);
++static ssize_t vdisk_del_device(const char *device_name);
++static ssize_t vcdrom_add_device(const char *device_name, char *params);
++static ssize_t vcdrom_del_device(const char *device_name);
++static int vdisk_task_mgmt_fn(struct scst_mgmt_cmd *mcmd,
++ struct scst_tgt_dev *tgt_dev);
++static uint64_t vdisk_gen_dev_id_num(const char *virt_dev_name);
++
++/** SYSFS **/
++
++static ssize_t vdev_sysfs_size_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdisk_sysfs_blocksize_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdisk_sysfs_rd_only_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdisk_sysfs_wt_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdisk_sysfs_tp_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdisk_sysfs_removable_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdev_sysfs_filename_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdisk_sysfs_resync_size_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count);
++static ssize_t vdev_sysfs_t10_dev_id_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count);
++static ssize_t vdev_sysfs_t10_dev_id_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++static ssize_t vdev_sysfs_usn_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count);
++static ssize_t vdev_sysfs_usn_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++
++static ssize_t vcdrom_sysfs_filename_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count);
++
++static struct kobj_attribute vdev_size_attr =
++ __ATTR(size_mb, S_IRUGO, vdev_sysfs_size_show, NULL);
++static struct kobj_attribute vdisk_blocksize_attr =
++ __ATTR(blocksize, S_IRUGO, vdisk_sysfs_blocksize_show, NULL);
++static struct kobj_attribute vdisk_rd_only_attr =
++ __ATTR(read_only, S_IRUGO, vdisk_sysfs_rd_only_show, NULL);
++static struct kobj_attribute vdisk_wt_attr =
++ __ATTR(write_through, S_IRUGO, vdisk_sysfs_wt_show, NULL);
++static struct kobj_attribute vdisk_tp_attr =
++ __ATTR(thin_provisioned, S_IRUGO, vdisk_sysfs_tp_show, NULL);
++static struct kobj_attribute vdisk_nv_cache_attr =
++ __ATTR(nv_cache, S_IRUGO, vdisk_sysfs_nv_cache_show, NULL);
++static struct kobj_attribute vdisk_o_direct_attr =
++ __ATTR(o_direct, S_IRUGO, vdisk_sysfs_o_direct_show, NULL);
++static struct kobj_attribute vdisk_removable_attr =
++ __ATTR(removable, S_IRUGO, vdisk_sysfs_removable_show, NULL);
++static struct kobj_attribute vdisk_filename_attr =
++ __ATTR(filename, S_IRUGO, vdev_sysfs_filename_show, NULL);
++static struct kobj_attribute vdisk_resync_size_attr =
++ __ATTR(resync_size, S_IWUSR, NULL, vdisk_sysfs_resync_size_store);
++static struct kobj_attribute vdev_t10_dev_id_attr =
++ __ATTR(t10_dev_id, S_IWUSR|S_IRUGO, vdev_sysfs_t10_dev_id_show,
++ vdev_sysfs_t10_dev_id_store);
++static struct kobj_attribute vdev_usn_attr =
++ __ATTR(usn, S_IWUSR|S_IRUGO, vdev_sysfs_usn_show, vdev_sysfs_usn_store);
++
++static struct kobj_attribute vcdrom_filename_attr =
++ __ATTR(filename, S_IRUGO|S_IWUSR, vdev_sysfs_filename_show,
++ vcdrom_sysfs_filename_store);
++
++static const struct attribute *vdisk_fileio_attrs[] = {
++ &vdev_size_attr.attr,
++ &vdisk_blocksize_attr.attr,
++ &vdisk_rd_only_attr.attr,
++ &vdisk_wt_attr.attr,
++ &vdisk_tp_attr.attr,
++ &vdisk_nv_cache_attr.attr,
++ &vdisk_o_direct_attr.attr,
++ &vdisk_removable_attr.attr,
++ &vdisk_filename_attr.attr,
++ &vdisk_resync_size_attr.attr,
++ &vdev_t10_dev_id_attr.attr,
++ &vdev_usn_attr.attr,
++ NULL,
++};
++
++static const struct attribute *vdisk_blockio_attrs[] = {
++ &vdev_size_attr.attr,
++ &vdisk_blocksize_attr.attr,
++ &vdisk_rd_only_attr.attr,
++ &vdisk_nv_cache_attr.attr,
++ &vdisk_removable_attr.attr,
++ &vdisk_filename_attr.attr,
++ &vdisk_resync_size_attr.attr,
++ &vdev_t10_dev_id_attr.attr,
++ &vdev_usn_attr.attr,
++ &vdisk_tp_attr.attr,
++ NULL,
++};
++
++static const struct attribute *vdisk_nullio_attrs[] = {
++ &vdev_size_attr.attr,
++ &vdisk_blocksize_attr.attr,
++ &vdisk_rd_only_attr.attr,
++ &vdisk_removable_attr.attr,
++ &vdev_t10_dev_id_attr.attr,
++ &vdev_usn_attr.attr,
++ NULL,
++};
++
++static const struct attribute *vcdrom_attrs[] = {
++ &vdev_size_attr.attr,
++ &vcdrom_filename_attr.attr,
++ &vdev_t10_dev_id_attr.attr,
++ &vdev_usn_attr.attr,
++ NULL,
++};
++
++/* Protects vdisks addition/deletion and related activities, like search */
++static DEFINE_MUTEX(scst_vdisk_mutex);
++
++/* Protects devices t10_dev_id and usn */
++static DEFINE_RWLOCK(vdisk_serial_rwlock);
++
++/* Protected by scst_vdisk_mutex */
++static LIST_HEAD(vdev_list);
++
++static struct kmem_cache *vdisk_thr_cachep;
++
++/*
++ * Be careful changing "name" field, since it is the name of the corresponding
++ * /sys/kernel/scst_tgt entry, hence a part of user space ABI.
++ */
++
++static struct scst_dev_type vdisk_file_devtype = {
++ .name = "vdisk_fileio",
++ .type = TYPE_DISK,
++ .exec_sync = 1,
++ .threads_num = -1,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = vdisk_attach,
++ .detach = vdisk_detach,
++ .attach_tgt = vdisk_attach_tgt,
++ .detach_tgt = vdisk_detach_tgt,
++ .parse = vdisk_parse,
++ .exec = vdisk_do_job,
++ .task_mgmt_fn = vdisk_task_mgmt_fn,
++ .add_device = vdisk_add_fileio_device,
++ .del_device = vdisk_del_device,
++ .dev_attrs = vdisk_fileio_attrs,
++ .add_device_parameters = "filename, blocksize, write_through, "
++ "nv_cache, o_direct, read_only, removable, thin_provisioned",
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++ .trace_tbl = vdisk_local_trace_tbl,
++ .trace_tbl_help = VDISK_TRACE_TBL_HELP,
++#endif
++};
++
++static struct kmem_cache *blockio_work_cachep;
++
++static struct scst_dev_type vdisk_blk_devtype = {
++ .name = "vdisk_blockio",
++ .type = TYPE_DISK,
++ .threads_num = 1,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = vdisk_attach,
++ .detach = vdisk_detach,
++ .attach_tgt = vdisk_attach_tgt,
++ .detach_tgt = vdisk_detach_tgt,
++ .parse = vdisk_parse,
++ .exec = vdisk_do_job,
++ .task_mgmt_fn = vdisk_task_mgmt_fn,
++ .add_device = vdisk_add_blockio_device,
++ .del_device = vdisk_del_device,
++ .dev_attrs = vdisk_blockio_attrs,
++ .add_device_parameters = "filename, blocksize, nv_cache, read_only, "
++ "removable, thin_provisioned",
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++ .trace_tbl = vdisk_local_trace_tbl,
++ .trace_tbl_help = VDISK_TRACE_TBL_HELP,
++#endif
++};
++
++static struct scst_dev_type vdisk_null_devtype = {
++ .name = "vdisk_nullio",
++ .type = TYPE_DISK,
++ .threads_num = 0,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = vdisk_attach,
++ .detach = vdisk_detach,
++ .attach_tgt = vdisk_attach_tgt,
++ .detach_tgt = vdisk_detach_tgt,
++ .parse = vdisk_parse,
++ .exec = vdisk_do_job,
++ .task_mgmt_fn = vdisk_task_mgmt_fn,
++ .add_device = vdisk_add_nullio_device,
++ .del_device = vdisk_del_device,
++ .dev_attrs = vdisk_nullio_attrs,
++ .add_device_parameters = "blocksize, read_only, removable",
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++ .trace_tbl = vdisk_local_trace_tbl,
++ .trace_tbl_help = VDISK_TRACE_TBL_HELP,
++#endif
++};
++
++static struct scst_dev_type vcdrom_devtype = {
++ .name = "vcdrom",
++ .type = TYPE_ROM,
++ .exec_sync = 1,
++ .threads_num = -1,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = vdisk_attach,
++ .detach = vdisk_detach,
++ .attach_tgt = vdisk_attach_tgt,
++ .detach_tgt = vdisk_detach_tgt,
++ .parse = vcdrom_parse,
++ .exec = vcdrom_exec,
++ .task_mgmt_fn = vdisk_task_mgmt_fn,
++ .add_device = vcdrom_add_device,
++ .del_device = vcdrom_del_device,
++ .dev_attrs = vcdrom_attrs,
++ .add_device_parameters = NULL,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++ .trace_tbl = vdisk_local_trace_tbl,
++ .trace_tbl_help = VDISK_TRACE_TBL_HELP,
++#endif
++};
++
++static struct scst_vdisk_thr nullio_thr_data;
++
++static const char *vdev_get_filename(const struct scst_vdisk_dev *virt_dev)
++{
++ if (virt_dev->filename != NULL)
++ return virt_dev->filename;
++ else
++ return "none";
++}
++
++/* Returns fd, use IS_ERR(fd) to get error status */
++static struct file *vdev_open_fd(const struct scst_vdisk_dev *virt_dev)
++{
++ int open_flags = 0;
++ struct file *fd;
++
++ TRACE_ENTRY();
++
++ if (virt_dev->dev->rd_only)
++ open_flags |= O_RDONLY;
++ else
++ open_flags |= O_RDWR;
++ if (virt_dev->o_direct_flag)
++ open_flags |= O_DIRECT;
++ if (virt_dev->wt_flag && !virt_dev->nv_cache)
++ open_flags |= O_SYNC;
++ TRACE_DBG("Opening file %s, flags 0x%x",
++ virt_dev->filename, open_flags);
++ fd = filp_open(virt_dev->filename, O_LARGEFILE | open_flags, 0600);
++
++ TRACE_EXIT();
++ return fd;
++}
++
++static void vdisk_blockio_check_flush_support(struct scst_vdisk_dev *virt_dev)
++{
++ struct inode *inode;
++ struct file *fd;
++
++ TRACE_ENTRY();
++
++ if (!virt_dev->blockio || virt_dev->rd_only || virt_dev->nv_cache)
++ goto out;
++
++ fd = filp_open(virt_dev->filename, O_LARGEFILE, 0600);
++ if (IS_ERR(fd)) {
++ PRINT_ERROR("filp_open(%s) returned error %ld",
++ virt_dev->filename, PTR_ERR(fd));
++ goto out;
++ }
++
++ inode = fd->f_dentry->d_inode;
++
++ if (!S_ISBLK(inode->i_mode)) {
++ PRINT_ERROR("%s is NOT a block device", virt_dev->filename);
++ goto out_close;
++ }
++
++ if (vdisk_blockio_flush(inode->i_bdev, GFP_KERNEL, false) != 0) {
++ PRINT_WARNING("Device %s doesn't support barriers, switching "
++ "to NV_CACHE mode. Read README for more details.",
++ virt_dev->filename);
++ virt_dev->nv_cache = 1;
++ }
++
++out_close:
++ filp_close(fd, NULL);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_check_tp_support(struct scst_vdisk_dev *virt_dev)
++{
++ struct inode *inode;
++ struct file *fd;
++ bool supported = false;
++
++ TRACE_ENTRY();
++
++ if (virt_dev->rd_only || !virt_dev->thin_provisioned)
++ goto out;
++
++ fd = filp_open(virt_dev->filename, O_LARGEFILE, 0600);
++ if (IS_ERR(fd)) {
++ PRINT_ERROR("filp_open(%s) returned error %ld",
++ virt_dev->filename, PTR_ERR(fd));
++ goto out;
++ }
++
++ inode = fd->f_dentry->d_inode;
++
++ if (virt_dev->blockio) {
++ if (!S_ISBLK(inode->i_mode)) {
++ PRINT_ERROR("%s is NOT a block device",
++ virt_dev->filename);
++ goto out_close;
++ }
++ supported = blk_queue_discard(bdev_get_queue(inode->i_bdev));
++
++ } else {
++ /*
++ * truncate_range() was chosen rather as a sample. In future,
++ * when unmap of range of blocks in file become standard, we
++ * will just switch to the new call.
++ */
++ supported = (inode->i_op->truncate_range != NULL);
++ }
++
++ if (!supported) {
++ PRINT_WARNING("Device %s doesn't support thin "
++ "provisioning, disabling it.",
++ virt_dev->filename);
++ virt_dev->thin_provisioned = 0;
++ }
++
++out_close:
++ filp_close(fd, NULL);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Returns 0 on success and file size in *file_size, error code otherwise */
++static int vdisk_get_file_size(const char *filename, bool blockio,
++ loff_t *file_size)
++{
++ struct inode *inode;
++ int res = 0;
++ struct file *fd;
++
++ TRACE_ENTRY();
++
++ *file_size = 0;
++
++ fd = filp_open(filename, O_LARGEFILE | O_RDONLY, 0600);
++ if (IS_ERR(fd)) {
++ res = PTR_ERR(fd);
++ PRINT_ERROR("filp_open(%s) returned error %d", filename, res);
++ goto out;
++ }
++
++ inode = fd->f_dentry->d_inode;
++
++ if (blockio && !S_ISBLK(inode->i_mode)) {
++ PRINT_ERROR("File %s is NOT a block device", filename);
++ res = -EINVAL;
++ goto out_close;
++ }
++
++ if (S_ISREG(inode->i_mode))
++ /* Nothing to do */;
++ else if (S_ISBLK(inode->i_mode))
++ inode = inode->i_bdev->bd_inode;
++ else {
++ res = -EINVAL;
++ goto out_close;
++ }
++
++ *file_size = inode->i_size;
++
++out_close:
++ filp_close(fd, NULL);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_vdisk_mutex supposed to be held */
++static struct scst_vdisk_dev *vdev_find(const char *name)
++{
++ struct scst_vdisk_dev *res, *vv;
++
++ TRACE_ENTRY();
++
++ res = NULL;
++ list_for_each_entry(vv, &vdev_list, vdev_list_entry) {
++ if (strcmp(vv->name, name) == 0) {
++ res = vv;
++ break;
++ }
++ }
++
++ TRACE_EXIT_HRES((unsigned long)res);
++ return res;
++}
++
++static int vdisk_attach(struct scst_device *dev)
++{
++ int res = 0;
++ loff_t err;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("virt_id %d (%s)", dev->virt_id, dev->virt_name);
++
++ if (dev->virt_id == 0) {
++ PRINT_ERROR("%s", "Not a virtual device");
++ res = -EINVAL;
++ goto out;
++ }
++
++ /*
++ * scst_vdisk_mutex must be already taken before
++ * scst_register_virtual_device()
++ */
++ virt_dev = vdev_find(dev->virt_name);
++ if (virt_dev == NULL) {
++ PRINT_ERROR("Device %s not found", dev->virt_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ virt_dev->dev = dev;
++
++ dev->rd_only = virt_dev->rd_only;
++
++ if (!virt_dev->cdrom_empty) {
++ if (virt_dev->nullio)
++ err = VDISK_NULLIO_SIZE;
++ else {
++ res = vdisk_get_file_size(virt_dev->filename,
++ virt_dev->blockio, &err);
++ if (res != 0)
++ goto out;
++ }
++ virt_dev->file_size = err;
++
++ TRACE_DBG("size of file: %lld", (long long unsigned int)err);
++
++ vdisk_blockio_check_flush_support(virt_dev);
++ vdisk_check_tp_support(virt_dev);
++ } else
++ virt_dev->file_size = 0;
++
++ virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift;
++
++ if (!virt_dev->cdrom_empty) {
++ PRINT_INFO("Attached SCSI target virtual %s %s "
++ "(file=\"%s\", fs=%lldMB, bs=%d, nblocks=%lld,"
++ " cyln=%lld%s)",
++ (dev->type == TYPE_DISK) ? "disk" : "cdrom",
++ virt_dev->name, vdev_get_filename(virt_dev),
++ virt_dev->file_size >> 20, virt_dev->block_size,
++ (long long unsigned int)virt_dev->nblocks,
++ (long long unsigned int)virt_dev->nblocks/64/32,
++ virt_dev->nblocks < 64*32
++ ? " !WARNING! cyln less than 1" : "");
++ } else {
++ PRINT_INFO("Attached empty SCSI target virtual cdrom %s",
++ virt_dev->name);
++ }
++
++ dev->dh_priv = virt_dev;
++
++ dev->tst = DEF_TST;
++ dev->d_sense = DEF_DSENSE;
++ if (virt_dev->wt_flag && !virt_dev->nv_cache)
++ dev->queue_alg = DEF_QUEUE_ALG_WT;
++ else
++ dev->queue_alg = DEF_QUEUE_ALG;
++ dev->swp = DEF_SWP;
++ dev->tas = DEF_TAS;
++
++out:
++ TRACE_EXIT();
++ return res;
++}
++
++/* scst_mutex supposed to be held */
++static void vdisk_detach(struct scst_device *dev)
++{
++ struct scst_vdisk_dev *virt_dev = dev->dh_priv;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("virt_id %d", dev->virt_id);
++
++ PRINT_INFO("Detached virtual device %s (\"%s\")",
++ virt_dev->name, vdev_get_filename(virt_dev));
++
++ /* virt_dev will be freed by the caller */
++ dev->dh_priv = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_free_thr_data(struct scst_thr_data_hdr *d)
++{
++ struct scst_vdisk_thr *thr =
++ container_of(d, struct scst_vdisk_thr, hdr);
++
++ TRACE_ENTRY();
++
++ if (thr->fd)
++ filp_close(thr->fd, NULL);
++
++ kfree(thr->iv);
++
++ kmem_cache_free(vdisk_thr_cachep, thr);
++
++ TRACE_EXIT();
++ return;
++}
++
++static struct scst_vdisk_thr *vdisk_init_thr_data(
++ struct scst_tgt_dev *tgt_dev, gfp_t gfp_mask)
++{
++ struct scst_vdisk_thr *res;
++ struct scst_vdisk_dev *virt_dev = tgt_dev->dev->dh_priv;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(virt_dev->nullio);
++
++ res = kmem_cache_zalloc(vdisk_thr_cachep, gfp_mask);
++ if (res == NULL) {
++ PRINT_ERROR("Unable to allocate struct scst_vdisk_thr"
++ " (size %zd)", sizeof(*res));
++ goto out;
++ }
++
++ if (!virt_dev->cdrom_empty) {
++ res->fd = vdev_open_fd(virt_dev);
++ if (IS_ERR(res->fd)) {
++ PRINT_ERROR("filp_open(%s) returned an error %ld",
++ virt_dev->filename, PTR_ERR(res->fd));
++ goto out_free;
++ }
++ if (virt_dev->blockio)
++ res->bdev = res->fd->f_dentry->d_inode->i_bdev;
++ else
++ res->bdev = NULL;
++ } else
++ res->fd = NULL;
++
++ scst_add_thr_data(tgt_dev, &res->hdr, vdisk_free_thr_data);
++
++out:
++ TRACE_EXIT_HRES((unsigned long)res);
++ return res;
++
++out_free:
++ kmem_cache_free(vdisk_thr_cachep, res);
++ res = NULL;
++ goto out;
++}
++
++static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /* Nothing to do */
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void vdisk_detach_tgt(struct scst_tgt_dev *tgt_dev)
++{
++ TRACE_ENTRY();
++
++ scst_del_all_thr_data(tgt_dev);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int vdisk_do_job(struct scst_cmd *cmd)
++{
++ int rc, res;
++ uint64_t lba_start = 0;
++ loff_t data_len = 0;
++ uint8_t *cdb = cmd->cdb;
++ int opcode = cdb[0];
++ loff_t loff;
++ struct scst_device *dev = cmd->dev;
++ struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
++ struct scst_vdisk_dev *virt_dev = dev->dh_priv;
++ struct scst_thr_data_hdr *d;
++ struct scst_vdisk_thr *thr = NULL;
++ int fua = 0;
++
++ TRACE_ENTRY();
++
++ switch (cmd->queue_type) {
++ case SCST_CMD_QUEUE_ORDERED:
++ TRACE(TRACE_ORDER, "ORDERED cmd %p (op %x)", cmd, cmd->cdb[0]);
++ break;
++ case SCST_CMD_QUEUE_HEAD_OF_QUEUE:
++ TRACE(TRACE_ORDER, "HQ cmd %p (op %x)", cmd, cmd->cdb[0]);
++ break;
++ default:
++ break;
++ }
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++
++ if (!virt_dev->nullio) {
++ d = scst_find_thr_data(tgt_dev);
++ if (unlikely(d == NULL)) {
++ thr = vdisk_init_thr_data(tgt_dev,
++ cmd->noio_mem_alloc ? GFP_NOIO : GFP_KERNEL);
++ if (thr == NULL) {
++ scst_set_busy(cmd);
++ goto out_compl;
++ }
++ scst_thr_data_get(&thr->hdr);
++ } else
++ thr = container_of(d, struct scst_vdisk_thr, hdr);
++ } else {
++ thr = &nullio_thr_data;
++ scst_thr_data_get(&thr->hdr);
++ }
++
++ 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 |= ((u64)cdb[2]) << 24;
++ lba_start |= ((u64)cdb[3]) << 16;
++ lba_start |= ((u64)cdb[4]) << 8;
++ lba_start |= ((u64)cdb[5]);
++ data_len = cmd->bufflen;
++ break;
++ case READ_16:
++ case WRITE_16:
++ case WRITE_VERIFY_16:
++ case VERIFY_16:
++ lba_start |= ((u64)cdb[2]) << 56;
++ lba_start |= ((u64)cdb[3]) << 48;
++ lba_start |= ((u64)cdb[4]) << 40;
++ lba_start |= ((u64)cdb[5]) << 32;
++ lba_start |= ((u64)cdb[6]) << 24;
++ lba_start |= ((u64)cdb[7]) << 16;
++ lba_start |= ((u64)cdb[8]) << 8;
++ lba_start |= ((u64)cdb[9]);
++ data_len = cmd->bufflen;
++ break;
++ case SYNCHRONIZE_CACHE:
++ lba_start |= ((u64)cdb[2]) << 24;
++ lba_start |= ((u64)cdb[3]) << 16;
++ lba_start |= ((u64)cdb[4]) << 8;
++ lba_start |= ((u64)cdb[5]);
++ data_len = ((cdb[7] << (BYTE * 1)) + (cdb[8] << (BYTE * 0)))
++ << virt_dev->block_shift;
++ if (data_len == 0)
++ data_len = virt_dev->file_size -
++ ((loff_t)lba_start << virt_dev->block_shift);
++ break;
++ }
++
++ loff = (loff_t)lba_start << virt_dev->block_shift;
++ TRACE_DBG("cmd %p, lba_start %lld, loff %lld, data_len %lld", cmd,
++ (long long unsigned int)lba_start,
++ (long long unsigned int)loff,
++ (long long unsigned int)data_len);
++ if (unlikely(loff < 0) || unlikely(data_len < 0) ||
++ unlikely((loff + data_len) > virt_dev->file_size)) {
++ PRINT_INFO("Access beyond the end of the device "
++ "(%lld of %lld, len %lld)",
++ (long long unsigned int)loff,
++ (long long unsigned int)virt_dev->file_size,
++ (long long unsigned int)data_len);
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_block_out_range_error));
++ goto out_compl;
++ }
++
++ switch (opcode) {
++ case WRITE_10:
++ case WRITE_12:
++ case WRITE_16:
++ fua = (cdb[1] & 0x8);
++ if (fua) {
++ TRACE(TRACE_ORDER, "FUA: loff=%lld, "
++ "data_len=%lld", (long long unsigned int)loff,
++ (long long unsigned int)data_len);
++ }
++ break;
++ }
++
++ switch (opcode) {
++ case READ_6:
++ case READ_10:
++ case READ_12:
++ case READ_16:
++ if (virt_dev->blockio) {
++ blockio_exec_rw(cmd, thr, lba_start, 0);
++ goto out_thr;
++ } else
++ vdisk_exec_read(cmd, thr, loff);
++ break;
++ case WRITE_6:
++ case WRITE_10:
++ case WRITE_12:
++ case WRITE_16:
++ {
++ if (virt_dev->blockio) {
++ blockio_exec_rw(cmd, thr, lba_start, 1);
++ goto out_thr;
++ } else
++ vdisk_exec_write(cmd, thr, loff);
++ /* O_SYNC flag is used for WT devices */
++ if (fua)
++ vdisk_fsync(thr, loff, data_len, cmd, dev);
++ break;
++ }
++ case WRITE_VERIFY:
++ case WRITE_VERIFY_12:
++ case WRITE_VERIFY_16:
++ {
++ /* ToDo: BLOCKIO VERIFY */
++ vdisk_exec_write(cmd, thr, loff);
++ /* O_SYNC flag is used for WT devices */
++ if (scsi_status_is_good(cmd->status))
++ vdisk_exec_verify(cmd, thr, loff);
++ break;
++ }
++ case SYNCHRONIZE_CACHE:
++ {
++ int immed = cdb[1] & 0x2;
++ TRACE(TRACE_ORDER, "SYNCHRONIZE_CACHE: "
++ "loff=%lld, data_len=%lld, immed=%d",
++ (long long unsigned int)loff,
++ (long long unsigned int)data_len, immed);
++ if (immed) {
++ scst_cmd_get(cmd); /* to protect dev */
++ cmd->completed = 1;
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT,
++ SCST_CONTEXT_SAME);
++ vdisk_fsync(thr, loff, data_len, NULL, dev);
++ /* ToDo: vdisk_fsync() error processing */
++ scst_cmd_put(cmd);
++ goto out_thr;
++ } else {
++ vdisk_fsync(thr, loff, data_len, cmd, dev);
++ break;
++ }
++ }
++ case VERIFY_6:
++ case VERIFY:
++ case VERIFY_12:
++ case VERIFY_16:
++ vdisk_exec_verify(cmd, thr, loff);
++ break;
++ case MODE_SENSE:
++ case MODE_SENSE_10:
++ vdisk_exec_mode_sense(cmd);
++ break;
++ case MODE_SELECT:
++ case MODE_SELECT_10:
++ vdisk_exec_mode_select(cmd);
++ break;
++ case LOG_SELECT:
++ case LOG_SENSE:
++ vdisk_exec_log(cmd);
++ break;
++ case ALLOW_MEDIUM_REMOVAL:
++ vdisk_exec_prevent_allow_medium_removal(cmd);
++ break;
++ case READ_TOC:
++ vdisk_exec_read_toc(cmd);
++ break;
++ case START_STOP:
++ vdisk_fsync(thr, 0, virt_dev->file_size, cmd, dev);
++ break;
++ case RESERVE:
++ case RESERVE_10:
++ case RELEASE:
++ case RELEASE_10:
++ case TEST_UNIT_READY:
++ break;
++ case INQUIRY:
++ vdisk_exec_inquiry(cmd);
++ break;
++ case REQUEST_SENSE:
++ vdisk_exec_request_sense(cmd);
++ break;
++ case READ_CAPACITY:
++ vdisk_exec_read_capacity(cmd);
++ break;
++ case SERVICE_ACTION_IN:
++ if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16) {
++ vdisk_exec_read_capacity16(cmd);
++ break;
++ }
++ goto out_invalid_opcode;
++ case UNMAP:
++ vdisk_exec_unmap(cmd, thr);
++ break;
++ case MAINTENANCE_IN:
++ switch (cmd->cdb[1] & 0x1f) {
++ case MI_REPORT_TARGET_PGS:
++ vdisk_exec_report_tpgs(cmd);
++ break;
++ default:
++ goto out_invalid_opcode;
++ }
++ break;
++ case REPORT_LUNS:
++ default:
++ goto out_invalid_opcode;
++ }
++
++out_compl:
++ cmd->completed = 1;
++
++out_done:
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++
++out_thr:
++ if (likely(thr != NULL))
++ scst_thr_data_put(&thr->hdr);
++
++ res = SCST_EXEC_COMPLETED;
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_invalid_opcode:
++ TRACE_DBG("Invalid opcode %d", opcode);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out_compl;
++}
++
++static int vdisk_get_block_shift(struct scst_cmd *cmd)
++{
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++ return virt_dev->block_shift;
++}
++
++static int vdisk_parse(struct scst_cmd *cmd)
++{
++ scst_sbc_generic_parse(cmd, vdisk_get_block_shift);
++ return SCST_CMD_STATE_DEFAULT;
++}
++
++static int vcdrom_parse(struct scst_cmd *cmd)
++{
++ scst_cdrom_generic_parse(cmd, vdisk_get_block_shift);
++ return SCST_CMD_STATE_DEFAULT;
++}
++
++static int vcdrom_exec(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_COMPLETED;
++ int opcode = cmd->cdb[0];
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++
++ TRACE_ENTRY();
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++
++ if (virt_dev->cdrom_empty && (opcode != INQUIRY)) {
++ TRACE_DBG("%s", "CDROM empty");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_not_ready));
++ goto out_done;
++ }
++
++ if (virt_dev->media_changed && scst_is_ua_command(cmd)) {
++ spin_lock(&virt_dev->flags_lock);
++ if (virt_dev->media_changed) {
++ virt_dev->media_changed = 0;
++ TRACE_DBG("%s", "Reporting media changed");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_medium_changed_UA));
++ spin_unlock(&virt_dev->flags_lock);
++ goto out_done;
++ }
++ spin_unlock(&virt_dev->flags_lock);
++ }
++
++ res = vdisk_do_job(cmd);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_done:
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++ goto out;
++}
++
++static uint64_t vdisk_gen_dev_id_num(const char *virt_dev_name)
++{
++ uint32_t dev_id_num;
++
++ dev_id_num = crc32c(0, virt_dev_name, strlen(virt_dev_name)+1);
++
++ return ((uint64_t)scst_get_setup_id() << 32) | dev_id_num;
++}
++
++static void vdisk_exec_unmap(struct scst_cmd *cmd, struct scst_vdisk_thr *thr)
++{
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++ ssize_t length = 0;
++ struct file *fd = thr->fd;
++ struct inode *inode;
++ uint8_t *address;
++ int offset, descriptor_len, total_len;
++
++ TRACE_ENTRY();
++
++ if (unlikely(!virt_dev->thin_provisioned)) {
++ TRACE_DBG("%s", "Invalid opcode UNMAP");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out;
++ }
++
++ if (unlikely(cmd->cdb[1] & 1)) {
++ /* ANCHOR not supported */
++ TRACE_DBG("%s", "Invalid ANCHOR field");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++
++ length = scst_get_buf_full(cmd, &address);
++ if (unlikely(length <= 0)) {
++ if (length == 0)
++ goto out_put;
++ else if (length == -ENOMEM)
++ scst_set_busy(cmd);
++ else
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out;
++ }
++
++ inode = fd->f_dentry->d_inode;
++
++ total_len = cmd->cdb[7] << 8 | cmd->cdb[8]; /* length */
++ offset = 8;
++
++ descriptor_len = address[2] << 8 | address[3];
++
++ TRACE_DBG("total_len %d, descriptor_len %d", total_len, descriptor_len);
++
++ if (descriptor_len == 0)
++ goto out_put;
++
++ if (unlikely((descriptor_len > (total_len - 8)) ||
++ ((descriptor_len % 16) != 0))) {
++ PRINT_ERROR("Bad descriptor length: %d < %d - 8",
++ descriptor_len, total_len);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
++ goto out_put;
++ }
++
++ while ((offset - 8) < descriptor_len) {
++ int err;
++ uint64_t start;
++ uint32_t len;
++ start = be64_to_cpu(get_unaligned((__be64 *)&address[offset]));
++ offset += 8;
++ len = be32_to_cpu(get_unaligned((__be32 *)&address[offset]));
++ offset += 8;
++
++ if ((start > virt_dev->nblocks) ||
++ ((start + len) > virt_dev->nblocks)) {
++ PRINT_ERROR("Device %s: attempt to write beyond max "
++ "size", virt_dev->name);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put;
++ }
++
++ TRACE_DBG("Unmapping lba %lld (blocks %d)",
++ (unsigned long long)start, len);
++
++ if (virt_dev->blockio) {
++ gfp_t gfp = cmd->noio_mem_alloc ? GFP_NOIO : GFP_KERNEL;
++ err = blkdev_issue_discard(inode->i_bdev, start, len,
++ gfp, 0);
++ if (unlikely(err != 0)) {
++ PRINT_ERROR("blkdev_issue_discard() for "
++ "LBA %lld len %d failed with err %d",
++ (unsigned long long)start, len, err);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_write_error));
++ goto out_put;
++ }
++ } else {
++ const int block_shift = virt_dev->block_shift;
++ const loff_t a0 = start << block_shift;
++ const loff_t a2 = (start + len) << block_shift;
++ const loff_t a1 = max_t(loff_t, a2 & PAGE_CACHE_MASK,
++ a0);
++
++ /*
++ * The SCSI UNMAP command discards a range of blocks
++ * of size (1 << block_shift) while the Linux VFS
++ * truncate_range() function discards a range of blocks
++ * of size PAGE_CACHE_SIZE. Hence pass range [a0, a1)
++ * to truncate_range() instead of range [a0,
++ * a2). Note: since we do not set TPRZ it is not
++ * necessary to overwrite the range [a1, a2) with
++ * zeroes.
++ */
++ WARN_ON(!(a0 <= a1 && a1 <= a2));
++ WARN_ON(!((a1 & (PAGE_CACHE_SIZE - 1)) == 0 ||
++ a0 == a1));
++ if (a0 < a1)
++ inode->i_op->truncate_range(inode, a0, a1 - 1);
++ }
++ }
++
++out_put:
++ scst_put_buf_full(cmd, address);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_exec_inquiry(struct scst_cmd *cmd)
++{
++ int32_t length, i, resp_len = 0;
++ uint8_t *address;
++ uint8_t *buf;
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++ uint16_t tg_id;
++
++ TRACE_ENTRY();
++
++ buf = kzalloc(INQ_BUF_SZ, GFP_KERNEL);
++ if (buf == NULL) {
++ scst_set_busy(cmd);
++ goto out;
++ }
++
++ length = scst_get_buf_full(cmd, &address);
++ TRACE_DBG("length %d", length);
++ if (unlikely(length <= 0)) {
++ if (length < 0) {
++ PRINT_ERROR("scst_get_buf_full() failed: %d", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ goto out_free;
++ }
++
++ if (cmd->cdb[1] & CMDDT) {
++ TRACE_DBG("%s", "INQUIRY: CMDDT is unsupported");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put;
++ }
++
++ buf[0] = cmd->dev->type; /* type dev */
++ /* Vital Product */
++ if (cmd->cdb[1] & EVPD) {
++ 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 */
++ if (virt_dev->dev->type == TYPE_DISK) {
++ buf[3] += 1;
++ buf[7] = 0xB0; /* block limits */
++ if (virt_dev->thin_provisioned) {
++ buf[3] += 1;
++ buf[8] = 0xB2; /* thin provisioning */
++ }
++ }
++ resp_len = buf[3] + 4;
++ } else if (0x80 == cmd->cdb[2]) {
++ /* unit serial number */
++ buf[1] = 0x80;
++ if (cmd->tgtt->get_serial) {
++ buf[3] = cmd->tgtt->get_serial(cmd->tgt_dev,
++ &buf[4], INQ_BUF_SZ - 4);
++ } else {
++ int usn_len;
++ read_lock(&vdisk_serial_rwlock);
++ usn_len = strlen(virt_dev->usn);
++ buf[3] = usn_len;
++ strncpy(&buf[4], virt_dev->usn, usn_len);
++ read_unlock(&vdisk_serial_rwlock);
++ }
++ resp_len = buf[3] + 4;
++ } else if (0x83 == cmd->cdb[2]) {
++ /* device identification */
++ int num = 4;
++
++ buf[1] = 0x83;
++ /* T10 vendor identifier field format (faked) */
++ buf[num + 0] = 0x2; /* ASCII */
++ buf[num + 1] = 0x1; /* Vendor ID */
++ if (cmd->tgtt->vendor)
++ memcpy(&buf[num + 4], cmd->tgtt->vendor, 8);
++ else if (virt_dev->blockio)
++ memcpy(&buf[num + 4], SCST_BIO_VENDOR, 8);
++ else
++ memcpy(&buf[num + 4], SCST_FIO_VENDOR, 8);
++
++ read_lock(&vdisk_serial_rwlock);
++ i = strlen(virt_dev->t10_dev_id);
++ memcpy(&buf[num + 12], virt_dev->t10_dev_id, i);
++ read_unlock(&vdisk_serial_rwlock);
++
++ buf[num + 3] = 8 + i;
++ num += buf[num + 3];
++
++ num += 4;
++
++ /*
++ * Relative target port identifier
++ */
++ buf[num + 0] = 0x01; /* binary */
++ /* Relative target port id */
++ buf[num + 1] = 0x10 | 0x04;
++
++ put_unaligned(cpu_to_be16(cmd->tgt->rel_tgt_id),
++ (__be16 *)&buf[num + 4 + 2]);
++
++ buf[num + 3] = 4;
++ num += buf[num + 3];
++
++ num += 4;
++
++ tg_id = scst_lookup_tg_id(cmd->dev, cmd->tgt);
++ if (tg_id) {
++ /*
++ * Target port group designator
++ */
++ buf[num + 0] = 0x01; /* binary */
++ /* Target port group id */
++ buf[num + 1] = 0x10 | 0x05;
++
++ put_unaligned(cpu_to_be16(tg_id),
++ (__be16 *)&buf[num + 4 + 2]);
++
++ buf[num + 3] = 4;
++ num += 4 + buf[num + 3];
++ }
++
++ /*
++ * IEEE id
++ */
++ buf[num + 0] = 0x01; /* binary */
++
++ /* EUI-64 */
++ buf[num + 1] = 0x02;
++ buf[num + 2] = 0x00;
++ buf[num + 3] = 0x08;
++
++ /* IEEE id */
++ buf[num + 4] = virt_dev->t10_dev_id[0];
++ buf[num + 5] = virt_dev->t10_dev_id[1];
++ buf[num + 6] = virt_dev->t10_dev_id[2];
++
++ /* IEEE ext id */
++ buf[num + 7] = virt_dev->t10_dev_id[3];
++ buf[num + 8] = virt_dev->t10_dev_id[4];
++ buf[num + 9] = virt_dev->t10_dev_id[5];
++ buf[num + 10] = virt_dev->t10_dev_id[6];
++ buf[num + 11] = virt_dev->t10_dev_id[7];
++ num += buf[num + 3];
++
++ resp_len = num;
++ buf[2] = (resp_len >> 8) & 0xFF;
++ buf[3] = resp_len & 0xFF;
++ resp_len += 4;
++ } else if ((0xB0 == cmd->cdb[2]) &&
++ (virt_dev->dev->type == TYPE_DISK)) {
++ /* Block Limits */
++ int max_transfer;
++ buf[1] = 0xB0;
++ buf[3] = 0x3C;
++ /* Optimal transfer granuality is PAGE_SIZE */
++ put_unaligned(cpu_to_be16(max_t(int,
++ PAGE_SIZE/virt_dev->block_size, 1)),
++ (uint16_t *)&buf[6]);
++ /* Max transfer len is min of sg limit and 8M */
++ max_transfer = min_t(int,
++ cmd->tgt_dev->max_sg_cnt << PAGE_SHIFT,
++ 8*1024*1024) / virt_dev->block_size;
++ put_unaligned(cpu_to_be32(max_transfer),
++ (uint32_t *)&buf[8]);
++ /*
++ * Let's have optimal transfer len 512KB. Better to not
++ * set it at all, because we don't have such limit,
++ * but some initiators may not understand that (?).
++ * From other side, too big transfers are not optimal,
++ * because SGV cache supports only <4M buffers.
++ */
++ put_unaligned(cpu_to_be32(min_t(int,
++ max_transfer,
++ 512*1024 / virt_dev->block_size)),
++ (uint32_t *)&buf[12]);
++ if (virt_dev->thin_provisioned) {
++ /* MAXIMUM UNMAP LBA COUNT is UNLIMITED */
++ put_unaligned(__constant_cpu_to_be32(0xFFFFFFFF),
++ (uint32_t *)&buf[20]);
++ /* MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT is UNLIMITED */
++ put_unaligned(__constant_cpu_to_be32(0xFFFFFFFF),
++ (uint32_t *)&buf[24]);
++ /* OPTIMAL UNMAP GRANULARITY is 1 */
++ put_unaligned(__constant_cpu_to_be32(1),
++ (uint32_t *)&buf[28]);
++ }
++ resp_len = buf[3] + 4;
++ } else if ((0xB2 == cmd->cdb[2]) &&
++ (virt_dev->dev->type == TYPE_DISK) &&
++ virt_dev->thin_provisioned) {
++ /* Thin Provisioning */
++ buf[1] = 0xB2;
++ buf[3] = 2;
++ buf[5] = 0x80;
++ resp_len = buf[3] + 4;
++ } else {
++ TRACE_DBG("INQUIRY: Unsupported EVPD page %x",
++ cmd->cdb[2]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put;
++ }
++ } else {
++ int len, num;
++
++ if (cmd->cdb[2] != 0) {
++ TRACE_DBG("INQUIRY: Unsupported page %x", cmd->cdb[2]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put;
++ }
++
++ if (virt_dev->removable)
++ buf[1] = 0x80; /* removable */
++ buf[2] = 5; /* Device complies to SPC-3 */
++ buf[3] = 0x02; /* Data in format specified in SPC */
++ if (cmd->tgtt->fake_aca)
++ buf[3] |= 0x20;
++ buf[4] = 31;/* n - 4 = 35 - 4 = 31 for full 36 byte data */
++ if (scst_impl_alua_configured(cmd->dev))
++ buf[5] = SCST_INQ_TPGS_MODE_IMPLICIT;
++ buf[6] = 0x10; /* MultiP 1 */
++ buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */
++
++ /*
++ * 8 byte ASCII Vendor Identification of the target
++ * - left aligned.
++ */
++ if (cmd->tgtt->vendor)
++ memcpy(&buf[8], cmd->tgtt->vendor, 8);
++ else if (virt_dev->blockio)
++ memcpy(&buf[8], SCST_BIO_VENDOR, 8);
++ else
++ memcpy(&buf[8], SCST_FIO_VENDOR, 8);
++
++ /*
++ * 16 byte ASCII Product Identification of the target - left
++ * aligned.
++ */
++ memset(&buf[16], ' ', 16);
++ if (cmd->tgtt->get_product_id)
++ cmd->tgtt->get_product_id(cmd->tgt_dev, &buf[16], 16);
++ else {
++ len = min_t(size_t, strlen(virt_dev->name), 16);
++ memcpy(&buf[16], virt_dev->name, len);
++ }
++
++ /*
++ * 4 byte ASCII Product Revision Level of the target - left
++ * aligned.
++ */
++ if (cmd->tgtt->revision)
++ memcpy(&buf[32], cmd->tgtt->revision, 4);
++ else
++ memcpy(&buf[32], SCST_FIO_REV, 4);
++
++ /** Version descriptors **/
++
++ buf[4] += 58 - 36;
++ num = 0;
++
++ /* SAM-3 T10/1561-D revision 14 */
++ buf[58 + num] = 0x0;
++ buf[58 + num + 1] = 0x76;
++ num += 2;
++
++ /* Physical transport */
++ if (cmd->tgtt->get_phys_transport_version != NULL) {
++ uint16_t v = cmd->tgtt->get_phys_transport_version(cmd->tgt);
++ if (v != 0) {
++ *((__be16 *)&buf[58 + num]) = cpu_to_be16(v);
++ num += 2;
++ }
++ }
++
++ /* SCSI transport */
++ if (cmd->tgtt->get_scsi_transport_version != NULL) {
++ *((__be16 *)&buf[58 + num]) =
++ cpu_to_be16(cmd->tgtt->get_scsi_transport_version(cmd->tgt));
++ num += 2;
++ }
++
++ /* SPC-3 T10/1416-D revision 23 */
++ buf[58 + num] = 0x3;
++ buf[58 + num + 1] = 0x12;
++ num += 2;
++
++ /* Device command set */
++ if (virt_dev->command_set_version != 0) {
++ *((__be16 *)&buf[58 + num]) =
++ cpu_to_be16(virt_dev->command_set_version);
++ num += 2;
++ }
++
++ /* Vendor specific information. */
++ if (cmd->tgtt->get_vend_specific) {
++ /* Skip to byte 96. */
++ num = 96 - 58;
++ num += cmd->tgtt->get_vend_specific(cmd->tgt_dev,
++ &buf[96], INQ_BUF_SZ - 96);
++ }
++
++ buf[4] += num;
++ resp_len = buf[4] + 5;
++ }
++
++ BUG_ON(resp_len >= INQ_BUF_SZ);
++
++ if (length > resp_len)
++ length = resp_len;
++ memcpy(address, buf, length);
++
++out_put:
++ scst_put_buf_full(cmd, address);
++ if (length < cmd->resp_data_len)
++ scst_set_resp_data_len(cmd, length);
++
++out_free:
++ kfree(buf);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_exec_request_sense(struct scst_cmd *cmd)
++{
++ int32_t length, sl;
++ uint8_t *address;
++ uint8_t b[SCST_STANDARD_SENSE_LEN];
++
++ TRACE_ENTRY();
++
++ sl = scst_set_sense(b, sizeof(b), cmd->dev->d_sense,
++ SCST_LOAD_SENSE(scst_sense_no_sense));
++
++ length = scst_get_buf_full(cmd, &address);
++ TRACE_DBG("length %d", length);
++ if (length < 0) {
++ PRINT_ERROR("scst_get_buf_full() failed: %d)", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out;
++ }
++
++ length = min(sl, length);
++ memcpy(address, b, length);
++ scst_set_resp_data_len(cmd, length);
++
++ scst_put_buf_full(cmd, address);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * <<Following mode pages info copied from ST318451LW with some corrections>>
++ *
++ * ToDo: revise them
++ */
++static int vdisk_err_recov_pg(unsigned char *p, int pcontrol,
++ struct scst_vdisk_dev *virt_dev)
++{ /* 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 vdisk_disconnect_pg(unsigned char *p, int pcontrol,
++ struct scst_vdisk_dev *virt_dev)
++{ /* 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 vdisk_rigid_geo_pg(unsigned char *p, int pcontrol,
++ struct scst_vdisk_dev *virt_dev)
++{
++ unsigned char geo_m_pg[] = {0x04, 0x16, 0, 0, 0, DEF_HEADS, 0, 0,
++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++ 0x3a, 0x98/* 15K RPM */, 0, 0};
++ int32_t ncyl, n, rem;
++ uint64_t dividend;
++
++ memcpy(p, geo_m_pg, sizeof(geo_m_pg));
++ /*
++ * Divide virt_dev->nblocks by (DEF_HEADS * DEF_SECTORS) and store
++ * the quotient in ncyl and the remainder in rem.
++ */
++ dividend = virt_dev->nblocks;
++ rem = do_div(dividend, DEF_HEADS * DEF_SECTORS);
++ ncyl = dividend;
++ if (rem != 0)
++ ncyl++;
++ memcpy(&n, p + 2, sizeof(u32));
++ n = n | ((__force u32)cpu_to_be32(ncyl) >> 8);
++ memcpy(p + 2, &n, sizeof(u32));
++ if (1 == pcontrol)
++ memset(p + 2, 0, sizeof(geo_m_pg) - 2);
++ return sizeof(geo_m_pg);
++}
++
++static int vdisk_format_pg(unsigned char *p, int pcontrol,
++ struct scst_vdisk_dev *virt_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 >> 8) & 0xff;
++ p[11] = DEF_SECTORS & 0xff;
++ p[12] = (virt_dev->block_size >> 8) & 0xff;
++ p[13] = virt_dev->block_size & 0xff;
++ if (1 == pcontrol)
++ memset(p + 2, 0, sizeof(format_pg) - 2);
++ return sizeof(format_pg);
++}
++
++static int vdisk_caching_pg(unsigned char *p, int pcontrol,
++ struct scst_vdisk_dev *virt_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] |= !(virt_dev->wt_flag || virt_dev->nv_cache) ? WCE : 0;
++ if (1 == pcontrol)
++ memset(p + 2, 0, sizeof(caching_pg) - 2);
++ return sizeof(caching_pg);
++}
++
++static int vdisk_ctrl_m_pg(unsigned char *p, int pcontrol,
++ struct scst_vdisk_dev *virt_dev)
++{ /* Control mode page for mode_sense */
++ const unsigned char ctrl_m_pg[] = {0xa, 0xa, 0, 0, 0, 0, 0, 0,
++ 0, 0, 0x2, 0x4b};
++
++ memcpy(p, ctrl_m_pg, sizeof(ctrl_m_pg));
++ switch (pcontrol) {
++ case 0:
++ p[2] |= virt_dev->dev->tst << 5;
++ p[2] |= virt_dev->dev->d_sense << 2;
++ p[3] |= virt_dev->dev->queue_alg << 4;
++ p[4] |= virt_dev->dev->swp << 3;
++ p[5] |= virt_dev->dev->tas << 6;
++ break;
++ case 1:
++ memset(p + 2, 0, sizeof(ctrl_m_pg) - 2);
++#if 0 /*
++ * It's too early to implement it, since we can't control the
++ * backstorage device parameters. ToDo
++ */
++ p[2] |= 7 << 5; /* TST */
++ p[3] |= 0xF << 4; /* QUEUE ALGORITHM MODIFIER */
++#endif
++ p[2] |= 1 << 2; /* D_SENSE */
++ p[4] |= 1 << 3; /* SWP */
++ p[5] |= 1 << 6; /* TAS */
++ break;
++ case 2:
++ p[2] |= DEF_TST << 5;
++ p[2] |= DEF_DSENSE << 2;
++ if (virt_dev->wt_flag || virt_dev->nv_cache)
++ p[3] |= DEF_QUEUE_ALG_WT << 4;
++ else
++ p[3] |= DEF_QUEUE_ALG << 4;
++ p[4] |= DEF_SWP << 3;
++ p[5] |= DEF_TAS << 6;
++ break;
++ default:
++ BUG();
++ }
++ return sizeof(ctrl_m_pg);
++}
++
++static int vdisk_iec_m_pg(unsigned char *p, int pcontrol,
++ struct scst_vdisk_dev *virt_dev)
++{ /* 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 vdisk_exec_mode_sense(struct scst_cmd *cmd)
++{
++ int32_t length;
++ uint8_t *address;
++ uint8_t *buf;
++ struct scst_vdisk_dev *virt_dev;
++ uint32_t 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();
++
++ buf = kzalloc(MSENSE_BUF_SZ, GFP_KERNEL);
++ if (buf == NULL) {
++ scst_set_busy(cmd);
++ goto out;
++ }
++
++ virt_dev = cmd->dev->dh_priv;
++ blocksize = virt_dev->block_size;
++ nblocks = virt_dev->nblocks;
++
++ type = cmd->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 = (virt_dev->dev->rd_only ||
++ cmd->tgt_dev->acg_dev->rd_only) ? WP : 0;
++
++ if (!virt_dev->blockio)
++ dev_spec |= DPOFUA;
++
++ length = scst_get_buf_full(cmd, &address);
++ if (unlikely(length <= 0)) {
++ if (length < 0) {
++ PRINT_ERROR("scst_get_buf_full() failed: %d", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ goto out_free;
++ }
++
++ if (0x3 == pcontrol) {
++ TRACE_DBG("%s", "MODE SENSE: Saving values not supported");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_saving_params_unsup));
++ goto out_put;
++ }
++
++ 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");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put;
++ }
++
++ 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 {
++ /* num blks */
++ buf[offset + 0] = (nblocks >> (BYTE * 3)) & 0xFF;
++ 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 = vdisk_err_recov_pg(bp, pcontrol, virt_dev);
++ break;
++ case 0x2: /* Disconnect-Reconnect page, all devices */
++ len = vdisk_disconnect_pg(bp, pcontrol, virt_dev);
++ break;
++ case 0x3: /* Format device page, direct access */
++ len = vdisk_format_pg(bp, pcontrol, virt_dev);
++ break;
++ case 0x4: /* Rigid disk geometry */
++ len = vdisk_rigid_geo_pg(bp, pcontrol, virt_dev);
++ break;
++ case 0x8: /* Caching page, direct access */
++ len = vdisk_caching_pg(bp, pcontrol, virt_dev);
++ break;
++ case 0xa: /* Control Mode page, all devices */
++ len = vdisk_ctrl_m_pg(bp, pcontrol, virt_dev);
++ break;
++ case 0x1c: /* Informational Exceptions Mode page, all devices */
++ len = vdisk_iec_m_pg(bp, pcontrol, virt_dev);
++ break;
++ case 0x3f: /* Read all Mode pages */
++ len = vdisk_err_recov_pg(bp, pcontrol, virt_dev);
++ len += vdisk_disconnect_pg(bp + len, pcontrol, virt_dev);
++ len += vdisk_format_pg(bp + len, pcontrol, virt_dev);
++ len += vdisk_caching_pg(bp + len, pcontrol, virt_dev);
++ len += vdisk_ctrl_m_pg(bp + len, pcontrol, virt_dev);
++ len += vdisk_iec_m_pg(bp + len, pcontrol, virt_dev);
++ len += vdisk_rigid_geo_pg(bp + len, pcontrol, virt_dev);
++ break;
++ default:
++ TRACE_DBG("MODE SENSE: Unsupported page %x", pcode);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put;
++ }
++
++ offset += len;
++
++ if (msense_6)
++ buf[0] = offset - 1;
++ else {
++ buf[0] = ((offset - 2) >> 8) & 0xff;
++ buf[1] = (offset - 2) & 0xff;
++ }
++
++ if (offset > length)
++ offset = length;
++ memcpy(address, buf, offset);
++
++out_put:
++ scst_put_buf_full(cmd, address);
++ if (offset < cmd->resp_data_len)
++ scst_set_resp_data_len(cmd, offset);
++
++out_free:
++ kfree(buf);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if ((virt_dev->wt_flag == wt) || virt_dev->nullio || virt_dev->nv_cache)
++ goto out;
++
++ spin_lock(&virt_dev->flags_lock);
++ virt_dev->wt_flag = wt;
++ spin_unlock(&virt_dev->flags_lock);
++
++ scst_dev_del_all_thr_data(virt_dev->dev);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void vdisk_ctrl_m_pg_select(unsigned char *p,
++ struct scst_vdisk_dev *virt_dev, struct scst_cmd *cmd)
++{
++ struct scst_device *dev = virt_dev->dev;
++ int old_swp = dev->swp, old_tas = dev->tas, old_dsense = dev->d_sense;
++
++#if 0 /* Not implemented yet, see comment in vdisk_ctrl_m_pg() */
++ dev->tst = (p[2] >> 5) & 1;
++ dev->queue_alg = p[3] >> 4;
++#else
++ if ((dev->tst != ((p[2] >> 5) & 1)) || (dev->queue_alg != (p[3] >> 4))) {
++ TRACE(TRACE_MINOR|TRACE_SCSI, "%s", "MODE SELECT: Changing of "
++ "TST and QUEUE ALGORITHM not supported");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ return;
++ }
++#endif
++ dev->swp = (p[4] & 0x8) >> 3;
++ dev->tas = (p[5] & 0x40) >> 6;
++ dev->d_sense = (p[2] & 0x4) >> 2;
++
++ PRINT_INFO("Device %s: new control mode page parameters: SWP %x "
++ "(was %x), TAS %x (was %x), D_SENSE %d (was %d)",
++ virt_dev->name, dev->swp, old_swp, dev->tas, old_tas,
++ dev->d_sense, old_dsense);
++ return;
++}
++
++static void vdisk_exec_mode_select(struct scst_cmd *cmd)
++{
++ int32_t length;
++ uint8_t *address;
++ struct scst_vdisk_dev *virt_dev;
++ int mselect_6, offset;
++
++ TRACE_ENTRY();
++
++ virt_dev = cmd->dev->dh_priv;
++ mselect_6 = (MODE_SELECT == cmd->cdb[0]);
++
++ length = scst_get_buf_full(cmd, &address);
++ if (unlikely(length <= 0)) {
++ if (length < 0) {
++ PRINT_ERROR("scst_get_buf_full() failed: %d", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ goto out;
++ }
++
++ if (!(cmd->cdb[1] & PF) || (cmd->cdb[1] & SP)) {
++ TRACE(TRACE_MINOR|TRACE_SCSI, "MODE SELECT: Unsupported "
++ "value(s) of PF and/or SP bits (cdb[1]=%x)",
++ cmd->cdb[1]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put;
++ }
++
++ if (mselect_6)
++ offset = 4;
++ else
++ offset = 8;
++
++ if (address[offset - 1] == 8) {
++ offset += 8;
++ } else if (address[offset - 1] != 0) {
++ PRINT_ERROR("%s", "MODE SELECT: Wrong parameters list "
++ "lenght");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
++ goto out_put;
++ }
++
++ while (length > offset + 2) {
++ if (address[offset] & PS) {
++ PRINT_ERROR("%s", "MODE SELECT: Illegal PS bit");
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_invalid_field_in_parm_list));
++ goto out_put;
++ }
++ if ((address[offset] & 0x3f) == 0x8) {
++ /* Caching page */
++ if (address[offset + 1] != 18) {
++ PRINT_ERROR("%s", "MODE SELECT: Invalid "
++ "caching page request");
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_invalid_field_in_parm_list));
++ goto out_put;
++ }
++ if (vdisk_set_wt(virt_dev,
++ (address[offset + 2] & WCE) ? 0 : 1) != 0) {
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_put;
++ }
++ break;
++ } else if ((address[offset] & 0x3f) == 0xA) {
++ /* Control page */
++ if (address[offset + 1] != 0xA) {
++ PRINT_ERROR("%s", "MODE SELECT: Invalid "
++ "control page request");
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_invalid_field_in_parm_list));
++ goto out_put;
++ }
++ vdisk_ctrl_m_pg_select(&address[offset], virt_dev, cmd);
++ } else {
++ PRINT_ERROR("MODE SELECT: Invalid request %x",
++ address[offset] & 0x3f);
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
++ scst_sense_invalid_field_in_parm_list));
++ goto out_put;
++ }
++ offset += address[offset + 1];
++ }
++
++out_put:
++ scst_put_buf_full(cmd, address);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_exec_log(struct scst_cmd *cmd)
++{
++ TRACE_ENTRY();
++
++ /* No log pages are supported */
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_exec_read_capacity(struct scst_cmd *cmd)
++{
++ int32_t length;
++ uint8_t *address;
++ struct scst_vdisk_dev *virt_dev;
++ uint32_t blocksize;
++ uint64_t nblocks;
++ uint8_t buffer[8];
++
++ TRACE_ENTRY();
++
++ virt_dev = cmd->dev->dh_priv;
++ blocksize = virt_dev->block_size;
++ nblocks = virt_dev->nblocks;
++
++ if ((cmd->cdb[8] & 1) == 0) {
++ uint64_t lba = be64_to_cpu(get_unaligned((__be64 *)&cmd->cdb[2]));
++ if (lba != 0) {
++ TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++ }
++
++ /* Last block on the virt_dev is (nblocks-1) */
++ memset(buffer, 0, sizeof(buffer));
++
++ /*
++ * If we are thinly provisioned, we must ensure that the initiator
++ * issues a READ_CAPACITY(16) so we can return the TPE bit. By
++ * returning 0xFFFFFFFF we do that.
++ */
++ if (nblocks >> 32 || virt_dev->thin_provisioned) {
++ 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;
++
++ length = scst_get_buf_full(cmd, &address);
++ if (unlikely(length <= 0)) {
++ if (length < 0) {
++ PRINT_ERROR("scst_get_buf_full() failed: %d", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ goto out;
++ }
++
++ length = min_t(int, length, sizeof(buffer));
++
++ memcpy(address, buffer, length);
++
++ scst_put_buf_full(cmd, address);
++
++ if (length < cmd->resp_data_len)
++ scst_set_resp_data_len(cmd, length);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_exec_read_capacity16(struct scst_cmd *cmd)
++{
++ int32_t length;
++ uint8_t *address;
++ struct scst_vdisk_dev *virt_dev;
++ uint32_t blocksize;
++ uint64_t nblocks;
++ uint8_t buffer[32];
++
++ TRACE_ENTRY();
++
++ virt_dev = cmd->dev->dh_priv;
++ blocksize = virt_dev->block_size;
++ nblocks = virt_dev->nblocks - 1;
++
++ if ((cmd->cdb[14] & 1) == 0) {
++ uint64_t lba = be64_to_cpu(get_unaligned((__be64 *)&cmd->cdb[2]));
++ if (lba != 0) {
++ TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++ }
++
++ 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;
++
++ switch (blocksize) {
++ case 512:
++ buffer[13] = 3;
++ break;
++ case 1024:
++ buffer[13] = 2;
++ break;
++ case 2048:
++ buffer[13] = 1;
++ break;
++ case 4096:
++ default:
++ buffer[13] = 0;
++ break;
++ }
++
++ if (virt_dev->thin_provisioned) {
++ buffer[14] |= 0x80; /* Add TPE */
++#if 0 /*
++ * Might be a big performance and functionality win, but might be
++ * dangerous as well, although generally nearly always it should be set,
++ * because nearly all devices should return zero for unmapped blocks.
++ * But let's be on the safe side and disable it for now.
++ */
++ buffer[14] |= 0x40; /* Add TPRZ */
++#endif
++ }
++
++ length = scst_get_buf_full(cmd, &address);
++ if (unlikely(length <= 0)) {
++ if (length < 0) {
++ PRINT_ERROR("scst_get_buf_full() failed: %d", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ goto out;
++ }
++
++ length = min_t(int, length, sizeof(buffer));
++
++ memcpy(address, buffer, length);
++
++ scst_put_buf_full(cmd, address);
++
++ if (length < cmd->resp_data_len)
++ scst_set_resp_data_len(cmd, length);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* SPC-4 REPORT TARGET PORT GROUPS command */
++static void vdisk_exec_report_tpgs(struct scst_cmd *cmd)
++{
++ struct scst_device *dev;
++ uint8_t *address;
++ void *buf;
++ int32_t buf_len;
++ uint32_t allocation_length, data_length, length;
++ uint8_t data_format;
++ int res;
++
++ TRACE_ENTRY();
++
++ buf_len = scst_get_buf_full(cmd, &address);
++ if (buf_len < 0) {
++ PRINT_ERROR("scst_get_buf_full() failed: %d", buf_len);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out;
++ }
++
++ if (cmd->cdb_len < 12)
++ PRINT_WARNING("received invalid REPORT TARGET PORT GROUPS "
++ "command - length %d is too small (should be at "
++ "least 12 bytes)", cmd->cdb_len);
++
++ dev = cmd->dev;
++ data_format = cmd->cdb_len > 1 ? cmd->cdb[1] >> 5 : 0;
++ allocation_length = cmd->cdb_len >= 10 ?
++ be32_to_cpu(get_unaligned((__be32 *)(cmd->cdb + 6))) : 1024;
++
++ res = scst_tg_get_group_info(&buf, &data_length, dev, data_format);
++ if (res == -ENOMEM) {
++ scst_set_busy(cmd);
++ goto out_put;
++ } else if (res < 0) {
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out_put;
++ }
++
++ length = min_t(uint32_t, min(allocation_length, data_length), buf_len);
++ memcpy(address, buf, length);
++ kfree(buf);
++ if (length < cmd->resp_data_len)
++ scst_set_resp_data_len(cmd, length);
++
++out_put:
++ scst_put_buf_full(cmd, address);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_exec_read_toc(struct scst_cmd *cmd)
++{
++ int32_t length, off = 0;
++ uint8_t *address;
++ struct scst_vdisk_dev *virt_dev;
++ uint32_t nblocks;
++ uint8_t buffer[4+8+8] = { 0x00, 0x0a, 0x01, 0x01, 0x00, 0x14,
++ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
++
++ TRACE_ENTRY();
++
++ if (cmd->dev->type != TYPE_ROM) {
++ PRINT_ERROR("%s", "READ TOC for non-CDROM device");
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_opcode));
++ goto out;
++ }
++
++ if (cmd->cdb[2] & 0x0e/*Format*/) {
++ PRINT_ERROR("%s", "READ TOC: invalid requested data format");
++ scst_set_cmd_error(cmd,
++ 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("READ TOC: invalid requested track number %x",
++ cmd->cdb[6]);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ goto out;
++ }
++
++ length = scst_get_buf_full(cmd, &address);
++ if (unlikely(length <= 0)) {
++ if (length < 0) {
++ PRINT_ERROR("scst_get_buf_full() failed: %d", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++ goto out;
++ }
++
++ virt_dev = cmd->dev->dh_priv;
++ /* ToDo when you have > 8TB ROM device. */
++ nblocks = (uint32_t)virt_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 */
++ /* ADDR 0x10 - Q Sub-channel encodes current position data
++ CONTROL 0x04 - Data track, recoreded uninterrupted */
++ buffer[off+1] = 0x14;
++ /* Track Number */
++ buffer[off+2] = 0x01;
++ off += 8;
++ }
++ if (!(cmd->cdb[2] & 0x01)) {
++ /* Lead-out area TOC Track Descriptor */
++ buffer[off+1] = 0x14;
++ /* Track Number */
++ buffer[off+2] = 0xAA;
++ /* Track Start Address */
++ buffer[off+4] = (nblocks >> (BYTE * 3)) & 0xFF;
++ 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);
++
++ scst_put_buf_full(cmd, address);
++
++ if (off < cmd->resp_data_len)
++ scst_set_resp_data_len(cmd, off);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void vdisk_exec_prevent_allow_medium_removal(struct scst_cmd *cmd)
++{
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++
++ TRACE_DBG("PERSIST/PREVENT 0x%02x", cmd->cdb[4]);
++
++ spin_lock(&virt_dev->flags_lock);
++ virt_dev->prevent_allow_medium_removal = cmd->cdb[4] & 0x01 ? 1 : 0;
++ spin_unlock(&virt_dev->flags_lock);
++
++ return;
++}
++
++static int vdisk_fsync(struct scst_vdisk_thr *thr, loff_t loff,
++ loff_t len, struct scst_cmd *cmd, struct scst_device *dev)
++{
++ int res = 0;
++ struct scst_vdisk_dev *virt_dev = dev->dh_priv;
++ struct file *file;
++
++ TRACE_ENTRY();
++
++ /* Hopefully, the compiler will generate the single comparison */
++ if (virt_dev->nv_cache || virt_dev->wt_flag ||
++ virt_dev->o_direct_flag || virt_dev->nullio)
++ goto out;
++
++ if (virt_dev->blockio) {
++ res = vdisk_blockio_flush(thr->bdev,
++ (cmd->noio_mem_alloc ? GFP_NOIO : GFP_KERNEL), true);
++ goto out;
++ }
++
++ file = thr->fd;
++
++#if 0 /* For sparse files we might need to sync metadata as well */
++ res = generic_write_sync(file, loff, len);
++#else
++ res = filemap_write_and_wait_range(file->f_mapping, loff, len);
++#endif
++ if (unlikely(res != 0)) {
++ PRINT_ERROR("sync range failed (%d)", res);
++ if (cmd != NULL) {
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_write_error));
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct iovec *vdisk_alloc_iv(struct scst_cmd *cmd,
++ struct scst_vdisk_thr *thr)
++{
++ int iv_count;
++
++ iv_count = min_t(int, scst_get_buf_count(cmd), UIO_MAXIOV);
++ if (iv_count > thr->iv_count) {
++ kfree(thr->iv);
++ /* It can't be called in atomic context */
++ thr->iv = kmalloc(sizeof(*thr->iv) * iv_count, GFP_KERNEL);
++ if (thr->iv == NULL) {
++ PRINT_ERROR("Unable to allocate iv (%d)", iv_count);
++ scst_set_busy(cmd);
++ goto out;
++ }
++ thr->iv_count = iv_count;
++ }
++
++out:
++ return thr->iv;
++}
++
++static void vdisk_exec_read(struct scst_cmd *cmd,
++ struct scst_vdisk_thr *thr, loff_t loff)
++{
++ mm_segment_t old_fs;
++ loff_t err;
++ ssize_t length, full_len;
++ uint8_t __user *address;
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++ struct file *fd = thr->fd;
++ struct iovec *iv;
++ int iv_count, i;
++ bool finished = false;
++
++ TRACE_ENTRY();
++
++ if (virt_dev->nullio)
++ goto out;
++
++ iv = vdisk_alloc_iv(cmd, thr);
++ if (iv == NULL)
++ goto out;
++
++ length = scst_get_buf_first(cmd, (uint8_t __force **)&address);
++ if (unlikely(length < 0)) {
++ PRINT_ERROR("scst_get_buf_first() failed: %zd", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out;
++ }
++
++ old_fs = get_fs();
++ set_fs(get_ds());
++
++ while (1) {
++ iv_count = 0;
++ full_len = 0;
++ i = -1;
++ while (length > 0) {
++ full_len += length;
++ i++;
++ iv_count++;
++ iv[i].iov_base = address;
++ iv[i].iov_len = length;
++ if (iv_count == UIO_MAXIOV)
++ break;
++ length = scst_get_buf_next(cmd,
++ (uint8_t __force **)&address);
++ }
++ if (length == 0) {
++ finished = true;
++ if (unlikely(iv_count == 0))
++ break;
++ } else if (unlikely(length < 0)) {
++ PRINT_ERROR("scst_get_buf_next() failed: %zd", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_set_fs;
++ }
++
++ TRACE_DBG("(iv_count %d, full_len %zd)", iv_count, full_len);
++ /* SEEK */
++ if (fd->f_op->llseek)
++ err = fd->f_op->llseek(fd, loff, 0/*SEEK_SET*/);
++ else
++ err = default_llseek(fd, loff, 0/*SEEK_SET*/);
++ if (err != loff) {
++ PRINT_ERROR("lseek trouble %lld != %lld",
++ (long long unsigned int)err,
++ (long long unsigned int)loff);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_set_fs;
++ }
++
++ /* READ */
++ err = vfs_readv(fd, (struct iovec __force __user *)iv, iv_count,
++ &fd->f_pos);
++
++ if ((err < 0) || (err < full_len)) {
++ PRINT_ERROR("readv() returned %lld from %zd",
++ (long long unsigned int)err,
++ full_len);
++ if (err == -EAGAIN)
++ scst_set_busy(cmd);
++ else {
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_read_error));
++ }
++ goto out_set_fs;
++ }
++
++ for (i = 0; i < iv_count; i++)
++ scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
++
++ if (finished)
++ break;
++
++ loff += full_len;
++ length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
++ };
++
++ set_fs(old_fs);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_set_fs:
++ set_fs(old_fs);
++ for (i = 0; i < iv_count; i++)
++ scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
++ goto out;
++}
++
++static void vdisk_exec_write(struct scst_cmd *cmd,
++ struct scst_vdisk_thr *thr, loff_t loff)
++{
++ mm_segment_t old_fs;
++ loff_t err;
++ ssize_t length, full_len, saved_full_len;
++ uint8_t __user *address;
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++ struct file *fd = thr->fd;
++ struct iovec *iv, *eiv;
++ int i, iv_count, eiv_count;
++ bool finished = false;
++
++ TRACE_ENTRY();
++
++ if (virt_dev->nullio)
++ goto out;
++
++ iv = vdisk_alloc_iv(cmd, thr);
++ if (iv == NULL)
++ goto out;
++
++ length = scst_get_buf_first(cmd, (uint8_t __force **)&address);
++ if (unlikely(length < 0)) {
++ PRINT_ERROR("scst_get_buf_first() failed: %zd", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out;
++ }
++
++ old_fs = get_fs();
++ set_fs(get_ds());
++
++ while (1) {
++ iv_count = 0;
++ full_len = 0;
++ i = -1;
++ while (length > 0) {
++ full_len += length;
++ i++;
++ iv_count++;
++ iv[i].iov_base = address;
++ iv[i].iov_len = length;
++ if (iv_count == UIO_MAXIOV)
++ break;
++ length = scst_get_buf_next(cmd,
++ (uint8_t __force **)&address);
++ }
++ if (length == 0) {
++ finished = true;
++ if (unlikely(iv_count == 0))
++ break;
++ } else if (unlikely(length < 0)) {
++ PRINT_ERROR("scst_get_buf_next() failed: %zd", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_set_fs;
++ }
++
++ saved_full_len = full_len;
++ eiv = iv;
++ eiv_count = iv_count;
++restart:
++ TRACE_DBG("writing(eiv_count %d, full_len %zd)", eiv_count, full_len);
++
++ /* SEEK */
++ if (fd->f_op->llseek)
++ err = fd->f_op->llseek(fd, loff, 0 /*SEEK_SET */);
++ else
++ err = default_llseek(fd, loff, 0 /*SEEK_SET */);
++ if (err != loff) {
++ PRINT_ERROR("lseek trouble %lld != %lld",
++ (long long unsigned int)err,
++ (long long unsigned int)loff);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_set_fs;
++ }
++
++ /* WRITE */
++ err = vfs_writev(fd, (struct iovec __force __user *)eiv, eiv_count,
++ &fd->f_pos);
++
++ if (err < 0) {
++ PRINT_ERROR("write() returned %lld from %zd",
++ (long long unsigned int)err,
++ full_len);
++ if (err == -EAGAIN)
++ scst_set_busy(cmd);
++ else {
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_write_error));
++ }
++ goto out_set_fs;
++ } else if (err < full_len) {
++ /*
++ * Probably that's wrong, but sometimes write() returns
++ * value less, than requested. Let's restart.
++ */
++ int e = eiv_count;
++ TRACE_MGMT_DBG("write() returned %d from %zd "
++ "(iv_count=%d)", (int)err, full_len,
++ eiv_count);
++ if (err == 0) {
++ PRINT_INFO("Suspicious: write() returned 0 from "
++ "%zd (iv_count=%d)", full_len, eiv_count);
++ }
++ full_len -= err;
++ for (i = 0; i < e; i++) {
++ if ((long long)eiv->iov_len < err) {
++ err -= eiv->iov_len;
++ eiv++;
++ eiv_count--;
++ } else {
++ eiv->iov_base =
++ (uint8_t __force __user *)eiv->iov_base + err;
++ eiv->iov_len -= err;
++ break;
++ }
++ }
++ goto restart;
++ }
++
++ for (i = 0; i < iv_count; i++)
++ scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
++
++ if (finished)
++ break;
++
++ loff += saved_full_len;
++ length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
++ }
++
++ set_fs(old_fs);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_set_fs:
++ set_fs(old_fs);
++ for (i = 0; i < iv_count; i++)
++ scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
++ goto out;
++}
++
++struct scst_blockio_work {
++ atomic_t bios_inflight;
++ struct scst_cmd *cmd;
++};
++
++static inline void blockio_check_finish(struct scst_blockio_work *blockio_work)
++{
++ /* Decrement the bios in processing, and if zero signal completion */
++ if (atomic_dec_and_test(&blockio_work->bios_inflight)) {
++ blockio_work->cmd->completed = 1;
++ blockio_work->cmd->scst_cmd_done(blockio_work->cmd,
++ SCST_CMD_STATE_DEFAULT, scst_estimate_context());
++ kmem_cache_free(blockio_work_cachep, blockio_work);
++ }
++ return;
++}
++
++static void blockio_endio(struct bio *bio, int error)
++{
++ struct scst_blockio_work *blockio_work = bio->bi_private;
++
++ if (unlikely(!bio_flagged(bio, BIO_UPTODATE))) {
++ if (error == 0) {
++ PRINT_ERROR("Not up to date bio with error 0 for "
++ "cmd %p, returning -EIO", blockio_work->cmd);
++ error = -EIO;
++ }
++ }
++
++ if (unlikely(error != 0)) {
++ static DEFINE_SPINLOCK(blockio_endio_lock);
++ unsigned long flags;
++
++ PRINT_ERROR("cmd %p returned error %d", blockio_work->cmd,
++ error);
++
++ /* To protect from several bios finishing simultaneously */
++ spin_lock_irqsave(&blockio_endio_lock, flags);
++
++ if (bio->bi_rw & REQ_WRITE)
++ scst_set_cmd_error(blockio_work->cmd,
++ SCST_LOAD_SENSE(scst_sense_write_error));
++ else
++ scst_set_cmd_error(blockio_work->cmd,
++ SCST_LOAD_SENSE(scst_sense_read_error));
++
++ spin_unlock_irqrestore(&blockio_endio_lock, flags);
++ }
++
++ blockio_check_finish(blockio_work);
++
++ bio_put(bio);
++ return;
++}
++
++static void blockio_exec_rw(struct scst_cmd *cmd, struct scst_vdisk_thr *thr,
++ u64 lba_start, int write)
++{
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++ struct block_device *bdev = thr->bdev;
++ struct request_queue *q = bdev_get_queue(bdev);
++ int length, max_nr_vecs = 0, offset;
++ struct page *page;
++ struct bio *bio = NULL, *hbio = NULL, *tbio = NULL;
++ int need_new_bio;
++ struct scst_blockio_work *blockio_work;
++ int bios = 0;
++ gfp_t gfp_mask = (cmd->noio_mem_alloc ? GFP_NOIO : GFP_KERNEL);
++ struct blk_plug plug;
++
++ TRACE_ENTRY();
++
++ if (virt_dev->nullio)
++ goto out;
++
++ /* Allocate and initialize blockio_work struct */
++ blockio_work = kmem_cache_alloc(blockio_work_cachep, gfp_mask);
++ if (blockio_work == NULL)
++ goto out_no_mem;
++
++ blockio_work->cmd = cmd;
++
++ if (q)
++ max_nr_vecs = min(bio_get_nr_vecs(bdev), BIO_MAX_PAGES);
++ else
++ max_nr_vecs = 1;
++
++ need_new_bio = 1;
++
++ length = scst_get_sg_page_first(cmd, &page, &offset);
++ while (length > 0) {
++ int len, bytes, off, thislen;
++ struct page *pg;
++ u64 lba_start0;
++
++ pg = page;
++ len = length;
++ off = offset;
++ thislen = 0;
++ lba_start0 = lba_start;
++
++ while (len > 0) {
++ int rc;
++
++ if (need_new_bio) {
++ bio = bio_kmalloc(gfp_mask, max_nr_vecs);
++ if (!bio) {
++ PRINT_ERROR("Failed to create bio "
++ "for data segment %d (cmd %p)",
++ cmd->get_sg_buf_entry_num, cmd);
++ goto out_no_bio;
++ }
++
++ bios++;
++ need_new_bio = 0;
++ bio->bi_end_io = blockio_endio;
++ bio->bi_sector = lba_start0 <<
++ (virt_dev->block_shift - 9);
++ bio->bi_bdev = bdev;
++ bio->bi_private = blockio_work;
++ /*
++ * Better to fail fast w/o any local recovery
++ * and retries.
++ */
++ bio->bi_rw |= REQ_FAILFAST_DEV |
++ REQ_FAILFAST_TRANSPORT |
++ REQ_FAILFAST_DRIVER;
++#if 0 /* It could be win, but could be not, so a performance study is needed */
++ bio->bi_rw |= REQ_SYNC;
++#endif
++ if (!hbio)
++ hbio = tbio = bio;
++ else
++ tbio = tbio->bi_next = bio;
++ }
++
++ bytes = min_t(unsigned int, len, PAGE_SIZE - off);
++
++ rc = bio_add_page(bio, pg, bytes, off);
++ if (rc < bytes) {
++ BUG_ON(rc != 0);
++ need_new_bio = 1;
++ lba_start0 += thislen >> virt_dev->block_shift;
++ thislen = 0;
++ continue;
++ }
++
++ pg++;
++ thislen += bytes;
++ len -= bytes;
++ off = 0;
++ }
++
++ lba_start += length >> virt_dev->block_shift;
++
++ scst_put_sg_page(cmd, page, offset);
++ length = scst_get_sg_page_next(cmd, &page, &offset);
++ }
++
++ /* +1 to prevent erroneous too early command completion */
++ atomic_set(&blockio_work->bios_inflight, bios+1);
++
++ blk_start_plug(&plug);
++
++ while (hbio) {
++ bio = hbio;
++ hbio = hbio->bi_next;
++ bio->bi_next = NULL;
++ submit_bio((write != 0), bio);
++ }
++
++ blk_finish_plug(&plug);
++
++ blockio_check_finish(blockio_work);
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_no_bio:
++ while (hbio) {
++ bio = hbio;
++ hbio = hbio->bi_next;
++ bio_put(bio);
++ }
++ kmem_cache_free(blockio_work_cachep, blockio_work);
++
++out_no_mem:
++ scst_set_busy(cmd);
++ goto out;
++}
++
++static int vdisk_blockio_flush(struct block_device *bdev, gfp_t gfp_mask,
++ bool report_error)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ res = blkdev_issue_flush(bdev, gfp_mask, NULL);
++ if ((res != 0) && report_error)
++ PRINT_ERROR("blkdev_issue_flush() failed: %d", res);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void vdisk_exec_verify(struct scst_cmd *cmd,
++ struct scst_vdisk_thr *thr, loff_t loff)
++{
++ mm_segment_t old_fs;
++ loff_t err;
++ ssize_t length, len_mem = 0;
++ uint8_t *address_sav, *address;
++ int compare;
++ struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv;
++ struct file *fd = thr->fd;
++ uint8_t *mem_verify = NULL;
++
++ TRACE_ENTRY();
++
++ if (vdisk_fsync(thr, loff, cmd->bufflen, cmd, cmd->dev) != 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 */
++ old_fs = get_fs();
++ set_fs(get_ds());
++
++ if (!virt_dev->nullio) {
++ if (fd->f_op->llseek)
++ err = fd->f_op->llseek(fd, loff, 0/*SEEK_SET*/);
++ else
++ err = default_llseek(fd, loff, 0/*SEEK_SET*/);
++ if (err != loff) {
++ PRINT_ERROR("lseek trouble %lld != %lld",
++ (long long unsigned int)err,
++ (long long unsigned int)loff);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_set_fs;
++ }
++ }
++
++ mem_verify = vmalloc(LEN_MEM);
++ if (mem_verify == NULL) {
++ PRINT_ERROR("Unable to allocate memory %d for verify",
++ LEN_MEM);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_set_fs;
++ }
++
++ length = scst_get_buf_first(cmd, &address);
++ address_sav = address;
++ if (!length && cmd->data_len) {
++ length = cmd->data_len;
++ compare = 0;
++ } else
++ compare = 1;
++
++ while (length > 0) {
++ len_mem = (length > LEN_MEM) ? LEN_MEM : length;
++ TRACE_DBG("Verify: length %zd - len_mem %zd", length, len_mem);
++
++ if (!virt_dev->nullio)
++ err = vfs_read(fd, (char __force __user *)mem_verify,
++ len_mem, &fd->f_pos);
++ else
++ err = len_mem;
++ if ((err < 0) || (err < len_mem)) {
++ PRINT_ERROR("verify() returned %lld from %zd",
++ (long long unsigned int)err, len_mem);
++ if (err == -EAGAIN)
++ scst_set_busy(cmd);
++ else {
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_read_error));
++ }
++ if (compare)
++ scst_put_buf(cmd, address_sav);
++ goto out_set_fs;
++ }
++ if (compare && memcmp(address, mem_verify, len_mem) != 0) {
++ TRACE_DBG("Verify: error memcmp length %zd", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_miscompare_error));
++ scst_put_buf(cmd, address_sav);
++ goto out_set_fs;
++ }
++ length -= len_mem;
++ address += len_mem;
++ if (compare && length <= 0) {
++ scst_put_buf(cmd, address_sav);
++ length = scst_get_buf_next(cmd, &address);
++ address_sav = address;
++ }
++ }
++
++ if (length < 0) {
++ PRINT_ERROR("scst_get_buf_() failed: %zd", length);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ }
++
++out_set_fs:
++ set_fs(old_fs);
++ if (mem_verify)
++ vfree(mem_verify);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int vdisk_task_mgmt_fn(struct scst_mgmt_cmd *mcmd,
++ struct scst_tgt_dev *tgt_dev)
++{
++ TRACE_ENTRY();
++
++ if ((mcmd->fn == SCST_LUN_RESET) || (mcmd->fn == SCST_TARGET_RESET)) {
++ /* Restore default values */
++ struct scst_device *dev = tgt_dev->dev;
++ struct scst_vdisk_dev *virt_dev = dev->dh_priv;
++
++ dev->tst = DEF_TST;
++ dev->d_sense = DEF_DSENSE;
++ if (virt_dev->wt_flag && !virt_dev->nv_cache)
++ dev->queue_alg = DEF_QUEUE_ALG_WT;
++ else
++ dev->queue_alg = DEF_QUEUE_ALG;
++ dev->swp = DEF_SWP;
++ dev->tas = DEF_TAS;
++
++ spin_lock(&virt_dev->flags_lock);
++ virt_dev->prevent_allow_medium_removal = 0;
++ spin_unlock(&virt_dev->flags_lock);
++ } else if (mcmd->fn == SCST_PR_ABORT_ALL) {
++ struct scst_device *dev = tgt_dev->dev;
++ struct scst_vdisk_dev *virt_dev = dev->dh_priv;
++ spin_lock(&virt_dev->flags_lock);
++ virt_dev->prevent_allow_medium_removal = 0;
++ spin_unlock(&virt_dev->flags_lock);
++ }
++
++ TRACE_EXIT();
++ return SCST_DEV_TM_NOT_COMPLETED;
++}
++
++static void vdisk_report_registering(const struct scst_vdisk_dev *virt_dev)
++{
++ char buf[128];
++ int i, j;
++
++ i = snprintf(buf, sizeof(buf), "Registering virtual %s device %s ",
++ virt_dev->vdev_devt->name, virt_dev->name);
++ j = i;
++
++ if (virt_dev->wt_flag)
++ i += snprintf(&buf[i], sizeof(buf) - i, "(WRITE_THROUGH");
++
++ if (virt_dev->nv_cache)
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sNV_CACHE",
++ (j == i) ? "(" : ", ");
++
++ if (virt_dev->rd_only)
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sREAD_ONLY",
++ (j == i) ? "(" : ", ");
++
++ if (virt_dev->o_direct_flag)
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sO_DIRECT",
++ (j == i) ? "(" : ", ");
++
++ if (virt_dev->nullio)
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sNULLIO",
++ (j == i) ? "(" : ", ");
++
++ if (virt_dev->blockio)
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sBLOCKIO",
++ (j == i) ? "(" : ", ");
++
++ if (virt_dev->removable)
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sREMOVABLE",
++ (j == i) ? "(" : ", ");
++
++ if (virt_dev->thin_provisioned)
++ i += snprintf(&buf[i], sizeof(buf) - i, "%sTHIN PROVISIONED",
++ (j == i) ? "(" : ", ");
++
++ if (j == i)
++ PRINT_INFO("%s", buf);
++ else
++ PRINT_INFO("%s)", buf);
++
++ return;
++}
++
++static int vdisk_resync_size(struct scst_vdisk_dev *virt_dev)
++{
++ loff_t file_size;
++ int res = 0;
++
++ BUG_ON(virt_dev->nullio);
++
++ res = vdisk_get_file_size(virt_dev->filename,
++ virt_dev->blockio, &file_size);
++ if (res != 0)
++ goto out;
++
++ if (file_size == virt_dev->file_size) {
++ PRINT_INFO("Size of virtual disk %s remained the same",
++ virt_dev->name);
++ goto out;
++ }
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out;
++
++ virt_dev->file_size = file_size;
++ virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift;
++
++ scst_dev_del_all_thr_data(virt_dev->dev);
++
++ PRINT_INFO("New size of SCSI target virtual disk %s "
++ "(fs=%lldMB, bs=%d, nblocks=%lld, cyln=%lld%s)",
++ virt_dev->name, virt_dev->file_size >> 20,
++ virt_dev->block_size,
++ (long long unsigned int)virt_dev->nblocks,
++ (long long unsigned int)virt_dev->nblocks/64/32,
++ virt_dev->nblocks < 64*32 ? " !WARNING! cyln less "
++ "than 1" : "");
++
++ scst_capacity_data_changed(virt_dev->dev);
++
++ scst_resume_activity();
++
++out:
++ return res;
++}
++
++static int vdev_create(struct scst_dev_type *devt,
++ const char *name, struct scst_vdisk_dev **res_virt_dev)
++{
++ int res;
++ struct scst_vdisk_dev *virt_dev;
++ uint64_t dev_id_num;
++
++ res = -EEXIST;
++ if (vdev_find(name))
++ goto out;
++
++ virt_dev = kzalloc(sizeof(*virt_dev), GFP_KERNEL);
++ if (virt_dev == NULL) {
++ PRINT_ERROR("Allocation of virtual device %s failed",
++ devt->name);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ spin_lock_init(&virt_dev->flags_lock);
++ virt_dev->vdev_devt = devt;
++
++ virt_dev->rd_only = DEF_RD_ONLY;
++ virt_dev->removable = DEF_REMOVABLE;
++ virt_dev->thin_provisioned = DEF_THIN_PROVISIONED;
++
++ virt_dev->block_size = DEF_DISK_BLOCKSIZE;
++ virt_dev->block_shift = DEF_DISK_BLOCKSIZE_SHIFT;
++
++ if (strlen(name) >= sizeof(virt_dev->name)) {
++ PRINT_ERROR("Name %s is too long (max allowed %zd)", name,
++ sizeof(virt_dev->name)-1);
++ res = -EINVAL;
++ goto out_free;
++ }
++ strcpy(virt_dev->name, name);
++
++ dev_id_num = vdisk_gen_dev_id_num(virt_dev->name);
++
++ snprintf(virt_dev->t10_dev_id, sizeof(virt_dev->t10_dev_id),
++ "%llx-%s", dev_id_num, virt_dev->name);
++ TRACE_DBG("t10_dev_id %s", virt_dev->t10_dev_id);
++
++ scnprintf(virt_dev->usn, sizeof(virt_dev->usn), "%llx", dev_id_num);
++ TRACE_DBG("usn %s", virt_dev->usn);
++
++ *res_virt_dev = virt_dev;
++ res = 0;
++
++out:
++ return res;
++
++out_free:
++ kfree(virt_dev);
++ goto out;
++}
++
++static void vdev_destroy(struct scst_vdisk_dev *virt_dev)
++{
++ kfree(virt_dev->filename);
++ kfree(virt_dev);
++ return;
++}
++
++static int vdev_parse_add_dev_params(struct scst_vdisk_dev *virt_dev,
++ char *params, const char *allowed_params[])
++{
++ int res = 0;
++ unsigned long val;
++ char *param, *p, *pp;
++
++ TRACE_ENTRY();
++
++ while (1) {
++ param = scst_get_next_token_str(&params);
++ if (param == NULL)
++ break;
++
++ p = scst_get_next_lexem(&param);
++ if (*p == '\0') {
++ PRINT_ERROR("Syntax error at %s (device %s)",
++ param, virt_dev->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (allowed_params != NULL) {
++ const char **a = allowed_params;
++ bool allowed = false;
++
++ while (*a != NULL) {
++ if (!strcasecmp(*a, p)) {
++ allowed = true;
++ break;
++ }
++ a++;
++ }
++
++ if (!allowed) {
++ PRINT_ERROR("Unknown parameter %s (device %s)", p,
++ virt_dev->name);
++ res = -EINVAL;
++ goto out;
++ }
++ }
++
++ pp = scst_get_next_lexem(&param);
++ if (*pp == '\0') {
++ PRINT_ERROR("Parameter %s value missed for device %s",
++ p, virt_dev->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (scst_get_next_lexem(&param)[0] != '\0') {
++ PRINT_ERROR("Too many parameter's %s values (device %s)",
++ p, virt_dev->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (!strcasecmp("filename", p)) {
++ if (*pp != '/') {
++ PRINT_ERROR("Filename %s must be global "
++ "(device %s)", pp, virt_dev->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ virt_dev->filename = kstrdup(pp, GFP_KERNEL);
++ if (virt_dev->filename == NULL) {
++ PRINT_ERROR("Unable to duplicate file name %s "
++ "(device %s)", pp, virt_dev->name);
++ res = -ENOMEM;
++ goto out;
++ }
++ continue;
++ }
++
++ res = strict_strtoul(pp, 0, &val);
++ if (res != 0) {
++ PRINT_ERROR("strict_strtoul() for %s failed: %d "
++ "(device %s)", pp, res, virt_dev->name);
++ goto out;
++ }
++
++ if (!strcasecmp("write_through", p)) {
++ virt_dev->wt_flag = val;
++ TRACE_DBG("WRITE THROUGH %d", virt_dev->wt_flag);
++ } else if (!strcasecmp("nv_cache", p)) {
++ virt_dev->nv_cache = val;
++ TRACE_DBG("NON-VOLATILE CACHE %d", virt_dev->nv_cache);
++ } else if (!strcasecmp("o_direct", p)) {
++#if 0
++ virt_dev->o_direct_flag = val;
++ TRACE_DBG("O_DIRECT %d", virt_dev->o_direct_flag);
++#else
++ PRINT_INFO("O_DIRECT flag doesn't currently"
++ " work, ignoring it, use fileio_tgt "
++ "in O_DIRECT mode instead (device %s)", virt_dev->name);
++#endif
++ } else if (!strcasecmp("read_only", p)) {
++ virt_dev->rd_only = val;
++ TRACE_DBG("READ ONLY %d", virt_dev->rd_only);
++ } else if (!strcasecmp("removable", p)) {
++ virt_dev->removable = val;
++ TRACE_DBG("REMOVABLE %d", virt_dev->removable);
++ } else if (!strcasecmp("thin_provisioned", p)) {
++ virt_dev->thin_provisioned = val;
++ TRACE_DBG("THIN PROVISIONED %d",
++ virt_dev->thin_provisioned);
++ } else if (!strcasecmp("blocksize", p)) {
++ virt_dev->block_size = val;
++ virt_dev->block_shift = scst_calc_block_shift(
++ virt_dev->block_size);
++ if (virt_dev->block_shift < 9) {
++ res = -EINVAL;
++ goto out;
++ }
++ TRACE_DBG("block_size %d, block_shift %d",
++ virt_dev->block_size,
++ virt_dev->block_shift);
++ } else {
++ PRINT_ERROR("Unknown parameter %s (device %s)", p,
++ virt_dev->name);
++ res = -EINVAL;
++ goto out;
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_vdisk_mutex supposed to be held */
++static int vdev_fileio_add_device(const char *device_name, char *params)
++{
++ int res = 0;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ res = vdev_create(&vdisk_file_devtype, device_name, &virt_dev);
++ if (res != 0)
++ goto out;
++
++ virt_dev->command_set_version = 0x04C0; /* SBC-3 */
++
++ virt_dev->wt_flag = DEF_WRITE_THROUGH;
++ virt_dev->nv_cache = DEF_NV_CACHE;
++ virt_dev->o_direct_flag = DEF_O_DIRECT;
++
++ res = vdev_parse_add_dev_params(virt_dev, params, NULL);
++ if (res != 0)
++ goto out_destroy;
++
++ if (virt_dev->rd_only && (virt_dev->wt_flag || virt_dev->nv_cache)) {
++ PRINT_ERROR("Write options on read only device %s",
++ virt_dev->name);
++ res = -EINVAL;
++ goto out_destroy;
++ }
++
++ if (virt_dev->filename == NULL) {
++ PRINT_ERROR("File name required (device %s)", virt_dev->name);
++ res = -EINVAL;
++ goto out_destroy;
++ }
++
++ list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
++
++ vdisk_report_registering(virt_dev);
++
++ virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
++ virt_dev->name);
++ if (virt_dev->virt_id < 0) {
++ res = virt_dev->virt_id;
++ goto out_del;
++ }
++
++ TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
++ virt_dev->virt_id);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ list_del(&virt_dev->vdev_list_entry);
++
++out_destroy:
++ vdev_destroy(virt_dev);
++ goto out;
++}
++
++/* scst_vdisk_mutex supposed to be held */
++static int vdev_blockio_add_device(const char *device_name, char *params)
++{
++ int res = 0;
++ const char *allowed_params[] = { "filename", "read_only", "removable",
++ "blocksize", "nv_cache",
++ "thin_provisioned", NULL };
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ res = vdev_create(&vdisk_blk_devtype, device_name, &virt_dev);
++ if (res != 0)
++ goto out;
++
++ virt_dev->command_set_version = 0x04C0; /* SBC-3 */
++
++ virt_dev->blockio = 1;
++
++ res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
++ if (res != 0)
++ goto out_destroy;
++
++ if (virt_dev->filename == NULL) {
++ PRINT_ERROR("File name required (device %s)", virt_dev->name);
++ res = -EINVAL;
++ goto out_destroy;
++ }
++
++ list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
++
++ vdisk_report_registering(virt_dev);
++
++ virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
++ virt_dev->name);
++ if (virt_dev->virt_id < 0) {
++ res = virt_dev->virt_id;
++ goto out_del;
++ }
++
++ TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
++ virt_dev->virt_id);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ list_del(&virt_dev->vdev_list_entry);
++
++out_destroy:
++ vdev_destroy(virt_dev);
++ goto out;
++}
++
++/* scst_vdisk_mutex supposed to be held */
++static int vdev_nullio_add_device(const char *device_name, char *params)
++{
++ int res = 0;
++ const char *allowed_params[] = { "read_only", "removable",
++ "blocksize", NULL };
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ res = vdev_create(&vdisk_null_devtype, device_name, &virt_dev);
++ if (res != 0)
++ goto out;
++
++ virt_dev->command_set_version = 0x04C0; /* SBC-3 */
++
++ virt_dev->nullio = 1;
++
++ res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
++ if (res != 0)
++ goto out_destroy;
++
++ list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
++
++ vdisk_report_registering(virt_dev);
++
++ virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
++ virt_dev->name);
++ if (virt_dev->virt_id < 0) {
++ res = virt_dev->virt_id;
++ goto out_del;
++ }
++
++ TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
++ virt_dev->virt_id);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ list_del(&virt_dev->vdev_list_entry);
++
++out_destroy:
++ vdev_destroy(virt_dev);
++ goto out;
++}
++
++static ssize_t vdisk_add_fileio_device(const char *device_name, char *params)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ res = vdev_fileio_add_device(device_name, params);
++
++ mutex_unlock(&scst_vdisk_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t vdisk_add_blockio_device(const char *device_name, char *params)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ res = vdev_blockio_add_device(device_name, params);
++
++ mutex_unlock(&scst_vdisk_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++}
++
++static ssize_t vdisk_add_nullio_device(const char *device_name, char *params)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ res = vdev_nullio_add_device(device_name, params);
++
++ mutex_unlock(&scst_vdisk_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++}
++
++/* scst_vdisk_mutex supposed to be held */
++static void vdev_del_device(struct scst_vdisk_dev *virt_dev)
++{
++ TRACE_ENTRY();
++
++ scst_unregister_virtual_device(virt_dev->virt_id);
++
++ list_del(&virt_dev->vdev_list_entry);
++
++ PRINT_INFO("Virtual device %s unregistered", virt_dev->name);
++ TRACE_DBG("virt_id %d unregistered", virt_dev->virt_id);
++
++ vdev_destroy(virt_dev);
++
++ return;
++}
++
++static ssize_t vdisk_del_device(const char *device_name)
++{
++ int res = 0;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ virt_dev = vdev_find(device_name);
++ if (virt_dev == NULL) {
++ PRINT_ERROR("Device %s not found", device_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ vdev_del_device(virt_dev);
++
++out_unlock:
++ mutex_unlock(&scst_vdisk_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* scst_vdisk_mutex supposed to be held */
++static ssize_t __vcdrom_add_device(const char *device_name, char *params)
++{
++ int res = 0;
++ const char *allowed_params[] = { NULL }; /* no params */
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ res = vdev_create(&vcdrom_devtype, device_name, &virt_dev);
++ if (res != 0)
++ goto out;
++
++#if 0 /*
++ * Our implementation is pretty minimalistic and doesn't support all
++ * mandatory commands, so it's better to not claim any standard
++ * confirmance.
++ */
++ virt_dev->command_set_version = 0x02A0; /* MMC-3 */
++#endif
++
++ virt_dev->rd_only = 1;
++ virt_dev->removable = 1;
++ virt_dev->cdrom_empty = 1;
++
++ virt_dev->block_size = DEF_CDROM_BLOCKSIZE;
++ virt_dev->block_shift = DEF_CDROM_BLOCKSIZE_SHIFT;
++
++ res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
++ if (res != 0)
++ goto out_destroy;
++
++ list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
++
++ vdisk_report_registering(virt_dev);
++
++ virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
++ virt_dev->name);
++ if (virt_dev->virt_id < 0) {
++ res = virt_dev->virt_id;
++ goto out_del;
++ }
++
++ TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
++ virt_dev->virt_id);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ list_del(&virt_dev->vdev_list_entry);
++
++out_destroy:
++ vdev_destroy(virt_dev);
++ goto out;
++}
++
++static ssize_t vcdrom_add_device(const char *device_name, char *params)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ res = __vcdrom_add_device(device_name, params);
++
++ mutex_unlock(&scst_vdisk_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++}
++
++static ssize_t vcdrom_del_device(const char *device_name)
++{
++ int res = 0;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ virt_dev = vdev_find(device_name);
++ if (virt_dev == NULL) {
++ PRINT_ERROR("Device %s not found", device_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ vdev_del_device(virt_dev);
++
++out_unlock:
++ mutex_unlock(&scst_vdisk_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int vcdrom_change(struct scst_vdisk_dev *virt_dev,
++ char *buffer)
++{
++ loff_t err;
++ char *old_fn, *p, *pp;
++ const char *filename = NULL;
++ int length = strlen(buffer);
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ p = buffer;
++
++ while (isspace(*p) && *p != '\0')
++ p++;
++ filename = p;
++ p = &buffer[length-1];
++ pp = &buffer[length];
++ while (isspace(*p) && (*p != '\0')) {
++ pp = p;
++ p--;
++ }
++ *pp = '\0';
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out;
++
++ /* To sync with detach*() functions */
++ mutex_lock(&scst_mutex);
++
++ if (*filename == '\0') {
++ virt_dev->cdrom_empty = 1;
++ TRACE_DBG("%s", "No media");
++ } else if (*filename != '/') {
++ PRINT_ERROR("File path \"%s\" is not absolute", filename);
++ res = -EINVAL;
++ goto out_unlock;
++ } else
++ virt_dev->cdrom_empty = 0;
++
++ old_fn = virt_dev->filename;
++
++ if (!virt_dev->cdrom_empty) {
++ char *fn = kstrdup(filename, GFP_KERNEL);
++ if (fn == NULL) {
++ PRINT_ERROR("%s", "Allocation of filename failed");
++ res = -ENOMEM;
++ goto out_unlock;
++ }
++
++ virt_dev->filename = fn;
++
++ res = vdisk_get_file_size(virt_dev->filename,
++ virt_dev->blockio, &err);
++ if (res != 0)
++ goto out_free_fn;
++ } else {
++ err = 0;
++ virt_dev->filename = NULL;
++ }
++
++ if (virt_dev->prevent_allow_medium_removal) {
++ PRINT_ERROR("Prevent medium removal for "
++ "virtual device with name %s", virt_dev->name);
++ res = -EINVAL;
++ goto out_free_fn;
++ }
++
++ virt_dev->file_size = err;
++ virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift;
++ if (!virt_dev->cdrom_empty)
++ virt_dev->media_changed = 1;
++
++ mutex_unlock(&scst_mutex);
++
++ scst_dev_del_all_thr_data(virt_dev->dev);
++
++ if (!virt_dev->cdrom_empty) {
++ PRINT_INFO("Changed SCSI target virtual cdrom %s "
++ "(file=\"%s\", fs=%lldMB, bs=%d, nblocks=%lld,"
++ " cyln=%lld%s)", virt_dev->name,
++ vdev_get_filename(virt_dev),
++ virt_dev->file_size >> 20, virt_dev->block_size,
++ (long long unsigned int)virt_dev->nblocks,
++ (long long unsigned int)virt_dev->nblocks/64/32,
++ virt_dev->nblocks < 64*32 ? " !WARNING! cyln less "
++ "than 1" : "");
++ } else {
++ PRINT_INFO("Removed media from SCSI target virtual cdrom %s",
++ virt_dev->name);
++ }
++
++ kfree(old_fn);
++
++out_resume:
++ scst_resume_activity();
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free_fn:
++ kfree(virt_dev->filename);
++ virt_dev->filename = old_fn;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++ goto out_resume;
++}
++
++static int vcdrom_sysfs_process_filename_store(struct scst_sysfs_work_item *work)
++{
++ int res;
++ struct scst_device *dev = work->dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ /* It's safe, since we taken dev_kobj and dh_priv NULLed in attach() */
++ virt_dev = dev->dh_priv;
++
++ res = vcdrom_change(virt_dev, work->buf);
++
++ kobject_put(&dev->dev_kobj);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t vcdrom_sysfs_filename_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ char *i_buf;
++ struct scst_sysfs_work_item *work;
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ i_buf = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf);
++ if (i_buf == NULL) {
++ PRINT_ERROR("Unable to alloc intermediate buffer with size %zd",
++ count+1);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ res = scst_alloc_sysfs_work(vcdrom_sysfs_process_filename_store,
++ false, &work);
++ if (res != 0)
++ goto out_free;
++
++ work->buf = i_buf;
++ work->dev = dev;
++
++ kobject_get(&dev->dev_kobj);
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(i_buf);
++ goto out;
++}
++
++static ssize_t vdev_sysfs_size_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ pos = sprintf(buf, "%lld\n", virt_dev->file_size / 1024 / 1024);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t vdisk_sysfs_blocksize_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ pos = sprintf(buf, "%d\n%s", (int)virt_dev->block_size,
++ (virt_dev->block_size == DEF_DISK_BLOCKSIZE) ? "" :
++ SCST_SYSFS_KEY_MARK "\n");
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t vdisk_sysfs_rd_only_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ pos = sprintf(buf, "%d\n%s", virt_dev->rd_only ? 1 : 0,
++ (virt_dev->rd_only == DEF_RD_ONLY) ? "" :
++ SCST_SYSFS_KEY_MARK "\n");
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t vdisk_sysfs_wt_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ pos = sprintf(buf, "%d\n%s", virt_dev->wt_flag ? 1 : 0,
++ (virt_dev->wt_flag == DEF_WRITE_THROUGH) ? "" :
++ SCST_SYSFS_KEY_MARK "\n");
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t vdisk_sysfs_tp_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ pos = sprintf(buf, "%d\n%s", virt_dev->thin_provisioned ? 1 : 0,
++ (virt_dev->thin_provisioned == DEF_THIN_PROVISIONED) ? "" :
++ SCST_SYSFS_KEY_MARK "\n");
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ pos = sprintf(buf, "%d\n%s", virt_dev->nv_cache ? 1 : 0,
++ (virt_dev->nv_cache == DEF_NV_CACHE) ? "" :
++ SCST_SYSFS_KEY_MARK "\n");
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ pos = sprintf(buf, "%d\n%s", virt_dev->o_direct_flag ? 1 : 0,
++ (virt_dev->o_direct_flag == DEF_O_DIRECT) ? "" :
++ SCST_SYSFS_KEY_MARK "\n");
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t vdisk_sysfs_removable_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ pos = sprintf(buf, "%d\n", virt_dev->removable ? 1 : 0);
++
++ if ((virt_dev->dev->type != TYPE_ROM) &&
++ (virt_dev->removable != DEF_REMOVABLE))
++ pos += sprintf(&buf[pos], "%s\n", SCST_SYSFS_KEY_MARK);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static int vdev_sysfs_process_get_filename(struct scst_sysfs_work_item *work)
++{
++ int res = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = work->dev;
++
++ /*
++ * Since we have a get() on dev->dev_kobj, we can not simply mutex_lock
++ * scst_vdisk_mutex, because otherwise we can fall in a deadlock with
++ * vdisk_del_device(), which is waiting for the last ref to dev_kobj
++ * under scst_vdisk_mutex.
++ */
++ while (!mutex_trylock(&scst_vdisk_mutex)) {
++ if ((volatile bool)(dev->dev_unregistering)) {
++ TRACE_MGMT_DBG("Skipping being unregistered dev %s",
++ dev->virt_name);
++ res = -ENOENT;
++ goto out_put;
++ }
++ if (signal_pending(current)) {
++ res = -EINTR;
++ goto out_put;
++ }
++ msleep(100);
++ }
++
++ virt_dev = dev->dh_priv;
++
++ if (virt_dev == NULL)
++ goto out_unlock;
++
++ if (virt_dev->filename != NULL)
++ work->res_buf = kasprintf(GFP_KERNEL, "%s\n%s\n",
++ vdev_get_filename(virt_dev), SCST_SYSFS_KEY_MARK);
++ else
++ work->res_buf = kasprintf(GFP_KERNEL, "%s\n",
++ vdev_get_filename(virt_dev));
++
++out_unlock:
++ mutex_unlock(&scst_vdisk_mutex);
++
++out_put:
++ kobject_put(&dev->dev_kobj);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t vdev_sysfs_filename_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int res = 0;
++ struct scst_device *dev;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ res = scst_alloc_sysfs_work(vdev_sysfs_process_get_filename,
++ true, &work);
++ if (res != 0)
++ goto out;
++
++ work->dev = dev;
++
++ kobject_get(&dev->dev_kobj);
++
++ scst_sysfs_work_get(work);
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res != 0)
++ goto out_put;
++
++ res = snprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n", work->res_buf);
++
++out_put:
++ scst_sysfs_work_put(work);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int vdisk_sysfs_process_resync_size_store(
++ struct scst_sysfs_work_item *work)
++{
++ int res;
++ struct scst_device *dev = work->dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ /* It's safe, since we taken dev_kobj and dh_priv NULLed in attach() */
++ virt_dev = dev->dh_priv;
++
++ res = vdisk_resync_size(virt_dev);
++
++ kobject_put(&dev->dev_kobj);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t vdisk_sysfs_resync_size_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_device *dev;
++ struct scst_sysfs_work_item *work;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++
++ res = scst_alloc_sysfs_work(vdisk_sysfs_process_resync_size_store,
++ false, &work);
++ if (res != 0)
++ goto out;
++
++ work->dev = dev;
++
++ kobject_get(&dev->dev_kobj);
++
++ res = scst_sysfs_queue_wait_work(work);
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t vdev_sysfs_t10_dev_id_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res, i;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ write_lock(&vdisk_serial_rwlock);
++
++ if ((count > sizeof(virt_dev->t10_dev_id)) ||
++ ((count == sizeof(virt_dev->t10_dev_id)) &&
++ (buf[count-1] != '\n'))) {
++ PRINT_ERROR("T10 device id is too long (max %zd "
++ "characters)", sizeof(virt_dev->t10_dev_id)-1);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ memset(virt_dev->t10_dev_id, 0, sizeof(virt_dev->t10_dev_id));
++ memcpy(virt_dev->t10_dev_id, buf, count);
++
++ i = 0;
++ while (i < sizeof(virt_dev->t10_dev_id)) {
++ if (virt_dev->t10_dev_id[i] == '\n') {
++ virt_dev->t10_dev_id[i] = '\0';
++ break;
++ }
++ i++;
++ }
++
++ virt_dev->t10_dev_id_set = 1;
++
++ res = count;
++
++ PRINT_INFO("T10 device id for device %s changed to %s", virt_dev->name,
++ virt_dev->t10_dev_id);
++
++out_unlock:
++ write_unlock(&vdisk_serial_rwlock);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t vdev_sysfs_t10_dev_id_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ read_lock(&vdisk_serial_rwlock);
++ pos = sprintf(buf, "%s\n%s", virt_dev->t10_dev_id,
++ virt_dev->t10_dev_id_set ? SCST_SYSFS_KEY_MARK "\n" : "");
++ read_unlock(&vdisk_serial_rwlock);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t vdev_sysfs_usn_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res, i;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ write_lock(&vdisk_serial_rwlock);
++
++ if ((count > sizeof(virt_dev->usn)) ||
++ ((count == sizeof(virt_dev->usn)) &&
++ (buf[count-1] != '\n'))) {
++ PRINT_ERROR("USN is too long (max %zd "
++ "characters)", sizeof(virt_dev->usn)-1);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ memset(virt_dev->usn, 0, sizeof(virt_dev->usn));
++ memcpy(virt_dev->usn, buf, count);
++
++ i = 0;
++ while (i < sizeof(virt_dev->usn)) {
++ if (virt_dev->usn[i] == '\n') {
++ virt_dev->usn[i] = '\0';
++ break;
++ }
++ i++;
++ }
++
++ virt_dev->usn_set = 1;
++
++ res = count;
++
++ PRINT_INFO("USN for device %s changed to %s", virt_dev->name,
++ virt_dev->usn);
++
++out_unlock:
++ write_unlock(&vdisk_serial_rwlock);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t vdev_sysfs_usn_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos = 0;
++ struct scst_device *dev;
++ struct scst_vdisk_dev *virt_dev;
++
++ TRACE_ENTRY();
++
++ dev = container_of(kobj, struct scst_device, dev_kobj);
++ virt_dev = dev->dh_priv;
++
++ read_lock(&vdisk_serial_rwlock);
++ pos = sprintf(buf, "%s\n%s", virt_dev->usn,
++ virt_dev->usn_set ? SCST_SYSFS_KEY_MARK "\n" : "");
++ read_unlock(&vdisk_serial_rwlock);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static int __init init_scst_vdisk(struct scst_dev_type *devtype)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ devtype->module = THIS_MODULE;
++
++ res = scst_register_virtual_dev_driver(devtype);
++ if (res < 0)
++ goto out;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++}
++
++static void exit_scst_vdisk(struct scst_dev_type *devtype)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_vdisk_mutex);
++ while (1) {
++ struct scst_vdisk_dev *virt_dev;
++
++ if (list_empty(&vdev_list))
++ break;
++
++ virt_dev = list_entry(vdev_list.next, typeof(*virt_dev),
++ vdev_list_entry);
++
++ vdev_del_device(virt_dev);
++ }
++ mutex_unlock(&scst_vdisk_mutex);
++
++ scst_unregister_virtual_dev_driver(devtype);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int __init init_scst_vdisk_driver(void)
++{
++ int res;
++
++ vdisk_thr_cachep = KMEM_CACHE(scst_vdisk_thr, SCST_SLAB_FLAGS);
++ if (vdisk_thr_cachep == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ blockio_work_cachep = KMEM_CACHE(scst_blockio_work, SCST_SLAB_FLAGS);
++ if (blockio_work_cachep == NULL) {
++ res = -ENOMEM;
++ goto out_free_vdisk_cache;
++ }
++
++ if (num_threads < 1) {
++ PRINT_ERROR("num_threads can not be less than 1, use "
++ "default %d", DEF_NUM_THREADS);
++ num_threads = DEF_NUM_THREADS;
++ }
++
++ vdisk_file_devtype.threads_num = num_threads;
++ vcdrom_devtype.threads_num = num_threads;
++
++ atomic_set(&nullio_thr_data.hdr.ref, 1); /* never destroy it */
++
++ res = init_scst_vdisk(&vdisk_file_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(&vdisk_null_devtype);
++ if (res != 0)
++ goto out_free_blk;
++
++ res = init_scst_vdisk(&vcdrom_devtype);
++ if (res != 0)
++ goto out_free_null;
++
++out:
++ return res;
++
++out_free_null:
++ exit_scst_vdisk(&vdisk_null_devtype);
++
++out_free_blk:
++ exit_scst_vdisk(&vdisk_blk_devtype);
++
++out_free_vdisk:
++ exit_scst_vdisk(&vdisk_file_devtype);
++
++out_free_slab:
++ kmem_cache_destroy(blockio_work_cachep);
++
++out_free_vdisk_cache:
++ kmem_cache_destroy(vdisk_thr_cachep);
++ goto out;
++}
++
++static void __exit exit_scst_vdisk_driver(void)
++{
++ exit_scst_vdisk(&vdisk_null_devtype);
++ exit_scst_vdisk(&vdisk_blk_devtype);
++ exit_scst_vdisk(&vdisk_file_devtype);
++ exit_scst_vdisk(&vcdrom_devtype);
++
++ kmem_cache_destroy(blockio_work_cachep);
++ kmem_cache_destroy(vdisk_thr_cachep);
++}
++
++module_init(init_scst_vdisk_driver);
++module_exit(exit_scst_vdisk_driver);
++
++MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCSI disk (type 0) and CDROM (type 5) dev handler for "
++ "SCST using files on file systems or block devices");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/scst_tg.c linux-3.2/drivers/scst/scst_tg.c
+--- orig/linux-3.2/drivers/scst/scst_tg.c
++++ linux-3.2/drivers/scst/scst_tg.c
+@@ -0,0 +1,809 @@
++/*
++ * scst_tg.c
++ *
++ * SCSI target group related code.
++ *
++ * Copyright (C) 2011 Bart Van Assche <bvanassche@acm.org>.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * version 2 as published by the Free Software Foundation.
++ *
++ * 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 <asm/unaligned.h>
++#include <scst/scst.h>
++#include "scst_priv.h"
++
++static struct list_head scst_dev_group_list;
++
++/* Look up a device by name. */
++static struct scst_device *__lookup_dev(const char *name)
++{
++ struct scst_device *dev;
++
++ list_for_each_entry(dev, &scst_dev_list, dev_list_entry)
++ if (strcmp(dev->virt_name, name) == 0)
++ return dev;
++
++ return NULL;
++}
++
++/* Look up a target by name. */
++static struct scst_tgt *__lookup_tgt(const char *name)
++{
++ struct scst_tgt_template *t;
++ struct scst_tgt *tgt;
++
++ list_for_each_entry(t, &scst_template_list, scst_template_list_entry)
++ list_for_each_entry(tgt, &t->tgt_list, tgt_list_entry)
++ if (strcmp(tgt->tgt_name, name) == 0)
++ return tgt;
++
++ return NULL;
++}
++
++/* Look up a target by name in the given device group. */
++static struct scst_tg_tgt *__lookup_dg_tgt(struct scst_dev_group *dg,
++ const char *tgt_name)
++{
++ struct scst_target_group *tg;
++ struct scst_tg_tgt *tg_tgt;
++
++ BUG_ON(!dg);
++ BUG_ON(!tgt_name);
++ list_for_each_entry(tg, &dg->tg_list, entry)
++ list_for_each_entry(tg_tgt, &tg->tgt_list, entry)
++ if (strcmp(tg_tgt->name, tgt_name) == 0)
++ return tg_tgt;
++
++ return NULL;
++}
++
++/* Look up a target group by name in the given device group. */
++static struct scst_target_group *
++__lookup_tg_by_name(struct scst_dev_group *dg, const char *name)
++{
++ struct scst_target_group *tg;
++
++ list_for_each_entry(tg, &dg->tg_list, entry)
++ if (strcmp(tg->name, name) == 0)
++ return tg;
++
++ return NULL;
++}
++
++/* Look up a device node by device pointer in the given device group. */
++static struct scst_dg_dev *__lookup_dg_dev_by_dev(struct scst_dev_group *dg,
++ struct scst_device *dev)
++{
++ struct scst_dg_dev *dgd;
++
++ list_for_each_entry(dgd, &dg->dev_list, entry)
++ if (dgd->dev == dev)
++ return dgd;
++
++ return NULL;
++}
++
++/* Look up a device node by name in the given device group. */
++static struct scst_dg_dev *__lookup_dg_dev_by_name(struct scst_dev_group *dg,
++ const char *name)
++{
++ struct scst_dg_dev *dgd;
++
++ list_for_each_entry(dgd, &dg->dev_list, entry)
++ if (strcmp(dgd->dev->virt_name, name) == 0)
++ return dgd;
++
++ return NULL;
++}
++
++/* Look up a device node by name in any device group. */
++static struct scst_dg_dev *__global_lookup_dg_dev_by_name(const char *name)
++{
++ struct scst_dev_group *dg;
++ struct scst_dg_dev *dgd;
++
++ list_for_each_entry(dg, &scst_dev_group_list, entry) {
++ dgd = __lookup_dg_dev_by_name(dg, name);
++ if (dgd)
++ return dgd;
++ }
++ return NULL;
++}
++
++/* Look up a device group by name. */
++static struct scst_dev_group *__lookup_dg_by_name(const char *name)
++{
++ struct scst_dev_group *dg;
++
++ list_for_each_entry(dg, &scst_dev_group_list, entry)
++ if (strcmp(dg->name, name) == 0)
++ return dg;
++
++ return NULL;
++}
++
++/* Look up a device group by device pointer. */
++static struct scst_dev_group *__lookup_dg_by_dev(struct scst_device *dev)
++{
++ struct scst_dev_group *dg;
++
++ list_for_each_entry(dg, &scst_dev_group_list, entry)
++ if (__lookup_dg_dev_by_dev(dg, dev))
++ return dg;
++
++ return NULL;
++}
++
++/*
++ * Target group contents management.
++ */
++
++static void scst_release_tg_tgt(struct kobject *kobj)
++{
++ struct scst_tg_tgt *tg_tgt;
++
++ tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj);
++ kfree(tg_tgt->name);
++ kfree(tg_tgt);
++}
++
++static struct kobj_type scst_tg_tgt_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_release_tg_tgt,
++};
++
++/**
++ * scst_tg_tgt_add() - Add a target to a target group.
++ */
++int scst_tg_tgt_add(struct scst_target_group *tg, const char *name)
++{
++ struct scst_tg_tgt *tg_tgt;
++ struct scst_tgt *tgt;
++ int res;
++
++ TRACE_ENTRY();
++ BUG_ON(!tg);
++ BUG_ON(!name);
++ res = -ENOMEM;
++ tg_tgt = kzalloc(sizeof *tg_tgt, GFP_KERNEL);
++ if (!tg_tgt)
++ goto out;
++ tg_tgt->tg = tg;
++ kobject_init(&tg_tgt->kobj, &scst_tg_tgt_ktype);
++ tg_tgt->name = kstrdup(name, GFP_KERNEL);
++ if (!tg_tgt->name)
++ goto out_put;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out_put;
++ res = -EEXIST;
++ tgt = __lookup_tgt(name);
++ if (__lookup_dg_tgt(tg->dg, name))
++ goto out_unlock;
++ tg_tgt->tgt = tgt;
++ res = scst_tg_tgt_sysfs_add(tg, tg_tgt);
++ if (res)
++ goto out_unlock;
++ list_add_tail(&tg_tgt->entry, &tg->tgt_list);
++ res = 0;
++ mutex_unlock(&scst_mutex);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out_put:
++ kobject_put(&tg_tgt->kobj);
++ goto out;
++}
++
++static void __scst_tg_tgt_remove(struct scst_target_group *tg,
++ struct scst_tg_tgt *tg_tgt)
++{
++ TRACE_ENTRY();
++ list_del(&tg_tgt->entry);
++ scst_tg_tgt_sysfs_del(tg, tg_tgt);
++ kobject_put(&tg_tgt->kobj);
++ TRACE_EXIT();
++}
++
++/**
++ * scst_tg_tgt_remove_by_name() - Remove a target from a target group.
++ */
++int scst_tg_tgt_remove_by_name(struct scst_target_group *tg, const char *name)
++{
++ struct scst_tg_tgt *tg_tgt;
++ int res;
++
++ TRACE_ENTRY();
++ BUG_ON(!tg);
++ BUG_ON(!name);
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out;
++ res = -EINVAL;
++ tg_tgt = __lookup_dg_tgt(tg->dg, name);
++ if (!tg_tgt)
++ goto out_unlock;
++ __scst_tg_tgt_remove(tg, tg_tgt);
++ res = 0;
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Caller must hold scst_mutex. Called from the target removal code. */
++void scst_tg_tgt_remove_by_tgt(struct scst_tgt *tgt)
++{
++ struct scst_dev_group *dg;
++ struct scst_target_group *tg;
++ struct scst_tg_tgt *t, *t2;
++
++ BUG_ON(!tgt);
++ list_for_each_entry(dg, &scst_dev_group_list, entry)
++ list_for_each_entry(tg, &dg->tg_list, entry)
++ list_for_each_entry_safe(t, t2, &tg->tgt_list, entry)
++ if (t->tgt == tgt)
++ __scst_tg_tgt_remove(tg, t);
++}
++
++/*
++ * Target group management.
++ */
++
++static void scst_release_tg(struct kobject *kobj)
++{
++ struct scst_target_group *tg;
++
++ tg = container_of(kobj, struct scst_target_group, kobj);
++ kfree(tg->name);
++ kfree(tg);
++}
++
++static struct kobj_type scst_tg_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_release_tg,
++};
++
++/**
++ * scst_tg_add() - Add a target group.
++ */
++int scst_tg_add(struct scst_dev_group *dg, const char *name)
++{
++ struct scst_target_group *tg;
++ int res;
++
++ TRACE_ENTRY();
++ res = -ENOMEM;
++ tg = kzalloc(sizeof *tg, GFP_KERNEL);
++ if (!tg)
++ goto out;
++ kobject_init(&tg->kobj, &scst_tg_ktype);
++ tg->name = kstrdup(name, GFP_KERNEL);
++ if (!tg->name)
++ goto out_put;
++ tg->dg = dg;
++ tg->state = SCST_TG_STATE_OPTIMIZED;
++ INIT_LIST_HEAD(&tg->tgt_list);
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out_put;
++ res = -EEXIST;
++ if (__lookup_tg_by_name(dg, name))
++ goto out_unlock;
++ res = scst_tg_sysfs_add(dg, tg);
++ if (res)
++ goto out_unlock;
++ list_add_tail(&tg->entry, &dg->tg_list);
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out_put:
++ kobject_put(&tg->kobj);
++ goto out;
++}
++
++static void __scst_tg_remove(struct scst_dev_group *dg,
++ struct scst_target_group *tg)
++{
++ struct scst_tg_tgt *tg_tgt;
++
++ TRACE_ENTRY();
++ BUG_ON(!dg);
++ BUG_ON(!tg);
++ while (!list_empty(&tg->tgt_list)) {
++ tg_tgt = list_first_entry(&tg->tgt_list, struct scst_tg_tgt,
++ entry);
++ __scst_tg_tgt_remove(tg, tg_tgt);
++ }
++ list_del(&tg->entry);
++ scst_tg_sysfs_del(tg);
++ kobject_put(&tg->kobj);
++ TRACE_EXIT();
++}
++
++/**
++ * scst_tg_remove_by_name() - Remove a target group.
++ */
++int scst_tg_remove_by_name(struct scst_dev_group *dg, const char *name)
++{
++ struct scst_target_group *tg;
++ int res;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out;
++ res = -EINVAL;
++ tg = __lookup_tg_by_name(dg, name);
++ if (!tg)
++ goto out_unlock;
++ __scst_tg_remove(dg, tg);
++ res = 0;
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out:
++ return res;
++}
++
++int scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state)
++{
++ struct scst_dg_dev *dg_dev;
++ struct scst_device *dev;
++ struct scst_tgt_dev *tgt_dev;
++ int res;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out;
++
++ tg->state = state;
++
++ list_for_each_entry(dg_dev, &tg->dg->dev_list, entry) {
++ dev = dg_dev->dev;
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed",
++ tgt_dev);
++ scst_gen_aen_or_ua(tgt_dev,
++ SCST_LOAD_SENSE(scst_sense_asym_access_state_changed));
++ }
++ }
++ mutex_unlock(&scst_mutex);
++out:
++ return res;
++}
++
++/*
++ * Device group contents manipulation.
++ */
++
++/**
++ * scst_dg_dev_add() - Add a device to a device group.
++ *
++ * It is verified whether 'name' refers to an existing device and whether that
++ * device has not yet been added to any other device group.
++ */
++int scst_dg_dev_add(struct scst_dev_group *dg, const char *name)
++{
++ struct scst_dg_dev *dgdev;
++ struct scst_device *dev;
++ int res;
++
++ res = -ENOMEM;
++ dgdev = kzalloc(sizeof *dgdev, GFP_KERNEL);
++ if (!dgdev)
++ goto out;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out_free;
++ res = -EEXIST;
++ if (__global_lookup_dg_dev_by_name(name))
++ goto out_unlock;
++ res = -EINVAL;
++ dev = __lookup_dev(name);
++ if (!dev)
++ goto out_unlock;
++ dgdev->dev = dev;
++ res = scst_dg_dev_sysfs_add(dg, dgdev);
++ if (res)
++ goto out_unlock;
++ list_add_tail(&dgdev->entry, &dg->dev_list);
++ mutex_unlock(&scst_mutex);
++
++out:
++ return res;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out_free:
++ kfree(dgdev);
++ goto out;
++}
++
++static void __scst_dg_dev_remove(struct scst_dev_group *dg,
++ struct scst_dg_dev *dgdev)
++{
++ list_del(&dgdev->entry);
++ scst_dg_dev_sysfs_del(dg, dgdev);
++ kfree(dgdev);
++}
++
++/**
++ * scst_dg_dev_remove_by_name() - Remove a device from a device group.
++ */
++int scst_dg_dev_remove_by_name(struct scst_dev_group *dg, const char *name)
++{
++ struct scst_dg_dev *dgdev;
++ int res;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out;
++ res = -EINVAL;
++ dgdev = __lookup_dg_dev_by_name(dg, name);
++ if (!dgdev)
++ goto out_unlock;
++ __scst_dg_dev_remove(dg, dgdev);
++ res = 0;
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out:
++ return res;
++}
++
++/* Caller must hold scst_mutex. Called from the device removal code. */
++int scst_dg_dev_remove_by_dev(struct scst_device *dev)
++{
++ struct scst_dev_group *dg;
++ struct scst_dg_dev *dgdev;
++ int res;
++
++ res = -EINVAL;
++ dg = __lookup_dg_by_dev(dev);
++ if (!dg)
++ goto out;
++ dgdev = __lookup_dg_dev_by_dev(dg, dev);
++ BUG_ON(!dgdev);
++ __scst_dg_dev_remove(dg, dgdev);
++ res = 0;
++out:
++ return res;
++}
++
++/*
++ * Device group management.
++ */
++
++static void scst_release_dg(struct kobject *kobj)
++{
++ struct scst_dev_group *dg;
++
++ dg = container_of(kobj, struct scst_dev_group, kobj);
++ kfree(dg->name);
++ kfree(dg);
++}
++
++static struct kobj_type scst_dg_ktype = {
++ .sysfs_ops = &scst_sysfs_ops,
++ .release = scst_release_dg,
++};
++
++/**
++ * scst_dg_add() - Add a new device group object and make it visible in sysfs.
++ */
++int scst_dg_add(struct kobject *parent, const char *name)
++{
++ struct scst_dev_group *dg;
++ int res;
++
++ TRACE_ENTRY();
++
++ res = -ENOMEM;
++ dg = kzalloc(sizeof(*dg), GFP_KERNEL);
++ if (!dg)
++ goto out;
++ kobject_init(&dg->kobj, &scst_dg_ktype);
++ dg->name = kstrdup(name, GFP_KERNEL);
++ if (!dg->name)
++ goto out_put;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out_put;
++ res = -EEXIST;
++ if (__lookup_dg_by_name(name))
++ goto out_unlock;
++ res = -ENOMEM;
++ INIT_LIST_HEAD(&dg->dev_list);
++ INIT_LIST_HEAD(&dg->tg_list);
++ res = scst_dg_sysfs_add(parent, dg);
++ if (res)
++ goto out_unlock;
++ list_add_tail(&dg->entry, &scst_dev_group_list);
++ mutex_unlock(&scst_mutex);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out_put:
++ kobject_put(&dg->kobj);
++ goto out;
++}
++
++static void __scst_dg_remove(struct scst_dev_group *dg)
++{
++ struct scst_dg_dev *dgdev;
++ struct scst_target_group *tg;
++
++ list_del(&dg->entry);
++ scst_dg_sysfs_del(dg);
++ while (!list_empty(&dg->dev_list)) {
++ dgdev = list_first_entry(&dg->dev_list, struct scst_dg_dev,
++ entry);
++ __scst_dg_dev_remove(dg, dgdev);
++ }
++ while (!list_empty(&dg->tg_list)) {
++ tg = list_first_entry(&dg->tg_list, struct scst_target_group,
++ entry);
++ __scst_tg_remove(dg, tg);
++ }
++ kobject_put(&dg->kobj);
++}
++
++int scst_dg_remove(const char *name)
++{
++ struct scst_dev_group *dg;
++ int res;
++
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out;
++ res = -EINVAL;
++ dg = __lookup_dg_by_name(name);
++ if (!dg)
++ goto out_unlock;
++ __scst_dg_remove(dg);
++ res = 0;
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out:
++ return res;
++}
++
++/*
++ * Given a pointer to a device_groups/<dg>/devices or
++ * device_groups/<dg>/target_groups kobject, return the pointer to the
++ * corresponding device group.
++ *
++ * Note: The caller must hold a reference on the kobject to avoid that the
++ * object disappears before the caller stops using the device group pointer.
++ */
++struct scst_dev_group *scst_lookup_dg_by_kobj(struct kobject *kobj)
++{
++ int res;
++ struct scst_dev_group *dg;
++
++ dg = NULL;
++ res = mutex_lock_interruptible(&scst_mutex);
++ if (res)
++ goto out;
++ list_for_each_entry(dg, &scst_dev_group_list, entry)
++ if (dg->dev_kobj == kobj || dg->tg_kobj == kobj)
++ goto out_unlock;
++ dg = NULL;
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out:
++ return dg;
++}
++
++/*
++ * Target group module management.
++ */
++
++void scst_tg_init(void)
++{
++ INIT_LIST_HEAD(&scst_dev_group_list);
++}
++
++void scst_tg_cleanup(void)
++{
++ struct scst_dev_group *tg;
++
++ mutex_lock(&scst_mutex);
++ while (!list_empty(&scst_dev_group_list)) {
++ tg = list_first_entry(&scst_dev_group_list,
++ struct scst_dev_group, entry);
++ __scst_dg_remove(tg);
++ }
++ mutex_unlock(&scst_mutex);
++}
++
++/*
++ * Functions for target group related SCSI command support.
++ */
++
++/**
++ * scst_lookup_tg_id() - Look up a target port group identifier.
++ * @dev: SCST device.
++ * @tgt: SCST target.
++ *
++ * Returns a non-zero number if the lookup was successful and zero if not.
++ */
++uint16_t scst_lookup_tg_id(struct scst_device *dev, struct scst_tgt *tgt)
++{
++ struct scst_dev_group *dg;
++ struct scst_target_group *tg;
++ struct scst_tg_tgt *tg_tgt;
++ uint16_t tg_id = 0;
++
++ TRACE_ENTRY();
++ mutex_lock(&scst_mutex);
++ dg = __lookup_dg_by_dev(dev);
++ if (!dg)
++ goto out_unlock;
++ tg_tgt = __lookup_dg_tgt(dg, tgt->tgt_name);
++ if (!tg_tgt)
++ goto out_unlock;
++ tg = tg_tgt->tg;
++ BUG_ON(!tg);
++ tg_id = tg->group_id;
++out_unlock:
++ mutex_unlock(&scst_mutex);
++
++ TRACE_EXIT_RES(tg_id);
++ return tg_id;
++}
++EXPORT_SYMBOL_GPL(scst_lookup_tg_id);
++
++/**
++ * scst_impl_alua_configured() - Whether implicit ALUA has been configured.
++ * @dev: Pointer to the SCST device to verify.
++ */
++bool scst_impl_alua_configured(struct scst_device *dev)
++{
++ struct scst_dev_group *dg;
++ bool res;
++
++ mutex_lock(&scst_mutex);
++ dg = __lookup_dg_by_dev(dev);
++ res = dg != NULL;
++ mutex_unlock(&scst_mutex);
++
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_impl_alua_configured);
++
++/**
++ * scst_tg_get_group_info() - Build REPORT TARGET GROUPS response.
++ * @buf: Pointer to a pointer to which the result buffer pointer will be set.
++ * @length: Response length, including the "RETURN DATA LENGTH" field.
++ * @dev: Pointer to the SCST device for which to obtain group information.
++ * @data_format: Three-bit response data format specification.
++ */
++int scst_tg_get_group_info(void **buf, uint32_t *length,
++ struct scst_device *dev, uint8_t data_format)
++{
++ struct scst_dev_group *dg;
++ struct scst_target_group *tg;
++ struct scst_tg_tgt *tgtgt;
++ struct scst_tgt *tgt;
++ uint8_t *p;
++ uint32_t ret_data_len;
++ uint16_t rel_tgt_id;
++ int res;
++
++ TRACE_ENTRY();
++
++ BUG_ON(!buf);
++ BUG_ON(!length);
++
++ ret_data_len = 0;
++
++ res = -EINVAL;
++ switch (data_format) {
++ case 0:
++ break;
++ case 1:
++ /* Extended header */
++ ret_data_len += 4;
++ break;
++ default:
++ goto out;
++ }
++
++ *length = 4;
++
++ mutex_lock(&scst_mutex);
++
++ dg = __lookup_dg_by_dev(dev);
++ if (dg) {
++ list_for_each_entry(tg, &dg->tg_list, entry) {
++ /* Target port group descriptor header. */
++ ret_data_len += 8;
++ list_for_each_entry(tgtgt, &tg->tgt_list, entry) {
++ /* Target port descriptor. */
++ ret_data_len += 4;
++ }
++ }
++ }
++
++ *length += ret_data_len;
++
++ res = -ENOMEM;
++ *buf = kzalloc(*length, GFP_KERNEL);
++ if (!*buf)
++ goto out_unlock;
++
++ p = *buf;
++ /* Return data length. */
++ put_unaligned(cpu_to_be32(ret_data_len), (__be32 *)p);
++ p += 4;
++ if (data_format == 1) {
++ /* Extended header */
++ *p++ = 0x10; /* format = 1 */
++ *p++ = 0x00; /* implicit transition time = 0 */
++ p += 2; /* reserved */
++ }
++
++ if (!dg)
++ goto done;
++
++ list_for_each_entry(tg, &dg->tg_list, entry) {
++ /* Target port group descriptor header. */
++ *p++ = (tg->preferred ? SCST_TG_PREFERRED : 0) | tg->state;
++ *p++ = SCST_TG_SUP_OPTIMIZED
++ | SCST_TG_SUP_NONOPTIMIZED
++ | SCST_TG_SUP_STANDBY
++ | SCST_TG_SUP_UNAVAILABLE;
++ put_unaligned(cpu_to_be16(tg->group_id), (__be16 *)p);
++ p += 2;
++ p++; /* reserved */
++ *p++ = 2; /* status code: implicit transition */
++ p++; /* vendor specific */
++ list_for_each_entry(tgtgt, &tg->tgt_list, entry)
++ (*p)++; /* target port count */
++ p++;
++ list_for_each_entry(tgtgt, &tg->tgt_list, entry) {
++ tgt = tgtgt->tgt;
++ rel_tgt_id = tgt ? tgt->rel_tgt_id : tgtgt->rel_tgt_id;
++ /* Target port descriptor. */
++ p += 2; /* reserved */
++ /* Relative target port identifier. */
++ put_unaligned(cpu_to_be16(rel_tgt_id),
++ (__be16 *)p);
++ p += 2;
++ }
++ }
++
++done:
++ WARN_ON(p - (uint8_t *)*buf != *length);
++
++ res = 0;
++
++out_unlock:
++ mutex_unlock(&scst_mutex);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_tg_get_group_info);
+diff -uprN orig/linux-3.2/drivers/scst/scst_proc.c linux-3.2/drivers/scst/scst_proc.c
+--- orig/linux-3.2/drivers/scst/scst_proc.c
++++ linux-3.2/drivers/scst/scst_proc.c
+@@ -0,0 +1,2716 @@
++/*
++ * scst_proc.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/unistd.h>
++#include <linux/string.h>
++#include <linux/proc_fs.h>
++#include <linux/seq_file.h>
++
++#include <scst/scst.h>
++#include "scst_priv.h"
++#include "scst_mem.h"
++#include "scst_pres.h"
++
++static int scst_proc_init_groups(void);
++static void scst_proc_cleanup_groups(void);
++static int scst_proc_assign_handler(char *buf);
++static int scst_proc_group_add(const char *p, unsigned int addr_method);
++static int scst_proc_del_free_acg(struct scst_acg *acg, int remove_proc);
++
++static struct scst_proc_data scst_version_proc_data;
++static struct scst_proc_data scst_help_proc_data;
++static struct scst_proc_data scst_sgv_proc_data;
++static struct scst_proc_data scst_groups_names_proc_data;
++static struct scst_proc_data scst_groups_devices_proc_data;
++static struct scst_proc_data scst_groups_addr_method_proc_data;
++static struct scst_proc_data scst_sessions_proc_data;
++static struct scst_proc_data scst_dev_handler_type_proc_data;
++static struct scst_proc_data scst_tgt_proc_data;
++static struct scst_proc_data scst_threads_proc_data;
++static struct scst_proc_data scst_scsi_tgt_proc_data;
++static struct scst_proc_data scst_dev_handler_proc_data;
++
++/*
++ * Must be less than 4K page size, since our output routines
++ * use some slack for overruns
++ */
++#define SCST_PROC_BLOCK_SIZE (PAGE_SIZE - 512)
++
++#define SCST_PROC_LOG_ENTRY_NAME "trace_level"
++#define SCST_PROC_DEV_HANDLER_TYPE_ENTRY_NAME "type"
++#define SCST_PROC_VERSION_NAME "version"
++#define SCST_PROC_SESSIONS_NAME "sessions"
++#define SCST_PROC_HELP_NAME "help"
++#define SCST_PROC_THREADS_NAME "threads"
++#define SCST_PROC_GROUPS_ENTRY_NAME "groups"
++#define SCST_PROC_GROUPS_DEVICES_ENTRY_NAME "devices"
++#define SCST_PROC_GROUPS_USERS_ENTRY_NAME "names"
++#define SCST_PROC_GROUPS_ADDR_METHOD_ENTRY_NAME "addr_method"
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++#define SCST_PROC_LAT_ENTRY_NAME "latency"
++#endif
++
++#define SCST_PROC_ACTION_ALL 1
++#define SCST_PROC_ACTION_NONE 2
++#define SCST_PROC_ACTION_DEFAULT 3
++#define SCST_PROC_ACTION_ADD 4
++#define SCST_PROC_ACTION_CLEAR 5
++#define SCST_PROC_ACTION_MOVE 6
++#define SCST_PROC_ACTION_DEL 7
++#define SCST_PROC_ACTION_REPLACE 8
++#define SCST_PROC_ACTION_VALUE 9
++#define SCST_PROC_ACTION_ASSIGN 10
++#define SCST_PROC_ACTION_ADD_GROUP 11
++#define SCST_PROC_ACTION_DEL_GROUP 12
++#define SCST_PROC_ACTION_RENAME_GROUP 13
++#define SCST_PROC_ACTION_DUMP_PRS 14
++
++static struct proc_dir_entry *scst_proc_scsi_tgt;
++static struct proc_dir_entry *scst_proc_groups_root;
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++static struct scst_proc_data scst_log_proc_data;
++
++static struct scst_trace_log scst_proc_trace_tbl[] = {
++ { TRACE_OUT_OF_MEM, "out_of_mem" },
++ { TRACE_MINOR, "minor" },
++ { TRACE_SG_OP, "sg" },
++ { TRACE_MEMORY, "mem" },
++ { TRACE_BUFF, "buff" },
++#ifndef GENERATING_UPSTREAM_PATCH
++ { TRACE_ENTRYEXIT, "entryexit" },
++#endif
++ { TRACE_PID, "pid" },
++ { TRACE_LINE, "line" },
++ { TRACE_FUNCTION, "function" },
++ { TRACE_DEBUG, "debug" },
++ { TRACE_SPECIAL, "special" },
++ { TRACE_SCSI, "scsi" },
++ { TRACE_MGMT, "mgmt" },
++ { TRACE_MGMT_DEBUG, "mgmt_dbg" },
++ { TRACE_FLOW_CONTROL, "flow_control" },
++ { TRACE_PRES, "pr" },
++ { 0, NULL }
++};
++
++static struct scst_trace_log scst_proc_local_trace_tbl[] = {
++ { TRACE_RTRY, "retry" },
++ { TRACE_SCSI_SERIALIZING, "scsi_serializing" },
++ { TRACE_RCV_BOT, "recv_bot" },
++ { TRACE_SND_BOT, "send_bot" },
++ { TRACE_RCV_TOP, "recv_top" },
++ { TRACE_SND_TOP, "send_top" },
++ { 0, NULL }
++};
++#endif
++
++static char *scst_proc_help_string =
++" echo \"assign H:C:I:L HANDLER_NAME\" >/proc/scsi_tgt/scsi_tgt\n"
++"\n"
++" echo \"add_group GROUP_NAME [FLAT]\" >/proc/scsi_tgt/scsi_tgt\n"
++" echo \"add_group GROUP_NAME [LUN]\" >/proc/scsi_tgt/scsi_tgt\n"
++" echo \"del_group GROUP_NAME\" >/proc/scsi_tgt/scsi_tgt\n"
++" echo \"rename_group OLD_NAME NEW_NAME\" >/proc/scsi_tgt/scsi_tgt\n"
++"\n"
++" echo \"add|del H:C:I:L lun [READ_ONLY]\""
++" >/proc/scsi_tgt/groups/GROUP_NAME/devices\n"
++" echo \"replace H:C:I:L lun [READ_ONLY]\""
++" >/proc/scsi_tgt/groups/GROUP_NAME/devices\n"
++" echo \"add|del V_NAME lun [READ_ONLY]\""
++" >/proc/scsi_tgt/groups/GROUP_NAME/devices\n"
++" echo \"replace V_NAME lun [READ_ONLY]\""
++" >/proc/scsi_tgt/groups/GROUP_NAME/devices\n"
++" echo \"clear\" >/proc/scsi_tgt/groups/GROUP_NAME/devices\n"
++"\n"
++" echo \"add|del NAME\" >/proc/scsi_tgt/groups/GROUP_NAME/names\n"
++" echo \"move NAME NEW_GROUP_NAME\" >/proc/scsi_tgt/groups/OLD_GROUP_NAME/names\n"
++" echo \"clear\" >/proc/scsi_tgt/groups/GROUP_NAME/names\n"
++"\n"
++" echo \"DEC|0xHEX|0OCT\" >/proc/scsi_tgt/threads\n"
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++"\n"
++" echo \"all|none|default\" >/proc/scsi_tgt/[DEV_HANDLER_NAME/]trace_level\n"
++" echo \"value DEC|0xHEX|0OCT\""
++" >/proc/scsi_tgt/[DEV_HANDLER_NAME/]trace_level\n"
++" echo \"set|add|del TOKEN\""
++" >/proc/scsi_tgt/[DEV_HANDLER_NAME/]trace_level\n"
++" where TOKEN is one of [debug, function, line, pid, entryexit,\n"
++" buff, mem, sg, out_of_mem, special, scsi,\n"
++" mgmt, minor, mgmt_dbg]\n"
++" Additionally for /proc/scsi_tgt/trace_level there are these TOKENs\n"
++" [scsi_serializing, retry, recv_bot, send_bot, recv_top, send_top]\n"
++" echo \"dump_prs dev_name\" >/proc/scsi_tgt/trace_level\n"
++#endif
++;
++
++static char *scst_proc_dev_handler_type[] = {
++ "Direct-access device (e.g., magnetic disk)",
++ "Sequential-access device (e.g., magnetic tape)",
++ "Printer device",
++ "Processor device",
++ "Write-once device (e.g., some optical disks)",
++ "CD-ROM device",
++ "Scanner device (obsolete)",
++ "Optical memory device (e.g., some optical disks)",
++ "Medium changer device (e.g., jukeboxes)",
++ "Communications device (obsolete)",
++ "Defined by ASC IT8 (Graphic arts pre-press devices)",
++ "Defined by ASC IT8 (Graphic arts pre-press devices)",
++ "Storage array controller device (e.g., RAID)",
++ "Enclosure services device",
++ "Simplified direct-access device (e.g., magnetic disk)",
++ "Optical card reader/writer device"
++};
++
++static DEFINE_MUTEX(scst_proc_mutex);
++
++#include <linux/ctype.h>
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++static DEFINE_MUTEX(scst_log_mutex);
++
++int scst_proc_log_entry_write(struct file *file, const char __user *buf,
++ unsigned long length, unsigned long *log_level,
++ unsigned long default_level, const struct scst_trace_log *tbl)
++{
++ int res = length;
++ int action;
++ unsigned long level = 0, oldlevel;
++ char *buffer, *p, *e;
++ const struct scst_trace_log *t;
++ char *data = (char *)PDE(file->f_dentry->d_inode)->data;
++
++ TRACE_ENTRY();
++
++ if (length > SCST_PROC_BLOCK_SIZE) {
++ res = -EOVERFLOW;
++ goto out;
++ }
++ if (!buf) {
++ res = -EINVAL;
++ goto out;
++ }
++ buffer = (char *)__get_free_page(GFP_KERNEL);
++ if (!buffer) {
++ res = -ENOMEM;
++ goto out;
++ }
++ if (copy_from_user(buffer, buf, length)) {
++ res = -EFAULT;
++ goto out_free;
++ }
++ if (length < PAGE_SIZE) {
++ buffer[length] = '\0';
++ } else if (buffer[PAGE_SIZE-1]) {
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ /*
++ * Usage:
++ * echo "all|none|default" >/proc/scsi_tgt/trace_level
++ * echo "value DEC|0xHEX|0OCT" >/proc/scsi_tgt/trace_level
++ * echo "add|del TOKEN" >/proc/scsi_tgt/trace_level
++ */
++ p = buffer;
++ if (!strncasecmp("all", p, 3)) {
++ action = SCST_PROC_ACTION_ALL;
++ } else if (!strncasecmp("none", p, 4) || !strncasecmp("null", p, 4)) {
++ action = SCST_PROC_ACTION_NONE;
++ } else if (!strncasecmp("default", p, 7)) {
++ action = SCST_PROC_ACTION_DEFAULT;
++ } else if (!strncasecmp("add ", p, 4)) {
++ p += 4;
++ action = SCST_PROC_ACTION_ADD;
++ } else if (!strncasecmp("del ", p, 4)) {
++ p += 4;
++ action = SCST_PROC_ACTION_DEL;
++ } else if (!strncasecmp("value ", p, 6)) {
++ p += 6;
++ action = SCST_PROC_ACTION_VALUE;
++ } else if (!strncasecmp("dump_prs ", p, 9)) {
++ p += 9;
++ action = SCST_PROC_ACTION_DUMP_PRS;
++ } else {
++ if (p[strlen(p) - 1] == '\n')
++ p[strlen(p) - 1] = '\0';
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ switch (action) {
++ case SCST_PROC_ACTION_ALL:
++ level = TRACE_ALL;
++ break;
++ case SCST_PROC_ACTION_DEFAULT:
++ level = default_level;
++ break;
++ case SCST_PROC_ACTION_NONE:
++ level = TRACE_NULL;
++ break;
++ case SCST_PROC_ACTION_ADD:
++ case SCST_PROC_ACTION_DEL:
++ while (isspace(*p) && *p != '\0')
++ p++;
++ e = p;
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = 0;
++ if (tbl) {
++ t = tbl;
++ while (t->token) {
++ if (!strcasecmp(p, t->token)) {
++ level = t->val;
++ break;
++ }
++ t++;
++ }
++ }
++ if (level == 0) {
++ t = scst_proc_trace_tbl;
++ while (t->token) {
++ if (!strcasecmp(p, t->token)) {
++ level = t->val;
++ break;
++ }
++ t++;
++ }
++ }
++ if (level == 0) {
++ PRINT_ERROR("Unknown token \"%s\"", p);
++ res = -EINVAL;
++ goto out_free;
++ }
++ break;
++ case SCST_PROC_ACTION_VALUE:
++ while (isspace(*p) && *p != '\0')
++ p++;
++ level = simple_strtoul(p, NULL, 0);
++ break;
++ case SCST_PROC_ACTION_DUMP_PRS:
++ {
++ struct scst_device *dev;
++
++ while (isspace(*p) && *p != '\0')
++ p++;
++ e = p;
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = '\0';
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out_free;
++ }
++
++ list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
++ if (strcmp(dev->virt_name, p) == 0) {
++ scst_pr_dump_prs(dev, true);
++ goto out_up;
++ }
++ }
++
++ PRINT_ERROR("Device %s not found", p);
++ res = -ENOENT;
++out_up:
++ mutex_unlock(&scst_mutex);
++ goto out_free;
++ }
++ }
++
++ oldlevel = *log_level;
++
++ switch (action) {
++ case SCST_PROC_ACTION_ADD:
++ *log_level |= level;
++ break;
++ case SCST_PROC_ACTION_DEL:
++ *log_level &= ~level;
++ break;
++ default:
++ *log_level = level;
++ break;
++ }
++
++ PRINT_INFO("Changed trace level for \"%s\": "
++ "old 0x%08lx, new 0x%08lx",
++ (char *)data, oldlevel, *log_level);
++
++out_free:
++ free_page((unsigned long)buffer);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_proc_log_entry_write);
++
++static ssize_t scst_proc_scsi_tgt_gen_write_log(struct file *file,
++ const char __user *buf,
++ size_t length, loff_t *off)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_log_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ res = scst_proc_log_entry_write(file, buf, length,
++ &trace_flag, SCST_DEFAULT_LOG_FLAGS,
++ scst_proc_local_trace_tbl);
++
++ mutex_unlock(&scst_log_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++
++static char *scst_io_size_names[] = {
++ "<=8K ",
++ "<=32K ",
++ "<=128K",
++ "<=512K",
++ ">512K "
++};
++
++static int lat_info_show(struct seq_file *seq, void *v)
++{
++ int res = 0;
++ struct scst_acg *acg;
++ struct scst_session *sess;
++ char buf[50];
++
++ TRACE_ENTRY();
++
++ BUILD_BUG_ON(SCST_LATENCY_STATS_NUM != ARRAY_SIZE(scst_io_size_names));
++ BUILD_BUG_ON(SCST_LATENCY_STATS_NUM != ARRAY_SIZE(sess->sess_latency_stat));
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ list_for_each_entry(acg, &scst_acg_list, acg_list_entry) {
++ bool header_printed = false;
++
++ list_for_each_entry(sess, &acg->acg_sess_list,
++ acg_sess_list_entry) {
++ unsigned int i;
++ int t;
++ uint64_t scst_time, tgt_time, dev_time;
++ unsigned int processed_cmds;
++
++ if (!header_printed) {
++ seq_printf(seq, "%-15s %-15s %-46s %-46s %-46s\n",
++ "T-L names", "Total commands", "SCST latency",
++ "Target latency", "Dev latency (min/avg/max/all ns)");
++ header_printed = true;
++ }
++
++ seq_printf(seq, "Target name: %s\nInitiator name: %s\n",
++ sess->tgt->tgtt->name,
++ sess->initiator_name);
++
++ spin_lock_bh(&sess->lat_lock);
++
++ for (i = 0; i < SCST_LATENCY_STATS_NUM ; i++) {
++ uint64_t scst_time_wr, tgt_time_wr, dev_time_wr;
++ unsigned int processed_cmds_wr;
++ uint64_t scst_time_rd, tgt_time_rd, dev_time_rd;
++ unsigned int processed_cmds_rd;
++ struct scst_ext_latency_stat *latency_stat;
++
++ latency_stat = &sess->sess_latency_stat[i];
++ scst_time_wr = latency_stat->scst_time_wr;
++ scst_time_rd = latency_stat->scst_time_rd;
++ tgt_time_wr = latency_stat->tgt_time_wr;
++ tgt_time_rd = latency_stat->tgt_time_rd;
++ dev_time_wr = latency_stat->dev_time_wr;
++ dev_time_rd = latency_stat->dev_time_rd;
++ processed_cmds_wr = latency_stat->processed_cmds_wr;
++ processed_cmds_rd = latency_stat->processed_cmds_rd;
++
++ seq_printf(seq, "%-5s %-9s %-15lu ",
++ "Write", scst_io_size_names[i],
++ (unsigned long)processed_cmds_wr);
++ if (processed_cmds_wr == 0)
++ processed_cmds_wr = 1;
++
++ do_div(scst_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_scst_time_wr,
++ (unsigned long)scst_time_wr,
++ (unsigned long)latency_stat->max_scst_time_wr,
++ (unsigned long)latency_stat->scst_time_wr);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(tgt_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_tgt_time_wr,
++ (unsigned long)tgt_time_wr,
++ (unsigned long)latency_stat->max_tgt_time_wr,
++ (unsigned long)latency_stat->tgt_time_wr);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(dev_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_dev_time_wr,
++ (unsigned long)dev_time_wr,
++ (unsigned long)latency_stat->max_dev_time_wr,
++ (unsigned long)latency_stat->dev_time_wr);
++ seq_printf(seq, "%-47s\n", buf);
++
++ seq_printf(seq, "%-5s %-9s %-15lu ",
++ "Read", scst_io_size_names[i],
++ (unsigned long)processed_cmds_rd);
++ if (processed_cmds_rd == 0)
++ processed_cmds_rd = 1;
++
++ do_div(scst_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_scst_time_rd,
++ (unsigned long)scst_time_rd,
++ (unsigned long)latency_stat->max_scst_time_rd,
++ (unsigned long)latency_stat->scst_time_rd);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(tgt_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_tgt_time_rd,
++ (unsigned long)tgt_time_rd,
++ (unsigned long)latency_stat->max_tgt_time_rd,
++ (unsigned long)latency_stat->tgt_time_rd);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(dev_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_dev_time_rd,
++ (unsigned long)dev_time_rd,
++ (unsigned long)latency_stat->max_dev_time_rd,
++ (unsigned long)latency_stat->dev_time_rd);
++ seq_printf(seq, "%-47s\n", buf);
++ }
++
++ for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) {
++ struct list_head *head =
++ &sess->sess_tgt_dev_list[t];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++
++ seq_printf(seq, "\nLUN: %llu\n", tgt_dev->lun);
++
++ for (i = 0; i < SCST_LATENCY_STATS_NUM ; i++) {
++ uint64_t scst_time_wr, tgt_time_wr, dev_time_wr;
++ unsigned int processed_cmds_wr;
++ uint64_t scst_time_rd, tgt_time_rd, dev_time_rd;
++ unsigned int processed_cmds_rd;
++ struct scst_ext_latency_stat *latency_stat;
++
++ latency_stat = &tgt_dev->dev_latency_stat[i];
++ scst_time_wr = latency_stat->scst_time_wr;
++ scst_time_rd = latency_stat->scst_time_rd;
++ tgt_time_wr = latency_stat->tgt_time_wr;
++ tgt_time_rd = latency_stat->tgt_time_rd;
++ dev_time_wr = latency_stat->dev_time_wr;
++ dev_time_rd = latency_stat->dev_time_rd;
++ processed_cmds_wr = latency_stat->processed_cmds_wr;
++ processed_cmds_rd = latency_stat->processed_cmds_rd;
++
++ seq_printf(seq, "%-5s %-9s %-15lu ",
++ "Write", scst_io_size_names[i],
++ (unsigned long)processed_cmds_wr);
++ if (processed_cmds_wr == 0)
++ processed_cmds_wr = 1;
++
++ do_div(scst_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_scst_time_wr,
++ (unsigned long)scst_time_wr,
++ (unsigned long)latency_stat->max_scst_time_wr,
++ (unsigned long)latency_stat->scst_time_wr);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(tgt_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_tgt_time_wr,
++ (unsigned long)tgt_time_wr,
++ (unsigned long)latency_stat->max_tgt_time_wr,
++ (unsigned long)latency_stat->tgt_time_wr);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(dev_time_wr, processed_cmds_wr);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_dev_time_wr,
++ (unsigned long)dev_time_wr,
++ (unsigned long)latency_stat->max_dev_time_wr,
++ (unsigned long)latency_stat->dev_time_wr);
++ seq_printf(seq, "%-47s\n", buf);
++
++ seq_printf(seq, "%-5s %-9s %-15lu ",
++ "Read", scst_io_size_names[i],
++ (unsigned long)processed_cmds_rd);
++ if (processed_cmds_rd == 0)
++ processed_cmds_rd = 1;
++
++ do_div(scst_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_scst_time_rd,
++ (unsigned long)scst_time_rd,
++ (unsigned long)latency_stat->max_scst_time_rd,
++ (unsigned long)latency_stat->scst_time_rd);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(tgt_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_tgt_time_rd,
++ (unsigned long)tgt_time_rd,
++ (unsigned long)latency_stat->max_tgt_time_rd,
++ (unsigned long)latency_stat->tgt_time_rd);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(dev_time_rd, processed_cmds_rd);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)latency_stat->min_dev_time_rd,
++ (unsigned long)dev_time_rd,
++ (unsigned long)latency_stat->max_dev_time_rd,
++ (unsigned long)latency_stat->dev_time_rd);
++ seq_printf(seq, "%-47s\n", buf);
++ }
++ }
++ }
++
++ scst_time = sess->scst_time;
++ tgt_time = sess->tgt_time;
++ dev_time = sess->dev_time;
++ processed_cmds = sess->processed_cmds;
++
++ seq_printf(seq, "\n%-15s %-16d", "Overall ",
++ processed_cmds);
++
++ if (processed_cmds == 0)
++ processed_cmds = 1;
++
++ do_div(scst_time, processed_cmds);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)sess->min_scst_time,
++ (unsigned long)scst_time,
++ (unsigned long)sess->max_scst_time,
++ (unsigned long)sess->scst_time);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(tgt_time, processed_cmds);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)sess->min_tgt_time,
++ (unsigned long)tgt_time,
++ (unsigned long)sess->max_tgt_time,
++ (unsigned long)sess->tgt_time);
++ seq_printf(seq, "%-47s", buf);
++
++ do_div(dev_time, processed_cmds);
++ snprintf(buf, sizeof(buf), "%lu/%lu/%lu/%lu",
++ (unsigned long)sess->min_dev_time,
++ (unsigned long)dev_time,
++ (unsigned long)sess->max_dev_time,
++ (unsigned long)sess->dev_time);
++ seq_printf(seq, "%-47s\n\n", buf);
++
++ spin_unlock_bh(&sess->lat_lock);
++ }
++ }
++
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_proc_scsi_tgt_gen_write_lat(struct file *file,
++ const char __user *buf,
++ size_t length, loff_t *off)
++{
++ int res = length, t;
++ struct scst_acg *acg;
++ struct scst_session *sess;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ list_for_each_entry(acg, &scst_acg_list, acg_list_entry) {
++ list_for_each_entry(sess, &acg->acg_sess_list,
++ acg_sess_list_entry) {
++ PRINT_INFO("Zeroing latency statistics for initiator "
++ "%s", sess->initiator_name);
++ spin_lock_bh(&sess->lat_lock);
++
++ sess->scst_time = 0;
++ sess->tgt_time = 0;
++ sess->dev_time = 0;
++ sess->min_scst_time = 0;
++ sess->min_tgt_time = 0;
++ sess->min_dev_time = 0;
++ sess->max_scst_time = 0;
++ sess->max_tgt_time = 0;
++ sess->max_dev_time = 0;
++ sess->processed_cmds = 0;
++ memset(sess->sess_latency_stat, 0,
++ sizeof(sess->sess_latency_stat));
++
++ for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) {
++ struct list_head *head =
++ &sess->sess_tgt_dev_list[t];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ tgt_dev->scst_time = 0;
++ tgt_dev->tgt_time = 0;
++ tgt_dev->dev_time = 0;
++ tgt_dev->processed_cmds = 0;
++ memset(tgt_dev->dev_latency_stat, 0,
++ sizeof(tgt_dev->dev_latency_stat));
++ }
++ }
++
++ spin_unlock_bh(&sess->lat_lock);
++ }
++ }
++
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_proc_data scst_lat_proc_data = {
++ SCST_DEF_RW_SEQ_OP(scst_proc_scsi_tgt_gen_write_lat)
++ .show = lat_info_show,
++ .data = "scsi_tgt",
++};
++
++#endif /* CONFIG_SCST_MEASURE_LATENCY */
++
++static int __init scst_proc_init_module_log(void)
++{
++ int res = 0;
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) || \
++ defined(CONFIG_SCST_MEASURE_LATENCY)
++ struct proc_dir_entry *generic;
++#endif
++
++ TRACE_ENTRY();
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ generic = scst_create_proc_entry(scst_proc_scsi_tgt,
++ SCST_PROC_LOG_ENTRY_NAME,
++ &scst_log_proc_data);
++ if (!generic) {
++ PRINT_ERROR("cannot init /proc/%s/%s",
++ SCST_PROC_ENTRY_NAME, SCST_PROC_LOG_ENTRY_NAME);
++ res = -ENOMEM;
++ }
++#endif
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++ if (res == 0) {
++ generic = scst_create_proc_entry(scst_proc_scsi_tgt,
++ SCST_PROC_LAT_ENTRY_NAME,
++ &scst_lat_proc_data);
++ if (!generic) {
++ PRINT_ERROR("cannot init /proc/%s/%s",
++ SCST_PROC_ENTRY_NAME,
++ SCST_PROC_LAT_ENTRY_NAME);
++ res = -ENOMEM;
++ }
++ }
++#endif
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void scst_proc_cleanup_module_log(void)
++{
++ TRACE_ENTRY();
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ remove_proc_entry(SCST_PROC_LOG_ENTRY_NAME, scst_proc_scsi_tgt);
++#endif
++
++#ifdef CONFIG_SCST_MEASURE_LATENCY
++ remove_proc_entry(SCST_PROC_LAT_ENTRY_NAME, scst_proc_scsi_tgt);
++#endif
++
++ TRACE_EXIT();
++ return;
++}
++
++static 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(name, scst_proc_groups_root);
++ if (acg->acg_proc_root == NULL) {
++ PRINT_ERROR("Not enough memory to register %s entry in "
++ "/proc/%s/%s", name, SCST_PROC_ENTRY_NAME,
++ SCST_PROC_GROUPS_ENTRY_NAME);
++ goto out;
++ }
++
++ scst_groups_addr_method_proc_data.data = acg;
++ generic = scst_create_proc_entry(acg->acg_proc_root,
++ SCST_PROC_GROUPS_ADDR_METHOD_ENTRY_NAME,
++ &scst_groups_addr_method_proc_data);
++ if (!generic) {
++ PRINT_ERROR("Cannot init /proc/%s/%s/%s/%s",
++ SCST_PROC_ENTRY_NAME,
++ SCST_PROC_GROUPS_ENTRY_NAME,
++ name, SCST_PROC_GROUPS_ADDR_METHOD_ENTRY_NAME);
++ res = -ENOMEM;
++ goto out_remove;
++ }
++
++ scst_groups_devices_proc_data.data = acg;
++ generic = scst_create_proc_entry(acg->acg_proc_root,
++ SCST_PROC_GROUPS_DEVICES_ENTRY_NAME,
++ &scst_groups_devices_proc_data);
++ if (!generic) {
++ PRINT_ERROR("Cannot init /proc/%s/%s/%s/%s",
++ SCST_PROC_ENTRY_NAME,
++ SCST_PROC_GROUPS_ENTRY_NAME,
++ name, SCST_PROC_GROUPS_DEVICES_ENTRY_NAME);
++ res = -ENOMEM;
++ goto out_remove0;
++ }
++
++ scst_groups_names_proc_data.data = acg;
++ generic = scst_create_proc_entry(acg->acg_proc_root,
++ SCST_PROC_GROUPS_USERS_ENTRY_NAME,
++ &scst_groups_names_proc_data);
++ if (!generic) {
++ PRINT_ERROR("Cannot init /proc/%s/%s/%s/%s",
++ SCST_PROC_ENTRY_NAME,
++ SCST_PROC_GROUPS_ENTRY_NAME,
++ name, SCST_PROC_GROUPS_USERS_ENTRY_NAME);
++ res = -ENOMEM;
++ goto out_remove1;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_remove1:
++ remove_proc_entry(SCST_PROC_GROUPS_DEVICES_ENTRY_NAME,
++ acg->acg_proc_root);
++
++out_remove0:
++ remove_proc_entry(SCST_PROC_GROUPS_ADDR_METHOD_ENTRY_NAME,
++ acg->acg_proc_root);
++out_remove:
++ remove_proc_entry(name, scst_proc_groups_root);
++ goto out;
++}
++
++static void scst_proc_del_acg_tree(struct proc_dir_entry *acg_proc_root,
++ const char *name)
++{
++ TRACE_ENTRY();
++
++ remove_proc_entry(SCST_PROC_GROUPS_ADDR_METHOD_ENTRY_NAME, acg_proc_root);
++ remove_proc_entry(SCST_PROC_GROUPS_USERS_ENTRY_NAME, acg_proc_root);
++ remove_proc_entry(SCST_PROC_GROUPS_DEVICES_ENTRY_NAME, acg_proc_root);
++ remove_proc_entry(name, scst_proc_groups_root);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++static int scst_proc_group_add(const char *p, unsigned int addr_method)
++{
++ int res = 0, len = strlen(p) + 1;
++ struct scst_acg *acg;
++ char *name = NULL;
++
++ TRACE_ENTRY();
++
++ name = kmalloc(len, GFP_KERNEL);
++ if (name == NULL) {
++ PRINT_ERROR("Allocation of new name (size %d) failed", len);
++ goto out_nomem;
++ }
++ strlcpy(name, p, len);
++
++ acg = scst_alloc_add_acg(NULL, name, false);
++ if (acg == NULL) {
++ PRINT_ERROR("scst_alloc_add_acg() (name %s) failed", name);
++ goto out_free;
++ }
++
++ acg->addr_method = addr_method;
++
++ res = scst_proc_group_add_tree(acg, p);
++ if (res != 0)
++ goto out_free_acg;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free_acg:
++ scst_proc_del_free_acg(acg, 0);
++
++out_free:
++ kfree(name);
++
++out_nomem:
++ res = -ENOMEM;
++ goto out;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++static int scst_proc_del_free_acg(struct scst_acg *acg, int remove_proc)
++{
++ struct proc_dir_entry *acg_proc_root = acg->acg_proc_root;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (acg != scst_default_acg) {
++ if (!scst_acg_sess_is_empty(acg)) {
++ PRINT_ERROR("%s", "Session is not empty");
++ res = -EBUSY;
++ goto out;
++ }
++ if (remove_proc)
++ scst_proc_del_acg_tree(acg_proc_root, acg->acg_name);
++ scst_del_free_acg(acg);
++ }
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++static int scst_proc_rename_acg(struct scst_acg *acg, const char *new_name)
++{
++ int res = 0, len = strlen(new_name) + 1;
++ char *name;
++ struct proc_dir_entry *old_acg_proc_root = acg->acg_proc_root;
++
++ TRACE_ENTRY();
++
++ name = kmalloc(len, GFP_KERNEL);
++ if (name == NULL) {
++ PRINT_ERROR("Allocation of new name (size %d) failed", len);
++ goto out_nomem;
++ }
++ strlcpy(name, new_name, len);
++
++ res = scst_proc_group_add_tree(acg, new_name);
++ if (res != 0)
++ goto out_free;
++
++ scst_proc_del_acg_tree(old_acg_proc_root, acg->acg_name);
++
++ kfree(acg->acg_name);
++ acg->acg_name = name;
++
++ scst_check_reassign_sessions();
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(name);
++
++out_nomem:
++ res = -ENOMEM;
++ goto out;
++}
++
++static int __init scst_proc_init_groups(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /* create the proc directory entry for the device */
++ scst_proc_groups_root = proc_mkdir(SCST_PROC_GROUPS_ENTRY_NAME,
++ scst_proc_scsi_tgt);
++ if (scst_proc_groups_root == NULL) {
++ PRINT_ERROR("Not enough memory to register %s entry in "
++ "/proc/%s", SCST_PROC_GROUPS_ENTRY_NAME,
++ SCST_PROC_ENTRY_NAME);
++ goto out_nomem;
++ }
++
++ res = scst_proc_group_add_tree(scst_default_acg,
++ SCST_DEFAULT_ACG_NAME);
++ if (res != 0)
++ goto out_remove;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_remove:
++ remove_proc_entry(SCST_PROC_GROUPS_ENTRY_NAME, scst_proc_scsi_tgt);
++
++out_nomem:
++ res = -ENOMEM;
++ goto out;
++}
++
++static void scst_proc_cleanup_groups(void)
++{
++ struct scst_acg *acg_tmp, *acg;
++
++ TRACE_ENTRY();
++
++ /* remove all groups (dir & entries) */
++ list_for_each_entry_safe(acg, acg_tmp, &scst_acg_list,
++ acg_list_entry) {
++ scst_proc_del_free_acg(acg, 1);
++ }
++
++ scst_proc_del_acg_tree(scst_default_acg->acg_proc_root,
++ SCST_DEFAULT_ACG_NAME);
++ TRACE_DBG("remove_proc_entry(%s, %p)",
++ SCST_PROC_GROUPS_ENTRY_NAME, scst_proc_scsi_tgt);
++ remove_proc_entry(SCST_PROC_GROUPS_ENTRY_NAME, scst_proc_scsi_tgt);
++
++ TRACE_EXIT();
++}
++
++static int __init scst_proc_init_sgv(void)
++{
++ int res = 0;
++ struct proc_dir_entry *pr;
++
++ TRACE_ENTRY();
++
++ pr = scst_create_proc_entry(scst_proc_scsi_tgt, "sgv",
++ &scst_sgv_proc_data);
++ if (pr == NULL) {
++ PRINT_ERROR("%s", "cannot create sgv /proc entry");
++ res = -ENOMEM;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void __exit scst_proc_cleanup_sgv(void)
++{
++ TRACE_ENTRY();
++ remove_proc_entry("sgv", scst_proc_scsi_tgt);
++ TRACE_EXIT();
++}
++
++int __init scst_proc_init_module(void)
++{
++ int res = 0;
++ struct proc_dir_entry *generic;
++
++ TRACE_ENTRY();
++
++ scst_proc_scsi_tgt = proc_mkdir(SCST_PROC_ENTRY_NAME, NULL);
++ if (!scst_proc_scsi_tgt) {
++ PRINT_ERROR("cannot init /proc/%s", SCST_PROC_ENTRY_NAME);
++ goto out_nomem;
++ }
++
++ generic = scst_create_proc_entry(scst_proc_scsi_tgt,
++ SCST_PROC_ENTRY_NAME,
++ &scst_tgt_proc_data);
++ if (!generic) {
++ PRINT_ERROR("cannot init /proc/%s/%s",
++ SCST_PROC_ENTRY_NAME, SCST_PROC_ENTRY_NAME);
++ goto out_remove;
++ }
++
++ generic = scst_create_proc_entry(scst_proc_scsi_tgt,
++ SCST_PROC_VERSION_NAME,
++ &scst_version_proc_data);
++ if (!generic) {
++ PRINT_ERROR("cannot init /proc/%s/%s",
++ SCST_PROC_ENTRY_NAME, SCST_PROC_VERSION_NAME);
++ goto out_remove1;
++ }
++
++ generic = scst_create_proc_entry(scst_proc_scsi_tgt,
++ SCST_PROC_SESSIONS_NAME,
++ &scst_sessions_proc_data);
++ if (!generic) {
++ PRINT_ERROR("cannot init /proc/%s/%s",
++ SCST_PROC_ENTRY_NAME, SCST_PROC_SESSIONS_NAME);
++ goto out_remove2;
++ }
++
++ generic = scst_create_proc_entry(scst_proc_scsi_tgt,
++ SCST_PROC_HELP_NAME,
++ &scst_help_proc_data);
++ if (!generic) {
++ PRINT_ERROR("cannot init /proc/%s/%s",
++ SCST_PROC_ENTRY_NAME, SCST_PROC_HELP_NAME);
++ goto out_remove3;
++ }
++
++ generic = scst_create_proc_entry(scst_proc_scsi_tgt,
++ SCST_PROC_THREADS_NAME,
++ &scst_threads_proc_data);
++ if (!generic) {
++ PRINT_ERROR("cannot init /proc/%s/%s",
++ SCST_PROC_ENTRY_NAME, SCST_PROC_THREADS_NAME);
++ goto out_remove4;
++ }
++
++ if (scst_proc_init_module_log() < 0)
++ goto out_remove5;
++
++ if (scst_proc_init_groups() < 0)
++ goto out_remove6;
++
++ if (scst_proc_init_sgv() < 0)
++ goto out_remove7;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_remove7:
++ scst_proc_cleanup_groups();
++
++out_remove6:
++ scst_proc_cleanup_module_log();
++
++out_remove5:
++ remove_proc_entry(SCST_PROC_THREADS_NAME, scst_proc_scsi_tgt);
++
++out_remove4:
++ remove_proc_entry(SCST_PROC_HELP_NAME, scst_proc_scsi_tgt);
++
++out_remove3:
++ remove_proc_entry(SCST_PROC_SESSIONS_NAME, scst_proc_scsi_tgt);
++
++out_remove2:
++ remove_proc_entry(SCST_PROC_VERSION_NAME, scst_proc_scsi_tgt);
++
++out_remove1:
++ remove_proc_entry(SCST_PROC_ENTRY_NAME, scst_proc_scsi_tgt);
++
++out_remove:
++ remove_proc_entry(SCST_PROC_ENTRY_NAME, NULL);
++
++out_nomem:
++ res = -ENOMEM;
++ goto out;
++}
++
++void __exit scst_proc_cleanup_module(void)
++{
++ TRACE_ENTRY();
++
++ /* We may not bother about locks here */
++ scst_proc_cleanup_sgv();
++ scst_proc_cleanup_groups();
++ scst_proc_cleanup_module_log();
++ remove_proc_entry(SCST_PROC_THREADS_NAME, scst_proc_scsi_tgt);
++ remove_proc_entry(SCST_PROC_HELP_NAME, scst_proc_scsi_tgt);
++ remove_proc_entry(SCST_PROC_SESSIONS_NAME, scst_proc_scsi_tgt);
++ remove_proc_entry(SCST_PROC_VERSION_NAME, scst_proc_scsi_tgt);
++ remove_proc_entry(SCST_PROC_ENTRY_NAME, scst_proc_scsi_tgt);
++ remove_proc_entry(SCST_PROC_ENTRY_NAME, NULL);
++
++ TRACE_EXIT();
++}
++
++static ssize_t scst_proc_threads_write(struct file *file,
++ const char __user *buf,
++ size_t length, loff_t *off)
++{
++ int res = length;
++ int oldtn, newtn, delta;
++ char *buffer;
++
++ TRACE_ENTRY();
++
++ if (length > SCST_PROC_BLOCK_SIZE) {
++ res = -EOVERFLOW;
++ goto out;
++ }
++ if (!buf) {
++ res = -EINVAL;
++ goto out;
++ }
++ buffer = (char *)__get_free_page(GFP_KERNEL);
++ if (!buffer) {
++ res = -ENOMEM;
++ goto out;
++ }
++ if (copy_from_user(buffer, buf, length)) {
++ res = -EFAULT;
++ goto out_free;
++ }
++ if (length < PAGE_SIZE) {
++ buffer[length] = '\0';
++ } else if (buffer[PAGE_SIZE-1]) {
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ if (mutex_lock_interruptible(&scst_proc_mutex) != 0) {
++ res = -EINTR;
++ goto out_free;
++ }
++
++ mutex_lock(&scst_mutex);
++
++ oldtn = scst_main_cmd_threads.nr_threads;
++ newtn = simple_strtoul(buffer, NULL, 0);
++ if (newtn <= 0) {
++ PRINT_ERROR("Illegal threads num value %d", newtn);
++ res = -EINVAL;
++ goto out_up_thr_free;
++ }
++ delta = newtn - oldtn;
++ if (delta < 0)
++ scst_del_threads(&scst_main_cmd_threads, -delta);
++ else {
++ int rc = scst_add_threads(&scst_main_cmd_threads, NULL, NULL,
++ delta);
++ if (rc != 0)
++ res = rc;
++ }
++
++ PRINT_INFO("Changed cmd threads num: old %d, new %d", oldtn, newtn);
++
++out_up_thr_free:
++ mutex_unlock(&scst_mutex);
++
++ mutex_unlock(&scst_proc_mutex);
++
++out_free:
++ free_page((unsigned long)buffer);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++int scst_build_proc_target_dir_entries(struct scst_tgt_template *vtt)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /* create the proc directory entry for the device */
++ vtt->proc_tgt_root = proc_mkdir(vtt->name, scst_proc_scsi_tgt);
++ if (vtt->proc_tgt_root == NULL) {
++ PRINT_ERROR("Not enough memory to register SCSI target %s "
++ "in /proc/%s", vtt->name, SCST_PROC_ENTRY_NAME);
++ goto out_nomem;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_nomem:
++ res = -ENOMEM;
++ goto out;
++}
++
++void scst_cleanup_proc_target_dir_entries(struct scst_tgt_template *vtt)
++{
++ TRACE_ENTRY();
++
++ remove_proc_entry(vtt->name, scst_proc_scsi_tgt);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called under scst_mutex */
++int scst_build_proc_target_entries(struct scst_tgt *vtt)
++{
++ int res = 0;
++ struct proc_dir_entry *p;
++ char name[20];
++
++ TRACE_ENTRY();
++
++ if (vtt->tgtt->read_proc || vtt->tgtt->write_proc) {
++ /* create the proc file entry for the device */
++ scnprintf(name, sizeof(name), "%d", vtt->tgtt->proc_dev_num);
++ scst_scsi_tgt_proc_data.data = (void *)vtt;
++ p = scst_create_proc_entry(vtt->tgtt->proc_tgt_root,
++ name,
++ &scst_scsi_tgt_proc_data);
++ if (p == NULL) {
++ PRINT_ERROR("Not enough memory to register SCSI "
++ "target entry %s in /proc/%s/%s", name,
++ SCST_PROC_ENTRY_NAME, vtt->tgtt->name);
++ res = -ENOMEM;
++ goto out;
++ }
++ vtt->proc_num = vtt->tgtt->proc_dev_num;
++ vtt->tgtt->proc_dev_num++;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++void scst_cleanup_proc_target_entries(struct scst_tgt *vtt)
++{
++ char name[20];
++
++ TRACE_ENTRY();
++
++ if (vtt->tgtt->read_proc || vtt->tgtt->write_proc) {
++ scnprintf(name, sizeof(name), "%d", vtt->proc_num);
++ remove_proc_entry(name, vtt->tgtt->proc_tgt_root);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static ssize_t scst_proc_scsi_tgt_write(struct file *file,
++ const char __user *buf,
++ size_t length, loff_t *off)
++{
++ struct scst_tgt *vtt =
++ (struct scst_tgt *)PDE(file->f_dentry->d_inode)->data;
++ ssize_t res = 0;
++ char *buffer;
++ char *start;
++ int eof = 0;
++
++ TRACE_ENTRY();
++
++ if (vtt->tgtt->write_proc == NULL) {
++ res = -ENOSYS;
++ goto out;
++ }
++
++ if (length > SCST_PROC_BLOCK_SIZE) {
++ res = -EOVERFLOW;
++ goto out;
++ }
++ if (!buf) {
++ res = -EINVAL;
++ goto out;
++ }
++ buffer = (char *)__get_free_page(GFP_KERNEL);
++ if (!buffer) {
++ res = -ENOMEM;
++ goto out;
++ }
++ if (copy_from_user(buffer, buf, length)) {
++ res = -EFAULT;
++ goto out_free;
++ }
++ if (length < PAGE_SIZE) {
++ buffer[length] = '\0';
++ } else if (buffer[PAGE_SIZE-1]) {
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ TRACE_BUFFER("Buffer", buffer, length);
++
++ if (mutex_lock_interruptible(&scst_proc_mutex) != 0) {
++ res = -EINTR;
++ goto out_free;
++ }
++
++ res = vtt->tgtt->write_proc(buffer, &start, 0, length, &eof, vtt);
++
++ mutex_unlock(&scst_proc_mutex);
++
++out_free:
++ free_page((unsigned long)buffer);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++int scst_build_proc_dev_handler_dir_entries(struct scst_dev_type *dev_type)
++{
++ int res = 0;
++ struct proc_dir_entry *p;
++ const char *name; /* workaround to keep /proc ABI intact */
++
++ TRACE_ENTRY();
++
++ BUG_ON(dev_type->proc_dev_type_root);
++
++ if (strcmp(dev_type->name, "vdisk_fileio") == 0)
++ name = "vdisk";
++ else
++ name = dev_type->name;
++
++ /* create the proc directory entry for the dev type handler */
++ dev_type->proc_dev_type_root = proc_mkdir(name,
++ scst_proc_scsi_tgt);
++ if (dev_type->proc_dev_type_root == NULL) {
++ PRINT_ERROR("Not enough memory to register dev handler dir "
++ "%s in /proc/%s", name, SCST_PROC_ENTRY_NAME);
++ goto out_nomem;
++ }
++
++ scst_dev_handler_type_proc_data.data = dev_type;
++ if (dev_type->type >= 0) {
++ p = scst_create_proc_entry(dev_type->proc_dev_type_root,
++ SCST_PROC_DEV_HANDLER_TYPE_ENTRY_NAME,
++ &scst_dev_handler_type_proc_data);
++ if (p == NULL) {
++ PRINT_ERROR("Not enough memory to register dev "
++ "handler entry %s in /proc/%s/%s",
++ SCST_PROC_DEV_HANDLER_TYPE_ENTRY_NAME,
++ SCST_PROC_ENTRY_NAME, name);
++ goto out_remove;
++ }
++ }
++
++ if (dev_type->read_proc || dev_type->write_proc) {
++ /* create the proc file entry for the dev type handler */
++ scst_dev_handler_proc_data.data = (void *)dev_type;
++ p = scst_create_proc_entry(dev_type->proc_dev_type_root,
++ name,
++ &scst_dev_handler_proc_data);
++ if (p == NULL) {
++ PRINT_ERROR("Not enough memory to register dev "
++ "handler entry %s in /proc/%s/%s", name,
++ SCST_PROC_ENTRY_NAME, name);
++ goto out_remove1;
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_remove1:
++ if (dev_type->type >= 0)
++ remove_proc_entry(SCST_PROC_DEV_HANDLER_TYPE_ENTRY_NAME,
++ dev_type->proc_dev_type_root);
++
++out_remove:
++ remove_proc_entry(name, scst_proc_scsi_tgt);
++
++out_nomem:
++ res = -ENOMEM;
++ goto out;
++}
++
++void scst_cleanup_proc_dev_handler_dir_entries(struct scst_dev_type *dev_type)
++{
++ /* Workaround to keep /proc ABI intact */
++ const char *name;
++
++ TRACE_ENTRY();
++
++ BUG_ON(dev_type->proc_dev_type_root == NULL);
++
++ if (strcmp(dev_type->name, "vdisk_fileio") == 0)
++ name = "vdisk";
++ else
++ name = dev_type->name;
++
++ if (dev_type->type >= 0) {
++ remove_proc_entry(SCST_PROC_DEV_HANDLER_TYPE_ENTRY_NAME,
++ dev_type->proc_dev_type_root);
++ }
++ if (dev_type->read_proc || dev_type->write_proc)
++ remove_proc_entry(name, dev_type->proc_dev_type_root);
++ remove_proc_entry(name, scst_proc_scsi_tgt);
++ dev_type->proc_dev_type_root = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++static ssize_t scst_proc_scsi_dev_handler_write(struct file *file,
++ const char __user *buf,
++ size_t length, loff_t *off)
++{
++ struct scst_dev_type *dev_type =
++ (struct scst_dev_type *)PDE(file->f_dentry->d_inode)->data;
++ ssize_t res = 0;
++ char *buffer;
++ char *start;
++ int eof = 0;
++
++ TRACE_ENTRY();
++
++ if (dev_type->write_proc == NULL) {
++ res = -ENOSYS;
++ goto out;
++ }
++
++ if (length > SCST_PROC_BLOCK_SIZE) {
++ res = -EOVERFLOW;
++ goto out;
++ }
++ if (!buf) {
++ res = -EINVAL;
++ goto out;
++ }
++
++ buffer = (char *)__get_free_page(GFP_KERNEL);
++ if (!buffer) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ if (copy_from_user(buffer, buf, length)) {
++ res = -EFAULT;
++ goto out_free;
++ }
++ if (length < PAGE_SIZE) {
++ buffer[length] = '\0';
++ } else if (buffer[PAGE_SIZE-1]) {
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ TRACE_BUFFER("Buffer", buffer, length);
++
++ if (mutex_lock_interruptible(&scst_proc_mutex) != 0) {
++ res = -EINTR;
++ goto out_free;
++ }
++
++ res = dev_type->write_proc(buffer, &start, 0, length, &eof, dev_type);
++
++ mutex_unlock(&scst_proc_mutex);
++
++out_free:
++ free_page((unsigned long)buffer);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_proc_scsi_tgt_gen_write(struct file *file,
++ const char __user *buf,
++ size_t length, loff_t *off)
++{
++ int res, rc = 0, action;
++ char *buffer, *p, *pp, *ppp;
++ struct scst_acg *a, *acg = NULL;
++ unsigned int addr_method = SCST_LUN_ADDR_METHOD_PERIPHERAL;
++
++ TRACE_ENTRY();
++
++ if (length > SCST_PROC_BLOCK_SIZE) {
++ res = -EOVERFLOW;
++ goto out;
++ }
++ if (!buf) {
++ res = -EINVAL;
++ goto out;
++ }
++ buffer = (char *)__get_free_page(GFP_KERNEL);
++ if (!buffer) {
++ res = -ENOMEM;
++ goto out;
++ }
++ if (copy_from_user(buffer, buf, length)) {
++ res = -EFAULT;
++ goto out_free;
++ }
++ if (length < PAGE_SIZE) {
++ buffer[length] = '\0';
++ } else if (buffer[PAGE_SIZE-1]) {
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ /*
++ * Usage: echo "add_group GROUP_NAME [FLAT]" >/proc/scsi_tgt/scsi_tgt
++ * or echo "add_group GROUP_NAME [LUN]" >/proc/scsi_tgt/scsi_tgt
++ * or echo "del_group GROUP_NAME" >/proc/scsi_tgt/scsi_tgt
++ * or echo "rename_group OLD_NAME NEW_NAME" >/proc/scsi_tgt/scsi_tgt"
++ * or echo "assign H:C:I:L HANDLER_NAME" >/proc/scsi_tgt/scsi_tgt
++ */
++ p = buffer;
++ if (p[strlen(p) - 1] == '\n')
++ p[strlen(p) - 1] = '\0';
++ if (!strncasecmp("assign ", p, 7)) {
++ p += 7;
++ action = SCST_PROC_ACTION_ASSIGN;
++ } else if (!strncasecmp("add_group ", p, 10)) {
++ p += 10;
++ action = SCST_PROC_ACTION_ADD_GROUP;
++ } else if (!strncasecmp("del_group ", p, 10)) {
++ p += 10;
++ action = SCST_PROC_ACTION_DEL_GROUP;
++ } else if (!strncasecmp("rename_group ", p, 13)) {
++ p += 13;
++ action = SCST_PROC_ACTION_RENAME_GROUP;
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out_free;
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out_free_resume;
++ }
++
++ res = length;
++
++ while (isspace(*p) && *p != '\0')
++ p++;
++
++ switch (action) {
++ case SCST_PROC_ACTION_ADD_GROUP:
++ case SCST_PROC_ACTION_DEL_GROUP:
++ case SCST_PROC_ACTION_RENAME_GROUP:
++ pp = p;
++ while (!isspace(*pp) && *pp != '\0')
++ pp++;
++ if (*pp != '\0') {
++ *pp = '\0';
++ pp++;
++ while (isspace(*pp) && *pp != '\0')
++ pp++;
++ if (*pp != '\0') {
++ switch (action) {
++ case SCST_PROC_ACTION_ADD_GROUP:
++ ppp = pp;
++ while (!isspace(*ppp) && *ppp != '\0')
++ ppp++;
++ if (*ppp != '\0') {
++ *ppp = '\0';
++ ppp++;
++ while (isspace(*ppp) && *ppp != '\0')
++ ppp++;
++ if (*ppp != '\0') {
++ PRINT_ERROR("%s", "Too many "
++ "arguments");
++ res = -EINVAL;
++ goto out_up_free;
++ }
++ }
++ if (strcasecmp(pp, "FLAT") == 0)
++ addr_method = SCST_LUN_ADDR_METHOD_FLAT;
++ else if (strcasecmp(pp, "LUN") == 0)
++ addr_method = SCST_LUN_ADDR_METHOD_LUN;
++ else {
++ PRINT_ERROR("Unexpected "
++ "argument %s", pp);
++ res = -EINVAL;
++ goto out_up_free;
++ }
++ break;
++ case SCST_PROC_ACTION_DEL_GROUP:
++ PRINT_ERROR("%s", "Too many "
++ "arguments");
++ res = -EINVAL;
++ goto out_up_free;
++ }
++ }
++ }
++
++ if (strcmp(p, SCST_DEFAULT_ACG_NAME) == 0) {
++ PRINT_ERROR("Attempt to add/delete/rename predefined "
++ "group \"%s\"", p);
++ res = -EINVAL;
++ goto out_up_free;
++ }
++
++ list_for_each_entry(a, &scst_acg_list, acg_list_entry) {
++ if (strcmp(a->acg_name, p) == 0) {
++ TRACE_DBG("group (acg) %p %s found",
++ a, a->acg_name);
++ acg = a;
++ break;
++ }
++ }
++
++ switch (action) {
++ case SCST_PROC_ACTION_ADD_GROUP:
++ if (acg) {
++ PRINT_ERROR("acg name %s exist", p);
++ res = -EINVAL;
++ goto out_up_free;
++ }
++ rc = scst_proc_group_add(p, addr_method);
++ break;
++ case SCST_PROC_ACTION_DEL_GROUP:
++ if (acg == NULL) {
++ PRINT_ERROR("acg name %s not found", p);
++ res = -EINVAL;
++ goto out_up_free;
++ }
++ rc = scst_proc_del_free_acg(acg, 1);
++ break;
++ case SCST_PROC_ACTION_RENAME_GROUP:
++ if (acg == NULL) {
++ PRINT_ERROR("acg name %s not found", p);
++ res = -EINVAL;
++ goto out_up_free;
++ }
++
++ p = pp;
++ while (!isspace(*pp) && *pp != '\0')
++ pp++;
++ if (*pp != '\0') {
++ *pp = '\0';
++ pp++;
++ while (isspace(*pp) && *pp != '\0')
++ pp++;
++ if (*pp != '\0') {
++ PRINT_ERROR("%s", "Too many arguments");
++ res = -EINVAL;
++ goto out_up_free;
++ }
++ }
++ rc = scst_proc_rename_acg(acg, p);
++ break;
++ }
++ break;
++ case SCST_PROC_ACTION_ASSIGN:
++ rc = scst_proc_assign_handler(p);
++ break;
++ }
++
++ if (rc != 0)
++ res = rc;
++
++out_up_free:
++ mutex_unlock(&scst_mutex);
++
++out_free_resume:
++ scst_resume_activity();
++
++out_free:
++ free_page((unsigned long)buffer);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* The activity supposed to be suspended and scst_mutex held */
++static int scst_proc_assign_handler(char *buf)
++{
++ int res = 0;
++ char *p = buf, *e, *ee;
++ unsigned long host, channel = 0, id = 0, lun = 0;
++ struct scst_device *d, *dev = NULL;
++ struct scst_dev_type *dt, *handler = NULL;
++
++ TRACE_ENTRY();
++
++ while (isspace(*p) && *p != '\0')
++ p++;
++
++ host = simple_strtoul(p, &p, 0);
++ if ((host == ULONG_MAX) || (*p != ':'))
++ goto out_synt_err;
++ p++;
++ channel = simple_strtoul(p, &p, 0);
++ if ((channel == ULONG_MAX) || (*p != ':'))
++ goto out_synt_err;
++ p++;
++ id = simple_strtoul(p, &p, 0);
++ if ((channel == ULONG_MAX) || (*p != ':'))
++ goto out_synt_err;
++ p++;
++ lun = simple_strtoul(p, &p, 0);
++ if (lun == ULONG_MAX)
++ goto out_synt_err;
++
++ e = p;
++ e++;
++ while (isspace(*e) && *e != '\0')
++ e++;
++ ee = e;
++ while (!isspace(*ee) && *ee != '\0')
++ ee++;
++ *ee = '\0';
++
++ TRACE_DBG("Dev %ld:%ld:%ld:%ld, handler %s", host, channel, id, lun, e);
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if ((d->virt_id == 0) &&
++ d->scsi_dev->host->host_no == host &&
++ d->scsi_dev->channel == channel &&
++ d->scsi_dev->id == id &&
++ d->scsi_dev->lun == lun) {
++ dev = d;
++ TRACE_DBG("Dev %p (%ld:%ld:%ld:%ld) found",
++ dev, host, channel, id, lun);
++ break;
++ }
++ }
++
++ if (dev == NULL) {
++ PRINT_ERROR("Device %ld:%ld:%ld:%ld not found",
++ host, channel, id, lun);
++ res = -EINVAL;
++ goto out;
++ }
++
++ list_for_each_entry(dt, &scst_dev_type_list, dev_type_list_entry) {
++ if (!strcmp(dt->name, e)) {
++ handler = dt;
++ TRACE_DBG("Dev handler %p with name %s found",
++ dt, dt->name);
++ break;
++ }
++ }
++
++ if (handler == NULL) {
++ PRINT_ERROR("Handler %s not found", e);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (dev->scsi_dev->type != handler->type) {
++ PRINT_ERROR("Type %d of device %s differs from type "
++ "%d of dev handler %s", dev->type,
++ dev->handler->name, handler->type, handler->name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = scst_assign_dev_handler(dev, handler);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_synt_err:
++ PRINT_ERROR("Syntax error on %s", p);
++ res = -EINVAL;
++ goto out;
++}
++
++static ssize_t scst_proc_groups_devices_write(struct file *file,
++ const char __user *buf,
++ size_t length, loff_t *off)
++{
++ int res, action, rc, read_only = 0;
++ char *buffer, *p, *e = NULL;
++ unsigned int virt_lun;
++ struct scst_acg *acg =
++ (struct scst_acg *)PDE(file->f_dentry->d_inode)->data;
++ struct scst_acg_dev *acg_dev = NULL, *acg_dev_tmp;
++ struct scst_device *d, *dev = NULL;
++
++ TRACE_ENTRY();
++
++ if (length > SCST_PROC_BLOCK_SIZE) {
++ res = -EOVERFLOW;
++ goto out;
++ }
++ if (!buf) {
++ res = -EINVAL;
++ goto out;
++ }
++ buffer = (char *)__get_free_page(GFP_KERNEL);
++ if (!buffer) {
++ res = -ENOMEM;
++ goto out;
++ }
++ if (copy_from_user(buffer, buf, length)) {
++ res = -EFAULT;
++ goto out_free;
++ }
++ if (length < PAGE_SIZE) {
++ buffer[length] = '\0';
++ } else if (buffer[PAGE_SIZE-1]) {
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ /*
++ * Usage: echo "add|del H:C:I:L lun [READ_ONLY]" \
++ * >/proc/scsi_tgt/groups/GROUP_NAME/devices
++ * or echo "replace H:C:I:L lun [READ_ONLY]" \
++ * >/proc/scsi_tgt/groups/GROUP_NAME/devices
++ * or echo "add|del V_NAME lun [READ_ONLY]" \
++ * >/proc/scsi_tgt/groups/GROUP_NAME/devices
++ * or echo "replace V_NAME lun [READ_ONLY]" \
++ * >/proc/scsi_tgt/groups/GROUP_NAME/devices
++ * or echo "clear" >/proc/scsi_tgt/groups/GROUP_NAME/devices
++ */
++ p = buffer;
++ if (p[strlen(p) - 1] == '\n')
++ p[strlen(p) - 1] = '\0';
++ if (!strncasecmp("clear", p, 5)) {
++ action = SCST_PROC_ACTION_CLEAR;
++ } else if (!strncasecmp("add ", p, 4)) {
++ p += 4;
++ action = SCST_PROC_ACTION_ADD;
++ } else if (!strncasecmp("del ", p, 4)) {
++ p += 4;
++ action = SCST_PROC_ACTION_DEL;
++ } else if (!strncasecmp("replace ", p, 8)) {
++ p += 8;
++ action = SCST_PROC_ACTION_REPLACE;
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ res = scst_suspend_activity(true);
++ if (res != 0)
++ goto out_free;
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out_free_resume;
++ }
++
++ res = length;
++
++ switch (action) {
++ case SCST_PROC_ACTION_ADD:
++ case SCST_PROC_ACTION_DEL:
++ case SCST_PROC_ACTION_REPLACE:
++ while (isspace(*p) && *p != '\0')
++ p++;
++ e = p; /* save p */
++ while (!isspace(*e) && *e != '\0')
++ e++;
++ *e = 0;
++
++ list_for_each_entry(d, &scst_dev_list, dev_list_entry) {
++ if (!strcmp(d->virt_name, p)) {
++ dev = d;
++ TRACE_DBG("Device %p (%s) found", dev, p);
++ break;
++ }
++ }
++ if (dev == NULL) {
++ PRINT_ERROR("Device %s not found", p);
++ res = -EINVAL;
++ goto out_free_up;
++ }
++ break;
++ }
++
++ /* ToDo: create separate functions */
++
++ switch (action) {
++ case SCST_PROC_ACTION_ADD:
++ case SCST_PROC_ACTION_REPLACE:
++ {
++ bool dev_replaced = false;
++
++ e++;
++ while (isspace(*e) && *e != '\0')
++ e++;
++
++ virt_lun = simple_strtoul(e, &e, 0);
++ if (virt_lun > SCST_MAX_LUN) {
++ PRINT_ERROR("Too big LUN %d (max %d)", virt_lun,
++ SCST_MAX_LUN);
++ res = -EINVAL;
++ goto out_free_up;
++ }
++
++ while (isspace(*e) && *e != '\0')
++ e++;
++
++ if (*e != '\0') {
++ if (!strncasecmp("READ_ONLY", e, 9))
++ read_only = 1;
++ else {
++ PRINT_ERROR("Unknown option \"%s\"", e);
++ res = -EINVAL;
++ goto out_free_up;
++ }
++ }
++
++ list_for_each_entry(acg_dev_tmp, &acg->acg_dev_list,
++ acg_dev_list_entry) {
++ if (acg_dev_tmp->lun == virt_lun) {
++ acg_dev = acg_dev_tmp;
++ break;
++ }
++ }
++ if (acg_dev != NULL) {
++ if (action == SCST_PROC_ACTION_ADD) {
++ PRINT_ERROR("virt lun %d already exists in "
++ "group %s", virt_lun, acg->acg_name);
++ res = -EEXIST;
++ goto out_free_up;
++ } else {
++ /* Replace */
++ rc = scst_acg_del_lun(acg, acg_dev->lun,
++ false);
++ if (rc) {
++ res = rc;
++ goto out_free_up;
++ }
++ dev_replaced = true;
++ }
++ }
++
++ rc = scst_acg_add_lun(acg, NULL, dev, virt_lun, read_only,
++ false, NULL);
++ if (rc) {
++ res = rc;
++ goto out_free_up;
++ }
++
++ if (action == SCST_PROC_ACTION_ADD)
++ scst_report_luns_changed(acg);
++
++ if (dev_replaced) {
++ struct scst_tgt_dev *tgt_dev;
++
++ list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list,
++ dev_tgt_dev_list_entry) {
++ if ((tgt_dev->acg_dev->acg == acg) &&
++ (tgt_dev->lun == virt_lun)) {
++ TRACE_MGMT_DBG("INQUIRY DATA HAS CHANGED"
++ " on tgt_dev %p", tgt_dev);
++ scst_gen_aen_or_ua(tgt_dev,
++ SCST_LOAD_SENSE(scst_sense_inquery_data_changed));
++ }
++ }
++ }
++ break;
++ }
++ case SCST_PROC_ACTION_DEL:
++ {
++ /*
++ * This code doesn't handle if there are >1 LUNs for the same
++ * device in the group. Instead, it always deletes the first
++ * entry. It wasn't fixed for compatibility reasons, because
++ * procfs is now obsoleted.
++ */
++ struct scst_acg_dev *a;
++ list_for_each_entry(a, &acg->acg_dev_list, acg_dev_list_entry) {
++ if (a->dev == dev) {
++ rc = scst_acg_del_lun(acg, a->lun, true);
++ if (rc)
++ res = rc;
++ goto out_free_up;
++ }
++ }
++ PRINT_ERROR("Device is not found in group %s", acg->acg_name);
++ break;
++ }
++ case SCST_PROC_ACTION_CLEAR:
++ list_for_each_entry_safe(acg_dev, acg_dev_tmp,
++ &acg->acg_dev_list,
++ acg_dev_list_entry) {
++ rc = scst_acg_del_lun(acg, acg_dev->lun,
++ list_is_last(&acg_dev->acg_dev_list_entry,
++ &acg->acg_dev_list));
++ if (rc) {
++ res = rc;
++ goto out_free_up;
++ }
++ }
++ break;
++ }
++
++out_free_up:
++ mutex_unlock(&scst_mutex);
++
++out_free_resume:
++ scst_resume_activity();
++
++out_free:
++ free_page((unsigned long)buffer);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_proc_groups_names_write(struct file *file,
++ const char __user *buf,
++ size_t length, loff_t *off)
++{
++ int res = length, rc = 0, action;
++ char *buffer, *p, *pp = NULL;
++ struct scst_acg *acg =
++ (struct scst_acg *)PDE(file->f_dentry->d_inode)->data;
++ struct scst_acn *n, *nn;
++
++ TRACE_ENTRY();
++
++ if (length > SCST_PROC_BLOCK_SIZE) {
++ res = -EOVERFLOW;
++ goto out;
++ }
++ if (!buf) {
++ res = -EINVAL;
++ goto out;
++ }
++ buffer = (char *)__get_free_page(GFP_KERNEL);
++ if (!buffer) {
++ res = -ENOMEM;
++ goto out;
++ }
++ if (copy_from_user(buffer, buf, length)) {
++ res = -EFAULT;
++ goto out_free;
++ }
++ if (length < PAGE_SIZE) {
++ buffer[length] = '\0';
++ } else if (buffer[PAGE_SIZE-1]) {
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ /*
++ * Usage: echo "add|del NAME" >/proc/scsi_tgt/groups/GROUP_NAME/names
++ * or echo "move NAME NEW_GROUP_NAME" >/proc/scsi_tgt/groups/OLD_GROUP_NAME/names"
++ * or echo "clear" >/proc/scsi_tgt/groups/GROUP_NAME/names
++ */
++ p = buffer;
++ if (p[strlen(p) - 1] == '\n')
++ p[strlen(p) - 1] = '\0';
++ if (!strncasecmp("clear", p, 5)) {
++ action = SCST_PROC_ACTION_CLEAR;
++ } else if (!strncasecmp("add ", p, 4)) {
++ p += 4;
++ action = SCST_PROC_ACTION_ADD;
++ } else if (!strncasecmp("del ", p, 4)) {
++ p += 4;
++ action = SCST_PROC_ACTION_DEL;
++ } else if (!strncasecmp("move ", p, 5)) {
++ p += 5;
++ action = SCST_PROC_ACTION_MOVE;
++ } else {
++ PRINT_ERROR("Unknown action \"%s\"", p);
++ res = -EINVAL;
++ goto out_free;
++ }
++
++ switch (action) {
++ case SCST_PROC_ACTION_ADD:
++ case SCST_PROC_ACTION_DEL:
++ case SCST_PROC_ACTION_MOVE:
++ while (isspace(*p) && *p != '\0')
++ p++;
++ pp = p;
++ while (!isspace(*pp) && *pp != '\0')
++ pp++;
++ if (*pp != '\0') {
++ *pp = '\0';
++ pp++;
++ while (isspace(*pp) && *pp != '\0')
++ pp++;
++ if (*pp != '\0') {
++ switch (action) {
++ case SCST_PROC_ACTION_ADD:
++ case SCST_PROC_ACTION_DEL:
++ PRINT_ERROR("%s", "Too many "
++ "arguments");
++ res = -EINVAL;
++ goto out_free;
++ }
++ }
++ }
++ break;
++ }
++
++ rc = scst_suspend_activity(true);
++ if (rc != 0)
++ goto out_free;
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out_free_resume;
++ }
++
++ switch (action) {
++ case SCST_PROC_ACTION_ADD:
++ rc = scst_acg_add_acn(acg, p);
++ break;
++ case SCST_PROC_ACTION_DEL:
++ rc = scst_acg_remove_name(acg, p, true);
++ break;
++ case SCST_PROC_ACTION_MOVE:
++ {
++ struct scst_acg *a, *new_acg = NULL;
++ char *name = p;
++ p = pp;
++ while (!isspace(*pp) && *pp != '\0')
++ pp++;
++ if (*pp != '\0') {
++ *pp = '\0';
++ pp++;
++ while (isspace(*pp) && *pp != '\0')
++ pp++;
++ if (*pp != '\0') {
++ PRINT_ERROR("%s", "Too many arguments");
++ res = -EINVAL;
++ goto out_free_unlock;
++ }
++ }
++ list_for_each_entry(a, &scst_acg_list, acg_list_entry) {
++ if (strcmp(a->acg_name, p) == 0) {
++ TRACE_DBG("group (acg) %p %s found",
++ a, a->acg_name);
++ new_acg = a;
++ break;
++ }
++ }
++ if (new_acg == NULL) {
++ PRINT_ERROR("Group %s not found", p);
++ res = -EINVAL;
++ goto out_free_unlock;
++ }
++ rc = scst_acg_remove_name(acg, name, false);
++ if (rc != 0)
++ goto out_free_unlock;
++ rc = scst_acg_add_acn(new_acg, name);
++ if (rc != 0)
++ scst_acg_add_acn(acg, name);
++ break;
++ }
++ case SCST_PROC_ACTION_CLEAR:
++ list_for_each_entry_safe(n, nn, &acg->acn_list,
++ acn_list_entry) {
++ scst_del_free_acn(n, false);
++ }
++ scst_check_reassign_sessions();
++ break;
++ }
++
++out_free_unlock:
++ mutex_unlock(&scst_mutex);
++
++out_free_resume:
++ scst_resume_activity();
++
++out_free:
++ free_page((unsigned long)buffer);
++
++out:
++ if (rc < 0)
++ res = rc;
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_version_info_show(struct seq_file *seq, void *v)
++{
++ TRACE_ENTRY();
++
++ seq_printf(seq, "%s\n", SCST_VERSION_STRING);
++
++#ifdef CONFIG_SCST_STRICT_SERIALIZING
++ seq_printf(seq, "STRICT_SERIALIZING\n");
++#endif
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ seq_printf(seq, "EXTRACHECKS\n");
++#endif
++
++#ifdef CONFIG_SCST_TRACING
++ seq_printf(seq, "TRACING\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++ seq_printf(seq, "DEBUG\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_TM
++ seq_printf(seq, "DEBUG_TM\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_RETRY
++ seq_printf(seq, "DEBUG_RETRY\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_OOM
++ seq_printf(seq, "DEBUG_OOM\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG_SN
++ seq_printf(seq, "DEBUG_SN\n");
++#endif
++
++#ifdef CONFIG_SCST_USE_EXPECTED_VALUES
++ seq_printf(seq, "USE_EXPECTED_VALUES\n");
++#endif
++
++#ifdef CONFIG_SCST_TEST_IO_IN_SIRQ
++ seq_printf(seq, "TEST_IO_IN_SIRQ\n");
++#endif
++
++#ifdef CONFIG_SCST_STRICT_SECURITY
++ seq_printf(seq, "STRICT_SECURITY\n");
++#endif
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static struct scst_proc_data scst_version_proc_data = {
++ SCST_DEF_RW_SEQ_OP(NULL)
++ .show = scst_version_info_show,
++};
++
++static int scst_help_info_show(struct seq_file *seq, void *v)
++{
++ TRACE_ENTRY();
++
++ seq_printf(seq, "%s\n", scst_proc_help_string);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static struct scst_proc_data scst_help_proc_data = {
++ SCST_DEF_RW_SEQ_OP(NULL)
++ .show = scst_help_info_show,
++};
++
++static int scst_dev_handler_type_info_show(struct seq_file *seq, void *v)
++{
++ struct scst_dev_type *dev_type = (struct scst_dev_type *)seq->private;
++
++ TRACE_ENTRY();
++
++ seq_printf(seq, "%d - %s\n", dev_type->type,
++ dev_type->type > (int)ARRAY_SIZE(scst_proc_dev_handler_type)
++ ? "unknown" : scst_proc_dev_handler_type[dev_type->type]);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static struct scst_proc_data scst_dev_handler_type_proc_data = {
++ SCST_DEF_RW_SEQ_OP(NULL)
++ .show = scst_dev_handler_type_info_show,
++};
++
++static int scst_sessions_info_show(struct seq_file *seq, void *v)
++{
++ int res = 0;
++ struct scst_acg *acg;
++ struct scst_session *sess;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ seq_printf(seq, "%-20s %-45s %-35s %-15s\n",
++ "Target name", "Initiator name",
++ "Group name", "Active/All Commands Count");
++
++ list_for_each_entry(acg, &scst_acg_list, acg_list_entry) {
++ list_for_each_entry(sess, &acg->acg_sess_list,
++ acg_sess_list_entry) {
++ int active_cmds = 0, t;
++ for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) {
++ struct list_head *head =
++ &sess->sess_tgt_dev_list[t];
++ struct scst_tgt_dev *tgt_dev;
++ list_for_each_entry(tgt_dev, head,
++ sess_tgt_dev_list_entry) {
++ active_cmds += atomic_read(&tgt_dev->tgt_dev_cmd_count);
++ }
++ }
++ seq_printf(seq, "%-20s %-45s %-35s %d/%d\n",
++ sess->tgt->tgtt->name,
++ sess->initiator_name,
++ acg->acg_name, active_cmds,
++ atomic_read(&sess->sess_cmd_count));
++ }
++ }
++
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_proc_data scst_sessions_proc_data = {
++ SCST_DEF_RW_SEQ_OP(NULL)
++ .show = scst_sessions_info_show,
++};
++
++static struct scst_proc_data scst_sgv_proc_data = {
++ SCST_DEF_RW_SEQ_OP(NULL)
++ .show = sgv_procinfo_show,
++};
++
++static int scst_groups_names_show(struct seq_file *seq, void *v)
++{
++ int res = 0;
++ struct scst_acg *acg = (struct scst_acg *)seq->private;
++ struct scst_acn *name;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ list_for_each_entry(name, &acg->acn_list, acn_list_entry) {
++ seq_printf(seq, "%s\n", name->name);
++ }
++
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_proc_data scst_groups_names_proc_data = {
++ SCST_DEF_RW_SEQ_OP(scst_proc_groups_names_write)
++ .show = scst_groups_names_show,
++};
++
++static int scst_groups_addr_method_show(struct seq_file *seq, void *v)
++{
++ int res = 0;
++ struct scst_acg *acg = (struct scst_acg *)seq->private;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ switch (acg->addr_method) {
++ case SCST_LUN_ADDR_METHOD_FLAT:
++ seq_printf(seq, "%s\n", "FLAT");
++ break;
++ case SCST_LUN_ADDR_METHOD_PERIPHERAL:
++ seq_printf(seq, "%s\n", "PERIPHERAL");
++ break;
++ case SCST_LUN_ADDR_METHOD_LUN:
++ seq_printf(seq, "%s\n", "LUN");
++ break;
++ default:
++ seq_printf(seq, "%s\n", "UNKNOWN");
++ break;
++ }
++
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++static struct scst_proc_data scst_groups_addr_method_proc_data = {
++ SCST_DEF_RW_SEQ_OP(NULL)
++ .show = scst_groups_addr_method_show,
++};
++static int scst_groups_devices_show(struct seq_file *seq, void *v)
++{
++ int res = 0;
++ struct scst_acg *acg = (struct scst_acg *)seq->private;
++ struct scst_acg_dev *acg_dev;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ seq_printf(seq, "%-60s%-13s%s\n", "Device (host:ch:id:lun or name)",
++ "LUN", "Options");
++
++ list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) {
++ seq_printf(seq, "%-60s%-13lld%s\n",
++ acg_dev->dev->virt_name,
++ (long long unsigned int)acg_dev->lun,
++ acg_dev->rd_only ? "RO" : "");
++ }
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_proc_data scst_groups_devices_proc_data = {
++ SCST_DEF_RW_SEQ_OP(scst_proc_groups_devices_write)
++ .show = scst_groups_devices_show,
++};
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++static int scst_proc_read_tbl(const struct scst_trace_log *tbl,
++ struct seq_file *seq,
++ unsigned long log_level, int *first)
++{
++ const struct scst_trace_log *t = tbl;
++ int res = 0;
++
++ while (t->token) {
++ if (log_level & t->val) {
++ seq_printf(seq, "%s%s", *first ? "" : " | ", t->token);
++ *first = 0;
++ }
++ t++;
++ }
++ return res;
++}
++
++int scst_proc_log_entry_read(struct seq_file *seq, unsigned long log_level,
++ const struct scst_trace_log *tbl)
++{
++ int res = 0, first = 1;
++
++ TRACE_ENTRY();
++
++ scst_proc_read_tbl(scst_proc_trace_tbl, seq, log_level, &first);
++
++ if (tbl)
++ scst_proc_read_tbl(tbl, seq, log_level, &first);
++
++ seq_printf(seq, "%s\n", first ? "none" : "");
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++EXPORT_SYMBOL_GPL(scst_proc_log_entry_read);
++
++static int log_info_show(struct seq_file *seq, void *v)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_log_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ res = scst_proc_log_entry_read(seq, trace_flag,
++ scst_proc_local_trace_tbl);
++
++ mutex_unlock(&scst_log_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_proc_data scst_log_proc_data = {
++ SCST_DEF_RW_SEQ_OP(scst_proc_scsi_tgt_gen_write_log)
++ .show = log_info_show,
++ .data = "scsi_tgt",
++};
++
++#endif
++
++static int scst_tgt_info_show(struct seq_file *seq, void *v)
++{
++ int res = 0;
++ struct scst_device *dev;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ seq_printf(seq, "%-60s%s\n", "Device (host:ch:id:lun or name)",
++ "Device handler");
++ list_for_each_entry(dev, &scst_dev_list, dev_list_entry) {
++ seq_printf(seq, "%-60s%s\n",
++ dev->virt_name, dev->handler->name);
++ }
++
++ mutex_unlock(&scst_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_proc_data scst_tgt_proc_data = {
++ SCST_DEF_RW_SEQ_OP(scst_proc_scsi_tgt_gen_write)
++ .show = scst_tgt_info_show,
++};
++
++static int scst_threads_info_show(struct seq_file *seq, void *v)
++{
++ TRACE_ENTRY();
++
++ seq_printf(seq, "%d\n", scst_main_cmd_threads.nr_threads);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static struct scst_proc_data scst_threads_proc_data = {
++ SCST_DEF_RW_SEQ_OP(scst_proc_threads_write)
++ .show = scst_threads_info_show,
++};
++
++static int scst_scsi_tgtinfo_show(struct seq_file *seq, void *v)
++{
++ struct scst_tgt *vtt = seq->private;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_proc_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ if (vtt->tgtt->read_proc)
++ res = vtt->tgtt->read_proc(seq, vtt);
++
++ mutex_unlock(&scst_proc_mutex);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_proc_data scst_scsi_tgt_proc_data = {
++ SCST_DEF_RW_SEQ_OP(scst_proc_scsi_tgt_write)
++ .show = scst_scsi_tgtinfo_show,
++};
++
++static int scst_dev_handler_info_show(struct seq_file *seq, void *v)
++{
++ struct scst_dev_type *dev_type = seq->private;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (mutex_lock_interruptible(&scst_proc_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ if (dev_type->read_proc)
++ res = dev_type->read_proc(seq, dev_type);
++
++ mutex_unlock(&scst_proc_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct scst_proc_data scst_dev_handler_proc_data = {
++ SCST_DEF_RW_SEQ_OP(scst_proc_scsi_dev_handler_write)
++ .show = scst_dev_handler_info_show,
++};
++
++struct proc_dir_entry *scst_create_proc_entry(struct proc_dir_entry *root,
++ const char *name, struct scst_proc_data *pdata)
++{
++ struct proc_dir_entry *p = NULL;
++
++ TRACE_ENTRY();
++
++ if (root) {
++ mode_t mode;
++
++ mode = S_IFREG | S_IRUGO | (pdata->seq_op.write ? S_IWUSR : 0);
++ p = create_proc_entry(name, mode, root);
++ if (p == NULL) {
++ PRINT_ERROR("Fail to create entry %s in /proc", name);
++ } else {
++ p->proc_fops = &pdata->seq_op;
++ p->data = pdata->data;
++ }
++ }
++
++ TRACE_EXIT();
++ return p;
++}
++EXPORT_SYMBOL_GPL(scst_create_proc_entry);
++
++int scst_single_seq_open(struct inode *inode, struct file *file)
++{
++ struct scst_proc_data *pdata = container_of(PDE(inode)->proc_fops,
++ struct scst_proc_data, seq_op);
++ return single_open(file, pdata->show, PDE(inode)->data);
++}
++EXPORT_SYMBOL_GPL(scst_single_seq_open);
++
++struct proc_dir_entry *scst_proc_get_tgt_root(
++ struct scst_tgt_template *vtt)
++{
++ return vtt->proc_tgt_root;
++}
++EXPORT_SYMBOL_GPL(scst_proc_get_tgt_root);
++
++struct proc_dir_entry *scst_proc_get_dev_type_root(
++ struct scst_dev_type *dtt)
++{
++ return dtt->proc_dev_type_root;
++}
++EXPORT_SYMBOL_GPL(scst_proc_get_dev_type_root);
+diff -uprN orig/linux-3.2/Documentation/scst/README.scst linux-3.2/Documentation/scst/README.scst
+--- orig/linux-3.2/Documentation/scst/README.scst
++++ linux-3.2/Documentation/scst/README.scst
+@@ -0,0 +1,1535 @@
++Generic SCSI target mid-level for Linux (SCST)
++==============================================
++
++SCST is designed to provide unified, consistent interface between SCSI
++target drivers and Linux kernel and simplify target drivers development
++as much as possible. Detail description of SCST's features and internals
++could be found on its Internet page http://scst.sourceforge.net.
++
++SCST supports the following I/O modes:
++
++ * Pass-through mode with one to many relationship, i.e. when multiple
++ initiators can connect to the exported pass-through devices, for
++ the following SCSI devices types: disks (type 0), tapes (type 1),
++ processors (type 3), CDROMs (type 5), MO disks (type 7), medium
++ changers (type 8) and RAID controllers (type 0xC).
++
++ * FILEIO mode, which allows to use files on file systems or block
++ devices as virtual remotely available SCSI disks or CDROMs with
++ benefits of the Linux page cache.
++
++ * BLOCKIO mode, which performs direct block IO with a block device,
++ bypassing page-cache for all operations. This mode works ideally with
++ high-end storage HBAs and for applications that either do not need
++ caching between application and disk or need the large block
++ throughput.
++
++ * "Performance" device handlers, which provide in pseudo pass-through
++ mode a way for direct performance measurements without overhead of
++ actual data transferring from/to underlying SCSI device.
++
++In addition, SCST supports advanced per-initiator access and devices
++visibility management, so different initiators could see different set
++of devices with different access permissions. See below for details.
++
++Full list of SCST features and comparison with other Linux targets you
++can find on http://scst.sourceforge.net/comparison.html.
++
++
++Installation
++------------
++
++To see your devices remotely, you need to add a corresponding LUN for
++them (see below how). By default, no local devices are seen remotely.
++There must be LUN 0 in each LUNs set (security group), i.e. LUs
++numeration must not start from, e.g., 1. Otherwise you will see no
++devices on remote initiators and SCST core will write into the kernel
++log message: "tgt_dev for LUN 0 not found, command to unexisting LU?"
++
++It is highly recommended to use scstadmin utility for configuring
++devices and security groups.
++
++The flow of SCST inialization should be as the following:
++
++1. Load of SCST modules with necessary module parameters, if needed.
++
++2. Configure targets, devices, LUNs, etc. using either scstadmin
++(recommended), or the sysfs interface directly as described below.
++
++If you experience problems during modules load or running, check your
++kernel logs (or run dmesg command for the few most recent messages).
++
++IMPORTANT: Without loading appropriate device handler, corresponding devices
++========= will be invisible for remote initiators, which could lead to holes
++ in the LUN addressing, so automatic device scanning by remote SCSI
++ mid-level could not notice the devices. Therefore you will have
++ to add them manually via
++ 'echo "- - -" >/sys/class/scsi_host/hostX/scan',
++ where X - is the host number.
++
++IMPORTANT: Working of target and initiator on the same host is
++========= supported, except the following 2 cases: swap over target exported
++ device and using a writable mmap over a file from target
++ exported device. The latter means you can't mount a file
++ system over target exported device. In other words, you can
++ freely use any sg, sd, st, etc. devices imported from target
++ on the same host, but you can't mount file systems or put
++ swap on them. This is a limitation of Linux memory/cache
++ manager, because in this case a memory allocation deadlock is
++ possible like: system needs some memory -> it decides to
++ clear some cache -> the cache is needed to be written on a
++ target exported device -> initiator sends request to the
++ target located on the same system -> the target needs memory
++ -> the system needs even more memory -> deadlock.
++
++IMPORTANT: In the current version simultaneous access to local SCSI devices
++========= via standard high-level SCSI drivers (sd, st, sg, etc.) and
++ SCST's target drivers is unsupported. Especially it is
++ important for execution via sg and st commands that change
++ the state of devices and their parameters, because that could
++ lead to data corruption. If any such command is done, at
++ least related device handler(s) must be restarted. For block
++ devices READ/WRITE commands using direct disk handler are
++ generally safe.
++
++
++Usage in failover mode
++----------------------
++
++It is recommended to use TEST UNIT READY ("tur") command to check if
++SCST target is alive in MPIO configurations.
++
++
++Device handlers
++---------------
++
++Device specific drivers (device handlers) are plugins for SCST, which
++help SCST to analyze incoming requests and determine parameters,
++specific to various types of devices. If an appropriate device handler
++for a SCSI device type isn't loaded, SCST doesn't know how to handle
++devices of this type, so they will be invisible for remote initiators
++(more precisely, "LUN not supported" sense code will be returned).
++
++In addition to device handlers for real devices, there are VDISK, user
++space and "performance" device handlers.
++
++VDISK device handler works over files on file systems and makes from
++them virtual remotely available SCSI disks or CDROM's. In addition, it
++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 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
++addressed in one of the future releases. Virtual CDROM's are useful for
++remote installation. See below for details how to setup and use VDISK
++device handler.
++
++"Performance" device handlers for disks, MO disks and tapes in their
++exec() method skip (pretend to execute) all READ and WRITE operations
++and thus provide a way for direct link performance measurements without
++overhead of actual data transferring from/to underlying SCSI device.
++
++NOTE: Since "perf" device handlers on READ operations don't touch the
++==== commands' data buffer, it is returned to remote initiators as it
++ was allocated, without even being zeroed. Thus, "perf" device
++ handlers impose some security risk, so use them with caution.
++
++
++Compilation options
++-------------------
++
++There are the following compilation options, that could be change using
++your favorite kernel configuration Makefile target, e.g. "make xconfig":
++
++ - CONFIG_SCST_DEBUG - if defined, turns on some debugging code,
++ including some logging. Makes the driver considerably bigger and slower,
++ producing large amount of log data.
++
++ - CONFIG_SCST_TRACING - if defined, turns on ability to log events. Makes the
++ driver considerably bigger and leads to some performance loss.
++
++ - CONFIG_SCST_EXTRACHECKS - if defined, adds extra validity checks in
++ the various places.
++
++ - CONFIG_SCST_USE_EXPECTED_VALUES - if not defined (default), initiator
++ supplied expected data transfer length and direction will be used
++ only for verification purposes to return error or warn in case if one
++ of them is invalid. Instead, locally decoded from SCSI command values
++ will be used. This is necessary for security reasons, because
++ otherwise a faulty initiator can crash target by supplying invalid
++ value in one of those parameters. This is especially important in
++ case of pass-through mode. If CONFIG_SCST_USE_EXPECTED_VALUES is
++ defined, initiator supplied expected data transfer length and
++ direction will override the locally decoded values. This might be
++ necessary if internal SCST commands translation table doesn't contain
++ SCSI command, which is used in your environment. You can know that if
++ you enable "minor" trace level and have messages like "Unknown
++ opcode XX for YY. Should you update scst_scsi_op_table?" in your
++ kernel log and your initiator returns an error. Also report those
++ messages in the SCST mailing list scst-devel@lists.sourceforge.net.
++ Note, that not all SCSI transports support supplying expected values.
++ You should try to enable this option if you have a not working with
++ SCST pass-through device, for instance, an SATA CDROM.
++
++ - CONFIG_SCST_DEBUG_TM - if defined, turns on task management functions
++ debugging, when on LUN 6 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 define
++ CONFIG_SCST_TM_DBG_GO_OFFLINE symbol in the Makefile if you want that
++ the device eventually become completely unresponsive, or otherwise to
++ circle around ABORTs and RESETs code. Needs CONFIG_SCST_DEBUG turned
++ on.
++
++ - CONFIG_SCST_STRICT_SERIALIZING - if defined, makes SCST send all commands to
++ underlying SCSI device synchronously, one after one. This makes task
++ management more reliable, with cost of some performance penalty. This
++ is mostly actual for stateful SCSI devices like tapes, where the
++ result of command's execution depends from device's settings defined
++ by previous commands. Disk and RAID devices are stateless in the most
++ cases. The current SCSI core in Linux doesn't allow to abort all
++ commands reliably if they sent asynchronously to a stateful device.
++ Turned off by default, turn it on if you use stateful device(s) and
++ need as much error recovery reliability as possible. As a side effect
++ of CONFIG_SCST_STRICT_SERIALIZING, on kernels below 2.6.30 no kernel
++ patching is necessary for pass-through device handlers (scst_disk,
++ etc.).
++
++ - CONFIG_SCST_TEST_IO_IN_SIRQ - if defined, allows SCST to submit selected
++ SCSI commands (TUR and READ/WRITE) from soft-IRQ context (tasklets).
++ Enabling it will decrease amount of context switches and slightly
++ improve performance. The goal of this option is to be able to measure
++ overhead of the context switches. If after enabling this option you
++ don't see under load in vmstat output on the target significant
++ decrease of amount of context switches, then your target driver
++ doesn't submit commands to SCST in IRQ context. For instance,
++ iSCSI-SCST doesn't do that, but qla2x00t with
++ CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD disabled - does. This option is
++ designed to be used with vdisk NULLIO backend.
++
++ WARNING! Using this option enabled with other backend than vdisk
++ NULLIO is unsafe and can lead you to a kernel crash!
++
++ - CONFIG_SCST_STRICT_SECURITY - if defined, makes SCST zero allocated data
++ buffers. Undefining it (default) considerably improves performance
++ and eases CPU load, but could create a security hole (information
++ leakage), so enable it, if you have strict security requirements.
++
++ - CONFIG_SCST_ABORT_CONSIDER_FINISHED_TASKS_AS_NOT_EXISTING - if defined,
++ in case when TASK MANAGEMENT function ABORT TASK is trying to abort a
++ command, which has already finished, remote initiator, which sent the
++ ABORT TASK request, will receive TASK NOT EXIST (or ABORT FAILED)
++ response for the ABORT TASK request. This is more logical response,
++ since, because the command finished, attempt to abort it failed, but
++ some initiators, particularly VMware iSCSI initiator, consider TASK
++ NOT EXIST response as if the target got crazy and try to RESET it.
++ Then sometimes get crazy itself. So, this option is disabled by
++ default.
++
++ - CONFIG_SCST_MEASURE_LATENCY - if defined, provides in "latency" files
++ global and per-LUN average commands processing latency statistic. You
++ can clear already measured results by writing 0 in each file. Note,
++ you need a non-preemptible kernel to have correct results.
++
++HIGHMEM kernel configurations are fully supported, but not recommended
++for performance reasons.
++
++
++Module parameters
++-----------------
++
++Module scst supports the following parameters:
++
++ - scst_threads - allows to set count of SCST's threads. By default it
++ is CPU count.
++
++ - scst_max_cmd_mem - sets maximum amount of memory in MB allowed to be
++ consumed by the SCST commands for data buffers at any given time. By
++ default it is approximately TotalMem/4.
++
++
++SCST sysfs interface
++--------------------
++
++SCST sysfs interface designed to be self descriptive and self
++containing. This means that a high level managament tool for it can be
++written once and automatically support any future sysfs interface
++changes (attributes additions or removals, new target drivers and dev
++handlers, etc.) without any modifications. Scstadmin is an example of
++such management tool.
++
++To implement that an management tool should not be implemented around
++drivers and their attributes, but around common rules those drivers and
++attributes follow. You can find those rules in SysfsRules file. For
++instance, each SCST sysfs file (attribute) can contain in the last line
++mark "[key]". It is automatically added to allow scstadmin and other
++management tools to see which attributes it should save in the config
++file. If you are doing manual attributes manipulations, you can ignore
++this mark.
++
++Root of SCST sysfs interface is /sys/kernel/scst_tgt. It has the
++following entries:
++
++ - devices - this is a root subdirectory for all SCST devices
++
++ - handlers - this is a root subdirectory for all SCST dev handlers
++
++ - max_tasklet_cmd - specifies how many commands at max can be queued in
++ the SCST core simultaneously on a single CPU from all connected
++ initiators to allow processing commands on this CPU in soft-IRQ
++ context in tasklets. If the count of the commands exceeds this value,
++ then all of them will be processed only in SCST threads. This is to
++ to prevent possible under heavy load starvation of processes on the
++ CPUs serving soft IRQs and in some cases to improve performance by
++ more evenly spreading load over available CPUs.
++
++ - sgv - this is a root subdirectory for all SCST SGV caches
++
++ - targets - this is a root subdirectory for all SCST targets
++
++ - setup_id - allows to read and write SCST setup ID. This ID can be
++ used in cases, when the same SCST configuration should be installed
++ on several targets, but exported from those targets devices should
++ have different IDs and SNs. For instance, VDISK dev handler uses this
++ ID to generate T10 vendor specific identifier and SN of the devices.
++
++ - threads - allows to read and set number of global SCST I/O threads.
++ Those threads used with async. dev handlers, for instance, vdisk
++ BLOCKIO or NULLIO.
++
++ - trace_level - allows to enable and disable various tracing
++ facilities. See content of this file for help how to use it. See also
++ section "Dealing with massive logs" for more info how to make correct
++ logs when you enabled trace levels producing a lot of logs data.
++
++ - version - read-only attribute, which allows to see version of
++ SCST and enabled optional features.
++
++ - last_sysfs_mgmt_res - read-only attribute returning completion status
++ of the last management command. In the sysfs implementation there are
++ some problems between internal sysfs and internal SCST locking. To
++ avoid them in some cases sysfs calls can return error with errno
++ EAGAIN. This doesn't mean the operation failed. It only means that
++ the operation queued and not yet completed. To wait for it to
++ complete, an management tool should poll this file. If the operation
++ hasn't yet completed, it will also return EAGAIN. But after it's
++ completed, it will return the result of this operation (0 for success
++ or -errno for error).
++
++"Devices" subdirectory contains subdirectories for each SCST devices.
++
++Content of each device's subdirectory is dev handler specific. See
++documentation for your dev handlers for more info about it as well as
++SysfsRules file for more info about common to all dev handlers rules.
++SCST dev handlers can have the following common entries:
++
++ - exported - subdirectory containing links to all LUNs where this
++ device was exported.
++
++ - handler - if dev handler determined for this device, this link points
++ to it. The handler can be not set for pass-through devices.
++
++ - threads_num - shows and allows to set number of threads in this device's
++ threads pool. If 0 - no threads will be created, and global SCST
++ threads pool will be used. If <0 - creation of the threads pool is
++ prohibited.
++
++ - threads_pool_type - shows and allows to sets threads pool type.
++ Possible values: "per_initiator" and "shared". When the value is
++ "per_initiator" (default), each session from each initiator will use
++ separate dedicated pool of threads. When the value is "shared", all
++ sessions from all initiators will share the same per-device pool of
++ threads. Valid only if threads_num attribute >0.
++
++ - dump_prs - allows to dump persistent reservations information in the
++ kernel log.
++
++ - type - SCSI type of this device
++
++See below for more information about other entries of this subdirectory
++of the standard SCST dev handlers.
++
++"Handlers" subdirectory contains subdirectories for each SCST dev
++handler.
++
++Content of each handler's subdirectory is dev handler specific. See
++documentation for your dev handlers for more info about it as well as
++SysfsRules file for more info about common to all dev handlers rules.
++SCST dev handlers can have the following common entries:
++
++ - mgmt - this entry allows to create virtual devices and their
++ attributes (for virtual devices dev handlers) or assign/unassign real
++ SCSI devices to/from this dev handler (for pass-through dev
++ handlers).
++
++ - trace_level - allows to enable and disable various tracing
++ facilities. See content of this file for help how to use it. See also
++ section "Dealing with massive logs" for more info how to make correct
++ logs when you enabled trace levels producing a lot of logs data.
++
++ - type - SCSI type of devices served by this dev handler.
++
++See below for more information about other entries of this subdirectory
++of the standard SCST dev handlers.
++
++"Sgv" subdirectory contains statistic information of SCST SGV caches. It
++has the following entries:
++
++ - None, one or more subdirectories for each existing SGV cache.
++
++ - global_stats - file containing global SGV caches statistics.
++
++Each SGV cache's subdirectory has the following item:
++
++ - stats - file containing statistics for this SGV caches.
++
++"Targets" subdirectory contains subdirectories for each SCST target.
++
++Content of each target's subdirectory is target specific. See
++documentation for your target for more info about it as well as
++SysfsRules file for more info about common to all targets rules.
++Every target should have at least the following entries:
++
++ - ini_groups - subdirectory, which contains and allows to define
++ initiator-oriented access control information, see below.
++
++ - luns - subdirectory, which contains list of available LUNs in the
++ target-oriented access control and allows to define it, see below.
++
++ - sessions - subdirectory containing connected to this target sessions.
++
++ - comment - this attribute can be used to store any human readable info
++ to help identify target. For instance, to help identify the target's
++ mapping to the corresponding hardware port. It isn't anyhow used by
++ SCST.
++
++ - enabled - using this attribute you can enable or disable this target/
++ It allows to finish configuring it before it starts accepting new
++ connections. 0 by default.
++
++ - addr_method - used LUNs addressing method. Possible values:
++ "Peripheral" and "Flat". Most initiators work well with Peripheral
++ addressing method (default), but some (HP-UX, for instance) may
++ require Flat method. This attribute is also available in the
++ initiators security groups, so you can assign the addressing method
++ on per-initiator basis.
++
++ - cpu_mask - defines CPU affinity mask for threads serving this target.
++ For threads serving LUNs it is used only for devices with
++ threads_pool_type "per_initiator".
++
++ - io_grouping_type - defines how I/O from sessions to this target are
++ grouped together. This I/O grouping is very important for
++ performance. By setting this attribute in a right value, you can
++ considerably increase performance of your setup. This grouping is
++ performed only if you use CFQ I/O scheduler on the target and for
++ devices with threads_num >= 0 and, if threads_num > 0, with
++ threads_pool_type "per_initiator". Possible values:
++ "this_group_only", "never", "auto", or I/O group number >0. When the
++ value is "this_group_only" all I/O from all sessions in this target
++ will be grouped together. When the value is "never", I/O from
++ different sessions will not be grouped together, i.e. all sessions in
++ this target will have separate dedicated I/O groups. When the value
++ is "auto" (default), all I/O from initiators with the same name
++ (iSCSI initiator name, for instance) in all targets will be grouped
++ together with a separate dedicated I/O group for each initiator name.
++ For iSCSI this mode works well, but other transports usually use
++ different initiator names for different sessions, so using such
++ transports in MPIO configurations you should either use value
++ "this_group_only", or an explicit I/O group number. This attribute is
++ also available in the initiators security groups, so you can assign
++ the I/O grouping on per-initiator basis. See below for more info how
++ to use this attribute.
++
++ - rel_tgt_id - allows to read or write SCSI Relative Target Port
++ Identifier attribute. This identifier is used to identify SCSI Target
++ Ports by some SCSI commands, mainly by Persistent Reservations
++ commands. This identifier must be unique among all SCST targets, but
++ for convenience SCST allows disabled targets to have not unique
++ rel_tgt_id. In this case SCST will not allow to enable this target
++ until rel_tgt_id becomes unique. This attribute initialized unique by
++ SCST by default.
++
++A target driver may have also the following entries:
++
++ - "hw_target" - if the target driver supports both hardware and virtual
++ targets (for instance, an FC adapter supporting NPIV, which has
++ hardware targets for its physical ports as well as virtual NPIV
++ targets), this read only attribute for all hardware targets will
++ exist and contain value 1.
++
++Subdirectory "sessions" contains one subdirectory for each connected
++session with name equal to name of the connected initiator.
++
++Each session subdirectory contains the following entries:
++
++ - initiator_name - contains initiator name
++
++ - force_close - optional write-only attribute, which allows to force
++ close this session.
++
++ - active_commands - contains number of active, i.e. not yet or being
++ executed, SCSI commands in this session.
++
++ - commands - contains overall number of SCSI commands in this session.
++
++ - latency - if CONFIG_SCST_MEASURE_LATENCY enabled, contains latency
++ statistics for this session.
++
++ - luns - a link pointing out to the corresponding LUNs set (security
++ group) where this session was attached to.
++
++ - One or more "lunX" subdirectories, where 'X' is a number, for each LUN
++ this session has (see below).
++
++ - other target driver specific attributes and subdirectories.
++
++See below description of the VDISK's sysfs interface for samples.
++
++
++Access and devices visibility management (LUN masking)
++------------------------------------------------------
++
++Access and devices visibility management allows for an initiator or
++group of initiators to see different devices with different LUNs
++with necessary access permissions.
++
++SCST supports two modes of access control:
++
++1. Target-oriented. In this mode you define for each target a default
++set of LUNs, which are accessible to all initiators, connected to that
++target. This is a regular access control mode, which people usually mean
++thinking about access control in general. For instance, in IET this is
++the only supported mode.
++
++2. Initiator-oriented. In this mode you define which LUNs are accessible
++for each initiator. In this mode you should create for each set of one
++or more initiators, which should access to the same set of devices with
++the same LUNs, a separate security group, then add to it devices and
++names of allowed initiator(s).
++
++Both modes can be used simultaneously. In this case the
++initiator-oriented mode has higher priority, than the target-oriented,
++i.e. initiators are at first searched in all defined security groups for
++this target and, if none matches, the default target's set of LUNs is
++used. This set of LUNs might be empty, then the initiator will not see
++any LUNs from the target.
++
++You can at any time find out which set of LUNs each session is assigned
++to by looking where link
++/sys/kernel/scst_tgt/targets/target_driver/target_name/sessions/initiator_name/luns
++points to.
++
++To configure the target-oriented access control SCST provides the
++following interface. Each target's sysfs subdirectory
++(/sys/kernel/scst_tgt/targets/target_driver/target_name) has "luns"
++subdirectory. This subdirectory contains the list of already defined
++target-oriented access control LUNs for this target as well as file
++"mgmt". This file has the following commands, which you can send to it,
++for instance, using "echo" shell command. You can always get a small
++help about supported commands by looking inside this file. "Parameters"
++are one or more param_name=value pairs separated by ';'.
++
++ - "add H:C:I:L lun [parameters]" - adds a pass-through device with
++ host:channel:id:lun with LUN "lun". Optionally, the device could be
++ marked as read only by using parameter "read_only". The recommended
++ way to find out H:C:I:L numbers is use of lsscsi utility.
++
++ - "replace H:C:I:L lun [parameters]" - replaces by pass-through device
++ with host:channel:id:lun existing with LUN "lun" device with
++ generation of INQUIRY DATA HAS CHANGED Unit Attention. If the old
++ device doesn't exist, this command acts as the "add" command.
++ Optionally, the device could be marked as read only by using
++ parameter "read_only". The recommended way to find out H:C:I:L
++ numbers is use of lsscsi utility.
++
++ - "add VNAME lun [parameters]" - adds a virtual device with name VNAME
++ with LUN "lun". Optionally, the device could be marked as read only
++ by using parameter "read_only".
++
++ - "replace VNAME lun [parameters]" - replaces by virtual device
++ with name VNAME existing with LUN "lun" device with generation of
++ INQUIRY DATA HAS CHANGED Unit Attention. If the old device doesn't
++ exist, this command acts as the "add" command. Optionally, the device
++ could be marked as read only by using parameter "read_only".
++
++ - "del lun" - deletes LUN lun
++
++ - "clear" - clears the list of devices
++
++To configure the initiator-oriented access control SCST provides the
++following interface. Each target's sysfs subdirectory
++(/sys/kernel/scst_tgt/targets/target_driver/target_name) has "ini_groups"
++subdirectory. This subdirectory contains the list of already defined
++security groups for this target as well as file "mgmt". This file has
++the following commands, which you can send to it, for instance, using
++"echo" shell command. You can always get a small help about supported
++commands by looking inside this file.
++
++ - "create GROUP_NAME" - creates a new security group.
++
++ - "del GROUP_NAME" - deletes a new security group.
++
++Each security group's subdirectory contains 2 subdirectories: initiators
++and luns as well as the following attributes: addr_method, cpu_mask and
++io_grouping_type. See above description of them.
++
++Each "initiators" subdirectory contains list of added to this groups
++initiator as well as as well as file "mgmt". This file has the following
++commands, which you can send to it, for instance, using "echo" shell
++command. You can always get a small help about supported commands by
++looking inside this file.
++
++ - "add INITIATOR_NAME" - adds initiator with name INITIATOR_NAME to the
++ group.
++
++ - "del INITIATOR_NAME" - deletes initiator with name INITIATOR_NAME
++ from the group.
++
++ - "move INITIATOR_NAME DEST_GROUP_NAME" moves initiator with name
++ INITIATOR_NAME from the current group to group with name
++ DEST_GROUP_NAME.
++
++ - "clear" - deletes all initiators from this group.
++
++For "add" and "del" commands INITIATOR_NAME can be a simple DOS-type
++patterns, containing '*' and '?' symbols. '*' means match all any
++symbols, '?' means match only any single symbol. For instance,
++"blah.xxx" will match "bl?h.*". Additionally, you can use negative sign
++'!' to revert the value of the pattern. For instance, "ah.xxx" will
++match "!bl?h.*".
++
++Each "luns" subdirectory contains the list of already defined LUNs for
++this group as well as file "mgmt". Content of this file as well as list
++of available in it commands is fully identical to the "luns"
++subdirectory of the target-oriented access control.
++
++Examples:
++
++ - echo "create INI" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/mgmt -
++ creates security group INI for target iqn.2006-10.net.vlnb:tgt1.
++
++ - echo "add 2:0:1:0 11" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/INI/luns/mgmt -
++ adds a pass-through device sitting on host 2, channel 0, ID 1, LUN 0
++ to group with name INI as LUN 11.
++
++ - echo "add disk1 0" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/INI/luns/mgmt -
++ adds a virtual disk with name disk1 to group with name INI as LUN 0.
++
++ - echo "add 21:*:e0:?b:83:*" >/sys/kernel/scst_tgt/targets/21:00:00:a0:8c:54:52:12/ini_groups/INI/initiators/mgmt -
++ adds a pattern to group with name INI to Fibre Channel target with
++ WWN 21:00:00:a0:8c:54:52:12, which matches WWNs of Fibre Channel
++ initiator ports.
++
++Consider you need to have an iSCSI target with name
++"iqn.2007-05.com.example:storage.disk1.sys1.xyz", which should export
++virtual device "dev1" with LUN 0 and virtual device "dev2" with LUN 1,
++but initiator with name
++"iqn.2007-05.com.example:storage.disk1.spec_ini.xyz" should see only
++virtual device "dev2" read only with LUN 0. To achieve that you should
++do the following commands:
++
++# echo "iqn.2007-05.com.example:storage.disk1.sys1.xyz" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++# echo "add dev1 0" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2007-05.com.example:storage.disk1.sys1.xyz/luns/mgmt
++# echo "add dev2 1" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2007-05.com.example:storage.disk1.sys1.xyz/luns/mgmt
++# echo "create SPEC_INI" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2007-05.com.example:storage.disk1.sys1.xyz/ini_groups/mgmt
++# echo "add dev2 0 read_only=1" \
++ >/sys/kernel/scst_tgt/targets/iscsi/iqn.2007-05.com.example:storage.disk1.sys1.xyz/ini_groups/SPEC_INI/luns/mgmt
++# echo "iqn.2007-05.com.example:storage.disk1.spec_ini.xyz" \
++ >/sys/kernel/scst_tgt/targets/iscsi/iqn.2007-05.com.example:storage.disk1.sys1.xyz/ini_groups/SPEC_INI/initiators/mgmt
++
++For Fibre Channel or SAS in the above example you should use target's
++and initiator ports WWNs instead of iSCSI names.
++
++It is highly recommended to use scstadmin utility instead of described
++in this section low level interface.
++
++IMPORTANT
++=========
++
++There must be LUN 0 in each set of LUNs, i.e. LUs numeration must not
++start from, e.g., 1. Otherwise you will see no devices on remote
++initiators and SCST core will write into the kernel log message: "tgt_dev
++for LUN 0 not found, command to unexisting LU?"
++
++IMPORTANT
++=========
++
++All the access control must be fully configured BEFORE the corresponding
++target is enabled. When you enable a target, it will immediately start
++accepting new connections, hence creating new sessions, and those new
++sessions will be assigned to security groups according to the
++*currently* configured access control settings. For instance, to
++the default target's set of LUNs, instead of "HOST004" group as you may
++need, because "HOST004" doesn't exist yet. So, you must configure all
++the security groups before new connections from the initiators are
++created, i.e. before the target enabled.
++
++
++VDISK device handler
++--------------------
++
++VDISK has 4 built-in dev handlers: vdisk_fileio, vdisk_blockio,
++vdisk_nullio and vcdrom. Roots of their sysfs interface are
++/sys/kernel/scst_tgt/handlers/handler_name, e.g. for vdisk_fileio:
++/sys/kernel/scst_tgt/handlers/vdisk_fileio. Each root has the following
++entries:
++
++ - None, one or more links to devices with name equal to names
++ of the corresponding devices.
++
++ - trace_level - allows to enable and disable various tracing
++ facilities. See content of this file for help how to use it. See also
++ section "Dealing with massive logs" for more info how to make correct
++ logs when you enabled trace levels producing a lot of logs data.
++
++ - mgmt - main management entry, which allows to add/delete VDISK
++ devices with the corresponding type.
++
++The "mgmt" file has the following commands, which you can send to it,
++for instance, using "echo" shell command. You can always get a small
++help about supported commands by looking inside this file. "Parameters"
++are one or more param_name=value pairs separated by ';'.
++
++ - echo "add_device device_name [parameters]" - adds a virtual device
++ with name device_name and specified parameters (see below)
++
++ - echo "del_device device_name" - deletes a virtual device with name
++ device_name.
++
++Handler vdisk_fileio provides FILEIO mode to create virtual devices.
++This mode uses as backend files and accesses to them using regular
++read()/write() file calls. This allows to use full power of Linux page
++cache. The following parameters possible for vdisk_fileio:
++
++ - filename - specifies path and file name of the backend file. The path
++ must be absolute.
++
++ - blocksize - specifies block size used by this virtual device. The
++ block size must be power of 2 and >= 512 bytes. Default is 512.
++
++ - write_through - disables write back caching. Note, this option
++ has sense only if you also *manually* disable write-back cache in
++ *all* your backstorage devices and make sure it's actually disabled,
++ since many devices are known to lie about this mode to get better
++ benchmark results. Default is 0.
++
++ - read_only - read only. Default is 0.
++
++ - o_direct - disables both read and write caching. This mode isn't
++ currently fully implemented, you should use user space fileio_tgt
++ program in O_DIRECT mode instead (see below).
++
++ - nv_cache - enables "non-volatile cache" mode. In this mode it is
++ assumed that the target has a GOOD UPS with ability to cleanly
++ shutdown target in case of power failure and it is software/hardware
++ bugs free, i.e. all data from the target's cache are guaranteed
++ sooner or later to go to the media. Hence all data synchronization
++ with media operations, like SYNCHRONIZE_CACHE, are ignored in order
++ to bring more performance. Also in this mode target reports to
++ initiators that the corresponding device has write-through cache to
++ disable all write-back cache workarounds used by initiators. Use with
++ extreme caution, since in this mode after a crash of the target
++ journaled file systems don't guarantee the consistency after journal
++ recovery, therefore manual fsck MUST be ran. Note, that since usually
++ the journal barrier protection (see "IMPORTANT" note below) turned
++ off, enabling NV_CACHE could change nothing from data protection
++ point of view, since no data synchronization with media operations
++ will go from the initiator. This option overrides "write_through"
++ option. Disabled by default.
++
++ - thin_provisioned - enables thin provisioning facility, when remote
++ initiators can unmap blocks of storage, if they don't need them
++ anymore. Backend storage also must support this facility.
++
++ - removable - with this flag set the device is reported to remote
++ initiators as removable.
++
++Handler vdisk_blockio provides BLOCKIO mode to create virtual devices.
++This mode performs direct block I/O with a block device, bypassing the
++page cache for all operations. This mode works ideally with high-end
++storage HBAs and for applications that either do not need caching
++between application and disk or need the large block throughput. See
++below for more info.
++
++The following parameters possible for vdisk_blockio: filename,
++blocksize, nv_cache, read_only, removable, thin_provisioned. See
++vdisk_fileio above for description of those parameters.
++
++Handler vdisk_nullio provides NULLIO mode to create virtual devices. In
++this mode no real I/O is done, but success returned to initiators.
++Intended to be used for performance measurements at the same way as
++"*_perf" handlers. The following parameters possible for vdisk_nullio:
++blocksize, read_only, removable. See vdisk_fileio above for description
++of those parameters.
++
++Handler vcdrom allows emulation of a virtual CDROM device using an ISO
++file as backend. It doesn't have any parameters.
++
++For example:
++
++echo "add_device disk1 filename=/disk1; blocksize=4096; nv_cache=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++
++will create a FILEIO virtual device disk1 with backend file /disk1
++with block size 4K and NV_CACHE enabled.
++
++Each vdisk_fileio's device has the following attributes in
++/sys/kernel/scst_tgt/devices/device_name:
++
++ - filename - contains path and file name of the backend file.
++
++ - blocksize - contains block size used by this virtual device.
++
++ - write_through - contains status of write back caching of this virtual
++ device.
++
++ - read_only - contains read only status of this virtual device.
++
++ - o_direct - contains O_DIRECT status of this virtual device.
++
++ - nv_cache - contains NV_CACHE status of this virtual device.
++
++ - thin_provisioned - contains thin provisioning status of this virtual
++ device
++
++ - removable - contains removable status of this virtual device.
++
++ - size_mb - contains size of this virtual device in MB.
++
++ - t10_dev_id - contains and allows to set T10 vendor specific
++ identifier for Device Identification VPD page (0x83) of INQUIRY data.
++ By default VDISK handler always generates t10_dev_id for every new
++ created device at creation time based on the device name and
++ scst_vdisk_ID scst_vdisk.ko module parameter (see below).
++
++ - usn - contains the virtual device's serial number of INQUIRY data. It
++ is created at the device creation time based on the device name and
++ scst_vdisk_ID scst_vdisk.ko module parameter (see below).
++
++ - type - contains SCSI type of this virtual device.
++
++ - resync_size - write only attribute, which makes vdisk_fileio to
++ rescan size of the backend file. It is useful if you changed it, for
++ instance, if you resized it.
++
++For example:
++
++/sys/kernel/scst_tgt/devices/disk1
++|-- blocksize
++|-- exported
++| |-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/0
++| |-- export1 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/INI/luns/0
++| |-- export2 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt1/luns/0
++| |-- export3 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/INI1/luns/0
++| |-- export4 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/INI2/luns/0
++|-- filename
++|-- handler -> ../../handlers/vdisk_fileio
++|-- nv_cache
++|-- o_direct
++|-- read_only
++|-- removable
++|-- resync_size
++|-- size_mb
++|-- t10_dev_id
++|-- thin_provisioned
++|-- threads_num
++|-- threads_pool_type
++|-- type
++|-- usn
++`-- write_through
++
++Each vdisk_blockio's device has the following attributes in
++/sys/kernel/scst_tgt/devices/device_name: blocksize, filename, nv_cache,
++read_only, removable, resync_size, size_mb, t10_dev_id,
++thin_provisioned, threads_num, threads_pool_type, type, usn. See above
++description of those parameters.
++
++Each vdisk_nullio's device has the following attributes in
++/sys/kernel/scst_tgt/devices/device_name: blocksize, read_only,
++removable, size_mb, t10_dev_id, threads_num, threads_pool_type, type,
++usn. See above description of those parameters.
++
++Each vcdrom's device has the following attributes in
++/sys/kernel/scst_tgt/devices/device_name: filename, size_mb,
++t10_dev_id, threads_num, threads_pool_type, type, usn. See above
++description of those parameters. Exception is filename attribute. For
++vcdrom it is writable. Writing to it allows to virtually insert or
++change virtual CD media in the virtual CDROM device. For example:
++
++ - echo "/image.iso" >/sys/kernel/scst_tgt/devices/cdrom/filename - will
++ insert file /image.iso as virtual media to the virtual CDROM cdrom.
++
++ - echo "" >/sys/kernel/scst_tgt/devices/cdrom/filename - will remove
++ "media" from the virtual CDROM cdrom.
++
++Additionally VDISK handler has module parameter "num_threads", which
++specifies count of I/O threads for each FILEIO VDISK's or VCDROM device.
++If you have a workload, which tends to produce rather random accesses
++(e.g. DB-like), you should increase this count to a bigger value, like
++32. If you have a rather sequential workload, you should decrease it to
++a lower value, like number of CPUs on the target or even 1. Due to some
++limitations of Linux I/O subsystem, increasing number of I/O threads too
++much leads to sequential performance drop, especially with deadline
++scheduler, so decreasing it can improve sequential performance. The
++default provides a good compromise between random and sequential
++accesses.
++
++You shouldn't be afraid to have too many VDISK I/O threads if you have
++many VDISK devices. Kernel threads consume very little amount of
++resources (several KBs) and only necessary threads will be used by SCST,
++so the threads will not trash your system.
++
++CAUTION: If you partitioned/formatted your device with block size X, *NEVER*
++======== ever try to export and then mount it (even accidentally) with another
++ block size. Otherwise you can *instantly* damage it pretty
++ badly as well as all your data on it. Messages on initiator
++ like: "attempt to access beyond end of device" is the sign of
++ such damage.
++
++ Moreover, if you want to compare how well different block sizes
++ work for you, you **MUST** EVERY TIME AFTER CHANGING BLOCK SIZE
++ **COMPLETELY** **WIPE OFF** ALL THE DATA FROM THE DEVICE. In
++ other words, THE **WHOLE** DEVICE **MUST** HAVE ONLY **ZEROS**
++ AS THE DATA AFTER YOU SWITCH TO NEW BLOCK SIZE. Switching block
++ sizes isn't like switching between FILEIO and BLOCKIO, after
++ changing block size all previously written with another block
++ size data MUST BE ERASED. Otherwise you will have a full set of
++ very weird behaviors, because blocks addressing will be
++ changed, but initiators in most cases will not have a
++ possibility to detect that old addresses written on the device
++ in, e.g., partition table, don't refer anymore to what they are
++ intended to refer.
++
++IMPORTANT: Some disk and partition table management utilities don't support
++========= block sizes >512 bytes, therefore make sure that your favorite one
++ supports it. Currently only cfdisk is known to work only with
++ 512 bytes blocks, other utilities like fdisk on Linux or
++ standard disk manager on Windows are proved to work well with
++ non-512 bytes blocks. Note, if you export a disk file or
++ device with some block size, different from one, with which
++ it was already partitioned, you could get various weird
++ things like utilities hang up or other unexpected behavior.
++ Hence, to be sure, zero the exported file or device before
++ the first access to it from the remote initiator with another
++ block size. On Window initiator make sure you "Set Signature"
++ in the disk manager on the imported from the target drive
++ before doing any other partitioning on it. After you
++ successfully mounted a file system over non-512 bytes block
++ size device, the block size stops matter, any program will
++ work with files on such file system.
++
++
++Dealing with massive logs
++-------------------------
++
++If you want to enable using "trace_level" file logging levels, which
++produce a lot of events, like "debug", to not loose logged events you
++should also:
++
++ * Increase in .config of your kernel CONFIG_LOG_BUF_SHIFT variable
++ to much bigger value, then recompile it. For example, value 25 will
++ provide good protection from logging overflow even under high volume
++ of logging events. To use it you will need to modify the maximum
++ allowed value for CONFIG_LOG_BUF_SHIFT in the corresponding Kconfig
++ file to 25 as well.
++
++ * Change in your /etc/syslog.conf or other config file of your favorite
++ logging program to store kernel logs in async manner. For example,
++ you can add in rsyslog.conf line "kern.info -/var/log/kernel" and
++ add "kern.none" in line for /var/log/messages, so the resulting line
++ would looks like:
++
++ "*.info;kern.none;mail.none;authpriv.none;cron.none /var/log/messages"
++
++
++Persistent Reservations
++-----------------------
++
++SCST implements Persistent Reservations with full set of capabilities,
++including "Persistence Through Power Loss".
++
++The "Persistence Through Power Loss" data are saved in /var/lib/scst/pr
++with files with names the same as the names of the corresponding
++devices. Also this directory contains backup versions of those files
++with suffix ".1". Those backup files are used in case of power or other
++failure to prevent Persistent Reservation information from corruption
++during update.
++
++The Persistent Reservations available on all transports implementing
++get_initiator_port_transport_id() callback. Transports not implementing
++this callback will act in one of 2 possible scenarios ("all or
++nothing"):
++
++1. If a device has such transport connected and doesn't have persistent
++reservations, it will refuse Persistent Reservations commands as if it
++doesn't support them.
++
++2. If a device has persistent reservations, all initiators newly
++connecting via such transports will not see this device. After all
++persistent reservations from this device are released, upon reconnect
++the initiators will see it.
++
++
++Caching
++-------
++
++By default for performance reasons VDISK FILEIO devices use write back
++caching policy.
++
++Generally, write back caching is safe for use and danger of it is
++greatly overestimated, because most modern (especially, Enterprise
++level) applications are well prepared to work with write back cached
++storage. Particularly, such are all transactions-based applications.
++Those applications flush cache to completely avoid ANY data loss on a
++crash or power failure. For instance, journaled file systems flush cache
++on each meta data update, so they survive power/hardware/software
++failures pretty well.
++
++Since locally on initiators write back caching is always on, if an
++application cares about its data consistency, it does flush the cache
++when necessary or on any write, if open files with O_SYNC. If it doesn't
++care, it doesn't flush the cache. As soon as the cache flushes
++propagated to the storage, write back caching on it doesn't make any
++difference. If application doesn't flush the cache, it's doomed to loose
++data in case of a crash or power failure doesn't matter where this cache
++located, locally or on the storage.
++
++To illustrate that consider, for example, a user who wants to copy /src
++directory to /dst directory reliably, i.e. after the copy finished no
++power failure or software/hardware crash could lead to a loss of the
++data in /dst. There are 2 ways to achieve this. Let's suppose for
++simplicity cp opens files for writing with O_SYNC flag, hence bypassing
++the local cache.
++
++1. Slow. Make the device behind /dst working in write through caching
++mode and then run "cp -a /src /dst".
++
++2. Fast. Let the device behind /dst working in write back caching mode
++and then run "cp -a /src /dst; sync". The reliability of the result is
++the same, but it's much faster than (1). Nobody would care if a crash
++happens during the copy, because after recovery simply leftovers from
++the not completed attempt would be deleted and the operation would be
++restarted from the very beginning.
++
++So, you can see in (2) there is no danger of ANY data loss from the
++write back caching. Moreover, since on practice cp doesn't open files
++for writing with O_SYNC flag, to get the copy done reliably, sync
++command must be called after cp anyway, so enabling write back caching
++wouldn't make any difference for reliability.
++
++Also you can consider it from another side. Modern HDDs have at least
++16MB of cache working in write back mode by default, so for a 10 drives
++RAID it is 160MB of a write back cache. How many people are happy with
++it and how many disabled write back cache of their HDDs? Almost all and
++almost nobody correspondingly? Moreover, many HDDs lie about state of
++their cache and report write through while working in write back mode.
++They are also successfully used.
++
++Note, Linux I/O subsystem guarantees to propagated cache flushes to the
++storage only using data protection barriers, which usually turned off by
++default (see http://lwn.net/Articles/283161). Without barriers enabled
++Linux doesn't provide a guarantee that after sync()/fsync() all written
++data really hit permanent storage. They can be stored in the cache of
++your backstorage devices and, hence, lost on a power failure event.
++Thus, ever with write-through cache mode, you still either need to
++enable barriers on your backend file system on the target (for direct
++/dev/sdX devices this is, indeed, impossible), or need a good UPS to
++protect yourself from not committed data loss. Some info about barriers
++from the XFS point of view could be found at
++http://oss.sgi.com/projects/xfs/faq.html#wcache. On Linux initiators for
++Ext3 and ReiserFS file systems the barrier protection could be turned on
++using "barrier=1" and "barrier=flush" mount options correspondingly. You
++can check if the barriers turn on or off by looking in /proc/mounts.
++Windows and, AFAIK, other UNIX'es don't need any special explicit
++options and do necessary barrier actions on write-back caching devices
++by default.
++
++To limit this data loss with write back caching you can use files in
++/proc/sys/vm to limit amount of unflushed data in the system cache.
++
++If you for some reason have to use VDISK FILEIO devices in write through
++caching mode, don't forget to disable internal caching on their backend
++devices or make sure they have additional battery or supercapacitors
++power supply on board. Otherwise, you still on a power failure would
++loose all the unsaved yet data in the devices internal cache.
++
++Note, on some real-life workloads write through caching might perform
++better, than write back one with the barrier protection turned on.
++
++
++BLOCKIO VDISK mode
++------------------
++
++This module works best for these types of scenarios:
++
++1) Data that are not aligned to 4K sector boundaries and <4K block sizes
++are used, which is normally found in virtualization environments where
++operating systems start partitions on odd sectors (Windows and it's
++sector 63).
++
++2) Large block data transfers normally found in database loads/dumps and
++streaming media.
++
++3) Advanced relational database systems that perform their own caching
++which prefer or demand direct IO access and, because of the nature of
++their data access, can actually see worse performance with
++non-discriminate caching.
++
++4) Multiple layers of targets were the secondary and above layers need
++to have a consistent view of the primary targets in order to preserve
++data integrity which a page cache backed IO type might not provide
++reliably.
++
++Also it has an advantage over FILEIO that it doesn't copy data between
++the system cache and the commands data buffers, so it saves a
++considerable amount of CPU power and memory bandwidth.
++
++IMPORTANT: Since data in BLOCKIO and FILEIO modes are not consistent between
++========= each other, if you try to use a device in both those modes
++ simultaneously, you will almost instantly corrupt your data
++ on that device.
++
++IMPORTANT: If SCST 1.x BLOCKIO worked by default in NV_CACHE mode, when
++========= each device reported to remote initiators as having write through
++ caching. But if your backend block device has internal write
++ back caching it might create a possibility for data loss of
++ the cached in the internal cache data in case of a power
++ failure. Starting from SCST 2.0 BLOCKIO works by default in
++ non-NV_CACHE mode, when each device reported to remote
++ initiators as having write back caching, and synchronizes the
++ internal device's cache on each SYNCHRONIZE_CACHE command
++ from the initiators. It might lead to some PERFORMANCE LOSS,
++ so if you are are sure in your power supply and want to
++ restore 1.x behavior, your should recreate your BLOCKIO
++ devices in NV_CACHE mode.
++
++
++Pass-through mode
++-----------------
++
++In the pass-through mode (i.e. using the pass-through device handlers
++scst_disk, scst_tape, etc) SCSI commands, coming from remote initiators,
++are passed to local SCSI devices on target as is, without any
++modifications.
++
++SCST supports 1 to many pass-through, when several initiators can safely
++connect a single pass-through device (a tape, for instance). For such
++cases SCST emulates all the necessary functionality.
++
++In the sysfs interface all real SCSI devices are listed in
++/sys/kernel/scst_tgt/devices in form host:channel:id:lun numbers, for
++instance 1:0:0:0. The recommended way to match those numbers to your
++devices is use of lsscsi utility.
++
++Each pass-through dev handler has in its root subdirectory
++/sys/kernel/scst_tgt/handlers/handler_name, e.g.
++/sys/kernel/scst_tgt/handlers/dev_disk, "mgmt" file. It allows the
++following commands. They can be sent to it using, e.g., echo command.
++
++ - "add_device" - this command assigns SCSI device with
++host:channel:id:lun numbers to this dev handler.
++
++echo "add_device 1:0:0:0" >/sys/kernel/scst_tgt/handlers/dev_disk/mgmt
++
++will assign SCSI device 1:0:0:0 to this dev handler.
++
++ - "del_device" - this command unassigns SCSI device with
++host:channel:id:lun numbers from this dev handler.
++
++As usually, on read the "mgmt" file returns small help about available
++commands.
++
++You need to manually assign each your real SCSI device to the
++corresponding pass-through dev handler using the "add_device" command,
++otherwise the real SCSI devices will not be visible remotely. The
++assignment isn't done automatically, because it could lead to the
++pass-through dev handlers load and initialization problems if any of the
++local real SCSI devices are malfunctioning.
++
++As any other hardware, the local SCSI hardware can not handle commands
++with amount of data and/or segments count in scatter-gather array bigger
++some values. Therefore, when using the pass-through mode you should note
++that values for maximum number of segments and maximum amount of
++transferred data (max_sectors) for each SCSI command on devices on
++initiators can not be bigger, than corresponding values of the
++corresponding SCSI devices on the target. Otherwise you will see
++symptoms like small transfers work well, but large ones stall and
++messages like: "Unable to complete command due to SG IO count
++limitation" are printed in the kernel logs.
++
++You can't control from the user space limit of the scatter-gather
++segments, but for block devices usually it is sufficient if you set on
++the initiators /sys/block/DEVICE_NAME/queue/max_sectors_kb in the same
++or lower value as in /sys/block/DEVICE_NAME/queue/max_hw_sectors_kb for
++the corresponding devices on the target.
++
++For not-block devices SCSI commands are usually generated directly by
++applications, so, if you experience large transfers stalls, you should
++check documentation for your application how to limit the transfer
++sizes.
++
++Another way to solve this issue is to build SG entries with more than 1
++page each. See the following patch as an example:
++http://scst.sourceforge.net/sgv_big_order_alloc.diff
++
++
++Performance
++-----------
++
++SCST from the very beginning has been designed and implemented to
++provide the best possible performance. Since there is no "one fit all"
++the best performance configuration for different setups and loads, SCST
++provides extensive set of settings to allow to tune it for the best
++performance in each particular case. You don't have to necessary use
++those settings. If you don't, SCST will do very good job to autotune for
++you, so the resulting performance will, in average, be better
++(sometimes, much better) than with other SCSI targets. But in some cases
++you can by manual tuning improve it even more.
++
++Before doing any performance measurements note that performance results
++are very much dependent from your type of load, so it is crucial that
++you choose access mode (FILEIO, BLOCKIO, O_DIRECT, pass-through), which
++suits your needs the best.
++
++In order to get the maximum performance you should:
++
++1. For SCST:
++
++ - Disable in Makefile CONFIG_SCST_STRICT_SERIALIZING, CONFIG_SCST_EXTRACHECKS,
++ CONFIG_SCST_TRACING, CONFIG_SCST_DEBUG*, CONFIG_SCST_STRICT_SECURITY,
++ CONFIG_SCST_MEASURE_LATENCY
++
++2. For target drivers:
++
++ - Disable in Makefiles CONFIG_SCST_EXTRACHECKS, CONFIG_SCST_TRACING,
++ CONFIG_SCST_DEBUG*
++
++3. For device handlers, including VDISK:
++
++ - Disable in Makefile CONFIG_SCST_TRACING and CONFIG_SCST_DEBUG.
++
++4. Make sure you have io_grouping_type option set correctly, especially
++in the following cases:
++
++ - Several initiators share your target's backstorage. It can be a
++ shared LU using some cluster FS, like VMFS, as well as can be
++ different LUs located on the same backstorage (RAID array). For
++ instance, if you have 3 initiators and each of them using its own
++ dedicated FILEIO device file from the same RAID-6 array on the
++ target.
++
++ In this case for the best performance you should have
++ io_grouping_type option set in value "never" in all the LUNs' targets
++ and security groups.
++
++ - Your initiator connected to your target in MPIO mode. In this case for
++ the best performance you should:
++
++ * Either connect all the sessions from the initiator to a single
++ target or security group and have io_grouping_type option set in
++ value "this_group_only" in the target or security group,
++
++ * Or, if it isn't possible to connect all the sessions from the
++ initiator to a single target or security group, assign the same
++ numeric io_grouping_type value for each target/security group this
++ initiator connected to. The exact value itself doesn't matter,
++ important only that all the targets/security groups use the same
++ value.
++
++Don't forget, io_grouping_type makes sense only if you use CFQ I/O
++scheduler on the target and for devices with threads_num >= 0 and, if
++threads_num > 0, with threads_pool_type "per_initiator".
++
++You can check if in your setup io_grouping_type set correctly as well as
++if the "auto" io_grouping_type value works for you by tests like the
++following:
++
++ - For not MPIO case you can run single thread sequential reading, e.g.
++ using buffered dd, from one initiator, then run the same single
++ thread sequential reading from the second initiator in parallel. If
++ io_grouping_type is set correctly the aggregate throughput measured
++ on the target should only slightly decrease as well as all initiators
++ should have nearly equal share of it. If io_grouping_type is not set
++ correctly, the aggregate throughput and/or throughput on any
++ initiator will decrease significantly, in 2 times or even more. For
++ instance, you have 80MB/s single thread sequential reading from the
++ target on any initiator. When then both initiators are reading in
++ parallel you should see on the target aggregate throughput something
++ like 70-75MB/s with correct io_grouping_type and something like
++ 35-40MB/s or 8-10MB/s on any initiator with incorrect.
++
++ - For the MPIO case it's quite easier. With incorrect io_grouping_type
++ you simply won't see performance increase from adding the second
++ session (assuming your hardware is capable to transfer data through
++ both sessions in parallel), or can even see a performance decrease.
++
++5. If you are going to use your target in an VM environment, for
++instance as a shared storage with VMware, make sure all your VMs
++connected to the target via *separate* sessions. For instance, for iSCSI
++it means that each VM has own connection to the target, not all VMs
++connected using a single connection. You can check it using SCST sysfs
++interface. For other transports you should use available facilities,
++like NPIV for Fibre Channel, to make separate sessions for each VM. If
++you miss it, you can greatly loose performance of parallel access to
++your target from different VMs. This isn't related to the case if your
++VMs are using the same shared storage, like with VMFS, for instance. In
++this case all your VM hosts will be connected to the target via separate
++sessions, which is enough.
++
++6. For other target and initiator software parts:
++
++ - Make sure you applied on your kernel all available SCST patches.
++ If for your kernel version this patch doesn't exist, it is strongly
++ recommended to upgrade your kernel to version, for which this patch
++ exists.
++
++ - Don't enable debug/hacking features in the kernel, i.e. use them as
++ they are by default.
++
++ - The default kernel read-ahead and queuing settings are optimized
++ for locally attached disks, therefore they are not optimal if they
++ attached remotely (SCSI target case), which sometimes could lead to
++ unexpectedly low throughput. You should increase read-ahead size to at
++ least 512KB or even more on all initiators and the target.
++
++ You should also limit on all initiators maximum amount of sectors per
++ SCSI command. This tuning is also recommended on targets with large
++ read-ahead values. To do it on Linux, run:
++
++ echo “64” > /sys/block/sdX/queue/max_sectors_kb
++
++ where specify instead of X your imported from target device letter,
++ like 'b', i.e. sdb.
++
++ To increase read-ahead size on Linux, run:
++
++ blockdev --setra N /dev/sdX
++
++ where N is a read-ahead number in 512-byte sectors and X is a device
++ letter like above.
++
++ Note: you need to set read-ahead setting for device sdX again after
++ you changed the maximum amount of sectors per SCSI command for that
++ device.
++
++ Note2: you need to restart SCST after you changed read-ahead settings
++ on the target. It is a limitation of the Linux read ahead
++ implementation. It reads RA values for each file only when the file
++ is open and not updates them when the global RA parameters changed.
++ Hence, the need for vdisk to reopen all its files/devices.
++
++ - You may need to increase amount of requests that OS on initiator
++ sends to the target device. To do it on Linux initiators, run
++
++ echo “64” > /sys/block/sdX/queue/nr_requests
++
++ where X is a device letter like above.
++
++ You may also experiment with other parameters in /sys/block/sdX
++ directory, they also affect performance. If you find the best values,
++ please share them with us.
++
++ - On the target use CFQ IO scheduler. In most cases it has performance
++ advantage over other IO schedulers, sometimes huge (2+ times
++ aggregate throughput increase).
++
++ - It is recommended to turn the kernel preemption off, i.e. set
++ the kernel preemption model to "No Forced Preemption (Server)".
++
++ - Looks like XFS is the best filesystem on the target to store device
++ files, because it allows considerably better linear write throughput,
++ than ext3.
++
++7. For hardware on target.
++
++ - Make sure that your target hardware (e.g. target FC or network card)
++ and underlaying IO hardware (e.g. IO card, like SATA, SCSI or RAID to
++ which your disks connected) don't share the same PCI bus. You can
++ check it using lspci utility. They have to work in parallel, so it
++ will be better if they don't compete for the bus. The problem is not
++ only in the bandwidth, which they have to share, but also in the
++ interaction between cards during that competition. This is very
++ important, because in some cases if target and backend storage
++ controllers share the same PCI bus, it could lead up to 5-10 times
++ less performance, than expected. Moreover, some motherboard (by
++ Supermicro, particularly) have serious stability issues if there are
++ several high speed devices on the same bus working in parallel. If
++ you have no choice, but PCI bus sharing, set in the BIOS PCI latency
++ as low as possible.
++
++8. If you use VDISK IO module in FILEIO mode, NV_CACHE option will
++provide you the best performance. But using it make sure you use a good
++UPS with ability to shutdown the target on the power failure.
++
++Baseline performance numbers you can find in those measurements:
++http://lkml.org/lkml/2009/3/30/283.
++
++IMPORTANT: If you use on initiator some versions of Windows (at least W2K)
++========= you can't get good write performance for VDISK FILEIO devices with
++ default 512 bytes block sizes. You could get about 10% of the
++ expected one. This is because of the partition alignment, which
++ is (simplifying) incompatible with how Linux page cache
++ works, so for each write the corresponding block must be read
++ first. Use 4096 bytes block sizes for VDISK devices and you
++ will have the expected write performance. Actually, any OS on
++ initiators, not only Windows, will benefit from block size
++ max(PAGE_SIZE, BLOCK_SIZE_ON_UNDERLYING_FS), where PAGE_SIZE
++ is the page size, BLOCK_SIZE_ON_UNDERLYING_FS is block size
++ on the underlying FS, on which the device file located, or 0,
++ if a device node is used. Both values are from the target.
++ See also important notes about setting block sizes >512 bytes
++ for VDISK FILEIO devices above.
++
++9. In some cases, for instance working with SSD devices, which consume
++100% of a single CPU load for data transfers in their internal threads,
++to maximize IOPS it can be needed to assign for those threads dedicated
++CPUs. Consider using cpu_mask attribute for devices with
++threads_pool_type "per_initiator" or Linux CPU affinity facilities for
++other threads_pool_types. No IRQ processing should be done on those
++CPUs. Check that using /proc/interrupts. See taskset command and
++Documentation/IRQ-affinity.txt in your kernel's source tree for how to
++assign IRQ affinity to tasks and IRQs.
++
++The reason for that is that processing of coming commands in SIRQ
++context might be done on the same CPUs as SSD devices' threads doing data
++transfers. As the result, those threads won't receive all the processing
++power of those CPUs and perform worse.
++
++
++Work if target's backstorage or link is too slow
++------------------------------------------------
++
++Under high I/O load, when your target's backstorage gets overloaded, or
++working over a slow link between initiator and target, when the link
++can't serve all the queued commands on time, you can experience I/O
++stalls or see in the kernel log abort or reset messages.
++
++At first, consider the case of too slow target's backstorage. On some
++seek intensive workloads even fast disks or RAIDs, which able to serve
++continuous data stream on 500+ MB/s speed, can be as slow as 0.3 MB/s.
++Another possible cause for that can be MD/LVM/RAID on your target as in
++http://lkml.org/lkml/2008/2/27/96 (check the whole thread as well).
++
++Thus, in such situations simply processing of one or more commands takes
++too long time, hence initiator decides that they are stuck on the target
++and tries to recover. Particularly, it is known that the default amount
++of simultaneously queued commands (48) is sometimes too high if you do
++intensive writes from VMware on a target disk, which uses LVM in the
++snapshot mode. In this case value like 16 or even 8-10 depending of your
++backstorage speed could be more appropriate.
++
++Unfortunately, currently SCST lacks dynamic I/O flow control, when the
++queue depth on the target is dynamically decreased/increased based on
++how slow/fast the backstorage speed comparing to the target link. So,
++there are 6 possible actions, which you can do to workaround or fix this
++issue in this case:
++
++1. Ignore incoming task management (TM) commands. It's fine if there are
++not too many of them, so average performance isn't hurt and the
++corresponding device isn't getting put offline, i.e. if the backstorage
++isn't too slow.
++
++2. Decrease /sys/block/sdX/device/queue_depth on the initiator in case
++if it's Linux (see below how) or/and SCST_MAX_TGT_DEV_COMMANDS constant
++in scst_priv.h file until you stop seeing incoming TM commands.
++ISCSI-SCST driver also has its own iSCSI specific parameter for that,
++see its README file.
++
++To decrease device queue depth on Linux initiators you can run command:
++
++# echo Y >/sys/block/sdX/device/queue_depth
++
++where Y is the new number of simultaneously queued commands, X - your
++imported device letter, like 'a' for sda device. There are no special
++limitations for Y value, it can be any value from 1 to possible maximum
++(usually, 32), so start from dividing the current value on 2, i.e. set
++16, if /sys/block/sdX/device/queue_depth contains 32.
++
++3. Increase the corresponding timeout on the initiator. For Linux it is
++located in
++/sys/devices/platform/host*/session*/target*:0:0/*:0:0:1/timeout. It can
++be done automatically by an udev rule. For instance, the following
++rule will increase it to 300 seconds:
++
++SUBSYSTEM=="scsi", KERNEL=="[0-9]*:[0-9]*", ACTION=="add", ATTR{type}=="0|7|14", ATTR{timeout}="300"
++
++By default, this timeout is 30 or 60 seconds, depending on your distribution.
++
++4. Try to avoid such seek intensive workloads.
++
++5. Increase speed of the target's backstorage.
++
++6. Implement in SCST dynamic I/O flow control. This will be an ultimate
++solution. See "Dynamic I/O flow control" section on
++http://scst.sourceforge.net/contributing.html page for possible
++implementation idea.
++
++Next, consider the case of too slow link between initiator and target,
++when the initiator tries to simultaneously push N commands to the target
++over it. In this case time to serve those commands, i.e. send or receive
++data for them over the link, can be more, than timeout for any single
++command, hence one or more commands in the tail of the queue can not be
++served on time less than the timeout, so the initiator will decide that
++they are stuck on the target and will try to recover.
++
++To workaround/fix this issue in this case you can use ways 1, 2, 3, 6
++above or (7): increase speed of the link between target and initiator.
++But for some initiators implementations for WRITE commands there might
++be cases when target has no way to detect the issue, so dynamic I/O flow
++control will not be able to help. In those cases you could also need on
++the initiator(s) to either decrease the queue depth (way 2), or increase
++the corresponding timeout (way 3).
++
++Note, that logged messages about QUEUE_FULL status are quite different
++by nature. This is a normal work, just SCSI flow control in action.
++Simply don't enable "mgmt_minor" logging level, or, alternatively, if
++you are confident in the worst case performance of your back-end storage
++or initiator-target link, you can increase SCST_MAX_TGT_DEV_COMMANDS in
++scst_priv.h to 64. Usually initiators don't try to push more commands on
++the target.
++
++
++Credits
++-------
++
++Thanks to:
++
++ * Mark Buechler <mark.buechler@gmail.com> for a lot of useful
++ suggestions, bug reports and help in debugging.
++
++ * Ming Zhang <mingz@ele.uri.edu> for fixes and comments.
++
++ * Nathaniel Clark <nate@misrule.us> for fixes and comments.
++
++ * Calvin Morrow <calvin.morrow@comcast.net> for testing and useful
++ suggestions.
++
++ * Hu Gang <hugang@soulinfo.com> for the original version of the
++ LSI target driver.
++
++ * Erik Habbinga <erikhabbinga@inphase-tech.com> for fixes and support
++ of the LSI target driver.
++
++ * Ross S. W. Walker <rswwalker@hotmail.com> for BLOCKIO inspiration
++ and Vu Pham <huongvp@yahoo.com> who implemented it for VDISK dev handler.
++
++ * Alessandro Premoli <a.premoli@andxor.it> for fixes
++
++ * Nathan Bullock <nbullock@yottayotta.com> for fixes.
++
++ * Terry Greeniaus <tgreeniaus@yottayotta.com> for fixes.
++
++ * Krzysztof Blaszkowski <kb@sysmikro.com.pl> for many fixes and bug reports.
++
++ * Jianxi Chen <pacers@users.sourceforge.net> for fixing problem with
++ devices >2TB in size
++
++ * Bart Van Assche <bvanassche@acm.org> for a lot of help
++
++ * Daniel Debonzi <debonzi@linux.vnet.ibm.com> for a big part of the
++ initial SCST sysfs tree implementation
++
++
++Vladislav Bolkhovitin <vst@vlnb.net>, http://scst.sourceforge.net
+diff -uprN orig/linux-3.2/Documentation/scst/SysfsRules linux-3.2/Documentation/scst/SysfsRules
+--- orig/linux-3.2/Documentation/scst/SysfsRules
++++ linux-3.2/Documentation/scst/SysfsRules
+@@ -0,0 +1,942 @@
++ SCST SYSFS interface rules
++ ==========================
++
++This file describes SYSFS interface rules, which all SCST target
++drivers, dev handlers and management utilities MUST follow. This allows
++to have a simple, self-documented, target drivers and dev handlers
++independent management interface.
++
++Words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
++"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
++document are to be interpreted as described in RFC 2119.
++
++In this document "key attribute" means a configuration attribute with
++not default value, which must be configured during the target driver's
++initialization. A key attribute MUST have in the last line keyword
++"[key]". If a default value set to a key attribute, it becomes a regular
++none-key attribute. For instance, iSCSI target has attribute DataDigest.
++Default value for this attribute is "None". It value "CRC32C" is set to
++this attribute, it will become a key attribute. If value "None" is again
++set, this attribute will become back to a none-key attribute.
++
++Each user configurable attribute with a not default value MUST be marked
++as key attribute.
++
++Key attributes SHOULD NOT have sysfs names finished on digits, because
++such names SHOULD be used to store several attributes with the same name
++on the sysfs tree where duplicated names are not allowed. For instance,
++iSCSI targets can have several incoming user names, so the corresponding
++attribute should have sysfs name "IncomingUser". If there are 2 user
++names, they should have sysfs names "IncomingUser" and "IncomingUser1".
++In other words, all "IncomingUser[0-9]*" names should be considered as
++different instances of the same "IncomingUser" attribute.
++
++
++I. Rules for target drivers
++===========================
++
++SCST core for each target driver (struct scst_tgt_template) creates a
++root subdirectory in /sys/kernel/scst_tgt/targets with name
++scst_tgt_template.name (called "target_driver_name" further in this
++document).
++
++For each target (struct scst_tgt) SCST core creates a root subdirectory
++in /sys/kernel/scst_tgt/targets/target_driver_name with name
++scst_tgt.tgt_name (called "target_name" further in this document).
++
++There are 2 type of targets possible: hardware and virtual targets.
++Hardware targets are targets corresponding to real hardware, for
++instance, a Fibre Channel adapter's port. Virtual targets are hardware
++independent targets, which can be dynamically added or removed, for
++instance, an iSCSI target, or NPIV Fibre Channel target.
++
++A target driver supporting virtual targets MUST support "mgmt" attribute
++and "add_target"/"del_target" commands.
++
++If target driver supports both hardware and virtual targets (for
++instance, an FC adapter supporting NPIV, which has hardware targets for
++its physical ports as well as virtual NPIV targets), it MUST create each
++hardware target with hw_target mark to make SCST core create "hw_target"
++attribute (see below).
++
++Attributes for target drivers
++-----------------------------
++
++A target driver MAY support in its root subdirectory the following
++optional attributes. Target drivers MAY also support there other
++read-only or read-writable attributes.
++
++1. "enabled" - this attribute MUST allow to enable and disable target
++driver as a whole, i.e. if disabled, the target driver MUST NOT accept
++new connections. The goal of this attribute is to allow the target
++driver's initial configuration. For instance, iSCSI target may need to
++have discovery user names and passwords set before it starts serving
++discovery connections.
++
++This attribute MUST have read and write permissions for superuser and be
++read-only for other users.
++
++On read it MUST return 0, if the target driver is disabled, and 1, if it
++is enabled.
++
++On write it MUST accept '0' character as request to disable and '1' as
++request to enable, but MAY also accept other driver specific commands.
++
++During disabling the target driver MAY close already connected sessions
++in all targets, but this is OPTIONAL.
++
++MUST be 0 by default.
++
++2. "trace_level" - this attribute SHOULD allow to change log level of this
++driver.
++
++This attribute SHOULD have read and write permissions for superuser and be
++read-only for other users.
++
++On read it SHOULD return a help text about available command and log levels.
++
++On write it SHOULD accept commands to change log levels according to the
++help text.
++
++For example:
++
++out_of_mem | minor | pid | line | function | special | mgmt | mgmt_dbg | flow_control | conn
++
++Usage:
++ echo "all|none|default" >trace_level
++ echo "value DEC|0xHEX|0OCT" >trace_level
++ echo "add|del TOKEN" >trace_level
++
++where TOKEN is one of [debug, function, line, pid,
++ entryexit, buff, mem, sg, out_of_mem,
++ special, scsi, mgmt, minor,
++ mgmt_dbg, scsi_serializing,
++ retry, recv_bot, send_bot, recv_top,
++ send_top, d_read, d_write, conn, conn_dbg, iov, pdu, net_page]
++
++
++3. "version" - this read-only for all attribute SHOULD return version of
++the target driver and some info about its enabled compile time facilities.
++
++For example:
++
++2.0.0
++EXTRACHECKS
++DEBUG
++
++4. "mgmt" - if supported this attribute MUST allow to add and delete
++targets, if virtual targets are supported by this driver, as well as it
++MAY allow to add and delete the target driver's or its targets'
++attributes.
++
++This attribute MUST have read and write permissions for superuser and be
++read-only for other users.
++
++On read it MUST return a help string describing available commands,
++parameters and attributes.
++
++To achieve that the target driver should just set in its struct
++scst_tgt_template correctly the following fields: mgmt_cmd_help,
++add_target_parameters, tgtt_optional_attributes and
++tgt_optional_attributes.
++
++For example:
++
++Usage: echo "add_target target_name [parameters]" >mgmt
++ echo "del_target target_name" >mgmt
++ echo "add_attribute <attribute> <value>" >mgmt
++ echo "del_attribute <attribute> <value>" >mgmt
++ echo "add_target_attribute target_name <attribute> <value>" >mgmt
++ echo "del_target_attribute target_name <attribute> <value>" >mgmt
++
++where parameters are one or more param_name=value pairs separated by ';'
++
++The following target driver attributes available: IncomingUser, OutgoingUser
++The following target attributes available: IncomingUser, OutgoingUser, allowed_portal
++
++4.1. "add_target" - if supported, this command MUST add new target with
++name "target_name" and specified optional or required parameters. Each
++parameter MUST be in form "parameter=value". All parameters MUST be
++separated by ';' symbol.
++
++All target drivers supporting creation of virtual targets MUST support
++this command.
++
++All target drivers supporting "add_target" command MUST support all
++read-only targets' key attributes as parameters to "add_target" command
++with the attributes' names as parameters' names and the attributes'
++values as parameters' values.
++
++For example:
++
++echo "add_target TARGET1 parameter1=1; parameter2=2" >mgmt
++
++will add target with name "TARGET1" and parameters with names
++"parameter1" and "parameter2" with values 1 and 2 correspondingly.
++
++4.2. "del_target" - if supported, this command MUST delete target with
++name "target_name". If "add_target" command is supported "del_target"
++MUST also be supported.
++
++4.3. "add_attribute" - if supported, this command MUST add a target
++driver's attribute with the specified name and one or more values.
++
++All target drivers supporting run time creation of the target driver's
++key attributes MUST support this command.
++
++For example, for iSCSI target:
++
++echo "add_attribute IncomingUser name password" >mgmt
++
++will add for discovery sessions an incoming user (attribute
++/sys/kernel/scst_tgt/targets/iscsi/IncomingUser) with name "name" and
++password "password".
++
++4.4. "del_attribute" - if supported, this command MUST delete target
++driver's attribute with the specified name and values. The values MUST
++be specified, because in some cases attributes MAY internally be
++distinguished by values. For instance, iSCSI target might have several
++incoming users. If not needed, target driver might ignore the values.
++
++If "add_attribute" command is supported "del_attribute" MUST
++also be supported.
++
++4.5. "add_target_attribute" - if supported, this command MUST add new
++attribute for the specified target with the specified name and one or
++more values.
++
++All target drivers supporting run time creation of targets' key
++attributes MUST support this command.
++
++For example:
++
++echo "add_target_attribute iqn.2006-10.net.vlnb:tgt IncomingUser name password" >mgmt
++
++will add for target with name "iqn.2006-10.net.vlnb:tgt" an incoming
++user (attribute
++/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/IncomingUser)
++with name "name" and password "password".
++
++4.6. "del_target_attribute" - if supported, this command MUST delete
++target's attribute with the specified name and values. The values MUST
++be specified, because in some cases attributes MAY internally be
++distinguished by values. For instance, iSCSI target might have several
++incoming users. If not needed, target driver might ignore the values.
++
++If "add_target_attribute" command is supported "del_target_attribute"
++MUST also be supported.
++
++Attributes for targets
++----------------------
++
++Each target MAY support in its root subdirectory the following optional
++attributes. Target drivers MAY also support there other read-only or
++read-writable attributes.
++
++1. "enabled" - this attribute MUST allow to enable and disable the
++corresponding target, i.e. if disabled, the target MUST NOT accept new
++connections. The goal of this attribute is to allow the target's initial
++configuration. For instance, each target needs to have its LUNs setup
++before it starts serving initiators. Another example is iSCSI target,
++which may need to have initialized a number of iSCSI parameters before
++it starts accepting new iSCSI connections.
++
++This attribute MUST have read and write permissions for superuser and be
++read-only for other users.
++
++On read it MUST return 0, if the target is disabled, and 1, if it is
++enabled.
++
++On write it MUST accept '0' character as request to disable and '1' as
++request to enable. Other requests MUST be rejected.
++
++SCST core provides some facilities, which MUST be used to implement this
++attribute.
++
++During disabling the target driver MAY close already connected sessions
++to the target, but this is OPTIONAL.
++
++MUST be 0 by default.
++
++SCST core will automatically create for all targets the following
++attributes:
++
++1. "rel_tgt_id" - allows to read or write SCSI Relative Target Port
++Identifier attribute.
++
++2. "hw_target" - allows to distinguish hardware and virtual targets, if
++the target driver supports both.
++
++To provide OPTIONAL force close session functionality target drivers
++MUST implement it using "force_close" write only session's attribute,
++which on write to it MUST close the corresponding session.
++
++See SCST core's README for more info about those attributes.
++
++
++II. Rules for dev handlers
++==========================
++
++There are 2 types of dev handlers: parent dev handlers and children dev
++handlers. The children dev handlers depend from the parent dev handlers.
++
++SCST core for each parent dev handler (struct scst_dev_type with
++parent member with value NULL) creates a root subdirectory in
++/sys/kernel/scst_tgt/handlers with name scst_dev_type.name (called
++"dev_handler_name" further in this document).
++
++Parent dev handlers can have one or more subdirectories for children dev
++handlers with names scst_dev_type.name of them.
++
++Only one level of the dev handlers' parent/children hierarchy is
++allowed. Parent dev handlers, which support children dev handlers, MUST
++NOT handle devices and MUST be only placeholders for the children dev
++handlers.
++
++Further in this document children dev handlers or parent dev handlers,
++which don't support children, will be called "end level dev handlers".
++
++End level dev handlers can be recognized by existence of the "mgmt"
++attribute.
++
++For each device (struct scst_device) SCST core creates a root
++subdirectory in /sys/kernel/scst_tgt/devices/device_name with name
++scst_device.virt_name (called "device_name" further in this document).
++
++Attributes for dev handlers
++---------------------------
++
++Each dev handler MUST have it in its root subdirectory "mgmt" attribute,
++which MUST support "add_device" and "del_device" attributes as described
++below.
++
++Parent dev handlers and end level dev handlers without parents MAY
++support in its root subdirectory the following optional attributes. They
++MAY also support there other read-only or read-writable attributes.
++
++1. "trace_level" - this attribute SHOULD allow to change log level of this
++driver.
++
++This attribute SHOULD have read and write permissions for superuser and be
++read-only for other users.
++
++On read it SHOULD return a help text about available command and log levels.
++
++On write it SHOULD accept commands to change log levels according to the
++help text.
++
++For example:
++
++out_of_mem | minor | pid | line | function | special | mgmt | mgmt_dbg
++
++
++Usage:
++ echo "all|none|default" >trace_level
++ echo "value DEC|0xHEX|0OCT" >trace_level
++ echo "add|del TOKEN" >trace_level
++
++where TOKEN is one of [debug, function, line, pid,
++ entryexit, buff, mem, sg, out_of_mem,
++ special, scsi, mgmt, minor,
++ mgmt_dbg, scsi_serializing,
++ retry, recv_bot, send_bot, recv_top,
++ send_top]
++
++2. "version" - this read-only for all attribute SHOULD return version of
++the dev handler and some info about its enabled compile time facilities.
++
++For example:
++
++2.0.0
++EXTRACHECKS
++DEBUG
++
++End level dev handlers in their root subdirectories MUST support "mgmt"
++attribute and MAY support other read-only or read-writable attributes.
++This attribute MUST have read and write permissions for superuser and be
++read-only for other users.
++
++Attribute "mgmt" for virtual devices dev handlers
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++
++For virtual devices dev handlers "mgmt" attribute MUST allow to add and
++delete devices as well as it MAY allow to add and delete the dev
++handler's or its devices' attributes.
++
++On read it MUST return a help string describing available commands and
++parameters.
++
++To achieve that the dev handler should just set in its struct
++scst_dev_type correctly the following fields: mgmt_cmd_help,
++add_device_parameters, devt_optional_attributes and
++dev_optional_attributes.
++
++For example:
++
++Usage: echo "add_device device_name [parameters]" >mgmt
++ echo "del_device device_name" >mgmt
++ echo "add_attribute <attribute> <value>" >mgmt
++ echo "del_attribute <attribute> <value>" >mgmt
++ echo "add_device_attribute device_name <attribute> <value>" >mgmt
++ echo "del_device_attribute device_name <attribute> <value>" >mgmt
++
++where parameters are one or more param_name=value pairs separated by ';'
++
++The following parameters available: filename, blocksize, write_through, nv_cache, o_direct, read_only, removable
++The following device driver attributes available: AttributeX, AttributeY
++The following device attributes available: AttributeDX, AttributeDY
++
++1. "add_device" - this command MUST add new device with name
++"device_name" and specified optional or required parameters. Each
++parameter MUST be in form "parameter=value". All parameters MUST be
++separated by ';' symbol.
++
++All dev handlers supporting "add_device" command MUST support all
++read-only devices' key attributes as parameters to "add_device" command
++with the attributes' names as parameters' names and the attributes'
++values as parameters' values.
++
++For example:
++
++echo "add_device device1 parameter1=1; parameter2=2" >mgmt
++
++will add device with name "device1" and parameters with names
++"parameter1" and "parameter2" with values 1 and 2 correspondingly.
++
++2. "del_device" - this command MUST delete device with name
++"device_name".
++
++3. "add_attribute" - if supported, this command MUST add a device
++driver's attribute with the specified name and one or more values.
++
++All dev handlers supporting run time creation of the dev handler's
++key attributes MUST support this command.
++
++For example:
++
++echo "add_attribute AttributeX ValueX" >mgmt
++
++will add attribute
++/sys/kernel/scst_tgt/handlers/dev_handler_name/AttributeX with value ValueX.
++
++4. "del_attribute" - if supported, this command MUST delete device
++driver's attribute with the specified name and values. The values MUST
++be specified, because in some cases attributes MAY internally be
++distinguished by values. If not needed, dev handler might ignore the
++values.
++
++If "add_attribute" command is supported "del_attribute" MUST also be
++supported.
++
++5. "add_device_attribute" - if supported, this command MUST add new
++attribute for the specified device with the specified name and one or
++more values.
++
++All dev handlers supporting run time creation of devices' key attributes
++MUST support this command.
++
++For example:
++
++echo "add_device_attribute device1 AttributeDX ValueDX" >mgmt
++
++will add for device with name "device1" attribute
++/sys/kernel/scst_tgt/devices/device_name/AttributeDX) with value
++ValueDX.
++
++6. "del_device_attribute" - if supported, this command MUST delete
++device's attribute with the specified name and values. The values MUST
++be specified, because in some cases attributes MAY internally be
++distinguished by values. If not needed, dev handler might ignore the
++values.
++
++If "add_device_attribute" command is supported "del_device_attribute"
++MUST also be supported.
++
++Attribute "mgmt" for pass-through devices dev handlers
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++
++For pass-through devices dev handlers "mgmt" attribute MUST allow to
++assign and unassign this dev handler to existing SCSI devices via
++"add_device" and "del_device" commands correspondingly.
++
++On read it MUST return a help string describing available commands and
++parameters.
++
++For example:
++
++Usage: echo "add_device H:C:I:L" >mgmt
++ echo "del_device H:C:I:L" >mgmt
++
++1. "add_device" - this command MUST assign SCSI device with
++host:channel:id:lun numbers to this dev handler.
++
++All pass-through dev handlers MUST support this command.
++
++For example:
++
++echo "add_device 1:0:0:0" >mgmt
++
++will assign SCSI device 1:0:0:0 to this dev handler.
++
++2. "del_device" - this command MUST unassign SCSI device with
++host:channel:id:lun numbers from this dev handler.
++
++SCST core will automatically create for all dev handlers the following
++attributes:
++
++1. "type" - SCSI type of device this dev handler can handle.
++
++See SCST core's README for more info about those attributes.
++
++Attributes for devices
++----------------------
++
++Each device MAY support in its root subdirectory any read-only or
++read-writable attributes.
++
++SCST core will automatically create for all devices the following
++attributes:
++
++1. "type" - SCSI type of this device
++
++See SCST core's README for more info about those attributes.
++
++
++III. Rules for management utilities
++===================================
++
++Rules summary
++-------------
++
++A management utility (scstadmin) SHOULD NOT keep any knowledge specific
++to any device, dev handler, target or target driver. It SHOULD only know
++the common SCST SYSFS rules, which all dev handlers and target drivers
++MUST follow. Namely:
++
++Common rules:
++~~~~~~~~~~~~~
++
++1. All key attributes MUST be marked by mark "[key]" in the last line of
++the attribute.
++
++2. All not key attributes don't matter and SHOULD be ignored.
++
++For target drivers and targets:
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++
++1. If target driver supports adding new targets, it MUST have "mgmt"
++attribute, which MUST support "add_target" and "del_target" commands as
++specified above.
++
++2. If target driver supports run time adding new key attributes, it MUST
++have "mgmt" attribute, which MUST support "add_attribute" and
++"del_attribute" commands as specified above.
++
++3. If target driver supports both hardware and virtual targets, all its
++hardware targets MUST have "hw_target" attribute with value 1.
++
++4. If target has read-only key attributes, the add_target command MUST
++support them as parameters.
++
++5. If target supports run time adding new key attributes, the target
++driver MUST have "mgmt" attribute, which MUST support
++"add_target_attribute" and "del_target_attribute" commands as specified
++above.
++
++6. Both target drivers and targets MAY support "enable" attribute. If
++supported, after configuring the corresponding target driver or target
++"1" MUST be written to this attribute in the following order: at first,
++for all targets of the target driver, then for the target driver.
++
++For devices and dev handlers:
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++
++1. Each dev handler in its root subdirectory MUST have "mgmt" attribute.
++
++2. Each dev handler MUST support "add_device" and "del_device" commands
++to the "mgmt" attribute as specified above.
++
++3. If dev handler driver supports run time adding new key attributes, it
++MUST support "add_attribute" and "del_attribute" commands to the "mgmt"
++attribute as specified above.
++
++4. All device handlers have links in the root subdirectory pointing to
++their devices.
++
++5. If device has read-only key attributes, the "add_device" command MUST
++support them as parameters.
++
++6. If device supports run time adding new key attributes, its dev
++handler MUST support "add_device_attribute" and "del_device_attribute"
++commands to the "mgmt" attribute as specified above.
++
++7. Each device has "handler" link to its dev handler's root
++subdirectory.
++
++How to distinguish and process different types of attributes
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++
++Since management utilities only interested in key attributes, they
++should simply ignore all non-key attributes, like
++devices/device_name/type or targets/target_driver/target_name/version
++doesn't matter if they are read-only or writable. So, the word "key"
++will be omitted later in this section.
++
++At first, any attribute can be a key attribute, doesn't matter how it's
++created.
++
++All the existing on the configuration save time attributes should be
++treated the same. Management utilities shouldn't try to separate anyhow
++them in config files.
++
++1. Always existing attributes
++-----------------------------
++
++There are 2 type of them:
++
++1.1. Writable, like devices/device_name/t10_dev_id or
++targets/qla2x00tgt/target_name/explicit_confirmation. They are the
++simplest and all the values can just be read and written from/to them.
++
++On the configuration save time they can be distinguished as existing.
++
++On the write configuration time they can be distinguished as existing
++and writable.
++
++1.2. Read-only, like devices/fileio_device_name/filename or
++devices/fileio_device_name/block_size. They are also easy to distinguish
++looking at the permissions.
++
++On the configuration save time they can be distinguished the same as for
++(1.1) as existing.
++
++On the write configuration time they can be distinguished as existing
++and read-only. They all should be passed to "add_target" or
++"add_device" commands for virtual targets and devices correspondingly.
++To apply changes to them, the whole corresponding object
++(fileio_device_name in this example) should be removed then recreated.
++
++2. Optional
++-----------
++
++For instance, targets/iscsi/IncomingUser or
++targets/iscsi/target_name/IncomingUser. There are 4 types of them:
++
++2.1. Global for target drivers and dev handlers
++-----------------------------------------------
++
++For instance, targets/iscsi/IncomingUser or handlers/vdisk_fileio/XX
++(none at the moment).
++
++On the configuration save time they can be distinguished the same as for
++(1.1).
++
++On the write configuration time they can be distinguished as one of 4
++choices:
++
++2.1.1. Existing and writable. In this case they should be treated as
++(1.1)
++
++2.1.2. Existing and read-only. In this case they should be treated as
++(1.2).
++
++2.1.3. Not existing. In this case they should be added using
++"add_attribute" command.
++
++2.1.4. Existing in the sysfs tree and not existing in the config file.
++In this case they should be deleted using "del_attribute" command.
++
++2.2. Global for targets
++-----------------------
++
++For instance, targets/iscsi/target_name/IncomingUser.
++
++On the configuration save time they can be distinguished the same as (1.1).
++
++On the write configuration time they can be distinguished as one of 4
++choices:
++
++2.2.1. Existing and writable. In this case they should be treated as
++(1.1).
++
++2.2.2. Existing and read-only. In this case they should be treated as
++(1.2).
++
++2.2.3. Not existing. In this case they should be added using
++"add_target_attribute" command.
++
++2.2.4. Existing in the sysfs tree and not existing in the config file.
++In this case they should be deleted using "del_target_attribute"
++command.
++
++2.3. Global for devices
++-----------------------
++
++For instance, devices/nullio/t10_dev_id.
++
++On the configuration save time they can be distinguished the same as (1.1).
++
++On the write configuration time they can be distinguished as one of 4
++choices:
++
++2.3.1. Existing and writable. In this case they should be treated as
++(1.1)
++
++2.3.2. Existing and read-only. In this case they should be treated as
++(1.2).
++
++2.3.3. Not existing. In this case they should be added using
++"add_device_attribute" command for the corresponding handler, e.g.
++devices/nullio/handler/.
++
++2.3.4. Existing in the sysfs tree and not existing in the config file.
++In this case they should be deleted using "del_device_attribute"
++command for the corresponding handler, e.g. devices/nullio/handler/.
++
++Thus, management utility should implement only 8 procedures: (1.1),
++(1.2), (2.1.3), (2.1.4), (2.2.3), (2.2.4), (2.3.3), (2.3.4).
++
++
++How to distinguish hardware and virtual targets
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++
++A target is hardware:
++
++ * if exist both "hw_target" attribute and "mgmt" management file
++
++ * or if both don't exist
++
++A target is virtual if there is "mgmt" file and "hw_target" attribute
++doesn't exist.
++
++
++Algorithm to convert current SCST configuration to config file
++--------------------------------------------------------------
++
++A management utility SHOULD use the following algorithm when converting
++current SCST configuration to a config file.
++
++For all attributes with digits at the end the name, the digits part
++should be omitted from the attributes' names during the store. For
++instance, "IncomingUser1" should be stored as "IncomingUser".
++
++1. Scan all attributes in /sys/kernel/scst_tgt (not recursive) and store
++all found key attributes.
++
++2. Scan all subdirectories of /sys/kernel/scst_tgt/handlers. Each
++subdirectory with "mgmt" attribute is a root subdirectory of a dev
++handler with name the name of the subdirectory. For each found dev
++handler do the following:
++
++2.1. Store the dev handler's name. Store also its path to the root
++subdirectory, if it isn't default (/sys/kernel/scst_tgt/handlers/handler_name).
++
++2.2. Store all dev handler's key attributes.
++
++2.3. Go through all links in the root subdirectory pointing to
++/sys/kernel/scst_tgt/devices and for each device:
++
++2.3.1. For virtual devices dev handlers:
++
++2.3.1.1. Store the name of the device.
++
++2.3.1.2. Store all key attributes. Mark all read only key attributes
++during storing, they will be parameters for the device's creation.
++
++2.3.2. For pass-through devices dev handlers:
++
++2.3.2.1. Store the H:C:I:L name of the device. Optionally, instead of
++the name unique T10 vendor device ID found using command:
++
++sg_inq -p 0x83 /dev/sdX
++
++can be stored. It will allow to reliably find out this device if on the
++next reboot it will have another host:channel:id:lin numbers. The sdX
++device can be found as the last letters after ':' in
++/sys/kernel/scst_tgt/devices/H:C:I:L/scsi_device/device/block:sdX.
++
++3. Go through all subdirectories in /sys/kernel/scst_tgt/targets. For
++each target driver:
++
++3.1. Store the name of the target driver.
++
++3.2. Store all its key attributes.
++
++3.3. Go through all target's subdirectories. For each target:
++
++3.3.1. Store the name of the target.
++
++3.3.2. Mark if the target is hardware or virtual target. The target is a
++hardware target if it has "hw_target" attribute or its target driver
++doesn't have "mgmt" attribute.
++
++3.3.3. Store all key attributes. Mark all read only key attributes
++during storing, they will be parameters for the target's creation.
++
++3.3.4. Scan all "luns" subdirectory and store:
++
++ - LUN.
++
++ - LU's device name.
++
++ - Key attributes.
++
++3.3.5. Scan all "ini_groups" subdirectories. For each group store the following:
++
++ - The group's name.
++
++ - The group's LUNs (the same info as for 3.3.4).
++
++ - The group's initiators.
++
++3.3.6. Store value of "enabled" attribute, if it exists.
++
++3.4. Store value of "enabled" attribute, if it exists.
++
++
++Algorithm to initialize SCST from config file
++---------------------------------------------
++
++A management utility SHOULD use the following algorithm when doing
++initial SCST configuration from a config file. All necessary kernel
++modules and user space programs supposed to be already loaded, hence all
++dev handlers' entries in /sys/kernel/scst_tgt/handlers as well as all
++entries for hardware targets already created.
++
++1. Set stored values for all stored global (/sys/kernel/scst_tgt)
++attributes.
++
++2. For each dev driver:
++
++2.1. Set stored values for all already existing stored attributes.
++
++2.2. Create not existing stored attributes using "add_attribute" command.
++
++2.3. For virtual devices dev handlers for each stored device:
++
++2.3.1. Create the device using "add_device" command using marked read
++only attributes as parameters.
++
++2.3.2. Set stored values for all already existing stored attributes.
++
++2.3.3. Create not existing stored attributes using
++"add_device_attribute" command.
++
++2.4. For pass-through dev handlers for each stores device:
++
++2.4.1. Assign the corresponding pass-through device to this dev handler
++using "add_device" command.
++
++3. For each target driver:
++
++3.1. Set stored values for all already existing stored attributes.
++
++3.2. Create not existing stored attributes using "add_attribute" command.
++
++3.3. For each target:
++
++3.3.1. For virtual targets:
++
++3.3.1.1. Create the target using "add_target" command using marked read
++only attributes as parameters.
++
++3.3.1.2. Set stored values for all already existing stored attributes.
++
++3.3.1.3. Create not existing stored attributes using
++"add_target_attribute" command.
++
++3.3.2. For hardware targets for each target:
++
++3.3.2.1. Set stored values for all already existing stored attributes.
++
++3.3.2.2. Create not existing stored attributes using
++"add_target_attribute" command.
++
++3.3.3. Setup LUNs
++
++3.3.4. Setup ini_groups, their LUNs and initiators' names.
++
++3.3.5. If this target supports enabling, enable it.
++
++3.4. If this target driver supports enabling, enable it.
++
++
++Algorithm to apply changes in config file to currently running SCST
++-------------------------------------------------------------------
++
++A management utility SHOULD use the following algorithm when applying
++changes in config file to currently running SCST.
++
++Not all changes can be applied on enabled targets or enabled target
++drivers. From other side, for some target drivers enabling/disabling is
++a very long and disruptive operation, which should be performed as rare
++as possible. Thus, the management utility SHOULD support additional
++option, which, if set, will make it to disable all affected targets
++before doing any change with them.
++
++1. Scan all attributes in /sys/kernel/scst_tgt (not recursive) and
++compare stored and actual key attributes. Apply all changes.
++
++2. Scan all subdirectories of /sys/kernel/scst_tgt/handlers. Each
++subdirectory with "mgmt" attribute is a root subdirectory of a dev
++handler with name the name of the subdirectory. For each found dev
++handler do the following:
++
++2.1. Compare stored and actual key attributes. Apply all changes. Create
++new attributes using "add_attribute" commands and delete not needed any
++more attributes using "del_attribute" command.
++
++2.2. Compare existing devices (links in the root subdirectory pointing
++to /sys/kernel/scst_tgt/devices) and stored devices in the config file.
++Delete all not needed devices and create new devices.
++
++2.3. For all existing devices:
++
++2.3.1. Compare stored and actual key attributes. Apply all changes.
++Create new attributes using "add_device_attribute" commands and delete
++not needed any more attributes using "del_device_attribute" command.
++
++2.3.2. If any read only key attribute for virtual device should be
++changed, delete the devices and recreate it.
++
++3. Go through all subdirectories in /sys/kernel/scst_tgt/targets. For
++each target driver:
++
++3.1. If this target driver should be disabled, disable it.
++
++3.2. Compare stored and actual key attributes. Apply all changes. Create
++new attributes using "add_attribute" commands and delete not needed any
++more attributes using "del_attribute" command.
++
++3.3. Go through all target's subdirectories. Compare existing and stored
++targets. Delete all not needed targets and create new targets.
++
++3.4. For all existing targets:
++
++3.4.1. If this target should be disabled, disable it.
++
++3.4.2. Compare stored and actual key attributes. Apply all changes.
++Create new attributes using "add_target_attribute" commands and delete
++not needed any more attributes using "del_target_attribute" command.
++
++3.4.3. If any read only key attribute for virtual target should be
++changed, delete the target and recreate it.
++
++3.4.4. Scan all "luns" subdirectory and apply necessary changes, using
++"replace" commands to replace one LUN by another, if needed.
++
++3.4.5. Scan all "ini_groups" subdirectories and apply necessary changes,
++using "replace" commands to replace one LUN by another and "move"
++command to move initiator from one group to another, if needed. It MUST
++be done in the following order:
++
++ - Necessary initiators deleted, if they aren't going to be moved
++
++ - LUNs updated
++
++ - Necessary initiators added or moved
++
++3.4.6. If this target should be enabled, enable it.
++
++3.5. If this target driver should be enabled, enable it.
++
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/Makefile linux-3.2/drivers/scst/dev_handlers/Makefile
+--- orig/linux-3.2/drivers/scst/dev_handlers/Makefile
++++ linux-3.2/drivers/scst/dev_handlers/Makefile
+@@ -0,0 +1,14 @@
++ccflags-y += -Wno-unused-parameter
++
++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_user.o
++
++obj-$(CONFIG_SCST_DISK) += scst_disk.o
++obj-$(CONFIG_SCST_TAPE) += scst_tape.o
++obj-$(CONFIG_SCST_CDROM) += scst_cdrom.o
++obj-$(CONFIG_SCST_MODISK) += scst_modisk.o
++obj-$(CONFIG_SCST_CHANGER) += scst_changer.o
++obj-$(CONFIG_SCST_RAID) += scst_raid.o
++obj-$(CONFIG_SCST_PROCESSOR) += scst_processor.o
++obj-$(CONFIG_SCST_VDISK) += scst_vdisk.o
++obj-$(CONFIG_SCST_USER) += scst_user.o
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_cdrom.c linux-3.2/drivers/scst/dev_handlers/scst_cdrom.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_cdrom.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_cdrom.c
+@@ -0,0 +1,263 @@
++/*
++ * scst_cdrom.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * SCSI CDROM (type 5) dev 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/cdrom.h>
++#include <scsi/scsi_host.h>
++#include <linux/slab.h>
++
++#define LOG_PREFIX "dev_cdrom"
++
++#include <scst/scst.h>
++#include "scst_dev_handler.h"
++
++#define CDROM_NAME "dev_cdrom"
++
++#define CDROM_DEF_BLOCK_SHIFT 11
++
++struct cdrom_params {
++ int block_shift;
++};
++
++static int cdrom_attach(struct scst_device *);
++static void cdrom_detach(struct scst_device *);
++static int cdrom_parse(struct scst_cmd *);
++static int cdrom_done(struct scst_cmd *);
++
++static struct scst_dev_type cdrom_devtype = {
++ .name = CDROM_NAME,
++ .type = TYPE_ROM,
++ .threads_num = 1,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = cdrom_attach,
++ .detach = cdrom_detach,
++ .parse = cdrom_parse,
++ .dev_done = cdrom_done,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static int cdrom_attach(struct scst_device *dev)
++{
++ int res, rc;
++ uint8_t cmd[10];
++ const int buffer_size = 512;
++ uint8_t *buffer = NULL;
++ int retries;
++ unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE];
++ enum dma_data_direction data_dir;
++ struct cdrom_params *params;
++
++ TRACE_ENTRY();
++
++ if (dev->scsi_dev == NULL ||
++ dev->scsi_dev->type != dev->type) {
++ PRINT_ERROR("%s", "SCSI device not define or illegal type");
++ res = -ENODEV;
++ goto out;
++ }
++
++ params = kzalloc(sizeof(*params), GFP_KERNEL);
++ if (params == NULL) {
++ PRINT_ERROR("Unable to allocate struct cdrom_params (size %zd)",
++ sizeof(*params));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ buffer = kmalloc(buffer_size, GFP_KERNEL);
++ if (!buffer) {
++ PRINT_ERROR("Buffer memory allocation (size %d) failure",
++ buffer_size);
++ res = -ENOMEM;
++ goto out_free_params;
++ }
++
++ /* Clear any existing UA's and get cdrom capacity (cdrom block size) */
++ memset(cmd, 0, sizeof(cmd));
++ cmd[0] = READ_CAPACITY;
++ cmd[1] = (dev->scsi_dev->scsi_level <= SCSI_2) ?
++ ((dev->scsi_dev->lun << 5) & 0xe0) : 0;
++ retries = SCST_DEV_UA_RETRIES;
++ while (1) {
++ memset(buffer, 0, buffer_size);
++ memset(sense_buffer, 0, sizeof(sense_buffer));
++ data_dir = SCST_DATA_READ;
++
++ TRACE_DBG("%s", "Doing READ_CAPACITY");
++ rc = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
++ buffer_size, sense_buffer,
++ SCST_GENERIC_CDROM_REG_TIMEOUT, 3, 0
++ , NULL
++ );
++
++ TRACE_DBG("READ_CAPACITY done: %x", rc);
++
++ if ((rc == 0) ||
++ !scst_analyze_sense(sense_buffer,
++ sizeof(sense_buffer), SCST_SENSE_KEY_VALID,
++ UNIT_ATTENTION, 0, 0))
++ break;
++
++ if (!--retries) {
++ PRINT_ERROR("UA not cleared after %d retries",
++ SCST_DEV_UA_RETRIES);
++ params->block_shift = CDROM_DEF_BLOCK_SHIFT;
++ res = -ENODEV;
++ goto out_free_buf;
++ }
++ }
++
++ if (rc == 0) {
++ int sector_size = ((buffer[4] << 24) | (buffer[5] << 16) |
++ (buffer[6] << 8) | (buffer[7] << 0));
++ if (sector_size == 0)
++ params->block_shift = CDROM_DEF_BLOCK_SHIFT;
++ else
++ params->block_shift =
++ scst_calc_block_shift(sector_size);
++ TRACE_DBG("Sector size is %i scsi_level %d(SCSI_2 %d)",
++ sector_size, dev->scsi_dev->scsi_level, SCSI_2);
++ } else {
++ params->block_shift = CDROM_DEF_BLOCK_SHIFT;
++ TRACE(TRACE_MINOR, "Read capacity failed: %x, using default "
++ "sector size %d", rc, params->block_shift);
++ PRINT_BUFF_FLAG(TRACE_MINOR, "Returned sense", sense_buffer,
++ sizeof(sense_buffer));
++ }
++
++ res = scst_obtain_device_parameters(dev);
++ if (res != 0) {
++ PRINT_ERROR("Failed to obtain control parameters for device "
++ "%s", dev->virt_name);
++ goto out_free_buf;
++ }
++
++out_free_buf:
++ kfree(buffer);
++
++out_free_params:
++ if (res == 0)
++ dev->dh_priv = params;
++ else
++ kfree(params);
++
++out:
++ TRACE_EXIT();
++ return res;
++}
++
++static void cdrom_detach(struct scst_device *dev)
++{
++ struct cdrom_params *params =
++ (struct cdrom_params *)dev->dh_priv;
++
++ TRACE_ENTRY();
++
++ kfree(params);
++ dev->dh_priv = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++static int cdrom_get_block_shift(struct scst_cmd *cmd)
++{
++ struct cdrom_params *params = (struct cdrom_params *)cmd->dev->dh_priv;
++ /*
++ * No need for locks here, since *_detach() can not be
++ * called, when there are existing commands.
++ */
++ return params->block_shift;
++}
++
++static int cdrom_parse(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ scst_cdrom_generic_parse(cmd, cdrom_get_block_shift);
++
++ cmd->retries = SCST_PASSTHROUGH_RETRIES;
++
++ return res;
++}
++
++static void cdrom_set_block_shift(struct scst_cmd *cmd, int block_shift)
++{
++ struct cdrom_params *params = (struct cdrom_params *)cmd->dev->dh_priv;
++ /*
++ * No need for locks here, since *_detach() can not be
++ * called, when there are existing commands.
++ */
++ if (block_shift != 0)
++ params->block_shift = block_shift;
++ else
++ params->block_shift = CDROM_DEF_BLOCK_SHIFT;
++ return;
++}
++
++static int cdrom_done(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ TRACE_ENTRY();
++
++ res = scst_block_generic_dev_done(cmd, cdrom_set_block_shift);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int __init cdrom_init(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ cdrom_devtype.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&cdrom_devtype);
++ if (res < 0)
++ goto out;
++
++out:
++ TRACE_EXIT();
++ return res;
++
++}
++
++static void __exit cdrom_exit(void)
++{
++ TRACE_ENTRY();
++ scst_unregister_dev_driver(&cdrom_devtype);
++ TRACE_EXIT();
++ return;
++}
++
++module_init(cdrom_init);
++module_exit(cdrom_exit);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
++MODULE_DESCRIPTION("SCSI CDROM (type 5) dev handler for SCST");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_changer.c linux-3.2/drivers/scst/dev_handlers/scst_changer.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_changer.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_changer.c
+@@ -0,0 +1,183 @@
++/*
++ * scst_changer.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * SCSI medium changer (type 8) dev 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 <scsi/scsi_host.h>
++#include <linux/slab.h>
++
++#define LOG_PREFIX "dev_changer"
++
++#include <scst/scst.h>
++#include "scst_dev_handler.h"
++
++#define CHANGER_NAME "dev_changer"
++
++#define CHANGER_RETRIES 2
++
++static int changer_attach(struct scst_device *);
++/* static void changer_detach(struct scst_device *); */
++static int changer_parse(struct scst_cmd *);
++/* static int changer_done(struct scst_cmd *); */
++
++static struct scst_dev_type changer_devtype = {
++ .name = CHANGER_NAME,
++ .type = TYPE_MEDIUM_CHANGER,
++ .threads_num = 1,
++ .parse_atomic = 1,
++/* .dev_done_atomic = 1, */
++ .attach = changer_attach,
++/* .detach = changer_detach, */
++ .parse = changer_parse,
++/* .dev_done = changer_done */
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static int changer_attach(struct scst_device *dev)
++{
++ int res, rc;
++ int retries;
++
++ TRACE_ENTRY();
++
++ if (dev->scsi_dev == NULL ||
++ dev->scsi_dev->type != dev->type) {
++ PRINT_ERROR("%s", "SCSI device not define or illegal type");
++ res = -ENODEV;
++ goto out;
++ }
++
++ /*
++ * If the device is offline, don't try to read capacity or any
++ * of the other stuff
++ */
++ if (dev->scsi_dev->sdev_state == SDEV_OFFLINE) {
++ TRACE_DBG("%s", "Device is offline");
++ res = -ENODEV;
++ goto out;
++ }
++
++ retries = SCST_DEV_UA_RETRIES;
++ do {
++ TRACE_DBG("%s", "Doing TEST_UNIT_READY");
++ rc = scsi_test_unit_ready(dev->scsi_dev,
++ SCST_GENERIC_CHANGER_TIMEOUT, CHANGER_RETRIES
++ , NULL);
++ TRACE_DBG("TEST_UNIT_READY done: %x", rc);
++ } while ((--retries > 0) && rc);
++
++ if (rc) {
++ PRINT_WARNING("Unit not ready: %x", rc);
++ /* Let's try not to be too smart and continue processing */
++ }
++
++ res = scst_obtain_device_parameters(dev);
++ if (res != 0) {
++ PRINT_ERROR("Failed to obtain control parameters for device "
++ "%s", dev->virt_name);
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_HRES(res);
++ return res;
++}
++
++#if 0
++void changer_detach(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ TRACE_EXIT();
++ return;
++}
++#endif
++
++static int changer_parse(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ scst_changer_generic_parse(cmd, NULL);
++
++ cmd->retries = SCST_PASSTHROUGH_RETRIES;
++
++ return res;
++}
++
++#if 0
++int changer_done(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->is_send_status and
++ * cmd->resp_data_len based on cmd->status and cmd->data_direction,
++ * therefore change them only if necessary
++ */
++
++#if 0
++ switch (cmd->cdb[0]) {
++ default:
++ /* It's all good */
++ break;
++ }
++#endif
++
++ TRACE_EXIT();
++ return res;
++}
++#endif
++
++static int __init changer_init(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ changer_devtype.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&changer_devtype);
++ if (res < 0)
++ goto out;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void __exit changer_exit(void)
++{
++ TRACE_ENTRY();
++ scst_unregister_dev_driver(&changer_devtype);
++ TRACE_EXIT();
++ return;
++}
++
++module_init(changer_init);
++module_exit(changer_exit);
++
++MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCSI medium changer (type 8) dev handler for SCST");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_dev_handler.h linux-3.2/drivers/scst/dev_handlers/scst_dev_handler.h
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_dev_handler.h
++++ linux-3.2/drivers/scst/dev_handlers/scst_dev_handler.h
+@@ -0,0 +1,27 @@
++#ifndef __SCST_DEV_HANDLER_H
++#define __SCST_DEV_HANDLER_H
++
++#include <linux/module.h>
++#include <scsi/scsi_eh.h>
++#include <scst/scst_debug.h>
++
++#define SCST_DEV_UA_RETRIES 5
++#define SCST_PASSTHROUGH_RETRIES 0
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++
++#ifdef CONFIG_SCST_DEBUG
++#define SCST_DEFAULT_DEV_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_PID | \
++ TRACE_LINE | TRACE_FUNCTION | TRACE_MGMT | TRACE_MINOR | \
++ TRACE_MGMT_DEBUG | TRACE_SPECIAL)
++#else
++#define SCST_DEFAULT_DEV_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \
++ TRACE_SPECIAL)
++#endif
++
++static unsigned long dh_trace_flag = SCST_DEFAULT_DEV_LOG_FLAGS;
++#define trace_flag dh_trace_flag
++
++#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */
++
++#endif /* __SCST_DEV_HANDLER_H */
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_disk.c linux-3.2/drivers/scst/dev_handlers/scst_disk.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_disk.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_disk.c
+@@ -0,0 +1,692 @@
++/*
++ * scst_disk.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * SCSI disk (type 0) dev handler
++ * &
++ * SCSI disk (type 0) "performance" device handler (skip all READ and WRITE
++ * operations).
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/blkdev.h>
++#include <scsi/scsi_host.h>
++#include <linux/slab.h>
++#include <asm/unaligned.h>
++
++#define LOG_PREFIX "dev_disk"
++
++#include <scst/scst.h>
++#include "scst_dev_handler.h"
++
++# define DISK_NAME "dev_disk"
++# define DISK_PERF_NAME "dev_disk_perf"
++
++#define DISK_DEF_BLOCK_SHIFT 9
++
++struct disk_params {
++ int block_shift;
++};
++
++static int disk_attach(struct scst_device *dev);
++static void disk_detach(struct scst_device *dev);
++static int disk_parse(struct scst_cmd *cmd);
++static int disk_perf_exec(struct scst_cmd *cmd);
++static int disk_done(struct scst_cmd *cmd);
++static int disk_exec(struct scst_cmd *cmd);
++static bool disk_on_sg_tablesize_low(struct scst_cmd *cmd);
++
++static struct scst_dev_type disk_devtype = {
++ .name = DISK_NAME,
++ .type = TYPE_DISK,
++ .threads_num = 1,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = disk_attach,
++ .detach = disk_detach,
++ .parse = disk_parse,
++ .exec = disk_exec,
++ .on_sg_tablesize_low = disk_on_sg_tablesize_low,
++ .dev_done = disk_done,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static struct scst_dev_type disk_devtype_perf = {
++ .name = DISK_PERF_NAME,
++ .type = TYPE_DISK,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = disk_attach,
++ .detach = disk_detach,
++ .parse = disk_parse,
++ .exec = disk_perf_exec,
++ .dev_done = disk_done,
++ .on_sg_tablesize_low = disk_on_sg_tablesize_low,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static int __init init_scst_disk_driver(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ disk_devtype.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&disk_devtype);
++ if (res < 0)
++ goto out;
++
++ disk_devtype_perf.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&disk_devtype_perf);
++ if (res < 0)
++ goto out_unreg;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unreg:
++ scst_unregister_dev_driver(&disk_devtype);
++ goto out;
++}
++
++static void __exit exit_scst_disk_driver(void)
++{
++ TRACE_ENTRY();
++
++ scst_unregister_dev_driver(&disk_devtype_perf);
++ scst_unregister_dev_driver(&disk_devtype);
++
++ TRACE_EXIT();
++ return;
++}
++
++module_init(init_scst_disk_driver);
++module_exit(exit_scst_disk_driver);
++
++static int disk_attach(struct scst_device *dev)
++{
++ int res, rc;
++ uint8_t cmd[10];
++ const int buffer_size = 512;
++ uint8_t *buffer = NULL;
++ int retries;
++ unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE];
++ enum dma_data_direction data_dir;
++ struct disk_params *params;
++
++ TRACE_ENTRY();
++
++ if (dev->scsi_dev == NULL ||
++ dev->scsi_dev->type != dev->type) {
++ PRINT_ERROR("%s", "SCSI device not define or illegal type");
++ res = -ENODEV;
++ goto out;
++ }
++
++ params = kzalloc(sizeof(*params), GFP_KERNEL);
++ if (params == NULL) {
++ PRINT_ERROR("Unable to allocate struct disk_params (size %zd)",
++ sizeof(*params));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ buffer = kmalloc(buffer_size, GFP_KERNEL);
++ if (!buffer) {
++ PRINT_ERROR("Buffer memory allocation (size %d) failure",
++ buffer_size);
++ res = -ENOMEM;
++ goto out_free_params;
++ }
++
++ /* Clear any existing UA's and get disk capacity (disk block size) */
++ memset(cmd, 0, sizeof(cmd));
++ cmd[0] = READ_CAPACITY;
++ cmd[1] = (dev->scsi_dev->scsi_level <= SCSI_2) ?
++ ((dev->scsi_dev->lun << 5) & 0xe0) : 0;
++ retries = SCST_DEV_UA_RETRIES;
++ while (1) {
++ memset(buffer, 0, buffer_size);
++ memset(sense_buffer, 0, sizeof(sense_buffer));
++ data_dir = SCST_DATA_READ;
++
++ TRACE_DBG("%s", "Doing READ_CAPACITY");
++ rc = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
++ buffer_size, sense_buffer,
++ SCST_GENERIC_DISK_REG_TIMEOUT, 3, 0
++ , NULL
++ );
++
++ TRACE_DBG("READ_CAPACITY done: %x", rc);
++
++ if ((rc == 0) ||
++ !scst_analyze_sense(sense_buffer,
++ sizeof(sense_buffer), SCST_SENSE_KEY_VALID,
++ UNIT_ATTENTION, 0, 0))
++ break;
++ if (!--retries) {
++ PRINT_ERROR("UA not clear after %d retries",
++ SCST_DEV_UA_RETRIES);
++ res = -ENODEV;
++ goto out_free_buf;
++ }
++ }
++ if (rc == 0) {
++ int sector_size = ((buffer[4] << 24) | (buffer[5] << 16) |
++ (buffer[6] << 8) | (buffer[7] << 0));
++ if (sector_size == 0)
++ params->block_shift = DISK_DEF_BLOCK_SHIFT;
++ else
++ params->block_shift =
++ scst_calc_block_shift(sector_size);
++ } else {
++ params->block_shift = DISK_DEF_BLOCK_SHIFT;
++ TRACE(TRACE_MINOR, "Read capacity failed: %x, using default "
++ "sector size %d", rc, params->block_shift);
++ PRINT_BUFF_FLAG(TRACE_MINOR, "Returned sense", sense_buffer,
++ sizeof(sense_buffer));
++ }
++
++ res = scst_obtain_device_parameters(dev);
++ if (res != 0) {
++ PRINT_ERROR("Failed to obtain control parameters for device "
++ "%s", dev->virt_name);
++ goto out_free_buf;
++ }
++
++out_free_buf:
++ kfree(buffer);
++
++out_free_params:
++ if (res == 0)
++ dev->dh_priv = params;
++ else
++ kfree(params);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void disk_detach(struct scst_device *dev)
++{
++ struct disk_params *params =
++ (struct disk_params *)dev->dh_priv;
++
++ TRACE_ENTRY();
++
++ kfree(params);
++ dev->dh_priv = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++static int disk_get_block_shift(struct scst_cmd *cmd)
++{
++ struct disk_params *params = (struct disk_params *)cmd->dev->dh_priv;
++ /*
++ * No need for locks here, since *_detach() can not be
++ * called, when there are existing commands.
++ */
++ return params->block_shift;
++}
++
++static int disk_parse(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ scst_sbc_generic_parse(cmd, disk_get_block_shift);
++
++ cmd->retries = SCST_PASSTHROUGH_RETRIES;
++
++ return res;
++}
++
++static void disk_set_block_shift(struct scst_cmd *cmd, int block_shift)
++{
++ struct disk_params *params = (struct disk_params *)cmd->dev->dh_priv;
++ /*
++ * No need for locks here, since *_detach() can not be
++ * called, when there are existing commands.
++ */
++ if (block_shift != 0)
++ params->block_shift = block_shift;
++ else
++ params->block_shift = DISK_DEF_BLOCK_SHIFT;
++ return;
++}
++
++static int disk_done(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ TRACE_ENTRY();
++
++ res = scst_block_generic_dev_done(cmd, disk_set_block_shift);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static bool disk_on_sg_tablesize_low(struct scst_cmd *cmd)
++{
++ bool res;
++
++ TRACE_ENTRY();
++
++ switch (cmd->cdb[0]) {
++ case WRITE_6:
++ case READ_6:
++ case WRITE_10:
++ case READ_10:
++ case WRITE_VERIFY:
++ case WRITE_12:
++ case READ_12:
++ case WRITE_VERIFY_12:
++ case WRITE_16:
++ case READ_16:
++ case WRITE_VERIFY_16:
++ res = true;
++ /* See comment in disk_exec */
++ cmd->inc_expected_sn_on_done = 1;
++ break;
++ default:
++ res = false;
++ break;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++struct disk_work {
++ struct scst_cmd *cmd;
++ struct completion disk_work_cmpl;
++ int result;
++ unsigned int left;
++ uint64_t save_lba;
++ unsigned int save_len;
++ struct scatterlist *save_sg;
++ int save_sg_cnt;
++};
++
++static int disk_cdb_get_transfer_data(const uint8_t *cdb,
++ uint64_t *out_lba, unsigned int *out_length)
++{
++ int res;
++ uint64_t lba;
++ unsigned int len;
++
++ TRACE_ENTRY();
++
++ switch (cdb[0]) {
++ case WRITE_6:
++ case READ_6:
++ lba = be16_to_cpu(get_unaligned((__be16 *)&cdb[2]));
++ len = cdb[4];
++ break;
++ case WRITE_10:
++ case READ_10:
++ case WRITE_VERIFY:
++ lba = be32_to_cpu(get_unaligned((__be32 *)&cdb[2]));
++ len = be16_to_cpu(get_unaligned((__be16 *)&cdb[7]));
++ break;
++ case WRITE_12:
++ case READ_12:
++ case WRITE_VERIFY_12:
++ lba = be32_to_cpu(get_unaligned((__be32 *)&cdb[2]));
++ len = be32_to_cpu(get_unaligned((__be32 *)&cdb[6]));
++ break;
++ case WRITE_16:
++ case READ_16:
++ case WRITE_VERIFY_16:
++ lba = be64_to_cpu(get_unaligned((__be64 *)&cdb[2]));
++ len = be32_to_cpu(get_unaligned((__be32 *)&cdb[10]));
++ break;
++ default:
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = 0;
++ *out_lba = lba;
++ *out_length = len;
++
++ TRACE_DBG("LBA %lld, length %d", (unsigned long long)lba, len);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int disk_cdb_set_transfer_data(uint8_t *cdb,
++ uint64_t lba, unsigned int len)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ switch (cdb[0]) {
++ case WRITE_6:
++ case READ_6:
++ put_unaligned(cpu_to_be16(lba), (__be16 *)&cdb[2]);
++ cdb[4] = len;
++ break;
++ case WRITE_10:
++ case READ_10:
++ case WRITE_VERIFY:
++ put_unaligned(cpu_to_be32(lba), (__be32 *)&cdb[2]);
++ put_unaligned(cpu_to_be16(len), (__be16 *)&cdb[7]);
++ break;
++ case WRITE_12:
++ case READ_12:
++ case WRITE_VERIFY_12:
++ put_unaligned(cpu_to_be32(lba), (__be32 *)&cdb[2]);
++ put_unaligned(cpu_to_be32(len), (__be32 *)&cdb[6]);
++ break;
++ case WRITE_16:
++ case READ_16:
++ case WRITE_VERIFY_16:
++ put_unaligned(cpu_to_be64(lba), (__be64 *)&cdb[2]);
++ put_unaligned(cpu_to_be32(len), (__be32 *)&cdb[10]);
++ break;
++ default:
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = 0;
++
++ TRACE_DBG("LBA %lld, length %d", (unsigned long long)lba, len);
++ TRACE_BUFFER("New CDB", cdb, SCST_MAX_CDB_SIZE);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void disk_restore_sg(struct disk_work *work)
++{
++ disk_cdb_set_transfer_data(work->cmd->cdb, work->save_lba, work->save_len);
++ work->cmd->sg = work->save_sg;
++ work->cmd->sg_cnt = work->save_sg_cnt;
++ return;
++}
++
++static void disk_cmd_done(void *data, char *sense, int result, int resid)
++{
++ struct disk_work *work = data;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("work %p, cmd %p, left %d, result %d, sense %p, resid %d",
++ work, work->cmd, work->left, result, sense, resid);
++
++ if (result == SAM_STAT_GOOD)
++ goto out_complete;
++
++ work->result = result;
++
++ disk_restore_sg(work);
++
++ scst_pass_through_cmd_done(work->cmd, sense, result, resid + work->left);
++
++out_complete:
++ complete_all(&work->disk_work_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Executes command and split CDB, if necessary */
++static int disk_exec(struct scst_cmd *cmd)
++{
++ int res, rc;
++ struct disk_params *params = (struct disk_params *)cmd->dev->dh_priv;
++ struct disk_work work;
++ unsigned int offset, cur_len; /* in blocks */
++ struct scatterlist *sg, *start_sg;
++ int cur_sg_cnt;
++ int sg_tablesize = cmd->dev->scsi_dev->host->sg_tablesize;
++ int max_sectors;
++ int num, j;
++
++ TRACE_ENTRY();
++
++ /*
++ * For PC requests we are going to submit max_hw_sectors used instead
++ * of max_sectors.
++ */
++ max_sectors = queue_max_hw_sectors(cmd->dev->scsi_dev->request_queue);
++
++ if (unlikely(((max_sectors << params->block_shift) & ~PAGE_MASK) != 0)) {
++ int mlen = max_sectors << params->block_shift;
++ int pg = ((mlen >> PAGE_SHIFT) + ((mlen & ~PAGE_MASK) != 0)) - 1;
++ int adj_len = pg << PAGE_SHIFT;
++ max_sectors = adj_len >> params->block_shift;
++ if (max_sectors == 0) {
++ PRINT_ERROR("Too low max sectors %d", max_sectors);
++ goto out_error;
++ }
++ }
++
++ if (unlikely((cmd->bufflen >> params->block_shift) > max_sectors)) {
++ if ((cmd->out_bufflen >> params->block_shift) > max_sectors) {
++ PRINT_ERROR("Too limited max_sectors %d for "
++ "bidirectional cmd %x (out_bufflen %d)",
++ max_sectors, cmd->cdb[0], cmd->out_bufflen);
++ /* Let lower level handle it */
++ res = SCST_EXEC_NOT_COMPLETED;
++ goto out;
++ }
++ goto split;
++ }
++
++ if (likely(cmd->sg_cnt <= sg_tablesize)) {
++ res = SCST_EXEC_NOT_COMPLETED;
++ goto out;
++ }
++
++split:
++ BUG_ON(cmd->out_sg_cnt > sg_tablesize);
++ BUG_ON((cmd->out_bufflen >> params->block_shift) > max_sectors);
++
++ /*
++ * We don't support changing BIDI CDBs (see disk_on_sg_tablesize_low()),
++ * so use only sg_cnt
++ */
++
++ memset(&work, 0, sizeof(work));
++ work.cmd = cmd;
++ work.save_sg = cmd->sg;
++ work.save_sg_cnt = cmd->sg_cnt;
++ rc = disk_cdb_get_transfer_data(cmd->cdb, &work.save_lba,
++ &work.save_len);
++ if (rc != 0)
++ goto out_error;
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++
++ TRACE_DBG("cmd %p, save_sg %p, save_sg_cnt %d, save_lba %lld, "
++ "save_len %d (sg_tablesize %d, max_sectors %d, block_shift %d, "
++ "sizeof(*sg) 0x%zx)", cmd, work.save_sg, work.save_sg_cnt,
++ (unsigned long long)work.save_lba, work.save_len,
++ sg_tablesize, max_sectors, params->block_shift, sizeof(*sg));
++
++ /*
++ * If we submit all chunks async'ly, it will be very not trivial what
++ * to do if several of them finish with sense or residual. So, let's
++ * do it synchronously.
++ */
++
++ num = 1;
++ j = 0;
++ offset = 0;
++ cur_len = 0;
++ sg = work.save_sg;
++ start_sg = sg;
++ cur_sg_cnt = 0;
++ while (1) {
++ unsigned int l;
++
++ if (unlikely(sg_is_chain(&sg[j]))) {
++ bool reset_start_sg = (start_sg == &sg[j]);
++ sg = sg_chain_ptr(&sg[j]);
++ j = 0;
++ if (reset_start_sg)
++ start_sg = sg;
++ }
++
++ l = sg[j].length >> params->block_shift;
++ cur_len += l;
++ cur_sg_cnt++;
++
++ TRACE_DBG("l %d, j %d, num %d, offset %d, cur_len %d, "
++ "cur_sg_cnt %d, start_sg %p", l, j, num, offset,
++ cur_len, cur_sg_cnt, start_sg);
++
++ if (((num % sg_tablesize) == 0) ||
++ (num == work.save_sg_cnt) ||
++ (cur_len >= max_sectors)) {
++ TRACE_DBG("%s", "Execing...");
++
++ disk_cdb_set_transfer_data(cmd->cdb,
++ work.save_lba + offset, cur_len);
++ cmd->sg = start_sg;
++ cmd->sg_cnt = cur_sg_cnt;
++
++ work.left = work.save_len - (offset + cur_len);
++ init_completion(&work.disk_work_cmpl);
++
++ rc = scst_scsi_exec_async(cmd, &work, disk_cmd_done);
++ if (unlikely(rc != 0)) {
++ PRINT_ERROR("scst_scsi_exec_async() failed: %d",
++ rc);
++ goto out_err_restore;
++ }
++
++ wait_for_completion(&work.disk_work_cmpl);
++
++ if (work.result != SAM_STAT_GOOD) {
++ /* cmd can be already dead */
++ res = SCST_EXEC_COMPLETED;
++ goto out;
++ }
++
++ offset += cur_len;
++ cur_len = 0;
++ cur_sg_cnt = 0;
++ start_sg = &sg[j+1];
++
++ if (num == work.save_sg_cnt)
++ break;
++ }
++ num++;
++ j++;
++ }
++
++ cmd->completed = 1;
++
++out_restore:
++ disk_restore_sg(&work);
++
++out_done:
++ res = SCST_EXEC_COMPLETED;
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_err_restore:
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_restore;
++
++out_error:
++ scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out_done;
++}
++
++static int disk_perf_exec(struct scst_cmd *cmd)
++{
++ int res, rc;
++ int opcode = cmd->cdb[0];
++
++ TRACE_ENTRY();
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++
++ switch (opcode) {
++ case WRITE_6:
++ case WRITE_10:
++ case WRITE_12:
++ case WRITE_16:
++ case READ_6:
++ case READ_10:
++ case READ_12:
++ case READ_16:
++ case WRITE_VERIFY:
++ case WRITE_VERIFY_12:
++ case WRITE_VERIFY_16:
++ goto out_complete;
++ }
++
++ res = SCST_EXEC_NOT_COMPLETED;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_complete:
++ cmd->completed = 1;
++
++out_done:
++ res = SCST_EXEC_COMPLETED;
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++ goto out;
++}
++
++MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCSI disk (type 0) dev handler for SCST");
++MODULE_VERSION(SCST_VERSION_STRING);
++
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_modisk.c linux-3.2/drivers/scst/dev_handlers/scst_modisk.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_modisk.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_modisk.c
+@@ -0,0 +1,350 @@
++/*
++ * scst_modisk.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * SCSI MO disk (type 7) dev handler
++ * &
++ * SCSI MO disk (type 7) "performance" device handler (skip all READ and WRITE
++ * operations).
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++#include <linux/init.h>
++#include <scsi/scsi_host.h>
++#include <linux/slab.h>
++
++#define LOG_PREFIX "dev_modisk"
++
++#include <scst/scst.h>
++#include "scst_dev_handler.h"
++
++# define MODISK_NAME "dev_modisk"
++# define MODISK_PERF_NAME "dev_modisk_perf"
++
++#define MODISK_DEF_BLOCK_SHIFT 10
++
++struct modisk_params {
++ int block_shift;
++};
++
++static int modisk_attach(struct scst_device *);
++static void modisk_detach(struct scst_device *);
++static int modisk_parse(struct scst_cmd *);
++static int modisk_done(struct scst_cmd *);
++static int modisk_perf_exec(struct scst_cmd *);
++
++static struct scst_dev_type modisk_devtype = {
++ .name = MODISK_NAME,
++ .type = TYPE_MOD,
++ .threads_num = 1,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = modisk_attach,
++ .detach = modisk_detach,
++ .parse = modisk_parse,
++ .dev_done = modisk_done,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static struct scst_dev_type modisk_devtype_perf = {
++ .name = MODISK_PERF_NAME,
++ .type = TYPE_MOD,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = modisk_attach,
++ .detach = modisk_detach,
++ .parse = modisk_parse,
++ .dev_done = modisk_done,
++ .exec = modisk_perf_exec,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static int __init init_scst_modisk_driver(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ modisk_devtype.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&modisk_devtype);
++ if (res < 0)
++ goto out;
++
++ modisk_devtype_perf.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&modisk_devtype_perf);
++ if (res < 0)
++ goto out_unreg;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unreg:
++ scst_unregister_dev_driver(&modisk_devtype);
++ goto out;
++}
++
++static void __exit exit_scst_modisk_driver(void)
++{
++ TRACE_ENTRY();
++
++ scst_unregister_dev_driver(&modisk_devtype_perf);
++ scst_unregister_dev_driver(&modisk_devtype);
++
++ TRACE_EXIT();
++ return;
++}
++
++module_init(init_scst_modisk_driver);
++module_exit(exit_scst_modisk_driver);
++
++static int modisk_attach(struct scst_device *dev)
++{
++ int res, rc;
++ uint8_t cmd[10];
++ const int buffer_size = 512;
++ uint8_t *buffer = NULL;
++ int retries;
++ unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE];
++ enum dma_data_direction data_dir;
++ struct modisk_params *params;
++
++ TRACE_ENTRY();
++
++ if (dev->scsi_dev == NULL ||
++ dev->scsi_dev->type != dev->type) {
++ PRINT_ERROR("%s", "SCSI device not define or illegal type");
++ res = -ENODEV;
++ goto out;
++ }
++
++ params = kzalloc(sizeof(*params), GFP_KERNEL);
++ if (params == NULL) {
++ PRINT_ERROR("Unable to allocate struct modisk_params (size %zd)",
++ sizeof(*params));
++ res = -ENOMEM;
++ goto out;
++ }
++ params->block_shift = MODISK_DEF_BLOCK_SHIFT;
++
++ /*
++ * If the device is offline, don't try to read capacity or any
++ * of the other stuff
++ */
++ if (dev->scsi_dev->sdev_state == SDEV_OFFLINE) {
++ TRACE_DBG("%s", "Device is offline");
++ res = -ENODEV;
++ goto out_free_params;
++ }
++
++ buffer = kmalloc(buffer_size, GFP_KERNEL);
++ if (!buffer) {
++ PRINT_ERROR("Buffer memory allocation (size %d) failure",
++ buffer_size);
++ res = -ENOMEM;
++ goto out_free_params;
++ }
++
++ /*
++ * Clear any existing UA's and get modisk capacity (modisk block
++ * size).
++ */
++ memset(cmd, 0, sizeof(cmd));
++ cmd[0] = READ_CAPACITY;
++ cmd[1] = (dev->scsi_dev->scsi_level <= SCSI_2) ?
++ ((dev->scsi_dev->lun << 5) & 0xe0) : 0;
++ retries = SCST_DEV_UA_RETRIES;
++ while (1) {
++ memset(buffer, 0, buffer_size);
++ memset(sense_buffer, 0, sizeof(sense_buffer));
++ data_dir = SCST_DATA_READ;
++
++ TRACE_DBG("%s", "Doing READ_CAPACITY");
++ rc = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
++ buffer_size, sense_buffer,
++ SCST_GENERIC_MODISK_REG_TIMEOUT, 3, 0
++ , NULL
++ );
++
++ TRACE_DBG("READ_CAPACITY done: %x", rc);
++
++ if (!rc || !scst_analyze_sense(sense_buffer,
++ sizeof(sense_buffer), SCST_SENSE_KEY_VALID,
++ UNIT_ATTENTION, 0, 0))
++ break;
++
++ if (!--retries) {
++ PRINT_ERROR("UA not cleared after %d retries",
++ SCST_DEV_UA_RETRIES);
++ res = -ENODEV;
++ goto out_free_buf;
++ }
++ }
++
++ if (rc == 0) {
++ int sector_size = ((buffer[4] << 24) | (buffer[5] << 16) |
++ (buffer[6] << 8) | (buffer[7] << 0));
++ if (sector_size == 0)
++ params->block_shift = MODISK_DEF_BLOCK_SHIFT;
++ else
++ params->block_shift =
++ scst_calc_block_shift(sector_size);
++ TRACE_DBG("Sector size is %i scsi_level %d(SCSI_2 %d)",
++ sector_size, dev->scsi_dev->scsi_level, SCSI_2);
++ } else {
++ params->block_shift = MODISK_DEF_BLOCK_SHIFT;
++ TRACE(TRACE_MINOR, "Read capacity failed: %x, using default "
++ "sector size %d", rc, params->block_shift);
++ PRINT_BUFF_FLAG(TRACE_MINOR, "Returned sense", sense_buffer,
++ sizeof(sense_buffer));
++ }
++
++ res = scst_obtain_device_parameters(dev);
++ if (res != 0) {
++ PRINT_ERROR("Failed to obtain control parameters for device "
++ "%s: %x", dev->virt_name, res);
++ goto out_free_buf;
++ }
++
++out_free_buf:
++ kfree(buffer);
++
++out_free_params:
++ if (res == 0)
++ dev->dh_priv = params;
++ else
++ kfree(params);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void modisk_detach(struct scst_device *dev)
++{
++ struct modisk_params *params =
++ (struct modisk_params *)dev->dh_priv;
++
++ TRACE_ENTRY();
++
++ kfree(params);
++ dev->dh_priv = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++static int modisk_get_block_shift(struct scst_cmd *cmd)
++{
++ struct modisk_params *params =
++ (struct modisk_params *)cmd->dev->dh_priv;
++ /*
++ * No need for locks here, since *_detach() can not be
++ * called, when there are existing commands.
++ */
++ return params->block_shift;
++}
++
++static int modisk_parse(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ scst_modisk_generic_parse(cmd, modisk_get_block_shift);
++
++ cmd->retries = SCST_PASSTHROUGH_RETRIES;
++
++ return res;
++}
++
++static void modisk_set_block_shift(struct scst_cmd *cmd, int block_shift)
++{
++ struct modisk_params *params =
++ (struct modisk_params *)cmd->dev->dh_priv;
++ /*
++ * No need for locks here, since *_detach() can not be
++ * called, when there are existing commands.
++ */
++ if (block_shift != 0)
++ params->block_shift = block_shift;
++ else
++ params->block_shift = MODISK_DEF_BLOCK_SHIFT;
++ return;
++}
++
++static int modisk_done(struct scst_cmd *cmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = scst_block_generic_dev_done(cmd, modisk_set_block_shift);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int modisk_perf_exec(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_NOT_COMPLETED, rc;
++ int opcode = cmd->cdb[0];
++
++ TRACE_ENTRY();
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++
++ switch (opcode) {
++ case WRITE_6:
++ case WRITE_10:
++ case WRITE_12:
++ case WRITE_16:
++ case READ_6:
++ case READ_10:
++ case READ_12:
++ case READ_16:
++ cmd->completed = 1;
++ goto out_done;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_done:
++ res = SCST_EXEC_COMPLETED;
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++ goto out;
++}
++
++MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCSI MO disk (type 7) dev handler for SCST");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_processor.c linux-3.2/drivers/scst/dev_handlers/scst_processor.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_processor.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_processor.c
+@@ -0,0 +1,183 @@
++/*
++ * scst_processor.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * SCSI medium processor (type 3) dev 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 <scsi/scsi_host.h>
++#include <linux/slab.h>
++
++#define LOG_PREFIX "dev_processor"
++
++#include <scst/scst.h>
++#include "scst_dev_handler.h"
++
++#define PROCESSOR_NAME "dev_processor"
++
++#define PROCESSOR_RETRIES 2
++
++static int processor_attach(struct scst_device *);
++/*static void processor_detach(struct scst_device *);*/
++static int processor_parse(struct scst_cmd *);
++/*static int processor_done(struct scst_cmd *);*/
++
++static struct scst_dev_type processor_devtype = {
++ .name = PROCESSOR_NAME,
++ .type = TYPE_PROCESSOR,
++ .threads_num = 1,
++ .parse_atomic = 1,
++/* .dev_done_atomic = 1,*/
++ .attach = processor_attach,
++/* .detach = processor_detach,*/
++ .parse = processor_parse,
++/* .dev_done = processor_done*/
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static int processor_attach(struct scst_device *dev)
++{
++ int res, rc;
++ int retries;
++
++ TRACE_ENTRY();
++
++ if (dev->scsi_dev == NULL ||
++ dev->scsi_dev->type != dev->type) {
++ PRINT_ERROR("%s", "SCSI device not define or illegal type");
++ res = -ENODEV;
++ goto out;
++ }
++
++ /*
++ * If the device is offline, don't try to read capacity or any
++ * of the other stuff
++ */
++ if (dev->scsi_dev->sdev_state == SDEV_OFFLINE) {
++ TRACE_DBG("%s", "Device is offline");
++ res = -ENODEV;
++ goto out;
++ }
++
++ retries = SCST_DEV_UA_RETRIES;
++ do {
++ TRACE_DBG("%s", "Doing TEST_UNIT_READY");
++ rc = scsi_test_unit_ready(dev->scsi_dev,
++ SCST_GENERIC_PROCESSOR_TIMEOUT, PROCESSOR_RETRIES
++ , NULL);
++ TRACE_DBG("TEST_UNIT_READY done: %x", rc);
++ } while ((--retries > 0) && rc);
++
++ if (rc) {
++ PRINT_WARNING("Unit not ready: %x", rc);
++ /* Let's try not to be too smart and continue processing */
++ }
++
++ res = scst_obtain_device_parameters(dev);
++ if (res != 0) {
++ PRINT_ERROR("Failed to obtain control parameters for device "
++ "%s", dev->virt_name);
++ goto out;
++ }
++
++out:
++ TRACE_EXIT();
++ return res;
++}
++
++#if 0
++void processor_detach(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ TRACE_EXIT();
++ return;
++}
++#endif
++
++static int processor_parse(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ scst_processor_generic_parse(cmd, NULL);
++
++ cmd->retries = SCST_PASSTHROUGH_RETRIES;
++
++ return res;
++}
++
++#if 0
++int processor_done(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->is_send_status and
++ * cmd->resp_data_len based on cmd->status and cmd->data_direction,
++ * therefore change them only if necessary.
++ */
++
++#if 0
++ switch (cmd->cdb[0]) {
++ default:
++ /* It's all good */
++ break;
++ }
++#endif
++
++ TRACE_EXIT();
++ return res;
++}
++#endif
++
++static int __init processor_init(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ processor_devtype.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&processor_devtype);
++ if (res < 0)
++ goto out;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void __exit processor_exit(void)
++{
++ TRACE_ENTRY();
++ scst_unregister_dev_driver(&processor_devtype);
++ TRACE_EXIT();
++ return;
++}
++
++module_init(processor_init);
++module_exit(processor_exit);
++
++MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCSI medium processor (type 3) dev handler for SCST");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_raid.c linux-3.2/drivers/scst/dev_handlers/scst_raid.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_raid.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_raid.c
+@@ -0,0 +1,184 @@
++/*
++ * scst_raid.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * SCSI raid(controller) (type 0xC) dev 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.
++ */
++
++#define LOG_PREFIX "dev_raid"
++
++#include <scsi/scsi_host.h>
++#include <linux/slab.h>
++
++#include <scst/scst.h>
++#include "scst_dev_handler.h"
++
++#define RAID_NAME "dev_raid"
++
++#define RAID_RETRIES 2
++
++static int raid_attach(struct scst_device *);
++/* static void raid_detach(struct scst_device *); */
++static int raid_parse(struct scst_cmd *);
++/* static int raid_done(struct scst_cmd *); */
++
++static struct scst_dev_type raid_devtype = {
++ .name = RAID_NAME,
++ .type = TYPE_RAID,
++ .threads_num = 1,
++ .parse_atomic = 1,
++/* .dev_done_atomic = 1,*/
++ .attach = raid_attach,
++/* .detach = raid_detach,*/
++ .parse = raid_parse,
++/* .dev_done = raid_done,*/
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static int raid_attach(struct scst_device *dev)
++{
++ int res, rc;
++ int retries;
++
++ TRACE_ENTRY();
++
++ if (dev->scsi_dev == NULL ||
++ dev->scsi_dev->type != dev->type) {
++ PRINT_ERROR("%s", "SCSI device not define or illegal type");
++ res = -ENODEV;
++ goto out;
++ }
++
++ /*
++ * If the device is offline, don't try to read capacity or any
++ * of the other stuff
++ */
++ if (dev->scsi_dev->sdev_state == SDEV_OFFLINE) {
++ TRACE_DBG("%s", "Device is offline");
++ res = -ENODEV;
++ goto out;
++ }
++
++ retries = SCST_DEV_UA_RETRIES;
++ do {
++ TRACE_DBG("%s", "Doing TEST_UNIT_READY");
++ rc = scsi_test_unit_ready(dev->scsi_dev,
++ SCST_GENERIC_RAID_TIMEOUT, RAID_RETRIES
++ , NULL);
++ TRACE_DBG("TEST_UNIT_READY done: %x", rc);
++ } while ((--retries > 0) && rc);
++
++ if (rc) {
++ PRINT_WARNING("Unit not ready: %x", rc);
++ /* Let's try not to be too smart and continue processing */
++ }
++
++ res = scst_obtain_device_parameters(dev);
++ if (res != 0) {
++ PRINT_ERROR("Failed to obtain control parameters for device "
++ "%s", dev->virt_name);
++ goto out;
++ }
++
++out:
++ TRACE_EXIT();
++ return res;
++}
++
++#if 0
++void raid_detach(struct scst_device *dev)
++{
++ TRACE_ENTRY();
++
++ TRACE_EXIT();
++ return;
++}
++#endif
++
++static int raid_parse(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ scst_raid_generic_parse(cmd, NULL);
++
++ cmd->retries = SCST_PASSTHROUGH_RETRIES;
++
++ return res;
++}
++
++#if 0
++int raid_done(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ TRACE_ENTRY();
++
++ /*
++ * SCST sets good defaults for cmd->is_send_status and
++ * cmd->resp_data_len based on cmd->status and cmd->data_direction,
++ * therefore change them only if necessary.
++ */
++
++#if 0
++ switch (cmd->cdb[0]) {
++ default:
++ /* It's all good */
++ break;
++ }
++#endif
++
++ TRACE_EXIT();
++ return res;
++}
++#endif
++
++static int __init raid_init(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ raid_devtype.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&raid_devtype);
++ if (res < 0)
++ goto out;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++}
++
++static void __exit raid_exit(void)
++{
++ TRACE_ENTRY();
++ scst_unregister_dev_driver(&raid_devtype);
++ TRACE_EXIT();
++ return;
++}
++
++module_init(raid_init);
++module_exit(raid_exit);
++
++MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCSI raid(controller) (type 0xC) dev handler for SCST");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/dev_handlers/scst_tape.c linux-3.2/drivers/scst/dev_handlers/scst_tape.c
+--- orig/linux-3.2/drivers/scst/dev_handlers/scst_tape.c
++++ linux-3.2/drivers/scst/dev_handlers/scst_tape.c
+@@ -0,0 +1,383 @@
++/*
++ * scst_tape.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * SCSI tape (type 1) dev handler
++ * &
++ * SCSI tape (type 1) "performance" device handler (skip all READ and WRITE
++ * operations).
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++#include <linux/init.h>
++#include <scsi/scsi_host.h>
++#include <linux/slab.h>
++
++#define LOG_PREFIX "dev_tape"
++
++#include <scst/scst.h>
++#include "scst_dev_handler.h"
++
++# define TAPE_NAME "dev_tape"
++# define TAPE_PERF_NAME "dev_tape_perf"
++
++#define TAPE_RETRIES 2
++
++#define TAPE_DEF_BLOCK_SIZE 512
++
++/* The fixed bit in READ/WRITE/VERIFY */
++#define SILI_BIT 2
++
++struct tape_params {
++ int block_size;
++};
++
++static int tape_attach(struct scst_device *);
++static void tape_detach(struct scst_device *);
++static int tape_parse(struct scst_cmd *);
++static int tape_done(struct scst_cmd *);
++static int tape_perf_exec(struct scst_cmd *);
++
++static struct scst_dev_type tape_devtype = {
++ .name = TAPE_NAME,
++ .type = TYPE_TAPE,
++ .threads_num = 1,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = tape_attach,
++ .detach = tape_detach,
++ .parse = tape_parse,
++ .dev_done = tape_done,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static struct scst_dev_type tape_devtype_perf = {
++ .name = TAPE_PERF_NAME,
++ .type = TYPE_TAPE,
++ .parse_atomic = 1,
++ .dev_done_atomic = 1,
++ .attach = tape_attach,
++ .detach = tape_detach,
++ .parse = tape_parse,
++ .dev_done = tape_done,
++ .exec = tape_perf_exec,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static int __init init_scst_tape_driver(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ tape_devtype.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&tape_devtype);
++ if (res < 0)
++ goto out;
++
++ tape_devtype_perf.module = THIS_MODULE;
++
++ res = scst_register_dev_driver(&tape_devtype_perf);
++ if (res < 0)
++ goto out_unreg;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unreg:
++ scst_unregister_dev_driver(&tape_devtype);
++ goto out;
++}
++
++static void __exit exit_scst_tape_driver(void)
++{
++ TRACE_ENTRY();
++
++ scst_unregister_dev_driver(&tape_devtype_perf);
++ scst_unregister_dev_driver(&tape_devtype);
++
++ TRACE_EXIT();
++ return;
++}
++
++module_init(init_scst_tape_driver);
++module_exit(exit_scst_tape_driver);
++
++static int tape_attach(struct scst_device *dev)
++{
++ int res, rc;
++ int retries;
++ struct scsi_mode_data data;
++ const int buffer_size = 512;
++ uint8_t *buffer = NULL;
++ struct tape_params *params;
++
++ TRACE_ENTRY();
++
++ if (dev->scsi_dev == NULL ||
++ dev->scsi_dev->type != dev->type) {
++ PRINT_ERROR("%s", "SCSI device not define or illegal type");
++ res = -ENODEV;
++ goto out;
++ }
++
++ params = kzalloc(sizeof(*params), GFP_KERNEL);
++ if (params == NULL) {
++ PRINT_ERROR("Unable to allocate struct tape_params (size %zd)",
++ sizeof(*params));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ params->block_size = TAPE_DEF_BLOCK_SIZE;
++
++ buffer = kmalloc(buffer_size, GFP_KERNEL);
++ if (!buffer) {
++ PRINT_ERROR("Buffer memory allocation (size %d) failure",
++ buffer_size);
++ res = -ENOMEM;
++ goto out_free_req;
++ }
++
++ retries = SCST_DEV_UA_RETRIES;
++ do {
++ TRACE_DBG("%s", "Doing TEST_UNIT_READY");
++ rc = scsi_test_unit_ready(dev->scsi_dev,
++ SCST_GENERIC_TAPE_SMALL_TIMEOUT, TAPE_RETRIES
++ , NULL);
++ TRACE_DBG("TEST_UNIT_READY done: %x", rc);
++ } while ((--retries > 0) && rc);
++
++ if (rc) {
++ PRINT_WARNING("Unit not ready: %x", rc);
++ /* Let's try not to be too smart and continue processing */
++ goto obtain;
++ }
++
++ TRACE_DBG("%s", "Doing MODE_SENSE");
++ rc = scsi_mode_sense(dev->scsi_dev,
++ ((dev->scsi_dev->scsi_level <= SCSI_2) ?
++ ((dev->scsi_dev->lun << 5) & 0xe0) : 0),
++ 0 /* Mode Page 0 */,
++ buffer, buffer_size,
++ SCST_GENERIC_TAPE_SMALL_TIMEOUT, TAPE_RETRIES,
++ &data, NULL);
++ TRACE_DBG("MODE_SENSE done: %x", rc);
++
++ if (rc == 0) {
++ int medium_type, mode, speed, density;
++ if (buffer[3] == 8) {
++ params->block_size = ((buffer[9] << 16) |
++ (buffer[10] << 8) |
++ (buffer[11] << 0));
++ } else
++ params->block_size = TAPE_DEF_BLOCK_SIZE;
++ medium_type = buffer[1];
++ mode = (buffer[2] & 0x70) >> 4;
++ speed = buffer[2] & 0x0f;
++ density = buffer[4];
++ TRACE_DBG("Tape: lun %d. bs %d. type 0x%02x mode 0x%02x "
++ "speed 0x%02x dens 0x%02x", dev->scsi_dev->lun,
++ params->block_size, medium_type, mode, speed, density);
++ } else {
++ PRINT_ERROR("MODE_SENSE failed: %x", rc);
++ res = -ENODEV;
++ goto out_free_buf;
++ }
++
++obtain:
++ res = scst_obtain_device_parameters(dev);
++ if (res != 0) {
++ PRINT_ERROR("Failed to obtain control parameters for device "
++ "%s", dev->virt_name);
++ goto out_free_buf;
++ }
++
++out_free_buf:
++ kfree(buffer);
++
++out_free_req:
++ if (res == 0)
++ dev->dh_priv = params;
++ else
++ kfree(params);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void tape_detach(struct scst_device *dev)
++{
++ struct tape_params *params =
++ (struct tape_params *)dev->dh_priv;
++
++ TRACE_ENTRY();
++
++ kfree(params);
++ dev->dh_priv = NULL;
++
++ TRACE_EXIT();
++ return;
++}
++
++static int tape_get_block_size(struct scst_cmd *cmd)
++{
++ struct tape_params *params = (struct tape_params *)cmd->dev->dh_priv;
++ /*
++ * No need for locks here, since *_detach() can not be called,
++ * when there are existing commands.
++ */
++ return params->block_size;
++}
++
++static int tape_parse(struct scst_cmd *cmd)
++{
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ scst_tape_generic_parse(cmd, tape_get_block_size);
++
++ cmd->retries = SCST_PASSTHROUGH_RETRIES;
++
++ return res;
++}
++
++static void tape_set_block_size(struct scst_cmd *cmd, int block_size)
++{
++ struct tape_params *params = (struct tape_params *)cmd->dev->dh_priv;
++ /*
++ * No need for locks here, since *_detach() can not be called, when
++ * there are existing commands.
++ */
++ params->block_size = block_size;
++ return;
++}
++
++static int tape_done(struct scst_cmd *cmd)
++{
++ int opcode = cmd->cdb[0];
++ int status = cmd->status;
++ int res = SCST_CMD_STATE_DEFAULT;
++
++ TRACE_ENTRY();
++
++ if ((status == SAM_STAT_GOOD) || (status == SAM_STAT_CONDITION_MET))
++ res = scst_tape_generic_dev_done(cmd, tape_set_block_size);
++ else if ((status == SAM_STAT_CHECK_CONDITION) &&
++ SCST_SENSE_VALID(cmd->sense)) {
++ struct tape_params *params;
++
++ TRACE_DBG("Extended sense %x", cmd->sense[0] & 0x7F);
++
++ if ((cmd->sense[0] & 0x7F) != 0x70) {
++ PRINT_ERROR("Sense format 0x%x is not supported",
++ cmd->sense[0] & 0x7F);
++ scst_set_cmd_error(cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ goto out;
++ }
++
++ if (opcode == READ_6 && !(cmd->cdb[1] & SILI_BIT) &&
++ (cmd->sense[2] & 0xe0)) {
++ /* EOF, EOM, or ILI */
++ int TransferLength, Residue = 0;
++ if ((cmd->sense[2] & 0x0f) == BLANK_CHECK)
++ /* No need for EOM in this case */
++ cmd->sense[2] &= 0xcf;
++ TransferLength = ((cmd->cdb[2] << 16) |
++ (cmd->cdb[3] << 8) | cmd->cdb[4]);
++ /* Compute the residual count */
++ if ((cmd->sense[0] & 0x80) != 0) {
++ Residue = ((cmd->sense[3] << 24) |
++ (cmd->sense[4] << 16) |
++ (cmd->sense[5] << 8) |
++ cmd->sense[6]);
++ }
++ TRACE_DBG("Checking the sense key "
++ "sn[2]=%x cmd->cdb[0,1]=%x,%x TransLen/Resid"
++ " %d/%d", (int)cmd->sense[2], cmd->cdb[0],
++ cmd->cdb[1], TransferLength, Residue);
++ if (TransferLength > Residue) {
++ int resp_data_len = TransferLength - Residue;
++ if (cmd->cdb[1] & SCST_TRANSFER_LEN_TYPE_FIXED) {
++ /*
++ * No need for locks here, since
++ * *_detach() can not be called, when
++ * there are existing commands.
++ */
++ params = (struct tape_params *)
++ cmd->dev->dh_priv;
++ resp_data_len *= params->block_size;
++ }
++ scst_set_resp_data_len(cmd, resp_data_len);
++ }
++ }
++ }
++
++out:
++ TRACE_DBG("cmd->is_send_status=%x, cmd->resp_data_len=%d, "
++ "res=%d", cmd->is_send_status, cmd->resp_data_len, res);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int tape_perf_exec(struct scst_cmd *cmd)
++{
++ int res = SCST_EXEC_NOT_COMPLETED, rc;
++ int opcode = cmd->cdb[0];
++
++ TRACE_ENTRY();
++
++ rc = scst_check_local_events(cmd);
++ if (unlikely(rc != 0))
++ goto out_done;
++
++ cmd->status = 0;
++ cmd->msg_status = 0;
++ cmd->host_status = DID_OK;
++ cmd->driver_status = 0;
++
++ switch (opcode) {
++ case WRITE_6:
++ case READ_6:
++ cmd->completed = 1;
++ goto out_done;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_done:
++ res = SCST_EXEC_COMPLETED;
++ cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
++ goto out;
++}
++
++MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCSI tape (type 1) dev handler for SCST");
++MODULE_VERSION(SCST_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/fcst/Makefile linux-3.2/drivers/scst/fcst/Makefile
+--- orig/linux-3.2/drivers/scst/fcst/Makefile
++++ linux-3.2/drivers/scst/fcst/Makefile
+@@ -0,0 +1,7 @@
++obj-$(CONFIG_FCST) += fcst.o
++
++fcst-objs := \
++ ft_cmd.o \
++ ft_io.o \
++ ft_scst.o \
++ ft_sess.o
+diff -uprN orig/linux-3.2/drivers/scst/fcst/Kconfig linux-3.2/drivers/scst/fcst/Kconfig
+--- orig/linux-3.2/drivers/scst/fcst/Kconfig
++++ linux-3.2/drivers/scst/fcst/Kconfig
+@@ -0,0 +1,5 @@
++config FCST
++ tristate "SCST target module for Fibre Channel using libfc"
++ depends on LIBFC && SCST
++ ---help---
++ Supports using libfc HBAs as target adapters with SCST
+diff -uprN orig/linux-3.2/drivers/scst/fcst/fcst.h linux-3.2/drivers/scst/fcst/fcst.h
+--- orig/linux-3.2/drivers/scst/fcst/fcst.h
++++ linux-3.2/drivers/scst/fcst/fcst.h
+@@ -0,0 +1,151 @@
++/*
++ * Copyright (c) 2010 Cisco Systems, Inc.
++ *
++ * This program is free software; you may 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.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
++ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
++ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ *
++ * $Id$
++ */
++#ifndef __SCSI_FCST_H__
++#define __SCSI_FCST_H__
++
++#include <scst/scst.h>
++
++#define FT_VERSION "0.3"
++#define FT_MODULE "fcst"
++
++#define FT_MAX_HW_PENDING_TIME 20 /* max I/O time in seconds */
++
++/*
++ * Debug options.
++ */
++#define FT_DEBUG_CONF 0x01 /* configuration messages */
++#define FT_DEBUG_SESS 0x02 /* session messages */
++#define FT_DEBUG_IO 0x04 /* I/O operations */
++
++extern unsigned int ft_debug_logging; /* debug options */
++
++#define FT_ERR(fmt, args...) \
++ printk(KERN_ERR FT_MODULE ": %s: " fmt, __func__, ##args)
++
++#define FT_DEBUG(mask, fmt, args...) \
++ do { \
++ if (ft_debug_logging & (mask)) \
++ printk(KERN_INFO FT_MODULE ": %s: " fmt, \
++ __func__, ##args); \
++ } while (0)
++
++#define FT_CONF_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_CONF, fmt, ##args)
++#define FT_SESS_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_SESS, fmt, ##args)
++#define FT_IO_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_IO, fmt, ##args)
++
++#define FT_NAMELEN 32 /* length of ASCI WWPNs including pad */
++
++/*
++ * Session (remote port).
++ */
++struct ft_sess {
++ u32 port_id; /* for hash lookup use only */
++ u32 params;
++ u16 max_payload; /* max transmitted payload size */
++ u32 max_lso_payload; /* max offloaded payload size */
++ u64 port_name; /* port name for transport ID */
++ struct ft_tport *tport;
++ struct hlist_node hash; /* linkage in ft_sess_hash table */
++ struct rcu_head rcu;
++ struct kref kref; /* ref for hash and outstanding I/Os */
++ struct scst_session *scst_sess;
++};
++
++/*
++ * Hash table of sessions per local port.
++ * Hash lookup by remote port FC_ID.
++ */
++#define FT_SESS_HASH_BITS 6
++#define FT_SESS_HASH_SIZE (1 << FT_SESS_HASH_BITS)
++
++/*
++ * Per local port data.
++ * This is created when the first session logs into the local port.
++ * Deleted when tpg is deleted or last session is logged off.
++ */
++struct ft_tport {
++ u32 sess_count; /* number of sessions in hash */
++ u8 enabled:1;
++ struct rcu_head rcu;
++ struct hlist_head hash[FT_SESS_HASH_SIZE]; /* list of sessions */
++ struct fc_lport *lport;
++ struct scst_tgt *tgt;
++};
++
++/*
++ * Commands
++ */
++struct ft_cmd {
++ int serial; /* order received, for debugging */
++ struct fc_seq *seq; /* sequence in exchange mgr */
++ struct fc_frame *req_frame; /* original request frame */
++ u32 write_data_len; /* data received from initiator */
++ u32 read_data_len; /* data sent to initiator */
++ u32 xfer_rdy_len; /* max xfer ready offset */
++ u32 max_lso_payload; /* max offloaded (LSO) data payload */
++ u16 max_payload; /* max transmitted data payload */
++ struct scst_cmd *scst_cmd;
++};
++
++extern struct list_head ft_lport_list;
++extern struct mutex ft_lport_lock;
++extern struct scst_tgt_template ft_scst_template;
++
++/*
++ * libfc interface.
++ */
++int ft_prli(struct fc_rport_priv *, u32 spp_len,
++ const struct fc_els_spp *, struct fc_els_spp *);
++void ft_prlo(struct fc_rport_priv *);
++void ft_recv(struct fc_lport *, struct fc_frame *);
++
++/*
++ * SCST interface.
++ */
++int ft_send_response(struct scst_cmd *);
++int ft_send_xfer_rdy(struct scst_cmd *);
++void ft_cmd_timeout(struct scst_cmd *);
++void ft_cmd_free(struct scst_cmd *);
++void ft_cmd_tm_done(struct scst_mgmt_cmd *);
++int ft_tgt_detect(struct scst_tgt_template *);
++int ft_tgt_release(struct scst_tgt *);
++int ft_tgt_enable(struct scst_tgt *, bool);
++bool ft_tgt_enabled(struct scst_tgt *);
++int ft_report_aen(struct scst_aen *);
++int ft_get_transport_id(struct scst_tgt *, struct scst_session *, uint8_t **);
++
++/*
++ * Session interface.
++ */
++int ft_lport_notify(struct notifier_block *, unsigned long, void *);
++void ft_lport_add(struct fc_lport *, void *);
++void ft_lport_del(struct fc_lport *, void *);
++
++/*
++ * other internal functions.
++ */
++int ft_thread(void *);
++void ft_recv_req(struct ft_sess *, struct fc_frame *);
++void ft_recv_write_data(struct scst_cmd *, struct fc_frame *);
++int ft_send_read_data(struct scst_cmd *);
++struct ft_tpg *ft_lport_find_tpg(struct fc_lport *);
++struct ft_node_acl *ft_acl_get(struct ft_tpg *, struct fc_rport_priv *);
++void ft_cmd_dump(struct scst_cmd *, const char *);
++
++#endif /* __SCSI_FCST_H__ */
+diff -uprN orig/linux-3.2/drivers/scst/fcst/ft_cmd.c linux-3.2/drivers/scst/fcst/ft_cmd.c
+--- orig/linux-3.2/drivers/scst/fcst/ft_cmd.c
++++ linux-3.2/drivers/scst/fcst/ft_cmd.c
+@@ -0,0 +1,685 @@
++/*
++ * Copyright (c) 2010 Cisco Systems, Inc.
++ *
++ * This program is free software; you may 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.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
++ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
++ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++#include <linux/kernel.h>
++#include <linux/types.h>
++#include <scsi/libfc.h>
++#include <scsi/fc_encode.h>
++#include "fcst.h"
++
++/*
++ * Append string to buffer safely.
++ * Also prepends a space if there's already something the buf.
++ */
++static void ft_cmd_flag(char *buf, size_t len, const char *desc)
++{
++ if (buf[0])
++ strlcat(buf, " ", len);
++ strlcat(buf, desc, len);
++}
++
++/*
++ * Debug: dump command.
++ */
++void ft_cmd_dump(struct scst_cmd *cmd, const char *caller)
++{
++ static atomic_t serial;
++ struct ft_cmd *fcmd;
++ struct fc_frame_header *fh;
++ char prefix[30];
++ char buf[150];
++
++ if (!(ft_debug_logging & FT_DEBUG_IO))
++ return;
++
++ fcmd = scst_cmd_get_tgt_priv(cmd);
++ fh = fc_frame_header_get(fcmd->req_frame);
++ snprintf(prefix, sizeof(prefix), FT_MODULE ": cmd %2x",
++ atomic_inc_return(&serial) & 0xff);
++
++ printk(KERN_INFO "%s %s oid %x oxid %x resp_len %u\n",
++ prefix, caller, ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id),
++ scst_cmd_get_resp_data_len(cmd));
++ printk(KERN_INFO "%s scst_cmd %p wlen %u rlen %u\n",
++ prefix, cmd, fcmd->write_data_len, fcmd->read_data_len);
++ printk(KERN_INFO "%s exp_dir %x exp_xfer_len %d exp_in_len %d\n",
++ prefix, cmd->expected_data_direction,
++ cmd->expected_transfer_len, cmd->expected_out_transfer_len);
++ printk(KERN_INFO "%s dir %x data_len %d bufflen %d out_bufflen %d\n",
++ prefix, cmd->data_direction, cmd->data_len,
++ cmd->bufflen, cmd->out_bufflen);
++ printk(KERN_INFO "%s sg_cnt reg %d in %d tgt %d tgt_in %d\n",
++ prefix, cmd->sg_cnt, cmd->out_sg_cnt,
++ cmd->tgt_sg_cnt, cmd->tgt_out_sg_cnt);
++
++ buf[0] = '\0';
++ if (cmd->sent_for_exec)
++ ft_cmd_flag(buf, sizeof(buf), "sent");
++ if (cmd->completed)
++ ft_cmd_flag(buf, sizeof(buf), "comp");
++ if (cmd->ua_ignore)
++ ft_cmd_flag(buf, sizeof(buf), "ua_ign");
++ if (cmd->atomic)
++ ft_cmd_flag(buf, sizeof(buf), "atom");
++ if (cmd->double_ua_possible)
++ ft_cmd_flag(buf, sizeof(buf), "dbl_ua_poss");
++ if (cmd->is_send_status)
++ ft_cmd_flag(buf, sizeof(buf), "send_stat");
++ if (cmd->retry)
++ ft_cmd_flag(buf, sizeof(buf), "retry");
++ if (cmd->internal)
++ ft_cmd_flag(buf, sizeof(buf), "internal");
++ if (cmd->unblock_dev)
++ ft_cmd_flag(buf, sizeof(buf), "unblock_dev");
++ if (cmd->cmd_hw_pending)
++ ft_cmd_flag(buf, sizeof(buf), "hw_pend");
++ if (cmd->tgt_need_alloc_data_buf)
++ ft_cmd_flag(buf, sizeof(buf), "tgt_need_alloc");
++ if (cmd->tgt_data_buf_alloced)
++ ft_cmd_flag(buf, sizeof(buf), "tgt_alloced");
++ if (cmd->dh_data_buf_alloced)
++ ft_cmd_flag(buf, sizeof(buf), "dh_alloced");
++ if (cmd->expected_values_set)
++ ft_cmd_flag(buf, sizeof(buf), "exp_val");
++ if (cmd->sg_buff_modified)
++ ft_cmd_flag(buf, sizeof(buf), "sg_buf_mod");
++ if (cmd->preprocessing_only)
++ ft_cmd_flag(buf, sizeof(buf), "pre_only");
++ if (cmd->sn_set)
++ ft_cmd_flag(buf, sizeof(buf), "sn_set");
++ if (cmd->hq_cmd_inced)
++ ft_cmd_flag(buf, sizeof(buf), "hq_cmd_inc");
++ if (cmd->set_sn_on_restart_cmd)
++ ft_cmd_flag(buf, sizeof(buf), "set_sn_on_restart");
++ if (cmd->no_sgv)
++ ft_cmd_flag(buf, sizeof(buf), "no_sgv");
++ if (cmd->may_need_dma_sync)
++ ft_cmd_flag(buf, sizeof(buf), "dma_sync");
++ if (cmd->out_of_sn)
++ ft_cmd_flag(buf, sizeof(buf), "oo_sn");
++ if (cmd->inc_expected_sn_on_done)
++ ft_cmd_flag(buf, sizeof(buf), "inc_sn_exp");
++ if (cmd->done)
++ ft_cmd_flag(buf, sizeof(buf), "done");
++ if (cmd->finished)
++ ft_cmd_flag(buf, sizeof(buf), "fin");
++
++ printk(KERN_INFO "%s flags %s\n", prefix, buf);
++ printk(KERN_INFO "%s lun %lld sn %d tag %lld cmd_flags %lx\n",
++ prefix, cmd->lun, cmd->sn, cmd->tag, cmd->cmd_flags);
++ printk(KERN_INFO "%s tgt_sn %d op_flags %x op %s\n",
++ prefix, cmd->tgt_sn, cmd->op_flags, cmd->op_name);
++ printk(KERN_INFO "%s status %x msg_status %x "
++ "host_status %x driver_status %x\n",
++ prefix, cmd->status, cmd->msg_status,
++ cmd->host_status, cmd->driver_status);
++ printk(KERN_INFO "%s cdb_len %d\n", prefix, cmd->cdb_len);
++ snprintf(buf, sizeof(buf), "%s cdb ", prefix);
++ print_hex_dump(KERN_INFO, buf, DUMP_PREFIX_NONE,
++ 16, 4, cmd->cdb, SCST_MAX_CDB_SIZE, 0);
++}
++
++/*
++ * Debug: dump mgmt command.
++ */
++static void ft_cmd_tm_dump(struct scst_mgmt_cmd *mcmd, const char *caller)
++{
++ struct ft_cmd *fcmd;
++ struct fc_frame_header *fh;
++ char prefix[30];
++ char buf[150];
++
++ if (!(ft_debug_logging & FT_DEBUG_IO))
++ return;
++ fcmd = scst_mgmt_cmd_get_tgt_priv(mcmd);
++ fh = fc_frame_header_get(fcmd->req_frame);
++
++ snprintf(prefix, sizeof(prefix), FT_MODULE ": mcmd");
++
++ printk(KERN_INFO "%s %s oid %x oxid %x lun %lld\n",
++ prefix, caller, ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id),
++ (unsigned long long)mcmd->lun);
++ printk(KERN_INFO "%s state %d fn %d fin_wait %d done_wait %d comp %d\n",
++ prefix, mcmd->state, mcmd->fn,
++ mcmd->cmd_finish_wait_count, mcmd->cmd_done_wait_count,
++ mcmd->completed_cmd_count);
++ buf[0] = '\0';
++ if (mcmd->needs_unblocking)
++ ft_cmd_flag(buf, sizeof(buf), "needs_unblock");
++ if (mcmd->lun_set)
++ ft_cmd_flag(buf, sizeof(buf), "lun_set");
++ if (mcmd->cmd_sn_set)
++ ft_cmd_flag(buf, sizeof(buf), "cmd_sn_set");
++ printk(KERN_INFO "%s flags %s\n", prefix, buf);
++ if (mcmd->cmd_to_abort)
++ ft_cmd_dump(mcmd->cmd_to_abort, caller);
++}
++
++/*
++ * Free command and associated frame.
++ */
++static void ft_cmd_done(struct ft_cmd *fcmd)
++{
++ struct fc_frame *fp = fcmd->req_frame;
++ struct fc_lport *lport;
++
++ lport = fr_dev(fp);
++ if (fr_seq(fp))
++ lport->tt.seq_release(fr_seq(fp));
++
++ fc_frame_free(fp);
++ kfree(fcmd);
++}
++
++/*
++ * Free command - callback from SCST.
++ */
++void ft_cmd_free(struct scst_cmd *cmd)
++{
++ struct ft_cmd *fcmd;
++
++ fcmd = scst_cmd_get_tgt_priv(cmd);
++ if (fcmd) {
++ scst_cmd_set_tgt_priv(cmd, NULL);
++ ft_cmd_done(fcmd);
++ }
++}
++
++/*
++ * Send response, after data if applicable.
++ */
++int ft_send_response(struct scst_cmd *cmd)
++{
++ struct ft_cmd *fcmd;
++ struct fc_frame *fp;
++ struct fcp_resp_with_ext *fcp;
++ struct fc_lport *lport;
++ struct fc_exch *ep;
++ unsigned int slen;
++ size_t len;
++ int resid = 0;
++ int bi_resid = 0;
++ int error;
++ int dir;
++ u32 status;
++
++ ft_cmd_dump(cmd, __func__);
++ fcmd = scst_cmd_get_tgt_priv(cmd);
++ ep = fc_seq_exch(fcmd->seq);
++ lport = ep->lp;
++
++ if (scst_cmd_aborted(cmd)) {
++ FT_IO_DBG("cmd aborted did %x oxid %x\n", ep->did, ep->oxid);
++ scst_set_delivery_status(cmd, SCST_CMD_DELIVERY_ABORTED);
++ goto done;
++ }
++
++ if (!scst_cmd_get_is_send_status(cmd)) {
++ FT_IO_DBG("send status not set. feature not implemented\n");
++ return SCST_TGT_RES_FATAL_ERROR;
++ }
++
++ status = scst_cmd_get_status(cmd);
++ dir = scst_cmd_get_data_direction(cmd);
++
++ slen = scst_cmd_get_sense_buffer_len(cmd);
++ len = sizeof(*fcp) + slen;
++
++ /*
++ * Send read data and set underflow/overflow residual count.
++ * For bi-directional comands, the bi_resid is for the read direction.
++ */
++ if (dir & SCST_DATA_WRITE)
++ resid = (signed)scst_cmd_get_bufflen(cmd) -
++ fcmd->write_data_len;
++ if (dir & SCST_DATA_READ) {
++ error = ft_send_read_data(cmd);
++ if (error) {
++ FT_ERR("ft_send_read_data returned %d\n", error);
++ return error;
++ }
++
++ if (dir == SCST_DATA_BIDI) {
++ bi_resid = (signed)scst_cmd_get_out_bufflen(cmd) -
++ scst_cmd_get_resp_data_len(cmd);
++ if (bi_resid)
++ len += sizeof(__be32);
++ } else
++ resid = (signed)scst_cmd_get_bufflen(cmd) -
++ scst_cmd_get_resp_data_len(cmd);
++ }
++
++ fp = fc_frame_alloc(lport, len);
++ if (!fp)
++ return SCST_TGT_RES_QUEUE_FULL;
++
++ fcp = fc_frame_payload_get(fp, len);
++ memset(fcp, 0, sizeof(*fcp));
++ fcp->resp.fr_status = status;
++
++ if (slen) {
++ fcp->resp.fr_flags |= FCP_SNS_LEN_VAL;
++ fcp->ext.fr_sns_len = htonl(slen);
++ memcpy(fcp + 1, scst_cmd_get_sense_buffer(cmd), slen);
++ }
++ if (bi_resid) {
++ if (bi_resid < 0) {
++ fcp->resp.fr_flags |= FCP_BIDI_READ_OVER;
++ bi_resid = -bi_resid;
++ } else
++ fcp->resp.fr_flags |= FCP_BIDI_READ_UNDER;
++ *(__be32 *)((u8 *)(fcp + 1) + slen) = htonl(bi_resid);
++ }
++ if (resid) {
++ if (resid < 0) {
++ resid = -resid;
++ fcp->resp.fr_flags |= FCP_RESID_OVER;
++ } else
++ fcp->resp.fr_flags |= FCP_RESID_UNDER;
++ fcp->ext.fr_resid = htonl(resid);
++ }
++ FT_IO_DBG("response did %x oxid %x\n", ep->did, ep->oxid);
++
++ /*
++ * Send response.
++ */
++ fcmd->seq = lport->tt.seq_start_next(fcmd->seq);
++ fc_fill_fc_hdr(fp, FC_RCTL_DD_CMD_STATUS, ep->did, ep->sid, FC_TYPE_FCP,
++ FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ, 0);
++
++ lport->tt.seq_send(lport, fcmd->seq, fp);
++done:
++ lport->tt.exch_done(fcmd->seq);
++ scst_tgt_cmd_done(cmd, SCST_CONTEXT_SAME);
++ return SCST_TGT_RES_SUCCESS;
++}
++
++/*
++ * FC sequence response handler for follow-on sequences (data) and aborts.
++ */
++static void ft_recv_seq(struct fc_seq *sp, struct fc_frame *fp, void *arg)
++{
++ struct scst_cmd *cmd = arg;
++ struct fc_frame_header *fh;
++
++ /*
++ * If an error is being reported, it must be FC_EX_CLOSED.
++ * Timeouts don't occur on incoming requests, and there are
++ * currently no other errors.
++ * The PRLO handler will be also called by libfc to delete
++ * the session and all pending commands, so we ignore this response.
++ */
++ if (IS_ERR(fp)) {
++ FT_IO_DBG("exchange error %ld - not handled\n", -PTR_ERR(fp));
++ return;
++ }
++
++ fh = fc_frame_header_get(fp);
++ switch (fh->fh_r_ctl) {
++ case FC_RCTL_DD_SOL_DATA: /* write data */
++ ft_recv_write_data(cmd, fp);
++ break;
++ case FC_RCTL_DD_UNSOL_CTL: /* command */
++ case FC_RCTL_DD_SOL_CTL: /* transfer ready */
++ case FC_RCTL_DD_DATA_DESC: /* transfer ready */
++ default:
++ printk(KERN_INFO "%s: unhandled frame r_ctl %x\n",
++ __func__, fh->fh_r_ctl);
++ fc_frame_free(fp);
++ break;
++ }
++}
++
++/*
++ * Command timeout.
++ * SCST calls this when the command has taken too long in the device handler.
++ */
++void ft_cmd_timeout(struct scst_cmd *cmd)
++{
++ FT_IO_DBG("timeout not implemented\n"); /* XXX TBD */
++}
++
++/*
++ * Send TX_RDY (transfer ready).
++ */
++static int ft_send_xfer_rdy_off(struct scst_cmd *cmd, u32 offset, u32 len)
++{
++ struct ft_cmd *fcmd;
++ struct fc_frame *fp;
++ struct fcp_txrdy *txrdy;
++ struct fc_lport *lport;
++ struct fc_exch *ep;
++
++ fcmd = scst_cmd_get_tgt_priv(cmd);
++ if (fcmd->xfer_rdy_len < len + offset)
++ fcmd->xfer_rdy_len = len + offset;
++
++ ep = fc_seq_exch(fcmd->seq);
++ lport = ep->lp;
++ fp = fc_frame_alloc(lport, sizeof(*txrdy));
++ if (!fp)
++ return SCST_TGT_RES_QUEUE_FULL;
++
++ txrdy = fc_frame_payload_get(fp, sizeof(*txrdy));
++ memset(txrdy, 0, sizeof(*txrdy));
++ txrdy->ft_data_ro = htonl(offset);
++ txrdy->ft_burst_len = htonl(len);
++
++ fcmd->seq = lport->tt.seq_start_next(fcmd->seq);
++ fc_fill_fc_hdr(fp, FC_RCTL_DD_DATA_DESC, ep->did, ep->sid, FC_TYPE_FCP,
++ FC_FC_EX_CTX | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
++ lport->tt.seq_send(lport, fcmd->seq, fp);
++ return SCST_TGT_RES_SUCCESS;
++}
++
++/*
++ * Send TX_RDY (transfer ready).
++ */
++int ft_send_xfer_rdy(struct scst_cmd *cmd)
++{
++ return ft_send_xfer_rdy_off(cmd, 0, scst_cmd_get_bufflen(cmd));
++}
++
++/*
++ * Send a FCP response including SCSI status and optional FCP rsp_code.
++ * status is SAM_STAT_GOOD (zero) if code is valid.
++ * This is used in error cases, such as allocation failures.
++ */
++static void ft_send_resp_status(struct fc_frame *rx_fp, u32 status,
++ enum fcp_resp_rsp_codes code)
++{
++ struct fc_frame *fp;
++ struct fc_frame_header *fh;
++ size_t len;
++ struct fcp_resp_with_ext *fcp;
++ struct fcp_resp_rsp_info *info;
++ struct fc_lport *lport;
++ struct fc_seq *sp;
++
++ sp = fr_seq(rx_fp);
++ fh = fc_frame_header_get(rx_fp);
++ FT_IO_DBG("FCP error response: did %x oxid %x status %x code %x\n",
++ ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id), status, code);
++ lport = fr_dev(rx_fp);
++ len = sizeof(*fcp);
++ if (status == SAM_STAT_GOOD)
++ len += sizeof(*info);
++ fp = fc_frame_alloc(lport, len);
++ if (!fp)
++ return;
++ fcp = fc_frame_payload_get(fp, len);
++ memset(fcp, 0, len);
++ fcp->resp.fr_status = status;
++ if (status == SAM_STAT_GOOD) {
++ fcp->ext.fr_rsp_len = htonl(sizeof(*info));
++ fcp->resp.fr_flags |= FCP_RSP_LEN_VAL;
++ info = (struct fcp_resp_rsp_info *)(fcp + 1);
++ info->rsp_code = code;
++ }
++
++ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_DD_CMD_STATUS, 0);
++ if (sp)
++ lport->tt.seq_send(lport, sp, fp);
++ else
++ lport->tt.frame_send(lport, fp);
++}
++
++/*
++ * Send error or task management response.
++ * Always frees the fcmd and associated state.
++ */
++static void ft_send_resp_code(struct ft_cmd *fcmd, enum fcp_resp_rsp_codes code)
++{
++ ft_send_resp_status(fcmd->req_frame, SAM_STAT_GOOD, code);
++ ft_cmd_done(fcmd);
++}
++
++void ft_cmd_tm_done(struct scst_mgmt_cmd *mcmd)
++{
++ struct ft_cmd *fcmd;
++ enum fcp_resp_rsp_codes code;
++
++ ft_cmd_tm_dump(mcmd, __func__);
++ fcmd = scst_mgmt_cmd_get_tgt_priv(mcmd);
++ switch (scst_mgmt_cmd_get_status(mcmd)) {
++ case SCST_MGMT_STATUS_SUCCESS:
++ code = FCP_TMF_CMPL;
++ break;
++ case SCST_MGMT_STATUS_REJECTED:
++ code = FCP_TMF_REJECTED;
++ break;
++ case SCST_MGMT_STATUS_LUN_NOT_EXIST:
++ code = FCP_TMF_INVALID_LUN;
++ break;
++ case SCST_MGMT_STATUS_TASK_NOT_EXIST:
++ case SCST_MGMT_STATUS_FN_NOT_SUPPORTED:
++ case SCST_MGMT_STATUS_FAILED:
++ default:
++ code = FCP_TMF_FAILED;
++ break;
++ }
++ FT_IO_DBG("tm cmd done fn %d code %d\n", mcmd->fn, code);
++ ft_send_resp_code(fcmd, code);
++}
++
++/*
++ * Handle an incoming FCP task management command frame.
++ * Note that this may be called directly from the softirq context.
++ */
++static void ft_recv_tm(struct scst_session *scst_sess,
++ struct ft_cmd *fcmd, struct fcp_cmnd *fcp)
++{
++ struct scst_rx_mgmt_params params;
++ int ret;
++
++ memset(&params, 0, sizeof(params));
++ params.lun = fcp->fc_lun;
++ params.lun_len = sizeof(fcp->fc_lun);
++ params.lun_set = 1;
++ params.atomic = SCST_ATOMIC;
++ params.tgt_priv = fcmd;
++
++ switch (fcp->fc_tm_flags) {
++ case FCP_TMF_LUN_RESET:
++ params.fn = SCST_LUN_RESET;
++ break;
++ case FCP_TMF_TGT_RESET:
++ params.fn = SCST_TARGET_RESET;
++ params.lun_set = 0;
++ break;
++ case FCP_TMF_CLR_TASK_SET:
++ params.fn = SCST_CLEAR_TASK_SET;
++ break;
++ case FCP_TMF_ABT_TASK_SET:
++ params.fn = SCST_ABORT_TASK_SET;
++ break;
++ case FCP_TMF_CLR_ACA:
++ params.fn = SCST_CLEAR_ACA;
++ break;
++ default:
++ /*
++ * FCP4r01 indicates having a combination of
++ * tm_flags set is invalid.
++ */
++ FT_IO_DBG("invalid FCP tm_flags %x\n", fcp->fc_tm_flags);
++ ft_send_resp_code(fcmd, FCP_CMND_FIELDS_INVALID);
++ return;
++ }
++ FT_IO_DBG("submit tm cmd fn %d\n", params.fn);
++ ret = scst_rx_mgmt_fn(scst_sess, &params);
++ FT_IO_DBG("scst_rx_mgmt_fn ret %d\n", ret);
++ if (ret)
++ ft_send_resp_code(fcmd, FCP_TMF_FAILED);
++}
++
++/*
++ * Handle an incoming FCP command frame.
++ * Note that this may be called directly from the softirq context.
++ */
++static void ft_recv_cmd(struct ft_sess *sess, struct fc_frame *fp)
++{
++ static atomic_t serial;
++ struct fc_seq *sp;
++ struct scst_cmd *cmd;
++ struct ft_cmd *fcmd;
++ struct fcp_cmnd *fcp;
++ struct fc_lport *lport;
++ int data_dir;
++ u32 data_len;
++ int cdb_len;
++
++ lport = sess->tport->lport;
++ fcmd = kzalloc(sizeof(*fcmd), GFP_ATOMIC);
++ if (!fcmd)
++ goto busy;
++ fcmd->serial = atomic_inc_return(&serial); /* debug only */
++ fcmd->max_payload = sess->max_payload;
++ fcmd->max_lso_payload = sess->max_lso_payload;
++ fcmd->req_frame = fp;
++
++ fcp = fc_frame_payload_get(fp, sizeof(*fcp));
++ if (!fcp)
++ goto err;
++ if (fcp->fc_tm_flags) {
++ ft_recv_tm(sess->scst_sess, fcmd, fcp);
++ return;
++ }
++
++ /*
++ * re-check length including specified CDB length.
++ * data_len is just after the CDB.
++ */
++ cdb_len = fcp->fc_flags & FCP_CFL_LEN_MASK;
++ fcp = fc_frame_payload_get(fp, sizeof(*fcp) + cdb_len);
++ if (!fcp)
++ goto err;
++ cdb_len += sizeof(fcp->fc_cdb);
++ data_len = ntohl(*(__be32 *)(fcp->fc_cdb + cdb_len));
++
++ cmd = scst_rx_cmd(sess->scst_sess, fcp->fc_lun, sizeof(fcp->fc_lun),
++ fcp->fc_cdb, cdb_len, SCST_ATOMIC);
++ if (!cmd)
++ goto busy;
++ fcmd->scst_cmd = cmd;
++ scst_cmd_set_tgt_priv(cmd, fcmd);
++
++ sp = lport->tt.seq_assign(lport, fp);
++ if (!sp)
++ goto busy;
++ fcmd->seq = sp;
++ lport->tt.seq_set_resp(sp, ft_recv_seq, cmd);
++
++ switch (fcp->fc_flags & (FCP_CFL_RDDATA | FCP_CFL_WRDATA)) {
++ case 0:
++ default:
++ data_dir = SCST_DATA_NONE;
++ break;
++ case FCP_CFL_RDDATA:
++ data_dir = SCST_DATA_READ;
++ break;
++ case FCP_CFL_WRDATA:
++ data_dir = SCST_DATA_WRITE;
++ break;
++ case FCP_CFL_RDDATA | FCP_CFL_WRDATA:
++ data_dir = SCST_DATA_BIDI;
++ break;
++ }
++ scst_cmd_set_expected(cmd, data_dir, data_len);
++
++ switch (fcp->fc_pri_ta & FCP_PTA_MASK) {
++ case FCP_PTA_SIMPLE:
++ scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_SIMPLE);
++ break;
++ case FCP_PTA_HEADQ:
++ scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE);
++ break;
++ case FCP_PTA_ACA:
++ scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_ACA);
++ break;
++ case FCP_PTA_ORDERED:
++ default:
++ scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ }
++
++ scst_cmd_init_done(cmd, SCST_CONTEXT_THREAD);
++ return;
++
++err:
++ ft_send_resp_code(fcmd, FCP_CMND_FIELDS_INVALID);
++ return;
++
++busy:
++ FT_IO_DBG("cmd allocation failure - sending BUSY\n");
++ ft_send_resp_status(fp, SAM_STAT_BUSY, 0);
++ ft_cmd_done(fcmd);
++}
++
++/*
++ * Send FCP ELS-4 Reject.
++ */
++static void ft_cmd_ls_rjt(struct fc_frame *rx_fp, enum fc_els_rjt_reason reason,
++ enum fc_els_rjt_explan explan)
++{
++ struct fc_seq_els_data rjt_data;
++ struct fc_lport *lport;
++
++ lport = fr_dev(rx_fp);
++ rjt_data.reason = reason;
++ rjt_data.explan = explan;
++ lport->tt.seq_els_rsp_send(rx_fp, ELS_LS_RJT, &rjt_data);
++}
++
++/*
++ * Handle an incoming FCP ELS-4 command frame.
++ * Note that this may be called directly from the softirq context.
++ */
++static void ft_recv_els4(struct ft_sess *sess, struct fc_frame *fp)
++{
++ u8 op = fc_frame_payload_op(fp);
++
++ switch (op) {
++ case ELS_SRR: /* TBD */
++ default:
++ FT_IO_DBG("unsupported ELS-4 op %x\n", op);
++ ft_cmd_ls_rjt(fp, ELS_RJT_INVAL, ELS_EXPL_NONE);
++ fc_frame_free(fp);
++ break;
++ }
++}
++
++/*
++ * Handle an incoming FCP frame.
++ * Note that this may be called directly from the softirq context.
++ */
++void ft_recv_req(struct ft_sess *sess, struct fc_frame *fp)
++{
++ struct fc_frame_header *fh = fc_frame_header_get(fp);
++
++ switch (fh->fh_r_ctl) {
++ case FC_RCTL_DD_UNSOL_CMD:
++ ft_recv_cmd(sess, fp);
++ break;
++ case FC_RCTL_ELS4_REQ:
++ ft_recv_els4(sess, fp);
++ break;
++ default:
++ printk(KERN_INFO "%s: unhandled frame r_ctl %x\n",
++ __func__, fh->fh_r_ctl);
++ fc_frame_free(fp);
++ break;
++ }
++}
+diff -uprN orig/linux-3.2/drivers/scst/fcst/ft_io.c linux-3.2/drivers/scst/fcst/ft_io.c
+--- orig/linux-3.2/drivers/scst/fcst/ft_io.c
++++ linux-3.2/drivers/scst/fcst/ft_io.c
+@@ -0,0 +1,276 @@
++/*
++ * Copyright (c) 2010 Cisco Systems, Inc.
++ *
++ * Portions based on drivers/scsi/libfc/fc_fcp.c and subject to the following:
++ *
++ * Copyright (c) 2007 Intel Corporation. All rights reserved.
++ * Copyright (c) 2008 Red Hat, Inc. All rights reserved.
++ * Copyright (c) 2008 Mike Christie
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++ * more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
++ */
++#include <linux/kernel.h>
++#include <linux/types.h>
++#include <scsi/libfc.h>
++#include <scsi/fc_encode.h>
++#include "fcst.h"
++
++/*
++ * Receive write data frame.
++ */
++void ft_recv_write_data(struct scst_cmd *cmd, struct fc_frame *fp)
++{
++ struct ft_cmd *fcmd;
++ struct fc_frame_header *fh;
++ unsigned int bufflen;
++ u32 rel_off;
++ size_t frame_len;
++ size_t mem_len;
++ size_t tlen;
++ void *from;
++ void *to;
++ int dir;
++ u8 *buf;
++
++ dir = scst_cmd_get_data_direction(cmd);
++ if (dir == SCST_DATA_BIDI) {
++ mem_len = scst_get_out_buf_first(cmd, &buf);
++ bufflen = scst_cmd_get_out_bufflen(cmd);
++ } else {
++ mem_len = scst_get_buf_first(cmd, &buf);
++ bufflen = scst_cmd_get_bufflen(cmd);
++ }
++ to = buf;
++
++ fcmd = scst_cmd_get_tgt_priv(cmd);
++ fh = fc_frame_header_get(fp);
++ frame_len = fr_len(fp);
++ rel_off = ntohl(fh->fh_parm_offset);
++
++ FT_IO_DBG("sid %x oxid %x payload_len %zd rel_off %x\n",
++ ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id),
++ frame_len - sizeof(*fh), rel_off);
++
++ if (!(ntoh24(fh->fh_f_ctl) & FC_FC_REL_OFF))
++ goto drop;
++ if (frame_len <= sizeof(*fh))
++ goto drop;
++ frame_len -= sizeof(*fh);
++ from = fc_frame_payload_get(fp, 0);
++
++ if (rel_off >= bufflen)
++ goto drop;
++ if (frame_len + rel_off > bufflen)
++ frame_len = bufflen - rel_off;
++
++ while (frame_len) {
++ if (!mem_len) {
++ if (dir == SCST_DATA_BIDI) {
++ scst_put_out_buf(cmd, buf);
++ mem_len = scst_get_out_buf_next(cmd, &buf);
++ } else {
++ scst_put_buf(cmd, buf);
++ mem_len = scst_get_buf_next(cmd, &buf);
++ }
++ to = buf;
++ if (!mem_len)
++ break;
++ }
++ if (rel_off) {
++ if (rel_off >= mem_len) {
++ rel_off -= mem_len;
++ mem_len = 0;
++ continue;
++ }
++ mem_len -= rel_off;
++ to += rel_off;
++ rel_off = 0;
++ }
++
++ tlen = min(mem_len, frame_len);
++ memcpy(to, from, tlen);
++
++ from += tlen;
++ frame_len -= tlen;
++ mem_len -= tlen;
++ to += tlen;
++ fcmd->write_data_len += tlen;
++ }
++ if (mem_len) {
++ if (dir == SCST_DATA_BIDI)
++ scst_put_out_buf(cmd, buf);
++ else
++ scst_put_buf(cmd, buf);
++ }
++ if (fcmd->write_data_len == cmd->data_len)
++ scst_rx_data(cmd, SCST_RX_STATUS_SUCCESS, SCST_CONTEXT_THREAD);
++drop:
++ fc_frame_free(fp);
++}
++
++/*
++ * Send read data back to initiator.
++ */
++int ft_send_read_data(struct scst_cmd *cmd)
++{
++ struct ft_cmd *fcmd;
++ struct fc_frame *fp = NULL;
++ struct fc_exch *ep;
++ struct fc_lport *lport;
++ size_t remaining;
++ u32 fh_off = 0;
++ u32 frame_off;
++ size_t frame_len = 0;
++ size_t mem_len;
++ u32 mem_off;
++ size_t tlen;
++ struct page *page;
++ int use_sg;
++ int error;
++ void *to = NULL;
++ u8 *from = NULL;
++ int loop_limit = 10000;
++
++ fcmd = scst_cmd_get_tgt_priv(cmd);
++ ep = fc_seq_exch(fcmd->seq);
++ lport = ep->lp;
++
++ frame_off = fcmd->read_data_len;
++ tlen = scst_cmd_get_resp_data_len(cmd);
++ FT_IO_DBG("oid %x oxid %x resp_len %zd frame_off %u\n",
++ ep->oid, ep->oxid, tlen, frame_off);
++ if (tlen <= frame_off)
++ return SCST_TGT_RES_SUCCESS;
++ remaining = tlen - frame_off;
++ if (remaining > UINT_MAX)
++ FT_ERR("oid %x oxid %x resp_len %zd frame_off %u\n",
++ ep->oid, ep->oxid, tlen, frame_off);
++
++ mem_len = scst_get_buf_first(cmd, &from);
++ mem_off = 0;
++ if (!mem_len) {
++ FT_IO_DBG("mem_len 0\n");
++ return SCST_TGT_RES_SUCCESS;
++ }
++ FT_IO_DBG("sid %x oxid %x mem_len %zd frame_off %u remaining %zd\n",
++ ep->sid, ep->oxid, mem_len, frame_off, remaining);
++
++ /*
++ * If we've already transferred some of the data, skip through
++ * the buffer over the data already sent and continue with the
++ * same sequence. Otherwise, get a new sequence for the data.
++ */
++ if (frame_off) {
++ tlen = frame_off;
++ while (mem_len <= tlen) {
++ tlen -= mem_len;
++ scst_put_buf(cmd, from);
++ mem_len = scst_get_buf_next(cmd, &from);
++ if (!mem_len)
++ return SCST_TGT_RES_SUCCESS;
++ }
++ mem_len -= tlen;
++ mem_off = tlen;
++ } else
++ fcmd->seq = lport->tt.seq_start_next(fcmd->seq);
++
++ /* no scatter/gather in skb for odd word length due to fc_seq_send() */
++ use_sg = !(remaining % 4) && lport->sg_supp;
++
++ while (remaining) {
++ if (!loop_limit) {
++ FT_ERR("hit loop limit. remaining %zx mem_len %zx "
++ "frame_len %zx tlen %zx\n",
++ remaining, mem_len, frame_len, tlen);
++ break;
++ }
++ loop_limit--;
++ if (!mem_len) {
++ scst_put_buf(cmd, from);
++ mem_len = scst_get_buf_next(cmd, &from);
++ mem_off = 0;
++ if (!mem_len) {
++ FT_ERR("mem_len 0 from get_buf_next\n");
++ break;
++ }
++ }
++ if (!frame_len) {
++ frame_len = fcmd->max_lso_payload;
++ frame_len = min(frame_len, remaining);
++ fp = fc_frame_alloc(lport, use_sg ? 0 : frame_len);
++ if (!fp) {
++ FT_IO_DBG("frame_alloc failed. "
++ "use_sg %d frame_len %zd\n",
++ use_sg, frame_len);
++ break;
++ }
++ fr_max_payload(fp) = fcmd->max_payload;
++ to = fc_frame_payload_get(fp, 0);
++ fh_off = frame_off;
++ }
++ tlen = min(mem_len, frame_len);
++ BUG_ON(!tlen);
++ BUG_ON(tlen > remaining);
++ BUG_ON(tlen > mem_len);
++ BUG_ON(tlen > frame_len);
++
++ if (use_sg) {
++ page = virt_to_page(from + mem_off);
++ get_page(page);
++ tlen = min_t(size_t, tlen,
++ PAGE_SIZE - (mem_off & ~PAGE_MASK));
++ skb_fill_page_desc(fp_skb(fp),
++ skb_shinfo(fp_skb(fp))->nr_frags,
++ page, offset_in_page(from + mem_off),
++ tlen);
++ fr_len(fp) += tlen;
++ fp_skb(fp)->data_len += tlen;
++ fp_skb(fp)->truesize +=
++ PAGE_SIZE << compound_order(page);
++ frame_len -= tlen;
++ if (skb_shinfo(fp_skb(fp))->nr_frags >= FC_FRAME_SG_LEN)
++ frame_len = 0;
++ } else {
++ memcpy(to, from + mem_off, tlen);
++ to += tlen;
++ frame_len -= tlen;
++ }
++
++ mem_len -= tlen;
++ mem_off += tlen;
++ remaining -= tlen;
++ frame_off += tlen;
++
++ if (frame_len)
++ continue;
++ fc_fill_fc_hdr(fp, FC_RCTL_DD_SOL_DATA, ep->did, ep->sid,
++ FC_TYPE_FCP,
++ remaining ? (FC_FC_EX_CTX | FC_FC_REL_OFF) :
++ (FC_FC_EX_CTX | FC_FC_REL_OFF | FC_FC_END_SEQ),
++ fh_off);
++ error = lport->tt.seq_send(lport, fcmd->seq, fp);
++ if (error) {
++ WARN_ON(1);
++ /* XXX For now, initiator will retry */
++ } else
++ fcmd->read_data_len = frame_off;
++ }
++ if (mem_len)
++ scst_put_buf(cmd, from);
++ if (remaining) {
++ FT_IO_DBG("remaining read data %zd\n", remaining);
++ return SCST_TGT_RES_QUEUE_FULL;
++ }
++ return SCST_TGT_RES_SUCCESS;
++}
+diff -uprN orig/linux-3.2/drivers/scst/fcst/ft_scst.c linux-3.2/drivers/scst/fcst/ft_scst.c
+--- orig/linux-3.2/drivers/scst/fcst/ft_scst.c
++++ linux-3.2/drivers/scst/fcst/ft_scst.c
+@@ -0,0 +1,96 @@
++/*
++ * Copyright (c) 2010 Cisco Systems, Inc.
++ *
++ * This program is free software; you may 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.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
++ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
++ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++#include <linux/kernel.h>
++#include <linux/types.h>
++#include <scsi/libfc.h>
++#include "fcst.h"
++
++MODULE_AUTHOR("Joe Eykholt <jeykholt@cisco.com>");
++MODULE_DESCRIPTION("Fibre-Channel SCST target");
++MODULE_LICENSE("GPL v2");
++
++unsigned int ft_debug_logging;
++module_param_named(debug_logging, ft_debug_logging, int, S_IRUGO | S_IWUSR);
++MODULE_PARM_DESC(debug_logging, "log levels bigmask");
++
++DEFINE_MUTEX(ft_lport_lock);
++
++/*
++ * Provider ops for libfc.
++ */
++static struct fc4_prov ft_prov = {
++ .prli = ft_prli,
++ .prlo = ft_prlo,
++ .recv = ft_recv,
++ .module = THIS_MODULE,
++};
++
++static struct notifier_block ft_notifier = {
++ .notifier_call = ft_lport_notify
++};
++
++/*
++ * SCST target ops and configuration.
++ * XXX - re-check uninitialized fields
++ */
++struct scst_tgt_template ft_scst_template = {
++ .sg_tablesize = 128, /* XXX get true limit from libfc */
++ .xmit_response_atomic = 1,
++ .rdy_to_xfer_atomic = 1,
++ .xmit_response = ft_send_response,
++ .rdy_to_xfer = ft_send_xfer_rdy,
++ .on_hw_pending_cmd_timeout = ft_cmd_timeout,
++ .on_free_cmd = ft_cmd_free,
++ .task_mgmt_fn_done = ft_cmd_tm_done,
++ .detect = ft_tgt_detect,
++ .release = ft_tgt_release,
++ .report_aen = ft_report_aen,
++ .enable_target = ft_tgt_enable,
++ .is_target_enabled = ft_tgt_enabled,
++ .get_initiator_port_transport_id = ft_get_transport_id,
++ .max_hw_pending_time = FT_MAX_HW_PENDING_TIME,
++ .name = FT_MODULE,
++};
++
++static int __init ft_module_init(void)
++{
++ int err;
++
++ err = scst_register_target_template(&ft_scst_template);
++ if (err)
++ return err;
++ err = fc_fc4_register_provider(FC_TYPE_FCP, &ft_prov);
++ if (err) {
++ scst_unregister_target_template(&ft_scst_template);
++ return err;
++ }
++ blocking_notifier_chain_register(&fc_lport_notifier_head, &ft_notifier);
++ fc_lport_iterate(ft_lport_add, NULL);
++ return 0;
++}
++module_init(ft_module_init);
++
++static void __exit ft_module_exit(void)
++{
++ blocking_notifier_chain_unregister(&fc_lport_notifier_head,
++ &ft_notifier);
++ fc_fc4_deregister_provider(FC_TYPE_FCP, &ft_prov);
++ fc_lport_iterate(ft_lport_del, NULL);
++ scst_unregister_target_template(&ft_scst_template);
++ synchronize_rcu();
++}
++module_exit(ft_module_exit);
+diff -uprN orig/linux-3.2/drivers/scst/fcst/ft_sess.c linux-3.2/drivers/scst/fcst/ft_sess.c
+--- orig/linux-3.2/drivers/scst/fcst/ft_sess.c
++++ linux-3.2/drivers/scst/fcst/ft_sess.c
+@@ -0,0 +1,585 @@
++/*
++ * Copyright (c) 2010 Cisco Systems, Inc.
++ *
++ * This program is free software; you may 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.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
++ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
++ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++#include <linux/kernel.h>
++#include <linux/types.h>
++#include <linux/mutex.h>
++#include <linux/hash.h>
++#include <asm/unaligned.h>
++#include <scsi/libfc.h>
++#include <scsi/fc/fc_els.h>
++#include "fcst.h"
++
++static int ft_tport_count;
++
++static ssize_t ft_format_wwn(char *buf, size_t len, u64 wwn)
++{
++ u8 b[8];
++
++ put_unaligned_be64(wwn, b);
++ return snprintf(buf, len,
++ "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x",
++ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
++}
++
++/*
++ * Lookup or allocate target local port.
++ * Caller holds ft_lport_lock.
++ */
++static struct ft_tport *ft_tport_create(struct fc_lport *lport)
++{
++ struct ft_tport *tport;
++ char name[FT_NAMELEN];
++ int i;
++
++ ft_format_wwn(name, sizeof(name), lport->wwpn);
++ FT_SESS_DBG("create %s\n", name);
++
++ tport = rcu_dereference(lport->prov[FC_TYPE_FCP]);
++ if (tport) {
++ FT_SESS_DBG("tport alloc %s - already setup\n", name);
++ return tport;
++ }
++
++ tport = kzalloc(sizeof(*tport), GFP_KERNEL);
++ if (!tport) {
++ FT_SESS_DBG("tport alloc %s failed\n", name);
++ return NULL;
++ }
++
++ tport->tgt = scst_register_target(&ft_scst_template, name);
++ if (!tport->tgt) {
++ FT_SESS_DBG("register_target %s failed\n", name);
++ kfree(tport);
++ return NULL;
++ }
++ scst_tgt_set_tgt_priv(tport->tgt, tport);
++ ft_tport_count++;
++
++ tport->lport = lport;
++ for (i = 0; i < FT_SESS_HASH_SIZE; i++)
++ INIT_HLIST_HEAD(&tport->hash[i]);
++
++ rcu_assign_pointer(lport->prov[FC_TYPE_FCP], tport);
++ FT_SESS_DBG("register_target %s succeeded\n", name);
++ return tport;
++}
++
++/*
++ * Free tport via RCU.
++ */
++static void ft_tport_rcu_free(struct rcu_head *rcu)
++{
++ struct ft_tport *tport = container_of(rcu, struct ft_tport, rcu);
++
++ kfree(tport);
++}
++
++/*
++ * Delete target local port, if any, associated with the local port.
++ * Caller holds ft_lport_lock.
++ */
++static void ft_tport_delete(struct ft_tport *tport)
++{
++ struct fc_lport *lport;
++ struct scst_tgt *tgt;
++
++ tgt = tport->tgt;
++ BUG_ON(!tgt);
++ FT_SESS_DBG("delete %s\n", scst_get_tgt_name(tgt));
++ scst_unregister_target(tgt);
++ lport = tport->lport;
++ BUG_ON(tport != lport->prov[FC_TYPE_FCP]);
++ rcu_assign_pointer(lport->prov[FC_TYPE_FCP], NULL);
++ tport->lport = NULL;
++ call_rcu(&tport->rcu, ft_tport_rcu_free);
++ ft_tport_count--;
++}
++
++/*
++ * Add local port.
++ * Called thru fc_lport_iterate().
++ */
++void ft_lport_add(struct fc_lport *lport, void *arg)
++{
++ mutex_lock(&ft_lport_lock);
++ ft_tport_create(lport);
++ mutex_unlock(&ft_lport_lock);
++}
++
++/*
++ * Delete local port.
++ * Called thru fc_lport_iterate().
++ */
++void ft_lport_del(struct fc_lport *lport, void *arg)
++{
++ struct ft_tport *tport;
++
++ mutex_lock(&ft_lport_lock);
++ tport = lport->prov[FC_TYPE_FCP];
++ if (tport)
++ ft_tport_delete(tport);
++ mutex_unlock(&ft_lport_lock);
++}
++
++/*
++ * Notification of local port change from libfc.
++ * Create or delete local port and associated tport.
++ */
++int ft_lport_notify(struct notifier_block *nb, unsigned long event, void *arg)
++{
++ struct fc_lport *lport = arg;
++
++ switch (event) {
++ case FC_LPORT_EV_ADD:
++ ft_lport_add(lport, NULL);
++ break;
++ case FC_LPORT_EV_DEL:
++ ft_lport_del(lport, NULL);
++ break;
++ }
++ return NOTIFY_DONE;
++}
++
++/*
++ * Find session in local port.
++ * Sessions and hash lists are RCU-protected.
++ * A reference is taken which must be eventually freed.
++ */
++static struct ft_sess *ft_sess_get(struct fc_lport *lport, u32 port_id)
++{
++ struct ft_tport *tport;
++ struct hlist_head *head;
++ struct hlist_node *pos;
++ struct ft_sess *sess = NULL;
++
++ rcu_read_lock();
++ tport = rcu_dereference(lport->prov[FC_TYPE_FCP]);
++ if (!tport)
++ goto out;
++
++ head = &tport->hash[hash_32(port_id, FT_SESS_HASH_BITS)];
++ hlist_for_each_entry_rcu(sess, pos, head, hash) {
++ if (sess->port_id == port_id) {
++ kref_get(&sess->kref);
++ rcu_read_unlock();
++ FT_SESS_DBG("port_id %x found %p\n", port_id, sess);
++ return sess;
++ }
++ }
++out:
++ rcu_read_unlock();
++ FT_SESS_DBG("port_id %x not found\n", port_id);
++ return NULL;
++}
++
++/*
++ * Allocate session and enter it in the hash for the local port.
++ * Caller holds ft_lport_lock.
++ */
++static int ft_sess_create(struct ft_tport *tport, struct fc_rport_priv *rdata,
++ u32 fcp_parm)
++{
++ struct ft_sess *sess;
++ struct scst_session *scst_sess;
++ struct hlist_head *head;
++ struct hlist_node *pos;
++ u32 port_id;
++ char name[FT_NAMELEN];
++
++ port_id = rdata->ids.port_id;
++ if (!rdata->maxframe_size) {
++ FT_SESS_DBG("port_id %x maxframe_size 0\n", port_id);
++ return FC_SPP_RESP_CONF;
++ }
++
++ head = &tport->hash[hash_32(port_id, FT_SESS_HASH_BITS)];
++ hlist_for_each_entry_rcu(sess, pos, head, hash) {
++ if (sess->port_id == port_id) {
++ sess->params = fcp_parm;
++ return 0;
++ }
++ }
++
++ sess = kzalloc(sizeof(*sess), GFP_KERNEL);
++ if (!sess)
++ return FC_SPP_RESP_RES; /* out of resources */
++
++ sess->port_name = rdata->ids.port_name;
++ sess->max_payload = rdata->maxframe_size;
++ sess->max_lso_payload = rdata->maxframe_size;
++ if (tport->lport->seq_offload)
++ sess->max_lso_payload = tport->lport->lso_max;
++ sess->params = fcp_parm;
++ sess->tport = tport;
++ sess->port_id = port_id;
++ kref_init(&sess->kref); /* ref for table entry */
++
++ ft_format_wwn(name, sizeof(name), rdata->ids.port_name);
++ FT_SESS_DBG("register %s\n", name);
++ scst_sess = scst_register_session(tport->tgt, 0, name, sess, NULL,
++ NULL);
++ if (!scst_sess) {
++ kfree(sess);
++ return FC_SPP_RESP_RES; /* out of resources */
++ }
++ sess->scst_sess = scst_sess;
++ hlist_add_head_rcu(&sess->hash, head);
++ tport->sess_count++;
++
++ FT_SESS_DBG("port_id %x sess %p\n", port_id, sess);
++
++ rdata->prli_count++;
++ return 0;
++}
++
++/*
++ * Unhash the session.
++ * Caller holds ft_lport_lock.
++ */
++static void ft_sess_unhash(struct ft_sess *sess)
++{
++ struct ft_tport *tport = sess->tport;
++
++ hlist_del_rcu(&sess->hash);
++ BUG_ON(!tport->sess_count);
++ tport->sess_count--;
++ sess->port_id = -1;
++ sess->params = 0;
++}
++
++/*
++ * Delete session from hash.
++ * Caller holds ft_lport_lock.
++ */
++static struct ft_sess *ft_sess_delete(struct ft_tport *tport, u32 port_id)
++{
++ struct hlist_head *head;
++ struct hlist_node *pos;
++ struct ft_sess *sess;
++
++ head = &tport->hash[hash_32(port_id, FT_SESS_HASH_BITS)];
++ hlist_for_each_entry_rcu(sess, pos, head, hash) {
++ if (sess->port_id == port_id) {
++ ft_sess_unhash(sess);
++ return sess;
++ }
++ }
++ return NULL;
++}
++
++/*
++ * Remove session and send PRLO.
++ * This is called when the target is being deleted.
++ * Caller holds ft_lport_lock.
++ */
++static void ft_sess_close(struct ft_sess *sess)
++{
++ struct fc_lport *lport;
++ u32 port_id;
++
++ lport = sess->tport->lport;
++ port_id = sess->port_id;
++ if (port_id == -1)
++ return;
++ FT_SESS_DBG("port_id %x\n", port_id);
++ ft_sess_unhash(sess);
++ /* XXX should send LOGO or PRLO to rport */
++}
++
++/*
++ * Allocate and fill in the SPC Transport ID for persistent reservations.
++ */
++int ft_get_transport_id(struct scst_tgt *tgt, struct scst_session *scst_sess,
++ uint8_t **result)
++{
++ struct ft_sess *sess;
++ struct {
++ u8 format_proto; /* format and protocol ID (0 for FC) */
++ u8 __resv1[7];
++ __be64 port_name; /* N_Port Name */
++ u8 __resv2[8];
++ } __attribute__((__packed__)) *id;
++
++ if (!scst_sess)
++ return SCSI_TRANSPORTID_PROTOCOLID_FCP2;
++
++ id = kzalloc(sizeof(*id), GFP_KERNEL);
++ if (!id)
++ return -ENOMEM;
++
++ sess = scst_sess_get_tgt_priv(scst_sess);
++ id->port_name = cpu_to_be64(sess->port_name);
++ id->format_proto = SCSI_TRANSPORTID_PROTOCOLID_FCP2;
++ *result = (uint8_t *)id;
++ return 0;
++}
++
++/*
++ * libfc ops involving sessions.
++ */
++
++/*
++ * Handle PRLI (process login) request.
++ * This could be a PRLI we're sending or receiving.
++ * Caller holds ft_lport_lock.
++ */
++static int ft_prli_locked(struct fc_rport_priv *rdata, u32 spp_len,
++ const struct fc_els_spp *rspp, struct fc_els_spp *spp)
++{
++ struct ft_tport *tport;
++ u32 fcp_parm;
++ int ret;
++
++ if (!rspp)
++ goto fill;
++
++ if (rspp->spp_flags & (FC_SPP_OPA_VAL | FC_SPP_RPA_VAL))
++ return FC_SPP_RESP_NO_PA;
++
++ /*
++ * If both target and initiator bits are off, the SPP is invalid.
++ */
++ fcp_parm = ntohl(rspp->spp_params); /* requested parameters */
++ if (!(fcp_parm & (FCP_SPPF_INIT_FCN | FCP_SPPF_TARG_FCN)))
++ return FC_SPP_RESP_INVL;
++
++ /*
++ * Create session (image pair) only if requested by
++ * EST_IMG_PAIR flag and if the requestor is an initiator.
++ */
++ if (rspp->spp_flags & FC_SPP_EST_IMG_PAIR) {
++ spp->spp_flags |= FC_SPP_EST_IMG_PAIR;
++
++ if (!(fcp_parm & FCP_SPPF_INIT_FCN))
++ return FC_SPP_RESP_CONF;
++ tport = rcu_dereference(rdata->local_port->prov[FC_TYPE_FCP]);
++ if (!tport) {
++ /* not a target for this local port */
++ return FC_SPP_RESP_CONF;
++ }
++ if (!tport->enabled) {
++ pr_err("Refused login from %#x because target port %s"
++ " not yet enabled", rdata->ids.port_id,
++ tport->tgt->tgt_name);
++ return FC_SPP_RESP_CONF;
++ }
++ ret = ft_sess_create(tport, rdata, fcp_parm);
++ if (ret)
++ return ret;
++ }
++
++ /*
++ * OR in our service parameters with other provider (initiator), if any.
++ * If the initiator indicates RETRY, we must support that, too.
++ * Don't force RETRY on the initiator, though.
++ */
++fill:
++ fcp_parm = ntohl(spp->spp_params); /* response parameters */
++ spp->spp_params = htonl(fcp_parm | FCP_SPPF_TARG_FCN);
++ return FC_SPP_RESP_ACK;
++}
++
++/**
++ * tcm_fcp_prli() - Handle incoming or outgoing PRLI for the FCP target
++ * @rdata: remote port private
++ * @spp_len: service parameter page length
++ * @rspp: received service parameter page (NULL for outgoing PRLI)
++ * @spp: response service parameter page
++ *
++ * Returns spp response code.
++ */
++int ft_prli(struct fc_rport_priv *rdata, u32 spp_len,
++ const struct fc_els_spp *rspp, struct fc_els_spp *spp)
++{
++ int ret;
++
++ FT_SESS_DBG("starting PRLI port_id %x\n", rdata->ids.port_id);
++ mutex_lock(&ft_lport_lock);
++ ret = ft_prli_locked(rdata, spp_len, rspp, spp);
++ mutex_unlock(&ft_lport_lock);
++ FT_SESS_DBG("port_id %x flags %x parms %x ret %x\n", rdata->ids.port_id,
++ rspp ? rspp->spp_flags : 0, ntohl(spp->spp_params), ret);
++ return ret;
++}
++
++static void ft_sess_rcu_free(struct rcu_head *rcu)
++{
++ struct ft_sess *sess = container_of(rcu, struct ft_sess, rcu);
++
++ kfree(sess);
++}
++
++static void ft_sess_free(struct kref *kref)
++{
++ struct ft_sess *sess = container_of(kref, struct ft_sess, kref);
++ struct scst_session *scst_sess;
++
++ scst_sess = sess->scst_sess;
++ FT_SESS_DBG("unregister %s\n", scst_sess->initiator_name);
++ scst_unregister_session(scst_sess, 0, NULL);
++ call_rcu(&sess->rcu, ft_sess_rcu_free);
++}
++
++static void ft_sess_put(struct ft_sess *sess)
++{
++ int sess_held = atomic_read(&sess->kref.refcount);
++
++ BUG_ON(!sess_held);
++ kref_put(&sess->kref, ft_sess_free);
++}
++
++/*
++ * Delete ft_sess for PRLO.
++ * Called with ft_lport_lock held.
++ */
++static struct ft_sess *ft_sess_lookup_delete(struct fc_rport_priv *rdata)
++{
++ struct ft_sess *sess;
++ struct ft_tport *tport;
++
++ tport = rcu_dereference(rdata->local_port->prov[FC_TYPE_FCP]);
++ if (!tport)
++ return NULL;
++ sess = ft_sess_delete(tport, rdata->ids.port_id);
++ if (sess)
++ sess->params = 0;
++ return sess;
++}
++
++/*
++ * Handle PRLO.
++ */
++void ft_prlo(struct fc_rport_priv *rdata)
++{
++ struct ft_sess *sess;
++
++ mutex_lock(&ft_lport_lock);
++ sess = ft_sess_lookup_delete(rdata);
++ mutex_unlock(&ft_lport_lock);
++ if (!sess)
++ return;
++
++ /*
++ * Release the session hold from the table.
++ * When all command-starting threads have returned,
++ * kref will call ft_sess_free which will unregister
++ * the session.
++ * fcmds referencing the session are safe.
++ */
++ ft_sess_put(sess); /* release from table */
++ rdata->prli_count--;
++}
++
++/*
++ * Handle incoming FCP request.
++ *
++ * Caller has verified that the frame is type FCP.
++ * Note that this may be called directly from the softirq context.
++ */
++void ft_recv(struct fc_lport *lport, struct fc_frame *fp)
++{
++ struct ft_sess *sess;
++ struct fc_frame_header *fh;
++ u32 sid;
++
++ fh = fc_frame_header_get(fp);
++ sid = ntoh24(fh->fh_s_id);
++
++ FT_SESS_DBG("sid %x preempt %x\n", sid, preempt_count());
++
++ sess = ft_sess_get(lport, sid);
++ if (!sess) {
++ FT_SESS_DBG("sid %x sess lookup failed\n", sid);
++ /* TBD XXX - if FCP_CMND, send LOGO */
++ fc_frame_free(fp);
++ return;
++ }
++ FT_SESS_DBG("sid %x sess lookup returned %p preempt %x\n",
++ sid, sess, preempt_count());
++ ft_recv_req(sess, fp);
++ ft_sess_put(sess);
++}
++
++/*
++ * Release all sessions for a target.
++ * Called through scst_unregister_target() as well as directly.
++ * Caller holds ft_lport_lock.
++ */
++int ft_tgt_release(struct scst_tgt *tgt)
++{
++ struct ft_tport *tport;
++ struct hlist_head *head;
++ struct hlist_node *pos;
++ struct ft_sess *sess;
++
++ tport = scst_tgt_get_tgt_priv(tgt);
++ tport->enabled = 0;
++ tport->lport->service_params &= ~FCP_SPPF_TARG_FCN;
++
++ for (head = tport->hash; head < &tport->hash[FT_SESS_HASH_SIZE]; head++)
++ hlist_for_each_entry_rcu(sess, pos, head, hash)
++ ft_sess_close(sess);
++
++ synchronize_rcu();
++ return 0;
++}
++
++int ft_tgt_enable(struct scst_tgt *tgt, bool enable)
++{
++ struct ft_tport *tport;
++ int ret = 0;
++
++ mutex_lock(&ft_lport_lock);
++ if (enable) {
++ FT_SESS_DBG("enable tgt %s\n", tgt->tgt_name);
++ tport = scst_tgt_get_tgt_priv(tgt);
++ tport->enabled = 1;
++ tport->lport->service_params |= FCP_SPPF_TARG_FCN;
++ } else {
++ FT_SESS_DBG("disable tgt %s\n", tgt->tgt_name);
++ ft_tgt_release(tgt);
++ }
++ mutex_unlock(&ft_lport_lock);
++ return ret;
++}
++
++bool ft_tgt_enabled(struct scst_tgt *tgt)
++{
++ struct ft_tport *tport;
++
++ tport = scst_tgt_get_tgt_priv(tgt);
++ return tport->enabled;
++}
++
++int ft_tgt_detect(struct scst_tgt_template *tt)
++{
++ return ft_tport_count;
++}
++
++/*
++ * Report AEN (Asynchronous Event Notification) from device to initiator.
++ * See notes in scst.h.
++ */
++int ft_report_aen(struct scst_aen *aen)
++{
++ struct ft_sess *sess;
++
++ sess = scst_sess_get_tgt_priv(scst_aen_get_sess(aen));
++ FT_SESS_DBG("AEN event %d sess to %x lun %lld\n",
++ aen->event_fn, sess->port_id, scst_aen_get_lun(aen));
++ return SCST_AEN_RES_FAILED; /* XXX TBD */
++}
+diff -uprN orig/linux-3.2/Documentation/scst/README.fcst linux-3.2/Documentation/scst/README.fcst
+--- orig/linux-3.2/Documentation/scst/README.fcst
++++ linux-3.2/Documentation/scst/README.fcst
+@@ -0,0 +1,114 @@
++About fcst
++==========
++
++The fcst kernel module implements an SCST target driver for the FCoE protocol.
++FCoE or Fibre Channel over Ethernet is a protocol that allows to communicate
++fibre channel frames over an Ethernet network. Since the FCoE protocol
++requires a lossless Ethernet network, special network adapters and switches
++are required. Ethernet network adapters that support FCoE are called
++Converged Network Adapters (CNA). The standard that makes lossless Ethernet
++communication possible is called DCB or Data Center Bridging.
++
++Since FCoE frames are a kind of Ethernet frames, communication between FCoE
++clients and servers is limited to a single Ethernet broadcast domain.
++
++
++Building and Installing
++=======================
++
++FCST is a kernel module that depends on libfc and SCST to provide FC target
++support.
++
++To build for linux-2.6.34, do:
++
++1. Get the kernel source:
++
++ KERNEL=linux-2.6.34
++
++ cd /usr/src/kernels
++ URL_DIR=http://www.kernel.org/pub/linux/kernel/v2.6
++ TARFILE=$KERNEL.tar.bz2
++ wget -o $TARFILE $URL_DIR/$TARFILE
++ tar xfj $TARFILE
++ cd $KERNEL
++
++2. Apply patches needed for libfc target hooks and point-to-point fixes:
++
++ KDIR=/usr/src/kernels/$KERNEL
++ PDIR=/usr/src/scst/trunk/fcst/linux-patches # use your dir here
++
++ cd $PDIR
++ for patch in `grep -v '^#' series-2.6.34`
++ do
++ (cd $KDIR; patch -p1) < $patch
++ done
++
++3. Apply SCST patches to the kernel
++ See trunk/scst/README
++ The readahead patches are not needed in 2.6.33 or later.
++
++4. Configure, make, and install your kernel
++
++5. Install SCST
++ See trunk/scst/README. Make sure you are building sysfs SCST build,
++ because FCST supports only it. You need to do
++
++ cd trunk/scst
++ make
++ make install
++
++6. Make FCST
++ In the directory containing this README, just do
++ make
++ make install
++
++7. Install the FCoE admin tools, including dcbd and fcoeadm.
++ Some distros may have these.
++ You should be able to use the source at
++ http://www.open-fcoe.org/openfc/downloads/2.6.34/open-fcoe-2.6.34.tar.gz
++
++8. Bring up SCST and configure the devices.
++
++9. Bring up an FCoE initiator (we'll enable target mode on it later):
++ modprobe fcoe
++ fcoeadm -c eth3
++
++ The other end can be an initiator as well, in point-to-point mode
++ over a full-duplex loss-less link (enable pause on both sides).
++ Alternatively, the other end can be an FCoE switch.
++
++10. Use fcc (part of the open-fcoe contrib tools in step 7) to see the
++ initiator setup. To get the FCoE port name for eth3
++
++ # fcc
++ FC HBAs:
++ HBA Port Name Port ID State Device
++ host4 20:00:00:1b:21:06:58:21 01:01:02 Online eth3
++
++ host4 Remote Ports:
++ Path Port Name Port ID State Roles
++ 4:0-0 10:00:50:41:4c:4f:3b:00 01:01:01 Online FCP Initiator
++
++ In the above example, there's one local host on eth3, and it's in
++ a point-to-point connection with the remote initiator with Port_id 010101.
++
++11. Load fcst
++
++ modprobe fcst
++
++12. Add any disks (configured in step 8) you want to export
++ Note that you must have a LUN 0.
++
++ LPORT=20:00:00:1b:21:06:58:21 # the local Port_Name
++
++ cd /sys/kernel/scst_tgt/targets/fcst/$LPORT
++ echo add disk-name 0 > luns/mgmt
++ echo add disk-name 1 > luns/mgmt
++
++13. Enable the initiator:
++
++ echo 1 > $LPORT/enabled
++
++14. As a temporary workaround, you may need to reset the interface
++ on the initiator side so it sees the SCST device as a target and
++ discovers LUNs. You can avoid this by bringing up the initiator last.
+diff -uprN orig/linux-3.2/include/scst/iscsi_scst.h linux-3.2/include/scst/iscsi_scst.h
+--- orig/linux-3.2/include/scst/iscsi_scst.h
++++ linux-3.2/include/scst/iscsi_scst.h
+@@ -0,0 +1,226 @@
++/*
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#ifndef _ISCSI_SCST_U_H
++#define _ISCSI_SCST_U_H
++
++#ifndef __KERNEL__
++#include <sys/uio.h>
++#endif
++
++#include "iscsi_scst_ver.h"
++#include "iscsi_scst_itf_ver.h"
++
++/* The maximum length of 223 bytes in the RFC. */
++#define ISCSI_NAME_LEN 256
++
++#define ISCSI_PORTAL_LEN 64
++
++/* Full name is iSCSI name + connected portal */
++#define ISCSI_FULL_NAME_LEN (ISCSI_NAME_LEN + ISCSI_PORTAL_LEN)
++
++#define ISCSI_LISTEN_PORT 3260
++
++#define SCSI_ID_LEN 24
++
++#ifndef aligned_u64
++#define aligned_u64 uint64_t __attribute__((aligned(8)))
++#endif
++
++#define ISCSI_MAX_ATTR_NAME_LEN 50
++#define ISCSI_MAX_ATTR_VALUE_LEN 512
++
++enum {
++ key_initial_r2t,
++ key_immediate_data,
++ key_max_connections,
++ key_max_recv_data_length,
++ key_max_xmit_data_length,
++ key_max_burst_length,
++ key_first_burst_length,
++ key_default_wait_time,
++ key_default_retain_time,
++ key_max_outstanding_r2t,
++ key_data_pdu_inorder,
++ key_data_sequence_inorder,
++ key_error_recovery_level,
++ key_header_digest,
++ key_data_digest,
++ key_ofmarker,
++ key_ifmarker,
++ key_ofmarkint,
++ key_ifmarkint,
++ session_key_last,
++};
++
++enum {
++ key_queued_cmnds,
++ key_rsp_timeout,
++ key_nop_in_interval,
++ key_nop_in_timeout,
++ key_max_sessions,
++ target_key_last,
++};
++
++enum {
++ key_session,
++ key_target,
++};
++
++struct iscsi_kern_target_info {
++ u32 tid;
++ u32 cookie;
++ char name[ISCSI_NAME_LEN];
++ u32 attrs_num;
++ aligned_u64 attrs_ptr;
++};
++
++struct iscsi_kern_session_info {
++ u32 tid;
++ aligned_u64 sid;
++ char initiator_name[ISCSI_NAME_LEN];
++ char full_initiator_name[ISCSI_FULL_NAME_LEN];
++ u32 exp_cmd_sn;
++ s32 session_params[session_key_last];
++ s32 target_params[target_key_last];
++};
++
++#define DIGEST_ALL (DIGEST_NONE | DIGEST_CRC32C)
++#define DIGEST_NONE (1 << 0)
++#define DIGEST_CRC32C (1 << 1)
++
++struct iscsi_kern_conn_info {
++ u32 tid;
++ aligned_u64 sid;
++
++ u32 cid;
++ u32 stat_sn;
++ u32 exp_stat_sn;
++ int fd;
++};
++
++struct iscsi_kern_attr {
++ u32 mode;
++ char name[ISCSI_MAX_ATTR_NAME_LEN];
++};
++
++struct iscsi_kern_mgmt_cmd_res_info {
++ u32 tid;
++ u32 cookie;
++ u32 req_cmd;
++ u32 result;
++ char value[ISCSI_MAX_ATTR_VALUE_LEN];
++};
++
++struct iscsi_kern_params_info {
++ u32 tid;
++ aligned_u64 sid;
++
++ u32 params_type;
++ u32 partial;
++
++ s32 session_params[session_key_last];
++ s32 target_params[target_key_last];
++};
++
++enum iscsi_kern_event_code {
++ E_ADD_TARGET,
++ E_DEL_TARGET,
++ E_MGMT_CMD,
++ E_ENABLE_TARGET,
++ E_DISABLE_TARGET,
++ E_GET_ATTR_VALUE,
++ E_SET_ATTR_VALUE,
++ E_CONN_CLOSE,
++};
++
++struct iscsi_kern_event {
++ u32 tid;
++ aligned_u64 sid;
++ u32 cid;
++ u32 code;
++ u32 cookie;
++ char target_name[ISCSI_NAME_LEN];
++ u32 param1_size;
++ u32 param2_size;
++};
++
++struct iscsi_kern_register_info {
++ union {
++ aligned_u64 version;
++ struct {
++ int max_data_seg_len;
++ int max_queued_cmds;
++ };
++ };
++};
++
++struct iscsi_kern_attr_info {
++ u32 tid;
++ u32 cookie;
++ struct iscsi_kern_attr attr;
++};
++
++struct iscsi_kern_initiator_info {
++ u32 tid;
++ char full_initiator_name[ISCSI_FULL_NAME_LEN];
++};
++
++#define DEFAULT_NR_QUEUED_CMNDS 32
++#define MIN_NR_QUEUED_CMNDS 1
++#define MAX_NR_QUEUED_CMNDS 256
++
++#define DEFAULT_RSP_TIMEOUT 90
++#define MIN_RSP_TIMEOUT 2
++#define MAX_RSP_TIMEOUT 65535
++
++#define DEFAULT_NOP_IN_INTERVAL 30
++#define MIN_NOP_IN_INTERVAL 0
++#define MAX_NOP_IN_INTERVAL 65535
++
++#define DEFAULT_NOP_IN_TIMEOUT 30
++#define MIN_NOP_IN_TIMEOUT 2
++#define MAX_NOP_IN_TIMEOUT 65535
++
++#define NETLINK_ISCSI_SCST 25
++
++#define REGISTER_USERD _IOWR('s', 0, struct iscsi_kern_register_info)
++#define ADD_TARGET _IOW('s', 1, struct iscsi_kern_target_info)
++#define DEL_TARGET _IOW('s', 2, struct iscsi_kern_target_info)
++#define ADD_SESSION _IOW('s', 3, struct iscsi_kern_session_info)
++#define DEL_SESSION _IOW('s', 4, struct iscsi_kern_session_info)
++#define ADD_CONN _IOW('s', 5, struct iscsi_kern_conn_info)
++#define DEL_CONN _IOW('s', 6, struct iscsi_kern_conn_info)
++#define ISCSI_PARAM_SET _IOW('s', 7, struct iscsi_kern_params_info)
++#define ISCSI_PARAM_GET _IOWR('s', 8, struct iscsi_kern_params_info)
++
++#define ISCSI_ATTR_ADD _IOW('s', 9, struct iscsi_kern_attr_info)
++#define ISCSI_ATTR_DEL _IOW('s', 10, struct iscsi_kern_attr_info)
++#define MGMT_CMD_CALLBACK _IOW('s', 11, struct iscsi_kern_mgmt_cmd_res_info)
++
++#define ISCSI_INITIATOR_ALLOWED _IOW('s', 12, struct iscsi_kern_initiator_info)
++
++static inline int iscsi_is_key_internal(int key)
++{
++ switch (key) {
++ case key_max_xmit_data_length:
++ return 1;
++ default:
++ return 0;
++ }
++}
++
++#endif
+diff -uprN orig/linux-3.2/include/scst/iscsi_scst_ver.h linux-3.2/include/scst/iscsi_scst_ver.h
+--- orig/linux-3.2/include/scst/iscsi_scst_ver.h
++++ linux-3.2/include/scst/iscsi_scst_ver.h
+@@ -0,0 +1,20 @@
++/*
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++
++#define ISCSI_VERSION_STRING_SUFFIX
++
++#define ISCSI_VERSION_STRING "2.2.0-pre" ISCSI_VERSION_STRING_SUFFIX
+diff -uprN orig/linux-3.2/include/scst/iscsi_scst_itf_ver.h linux-3.2/include/scst/iscsi_scst_itf_ver.h
+--- orig/linux-3.2/include/scst/iscsi_scst_itf_ver.h
++++ linux-3.2/include/scst/iscsi_scst_itf_ver.h
+@@ -0,0 +1,3 @@
++/* Autogenerated, don't edit */
++
++#define ISCSI_SCST_INTERFACE_VERSION ISCSI_VERSION_STRING "_" "6e5293bf78ac2fa099a12c932a10afb091dc7731"
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/Makefile linux-3.2/drivers/scst/iscsi-scst/Makefile
+--- orig/linux-3.2/drivers/scst/iscsi-scst/Makefile
++++ linux-3.2/drivers/scst/iscsi-scst/Makefile
+@@ -0,0 +1,4 @@
++iscsi-scst-y := iscsi.o nthread.o config.o digest.o \
++ conn.o session.o target.o event.o param.o
++
++obj-$(CONFIG_SCST_ISCSI) += iscsi-scst.o
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/Kconfig linux-3.2/drivers/scst/iscsi-scst/Kconfig
+--- orig/linux-3.2/drivers/scst/iscsi-scst/Kconfig
++++ linux-3.2/drivers/scst/iscsi-scst/Kconfig
+@@ -0,0 +1,25 @@
++config SCST_ISCSI
++ tristate "ISCSI Target"
++ depends on SCST && INET && LIBCRC32C
++ default SCST
++ help
++ ISCSI target driver for SCST framework. The iSCSI protocol has been
++ defined in RFC 3720. To use it you should download from
++ http://scst.sourceforge.net the user space part of it.
++
++config SCST_ISCSI_DEBUG_DIGEST_FAILURES
++ bool "Simulate iSCSI digest failures"
++ depends on SCST_ISCSI
++ help
++ Simulates iSCSI digest failures in random places. Even when iSCSI
++ traffic is sent over a TCP connection, the 16-bit TCP checksum is too
++ weak for the requirements of a storage protocol. Furthermore, there
++ are also instances where the TCP checksum does not protect iSCSI
++ data, as when data is corrupted while being transferred on a PCI bus
++ or while in memory. The iSCSI protocol therefore defines a 32-bit CRC
++ digest on iSCSI packets in order to detect data corruption on an
++ end-to-end basis. CRCs can be used on iSCSI PDU headers and/or data.
++ Enabling this option allows to test digest failure recovery in the
++ iSCSI initiator that is talking to SCST.
++
++ If unsure, say "N".
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/config.c linux-3.2/drivers/scst/iscsi-scst/config.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/config.c
++++ linux-3.2/drivers/scst/iscsi-scst/config.c
+@@ -0,0 +1,1034 @@
++/*
++ * Copyright (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++#include "iscsi.h"
++
++/* Protected by target_mgmt_mutex */
++int ctr_open_state;
++
++/* Protected by target_mgmt_mutex */
++static LIST_HEAD(iscsi_attrs_list);
++
++static ssize_t iscsi_version_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ TRACE_ENTRY();
++
++ sprintf(buf, "%s\n", ISCSI_VERSION_STRING);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ strcat(buf, "EXTRACHECKS\n");
++#endif
++
++#ifdef CONFIG_SCST_TRACING
++ strcat(buf, "TRACING\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++ strcat(buf, "DEBUG\n");
++#endif
++
++#ifdef CONFIG_SCST_ISCSI_DEBUG_DIGEST_FAILURES
++ strcat(buf, "DEBUG_DIGEST_FAILURES\n");
++#endif
++
++ TRACE_EXIT();
++ return strlen(buf);
++}
++
++static struct kobj_attribute iscsi_version_attr =
++ __ATTR(version, S_IRUGO, iscsi_version_show, NULL);
++
++static ssize_t iscsi_open_state_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ switch (ctr_open_state) {
++ case ISCSI_CTR_OPEN_STATE_CLOSED:
++ sprintf(buf, "%s\n", "closed");
++ break;
++ case ISCSI_CTR_OPEN_STATE_OPEN:
++ sprintf(buf, "%s\n", "open");
++ break;
++ case ISCSI_CTR_OPEN_STATE_CLOSING:
++ sprintf(buf, "%s\n", "closing");
++ break;
++ default:
++ sprintf(buf, "%s\n", "unknown");
++ break;
++ }
++
++ return strlen(buf);
++}
++
++static struct kobj_attribute iscsi_open_state_attr =
++ __ATTR(open_state, S_IRUGO, iscsi_open_state_show, NULL);
++
++const struct attribute *iscsi_attrs[] = {
++ &iscsi_version_attr.attr,
++ &iscsi_open_state_attr.attr,
++ NULL,
++};
++
++/* target_mgmt_mutex supposed to be locked */
++static int add_conn(void __user *ptr)
++{
++ int err, rc;
++ struct iscsi_session *session;
++ struct iscsi_kern_conn_info info;
++ struct iscsi_target *target;
++
++ TRACE_ENTRY();
++
++ rc = copy_from_user(&info, ptr, sizeof(info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out;
++ }
++
++ target = target_lookup_by_id(info.tid);
++ if (target == NULL) {
++ PRINT_ERROR("Target %d not found", info.tid);
++ err = -ENOENT;
++ goto out;
++ }
++
++ mutex_lock(&target->target_mutex);
++
++ session = session_lookup(target, info.sid);
++ if (!session) {
++ PRINT_ERROR("Session %lld not found",
++ (long long unsigned int)info.tid);
++ err = -ENOENT;
++ goto out_unlock;
++ }
++
++ err = __add_conn(session, &info);
++
++out_unlock:
++ mutex_unlock(&target->target_mutex);
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int del_conn(void __user *ptr)
++{
++ int err, rc;
++ struct iscsi_session *session;
++ struct iscsi_kern_conn_info info;
++ struct iscsi_target *target;
++
++ TRACE_ENTRY();
++
++ rc = copy_from_user(&info, ptr, sizeof(info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out;
++ }
++
++ target = target_lookup_by_id(info.tid);
++ if (target == NULL) {
++ PRINT_ERROR("Target %d not found", info.tid);
++ err = -ENOENT;
++ goto out;
++ }
++
++ mutex_lock(&target->target_mutex);
++
++ session = session_lookup(target, info.sid);
++ if (!session) {
++ PRINT_ERROR("Session %llx not found",
++ (long long unsigned int)info.sid);
++ err = -ENOENT;
++ goto out_unlock;
++ }
++
++ err = __del_conn(session, &info);
++
++out_unlock:
++ mutex_unlock(&target->target_mutex);
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int add_session(void __user *ptr)
++{
++ int err, rc;
++ struct iscsi_kern_session_info *info;
++ struct iscsi_target *target;
++
++ TRACE_ENTRY();
++
++ info = kzalloc(sizeof(*info), GFP_KERNEL);
++ if (info == NULL) {
++ PRINT_ERROR("Can't alloc info (size %zd)", sizeof(*info));
++ err = -ENOMEM;
++ goto out;
++ }
++
++ rc = copy_from_user(info, ptr, sizeof(*info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out_free;
++ }
++
++ info->initiator_name[sizeof(info->initiator_name)-1] = '\0';
++ info->full_initiator_name[sizeof(info->full_initiator_name)-1] = '\0';
++
++ target = target_lookup_by_id(info->tid);
++ if (target == NULL) {
++ PRINT_ERROR("Target %d not found", info->tid);
++ err = -ENOENT;
++ goto out_free;
++ }
++
++ err = __add_session(target, info);
++
++out_free:
++ kfree(info);
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int del_session(void __user *ptr)
++{
++ int err, rc;
++ struct iscsi_kern_session_info *info;
++ struct iscsi_target *target;
++
++ TRACE_ENTRY();
++
++ info = kzalloc(sizeof(*info), GFP_KERNEL);
++ if (info == NULL) {
++ PRINT_ERROR("Can't alloc info (size %zd)", sizeof(*info));
++ err = -ENOMEM;
++ goto out;
++ }
++
++ rc = copy_from_user(info, ptr, sizeof(*info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out_free;
++ }
++
++ info->initiator_name[sizeof(info->initiator_name)-1] = '\0';
++
++ target = target_lookup_by_id(info->tid);
++ if (target == NULL) {
++ PRINT_ERROR("Target %d not found", info->tid);
++ err = -ENOENT;
++ goto out_free;
++ }
++
++ mutex_lock(&target->target_mutex);
++ err = __del_session(target, info->sid);
++ mutex_unlock(&target->target_mutex);
++
++out_free:
++ kfree(info);
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int iscsi_params_config(void __user *ptr, int set)
++{
++ int err, rc;
++ struct iscsi_kern_params_info info;
++ struct iscsi_target *target;
++
++ TRACE_ENTRY();
++
++ rc = copy_from_user(&info, ptr, sizeof(info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out;
++ }
++
++ target = target_lookup_by_id(info.tid);
++ if (target == NULL) {
++ PRINT_ERROR("Target %d not found", info.tid);
++ err = -ENOENT;
++ goto out;
++ }
++
++ mutex_lock(&target->target_mutex);
++ err = iscsi_params_set(target, &info, set);
++ mutex_unlock(&target->target_mutex);
++
++ if (err < 0)
++ goto out;
++
++ if (!set) {
++ rc = copy_to_user(ptr, &info, sizeof(info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy to user %d bytes", rc);
++ err = -EFAULT;
++ goto out;
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int iscsi_initiator_allowed(void __user *ptr)
++{
++ int err = 0, rc;
++ struct iscsi_kern_initiator_info cinfo;
++ struct iscsi_target *target;
++
++ TRACE_ENTRY();
++
++ rc = copy_from_user(&cinfo, ptr, sizeof(cinfo));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out;
++ }
++
++ cinfo.full_initiator_name[sizeof(cinfo.full_initiator_name)-1] = '\0';
++
++ target = target_lookup_by_id(cinfo.tid);
++ if (target == NULL) {
++ PRINT_ERROR("Target %d not found", cinfo.tid);
++ err = -ENOENT;
++ goto out;
++ }
++
++ err = scst_initiator_has_luns(target->scst_tgt,
++ cinfo.full_initiator_name);
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int mgmt_cmd_callback(void __user *ptr)
++{
++ int err = 0, rc;
++ struct iscsi_kern_mgmt_cmd_res_info cinfo;
++ struct scst_sysfs_user_info *info;
++
++ TRACE_ENTRY();
++
++ rc = copy_from_user(&cinfo, ptr, sizeof(cinfo));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out;
++ }
++
++ cinfo.value[sizeof(cinfo.value)-1] = '\0';
++
++ info = scst_sysfs_user_get_info(cinfo.cookie);
++ TRACE_DBG("cookie %u, info %p, result %d", cinfo.cookie, info,
++ cinfo.result);
++ if (info == NULL) {
++ err = -EINVAL;
++ goto out;
++ }
++
++ info->info_status = 0;
++
++ if (cinfo.result != 0) {
++ info->info_status = cinfo.result;
++ goto out_complete;
++ }
++
++ switch (cinfo.req_cmd) {
++ case E_ENABLE_TARGET:
++ case E_DISABLE_TARGET:
++ {
++ struct iscsi_target *target;
++
++ target = target_lookup_by_id(cinfo.tid);
++ if (target == NULL) {
++ PRINT_ERROR("Target %d not found", cinfo.tid);
++ err = -ENOENT;
++ goto out_status;
++ }
++
++ target->tgt_enabled = (cinfo.req_cmd == E_ENABLE_TARGET) ? 1 : 0;
++ break;
++ }
++
++ case E_GET_ATTR_VALUE:
++ info->data = kstrdup(cinfo.value, GFP_KERNEL);
++ if (info->data == NULL) {
++ PRINT_ERROR("Can't dublicate value %s", cinfo.value);
++ info->info_status = -ENOMEM;
++ goto out_complete;
++ }
++ break;
++ }
++
++out_complete:
++ complete(&info->info_completion);
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++
++out_status:
++ info->info_status = err;
++ goto out_complete;
++}
++
++static ssize_t iscsi_attr_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos;
++ struct iscsi_attr *tgt_attr;
++ void *value;
++
++ TRACE_ENTRY();
++
++ tgt_attr = container_of(attr, struct iscsi_attr, attr);
++
++ pos = iscsi_sysfs_send_event(
++ (tgt_attr->target != NULL) ? tgt_attr->target->tid : 0,
++ E_GET_ATTR_VALUE, tgt_attr->name, NULL, &value);
++
++ if (pos != 0)
++ goto out;
++
++ pos = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n", (char *)value);
++
++ kfree(value);
++
++out:
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t iscsi_attr_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ char *buffer;
++ struct iscsi_attr *tgt_attr;
++
++ TRACE_ENTRY();
++
++ buffer = kzalloc(count+1, GFP_KERNEL);
++ if (buffer == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++ memcpy(buffer, buf, count);
++ buffer[count] = '\0';
++
++ tgt_attr = container_of(attr, struct iscsi_attr, attr);
++
++ TRACE_DBG("attr %s, buffer %s", tgt_attr->attr.attr.name, buffer);
++
++ res = iscsi_sysfs_send_event(
++ (tgt_attr->target != NULL) ? tgt_attr->target->tid : 0,
++ E_SET_ATTR_VALUE, tgt_attr->name, buffer, NULL);
++
++ kfree(buffer);
++
++ if (res == 0)
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * target_mgmt_mutex supposed to be locked. If target != 0, target_mutex
++ * supposed to be locked as well.
++ */
++int iscsi_add_attr(struct iscsi_target *target,
++ const struct iscsi_kern_attr *attr_info)
++{
++ int res = 0;
++ struct iscsi_attr *tgt_attr;
++ struct list_head *attrs_list;
++ const char *name;
++#ifdef CONFIG_DEBUG_LOCK_ALLOC
++ static struct lock_class_key __key;
++#endif
++
++ TRACE_ENTRY();
++
++ if (target != NULL) {
++ attrs_list = &target->attrs_list;
++ name = target->name;
++ } else {
++ attrs_list = &iscsi_attrs_list;
++ name = "global";
++ }
++
++ list_for_each_entry(tgt_attr, attrs_list, attrs_list_entry) {
++ /* Both for sure NULL-terminated */
++ if (strcmp(tgt_attr->name, attr_info->name) == 0) {
++ PRINT_ERROR("Attribute %s for %s already exist",
++ attr_info->name, name);
++ res = -EEXIST;
++ goto out;
++ }
++ }
++
++ TRACE_DBG("Adding %s's attr %s with mode %x", name,
++ attr_info->name, attr_info->mode);
++
++ tgt_attr = kzalloc(sizeof(*tgt_attr), GFP_KERNEL);
++ if (tgt_attr == NULL) {
++ PRINT_ERROR("Unable to allocate user (size %zd)",
++ sizeof(*tgt_attr));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ tgt_attr->target = target;
++
++ tgt_attr->name = kstrdup(attr_info->name, GFP_KERNEL);
++ if (tgt_attr->name == NULL) {
++ PRINT_ERROR("Unable to allocate attr %s name/value (target %s)",
++ attr_info->name, name);
++ res = -ENOMEM;
++ goto out_free;
++ }
++
++ list_add(&tgt_attr->attrs_list_entry, attrs_list);
++
++ tgt_attr->attr.attr.name = tgt_attr->name;
++#ifdef CONFIG_DEBUG_LOCK_ALLOC
++ tgt_attr->attr.attr.key = &__key;
++#endif
++ tgt_attr->attr.attr.mode = attr_info->mode & (S_IRUGO | S_IWUGO);
++ tgt_attr->attr.show = iscsi_attr_show;
++ tgt_attr->attr.store = iscsi_attr_store;
++
++ TRACE_DBG("tgt_attr %p, attr %p", tgt_attr, &tgt_attr->attr.attr);
++
++ res = sysfs_create_file(
++ (target != NULL) ? scst_sysfs_get_tgt_kobj(target->scst_tgt) :
++ scst_sysfs_get_tgtt_kobj(&iscsi_template),
++ &tgt_attr->attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Unable to create file '%s' for target '%s'",
++ tgt_attr->attr.attr.name, name);
++ goto out_del;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_del:
++ list_del(&tgt_attr->attrs_list_entry);
++
++out_free:
++ kfree(tgt_attr->name);
++ kfree(tgt_attr);
++ goto out;
++}
++
++void __iscsi_del_attr(struct iscsi_target *target,
++ struct iscsi_attr *tgt_attr)
++{
++ TRACE_ENTRY();
++
++ TRACE_DBG("Deleting attr %s (target %s, tgt_attr %p, attr %p)",
++ tgt_attr->name, (target != NULL) ? target->name : "global",
++ tgt_attr, &tgt_attr->attr.attr);
++
++ list_del(&tgt_attr->attrs_list_entry);
++
++ sysfs_remove_file((target != NULL) ?
++ scst_sysfs_get_tgt_kobj(target->scst_tgt) :
++ scst_sysfs_get_tgtt_kobj(&iscsi_template),
++ &tgt_attr->attr.attr);
++
++ kfree(tgt_attr->name);
++ kfree(tgt_attr);
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * target_mgmt_mutex supposed to be locked. If target != 0, target_mutex
++ * supposed to be locked as well.
++ */
++static int iscsi_del_attr(struct iscsi_target *target,
++ const char *attr_name)
++{
++ int res = 0;
++ struct iscsi_attr *tgt_attr, *a;
++ struct list_head *attrs_list;
++
++ TRACE_ENTRY();
++
++ if (target != NULL)
++ attrs_list = &target->attrs_list;
++ else
++ attrs_list = &iscsi_attrs_list;
++
++ tgt_attr = NULL;
++ list_for_each_entry(a, attrs_list, attrs_list_entry) {
++ /* Both for sure NULL-terminated */
++ if (strcmp(a->name, attr_name) == 0) {
++ tgt_attr = a;
++ break;
++ }
++ }
++
++ if (tgt_attr == NULL) {
++ PRINT_ERROR("attr %s not found (target %s)", attr_name,
++ (target != NULL) ? target->name : "global");
++ res = -ENOENT;
++ goto out;
++ }
++
++ __iscsi_del_attr(target, tgt_attr);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int iscsi_attr_cmd(void __user *ptr, unsigned int cmd)
++{
++ int rc, err = 0;
++ struct iscsi_kern_attr_info info;
++ struct iscsi_target *target;
++ struct scst_sysfs_user_info *i = NULL;
++
++ TRACE_ENTRY();
++
++ rc = copy_from_user(&info, ptr, sizeof(info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out;
++ }
++
++ info.attr.name[sizeof(info.attr.name)-1] = '\0';
++
++ if (info.cookie != 0) {
++ i = scst_sysfs_user_get_info(info.cookie);
++ TRACE_DBG("cookie %u, uinfo %p", info.cookie, i);
++ if (i == NULL) {
++ err = -EINVAL;
++ goto out;
++ }
++ }
++
++ target = target_lookup_by_id(info.tid);
++
++ if (target != NULL)
++ mutex_lock(&target->target_mutex);
++
++ switch (cmd) {
++ case ISCSI_ATTR_ADD:
++ err = iscsi_add_attr(target, &info.attr);
++ break;
++ case ISCSI_ATTR_DEL:
++ err = iscsi_del_attr(target, info.attr.name);
++ break;
++ default:
++ BUG();
++ }
++
++ if (target != NULL)
++ mutex_unlock(&target->target_mutex);
++
++ if (i != NULL) {
++ i->info_status = err;
++ complete(&i->info_completion);
++ }
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int add_target(void __user *ptr)
++{
++ int err, rc;
++ struct iscsi_kern_target_info *info;
++ struct scst_sysfs_user_info *uinfo;
++
++ TRACE_ENTRY();
++
++ info = kzalloc(sizeof(*info), GFP_KERNEL);
++ if (info == NULL) {
++ PRINT_ERROR("Can't alloc info (size %zd)", sizeof(*info));
++ err = -ENOMEM;
++ goto out;
++ }
++
++ rc = copy_from_user(info, ptr, sizeof(*info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out_free;
++ }
++
++ if (target_lookup_by_id(info->tid) != NULL) {
++ PRINT_ERROR("Target %u already exist!", info->tid);
++ err = -EEXIST;
++ goto out_free;
++ }
++
++ info->name[sizeof(info->name)-1] = '\0';
++
++ if (info->cookie != 0) {
++ uinfo = scst_sysfs_user_get_info(info->cookie);
++ TRACE_DBG("cookie %u, uinfo %p", info->cookie, uinfo);
++ if (uinfo == NULL) {
++ err = -EINVAL;
++ goto out_free;
++ }
++ } else
++ uinfo = NULL;
++
++ err = __add_target(info);
++
++ if (uinfo != NULL) {
++ uinfo->info_status = err;
++ complete(&uinfo->info_completion);
++ }
++
++out_free:
++ kfree(info);
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int del_target(void __user *ptr)
++{
++ int err, rc;
++ struct iscsi_kern_target_info info;
++ struct scst_sysfs_user_info *uinfo;
++
++ TRACE_ENTRY();
++
++ rc = copy_from_user(&info, ptr, sizeof(info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy %d user's bytes", rc);
++ err = -EFAULT;
++ goto out;
++ }
++
++ info.name[sizeof(info.name)-1] = '\0';
++
++ if (info.cookie != 0) {
++ uinfo = scst_sysfs_user_get_info(info.cookie);
++ TRACE_DBG("cookie %u, uinfo %p", info.cookie, uinfo);
++ if (uinfo == NULL) {
++ err = -EINVAL;
++ goto out;
++ }
++ } else
++ uinfo = NULL;
++
++ err = __del_target(info.tid);
++
++ if (uinfo != NULL) {
++ uinfo->info_status = err;
++ complete(&uinfo->info_completion);
++ }
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++static int iscsi_register(void __user *arg)
++{
++ struct iscsi_kern_register_info reg;
++ char ver[sizeof(ISCSI_SCST_INTERFACE_VERSION)+1];
++ int res, rc;
++
++ TRACE_ENTRY();
++
++ rc = copy_from_user(&reg, arg, sizeof(reg));
++ if (rc != 0) {
++ PRINT_ERROR("%s", "Unable to get register info");
++ res = -EFAULT;
++ goto out;
++ }
++
++ rc = copy_from_user(ver, (void __user *)(unsigned long)reg.version,
++ sizeof(ver));
++ if (rc != 0) {
++ PRINT_ERROR("%s", "Unable to get version string");
++ res = -EFAULT;
++ goto out;
++ }
++ ver[sizeof(ver)-1] = '\0';
++
++ if (strcmp(ver, ISCSI_SCST_INTERFACE_VERSION) != 0) {
++ PRINT_ERROR("Incorrect version of user space %s (expected %s)",
++ ver, ISCSI_SCST_INTERFACE_VERSION);
++ res = -EINVAL;
++ goto out;
++ }
++
++ memset(&reg, 0, sizeof(reg));
++ reg.max_data_seg_len = ISCSI_CONN_IOV_MAX << PAGE_SHIFT;
++ reg.max_queued_cmds = scst_get_max_lun_commands(NULL, NO_SUCH_LUN);
++
++ res = 0;
++
++ rc = copy_to_user(arg, &reg, sizeof(reg));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy to user %d bytes", rc);
++ res = -EFAULT;
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static long ioctl(struct file *file, unsigned int cmd, unsigned long arg)
++{
++ long err;
++
++ TRACE_ENTRY();
++
++ if (cmd == REGISTER_USERD) {
++ err = iscsi_register((void __user *)arg);
++ goto out;
++ }
++
++ err = mutex_lock_interruptible(&target_mgmt_mutex);
++ if (err < 0)
++ goto out;
++
++ switch (cmd) {
++ case ADD_TARGET:
++ err = add_target((void __user *)arg);
++ break;
++
++ case DEL_TARGET:
++ err = del_target((void __user *)arg);
++ break;
++
++ case ISCSI_ATTR_ADD:
++ case ISCSI_ATTR_DEL:
++ err = iscsi_attr_cmd((void __user *)arg, cmd);
++ break;
++
++ case MGMT_CMD_CALLBACK:
++ err = mgmt_cmd_callback((void __user *)arg);
++ break;
++
++ case ISCSI_INITIATOR_ALLOWED:
++ err = iscsi_initiator_allowed((void __user *)arg);
++ break;
++
++ case ADD_SESSION:
++ err = add_session((void __user *)arg);
++ break;
++
++ case DEL_SESSION:
++ err = del_session((void __user *)arg);
++ break;
++
++ case ISCSI_PARAM_SET:
++ err = iscsi_params_config((void __user *)arg, 1);
++ break;
++
++ case ISCSI_PARAM_GET:
++ err = iscsi_params_config((void __user *)arg, 0);
++ break;
++
++ case ADD_CONN:
++ err = add_conn((void __user *)arg);
++ break;
++
++ case DEL_CONN:
++ err = del_conn((void __user *)arg);
++ break;
++
++ default:
++ PRINT_ERROR("Invalid ioctl cmd %x", cmd);
++ err = -EINVAL;
++ goto out_unlock;
++ }
++
++out_unlock:
++ mutex_unlock(&target_mgmt_mutex);
++
++out:
++ TRACE_EXIT_RES(err);
++ return err;
++}
++
++static int open(struct inode *inode, struct file *file)
++{
++ bool already;
++
++ mutex_lock(&target_mgmt_mutex);
++ already = (ctr_open_state != ISCSI_CTR_OPEN_STATE_CLOSED);
++ if (!already)
++ ctr_open_state = ISCSI_CTR_OPEN_STATE_OPEN;
++ mutex_unlock(&target_mgmt_mutex);
++
++ if (already) {
++ PRINT_WARNING("%s", "Attempt to second open the control "
++ "device!");
++ return -EBUSY;
++ } else
++ return 0;
++}
++
++static int release(struct inode *inode, struct file *filp)
++{
++ struct iscsi_attr *attr, *t;
++
++ TRACE(TRACE_MGMT, "%s", "Releasing allocated resources");
++
++ mutex_lock(&target_mgmt_mutex);
++ ctr_open_state = ISCSI_CTR_OPEN_STATE_CLOSING;
++ mutex_unlock(&target_mgmt_mutex);
++
++ target_del_all();
++
++ mutex_lock(&target_mgmt_mutex);
++
++ list_for_each_entry_safe(attr, t, &iscsi_attrs_list,
++ attrs_list_entry) {
++ __iscsi_del_attr(NULL, attr);
++ }
++
++ ctr_open_state = ISCSI_CTR_OPEN_STATE_CLOSED;
++
++ mutex_unlock(&target_mgmt_mutex);
++
++ return 0;
++}
++
++const struct file_operations ctr_fops = {
++ .owner = THIS_MODULE,
++ .unlocked_ioctl = ioctl,
++ .compat_ioctl = ioctl,
++ .open = open,
++ .release = release,
++};
++
++#ifdef CONFIG_SCST_DEBUG
++static void iscsi_dump_char(int ch, unsigned char *text, int *pos)
++{
++ int i = *pos;
++
++ if (ch < 0) {
++ while ((i % 16) != 0) {
++ printk(KERN_CONT " ");
++ text[i] = ' ';
++ i++;
++ if ((i % 16) == 0)
++ printk(KERN_CONT " | %.16s |\n", text);
++ else if ((i % 4) == 0)
++ printk(KERN_CONT " |");
++ }
++ i = 0;
++ goto out;
++ }
++
++ text[i] = (ch < 0x20 || (ch >= 0x80 && ch <= 0xa0)) ? ' ' : ch;
++ printk(KERN_CONT " %02x", ch);
++ i++;
++ if ((i % 16) == 0) {
++ printk(KERN_CONT " | %.16s |\n", text);
++ i = 0;
++ } else if ((i % 4) == 0)
++ printk(KERN_CONT " |");
++
++out:
++ *pos = i;
++ return;
++}
++
++void iscsi_dump_pdu(struct iscsi_pdu *pdu)
++{
++ unsigned char text[16];
++ int pos = 0;
++
++ if (trace_flag & TRACE_D_DUMP_PDU) {
++ unsigned char *buf;
++ int i;
++
++ buf = (void *)&pdu->bhs;
++ printk(KERN_DEBUG "BHS: (%p,%zd)\n", buf, sizeof(pdu->bhs));
++ for (i = 0; i < (int)sizeof(pdu->bhs); i++)
++ iscsi_dump_char(*buf++, text, &pos);
++ iscsi_dump_char(-1, text, &pos);
++
++ buf = (void *)pdu->ahs;
++ printk(KERN_DEBUG "AHS: (%p,%d)\n", buf, pdu->ahssize);
++ for (i = 0; i < pdu->ahssize; i++)
++ iscsi_dump_char(*buf++, text, &pos);
++ iscsi_dump_char(-1, text, &pos);
++
++ printk(KERN_DEBUG "Data: (%d)\n", pdu->datasize);
++ }
++}
++
++unsigned long iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(struct iscsi_cmnd *cmnd)
++{
++ unsigned long flag;
++
++ if (cmnd->cmd_req != NULL)
++ cmnd = cmnd->cmd_req;
++
++ if (cmnd->scst_cmd == NULL)
++ flag = TRACE_MGMT_DEBUG;
++ else {
++ int status = scst_cmd_get_status(cmnd->scst_cmd);
++ if ((status == SAM_STAT_TASK_SET_FULL) ||
++ (status == SAM_STAT_BUSY))
++ flag = TRACE_FLOW_CONTROL;
++ else
++ flag = TRACE_MGMT_DEBUG;
++ }
++ return flag;
++}
++
++#endif /* CONFIG_SCST_DEBUG */
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/conn.c linux-3.2/drivers/scst/iscsi-scst/conn.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/conn.c
++++ linux-3.2/drivers/scst/iscsi-scst/conn.c
+@@ -0,0 +1,945 @@
++/*
++ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@ardistech.com>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/file.h>
++#include <linux/ip.h>
++#include <net/tcp.h>
++
++#include "iscsi.h"
++#include "digest.h"
++
++static int print_conn_state(char *p, size_t size, struct iscsi_conn *conn)
++{
++ int pos = 0;
++
++ if (conn->closing) {
++ pos += scnprintf(p, size, "%s", "closing");
++ goto out;
++ }
++
++ switch (conn->rd_state) {
++ case ISCSI_CONN_RD_STATE_PROCESSING:
++ pos += scnprintf(&p[pos], size - pos, "%s", "read_processing ");
++ break;
++ case ISCSI_CONN_RD_STATE_IN_LIST:
++ pos += scnprintf(&p[pos], size - pos, "%s", "in_read_list ");
++ break;
++ }
++
++ switch (conn->wr_state) {
++ case ISCSI_CONN_WR_STATE_PROCESSING:
++ pos += scnprintf(&p[pos], size - pos, "%s", "write_processing ");
++ break;
++ case ISCSI_CONN_WR_STATE_IN_LIST:
++ pos += scnprintf(&p[pos], size - pos, "%s", "in_write_list ");
++ break;
++ case ISCSI_CONN_WR_STATE_SPACE_WAIT:
++ pos += scnprintf(&p[pos], size - pos, "%s", "space_waiting ");
++ break;
++ }
++
++ if (test_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags))
++ pos += scnprintf(&p[pos], size - pos, "%s", "reinstating ");
++ else if (pos == 0)
++ pos += scnprintf(&p[pos], size - pos, "%s", "established idle ");
++
++out:
++ return pos;
++}
++
++static void iscsi_conn_release(struct kobject *kobj)
++{
++ struct iscsi_conn *conn;
++
++ TRACE_ENTRY();
++
++ conn = container_of(kobj, struct iscsi_conn, conn_kobj);
++ if (conn->conn_kobj_release_cmpl != NULL)
++ complete_all(conn->conn_kobj_release_cmpl);
++
++ TRACE_EXIT();
++ return;
++}
++
++struct kobj_type iscsi_conn_ktype = {
++ .release = iscsi_conn_release,
++};
++
++static ssize_t iscsi_get_initiator_ip(struct iscsi_conn *conn,
++ char *buf, int size)
++{
++ int pos;
++ struct sock *sk;
++
++ TRACE_ENTRY();
++
++ sk = conn->sock->sk;
++ switch (sk->sk_family) {
++ case AF_INET:
++ pos = scnprintf(buf, size,
++ "%pI4", &inet_sk(sk)->inet_daddr);
++ break;
++ case AF_INET6:
++ pos = scnprintf(buf, size, "[%p6]",
++ &inet6_sk(sk)->daddr);
++ break;
++ default:
++ pos = scnprintf(buf, size, "Unknown family %d",
++ sk->sk_family);
++ break;
++ }
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static ssize_t iscsi_conn_ip_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos;
++ struct iscsi_conn *conn;
++
++ TRACE_ENTRY();
++
++ conn = container_of(kobj, struct iscsi_conn, conn_kobj);
++
++ pos = iscsi_get_initiator_ip(conn, buf, SCST_SYSFS_BLOCK_SIZE);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static struct kobj_attribute iscsi_conn_ip_attr =
++ __ATTR(ip, S_IRUGO, iscsi_conn_ip_show, NULL);
++
++static ssize_t iscsi_conn_cid_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos;
++ struct iscsi_conn *conn;
++
++ TRACE_ENTRY();
++
++ conn = container_of(kobj, struct iscsi_conn, conn_kobj);
++
++ pos = sprintf(buf, "%u", conn->cid);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static struct kobj_attribute iscsi_conn_cid_attr =
++ __ATTR(cid, S_IRUGO, iscsi_conn_cid_show, NULL);
++
++static ssize_t iscsi_conn_state_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos;
++ struct iscsi_conn *conn;
++
++ TRACE_ENTRY();
++
++ conn = container_of(kobj, struct iscsi_conn, conn_kobj);
++
++ pos = print_conn_state(buf, SCST_SYSFS_BLOCK_SIZE, conn);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static struct kobj_attribute iscsi_conn_state_attr =
++ __ATTR(state, S_IRUGO, iscsi_conn_state_show, NULL);
++
++static void conn_sysfs_del(struct iscsi_conn *conn)
++{
++ int rc;
++ DECLARE_COMPLETION_ONSTACK(c);
++
++ TRACE_ENTRY();
++
++ conn->conn_kobj_release_cmpl = &c;
++
++ kobject_del(&conn->conn_kobj);
++ kobject_put(&conn->conn_kobj);
++
++ rc = wait_for_completion_timeout(conn->conn_kobj_release_cmpl, HZ);
++ if (rc == 0) {
++ PRINT_INFO("Waiting for releasing sysfs entry "
++ "for conn %p (%d refs)...", conn,
++ atomic_read(&conn->conn_kobj.kref.refcount));
++ wait_for_completion(conn->conn_kobj_release_cmpl);
++ PRINT_INFO("Done waiting for releasing sysfs "
++ "entry for conn %p", conn);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static int conn_sysfs_add(struct iscsi_conn *conn)
++{
++ int res;
++ struct iscsi_session *session = conn->session;
++ struct iscsi_conn *c;
++ int n = 1;
++ char addr[64];
++
++ TRACE_ENTRY();
++
++ iscsi_get_initiator_ip(conn, addr, sizeof(addr));
++
++restart:
++ list_for_each_entry(c, &session->conn_list, conn_list_entry) {
++ if (strcmp(addr, kobject_name(&conn->conn_kobj)) == 0) {
++ char c_addr[64];
++
++ iscsi_get_initiator_ip(conn, c_addr, sizeof(c_addr));
++
++ TRACE_DBG("Duplicated conn from the same initiator "
++ "%s found", c_addr);
++
++ snprintf(addr, sizeof(addr), "%s_%d", c_addr, n);
++ n++;
++ goto restart;
++ }
++ }
++
++ res = kobject_init_and_add(&conn->conn_kobj, &iscsi_conn_ktype,
++ scst_sysfs_get_sess_kobj(session->scst_sess), addr);
++ if (res != 0) {
++ PRINT_ERROR("Unable create sysfs entries for conn %s",
++ addr);
++ goto out;
++ }
++
++ TRACE_DBG("conn %p, conn_kobj %p", conn, &conn->conn_kobj);
++
++ res = sysfs_create_file(&conn->conn_kobj,
++ &iscsi_conn_state_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Unable create sysfs attribute %s for conn %s",
++ iscsi_conn_state_attr.attr.name, addr);
++ goto out_err;
++ }
++
++ res = sysfs_create_file(&conn->conn_kobj,
++ &iscsi_conn_cid_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Unable create sysfs attribute %s for conn %s",
++ iscsi_conn_cid_attr.attr.name, addr);
++ goto out_err;
++ }
++
++ res = sysfs_create_file(&conn->conn_kobj,
++ &iscsi_conn_ip_attr.attr);
++ if (res != 0) {
++ PRINT_ERROR("Unable create sysfs attribute %s for conn %s",
++ iscsi_conn_ip_attr.attr.name, addr);
++ goto out_err;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_err:
++ conn_sysfs_del(conn);
++ goto out;
++}
++
++/* target_mutex supposed to be locked */
++struct iscsi_conn *conn_lookup(struct iscsi_session *session, u16 cid)
++{
++ struct iscsi_conn *conn;
++
++ /*
++ * We need to find the latest conn to correctly handle
++ * multi-reinstatements
++ */
++ list_for_each_entry_reverse(conn, &session->conn_list,
++ conn_list_entry) {
++ if (conn->cid == cid)
++ return conn;
++ }
++ return NULL;
++}
++
++void iscsi_make_conn_rd_active(struct iscsi_conn *conn)
++{
++ struct iscsi_thread_pool *p = conn->conn_thr_pool;
++
++ TRACE_ENTRY();
++
++ spin_lock_bh(&p->rd_lock);
++
++ TRACE_DBG("conn %p, rd_state %x, rd_data_ready %d", conn,
++ conn->rd_state, conn->rd_data_ready);
++
++ /*
++ * Let's start processing ASAP not waiting for all the being waited
++ * data be received, even if we need several wakup iteration to receive
++ * them all, because starting ASAP, i.e. in parallel, is better for
++ * performance, especially on multi-CPU/core systems.
++ */
++
++ conn->rd_data_ready = 1;
++
++ if (conn->rd_state == ISCSI_CONN_RD_STATE_IDLE) {
++ list_add_tail(&conn->rd_list_entry, &p->rd_list);
++ conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST;
++ wake_up(&p->rd_waitQ);
++ }
++
++ spin_unlock_bh(&p->rd_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++void iscsi_make_conn_wr_active(struct iscsi_conn *conn)
++{
++ struct iscsi_thread_pool *p = conn->conn_thr_pool;
++
++ TRACE_ENTRY();
++
++ spin_lock_bh(&p->wr_lock);
++
++ TRACE_DBG("conn %p, wr_state %x, wr_space_ready %d", conn,
++ conn->wr_state, conn->wr_space_ready);
++
++ /*
++ * Let's start sending waiting to be sent data ASAP, even if there's
++ * still not all the needed buffers ready and we need several wakup
++ * iteration to send them all, because starting ASAP, i.e. in parallel,
++ * is better for performance, especially on multi-CPU/core systems.
++ */
++
++ if (conn->wr_state == ISCSI_CONN_WR_STATE_IDLE) {
++ list_add_tail(&conn->wr_list_entry, &p->wr_list);
++ conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
++ wake_up(&p->wr_waitQ);
++ }
++
++ spin_unlock_bh(&p->wr_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++void __mark_conn_closed(struct iscsi_conn *conn, int flags)
++{
++ spin_lock_bh(&conn->conn_thr_pool->rd_lock);
++ conn->closing = 1;
++ if (flags & ISCSI_CONN_ACTIVE_CLOSE)
++ conn->active_close = 1;
++ if (flags & ISCSI_CONN_DELETING)
++ conn->deleting = 1;
++ spin_unlock_bh(&conn->conn_thr_pool->rd_lock);
++
++ iscsi_make_conn_rd_active(conn);
++}
++
++void mark_conn_closed(struct iscsi_conn *conn)
++{
++ __mark_conn_closed(conn, ISCSI_CONN_ACTIVE_CLOSE);
++}
++
++static void __iscsi_state_change(struct sock *sk)
++{
++ struct iscsi_conn *conn = sk->sk_user_data;
++
++ TRACE_ENTRY();
++
++ if (unlikely(sk->sk_state != TCP_ESTABLISHED)) {
++ if (!conn->closing) {
++ PRINT_ERROR("Connection with initiator %s "
++ "unexpectedly closed!",
++ conn->session->initiator_name);
++ TRACE_MGMT_DBG("conn %p, sk state %d", conn,
++ sk->sk_state);
++ __mark_conn_closed(conn, 0);
++ }
++ } else
++ iscsi_make_conn_rd_active(conn);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void iscsi_state_change(struct sock *sk)
++{
++ struct iscsi_conn *conn = sk->sk_user_data;
++
++ __iscsi_state_change(sk);
++ conn->old_state_change(sk);
++
++ return;
++}
++
++static void iscsi_data_ready(struct sock *sk, int len)
++{
++ struct iscsi_conn *conn = sk->sk_user_data;
++
++ TRACE_ENTRY();
++
++ iscsi_make_conn_rd_active(conn);
++
++ conn->old_data_ready(sk, len);
++
++ TRACE_EXIT();
++ return;
++}
++
++void __iscsi_write_space_ready(struct iscsi_conn *conn)
++{
++ struct iscsi_thread_pool *p = conn->conn_thr_pool;
++
++ TRACE_ENTRY();
++
++ spin_lock_bh(&p->wr_lock);
++ conn->wr_space_ready = 1;
++ if ((conn->wr_state == ISCSI_CONN_WR_STATE_SPACE_WAIT)) {
++ TRACE_DBG("wr space ready (conn %p)", conn);
++ list_add_tail(&conn->wr_list_entry, &p->wr_list);
++ conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
++ wake_up(&p->wr_waitQ);
++ }
++ spin_unlock_bh(&p->wr_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void iscsi_write_space_ready(struct sock *sk)
++{
++ struct iscsi_conn *conn = sk->sk_user_data;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Write space ready for conn %p", conn);
++
++ __iscsi_write_space_ready(conn);
++
++ conn->old_write_space(sk);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void conn_rsp_timer_fn(unsigned long arg)
++{
++ struct iscsi_conn *conn = (struct iscsi_conn *)arg;
++ struct iscsi_cmnd *cmnd;
++ unsigned long j = jiffies;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Timer (conn %p)", conn);
++
++ spin_lock_bh(&conn->write_list_lock);
++
++ if (!list_empty(&conn->write_timeout_list)) {
++ unsigned long timeout_time;
++ cmnd = list_entry(conn->write_timeout_list.next,
++ struct iscsi_cmnd, write_timeout_list_entry);
++
++ timeout_time = j + iscsi_get_timeout(cmnd) + ISCSI_ADD_SCHED_TIME;
++
++ if (unlikely(time_after_eq(j, iscsi_get_timeout_time(cmnd)))) {
++ if (!conn->closing) {
++ PRINT_ERROR("Timeout %ld sec sending data/waiting "
++ "for reply to/from initiator "
++ "%s (SID %llx), closing connection",
++ iscsi_get_timeout(cmnd)/HZ,
++ conn->session->initiator_name,
++ (long long unsigned int)
++ conn->session->sid);
++ /*
++ * We must call mark_conn_closed() outside of
++ * write_list_lock or we will have a circular
++ * locking dependency with rd_lock.
++ */
++ spin_unlock_bh(&conn->write_list_lock);
++ mark_conn_closed(conn);
++ goto out;
++ }
++ } else if (!timer_pending(&conn->rsp_timer) ||
++ time_after(conn->rsp_timer.expires, timeout_time)) {
++ TRACE_DBG("Restarting timer on %ld (conn %p)",
++ timeout_time, conn);
++ /*
++ * Timer might have been restarted while we were
++ * entering here.
++ *
++ * Since we have not empty write_timeout_list, we are
++ * safe to restart the timer, because we not race with
++ * del_timer_sync() in conn_free().
++ */
++ mod_timer(&conn->rsp_timer, timeout_time);
++ }
++ }
++
++ spin_unlock_bh(&conn->write_list_lock);
++
++ if (unlikely(conn->conn_tm_active)) {
++ TRACE_MGMT_DBG("TM active: making conn %p RD active", conn);
++ iscsi_make_conn_rd_active(conn);
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void conn_nop_in_delayed_work_fn(struct delayed_work *work)
++{
++ struct iscsi_conn *conn = container_of(work, struct iscsi_conn,
++ nop_in_delayed_work);
++
++ TRACE_ENTRY();
++
++ if (time_after_eq(jiffies, conn->last_rcv_time +
++ conn->nop_in_interval)) {
++ iscsi_send_nop_in(conn);
++ }
++
++ if ((conn->nop_in_interval > 0) &&
++ !test_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags)) {
++ TRACE_DBG("Reschedule Nop-In work for conn %p", conn);
++ schedule_delayed_work(&conn->nop_in_delayed_work,
++ conn->nop_in_interval + ISCSI_ADD_SCHED_TIME);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called from rd thread only */
++void iscsi_check_tm_data_wait_timeouts(struct iscsi_conn *conn, bool force)
++{
++ struct iscsi_cmnd *cmnd;
++ unsigned long j = jiffies;
++ bool aborted_cmds_pending;
++ unsigned long timeout_time = j + ISCSI_TM_DATA_WAIT_TIMEOUT +
++ ISCSI_ADD_SCHED_TIME;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG_FLAG(TRACE_MGMT_DEBUG, "conn %p, read_cmnd %p, read_state "
++ "%d, j %ld (TIMEOUT %d, force %d)", conn, conn->read_cmnd,
++ conn->read_state, j,
++ ISCSI_TM_DATA_WAIT_TIMEOUT + ISCSI_ADD_SCHED_TIME, force);
++
++ iscsi_extracheck_is_rd_thread(conn);
++
++again:
++ spin_lock_bh(&conn->conn_thr_pool->rd_lock);
++ spin_lock(&conn->write_list_lock);
++
++ aborted_cmds_pending = false;
++ list_for_each_entry(cmnd, &conn->write_timeout_list,
++ write_timeout_list_entry) {
++ /*
++ * This should not happen, because DATA OUT commands can't get
++ * into write_timeout_list.
++ */
++ BUG_ON(cmnd->cmd_req != NULL);
++
++ if (test_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags)) {
++ TRACE_MGMT_DBG("Checking aborted cmnd %p (scst_state "
++ "%d, on_write_timeout_list %d, write_start "
++ "%ld, r2t_len_to_receive %d)", cmnd,
++ cmnd->scst_state, cmnd->on_write_timeout_list,
++ cmnd->write_start, cmnd->r2t_len_to_receive);
++ if ((cmnd == conn->read_cmnd) ||
++ cmnd->data_out_in_data_receiving) {
++ BUG_ON((cmnd == conn->read_cmnd) && force);
++ /*
++ * We can't abort command waiting for data from
++ * the net, because otherwise we are risking to
++ * get out of sync with the sender, so we have
++ * to wait until the timeout timer gets into the
++ * action and close this connection.
++ */
++ TRACE_MGMT_DBG("Aborted cmnd %p is %s, "
++ "keep waiting", cmnd,
++ (cmnd == conn->read_cmnd) ? "RX cmnd" :
++ "waiting for DATA OUT data");
++ goto cont;
++ }
++ if ((cmnd->r2t_len_to_receive != 0) &&
++ (time_after_eq(j, cmnd->write_start + ISCSI_TM_DATA_WAIT_TIMEOUT) ||
++ force)) {
++ spin_unlock(&conn->write_list_lock);
++ spin_unlock_bh(&conn->conn_thr_pool->rd_lock);
++ iscsi_fail_data_waiting_cmnd(cmnd);
++ goto again;
++ }
++cont:
++ aborted_cmds_pending = true;
++ }
++ }
++
++ if (aborted_cmds_pending) {
++ if (!force &&
++ (!timer_pending(&conn->rsp_timer) ||
++ time_after(conn->rsp_timer.expires, timeout_time))) {
++ TRACE_MGMT_DBG("Mod timer on %ld (conn %p)",
++ timeout_time, conn);
++ mod_timer(&conn->rsp_timer, timeout_time);
++ }
++ } else {
++ TRACE_MGMT_DBG("Clearing conn_tm_active for conn %p", conn);
++ conn->conn_tm_active = 0;
++ }
++
++ spin_unlock(&conn->write_list_lock);
++ spin_unlock_bh(&conn->conn_thr_pool->rd_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* target_mutex supposed to be locked */
++void conn_reinst_finished(struct iscsi_conn *conn)
++{
++ struct iscsi_cmnd *cmnd, *t;
++
++ TRACE_ENTRY();
++
++ clear_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags);
++
++ list_for_each_entry_safe(cmnd, t, &conn->reinst_pending_cmd_list,
++ reinst_pending_cmd_list_entry) {
++ TRACE_MGMT_DBG("Restarting reinst pending cmnd %p",
++ cmnd);
++
++ list_del(&cmnd->reinst_pending_cmd_list_entry);
++
++ /* Restore the state for preliminary completion/cmnd_done() */
++ cmnd->scst_state = ISCSI_CMD_STATE_AFTER_PREPROC;
++
++ iscsi_restart_cmnd(cmnd);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static void conn_activate(struct iscsi_conn *conn)
++{
++ TRACE_MGMT_DBG("Enabling conn %p", conn);
++
++ /* Catch double bind */
++ BUG_ON(conn->sock->sk->sk_state_change == iscsi_state_change);
++
++ write_lock_bh(&conn->sock->sk->sk_callback_lock);
++
++ conn->old_state_change = conn->sock->sk->sk_state_change;
++ conn->sock->sk->sk_state_change = iscsi_state_change;
++
++ conn->old_data_ready = conn->sock->sk->sk_data_ready;
++ conn->sock->sk->sk_data_ready = iscsi_data_ready;
++
++ conn->old_write_space = conn->sock->sk->sk_write_space;
++ conn->sock->sk->sk_write_space = iscsi_write_space_ready;
++
++ write_unlock_bh(&conn->sock->sk->sk_callback_lock);
++
++ /*
++ * Check, if conn was closed while we were initializing it.
++ * This function will make conn rd_active, if necessary.
++ */
++ __iscsi_state_change(conn->sock->sk);
++
++ return;
++}
++
++/*
++ * Note: the code below passes a kernel space pointer (&opt) to setsockopt()
++ * while the declaration of setsockopt specifies that it expects a user space
++ * pointer. This seems to work fine, and this approach is also used in some
++ * other parts of the Linux kernel (see e.g. fs/ocfs2/cluster/tcp.c).
++ */
++static int conn_setup_sock(struct iscsi_conn *conn)
++{
++ int res = 0;
++ int opt = 1;
++ mm_segment_t oldfs;
++ struct iscsi_session *session = conn->session;
++
++ TRACE_DBG("%llx", (long long unsigned int)session->sid);
++
++ conn->sock = SOCKET_I(conn->file->f_dentry->d_inode);
++
++ if (conn->sock->ops->sendpage == NULL) {
++ PRINT_ERROR("Socket for sid %llx doesn't support sendpage()",
++ (long long unsigned int)session->sid);
++ res = -EINVAL;
++ goto out;
++ }
++
++#if 0
++ conn->sock->sk->sk_allocation = GFP_NOIO;
++#endif
++ conn->sock->sk->sk_user_data = conn;
++
++ oldfs = get_fs();
++ set_fs(get_ds());
++ conn->sock->ops->setsockopt(conn->sock, SOL_TCP, TCP_NODELAY,
++ (void __force __user *)&opt, sizeof(opt));
++ set_fs(oldfs);
++
++out:
++ return res;
++}
++
++/* target_mutex supposed to be locked */
++int conn_free(struct iscsi_conn *conn)
++{
++ struct iscsi_session *session = conn->session;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Freeing conn %p (sess=%p, %#Lx %u)", conn,
++ session, (long long unsigned int)session->sid, conn->cid);
++
++ del_timer_sync(&conn->rsp_timer);
++
++ conn_sysfs_del(conn);
++
++ BUG_ON(atomic_read(&conn->conn_ref_cnt) != 0);
++ BUG_ON(!list_empty(&conn->cmd_list));
++ BUG_ON(!list_empty(&conn->write_list));
++ BUG_ON(!list_empty(&conn->write_timeout_list));
++ BUG_ON(conn->conn_reinst_successor != NULL);
++ BUG_ON(!test_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags));
++
++ /* Just in case if new conn gets freed before the old one */
++ if (test_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags)) {
++ struct iscsi_conn *c;
++ TRACE_MGMT_DBG("Freeing being reinstated conn %p", conn);
++ list_for_each_entry(c, &session->conn_list,
++ conn_list_entry) {
++ if (c->conn_reinst_successor == conn) {
++ c->conn_reinst_successor = NULL;
++ break;
++ }
++ }
++ }
++
++ list_del(&conn->conn_list_entry);
++
++ fput(conn->file);
++ conn->file = NULL;
++ conn->sock = NULL;
++
++ free_page((unsigned long)conn->read_iov);
++
++ kfree(conn);
++
++ if (list_empty(&session->conn_list)) {
++ BUG_ON(session->sess_reinst_successor != NULL);
++ session_free(session, true);
++ }
++
++ return 0;
++}
++
++/* target_mutex supposed to be locked */
++static int iscsi_conn_alloc(struct iscsi_session *session,
++ struct iscsi_kern_conn_info *info, struct iscsi_conn **new_conn)
++{
++ struct iscsi_conn *conn;
++ int res = 0;
++
++ conn = kzalloc(sizeof(*conn), GFP_KERNEL);
++ if (!conn) {
++ res = -ENOMEM;
++ goto out_err;
++ }
++
++ TRACE_MGMT_DBG("Creating connection %p for sid %#Lx, cid %u", conn,
++ (long long unsigned int)session->sid, info->cid);
++
++ /* Changing it, change ISCSI_CONN_IOV_MAX as well !! */
++ conn->read_iov = (struct iovec *)get_zeroed_page(GFP_KERNEL);
++ if (conn->read_iov == NULL) {
++ res = -ENOMEM;
++ goto out_err_free_conn;
++ }
++
++ atomic_set(&conn->conn_ref_cnt, 0);
++ conn->session = session;
++ if (session->sess_reinstating)
++ __set_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags);
++ conn->cid = info->cid;
++ conn->stat_sn = info->stat_sn;
++ conn->exp_stat_sn = info->exp_stat_sn;
++ conn->rd_state = ISCSI_CONN_RD_STATE_IDLE;
++ conn->wr_state = ISCSI_CONN_WR_STATE_IDLE;
++
++ conn->hdigest_type = session->sess_params.header_digest;
++ conn->ddigest_type = session->sess_params.data_digest;
++ res = digest_init(conn);
++ if (res != 0)
++ goto out_free_iov;
++
++ conn->target = session->target;
++ spin_lock_init(&conn->cmd_list_lock);
++ INIT_LIST_HEAD(&conn->cmd_list);
++ spin_lock_init(&conn->write_list_lock);
++ INIT_LIST_HEAD(&conn->write_list);
++ INIT_LIST_HEAD(&conn->write_timeout_list);
++ setup_timer(&conn->rsp_timer, conn_rsp_timer_fn, (unsigned long)conn);
++ init_waitqueue_head(&conn->read_state_waitQ);
++ init_completion(&conn->ready_to_free);
++ INIT_LIST_HEAD(&conn->reinst_pending_cmd_list);
++ INIT_LIST_HEAD(&conn->nop_req_list);
++ spin_lock_init(&conn->nop_req_list_lock);
++
++ conn->conn_thr_pool = session->sess_thr_pool;
++
++ conn->nop_in_ttt = 0;
++ INIT_DELAYED_WORK(&conn->nop_in_delayed_work,
++ (void (*)(struct work_struct *))conn_nop_in_delayed_work_fn);
++ conn->last_rcv_time = jiffies;
++ conn->data_rsp_timeout = session->tgt_params.rsp_timeout * HZ;
++ conn->nop_in_interval = session->tgt_params.nop_in_interval * HZ;
++ conn->nop_in_timeout = session->tgt_params.nop_in_timeout * HZ;
++ if (conn->nop_in_interval > 0) {
++ TRACE_DBG("Schedule Nop-In work for conn %p", conn);
++ schedule_delayed_work(&conn->nop_in_delayed_work,
++ conn->nop_in_interval + ISCSI_ADD_SCHED_TIME);
++ }
++
++ conn->file = fget(info->fd);
++
++ res = conn_setup_sock(conn);
++ if (res != 0)
++ goto out_fput;
++
++ res = conn_sysfs_add(conn);
++ if (res != 0)
++ goto out_fput;
++
++ list_add_tail(&conn->conn_list_entry, &session->conn_list);
++
++ *new_conn = conn;
++
++out:
++ return res;
++
++out_fput:
++ fput(conn->file);
++
++out_free_iov:
++ free_page((unsigned long)conn->read_iov);
++
++out_err_free_conn:
++ kfree(conn);
++
++out_err:
++ goto out;
++}
++
++/* target_mutex supposed to be locked */
++int __add_conn(struct iscsi_session *session, struct iscsi_kern_conn_info *info)
++{
++ struct iscsi_conn *conn, *new_conn = NULL;
++ int err;
++ bool reinstatement = false;
++
++ conn = conn_lookup(session, info->cid);
++ if ((conn != NULL) &&
++ !test_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags)) {
++ /* conn reinstatement */
++ reinstatement = true;
++ } else if (!list_empty(&session->conn_list)) {
++ err = -EEXIST;
++ goto out;
++ }
++
++ err = iscsi_conn_alloc(session, info, &new_conn);
++ if (err != 0)
++ goto out;
++
++ if (reinstatement) {
++ TRACE_MGMT_DBG("Reinstating conn (old %p, new %p)", conn,
++ new_conn);
++ conn->conn_reinst_successor = new_conn;
++ __set_bit(ISCSI_CONN_REINSTATING, &new_conn->conn_aflags);
++ __mark_conn_closed(conn, 0);
++ }
++
++ conn_activate(new_conn);
++
++out:
++ return err;
++}
++
++/* target_mutex supposed to be locked */
++int __del_conn(struct iscsi_session *session, struct iscsi_kern_conn_info *info)
++{
++ struct iscsi_conn *conn;
++ int err = -EEXIST;
++
++ conn = conn_lookup(session, info->cid);
++ if (!conn) {
++ PRINT_WARNING("Connection %d not found", info->cid);
++ return err;
++ }
++
++ PRINT_INFO("Deleting connection with initiator %s (%p)",
++ conn->session->initiator_name, conn);
++
++ __mark_conn_closed(conn, ISCSI_CONN_ACTIVE_CLOSE|ISCSI_CONN_DELETING);
++
++ return 0;
++}
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++
++void iscsi_extracheck_is_rd_thread(struct iscsi_conn *conn)
++{
++ if (unlikely(current != conn->rd_task)) {
++ printk(KERN_EMERG "conn %p rd_task != current %p (pid %d)\n",
++ conn, current, current->pid);
++ while (in_softirq())
++ local_bh_enable();
++ printk(KERN_EMERG "rd_state %x\n", conn->rd_state);
++ printk(KERN_EMERG "rd_task %p\n", conn->rd_task);
++ printk(KERN_EMERG "rd_task->pid %d\n", conn->rd_task->pid);
++ BUG();
++ }
++}
++
++void iscsi_extracheck_is_wr_thread(struct iscsi_conn *conn)
++{
++ if (unlikely(current != conn->wr_task)) {
++ printk(KERN_EMERG "conn %p wr_task != current %p (pid %d)\n",
++ conn, current, current->pid);
++ while (in_softirq())
++ local_bh_enable();
++ printk(KERN_EMERG "wr_state %x\n", conn->wr_state);
++ printk(KERN_EMERG "wr_task %p\n", conn->wr_task);
++ printk(KERN_EMERG "wr_task->pid %d\n", conn->wr_task->pid);
++ BUG();
++ }
++}
++
++#endif /* CONFIG_SCST_EXTRACHECKS */
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/digest.c linux-3.2/drivers/scst/iscsi-scst/digest.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/digest.c
++++ linux-3.2/drivers/scst/iscsi-scst/digest.c
+@@ -0,0 +1,245 @@
++/*
++ * iSCSI digest handling.
++ *
++ * Copyright (C) 2004 - 2006 Xiranet Communications GmbH
++ * <arne.redlich@xiranet.com>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation.
++ *
++ * 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/types.h>
++#include <linux/scatterlist.h>
++
++#include "iscsi.h"
++#include "digest.h"
++#include <linux/crc32c.h>
++
++void digest_alg_available(int *val)
++{
++#if defined(CONFIG_LIBCRC32C_MODULE) || defined(CONFIG_LIBCRC32C)
++ int crc32c = 1;
++#else
++ int crc32c = 0;
++#endif
++
++ if ((*val & DIGEST_CRC32C) && !crc32c) {
++ PRINT_ERROR("%s", "CRC32C digest algorithm not available "
++ "in kernel");
++ *val |= ~DIGEST_CRC32C;
++ }
++}
++
++/**
++ * initialize support for digest calculation.
++ *
++ * digest_init -
++ * @conn: ptr to connection to make use of digests
++ *
++ * @return: 0 on success, < 0 on error
++ */
++int digest_init(struct iscsi_conn *conn)
++{
++ if (!(conn->hdigest_type & DIGEST_ALL))
++ conn->hdigest_type = DIGEST_NONE;
++
++ if (!(conn->ddigest_type & DIGEST_ALL))
++ conn->ddigest_type = DIGEST_NONE;
++
++ return 0;
++}
++
++static __be32 evaluate_crc32_from_sg(struct scatterlist *sg, int nbytes,
++ uint32_t padding)
++{
++ u32 crc = ~0;
++ int pad_bytes = ((nbytes + 3) & -4) - nbytes;
++
++#ifdef CONFIG_SCST_ISCSI_DEBUG_DIGEST_FAILURES
++ if (((scst_random() % 100000) == 752)) {
++ PRINT_INFO("%s", "Simulating digest failure");
++ return 0;
++ }
++#endif
++
++#if defined(CONFIG_LIBCRC32C_MODULE) || defined(CONFIG_LIBCRC32C)
++ while (nbytes > 0) {
++ int d = min(nbytes, (int)(sg->length));
++ crc = crc32c(crc, sg_virt(sg), d);
++ nbytes -= d;
++ sg++;
++ }
++
++ if (pad_bytes)
++ crc = crc32c(crc, (u8 *)&padding, pad_bytes);
++#endif
++
++ return (__force __be32)~cpu_to_le32(crc);
++}
++
++static __be32 digest_header(struct iscsi_pdu *pdu)
++{
++ struct scatterlist sg[2];
++ unsigned int nbytes = sizeof(struct iscsi_hdr);
++ int asize = (pdu->ahssize + 3) & -4;
++
++ sg_init_table(sg, 2);
++
++ sg_set_buf(&sg[0], &pdu->bhs, nbytes);
++ if (pdu->ahssize) {
++ sg_set_buf(&sg[1], pdu->ahs, asize);
++ nbytes += asize;
++ }
++ EXTRACHECKS_BUG_ON((nbytes & 3) != 0);
++ return evaluate_crc32_from_sg(sg, nbytes, 0);
++}
++
++static __be32 digest_data(struct iscsi_cmnd *cmd, u32 size, u32 offset,
++ uint32_t padding)
++{
++ struct scatterlist *sg = cmd->sg;
++ int idx, count;
++ struct scatterlist saved_sg;
++ __be32 crc;
++
++ offset += sg[0].offset;
++ idx = offset >> PAGE_SHIFT;
++ offset &= ~PAGE_MASK;
++
++ count = get_pgcnt(size, offset);
++
++ TRACE_DBG("req %p, idx %d, count %d, sg_cnt %d, size %d, "
++ "offset %d", cmd, idx, count, cmd->sg_cnt, size, offset);
++ BUG_ON(idx + count > cmd->sg_cnt);
++
++ saved_sg = sg[idx];
++ sg[idx].offset = offset;
++ sg[idx].length -= offset - saved_sg.offset;
++
++ crc = evaluate_crc32_from_sg(sg + idx, size, padding);
++
++ sg[idx] = saved_sg;
++ return crc;
++}
++
++int digest_rx_header(struct iscsi_cmnd *cmnd)
++{
++ __be32 crc;
++
++ crc = digest_header(&cmnd->pdu);
++ if (unlikely(crc != cmnd->hdigest)) {
++ PRINT_ERROR("%s", "RX header digest failed");
++ return -EIO;
++ } else
++ TRACE_DBG("RX header digest OK for cmd %p", cmnd);
++
++ return 0;
++}
++
++void digest_tx_header(struct iscsi_cmnd *cmnd)
++{
++ cmnd->hdigest = digest_header(&cmnd->pdu);
++ TRACE_DBG("TX header digest for cmd %p: %x", cmnd, cmnd->hdigest);
++}
++
++int digest_rx_data(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_cmnd *req;
++ struct iscsi_data_out_hdr *req_hdr;
++ u32 offset;
++ __be32 crc;
++ int res = 0;
++
++ switch (cmnd_opcode(cmnd)) {
++ case ISCSI_OP_SCSI_DATA_OUT:
++ req = cmnd->cmd_req;
++ if (unlikely(req == NULL)) {
++ /* It can be for prelim completed commands */
++ req = cmnd;
++ goto out;
++ }
++ req_hdr = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
++ offset = be32_to_cpu(req_hdr->buffer_offset);
++ break;
++
++ default:
++ req = cmnd;
++ offset = 0;
++ }
++
++ /*
++ * We need to skip the digest check for prelim completed commands,
++ * because we use shared data buffer for them, so, most likely, the
++ * check will fail. Plus, for such commands we sometimes don't have
++ * sg_cnt set correctly (cmnd_prepare_get_rejected_cmd_data() doesn't
++ * do it).
++ */
++ if (unlikely(req->prelim_compl_flags != 0))
++ goto out;
++
++ /*
++ * Temporary to not crash with write residual overflows. ToDo. Until
++ * that let's always have succeeded data digests for such overflows.
++ * In ideal, we should allocate additional one or more sg's for the
++ * overflowed data and free them here or on req release. It's quite
++ * not trivial for such virtually never used case, so let's do it,
++ * when it gets needed.
++ */
++ if (unlikely(offset + cmnd->pdu.datasize > req->bufflen)) {
++ PRINT_WARNING("Skipping RX data digest check for residual "
++ "overflow command op %x (data size %d, buffer size %d)",
++ cmnd_hdr(req)->scb[0], offset + cmnd->pdu.datasize,
++ req->bufflen);
++ goto out;
++ }
++
++ crc = digest_data(req, cmnd->pdu.datasize, offset,
++ cmnd->conn->rpadding);
++
++ if (unlikely(crc != cmnd->ddigest)) {
++ TRACE(TRACE_MINOR|TRACE_MGMT_DEBUG, "%s", "RX data digest "
++ "failed");
++ TRACE_MGMT_DBG("Calculated crc %x, ddigest %x, offset %d", crc,
++ cmnd->ddigest, offset);
++ iscsi_dump_pdu(&cmnd->pdu);
++ res = -EIO;
++ } else
++ TRACE_DBG("RX data digest OK for cmd %p", cmnd);
++
++out:
++ return res;
++}
++
++void digest_tx_data(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_data_in_hdr *hdr;
++ u32 offset;
++
++ TRACE_DBG("%s:%d req %p, own_sg %d, sg %p, sgcnt %d cmnd %p, "
++ "own_sg %d, sg %p, sgcnt %d", __func__, __LINE__,
++ cmnd->parent_req, cmnd->parent_req->own_sg,
++ cmnd->parent_req->sg, cmnd->parent_req->sg_cnt,
++ cmnd, cmnd->own_sg, cmnd->sg, cmnd->sg_cnt);
++
++ switch (cmnd_opcode(cmnd)) {
++ case ISCSI_OP_SCSI_DATA_IN:
++ hdr = (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs;
++ offset = be32_to_cpu(hdr->buffer_offset);
++ break;
++ default:
++ offset = 0;
++ }
++
++ cmnd->ddigest = digest_data(cmnd, cmnd->pdu.datasize, offset, 0);
++ TRACE_DBG("TX data digest for cmd %p: %x (offset %d, opcode %x)", cmnd,
++ cmnd->ddigest, offset, cmnd_opcode(cmnd));
++}
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/digest.h linux-3.2/drivers/scst/iscsi-scst/digest.h
+--- orig/linux-3.2/drivers/scst/iscsi-scst/digest.h
++++ linux-3.2/drivers/scst/iscsi-scst/digest.h
+@@ -0,0 +1,32 @@
++/*
++ * iSCSI digest handling.
++ *
++ * Copyright (C) 2004 Xiranet Communications GmbH <arne.redlich@xiranet.com>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation.
++ *
++ * 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 __ISCSI_DIGEST_H__
++#define __ISCSI_DIGEST_H__
++
++extern void digest_alg_available(int *val);
++
++extern int digest_init(struct iscsi_conn *conn);
++
++extern int digest_rx_header(struct iscsi_cmnd *cmnd);
++extern int digest_rx_data(struct iscsi_cmnd *cmnd);
++
++extern void digest_tx_header(struct iscsi_cmnd *cmnd);
++extern void digest_tx_data(struct iscsi_cmnd *cmnd);
++
++#endif /* __ISCSI_DIGEST_H__ */
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/event.c linux-3.2/drivers/scst/iscsi-scst/event.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/event.c
++++ linux-3.2/drivers/scst/iscsi-scst/event.c
+@@ -0,0 +1,163 @@
++/*
++ * Event notification code.
++ *
++ * Copyright (C) 2005 FUJITA Tomonori <tomof@acm.org>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ */
++
++#include <linux/module.h>
++#include <net/tcp.h>
++#include <scst/iscsi_scst.h>
++#include "iscsi.h"
++
++static struct sock *nl;
++static u32 iscsid_pid;
++
++static int event_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
++{
++ u32 pid;
++
++ pid = NETLINK_CB(skb).pid;
++ WARN_ON(pid == 0);
++
++ iscsid_pid = pid;
++
++ return 0;
++}
++
++static void event_recv_skb(struct sk_buff *skb)
++{
++ int err;
++ struct nlmsghdr *nlh;
++ u32 rlen;
++
++ while (skb->len >= NLMSG_SPACE(0)) {
++ nlh = (struct nlmsghdr *)skb->data;
++ if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
++ goto out;
++ rlen = NLMSG_ALIGN(nlh->nlmsg_len);
++ if (rlen > skb->len)
++ rlen = skb->len;
++ err = event_recv_msg(skb, nlh);
++ if (err)
++ netlink_ack(skb, nlh, -err);
++ else if (nlh->nlmsg_flags & NLM_F_ACK)
++ netlink_ack(skb, nlh, 0);
++ skb_pull(skb, rlen);
++ }
++
++out:
++ return;
++}
++
++/* event_mutex supposed to be held */
++static int __event_send(const void *buf, int buf_len)
++{
++ int res = 0, len;
++ struct sk_buff *skb;
++ struct nlmsghdr *nlh;
++ static u32 seq; /* protected by event_mutex */
++
++ TRACE_ENTRY();
++
++ if (ctr_open_state != ISCSI_CTR_OPEN_STATE_OPEN)
++ goto out;
++
++ len = NLMSG_SPACE(buf_len);
++
++ skb = alloc_skb(len, GFP_KERNEL);
++ if (skb == NULL) {
++ PRINT_ERROR("alloc_skb() failed (len %d)", len);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ nlh = __nlmsg_put(skb, iscsid_pid, seq++, NLMSG_DONE, buf_len, 0);
++
++ memcpy(NLMSG_DATA(nlh), buf, buf_len);
++ res = netlink_unicast(nl, skb, iscsid_pid, 0);
++ if (res <= 0) {
++ if (res != -ECONNREFUSED)
++ PRINT_ERROR("netlink_unicast() failed: %d", res);
++ else
++ TRACE(TRACE_MINOR, "netlink_unicast() failed: %s. "
++ "Not functioning user space?",
++ "Connection refused");
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++int event_send(u32 tid, u64 sid, u32 cid, u32 cookie,
++ enum iscsi_kern_event_code code,
++ const char *param1, const char *param2)
++{
++ int err;
++ static DEFINE_MUTEX(event_mutex);
++ struct iscsi_kern_event event;
++ int param1_size, param2_size;
++
++ param1_size = (param1 != NULL) ? strlen(param1) : 0;
++ param2_size = (param2 != NULL) ? strlen(param2) : 0;
++
++ event.tid = tid;
++ event.sid = sid;
++ event.cid = cid;
++ event.code = code;
++ event.cookie = cookie;
++ event.param1_size = param1_size;
++ event.param2_size = param2_size;
++
++ mutex_lock(&event_mutex);
++
++ err = __event_send(&event, sizeof(event));
++ if (err <= 0)
++ goto out_unlock;
++
++ if (param1_size > 0) {
++ err = __event_send(param1, param1_size);
++ if (err <= 0)
++ goto out_unlock;
++ }
++
++ if (param2_size > 0) {
++ err = __event_send(param2, param2_size);
++ if (err <= 0)
++ goto out_unlock;
++ }
++
++out_unlock:
++ mutex_unlock(&event_mutex);
++ return err;
++}
++
++int __init event_init(void)
++{
++ nl = netlink_kernel_create(&init_net, NETLINK_ISCSI_SCST, 1,
++ event_recv_skb, NULL, THIS_MODULE);
++ if (!nl) {
++ PRINT_ERROR("%s", "netlink_kernel_create() failed");
++ return -ENOMEM;
++ } else
++ return 0;
++}
++
++void event_exit(void)
++{
++ netlink_kernel_release(nl);
++}
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/iscsi.c linux-3.2/drivers/scst/iscsi-scst/iscsi.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/iscsi.c
++++ linux-3.2/drivers/scst/iscsi-scst/iscsi.c
+@@ -0,0 +1,4137 @@
++/*
++ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@ardistech.com>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++#include <linux/hash.h>
++#include <linux/kthread.h>
++#include <linux/scatterlist.h>
++#include <linux/ctype.h>
++#include <net/tcp.h>
++#include <scsi/scsi.h>
++#include <asm/byteorder.h>
++#include <asm/unaligned.h>
++
++#include "iscsi.h"
++#include "digest.h"
++
++#ifndef GENERATING_UPSTREAM_PATCH
++#if !defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++#warning Patch put_page_callback-<kernel-version>.patch not applied on your \
++kernel or CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION \
++config option not set. ISCSI-SCST will be working with not the best \
++performance. Refer README file for details.
++#endif
++#endif
++
++#define ISCSI_INIT_WRITE_WAKE 0x1
++
++static int ctr_major;
++static const char ctr_name[] = "iscsi-scst-ctl";
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++unsigned long iscsi_trace_flag = ISCSI_DEFAULT_LOG_FLAGS;
++#endif
++
++static struct kmem_cache *iscsi_cmnd_cache;
++
++static DEFINE_MUTEX(iscsi_threads_pool_mutex);
++static LIST_HEAD(iscsi_thread_pools_list);
++
++static struct iscsi_thread_pool *iscsi_main_thread_pool;
++
++static struct page *dummy_page;
++static struct scatterlist dummy_sg;
++
++static void cmnd_remove_data_wait_hash(struct iscsi_cmnd *cmnd);
++static void iscsi_send_task_mgmt_resp(struct iscsi_cmnd *req, int status);
++static void iscsi_check_send_delayed_tm_resp(struct iscsi_session *sess);
++static void req_cmnd_release(struct iscsi_cmnd *req);
++static int cmnd_insert_data_wait_hash(struct iscsi_cmnd *cmnd);
++static void iscsi_cmnd_init_write(struct iscsi_cmnd *rsp, int flags);
++static void iscsi_set_resid_no_scst_cmd(struct iscsi_cmnd *rsp);
++static void iscsi_set_resid(struct iscsi_cmnd *rsp);
++
++static void iscsi_set_not_received_data_len(struct iscsi_cmnd *req,
++ unsigned int not_received)
++{
++ req->not_received_data_len = not_received;
++ if (req->scst_cmd != NULL)
++ scst_cmd_set_write_not_received_data_len(req->scst_cmd,
++ not_received);
++ return;
++}
++
++static void req_del_from_write_timeout_list(struct iscsi_cmnd *req)
++{
++ struct iscsi_conn *conn;
++
++ TRACE_ENTRY();
++
++ if (!req->on_write_timeout_list)
++ goto out;
++
++ conn = req->conn;
++
++ TRACE_DBG("Deleting cmd %p from conn %p write_timeout_list",
++ req, conn);
++
++ spin_lock_bh(&conn->write_list_lock);
++
++ /* Recheck, since it can be changed behind us */
++ if (unlikely(!req->on_write_timeout_list))
++ goto out_unlock;
++
++ list_del(&req->write_timeout_list_entry);
++ req->on_write_timeout_list = 0;
++
++out_unlock:
++ spin_unlock_bh(&conn->write_list_lock);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static inline u32 cmnd_write_size(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);
++
++ if (hdr->flags & ISCSI_CMD_WRITE)
++ return be32_to_cpu(hdr->data_length);
++ return 0;
++}
++
++static inline int cmnd_read_size(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);
++
++ if (hdr->flags & ISCSI_CMD_READ) {
++ struct iscsi_ahs_hdr *ahdr;
++
++ if (!(hdr->flags & ISCSI_CMD_WRITE))
++ return be32_to_cpu(hdr->data_length);
++
++ ahdr = (struct iscsi_ahs_hdr *)cmnd->pdu.ahs;
++ if (ahdr != NULL) {
++ uint8_t *p = (uint8_t *)ahdr;
++ unsigned int size = 0;
++ do {
++ int s;
++
++ ahdr = (struct iscsi_ahs_hdr *)p;
++
++ if (ahdr->ahstype == ISCSI_AHSTYPE_RLENGTH) {
++ struct iscsi_rlength_ahdr *rh =
++ (struct iscsi_rlength_ahdr *)ahdr;
++ return be32_to_cpu(rh->read_length);
++ }
++
++ s = 3 + be16_to_cpu(ahdr->ahslength);
++ s = (s + 3) & -4;
++ size += s;
++ p += s;
++ } while (size < cmnd->pdu.ahssize);
++ }
++ return -1;
++ }
++ return 0;
++}
++
++void iscsi_restart_cmnd(struct iscsi_cmnd *cmnd)
++{
++ int status;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(cmnd->r2t_len_to_receive != 0);
++ EXTRACHECKS_BUG_ON(cmnd->r2t_len_to_send != 0);
++
++ req_del_from_write_timeout_list(cmnd);
++
++ /*
++ * Let's remove cmnd from the hash earlier to keep it smaller.
++ * Also we have to remove hashed req from the hash before sending
++ * response. Otherwise we can have a race, when for some reason cmd's
++ * release (and, hence, removal from the hash) is delayed after the
++ * transmission and initiator sends cmd with the same ITT, hence
++ * the new command will be erroneously rejected as a duplicate.
++ */
++ if (cmnd->hashed)
++ cmnd_remove_data_wait_hash(cmnd);
++
++ if (unlikely(test_bit(ISCSI_CONN_REINSTATING,
++ &cmnd->conn->conn_aflags))) {
++ struct iscsi_target *target = cmnd->conn->session->target;
++ bool get_out;
++
++ mutex_lock(&target->target_mutex);
++
++ get_out = test_bit(ISCSI_CONN_REINSTATING,
++ &cmnd->conn->conn_aflags);
++ /* Let's don't look dead */
++ if (scst_cmd_get_cdb(cmnd->scst_cmd)[0] == TEST_UNIT_READY)
++ get_out = false;
++
++ if (!get_out)
++ goto unlock_cont;
++
++ TRACE_MGMT_DBG("Pending cmnd %p, because conn %p is "
++ "reinstated", cmnd, cmnd->conn);
++
++ cmnd->scst_state = ISCSI_CMD_STATE_REINST_PENDING;
++ list_add_tail(&cmnd->reinst_pending_cmd_list_entry,
++ &cmnd->conn->reinst_pending_cmd_list);
++
++unlock_cont:
++ mutex_unlock(&target->target_mutex);
++
++ if (get_out)
++ goto out;
++ }
++
++ if (unlikely(cmnd->prelim_compl_flags != 0)) {
++ if (test_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags)) {
++ TRACE_MGMT_DBG("cmnd %p (scst_cmd %p) aborted", cmnd,
++ cmnd->scst_cmd);
++ req_cmnd_release_force(cmnd);
++ goto out;
++ }
++
++ if (cmnd->scst_cmd == NULL) {
++ TRACE_MGMT_DBG("Finishing preliminary completed cmd %p "
++ "with NULL scst_cmd", cmnd);
++ req_cmnd_release(cmnd);
++ goto out;
++ }
++
++ status = SCST_PREPROCESS_STATUS_ERROR_SENSE_SET;
++ } else
++ status = SCST_PREPROCESS_STATUS_SUCCESS;
++
++ cmnd->scst_state = ISCSI_CMD_STATE_RESTARTED;
++
++ scst_restart_cmd(cmnd->scst_cmd, status, SCST_CONTEXT_THREAD);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static struct iscsi_cmnd *iscsi_create_tm_clone(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_cmnd *tm_clone;
++
++ TRACE_ENTRY();
++
++ tm_clone = cmnd_alloc(cmnd->conn, NULL);
++ if (tm_clone != NULL) {
++ set_bit(ISCSI_CMD_ABORTED, &tm_clone->prelim_compl_flags);
++ tm_clone->pdu = cmnd->pdu;
++
++ TRACE_MGMT_DBG("TM clone %p for cmnd %p created",
++ tm_clone, cmnd);
++ } else
++ PRINT_ERROR("Failed to create TM clone for cmnd %p", cmnd);
++
++ TRACE_EXIT_HRES((unsigned long)tm_clone);
++ return tm_clone;
++}
++
++void iscsi_fail_data_waiting_cmnd(struct iscsi_cmnd *cmnd)
++{
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Failing data waiting cmnd %p (data_out_in_data_receiving %d)",
++ cmnd, cmnd->data_out_in_data_receiving);
++
++ /*
++ * There is no race with conn_abort(), since all functions
++ * called from single read thread
++ */
++ iscsi_extracheck_is_rd_thread(cmnd->conn);
++
++ /* This cmnd is going to die without response */
++ cmnd->r2t_len_to_receive = 0;
++ cmnd->r2t_len_to_send = 0;
++
++ if (cmnd->pending) {
++ struct iscsi_session *session = cmnd->conn->session;
++ struct iscsi_cmnd *tm_clone;
++
++ TRACE_MGMT_DBG("Unpending cmnd %p (sn %u, exp_cmd_sn %u)", cmnd,
++ cmnd->pdu.bhs.sn, session->exp_cmd_sn);
++
++ /*
++ * If cmnd is pending, then the next command, if any, must be
++ * pending too. So, just insert a clone instead of cmnd to
++ * fill the hole in SNs. Then we can release cmnd.
++ */
++
++ tm_clone = iscsi_create_tm_clone(cmnd);
++
++ spin_lock(&session->sn_lock);
++
++ if (tm_clone != NULL) {
++ TRACE_MGMT_DBG("Adding tm_clone %p after its cmnd",
++ tm_clone);
++ list_add(&tm_clone->pending_list_entry,
++ &cmnd->pending_list_entry);
++ }
++
++ list_del(&cmnd->pending_list_entry);
++ cmnd->pending = 0;
++
++ spin_unlock(&session->sn_lock);
++ }
++
++ req_cmnd_release_force(cmnd);
++
++ TRACE_EXIT();
++ return;
++}
++
++struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *conn,
++ struct iscsi_cmnd *parent)
++{
++ struct iscsi_cmnd *cmnd;
++
++ /* ToDo: __GFP_NOFAIL?? */
++ cmnd = kmem_cache_zalloc(iscsi_cmnd_cache, GFP_KERNEL|__GFP_NOFAIL);
++
++ atomic_set(&cmnd->ref_cnt, 1);
++ cmnd->scst_state = ISCSI_CMD_STATE_NEW;
++ cmnd->conn = conn;
++ cmnd->parent_req = parent;
++
++ if (parent == NULL) {
++ conn_get(conn);
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ atomic_set(&cmnd->net_ref_cnt, 0);
++#endif
++ INIT_LIST_HEAD(&cmnd->rsp_cmd_list);
++ INIT_LIST_HEAD(&cmnd->rx_ddigest_cmd_list);
++ cmnd->target_task_tag = ISCSI_RESERVED_TAG_CPU32;
++
++ spin_lock_bh(&conn->cmd_list_lock);
++ list_add_tail(&cmnd->cmd_list_entry, &conn->cmd_list);
++ spin_unlock_bh(&conn->cmd_list_lock);
++ }
++
++ TRACE_DBG("conn %p, parent %p, cmnd %p", conn, parent, cmnd);
++ return cmnd;
++}
++
++/* Frees a command. Also frees the additional header. */
++static void cmnd_free(struct iscsi_cmnd *cmnd)
++{
++ TRACE_ENTRY();
++
++ TRACE_DBG("cmnd %p", cmnd);
++
++ if (unlikely(test_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags))) {
++ TRACE_MGMT_DBG("Free aborted cmd %p (scst cmd %p, state %d, "
++ "parent_req %p)", cmnd, cmnd->scst_cmd,
++ cmnd->scst_state, cmnd->parent_req);
++ }
++
++ /* Catch users from cmd_list or rsp_cmd_list */
++ EXTRACHECKS_BUG_ON(atomic_read(&cmnd->ref_cnt) != 0);
++
++ kfree(cmnd->pdu.ahs);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (unlikely(cmnd->on_write_list || cmnd->on_write_timeout_list)) {
++ struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
++
++ PRINT_CRIT_ERROR("cmnd %p still on some list?, %x, %x, %x, "
++ "%x, %x, %x, %x", cmnd, req->opcode, req->scb[0],
++ req->flags, req->itt, be32_to_cpu(req->data_length),
++ req->cmd_sn, be32_to_cpu((__force __be32)(cmnd->pdu.datasize)));
++
++ if (unlikely(cmnd->parent_req)) {
++ struct iscsi_scsi_cmd_hdr *preq =
++ cmnd_hdr(cmnd->parent_req);
++ PRINT_CRIT_ERROR("%p %x %u", preq, preq->opcode,
++ preq->scb[0]);
++ }
++ BUG();
++ }
++#endif
++
++ kmem_cache_free(iscsi_cmnd_cache, cmnd);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void iscsi_dec_active_cmds(struct iscsi_cmnd *req)
++{
++ struct iscsi_session *sess = req->conn->session;
++
++ TRACE_DBG("Decrementing active_cmds (req %p, sess %p, "
++ "new value %d)", req, sess,
++ atomic_read(&sess->active_cmds)-1);
++
++ EXTRACHECKS_BUG_ON(!req->dec_active_cmds);
++
++ atomic_dec(&sess->active_cmds);
++ smp_mb__after_atomic_dec();
++ req->dec_active_cmds = 0;
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (unlikely(atomic_read(&sess->active_cmds) < 0)) {
++ PRINT_CRIT_ERROR("active_cmds < 0 (%d)!!",
++ atomic_read(&sess->active_cmds));
++ BUG();
++ }
++#endif
++ return;
++}
++
++/* Might be called under some lock and on SIRQ */
++void cmnd_done(struct iscsi_cmnd *cmnd)
++{
++ TRACE_ENTRY();
++
++ TRACE_DBG("cmnd %p", cmnd);
++
++ if (unlikely(test_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags))) {
++ TRACE_MGMT_DBG("Done aborted cmd %p (scst cmd %p, state %d, "
++ "parent_req %p)", cmnd, cmnd->scst_cmd,
++ cmnd->scst_state, cmnd->parent_req);
++ }
++
++ EXTRACHECKS_BUG_ON(cmnd->on_rx_digest_list);
++ EXTRACHECKS_BUG_ON(cmnd->hashed);
++ EXTRACHECKS_BUG_ON(cmnd->cmd_req);
++ EXTRACHECKS_BUG_ON(cmnd->data_out_in_data_receiving);
++
++ req_del_from_write_timeout_list(cmnd);
++
++ if (cmnd->parent_req == NULL) {
++ struct iscsi_conn *conn = cmnd->conn;
++ struct iscsi_cmnd *rsp, *t;
++
++ TRACE_DBG("Deleting req %p from conn %p", cmnd, conn);
++
++ spin_lock_bh(&conn->cmd_list_lock);
++ list_del(&cmnd->cmd_list_entry);
++ spin_unlock_bh(&conn->cmd_list_lock);
++
++ conn_put(conn);
++
++ EXTRACHECKS_BUG_ON(!list_empty(&cmnd->rx_ddigest_cmd_list));
++
++ /* Order between above and below code is important! */
++
++ if ((cmnd->scst_cmd != NULL) || (cmnd->scst_aen != NULL)) {
++ switch (cmnd->scst_state) {
++ case ISCSI_CMD_STATE_PROCESSED:
++ TRACE_DBG("cmd %p PROCESSED", cmnd);
++ scst_tgt_cmd_done(cmnd->scst_cmd,
++ SCST_CONTEXT_DIRECT_ATOMIC);
++ break;
++
++ case ISCSI_CMD_STATE_AFTER_PREPROC:
++ {
++ /* It can be for some aborted commands */
++ struct scst_cmd *scst_cmd = cmnd->scst_cmd;
++ TRACE_DBG("cmd %p AFTER_PREPROC", cmnd);
++ cmnd->scst_state = ISCSI_CMD_STATE_RESTARTED;
++ cmnd->scst_cmd = NULL;
++ scst_restart_cmd(scst_cmd,
++ SCST_PREPROCESS_STATUS_ERROR_FATAL,
++ SCST_CONTEXT_THREAD);
++ break;
++ }
++
++ case ISCSI_CMD_STATE_AEN:
++ TRACE_DBG("cmd %p AEN PROCESSED", cmnd);
++ scst_aen_done(cmnd->scst_aen);
++ break;
++
++ case ISCSI_CMD_STATE_OUT_OF_SCST_PRELIM_COMPL:
++ break;
++
++ default:
++ PRINT_CRIT_ERROR("Unexpected cmnd scst state "
++ "%d", cmnd->scst_state);
++ BUG();
++ break;
++ }
++ }
++
++ if (cmnd->own_sg) {
++ TRACE_DBG("own_sg for req %p", cmnd);
++ if (cmnd->sg != &dummy_sg)
++ scst_free(cmnd->sg, cmnd->sg_cnt);
++#ifdef CONFIG_SCST_DEBUG
++ cmnd->own_sg = 0;
++ cmnd->sg = NULL;
++ cmnd->sg_cnt = -1;
++#endif
++ }
++
++ if (unlikely(cmnd->dec_active_cmds))
++ iscsi_dec_active_cmds(cmnd);
++
++ list_for_each_entry_safe(rsp, t, &cmnd->rsp_cmd_list,
++ rsp_cmd_list_entry) {
++ cmnd_free(rsp);
++ }
++
++ cmnd_free(cmnd);
++ } else {
++ struct iscsi_cmnd *parent = cmnd->parent_req;
++
++ if (cmnd->own_sg) {
++ TRACE_DBG("own_sg for rsp %p", cmnd);
++ if ((cmnd->sg != &dummy_sg) && (cmnd->sg != cmnd->rsp_sg))
++ scst_free(cmnd->sg, cmnd->sg_cnt);
++#ifdef CONFIG_SCST_DEBUG
++ cmnd->own_sg = 0;
++ cmnd->sg = NULL;
++ cmnd->sg_cnt = -1;
++#endif
++ }
++
++ EXTRACHECKS_BUG_ON(cmnd->dec_active_cmds);
++
++ if (cmnd == parent->main_rsp) {
++ TRACE_DBG("Finishing main rsp %p (req %p)", cmnd,
++ parent);
++ parent->main_rsp = NULL;
++ }
++
++ cmnd_put(parent);
++ /*
++ * cmnd will be freed on the last parent's put and can already
++ * be freed!!
++ */
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * Corresponding conn may also get destroyed after this function, except only
++ * if it's called from the read thread!
++ *
++ * It can't be called in parallel with iscsi_cmnds_init_write()!
++ */
++void req_cmnd_release_force(struct iscsi_cmnd *req)
++{
++ struct iscsi_cmnd *rsp, *t;
++ struct iscsi_conn *conn = req->conn;
++ LIST_HEAD(cmds_list);
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("req %p", req);
++
++ BUG_ON(req == conn->read_cmnd);
++
++ spin_lock_bh(&conn->write_list_lock);
++ list_for_each_entry_safe(rsp, t, &conn->write_list, write_list_entry) {
++ if (rsp->parent_req != req)
++ continue;
++
++ cmd_del_from_write_list(rsp);
++
++ list_add_tail(&rsp->write_list_entry, &cmds_list);
++ }
++ spin_unlock_bh(&conn->write_list_lock);
++
++ list_for_each_entry_safe(rsp, t, &cmds_list, write_list_entry) {
++ TRACE_MGMT_DBG("Putting write rsp %p", rsp);
++ list_del(&rsp->write_list_entry);
++ cmnd_put(rsp);
++ }
++
++ /* Supposed nobody can add responses in the list anymore */
++ list_for_each_entry_reverse(rsp, &req->rsp_cmd_list,
++ rsp_cmd_list_entry) {
++ bool r;
++
++ if (rsp->force_cleanup_done)
++ continue;
++
++ rsp->force_cleanup_done = 1;
++
++ if (cmnd_get_check(rsp))
++ continue;
++
++ spin_lock_bh(&conn->write_list_lock);
++ r = rsp->on_write_list || rsp->write_processing_started;
++ spin_unlock_bh(&conn->write_list_lock);
++
++ cmnd_put(rsp);
++
++ if (r)
++ continue;
++
++ /*
++ * If both on_write_list and write_processing_started not set,
++ * we can safely put() rsp.
++ */
++ TRACE_MGMT_DBG("Putting rsp %p", rsp);
++ cmnd_put(rsp);
++ }
++
++ if (req->main_rsp != NULL) {
++ TRACE_MGMT_DBG("Putting main rsp %p", req->main_rsp);
++ cmnd_put(req->main_rsp);
++ req->main_rsp = NULL;
++ }
++
++ req_cmnd_release(req);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void req_cmnd_pre_release(struct iscsi_cmnd *req)
++{
++ struct iscsi_cmnd *c, *t;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("req %p", req);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ BUG_ON(req->release_called);
++ req->release_called = 1;
++#endif
++
++ if (unlikely(test_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags))) {
++ TRACE_MGMT_DBG("Release aborted req cmd %p (scst cmd %p, "
++ "state %d)", req, req->scst_cmd, req->scst_state);
++ }
++
++ BUG_ON(req->parent_req != NULL);
++
++ if (unlikely(req->hashed)) {
++ /* It sometimes can happen during errors recovery */
++ TRACE_MGMT_DBG("Removing req %p from hash", req);
++ cmnd_remove_data_wait_hash(req);
++ }
++
++ if (unlikely(req->cmd_req)) {
++ /* It sometimes can happen during errors recovery */
++ TRACE_MGMT_DBG("Putting cmd_req %p (req %p)", req->cmd_req, req);
++ req->cmd_req->data_out_in_data_receiving = 0;
++ cmnd_put(req->cmd_req);
++ req->cmd_req = NULL;
++ }
++
++ if (unlikely(req->main_rsp != NULL)) {
++ TRACE_DBG("Sending main rsp %p", req->main_rsp);
++ if (cmnd_opcode(req) == ISCSI_OP_SCSI_CMD) {
++ if (req->scst_cmd != NULL)
++ iscsi_set_resid(req->main_rsp);
++ else
++ iscsi_set_resid_no_scst_cmd(req->main_rsp);
++ }
++ iscsi_cmnd_init_write(req->main_rsp, ISCSI_INIT_WRITE_WAKE);
++ req->main_rsp = NULL;
++ }
++
++ list_for_each_entry_safe(c, t, &req->rx_ddigest_cmd_list,
++ rx_ddigest_cmd_list_entry) {
++ cmd_del_from_rx_ddigest_list(c);
++ cmnd_put(c);
++ }
++
++ EXTRACHECKS_BUG_ON(req->pending);
++
++ if (unlikely(req->dec_active_cmds))
++ iscsi_dec_active_cmds(req);
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * Corresponding conn may also get destroyed after this function, except only
++ * if it's called from the read thread!
++ */
++static void req_cmnd_release(struct iscsi_cmnd *req)
++{
++ TRACE_ENTRY();
++
++ req_cmnd_pre_release(req);
++ cmnd_put(req);
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * Corresponding conn may also get destroyed after this function, except only
++ * if it's called from the read thread!
++ */
++void rsp_cmnd_release(struct iscsi_cmnd *cmnd)
++{
++ TRACE_DBG("%p", cmnd);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ BUG_ON(cmnd->release_called);
++ cmnd->release_called = 1;
++#endif
++
++ EXTRACHECKS_BUG_ON(cmnd->parent_req == NULL);
++
++ cmnd_put(cmnd);
++ return;
++}
++
++static struct iscsi_cmnd *iscsi_alloc_rsp(struct iscsi_cmnd *parent)
++{
++ struct iscsi_cmnd *rsp;
++
++ TRACE_ENTRY();
++
++ rsp = cmnd_alloc(parent->conn, parent);
++
++ TRACE_DBG("Adding rsp %p to parent %p", rsp, parent);
++ list_add_tail(&rsp->rsp_cmd_list_entry, &parent->rsp_cmd_list);
++
++ cmnd_get(parent);
++
++ TRACE_EXIT_HRES((unsigned long)rsp);
++ return rsp;
++}
++
++static inline struct iscsi_cmnd *iscsi_alloc_main_rsp(struct iscsi_cmnd *parent)
++{
++ struct iscsi_cmnd *rsp;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(parent->main_rsp != NULL);
++
++ rsp = iscsi_alloc_rsp(parent);
++ parent->main_rsp = rsp;
++
++ TRACE_EXIT_HRES((unsigned long)rsp);
++ return rsp;
++}
++
++static void iscsi_cmnds_init_write(struct list_head *send, int flags)
++{
++ struct iscsi_cmnd *rsp = list_entry(send->next, struct iscsi_cmnd,
++ write_list_entry);
++ struct iscsi_conn *conn = rsp->conn;
++ struct list_head *pos, *next;
++
++ BUG_ON(list_empty(send));
++
++ if (!(conn->ddigest_type & DIGEST_NONE)) {
++ list_for_each(pos, send) {
++ rsp = list_entry(pos, struct iscsi_cmnd,
++ write_list_entry);
++
++ if (rsp->pdu.datasize != 0) {
++ TRACE_DBG("Doing data digest (%p:%x)", rsp,
++ cmnd_opcode(rsp));
++ digest_tx_data(rsp);
++ }
++ }
++ }
++
++ spin_lock_bh(&conn->write_list_lock);
++ list_for_each_safe(pos, next, send) {
++ rsp = list_entry(pos, struct iscsi_cmnd, write_list_entry);
++
++ TRACE_DBG("%p:%x", rsp, cmnd_opcode(rsp));
++
++ BUG_ON(conn != rsp->conn);
++
++ list_del(&rsp->write_list_entry);
++ cmd_add_on_write_list(conn, rsp);
++ }
++ spin_unlock_bh(&conn->write_list_lock);
++
++ if (flags & ISCSI_INIT_WRITE_WAKE)
++ iscsi_make_conn_wr_active(conn);
++
++ return;
++}
++
++static void iscsi_cmnd_init_write(struct iscsi_cmnd *rsp, int flags)
++{
++ LIST_HEAD(head);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ if (unlikely(rsp->on_write_list)) {
++ PRINT_CRIT_ERROR("cmd already on write list (%x %x %x "
++ "%u %u %d %d", rsp->pdu.bhs.itt,
++ cmnd_opcode(rsp), cmnd_scsicode(rsp),
++ rsp->hdigest, rsp->ddigest,
++ list_empty(&rsp->rsp_cmd_list), rsp->hashed);
++ BUG();
++ }
++#endif
++ list_add_tail(&rsp->write_list_entry, &head);
++ iscsi_cmnds_init_write(&head, flags);
++ return;
++}
++
++static void iscsi_set_resid_no_scst_cmd(struct iscsi_cmnd *rsp)
++{
++ struct iscsi_cmnd *req = rsp->parent_req;
++ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
++ struct iscsi_scsi_rsp_hdr *rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
++ int resid, out_resid;
++
++ TRACE_ENTRY();
++
++ BUG_ON(req->scst_cmd != NULL);
++
++ TRACE_DBG("req %p, rsp %p, outstanding_r2t %d, r2t_len_to_receive %d, "
++ "r2t_len_to_send %d, not_received_data_len %d", req, rsp,
++ req->outstanding_r2t, req->r2t_len_to_receive,
++ req->r2t_len_to_send, req->not_received_data_len);
++
++ if ((req_hdr->flags & ISCSI_CMD_READ) &&
++ (req_hdr->flags & ISCSI_CMD_WRITE)) {
++ out_resid = req->not_received_data_len;
++ if (out_resid > 0) {
++ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
++ rsp_hdr->residual_count = cpu_to_be32(out_resid);
++ } else if (out_resid < 0) {
++ out_resid = -out_resid;
++ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_OVERFLOW;
++ rsp_hdr->residual_count = cpu_to_be32(out_resid);
++ }
++
++ resid = cmnd_read_size(req);
++ if (resid > 0) {
++ rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_UNDERFLOW;
++ rsp_hdr->bi_residual_count = cpu_to_be32(resid);
++ } else if (resid < 0) {
++ resid = -resid;
++ rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_OVERFLOW;
++ rsp_hdr->bi_residual_count = cpu_to_be32(resid);
++ }
++ } else if (req_hdr->flags & ISCSI_CMD_READ) {
++ resid = be32_to_cpu(req_hdr->data_length);
++ if (resid > 0) {
++ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
++ rsp_hdr->residual_count = cpu_to_be32(resid);
++ }
++ } else if (req_hdr->flags & ISCSI_CMD_WRITE) {
++ resid = req->not_received_data_len;
++ if (resid > 0) {
++ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
++ rsp_hdr->residual_count = cpu_to_be32(resid);
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static void iscsi_set_resid(struct iscsi_cmnd *rsp)
++{
++ struct iscsi_cmnd *req = rsp->parent_req;
++ struct scst_cmd *scst_cmd = req->scst_cmd;
++ struct iscsi_scsi_cmd_hdr *req_hdr;
++ struct iscsi_scsi_rsp_hdr *rsp_hdr;
++ int resid, out_resid;
++
++ TRACE_ENTRY();
++
++ if (likely(!scst_get_resid(scst_cmd, &resid, &out_resid))) {
++ TRACE_DBG("No residuals for req %p", req);
++ goto out;
++ }
++
++ TRACE_DBG("req %p, resid %d, out_resid %d", req, resid, out_resid);
++
++ req_hdr = cmnd_hdr(req);
++ rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
++
++ if ((req_hdr->flags & ISCSI_CMD_READ) &&
++ (req_hdr->flags & ISCSI_CMD_WRITE)) {
++ if (out_resid > 0) {
++ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
++ rsp_hdr->residual_count = cpu_to_be32(out_resid);
++ } else if (out_resid < 0) {
++ out_resid = -out_resid;
++ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_OVERFLOW;
++ rsp_hdr->residual_count = cpu_to_be32(out_resid);
++ }
++
++ if (resid > 0) {
++ rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_UNDERFLOW;
++ rsp_hdr->bi_residual_count = cpu_to_be32(resid);
++ } else if (resid < 0) {
++ resid = -resid;
++ rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_OVERFLOW;
++ rsp_hdr->bi_residual_count = cpu_to_be32(resid);
++ }
++ } else {
++ if (resid > 0) {
++ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
++ rsp_hdr->residual_count = cpu_to_be32(resid);
++ } else if (resid < 0) {
++ resid = -resid;
++ rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_OVERFLOW;
++ rsp_hdr->residual_count = cpu_to_be32(resid);
++ }
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void send_data_rsp(struct iscsi_cmnd *req, u8 status, int send_status)
++{
++ struct iscsi_cmnd *rsp;
++ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
++ struct iscsi_data_in_hdr *rsp_hdr;
++ u32 pdusize, size, offset, sn;
++ LIST_HEAD(send);
++
++ TRACE_DBG("req %p", req);
++
++ pdusize = req->conn->session->sess_params.max_xmit_data_length;
++ size = req->bufflen;
++ offset = 0;
++ sn = 0;
++
++ while (1) {
++ rsp = iscsi_alloc_rsp(req);
++ TRACE_DBG("rsp %p", rsp);
++ rsp->sg = req->sg;
++ rsp->sg_cnt = req->sg_cnt;
++ rsp->bufflen = req->bufflen;
++ rsp_hdr = (struct iscsi_data_in_hdr *)&rsp->pdu.bhs;
++
++ rsp_hdr->opcode = ISCSI_OP_SCSI_DATA_IN;
++ rsp_hdr->itt = req_hdr->itt;
++ rsp_hdr->ttt = ISCSI_RESERVED_TAG;
++ rsp_hdr->buffer_offset = cpu_to_be32(offset);
++ rsp_hdr->data_sn = cpu_to_be32(sn);
++
++ if (size <= pdusize) {
++ TRACE_DBG("offset %d, size %d", offset, size);
++ rsp->pdu.datasize = size;
++ if (send_status) {
++ TRACE_DBG("status %x", status);
++
++ EXTRACHECKS_BUG_ON((cmnd_hdr(req)->flags & ISCSI_CMD_WRITE) != 0);
++
++ rsp_hdr->flags = ISCSI_FLG_FINAL | ISCSI_FLG_STATUS;
++ rsp_hdr->cmd_status = status;
++
++ iscsi_set_resid(rsp);
++ }
++ list_add_tail(&rsp->write_list_entry, &send);
++ break;
++ }
++
++ TRACE_DBG("pdusize %d, offset %d, size %d", pdusize, offset,
++ size);
++
++ rsp->pdu.datasize = pdusize;
++
++ size -= pdusize;
++ offset += pdusize;
++ sn++;
++
++ list_add_tail(&rsp->write_list_entry, &send);
++ }
++ iscsi_cmnds_init_write(&send, 0);
++ return;
++}
++
++static void iscsi_init_status_rsp(struct iscsi_cmnd *rsp,
++ int status, const u8 *sense_buf, int sense_len)
++{
++ struct iscsi_cmnd *req = rsp->parent_req;
++ struct iscsi_scsi_rsp_hdr *rsp_hdr;
++ struct scatterlist *sg;
++
++ TRACE_ENTRY();
++
++ rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
++ rsp_hdr->opcode = ISCSI_OP_SCSI_RSP;
++ rsp_hdr->flags = ISCSI_FLG_FINAL;
++ rsp_hdr->response = ISCSI_RESPONSE_COMMAND_COMPLETED;
++ rsp_hdr->cmd_status = status;
++ rsp_hdr->itt = cmnd_hdr(req)->itt;
++
++ if (SCST_SENSE_VALID(sense_buf)) {
++ TRACE_DBG("%s", "SENSE VALID");
++
++ sg = rsp->sg = rsp->rsp_sg;
++ rsp->sg_cnt = 2;
++ rsp->own_sg = 1;
++
++ sg_init_table(sg, 2);
++ sg_set_buf(&sg[0], &rsp->sense_hdr, sizeof(rsp->sense_hdr));
++ sg_set_buf(&sg[1], sense_buf, sense_len);
++
++ rsp->sense_hdr.length = cpu_to_be16(sense_len);
++
++ rsp->pdu.datasize = sizeof(rsp->sense_hdr) + sense_len;
++ rsp->bufflen = rsp->pdu.datasize;
++ } else {
++ rsp->pdu.datasize = 0;
++ rsp->bufflen = 0;
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline struct iscsi_cmnd *create_status_rsp(struct iscsi_cmnd *req,
++ int status, const u8 *sense_buf, int sense_len)
++{
++ struct iscsi_cmnd *rsp;
++
++ TRACE_ENTRY();
++
++ rsp = iscsi_alloc_rsp(req);
++ TRACE_DBG("rsp %p", rsp);
++
++ iscsi_init_status_rsp(rsp, status, sense_buf, sense_len);
++ iscsi_set_resid(rsp);
++
++ TRACE_EXIT_HRES((unsigned long)rsp);
++ return rsp;
++}
++
++/*
++ * Initializes data receive fields. Can be called only when they have not been
++ * initialized yet.
++ */
++static int iscsi_set_prelim_r2t_len_to_receive(struct iscsi_cmnd *req)
++{
++ struct iscsi_scsi_cmd_hdr *req_hdr = (struct iscsi_scsi_cmd_hdr *)&req->pdu.bhs;
++ int res = 0;
++ unsigned int not_received;
++
++ TRACE_ENTRY();
++
++ if (req_hdr->flags & ISCSI_CMD_FINAL) {
++ if (req_hdr->flags & ISCSI_CMD_WRITE)
++ iscsi_set_not_received_data_len(req,
++ be32_to_cpu(req_hdr->data_length) -
++ req->pdu.datasize);
++ goto out;
++ }
++
++ BUG_ON(req->outstanding_r2t != 0);
++
++ res = cmnd_insert_data_wait_hash(req);
++ if (res != 0) {
++ /*
++ * We have to close connection, because otherwise a data
++ * corruption is possible if we allow to receive data
++ * for this request in another request with dublicated ITT.
++ */
++ mark_conn_closed(req->conn);
++ goto out;
++ }
++
++ /*
++ * We need to wait for one or more PDUs. Let's simplify
++ * other code and pretend we need to receive 1 byte.
++ * In data_out_start() we will correct it.
++ */
++ req->outstanding_r2t = 1;
++ req_add_to_write_timeout_list(req);
++ req->r2t_len_to_receive = 1;
++ req->r2t_len_to_send = 0;
++
++ not_received = be32_to_cpu(req_hdr->data_length) - req->pdu.datasize;
++ not_received -= min_t(unsigned int, not_received,
++ req->conn->session->sess_params.first_burst_length);
++ iscsi_set_not_received_data_len(req, not_received);
++
++ TRACE_DBG("req %p, op %x, outstanding_r2t %d, r2t_len_to_receive %d, "
++ "r2t_len_to_send %d, not_received_data_len %d", req,
++ cmnd_opcode(req), req->outstanding_r2t, req->r2t_len_to_receive,
++ req->r2t_len_to_send, req->not_received_data_len);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int create_preliminary_no_scst_rsp(struct iscsi_cmnd *req,
++ int status, const u8 *sense_buf, int sense_len)
++{
++ struct iscsi_cmnd *rsp;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (req->prelim_compl_flags != 0) {
++ TRACE_MGMT_DBG("req %p already prelim completed", req);
++ goto out;
++ }
++
++ req->scst_state = ISCSI_CMD_STATE_OUT_OF_SCST_PRELIM_COMPL;
++
++ BUG_ON(req->scst_cmd != NULL);
++
++ res = iscsi_preliminary_complete(req, req, true);
++
++ rsp = iscsi_alloc_main_rsp(req);
++ TRACE_DBG("main rsp %p", rsp);
++
++ iscsi_init_status_rsp(rsp, status, sense_buf, sense_len);
++
++ /* Resid will be set in req_cmnd_release() */
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++int set_scst_preliminary_status_rsp(struct iscsi_cmnd *req,
++ bool get_data, int key, int asc, int ascq)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (req->scst_cmd == NULL) {
++ /* There must be already error set */
++ goto complete;
++ }
++
++ scst_set_cmd_error(req->scst_cmd, key, asc, ascq);
++
++complete:
++ res = iscsi_preliminary_complete(req, req, get_data);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int create_reject_rsp(struct iscsi_cmnd *req, int reason, bool get_data)
++{
++ int res = 0;
++ struct iscsi_cmnd *rsp;
++ struct iscsi_reject_hdr *rsp_hdr;
++ struct scatterlist *sg;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Reject: req %p, reason %x", req, reason);
++
++ if (cmnd_opcode(req) == ISCSI_OP_SCSI_CMD) {
++ if (req->scst_cmd == NULL) {
++ /* BUSY status must be already set */
++ struct iscsi_scsi_rsp_hdr *rsp_hdr1;
++ rsp_hdr1 = (struct iscsi_scsi_rsp_hdr *)&req->main_rsp->pdu.bhs;
++ BUG_ON(rsp_hdr1->cmd_status == 0);
++ /*
++ * Let's not send REJECT here. The initiator will retry
++ * and, hopefully, next time we will not fail allocating
++ * scst_cmd, so we will then send the REJECT.
++ */
++ goto out;
++ } else {
++ /*
++ * "In all the cases in which a pre-instantiated SCSI
++ * task is terminated because of the reject, the target
++ * MUST issue a proper SCSI command response with CHECK
++ * CONDITION as described in Section 10.4.3 Response" -
++ * RFC 3720.
++ */
++ set_scst_preliminary_status_rsp(req, get_data,
++ SCST_LOAD_SENSE(scst_sense_invalid_message));
++ }
++ }
++
++ rsp = iscsi_alloc_main_rsp(req);
++ rsp_hdr = (struct iscsi_reject_hdr *)&rsp->pdu.bhs;
++
++ rsp_hdr->opcode = ISCSI_OP_REJECT;
++ rsp_hdr->ffffffff = ISCSI_RESERVED_TAG;
++ rsp_hdr->reason = reason;
++
++ sg = rsp->sg = rsp->rsp_sg;
++ rsp->sg_cnt = 1;
++ rsp->own_sg = 1;
++ sg_init_one(sg, &req->pdu.bhs, sizeof(struct iscsi_hdr));
++ rsp->bufflen = rsp->pdu.datasize = sizeof(struct iscsi_hdr);
++
++ res = iscsi_preliminary_complete(req, req, true);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static inline int iscsi_get_allowed_cmds(struct iscsi_session *sess)
++{
++ int res = max(-1, (int)sess->tgt_params.queued_cmnds -
++ atomic_read(&sess->active_cmds)-1);
++ TRACE_DBG("allowed cmds %d (sess %p, active_cmds %d)", res,
++ sess, atomic_read(&sess->active_cmds));
++ return res;
++}
++
++static __be32 cmnd_set_sn(struct iscsi_cmnd *cmnd, int set_stat_sn)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++ struct iscsi_session *sess = conn->session;
++ __be32 res;
++
++ spin_lock(&sess->sn_lock);
++
++ if (set_stat_sn)
++ cmnd->pdu.bhs.sn = (__force u32)cpu_to_be32(conn->stat_sn++);
++ cmnd->pdu.bhs.exp_sn = (__force u32)cpu_to_be32(sess->exp_cmd_sn);
++ cmnd->pdu.bhs.max_sn = (__force u32)cpu_to_be32(sess->exp_cmd_sn +
++ iscsi_get_allowed_cmds(sess));
++
++ res = cpu_to_be32(conn->stat_sn);
++
++ spin_unlock(&sess->sn_lock);
++ return res;
++}
++
++/* Called under sn_lock */
++static void update_stat_sn(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++ u32 exp_stat_sn;
++
++ cmnd->pdu.bhs.exp_sn = exp_stat_sn = be32_to_cpu((__force __be32)cmnd->pdu.bhs.exp_sn);
++ TRACE_DBG("%x,%x", cmnd_opcode(cmnd), exp_stat_sn);
++ if ((int)(exp_stat_sn - conn->exp_stat_sn) > 0 &&
++ (int)(exp_stat_sn - conn->stat_sn) <= 0) {
++ /* free pdu resources */
++ cmnd->conn->exp_stat_sn = exp_stat_sn;
++ }
++ return;
++}
++
++static struct iscsi_cmnd *cmnd_find_itt_get(struct iscsi_conn *conn, __be32 itt)
++{
++ struct iscsi_cmnd *cmnd, *found_cmnd = NULL;
++
++ spin_lock_bh(&conn->cmd_list_lock);
++ list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) {
++ if ((cmnd->pdu.bhs.itt == itt) && !cmnd_get_check(cmnd)) {
++ found_cmnd = cmnd;
++ break;
++ }
++ }
++ spin_unlock_bh(&conn->cmd_list_lock);
++
++ return found_cmnd;
++}
++
++/**
++ ** We use the ITT hash only to find original request PDU for subsequent
++ ** Data-Out PDUs.
++ **/
++
++/* Must be called under cmnd_data_wait_hash_lock */
++static struct iscsi_cmnd *__cmnd_find_data_wait_hash(struct iscsi_conn *conn,
++ __be32 itt)
++{
++ struct list_head *head;
++ struct iscsi_cmnd *cmnd;
++
++ head = &conn->session->cmnd_data_wait_hash[cmnd_hashfn((__force u32)itt)];
++
++ list_for_each_entry(cmnd, head, hash_list_entry) {
++ if (cmnd->pdu.bhs.itt == itt)
++ return cmnd;
++ }
++ return NULL;
++}
++
++static struct iscsi_cmnd *cmnd_find_data_wait_hash(struct iscsi_conn *conn,
++ __be32 itt)
++{
++ struct iscsi_cmnd *res;
++ struct iscsi_session *session = conn->session;
++
++ spin_lock(&session->cmnd_data_wait_hash_lock);
++ res = __cmnd_find_data_wait_hash(conn, itt);
++ spin_unlock(&session->cmnd_data_wait_hash_lock);
++
++ return res;
++}
++
++static inline u32 get_next_ttt(struct iscsi_conn *conn)
++{
++ u32 ttt;
++ struct iscsi_session *session = conn->session;
++
++ /* Not compatible with MC/S! */
++
++ iscsi_extracheck_is_rd_thread(conn);
++
++ if (unlikely(session->next_ttt == ISCSI_RESERVED_TAG_CPU32))
++ session->next_ttt++;
++ ttt = session->next_ttt++;
++
++ return ttt;
++}
++
++static int cmnd_insert_data_wait_hash(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_session *session = cmnd->conn->session;
++ struct iscsi_cmnd *tmp;
++ struct list_head *head;
++ int err = 0;
++ __be32 itt = cmnd->pdu.bhs.itt;
++
++ if (unlikely(cmnd->hashed)) {
++ /*
++ * It can be for preliminary completed commands, when this
++ * function already failed.
++ */
++ goto out;
++ }
++
++ /*
++ * We don't need TTT, because ITT/buffer_offset pair is sufficient
++ * to find out the original request and buffer for Data-Out PDUs, but
++ * crazy iSCSI spec requires us to send this superfluous field in
++ * R2T PDUs and some initiators may rely on it.
++ */
++ cmnd->target_task_tag = get_next_ttt(cmnd->conn);
++
++ TRACE_DBG("%p:%x", cmnd, itt);
++ if (unlikely(itt == ISCSI_RESERVED_TAG)) {
++ PRINT_ERROR("%s", "ITT is RESERVED_TAG");
++ PRINT_BUFFER("Incorrect BHS", &cmnd->pdu.bhs,
++ sizeof(cmnd->pdu.bhs));
++ err = -ISCSI_REASON_PROTOCOL_ERROR;
++ goto out;
++ }
++
++ spin_lock(&session->cmnd_data_wait_hash_lock);
++
++ head = &session->cmnd_data_wait_hash[cmnd_hashfn((__force u32)itt)];
++
++ tmp = __cmnd_find_data_wait_hash(cmnd->conn, itt);
++ if (likely(!tmp)) {
++ TRACE_DBG("Adding cmnd %p to the hash (ITT %x)", cmnd,
++ cmnd->pdu.bhs.itt);
++ list_add_tail(&cmnd->hash_list_entry, head);
++ cmnd->hashed = 1;
++ } else {
++ PRINT_ERROR("Task %x in progress, cmnd %p", itt, cmnd);
++ err = -ISCSI_REASON_TASK_IN_PROGRESS;
++ }
++
++ spin_unlock(&session->cmnd_data_wait_hash_lock);
++
++out:
++ return err;
++}
++
++static void cmnd_remove_data_wait_hash(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_session *session = cmnd->conn->session;
++ struct iscsi_cmnd *tmp;
++
++ spin_lock(&session->cmnd_data_wait_hash_lock);
++
++ tmp = __cmnd_find_data_wait_hash(cmnd->conn, cmnd->pdu.bhs.itt);
++
++ if (likely(tmp && tmp == cmnd)) {
++ TRACE_DBG("Deleting cmnd %p from the hash (ITT %x)", cmnd,
++ cmnd->pdu.bhs.itt);
++ list_del(&cmnd->hash_list_entry);
++ cmnd->hashed = 0;
++ } else
++ PRINT_ERROR("%p:%x not found", cmnd, cmnd->pdu.bhs.itt);
++
++ spin_unlock(&session->cmnd_data_wait_hash_lock);
++
++ return;
++}
++
++static void cmnd_prepare_get_rejected_immed_data(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++ struct scatterlist *sg = cmnd->sg;
++ char __user *addr;
++ u32 size;
++ unsigned int i;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG_FLAG(iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(cmnd),
++ "Skipping (cmnd %p, ITT %x, op %x, cmd op %x, "
++ "datasize %u, scst_cmd %p, scst state %d)", cmnd,
++ cmnd->pdu.bhs.itt, cmnd_opcode(cmnd), cmnd_hdr(cmnd)->scb[0],
++ cmnd->pdu.datasize, cmnd->scst_cmd, cmnd->scst_state);
++
++ iscsi_extracheck_is_rd_thread(conn);
++
++ size = cmnd->pdu.datasize;
++ if (!size)
++ goto out;
++
++ /* We already checked pdu.datasize in check_segment_length() */
++
++ /*
++ * There are no problems with the safety from concurrent
++ * accesses to dummy_page in dummy_sg, since data only
++ * will be read and then discarded.
++ */
++ sg = &dummy_sg;
++ if (cmnd->sg == NULL) {
++ /* just in case */
++ cmnd->sg = sg;
++ cmnd->bufflen = PAGE_SIZE;
++ cmnd->own_sg = 1;
++ }
++
++ addr = (char __force __user *)(page_address(sg_page(&sg[0])));
++ conn->read_size = size;
++ for (i = 0; size > PAGE_SIZE; i++, size -= PAGE_SIZE) {
++ /* We already checked pdu.datasize in check_segment_length() */
++ BUG_ON(i >= ISCSI_CONN_IOV_MAX);
++ conn->read_iov[i].iov_base = addr;
++ conn->read_iov[i].iov_len = PAGE_SIZE;
++ }
++ conn->read_iov[i].iov_base = addr;
++ conn->read_iov[i].iov_len = size;
++ conn->read_msg.msg_iov = conn->read_iov;
++ conn->read_msg.msg_iovlen = ++i;
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++int iscsi_preliminary_complete(struct iscsi_cmnd *req,
++ struct iscsi_cmnd *orig_req, bool get_data)
++{
++ int res = 0;
++ bool set_r2t_len;
++ struct iscsi_hdr *orig_req_hdr = &orig_req->pdu.bhs;
++
++ TRACE_ENTRY();
++
++#ifdef CONFIG_SCST_DEBUG
++ {
++ struct iscsi_hdr *req_hdr = &req->pdu.bhs;
++ TRACE_DBG_FLAG(iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(orig_req),
++ "Prelim completed req %p, orig_req %p (FINAL %x, "
++ "outstanding_r2t %d)", req, orig_req,
++ (req_hdr->flags & ISCSI_CMD_FINAL),
++ orig_req->outstanding_r2t);
++ }
++#endif
++
++ iscsi_extracheck_is_rd_thread(req->conn);
++ BUG_ON(req->parent_req != NULL);
++
++ if (test_bit(ISCSI_CMD_PRELIM_COMPLETED, &req->prelim_compl_flags)) {
++ TRACE_MGMT_DBG("req %p already prelim completed", req);
++ /* To not try to get data twice */
++ get_data = false;
++ }
++
++ /*
++ * We need to receive all outstanding PDUs, even if direction isn't
++ * WRITE. Test of PRELIM_COMPLETED is needed, because
++ * iscsi_set_prelim_r2t_len_to_receive() could also have failed before.
++ */
++ set_r2t_len = !orig_req->hashed &&
++ (cmnd_opcode(orig_req) == ISCSI_OP_SCSI_CMD) &&
++ !test_bit(ISCSI_CMD_PRELIM_COMPLETED,
++ &orig_req->prelim_compl_flags);
++
++ TRACE_DBG("get_data %d, set_r2t_len %d", get_data, set_r2t_len);
++
++ if (get_data)
++ cmnd_prepare_get_rejected_immed_data(req);
++
++ if (test_bit(ISCSI_CMD_PRELIM_COMPLETED, &orig_req->prelim_compl_flags))
++ goto out_set;
++
++ if (set_r2t_len)
++ res = iscsi_set_prelim_r2t_len_to_receive(orig_req);
++ else if (orig_req_hdr->flags & ISCSI_CMD_WRITE) {
++ /*
++ * We will get here if orig_req prelim completed in the middle
++ * of data receiving. We won't send more R2T's, so
++ * r2t_len_to_send is final and won't be updated anymore in
++ * future.
++ */
++ iscsi_set_not_received_data_len(orig_req,
++ orig_req->r2t_len_to_send);
++ }
++
++out_set:
++ set_bit(ISCSI_CMD_PRELIM_COMPLETED, &orig_req->prelim_compl_flags);
++ set_bit(ISCSI_CMD_PRELIM_COMPLETED, &req->prelim_compl_flags);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int cmnd_prepare_recv_pdu(struct iscsi_conn *conn,
++ struct iscsi_cmnd *cmd, u32 offset, u32 size)
++{
++ struct scatterlist *sg = cmd->sg;
++ unsigned int bufflen = cmd->bufflen;
++ unsigned int idx, i, buff_offs;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("cmd %p, sg %p, offset %u, size %u", cmd, cmd->sg,
++ offset, size);
++
++ iscsi_extracheck_is_rd_thread(conn);
++
++ buff_offs = offset;
++ idx = (offset + sg[0].offset) >> PAGE_SHIFT;
++ offset &= ~PAGE_MASK;
++
++ conn->read_msg.msg_iov = conn->read_iov;
++ conn->read_size = size;
++
++ i = 0;
++ while (1) {
++ unsigned int sg_len;
++ char __user *addr;
++
++ if (unlikely(buff_offs >= bufflen)) {
++ TRACE_DBG("Residual overflow (cmd %p, buff_offs %d, "
++ "bufflen %d)", cmd, buff_offs, bufflen);
++ idx = 0;
++ sg = &dummy_sg;
++ offset = 0;
++ }
++
++ addr = (char __force __user *)(sg_virt(&sg[idx]));
++ EXTRACHECKS_BUG_ON(addr == NULL);
++ sg_len = sg[idx].length - offset;
++
++ conn->read_iov[i].iov_base = addr + offset;
++
++ if (size <= sg_len) {
++ TRACE_DBG("idx=%d, i=%d, offset=%u, size=%d, addr=%p",
++ idx, i, offset, size, addr);
++ conn->read_iov[i].iov_len = size;
++ conn->read_msg.msg_iovlen = i+1;
++ break;
++ }
++ conn->read_iov[i].iov_len = sg_len;
++
++ TRACE_DBG("idx=%d, i=%d, offset=%u, size=%d, sg_len=%u, "
++ "addr=%p", idx, i, offset, size, sg_len, addr);
++
++ size -= sg_len;
++ buff_offs += sg_len;
++
++ i++;
++ if (unlikely(i >= ISCSI_CONN_IOV_MAX)) {
++ PRINT_ERROR("Initiator %s violated negotiated "
++ "parameters by sending too much data (size "
++ "left %d)", conn->session->initiator_name,
++ size);
++ mark_conn_closed(conn);
++ res = -EINVAL;
++ break;
++ }
++
++ idx++;
++ offset = 0;
++ }
++
++ TRACE_DBG("msg_iov=%p, msg_iovlen=%zd",
++ conn->read_msg.msg_iov, conn->read_msg.msg_iovlen);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void send_r2t(struct iscsi_cmnd *req)
++{
++ struct iscsi_session *sess = req->conn->session;
++ struct iscsi_cmnd *rsp;
++ struct iscsi_r2t_hdr *rsp_hdr;
++ u32 offset, burst;
++ LIST_HEAD(send);
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(req->r2t_len_to_send == 0);
++
++ /*
++ * There is no race with data_out_start() and conn_abort(), since
++ * all functions called from single read thread
++ */
++ iscsi_extracheck_is_rd_thread(req->conn);
++
++ /*
++ * We don't need to check for PRELIM_COMPLETED here, because for such
++ * commands we set r2t_len_to_send = 0, hence made sure we won't be
++ * called here.
++ */
++
++ EXTRACHECKS_BUG_ON(req->outstanding_r2t >
++ sess->sess_params.max_outstanding_r2t);
++
++ if (req->outstanding_r2t == sess->sess_params.max_outstanding_r2t)
++ goto out;
++
++ burst = sess->sess_params.max_burst_length;
++ offset = be32_to_cpu(cmnd_hdr(req)->data_length) -
++ req->r2t_len_to_send;
++
++ do {
++ rsp = iscsi_alloc_rsp(req);
++ rsp->pdu.bhs.ttt = (__force __be32)req->target_task_tag;
++ rsp_hdr = (struct iscsi_r2t_hdr *)&rsp->pdu.bhs;
++ rsp_hdr->opcode = ISCSI_OP_R2T;
++ rsp_hdr->flags = ISCSI_FLG_FINAL;
++ rsp_hdr->lun = cmnd_hdr(req)->lun;
++ rsp_hdr->itt = cmnd_hdr(req)->itt;
++ rsp_hdr->r2t_sn = (__force u32)cpu_to_be32(req->r2t_sn++);
++ rsp_hdr->buffer_offset = cpu_to_be32(offset);
++ if (req->r2t_len_to_send > burst) {
++ rsp_hdr->data_length = cpu_to_be32(burst);
++ req->r2t_len_to_send -= burst;
++ offset += burst;
++ } else {
++ rsp_hdr->data_length = cpu_to_be32(req->r2t_len_to_send);
++ req->r2t_len_to_send = 0;
++ }
++
++ TRACE_WRITE("req %p, data_length %u, buffer_offset %u, "
++ "r2t_sn %u, outstanding_r2t %u", req,
++ be32_to_cpu(rsp_hdr->data_length),
++ be32_to_cpu(rsp_hdr->buffer_offset),
++ be32_to_cpu((__force __be32)rsp_hdr->r2t_sn), req->outstanding_r2t);
++
++ list_add_tail(&rsp->write_list_entry, &send);
++ req->outstanding_r2t++;
++
++ } while ((req->outstanding_r2t < sess->sess_params.max_outstanding_r2t) &&
++ (req->r2t_len_to_send != 0));
++
++ iscsi_cmnds_init_write(&send, ISCSI_INIT_WRITE_WAKE);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int iscsi_pre_exec(struct scst_cmd *scst_cmd)
++{
++ int res = SCST_PREPROCESS_STATUS_SUCCESS;
++ struct iscsi_cmnd *req = (struct iscsi_cmnd *)
++ scst_cmd_get_tgt_priv(scst_cmd);
++ struct iscsi_cmnd *c, *t;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(scst_cmd_atomic(scst_cmd));
++
++ /* If data digest isn't used this list will be empty */
++ list_for_each_entry_safe(c, t, &req->rx_ddigest_cmd_list,
++ rx_ddigest_cmd_list_entry) {
++ TRACE_DBG("Checking digest of RX ddigest cmd %p", c);
++ if (digest_rx_data(c) != 0) {
++ scst_set_cmd_error(scst_cmd,
++ SCST_LOAD_SENSE(iscsi_sense_crc_error));
++ res = SCST_PREPROCESS_STATUS_ERROR_SENSE_SET;
++ /*
++ * The rest of rx_ddigest_cmd_list will be freed
++ * in req_cmnd_release()
++ */
++ goto out;
++ }
++ cmd_del_from_rx_ddigest_list(c);
++ cmnd_put(c);
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int nop_out_start(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++ struct iscsi_hdr *req_hdr = &cmnd->pdu.bhs;
++ u32 size, tmp;
++ int i, err = 0;
++
++ TRACE_DBG("%p", cmnd);
++
++ iscsi_extracheck_is_rd_thread(conn);
++
++ if (!(req_hdr->flags & ISCSI_FLG_FINAL)) {
++ PRINT_ERROR("%s", "Initiator sent Nop-Out with not a single "
++ "PDU");
++ err = -ISCSI_REASON_PROTOCOL_ERROR;
++ goto out;
++ }
++
++ if (cmnd->pdu.bhs.itt == ISCSI_RESERVED_TAG) {
++ if (unlikely(!(cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE)))
++ PRINT_ERROR("%s", "Initiator sent RESERVED tag for "
++ "non-immediate Nop-Out command");
++ }
++
++ update_stat_sn(cmnd);
++
++ size = cmnd->pdu.datasize;
++
++ if (size) {
++ conn->read_msg.msg_iov = conn->read_iov;
++ if (cmnd->pdu.bhs.itt != ISCSI_RESERVED_TAG) {
++ struct scatterlist *sg;
++
++ cmnd->sg = sg = scst_alloc(size, GFP_KERNEL,
++ &cmnd->sg_cnt);
++ if (sg == NULL) {
++ TRACE(TRACE_OUT_OF_MEM, "Allocation of buffer "
++ "for %d Nop-Out payload failed", size);
++ err = -ISCSI_REASON_OUT_OF_RESOURCES;
++ goto out;
++ }
++
++ /* We already checked it in check_segment_length() */
++ BUG_ON(cmnd->sg_cnt > (signed)ISCSI_CONN_IOV_MAX);
++
++ cmnd->own_sg = 1;
++ cmnd->bufflen = size;
++
++ for (i = 0; i < cmnd->sg_cnt; i++) {
++ conn->read_iov[i].iov_base =
++ (void __force __user *)(page_address(sg_page(&sg[i])));
++ tmp = min_t(u32, size, PAGE_SIZE);
++ conn->read_iov[i].iov_len = tmp;
++ conn->read_size += tmp;
++ size -= tmp;
++ }
++ BUG_ON(size != 0);
++ } else {
++ /*
++ * There are no problems with the safety from concurrent
++ * accesses to dummy_page, since for ISCSI_RESERVED_TAG
++ * the data only read and then discarded.
++ */
++ for (i = 0; i < (signed)ISCSI_CONN_IOV_MAX; i++) {
++ conn->read_iov[i].iov_base =
++ (void __force __user *)(page_address(dummy_page));
++ tmp = min_t(u32, size, PAGE_SIZE);
++ conn->read_iov[i].iov_len = tmp;
++ conn->read_size += tmp;
++ size -= tmp;
++ }
++
++ /* We already checked size in check_segment_length() */
++ BUG_ON(size != 0);
++ }
++
++ conn->read_msg.msg_iovlen = i;
++ TRACE_DBG("msg_iov=%p, msg_iovlen=%zd", conn->read_msg.msg_iov,
++ conn->read_msg.msg_iovlen);
++ }
++
++out:
++ return err;
++}
++
++int cmnd_rx_continue(struct iscsi_cmnd *req)
++{
++ struct iscsi_conn *conn = req->conn;
++ struct iscsi_session *session = conn->session;
++ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
++ struct scst_cmd *scst_cmd = req->scst_cmd;
++ scst_data_direction dir;
++ bool unsolicited_data_expected = false;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("scsi command: %x", req_hdr->scb[0]);
++
++ EXTRACHECKS_BUG_ON(req->scst_state != ISCSI_CMD_STATE_AFTER_PREPROC);
++
++ dir = scst_cmd_get_data_direction(scst_cmd);
++
++ /*
++ * Check for preliminary completion here to save R2Ts. For TASK QUEUE
++ * FULL statuses that might be a big performance win.
++ */
++ if (unlikely(scst_cmd_prelim_completed(scst_cmd) ||
++ unlikely(req->prelim_compl_flags != 0))) {
++ /*
++ * If necessary, ISCSI_CMD_ABORTED will be set by
++ * iscsi_xmit_response().
++ */
++ res = iscsi_preliminary_complete(req, req, true);
++ goto trace;
++ }
++
++ /* For prelim completed commands sg & K can be already set! */
++
++ if (dir & SCST_DATA_WRITE) {
++ req->bufflen = scst_cmd_get_write_fields(scst_cmd, &req->sg,
++ &req->sg_cnt);
++ unsolicited_data_expected = !(req_hdr->flags & ISCSI_CMD_FINAL);
++
++ if (unlikely(session->sess_params.initial_r2t &&
++ unsolicited_data_expected)) {
++ PRINT_ERROR("Initiator %s violated negotiated "
++ "parameters: initial R2T is required (ITT %x, "
++ "op %x)", session->initiator_name,
++ req->pdu.bhs.itt, req_hdr->scb[0]);
++ goto out_close;
++ }
++
++ if (unlikely(!session->sess_params.immediate_data &&
++ req->pdu.datasize)) {
++ PRINT_ERROR("Initiator %s violated negotiated "
++ "parameters: forbidden immediate data sent "
++ "(ITT %x, op %x)", session->initiator_name,
++ req->pdu.bhs.itt, req_hdr->scb[0]);
++ goto out_close;
++ }
++
++ if (unlikely(session->sess_params.first_burst_length < req->pdu.datasize)) {
++ PRINT_ERROR("Initiator %s violated negotiated "
++ "parameters: immediate data len (%d) > "
++ "first_burst_length (%d) (ITT %x, op %x)",
++ session->initiator_name,
++ req->pdu.datasize,
++ session->sess_params.first_burst_length,
++ req->pdu.bhs.itt, req_hdr->scb[0]);
++ goto out_close;
++ }
++
++ req->r2t_len_to_receive = be32_to_cpu(req_hdr->data_length) -
++ req->pdu.datasize;
++
++ /*
++ * In case of residual overflow req->r2t_len_to_receive and
++ * req->pdu.datasize might be > req->bufflen
++ */
++
++ res = cmnd_insert_data_wait_hash(req);
++ if (unlikely(res != 0)) {
++ /*
++ * We have to close connection, because otherwise a data
++ * corruption is possible if we allow to receive data
++ * for this request in another request with dublicated
++ * ITT.
++ */
++ goto out_close;
++ }
++
++ if (unsolicited_data_expected) {
++ req->outstanding_r2t = 1;
++ req->r2t_len_to_send = req->r2t_len_to_receive -
++ min_t(unsigned int,
++ session->sess_params.first_burst_length -
++ req->pdu.datasize,
++ req->r2t_len_to_receive);
++ } else
++ req->r2t_len_to_send = req->r2t_len_to_receive;
++
++ req_add_to_write_timeout_list(req);
++
++ if (req->pdu.datasize) {
++ res = cmnd_prepare_recv_pdu(conn, req, 0, req->pdu.datasize);
++ /* For performance better to send R2Ts ASAP */
++ if (likely(res == 0) && (req->r2t_len_to_send != 0))
++ send_r2t(req);
++ }
++ } else {
++ req->sg = scst_cmd_get_sg(scst_cmd);
++ req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
++ req->bufflen = scst_cmd_get_bufflen(scst_cmd);
++
++ if (unlikely(!(req_hdr->flags & ISCSI_CMD_FINAL) ||
++ req->pdu.datasize)) {
++ PRINT_ERROR("Unexpected unsolicited data (ITT %x "
++ "CDB %x)", req->pdu.bhs.itt, req_hdr->scb[0]);
++ set_scst_preliminary_status_rsp(req, true,
++ SCST_LOAD_SENSE(iscsi_sense_unexpected_unsolicited_data));
++ }
++ }
++
++trace:
++ TRACE_DBG("req=%p, dir=%d, unsolicited_data_expected=%d, "
++ "r2t_len_to_receive=%d, r2t_len_to_send=%d, bufflen=%d, "
++ "own_sg %d", req, dir, unsolicited_data_expected,
++ req->r2t_len_to_receive, req->r2t_len_to_send, req->bufflen,
++ req->own_sg);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_close:
++ mark_conn_closed(conn);
++ res = -EINVAL;
++ goto out;
++}
++
++static int scsi_cmnd_start(struct iscsi_cmnd *req)
++{
++ struct iscsi_conn *conn = req->conn;
++ struct iscsi_session *session = conn->session;
++ struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
++ struct scst_cmd *scst_cmd;
++ scst_data_direction dir;
++ struct iscsi_ahs_hdr *ahdr;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("scsi command: %x", req_hdr->scb[0]);
++
++ TRACE_DBG("Incrementing active_cmds (cmd %p, sess %p, "
++ "new value %d)", req, session,
++ atomic_read(&session->active_cmds)+1);
++ atomic_inc(&session->active_cmds);
++ req->dec_active_cmds = 1;
++
++ scst_cmd = scst_rx_cmd(session->scst_sess,
++ (uint8_t *)&req_hdr->lun, sizeof(req_hdr->lun),
++ req_hdr->scb, sizeof(req_hdr->scb), SCST_NON_ATOMIC);
++ if (scst_cmd == NULL) {
++ res = create_preliminary_no_scst_rsp(req, SAM_STAT_BUSY,
++ NULL, 0);
++ goto out;
++ }
++
++ req->scst_cmd = scst_cmd;
++ scst_cmd_set_tag(scst_cmd, (__force u32)req_hdr->itt);
++ scst_cmd_set_tgt_priv(scst_cmd, req);
++
++ if ((req_hdr->flags & ISCSI_CMD_READ) &&
++ (req_hdr->flags & ISCSI_CMD_WRITE)) {
++ int sz = cmnd_read_size(req);
++ if (unlikely(sz < 0)) {
++ PRINT_ERROR("%s", "BIDI data transfer, but initiator "
++ "not supplied Bidirectional Read Expected Data "
++ "Transfer Length AHS");
++ set_scst_preliminary_status_rsp(req, true,
++ SCST_LOAD_SENSE(scst_sense_parameter_value_invalid));
++ } else {
++ dir = SCST_DATA_BIDI;
++ scst_cmd_set_expected(scst_cmd, dir, sz);
++ scst_cmd_set_expected_out_transfer_len(scst_cmd,
++ be32_to_cpu(req_hdr->data_length));
++#if !defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ scst_cmd_set_tgt_need_alloc_data_buf(scst_cmd);
++#endif
++ }
++ } else if (req_hdr->flags & ISCSI_CMD_READ) {
++ dir = SCST_DATA_READ;
++ scst_cmd_set_expected(scst_cmd, dir,
++ be32_to_cpu(req_hdr->data_length));
++#if !defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ scst_cmd_set_tgt_need_alloc_data_buf(scst_cmd);
++#endif
++ } else if (req_hdr->flags & ISCSI_CMD_WRITE) {
++ dir = SCST_DATA_WRITE;
++ scst_cmd_set_expected(scst_cmd, dir,
++ be32_to_cpu(req_hdr->data_length));
++ } else {
++ dir = SCST_DATA_NONE;
++ scst_cmd_set_expected(scst_cmd, dir, 0);
++ }
++
++ switch (req_hdr->flags & ISCSI_CMD_ATTR_MASK) {
++ case ISCSI_CMD_SIMPLE:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_SIMPLE);
++ break;
++ case ISCSI_CMD_HEAD_OF_QUEUE:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE);
++ break;
++ case ISCSI_CMD_ORDERED:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ case ISCSI_CMD_ACA:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ACA);
++ break;
++ case ISCSI_CMD_UNTAGGED:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_UNTAGGED);
++ break;
++ default:
++ PRINT_ERROR("Unknown task code %x, use ORDERED instead",
++ req_hdr->flags & ISCSI_CMD_ATTR_MASK);
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ }
++
++ scst_cmd_set_tgt_sn(scst_cmd, req_hdr->cmd_sn);
++
++ ahdr = (struct iscsi_ahs_hdr *)req->pdu.ahs;
++ if (ahdr != NULL) {
++ uint8_t *p = (uint8_t *)ahdr;
++ unsigned int size = 0;
++ do {
++ int s;
++
++ ahdr = (struct iscsi_ahs_hdr *)p;
++
++ if (ahdr->ahstype == ISCSI_AHSTYPE_CDB) {
++ struct iscsi_cdb_ahdr *eca =
++ (struct iscsi_cdb_ahdr *)ahdr;
++ scst_cmd_set_ext_cdb(scst_cmd, eca->cdb,
++ be16_to_cpu(ahdr->ahslength) - 1,
++ GFP_KERNEL);
++ break;
++ }
++ s = 3 + be16_to_cpu(ahdr->ahslength);
++ s = (s + 3) & -4;
++ size += s;
++ p += s;
++ } while (size < req->pdu.ahssize);
++ }
++
++ TRACE_DBG("START Command (itt %x, queue_type %d)",
++ req_hdr->itt, scst_cmd_get_queue_type(scst_cmd));
++ req->scst_state = ISCSI_CMD_STATE_RX_CMD;
++ conn->rx_task = current;
++ scst_cmd_init_stage1_done(scst_cmd, SCST_CONTEXT_DIRECT, 0);
++
++ if (req->scst_state != ISCSI_CMD_STATE_RX_CMD)
++ res = cmnd_rx_continue(req);
++ else {
++ TRACE_DBG("Delaying req %p post processing (scst_state %d)",
++ req, req->scst_state);
++ res = 1;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int data_out_start(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++ struct iscsi_data_out_hdr *req_hdr =
++ (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
++ struct iscsi_cmnd *orig_req;
++#if 0
++ struct iscsi_hdr *orig_req_hdr;
++#endif
++ u32 offset = be32_to_cpu(req_hdr->buffer_offset);
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ /*
++ * There is no race with send_r2t(), conn_abort() and
++ * iscsi_check_tm_data_wait_timeouts(), since
++ * all the functions called from single read thread
++ */
++ iscsi_extracheck_is_rd_thread(cmnd->conn);
++
++ update_stat_sn(cmnd);
++
++ orig_req = cmnd_find_data_wait_hash(conn, req_hdr->itt);
++ cmnd->cmd_req = orig_req;
++ if (unlikely(orig_req == NULL)) {
++ /*
++ * It shouldn't happen, since we don't abort any request until
++ * we received all related PDUs from the initiator or timeout
++ * them. Let's quietly drop such PDUs.
++ */
++ TRACE_MGMT_DBG("Unable to find scsi task ITT %x",
++ cmnd->pdu.bhs.itt);
++ res = iscsi_preliminary_complete(cmnd, cmnd, true);
++ goto out;
++ }
++
++ cmnd_get(orig_req);
++
++ if (unlikely(orig_req->r2t_len_to_receive < cmnd->pdu.datasize)) {
++ if (orig_req->prelim_compl_flags != 0) {
++ /* We can have fake r2t_len_to_receive */
++ goto go;
++ }
++ PRINT_ERROR("Data size (%d) > R2T length to receive (%d)",
++ cmnd->pdu.datasize, orig_req->r2t_len_to_receive);
++ set_scst_preliminary_status_rsp(orig_req, false,
++ SCST_LOAD_SENSE(iscsi_sense_incorrect_amount_of_data));
++ goto go;
++ }
++
++ /* Crazy iSCSI spec requires us to make this unneeded check */
++#if 0 /* ...but some initiators (Windows) don't care to correctly set it */
++ orig_req_hdr = &orig_req->pdu.bhs;
++ if (unlikely(orig_req_hdr->lun != req_hdr->lun)) {
++ PRINT_ERROR("Wrong LUN (%lld) in Data-Out PDU (expected %lld), "
++ "orig_req %p, cmnd %p", (unsigned long long)req_hdr->lun,
++ (unsigned long long)orig_req_hdr->lun, orig_req, cmnd);
++ create_reject_rsp(orig_req, ISCSI_REASON_PROTOCOL_ERROR, false);
++ goto go;
++ }
++#endif
++
++go:
++ if (req_hdr->flags & ISCSI_FLG_FINAL)
++ orig_req->outstanding_r2t--;
++
++ EXTRACHECKS_BUG_ON(orig_req->data_out_in_data_receiving);
++ orig_req->data_out_in_data_receiving = 1;
++
++ TRACE_WRITE("cmnd %p, orig_req %p, offset %u, datasize %u", cmnd,
++ orig_req, offset, cmnd->pdu.datasize);
++
++ if (unlikely(orig_req->prelim_compl_flags != 0))
++ res = iscsi_preliminary_complete(cmnd, orig_req, true);
++ else
++ res = cmnd_prepare_recv_pdu(conn, orig_req, offset, cmnd->pdu.datasize);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static void data_out_end(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_data_out_hdr *req_hdr =
++ (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
++ struct iscsi_cmnd *req;
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_BUG_ON(cmnd == NULL);
++ req = cmnd->cmd_req;
++ if (unlikely(req == NULL))
++ goto out;
++
++ TRACE_DBG("cmnd %p, req %p", cmnd, req);
++
++ iscsi_extracheck_is_rd_thread(cmnd->conn);
++
++ req->data_out_in_data_receiving = 0;
++
++ if (!(cmnd->conn->ddigest_type & DIGEST_NONE) &&
++ !cmnd->ddigest_checked) {
++ cmd_add_on_rx_ddigest_list(req, cmnd);
++ cmnd_get(cmnd);
++ }
++
++ /*
++ * Now we received the data and can adjust r2t_len_to_receive of the
++ * orig req. We couldn't do it earlier, because it will break data
++ * receiving errors recovery (calls of iscsi_fail_data_waiting_cmnd()).
++ */
++ req->r2t_len_to_receive -= cmnd->pdu.datasize;
++
++ if (unlikely(req->prelim_compl_flags != 0)) {
++ /*
++ * We need to call iscsi_preliminary_complete() again
++ * to handle the case if we just been aborted. This call must
++ * be done before zeroing r2t_len_to_send to correctly calc.
++ * residual.
++ */
++ iscsi_preliminary_complete(cmnd, req, false);
++
++ /*
++ * We might need to wait for one or more PDUs. Let's simplify
++ * other code and not perform exact r2t_len_to_receive
++ * calculation.
++ */
++ req->r2t_len_to_receive = req->outstanding_r2t;
++ req->r2t_len_to_send = 0;
++ }
++
++ TRACE_DBG("req %p, FINAL %x, outstanding_r2t %d, r2t_len_to_receive %d,"
++ " r2t_len_to_send %d", req, req_hdr->flags & ISCSI_FLG_FINAL,
++ req->outstanding_r2t, req->r2t_len_to_receive,
++ req->r2t_len_to_send);
++
++ if (!(req_hdr->flags & ISCSI_FLG_FINAL))
++ goto out_put;
++
++ if (req->r2t_len_to_receive == 0) {
++ if (!req->pending)
++ iscsi_restart_cmnd(req);
++ } else if (req->r2t_len_to_send != 0)
++ send_r2t(req);
++
++out_put:
++ cmnd_put(req);
++ cmnd->cmd_req = NULL;
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Might be called under target_mutex and cmd_list_lock */
++static void __cmnd_abort(struct iscsi_cmnd *cmnd)
++{
++ unsigned long timeout_time = jiffies + ISCSI_TM_DATA_WAIT_TIMEOUT +
++ ISCSI_ADD_SCHED_TIME;
++ struct iscsi_conn *conn = cmnd->conn;
++
++ TRACE_MGMT_DBG("Aborting cmd %p, scst_cmd %p (scst state %x, "
++ "ref_cnt %d, on_write_timeout_list %d, write_start %ld, ITT %x, "
++ "sn %u, op %x, r2t_len_to_receive %d, r2t_len_to_send %d, "
++ "CDB op %x, size to write %u, outstanding_r2t %d, "
++ "sess->exp_cmd_sn %u, conn %p, rd_task %p, read_cmnd %p, "
++ "read_state %d)", cmnd, cmnd->scst_cmd, cmnd->scst_state,
++ atomic_read(&cmnd->ref_cnt), cmnd->on_write_timeout_list,
++ cmnd->write_start, cmnd->pdu.bhs.itt, cmnd->pdu.bhs.sn,
++ cmnd_opcode(cmnd), cmnd->r2t_len_to_receive,
++ cmnd->r2t_len_to_send, cmnd_scsicode(cmnd),
++ cmnd_write_size(cmnd), cmnd->outstanding_r2t,
++ cmnd->conn->session->exp_cmd_sn, cmnd->conn,
++ cmnd->conn->rd_task, cmnd->conn->read_cmnd,
++ cmnd->conn->read_state);
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ TRACE_MGMT_DBG("net_ref_cnt %d", atomic_read(&cmnd->net_ref_cnt));
++#endif
++
++ /*
++ * Lock to sync with iscsi_check_tm_data_wait_timeouts(), including
++ * CMD_ABORTED bit set.
++ */
++ spin_lock_bh(&conn->conn_thr_pool->rd_lock);
++
++ /*
++ * We suppose that preliminary commands completion is tested by
++ * comparing prelim_compl_flags with 0. Otherwise a race is possible,
++ * like sending command in SCST core as PRELIM_COMPLETED, while it
++ * wasn't aborted in it yet and have as the result a wrong success
++ * status sent to the initiator.
++ */
++ set_bit(ISCSI_CMD_ABORTED, &cmnd->prelim_compl_flags);
++
++ TRACE_MGMT_DBG("Setting conn_tm_active for conn %p", conn);
++ conn->conn_tm_active = 1;
++
++ spin_unlock_bh(&conn->conn_thr_pool->rd_lock);
++
++ /*
++ * We need the lock to sync with req_add_to_write_timeout_list() and
++ * close races for rsp_timer.expires.
++ */
++ spin_lock_bh(&conn->write_list_lock);
++ if (!timer_pending(&conn->rsp_timer) ||
++ time_after(conn->rsp_timer.expires, timeout_time)) {
++ TRACE_MGMT_DBG("Mod timer on %ld (conn %p)", timeout_time,
++ conn);
++ mod_timer(&conn->rsp_timer, timeout_time);
++ } else
++ TRACE_MGMT_DBG("Timer for conn %p is going to fire on %ld "
++ "(timeout time %ld)", conn, conn->rsp_timer.expires,
++ timeout_time);
++ spin_unlock_bh(&conn->write_list_lock);
++
++ return;
++}
++
++/* Must be called from the read or conn close thread */
++static int cmnd_abort_pre_checks(struct iscsi_cmnd *req, int *status)
++{
++ struct iscsi_task_mgt_hdr *req_hdr =
++ (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
++ struct iscsi_cmnd *cmnd;
++ int res = -1;
++
++ req_hdr->ref_cmd_sn = be32_to_cpu((__force __be32)req_hdr->ref_cmd_sn);
++
++ if (!before(req_hdr->ref_cmd_sn, req_hdr->cmd_sn)) {
++ TRACE(TRACE_MGMT, "ABORT TASK: RefCmdSN(%u) > CmdSN(%u)",
++ req_hdr->ref_cmd_sn, req_hdr->cmd_sn);
++ *status = ISCSI_RESPONSE_UNKNOWN_TASK;
++ goto out;
++ }
++
++ cmnd = cmnd_find_itt_get(req->conn, req_hdr->rtt);
++ if (cmnd) {
++ struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);
++
++ if (req_hdr->lun != hdr->lun) {
++ PRINT_ERROR("ABORT TASK: LUN mismatch: req LUN "
++ "%llx, cmd LUN %llx, rtt %u",
++ (long long unsigned)be64_to_cpu(req_hdr->lun),
++ (long long unsigned)be64_to_cpu(hdr->lun),
++ req_hdr->rtt);
++ *status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ goto out_put;
++ }
++
++ if (cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) {
++ if (req_hdr->ref_cmd_sn != req_hdr->cmd_sn) {
++ PRINT_ERROR("ABORT TASK: RefCmdSN(%u) != TM "
++ "cmd CmdSN(%u) for immediate command "
++ "%p", req_hdr->ref_cmd_sn,
++ req_hdr->cmd_sn, cmnd);
++ *status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ goto out_put;
++ }
++ } else {
++ if (req_hdr->ref_cmd_sn != hdr->cmd_sn) {
++ PRINT_ERROR("ABORT TASK: RefCmdSN(%u) != "
++ "CmdSN(%u) for command %p",
++ req_hdr->ref_cmd_sn, req_hdr->cmd_sn,
++ cmnd);
++ *status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ goto out_put;
++ }
++ }
++
++ if (before(req_hdr->cmd_sn, hdr->cmd_sn) ||
++ (req_hdr->cmd_sn == hdr->cmd_sn)) {
++ PRINT_ERROR("ABORT TASK: SN mismatch: req SN %x, "
++ "cmd SN %x, rtt %u", req_hdr->cmd_sn,
++ hdr->cmd_sn, req_hdr->rtt);
++ *status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ goto out_put;
++ }
++
++ cmnd_put(cmnd);
++ res = 0;
++ } else {
++ TRACE_MGMT_DBG("cmd RTT %x not found", req_hdr->rtt);
++ /*
++ * iSCSI RFC:
++ *
++ * b) If the Referenced Task Tag does not identify an existing task,
++ * but if the CmdSN indicated by the RefCmdSN field in the Task
++ * Management function request is within the valid CmdSN window
++ * and less than the CmdSN of the Task Management function
++ * request itself, then targets must consider the CmdSN received
++ * and return the "Function complete" response.
++ *
++ * c) If the Referenced Task Tag does not identify an existing task
++ * and if the CmdSN indicated by the RefCmdSN field in the Task
++ * Management function request is outside the valid CmdSN window,
++ * then targets must return the "Task does not exist" response.
++ *
++ * 128 seems to be a good "window".
++ */
++ if (between(req_hdr->ref_cmd_sn, req_hdr->cmd_sn - 128,
++ req_hdr->cmd_sn)) {
++ *status = ISCSI_RESPONSE_FUNCTION_COMPLETE;
++ res = 0;
++ } else
++ *status = ISCSI_RESPONSE_UNKNOWN_TASK;
++ }
++
++out:
++ return res;
++
++out_put:
++ cmnd_put(cmnd);
++ goto out;
++}
++
++struct iscsi_cmnd_abort_params {
++ struct work_struct iscsi_cmnd_abort_work;
++ struct scst_cmd *scst_cmd;
++};
++
++static mempool_t *iscsi_cmnd_abort_mempool;
++
++static void iscsi_cmnd_abort_fn(struct work_struct *work)
++{
++ struct iscsi_cmnd_abort_params *params = container_of(work,
++ struct iscsi_cmnd_abort_params, iscsi_cmnd_abort_work);
++ struct scst_cmd *scst_cmd = params->scst_cmd;
++ struct iscsi_session *session = scst_sess_get_tgt_priv(scst_cmd->sess);
++ struct iscsi_conn *conn;
++ struct iscsi_cmnd *cmnd = scst_cmd_get_tgt_priv(scst_cmd);
++ bool done = false;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Checking aborted scst_cmd %p (cmnd %p)", scst_cmd, cmnd);
++
++ mutex_lock(&session->target->target_mutex);
++
++ /*
++ * cmnd pointer is valid only under cmd_list_lock, but we can't know the
++ * corresponding conn without dereferencing cmnd at first, so let's
++ * check all conns and cmnds to find out if our cmnd is still valid
++ * under lock.
++ */
++ list_for_each_entry(conn, &session->conn_list, conn_list_entry) {
++ struct iscsi_cmnd *c;
++ spin_lock_bh(&conn->cmd_list_lock);
++ list_for_each_entry(c, &conn->cmd_list, cmd_list_entry) {
++ if (c == cmnd) {
++ __cmnd_abort(cmnd);
++ done = true;
++ break;
++ }
++ }
++ spin_unlock_bh(&conn->cmd_list_lock);
++ if (done)
++ break;
++ }
++
++ mutex_unlock(&session->target->target_mutex);
++
++ scst_cmd_put(scst_cmd);
++
++ mempool_free(params, iscsi_cmnd_abort_mempool);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void iscsi_on_abort_cmd(struct scst_cmd *scst_cmd)
++{
++ struct iscsi_cmnd_abort_params *params;
++
++ TRACE_ENTRY();
++
++ params = mempool_alloc(iscsi_cmnd_abort_mempool, GFP_ATOMIC);
++ if (params == NULL) {
++ PRINT_CRIT_ERROR("Unable to create iscsi_cmnd_abort_params, "
++ "iSCSI cmnd for scst_cmd %p may not be aborted",
++ scst_cmd);
++ goto out;
++ }
++
++ memset(params, 0, sizeof(*params));
++ INIT_WORK(&params->iscsi_cmnd_abort_work, iscsi_cmnd_abort_fn);
++ params->scst_cmd = scst_cmd;
++
++ scst_cmd_get(scst_cmd);
++
++ TRACE_MGMT_DBG("Scheduling abort check for scst_cmd %p", scst_cmd);
++
++ schedule_work(&params->iscsi_cmnd_abort_work);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called from the read or conn close thread */
++void conn_abort(struct iscsi_conn *conn)
++{
++ struct iscsi_cmnd *cmnd, *r, *t;
++
++ TRACE_MGMT_DBG("Aborting conn %p", conn);
++
++ iscsi_extracheck_is_rd_thread(conn);
++
++ cancel_delayed_work_sync(&conn->nop_in_delayed_work);
++
++ /* No locks, we are the only user */
++ list_for_each_entry_safe(r, t, &conn->nop_req_list,
++ nop_req_list_entry) {
++ list_del(&r->nop_req_list_entry);
++ cmnd_put(r);
++ }
++
++ spin_lock_bh(&conn->cmd_list_lock);
++again:
++ list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) {
++ __cmnd_abort(cmnd);
++ if (cmnd->r2t_len_to_receive != 0) {
++ if (!cmnd_get_check(cmnd)) {
++ spin_unlock_bh(&conn->cmd_list_lock);
++
++ /* ToDo: this is racy for MC/S */
++ iscsi_fail_data_waiting_cmnd(cmnd);
++
++ cmnd_put(cmnd);
++
++ /*
++ * We are in the read thread, so we may not
++ * worry that after cmnd release conn gets
++ * released as well.
++ */
++ spin_lock_bh(&conn->cmd_list_lock);
++ goto again;
++ }
++ }
++ }
++ spin_unlock_bh(&conn->cmd_list_lock);
++
++ return;
++}
++
++static void execute_task_management(struct iscsi_cmnd *req)
++{
++ struct iscsi_conn *conn = req->conn;
++ struct iscsi_session *sess = conn->session;
++ struct iscsi_task_mgt_hdr *req_hdr =
++ (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
++ int rc, status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ int function = req_hdr->function & ISCSI_FUNCTION_MASK;
++ struct scst_rx_mgmt_params params;
++
++ TRACE(TRACE_MGMT, "iSCSI TM fn %d", function);
++
++ TRACE_MGMT_DBG("TM req %p, ITT %x, RTT %x, sn %u, con %p", req,
++ req->pdu.bhs.itt, req_hdr->rtt, req_hdr->cmd_sn, conn);
++
++ iscsi_extracheck_is_rd_thread(conn);
++
++ spin_lock(&sess->sn_lock);
++ sess->tm_active++;
++ sess->tm_sn = req_hdr->cmd_sn;
++ if (sess->tm_rsp != NULL) {
++ struct iscsi_cmnd *tm_rsp = sess->tm_rsp;
++
++ TRACE_MGMT_DBG("Dropping delayed TM rsp %p", tm_rsp);
++
++ sess->tm_rsp = NULL;
++ sess->tm_active--;
++
++ spin_unlock(&sess->sn_lock);
++
++ BUG_ON(sess->tm_active < 0);
++
++ rsp_cmnd_release(tm_rsp);
++ } else
++ spin_unlock(&sess->sn_lock);
++
++ memset(&params, 0, sizeof(params));
++ params.atomic = SCST_NON_ATOMIC;
++ params.tgt_priv = req;
++
++ if ((function != ISCSI_FUNCTION_ABORT_TASK) &&
++ (req_hdr->rtt != ISCSI_RESERVED_TAG)) {
++ PRINT_ERROR("Invalid RTT %x (TM fn %d)", req_hdr->rtt,
++ function);
++ rc = -1;
++ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ goto reject;
++ }
++
++ /* cmd_sn is already in CPU format converted in cmnd_rx_start() */
++
++ switch (function) {
++ case ISCSI_FUNCTION_ABORT_TASK:
++ rc = cmnd_abort_pre_checks(req, &status);
++ if (rc == 0) {
++ params.fn = SCST_ABORT_TASK;
++ params.tag = (__force u32)req_hdr->rtt;
++ params.tag_set = 1;
++ params.lun = (uint8_t *)&req_hdr->lun;
++ params.lun_len = sizeof(req_hdr->lun);
++ params.lun_set = 1;
++ params.cmd_sn = req_hdr->cmd_sn;
++ params.cmd_sn_set = 1;
++ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
++ &params);
++ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ }
++ break;
++ case ISCSI_FUNCTION_ABORT_TASK_SET:
++ params.fn = SCST_ABORT_TASK_SET;
++ params.lun = (uint8_t *)&req_hdr->lun;
++ params.lun_len = sizeof(req_hdr->lun);
++ params.lun_set = 1;
++ params.cmd_sn = req_hdr->cmd_sn;
++ params.cmd_sn_set = 1;
++ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
++ &params);
++ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ break;
++ case ISCSI_FUNCTION_CLEAR_TASK_SET:
++ params.fn = SCST_CLEAR_TASK_SET;
++ params.lun = (uint8_t *)&req_hdr->lun;
++ params.lun_len = sizeof(req_hdr->lun);
++ params.lun_set = 1;
++ params.cmd_sn = req_hdr->cmd_sn;
++ params.cmd_sn_set = 1;
++ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
++ &params);
++ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ break;
++ case ISCSI_FUNCTION_CLEAR_ACA:
++ params.fn = SCST_CLEAR_ACA;
++ params.lun = (uint8_t *)&req_hdr->lun;
++ params.lun_len = sizeof(req_hdr->lun);
++ params.lun_set = 1;
++ params.cmd_sn = req_hdr->cmd_sn;
++ params.cmd_sn_set = 1;
++ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
++ &params);
++ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ break;
++ case ISCSI_FUNCTION_TARGET_COLD_RESET:
++ case ISCSI_FUNCTION_TARGET_WARM_RESET:
++ params.fn = SCST_TARGET_RESET;
++ params.cmd_sn = req_hdr->cmd_sn;
++ params.cmd_sn_set = 1;
++ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
++ &params);
++ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ break;
++ case ISCSI_FUNCTION_LOGICAL_UNIT_RESET:
++ params.fn = SCST_LUN_RESET;
++ params.lun = (uint8_t *)&req_hdr->lun;
++ params.lun_len = sizeof(req_hdr->lun);
++ params.lun_set = 1;
++ params.cmd_sn = req_hdr->cmd_sn;
++ params.cmd_sn_set = 1;
++ rc = scst_rx_mgmt_fn(conn->session->scst_sess,
++ &params);
++ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ break;
++ case ISCSI_FUNCTION_TASK_REASSIGN:
++ rc = -1;
++ status = ISCSI_RESPONSE_ALLEGIANCE_REASSIGNMENT_UNSUPPORTED;
++ break;
++ default:
++ PRINT_ERROR("Unknown TM function %d", function);
++ rc = -1;
++ status = ISCSI_RESPONSE_FUNCTION_REJECTED;
++ break;
++ }
++
++reject:
++ if (rc != 0)
++ iscsi_send_task_mgmt_resp(req, status);
++
++ return;
++}
++
++static void nop_out_exec(struct iscsi_cmnd *req)
++{
++ struct iscsi_cmnd *rsp;
++ struct iscsi_nop_in_hdr *rsp_hdr;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("%p", req);
++
++ if (req->pdu.bhs.itt != ISCSI_RESERVED_TAG) {
++ rsp = iscsi_alloc_main_rsp(req);
++
++ rsp_hdr = (struct iscsi_nop_in_hdr *)&rsp->pdu.bhs;
++ rsp_hdr->opcode = ISCSI_OP_NOP_IN;
++ rsp_hdr->flags = ISCSI_FLG_FINAL;
++ rsp_hdr->itt = req->pdu.bhs.itt;
++ rsp_hdr->ttt = ISCSI_RESERVED_TAG;
++
++ if (req->pdu.datasize)
++ BUG_ON(req->sg == NULL);
++ else
++ BUG_ON(req->sg != NULL);
++
++ if (req->sg) {
++ rsp->sg = req->sg;
++ rsp->sg_cnt = req->sg_cnt;
++ rsp->bufflen = req->bufflen;
++ }
++
++ /* We already checked it in check_segment_length() */
++ BUG_ON(get_pgcnt(req->pdu.datasize, 0) > ISCSI_CONN_IOV_MAX);
++
++ rsp->pdu.datasize = req->pdu.datasize;
++ } else {
++ bool found = false;
++ struct iscsi_cmnd *r;
++ struct iscsi_conn *conn = req->conn;
++
++ TRACE_DBG("Receive Nop-In response (ttt 0x%08x)",
++ be32_to_cpu(req->pdu.bhs.ttt));
++
++ spin_lock_bh(&conn->nop_req_list_lock);
++ list_for_each_entry(r, &conn->nop_req_list,
++ nop_req_list_entry) {
++ if (req->pdu.bhs.ttt == r->pdu.bhs.ttt) {
++ list_del(&r->nop_req_list_entry);
++ found = true;
++ break;
++ }
++ }
++ spin_unlock_bh(&conn->nop_req_list_lock);
++
++ if (found)
++ cmnd_put(r);
++ else
++ TRACE_MGMT_DBG("%s", "Got Nop-out response without "
++ "corresponding Nop-In request");
++ }
++
++ req_cmnd_release(req);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void logout_exec(struct iscsi_cmnd *req)
++{
++ struct iscsi_logout_req_hdr *req_hdr;
++ struct iscsi_cmnd *rsp;
++ struct iscsi_logout_rsp_hdr *rsp_hdr;
++
++ PRINT_INFO("Logout received from initiator %s",
++ req->conn->session->initiator_name);
++ TRACE_DBG("%p", req);
++
++ req_hdr = (struct iscsi_logout_req_hdr *)&req->pdu.bhs;
++ rsp = iscsi_alloc_main_rsp(req);
++ rsp_hdr = (struct iscsi_logout_rsp_hdr *)&rsp->pdu.bhs;
++ rsp_hdr->opcode = ISCSI_OP_LOGOUT_RSP;
++ rsp_hdr->flags = ISCSI_FLG_FINAL;
++ rsp_hdr->itt = req_hdr->itt;
++ rsp->should_close_conn = 1;
++
++ req_cmnd_release(req);
++
++ return;
++}
++
++static void iscsi_cmnd_exec(struct iscsi_cmnd *cmnd)
++{
++ TRACE_ENTRY();
++
++ TRACE_DBG("cmnd %p, op %x, SN %u", cmnd, cmnd_opcode(cmnd),
++ cmnd->pdu.bhs.sn);
++
++ iscsi_extracheck_is_rd_thread(cmnd->conn);
++
++ if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_CMD) {
++ if (cmnd->r2t_len_to_receive == 0)
++ iscsi_restart_cmnd(cmnd);
++ else if (cmnd->r2t_len_to_send != 0)
++ send_r2t(cmnd);
++ goto out;
++ }
++
++ if (cmnd->prelim_compl_flags != 0) {
++ TRACE_MGMT_DBG("Terminating prelim completed non-SCSI cmnd %p "
++ "(op %x)", cmnd, cmnd_opcode(cmnd));
++ req_cmnd_release(cmnd);
++ goto out;
++ }
++
++ switch (cmnd_opcode(cmnd)) {
++ case ISCSI_OP_NOP_OUT:
++ nop_out_exec(cmnd);
++ break;
++ case ISCSI_OP_SCSI_TASK_MGT_MSG:
++ execute_task_management(cmnd);
++ break;
++ case ISCSI_OP_LOGOUT_CMD:
++ logout_exec(cmnd);
++ break;
++ default:
++ PRINT_CRIT_ERROR("Unexpected cmnd op %x", cmnd_opcode(cmnd));
++ BUG();
++ break;
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void set_cork(struct socket *sock, int on)
++{
++ int opt = on;
++ mm_segment_t oldfs;
++
++ oldfs = get_fs();
++ set_fs(get_ds());
++ sock->ops->setsockopt(sock, SOL_TCP, TCP_CORK,
++ (void __force __user *)&opt, sizeof(opt));
++ set_fs(oldfs);
++ return;
++}
++
++void cmnd_tx_start(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++
++ TRACE_DBG("conn %p, cmnd %p, opcode %x", conn, cmnd, cmnd_opcode(cmnd));
++ iscsi_cmnd_set_length(&cmnd->pdu);
++
++ iscsi_extracheck_is_wr_thread(conn);
++
++ set_cork(conn->sock, 1);
++
++ conn->write_iop = conn->write_iov;
++ conn->write_iop->iov_base = (void __force __user *)(&cmnd->pdu.bhs);
++ conn->write_iop->iov_len = sizeof(cmnd->pdu.bhs);
++ conn->write_iop_used = 1;
++ conn->write_size = sizeof(cmnd->pdu.bhs) + cmnd->pdu.datasize;
++ conn->write_offset = 0;
++
++ switch (cmnd_opcode(cmnd)) {
++ case ISCSI_OP_NOP_IN:
++ if (cmnd->pdu.bhs.itt == ISCSI_RESERVED_TAG)
++ cmnd->pdu.bhs.sn = (__force u32)cmnd_set_sn(cmnd, 0);
++ else
++ cmnd_set_sn(cmnd, 1);
++ break;
++ case ISCSI_OP_SCSI_RSP:
++ cmnd_set_sn(cmnd, 1);
++ break;
++ case ISCSI_OP_SCSI_TASK_MGT_RSP:
++ cmnd_set_sn(cmnd, 1);
++ break;
++ case ISCSI_OP_TEXT_RSP:
++ cmnd_set_sn(cmnd, 1);
++ break;
++ case ISCSI_OP_SCSI_DATA_IN:
++ {
++ struct iscsi_data_in_hdr *rsp =
++ (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs;
++ u32 offset = be32_to_cpu(rsp->buffer_offset);
++
++ TRACE_DBG("cmnd %p, offset %u, datasize %u, bufflen %u", cmnd,
++ offset, cmnd->pdu.datasize, cmnd->bufflen);
++
++ BUG_ON(offset > cmnd->bufflen);
++ BUG_ON(offset + cmnd->pdu.datasize > cmnd->bufflen);
++
++ conn->write_offset = offset;
++
++ cmnd_set_sn(cmnd, (rsp->flags & ISCSI_FLG_FINAL) ? 1 : 0);
++ break;
++ }
++ case ISCSI_OP_LOGOUT_RSP:
++ cmnd_set_sn(cmnd, 1);
++ break;
++ case ISCSI_OP_R2T:
++ cmnd->pdu.bhs.sn = (__force u32)cmnd_set_sn(cmnd, 0);
++ break;
++ case ISCSI_OP_ASYNC_MSG:
++ cmnd_set_sn(cmnd, 1);
++ break;
++ case ISCSI_OP_REJECT:
++ cmnd_set_sn(cmnd, 1);
++ break;
++ default:
++ PRINT_ERROR("Unexpected cmnd op %x", cmnd_opcode(cmnd));
++ break;
++ }
++
++ iscsi_dump_pdu(&cmnd->pdu);
++ return;
++}
++
++void cmnd_tx_end(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++
++ TRACE_DBG("%p:%x (should_close_conn %d, should_close_all_conn %d)",
++ cmnd, cmnd_opcode(cmnd), cmnd->should_close_conn,
++ cmnd->should_close_all_conn);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ switch (cmnd_opcode(cmnd)) {
++ case ISCSI_OP_NOP_IN:
++ case ISCSI_OP_SCSI_RSP:
++ case ISCSI_OP_SCSI_TASK_MGT_RSP:
++ case ISCSI_OP_TEXT_RSP:
++ case ISCSI_OP_R2T:
++ case ISCSI_OP_ASYNC_MSG:
++ case ISCSI_OP_REJECT:
++ case ISCSI_OP_SCSI_DATA_IN:
++ case ISCSI_OP_LOGOUT_RSP:
++ break;
++ default:
++ PRINT_CRIT_ERROR("unexpected cmnd op %x", cmnd_opcode(cmnd));
++ BUG();
++ break;
++ }
++#endif
++
++ if (unlikely(cmnd->should_close_conn)) {
++ if (cmnd->should_close_all_conn) {
++ PRINT_INFO("Closing all connections for target %x at "
++ "initiator's %s request",
++ cmnd->conn->session->target->tid,
++ conn->session->initiator_name);
++ target_del_all_sess(cmnd->conn->session->target, 0);
++ } else {
++ PRINT_INFO("Closing connection at initiator's %s "
++ "request", conn->session->initiator_name);
++ mark_conn_closed(conn);
++ }
++ }
++
++ set_cork(cmnd->conn->sock, 0);
++ return;
++}
++
++/*
++ * Push the command for execution. This functions reorders the commands.
++ * Called from the read thread.
++ *
++ * Basically, since we don't support MC/S and TCP guarantees data delivery
++ * order, all that SN's stuff isn't needed at all (commands delivery order is
++ * a natural commands execution order), but insane iSCSI spec requires
++ * us to check it and we have to, because some crazy initiators can rely
++ * on the SN's based order and reorder requests during sending. For all other
++ * normal initiators all that code is a NOP.
++ */
++static void iscsi_push_cmnd(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_session *session = cmnd->conn->session;
++ struct list_head *entry;
++ u32 cmd_sn;
++
++ TRACE_DBG("cmnd %p, iSCSI opcode %x, sn %u, exp sn %u", cmnd,
++ cmnd_opcode(cmnd), cmnd->pdu.bhs.sn, session->exp_cmd_sn);
++
++ iscsi_extracheck_is_rd_thread(cmnd->conn);
++
++ BUG_ON(cmnd->parent_req != NULL);
++
++ if (cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) {
++ TRACE_DBG("Immediate cmd %p (cmd_sn %u)", cmnd,
++ cmnd->pdu.bhs.sn);
++ iscsi_cmnd_exec(cmnd);
++ goto out;
++ }
++
++ spin_lock(&session->sn_lock);
++
++ cmd_sn = cmnd->pdu.bhs.sn;
++ if (cmd_sn == session->exp_cmd_sn) {
++ while (1) {
++ session->exp_cmd_sn = ++cmd_sn;
++
++ if (unlikely(session->tm_active > 0)) {
++ if (before(cmd_sn, session->tm_sn)) {
++ struct iscsi_conn *conn = cmnd->conn;
++
++ spin_unlock(&session->sn_lock);
++
++ spin_lock_bh(&conn->cmd_list_lock);
++ __cmnd_abort(cmnd);
++ spin_unlock_bh(&conn->cmd_list_lock);
++
++ spin_lock(&session->sn_lock);
++ }
++ iscsi_check_send_delayed_tm_resp(session);
++ }
++
++ spin_unlock(&session->sn_lock);
++
++ iscsi_cmnd_exec(cmnd);
++
++ spin_lock(&session->sn_lock);
++
++ if (list_empty(&session->pending_list))
++ break;
++ cmnd = list_entry(session->pending_list.next,
++ struct iscsi_cmnd,
++ pending_list_entry);
++ if (cmnd->pdu.bhs.sn != cmd_sn)
++ break;
++
++ list_del(&cmnd->pending_list_entry);
++ cmnd->pending = 0;
++
++ TRACE_MGMT_DBG("Processing pending cmd %p (cmd_sn %u)",
++ cmnd, cmd_sn);
++ }
++ } else {
++ int drop = 0;
++
++ TRACE_DBG("Pending cmd %p (cmd_sn %u, exp_cmd_sn %u)",
++ cmnd, cmd_sn, session->exp_cmd_sn);
++
++ /*
++ * iSCSI RFC 3720: "The target MUST silently ignore any
++ * non-immediate command outside of [from ExpCmdSN to MaxCmdSN
++ * inclusive] range". But we won't honor the MaxCmdSN
++ * requirement, because, since we adjust MaxCmdSN from the
++ * separate write thread, rarely it is possible that initiator
++ * can legally send command with CmdSN>MaxSN. But it won't
++ * hurt anything, in the worst case it will lead to
++ * additional QUEUE FULL status.
++ */
++
++ if (unlikely(before(cmd_sn, session->exp_cmd_sn))) {
++ TRACE_MGMT_DBG("Ignoring out of expected range cmd_sn "
++ "(sn %u, exp_sn %u, cmd %p, op %x, CDB op %x)",
++ cmd_sn, session->exp_cmd_sn, cmnd,
++ cmnd_opcode(cmnd), cmnd_scsicode(cmnd));
++ drop = 1;
++ }
++
++#if 0
++ if (unlikely(after(cmd_sn, session->exp_cmd_sn +
++ iscsi_get_allowed_cmds(session)))) {
++ TRACE_MGMT_DBG("Too large cmd_sn %u (exp_cmd_sn %u, "
++ "max_sn %u)", cmd_sn, session->exp_cmd_sn,
++ iscsi_get_allowed_cmds(session));
++ drop = 1;
++ }
++#endif
++
++ spin_unlock(&session->sn_lock);
++
++ if (unlikely(drop)) {
++ req_cmnd_release_force(cmnd);
++ goto out;
++ }
++
++ if (unlikely(test_bit(ISCSI_CMD_ABORTED,
++ &cmnd->prelim_compl_flags))) {
++ struct iscsi_cmnd *tm_clone;
++
++ TRACE_MGMT_DBG("Aborted pending cmnd %p, creating TM "
++ "clone (scst cmd %p, state %d)", cmnd,
++ cmnd->scst_cmd, cmnd->scst_state);
++
++ tm_clone = iscsi_create_tm_clone(cmnd);
++ if (tm_clone != NULL) {
++ iscsi_cmnd_exec(cmnd);
++ cmnd = tm_clone;
++ }
++ }
++
++ TRACE_MGMT_DBG("Pending cmnd %p (op %x, sn %u, exp sn %u)",
++ cmnd, cmnd_opcode(cmnd), cmd_sn, session->exp_cmd_sn);
++
++ spin_lock(&session->sn_lock);
++ list_for_each(entry, &session->pending_list) {
++ struct iscsi_cmnd *tmp =
++ list_entry(entry, struct iscsi_cmnd,
++ pending_list_entry);
++ if (before(cmd_sn, tmp->pdu.bhs.sn))
++ break;
++ }
++ list_add_tail(&cmnd->pending_list_entry, entry);
++ cmnd->pending = 1;
++ }
++
++ spin_unlock(&session->sn_lock);
++out:
++ return;
++}
++
++static int check_segment_length(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++ struct iscsi_session *session = conn->session;
++
++ if (unlikely(cmnd->pdu.datasize > session->sess_params.max_recv_data_length)) {
++ PRINT_ERROR("Initiator %s violated negotiated parameters: "
++ "data too long (ITT %x, datasize %u, "
++ "max_recv_data_length %u", session->initiator_name,
++ cmnd->pdu.bhs.itt, cmnd->pdu.datasize,
++ session->sess_params.max_recv_data_length);
++ mark_conn_closed(conn);
++ return -EINVAL;
++ }
++ return 0;
++}
++
++int cmnd_rx_start(struct iscsi_cmnd *cmnd)
++{
++ int res, rc = 0;
++
++ iscsi_dump_pdu(&cmnd->pdu);
++
++ res = check_segment_length(cmnd);
++ if (res != 0)
++ goto out;
++
++ cmnd->pdu.bhs.sn = be32_to_cpu((__force __be32)cmnd->pdu.bhs.sn);
++
++ switch (cmnd_opcode(cmnd)) {
++ case ISCSI_OP_SCSI_CMD:
++ res = scsi_cmnd_start(cmnd);
++ if (unlikely(res < 0))
++ goto out;
++ update_stat_sn(cmnd);
++ break;
++ case ISCSI_OP_SCSI_DATA_OUT:
++ res = data_out_start(cmnd);
++ goto out;
++ case ISCSI_OP_NOP_OUT:
++ rc = nop_out_start(cmnd);
++ break;
++ case ISCSI_OP_SCSI_TASK_MGT_MSG:
++ case ISCSI_OP_LOGOUT_CMD:
++ update_stat_sn(cmnd);
++ break;
++ case ISCSI_OP_TEXT_CMD:
++ case ISCSI_OP_SNACK_CMD:
++ default:
++ rc = -ISCSI_REASON_UNSUPPORTED_COMMAND;
++ break;
++ }
++
++ if (unlikely(rc < 0)) {
++ PRINT_ERROR("Error %d (iSCSI opcode %x, ITT %x)", rc,
++ cmnd_opcode(cmnd), cmnd->pdu.bhs.itt);
++ res = create_reject_rsp(cmnd, -rc, true);
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++void cmnd_rx_end(struct iscsi_cmnd *cmnd)
++{
++ TRACE_ENTRY();
++
++ TRACE_DBG("cmnd %p, opcode %x", cmnd, cmnd_opcode(cmnd));
++
++ cmnd->conn->last_rcv_time = jiffies;
++ TRACE_DBG("Updated last_rcv_time %ld", cmnd->conn->last_rcv_time);
++
++ switch (cmnd_opcode(cmnd)) {
++ case ISCSI_OP_SCSI_CMD:
++ case ISCSI_OP_NOP_OUT:
++ case ISCSI_OP_SCSI_TASK_MGT_MSG:
++ case ISCSI_OP_LOGOUT_CMD:
++ iscsi_push_cmnd(cmnd);
++ goto out;
++ case ISCSI_OP_SCSI_DATA_OUT:
++ data_out_end(cmnd);
++ break;
++ default:
++ PRINT_ERROR("Unexpected cmnd op %x", cmnd_opcode(cmnd));
++ break;
++ }
++
++ req_cmnd_release(cmnd);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++#if !defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++static int iscsi_alloc_data_buf(struct scst_cmd *cmd)
++{
++ /*
++ * sock->ops->sendpage() is async zero copy operation,
++ * so we must be sure not to free and reuse
++ * the command's buffer before the sending was completed
++ * by the network layers. It is possible only if we
++ * don't use SGV cache.
++ */
++ EXTRACHECKS_BUG_ON(!(scst_cmd_get_data_direction(cmd) & SCST_DATA_READ));
++ scst_cmd_set_no_sgv(cmd);
++ return 1;
++}
++#endif
++
++static void iscsi_preprocessing_done(struct scst_cmd *scst_cmd)
++{
++ struct iscsi_cmnd *req = (struct iscsi_cmnd *)
++ scst_cmd_get_tgt_priv(scst_cmd);
++
++ TRACE_DBG("req %p", req);
++
++ if (req->conn->rx_task == current)
++ req->scst_state = ISCSI_CMD_STATE_AFTER_PREPROC;
++ else {
++ /*
++ * We wait for the state change without any protection, so
++ * without cmnd_get() it is possible that req will die
++ * "immediately" after the state assignment and
++ * iscsi_make_conn_rd_active() will operate on dead data.
++ * We use the ordered version of cmnd_get(), because "get"
++ * must be done before the state assignment.
++ *
++ * We protected from the race on calling cmnd_rx_continue(),
++ * because there can be only one read thread processing
++ * connection.
++ */
++ cmnd_get(req);
++ req->scst_state = ISCSI_CMD_STATE_AFTER_PREPROC;
++ iscsi_make_conn_rd_active(req->conn);
++ if (unlikely(req->conn->closing)) {
++ TRACE_DBG("Waking up closing conn %p", req->conn);
++ wake_up(&req->conn->read_state_waitQ);
++ }
++ cmnd_put(req);
++ }
++
++ return;
++}
++
++/* No locks */
++static void iscsi_try_local_processing(struct iscsi_cmnd *req)
++{
++ struct iscsi_conn *conn = req->conn;
++ struct iscsi_thread_pool *p = conn->conn_thr_pool;
++ bool local;
++
++ TRACE_ENTRY();
++
++ spin_lock_bh(&p->wr_lock);
++ switch (conn->wr_state) {
++ case ISCSI_CONN_WR_STATE_IN_LIST:
++ list_del(&conn->wr_list_entry);
++ /* go through */
++ case ISCSI_CONN_WR_STATE_IDLE:
++#ifdef CONFIG_SCST_EXTRACHECKS
++ conn->wr_task = current;
++#endif
++ conn->wr_state = ISCSI_CONN_WR_STATE_PROCESSING;
++ conn->wr_space_ready = 0;
++ local = true;
++ break;
++ default:
++ local = false;
++ break;
++ }
++ spin_unlock_bh(&p->wr_lock);
++
++ if (local) {
++ int rc = 1;
++
++ do {
++ rc = iscsi_send(conn);
++ if (rc <= 0)
++ break;
++ } while (req->not_processed_rsp_cnt != 0);
++
++ spin_lock_bh(&p->wr_lock);
++#ifdef CONFIG_SCST_EXTRACHECKS
++ conn->wr_task = NULL;
++#endif
++ if ((rc == -EAGAIN) && !conn->wr_space_ready) {
++ TRACE_DBG("EAGAIN, setting WR_STATE_SPACE_WAIT "
++ "(conn %p)", conn);
++ conn->wr_state = ISCSI_CONN_WR_STATE_SPACE_WAIT;
++ } else if (test_write_ready(conn)) {
++ list_add_tail(&conn->wr_list_entry, &p->wr_list);
++ conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
++ wake_up(&p->wr_waitQ);
++ } else
++ conn->wr_state = ISCSI_CONN_WR_STATE_IDLE;
++ spin_unlock_bh(&p->wr_lock);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static int iscsi_xmit_response(struct scst_cmd *scst_cmd)
++{
++ int is_send_status = scst_cmd_get_is_send_status(scst_cmd);
++ struct iscsi_cmnd *req = (struct iscsi_cmnd *)
++ scst_cmd_get_tgt_priv(scst_cmd);
++ struct iscsi_conn *conn = req->conn;
++ int status = scst_cmd_get_status(scst_cmd);
++ u8 *sense = scst_cmd_get_sense_buffer(scst_cmd);
++ int sense_len = scst_cmd_get_sense_buffer_len(scst_cmd);
++ struct iscsi_cmnd *wr_rsp, *our_rsp;
++
++ EXTRACHECKS_BUG_ON(scst_cmd_atomic(scst_cmd));
++
++ scst_cmd_set_tgt_priv(scst_cmd, NULL);
++
++ EXTRACHECKS_BUG_ON(req->scst_state != ISCSI_CMD_STATE_RESTARTED);
++
++ if (unlikely(scst_cmd_aborted(scst_cmd)))
++ set_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags);
++
++ if (unlikely(req->prelim_compl_flags != 0)) {
++ if (test_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags)) {
++ TRACE_MGMT_DBG("req %p (scst_cmd %p) aborted", req,
++ req->scst_cmd);
++ scst_set_delivery_status(req->scst_cmd,
++ SCST_CMD_DELIVERY_ABORTED);
++ req->scst_state = ISCSI_CMD_STATE_PROCESSED;
++ req_cmnd_release_force(req);
++ goto out;
++ }
++
++ TRACE_DBG("Prelim completed req %p", req);
++
++ /*
++ * We could preliminary have finished req before we
++ * knew its device, so check if we return correct sense
++ * format.
++ */
++ scst_check_convert_sense(scst_cmd);
++
++ if (!req->own_sg) {
++ req->sg = scst_cmd_get_sg(scst_cmd);
++ req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
++ }
++ } else {
++ EXTRACHECKS_BUG_ON(req->own_sg);
++ req->sg = scst_cmd_get_sg(scst_cmd);
++ req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
++ }
++
++ req->bufflen = scst_cmd_get_adjusted_resp_data_len(scst_cmd);
++
++ req->scst_state = ISCSI_CMD_STATE_PROCESSED;
++
++ TRACE_DBG("req %p, is_send_status=%x, req->bufflen=%d, req->sg=%p, "
++ "req->sg_cnt %d", req, is_send_status, req->bufflen, req->sg,
++ req->sg_cnt);
++
++ EXTRACHECKS_BUG_ON(req->hashed);
++ if (req->main_rsp != NULL)
++ EXTRACHECKS_BUG_ON(cmnd_opcode(req->main_rsp) != ISCSI_OP_REJECT);
++
++ if (unlikely((req->bufflen != 0) && !is_send_status)) {
++ PRINT_CRIT_ERROR("%s", "Sending DATA without STATUS is "
++ "unsupported");
++ scst_set_cmd_error(scst_cmd,
++ SCST_LOAD_SENSE(scst_sense_hardw_error));
++ BUG(); /* ToDo */
++ }
++
++ /*
++ * We need to decrement active_cmds before adding any responses into
++ * the write queue to eliminate a race, when all responses sent
++ * with wrong MaxCmdSN.
++ */
++ if (likely(req->dec_active_cmds))
++ iscsi_dec_active_cmds(req);
++
++ if (req->bufflen != 0) {
++ /*
++ * Check above makes sure that is_send_status is set,
++ * so status is valid here, but in future that could change.
++ * ToDo
++ */
++ if ((status != SAM_STAT_CHECK_CONDITION) &&
++ ((cmnd_hdr(req)->flags & (ISCSI_CMD_WRITE|ISCSI_CMD_READ)) !=
++ (ISCSI_CMD_WRITE|ISCSI_CMD_READ))) {
++ send_data_rsp(req, status, is_send_status);
++ } else {
++ struct iscsi_cmnd *rsp;
++ send_data_rsp(req, 0, 0);
++ if (is_send_status) {
++ rsp = create_status_rsp(req, status, sense,
++ sense_len);
++ iscsi_cmnd_init_write(rsp, 0);
++ }
++ }
++ } else if (is_send_status) {
++ struct iscsi_cmnd *rsp;
++ rsp = create_status_rsp(req, status, sense, sense_len);
++ iscsi_cmnd_init_write(rsp, 0);
++ }
++#ifdef CONFIG_SCST_EXTRACHECKS
++ else
++ BUG();
++#endif
++
++ /*
++ * There's no need for protection, since we are not going to
++ * dereference them.
++ */
++ wr_rsp = list_entry(conn->write_list.next, struct iscsi_cmnd,
++ write_list_entry);
++ our_rsp = list_entry(req->rsp_cmd_list.next, struct iscsi_cmnd,
++ rsp_cmd_list_entry);
++ if (wr_rsp == our_rsp) {
++ /*
++ * This is our rsp, so let's try to process it locally to
++ * decrease latency. We need to call pre_release before
++ * processing to handle some error recovery cases.
++ */
++ if (scst_get_active_cmd_count(scst_cmd) <= 2) {
++ req_cmnd_pre_release(req);
++ iscsi_try_local_processing(req);
++ cmnd_put(req);
++ } else {
++ /*
++ * There's too much backend activity, so it could be
++ * better to push it to the write thread.
++ */
++ goto out_push_to_wr_thread;
++ }
++ } else
++ goto out_push_to_wr_thread;
++
++out:
++ return SCST_TGT_RES_SUCCESS;
++
++out_push_to_wr_thread:
++ TRACE_DBG("Waking up write thread (conn %p)", conn);
++ req_cmnd_release(req);
++ iscsi_make_conn_wr_active(conn);
++ goto out;
++}
++
++/* Called under sn_lock */
++static bool iscsi_is_delay_tm_resp(struct iscsi_cmnd *rsp)
++{
++ bool res = 0;
++ struct iscsi_task_mgt_hdr *req_hdr =
++ (struct iscsi_task_mgt_hdr *)&rsp->parent_req->pdu.bhs;
++ int function = req_hdr->function & ISCSI_FUNCTION_MASK;
++ struct iscsi_session *sess = rsp->conn->session;
++
++ TRACE_ENTRY();
++
++ /* This should be checked for immediate TM commands as well */
++
++ switch (function) {
++ default:
++ if (before(sess->exp_cmd_sn, req_hdr->cmd_sn))
++ res = 1;
++ break;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Called under sn_lock, but might drop it inside, then reacquire */
++static void iscsi_check_send_delayed_tm_resp(struct iscsi_session *sess)
++ __acquires(&sn_lock)
++ __releases(&sn_lock)
++{
++ struct iscsi_cmnd *tm_rsp = sess->tm_rsp;
++
++ TRACE_ENTRY();
++
++ if (tm_rsp == NULL)
++ goto out;
++
++ if (iscsi_is_delay_tm_resp(tm_rsp))
++ goto out;
++
++ TRACE_MGMT_DBG("Sending delayed rsp %p", tm_rsp);
++
++ sess->tm_rsp = NULL;
++ sess->tm_active--;
++
++ spin_unlock(&sess->sn_lock);
++
++ BUG_ON(sess->tm_active < 0);
++
++ iscsi_cmnd_init_write(tm_rsp, ISCSI_INIT_WRITE_WAKE);
++
++ spin_lock(&sess->sn_lock);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static void iscsi_send_task_mgmt_resp(struct iscsi_cmnd *req, int status)
++{
++ struct iscsi_cmnd *rsp;
++ struct iscsi_task_mgt_hdr *req_hdr =
++ (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
++ struct iscsi_task_rsp_hdr *rsp_hdr;
++ struct iscsi_session *sess = req->conn->session;
++ int fn = req_hdr->function & ISCSI_FUNCTION_MASK;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("TM req %p finished", req);
++ TRACE(TRACE_MGMT, "iSCSI TM fn %d finished, status %d", fn, status);
++
++ rsp = iscsi_alloc_rsp(req);
++ rsp_hdr = (struct iscsi_task_rsp_hdr *)&rsp->pdu.bhs;
++
++ rsp_hdr->opcode = ISCSI_OP_SCSI_TASK_MGT_RSP;
++ rsp_hdr->flags = ISCSI_FLG_FINAL;
++ rsp_hdr->itt = req_hdr->itt;
++ rsp_hdr->response = status;
++
++ if (fn == ISCSI_FUNCTION_TARGET_COLD_RESET) {
++ rsp->should_close_conn = 1;
++ rsp->should_close_all_conn = 1;
++ }
++
++ BUG_ON(sess->tm_rsp != NULL);
++
++ spin_lock(&sess->sn_lock);
++ if (iscsi_is_delay_tm_resp(rsp)) {
++ TRACE_MGMT_DBG("Delaying TM fn %d response %p "
++ "(req %p), because not all affected commands "
++ "received (TM cmd sn %u, exp sn %u)",
++ req_hdr->function & ISCSI_FUNCTION_MASK, rsp, req,
++ req_hdr->cmd_sn, sess->exp_cmd_sn);
++ sess->tm_rsp = rsp;
++ spin_unlock(&sess->sn_lock);
++ goto out_release;
++ }
++ sess->tm_active--;
++ spin_unlock(&sess->sn_lock);
++
++ BUG_ON(sess->tm_active < 0);
++
++ iscsi_cmnd_init_write(rsp, ISCSI_INIT_WRITE_WAKE);
++
++out_release:
++ req_cmnd_release(req);
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline int iscsi_get_mgmt_response(int status)
++{
++ switch (status) {
++ case SCST_MGMT_STATUS_SUCCESS:
++ return ISCSI_RESPONSE_FUNCTION_COMPLETE;
++
++ case SCST_MGMT_STATUS_TASK_NOT_EXIST:
++ return ISCSI_RESPONSE_UNKNOWN_TASK;
++
++ case SCST_MGMT_STATUS_LUN_NOT_EXIST:
++ return ISCSI_RESPONSE_UNKNOWN_LUN;
++
++ case SCST_MGMT_STATUS_FN_NOT_SUPPORTED:
++ return ISCSI_RESPONSE_FUNCTION_UNSUPPORTED;
++
++ case SCST_MGMT_STATUS_REJECTED:
++ case SCST_MGMT_STATUS_FAILED:
++ default:
++ return ISCSI_RESPONSE_FUNCTION_REJECTED;
++ }
++}
++
++static void iscsi_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd)
++{
++ int fn = scst_mgmt_cmd_get_fn(scst_mcmd);
++ struct iscsi_cmnd *req = (struct iscsi_cmnd *)
++ scst_mgmt_cmd_get_tgt_priv(scst_mcmd);
++ int status =
++ iscsi_get_mgmt_response(scst_mgmt_cmd_get_status(scst_mcmd));
++
++ if ((status == ISCSI_RESPONSE_UNKNOWN_TASK) &&
++ (fn == SCST_ABORT_TASK)) {
++ /* If we are here, we found the task, so must succeed */
++ status = ISCSI_RESPONSE_FUNCTION_COMPLETE;
++ }
++
++ TRACE_MGMT_DBG("req %p, scst_mcmd %p, fn %d, scst status %d, status %d",
++ req, scst_mcmd, fn, scst_mgmt_cmd_get_status(scst_mcmd),
++ status);
++
++ switch (fn) {
++ case SCST_NEXUS_LOSS_SESS:
++ case SCST_ABORT_ALL_TASKS_SESS:
++ /* They are internal */
++ break;
++ default:
++ iscsi_send_task_mgmt_resp(req, status);
++ scst_mgmt_cmd_set_tgt_priv(scst_mcmd, NULL);
++ break;
++ }
++ return;
++}
++
++static int iscsi_scsi_aen(struct scst_aen *aen)
++{
++ int res = SCST_AEN_RES_SUCCESS;
++ __be64 lun = scst_aen_get_lun(aen);
++ const uint8_t *sense = scst_aen_get_sense(aen);
++ int sense_len = scst_aen_get_sense_len(aen);
++ struct iscsi_session *sess = scst_sess_get_tgt_priv(
++ scst_aen_get_sess(aen));
++ struct iscsi_conn *conn;
++ bool found;
++ struct iscsi_cmnd *fake_req, *rsp;
++ struct iscsi_async_msg_hdr *rsp_hdr;
++ struct scatterlist *sg;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("SCSI AEN to sess %p (initiator %s)", sess,
++ sess->initiator_name);
++
++ mutex_lock(&sess->target->target_mutex);
++
++ found = false;
++ list_for_each_entry_reverse(conn, &sess->conn_list, conn_list_entry) {
++ if (!test_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags) &&
++ (conn->conn_reinst_successor == NULL)) {
++ found = true;
++ break;
++ }
++ }
++ if (!found) {
++ TRACE_MGMT_DBG("Unable to find alive conn for sess %p", sess);
++ goto out_err;
++ }
++
++ /* Create a fake request */
++ fake_req = cmnd_alloc(conn, NULL);
++ if (fake_req == NULL) {
++ PRINT_ERROR("%s", "Unable to alloc fake AEN request");
++ goto out_err;
++ }
++
++ mutex_unlock(&sess->target->target_mutex);
++
++ rsp = iscsi_alloc_main_rsp(fake_req);
++ if (rsp == NULL) {
++ PRINT_ERROR("%s", "Unable to alloc AEN rsp");
++ goto out_err_free_req;
++ }
++
++ fake_req->scst_state = ISCSI_CMD_STATE_AEN;
++ fake_req->scst_aen = aen;
++
++ rsp_hdr = (struct iscsi_async_msg_hdr *)&rsp->pdu.bhs;
++
++ rsp_hdr->opcode = ISCSI_OP_ASYNC_MSG;
++ rsp_hdr->flags = ISCSI_FLG_FINAL;
++ rsp_hdr->lun = lun; /* it's already in SCSI form */
++ rsp_hdr->ffffffff = __constant_cpu_to_be32(0xffffffff);
++ rsp_hdr->async_event = ISCSI_ASYNC_SCSI;
++
++ sg = rsp->sg = rsp->rsp_sg;
++ rsp->sg_cnt = 2;
++ rsp->own_sg = 1;
++
++ sg_init_table(sg, 2);
++ sg_set_buf(&sg[0], &rsp->sense_hdr, sizeof(rsp->sense_hdr));
++ sg_set_buf(&sg[1], sense, sense_len);
++
++ rsp->sense_hdr.length = cpu_to_be16(sense_len);
++ rsp->pdu.datasize = sizeof(rsp->sense_hdr) + sense_len;
++ rsp->bufflen = rsp->pdu.datasize;
++
++ req_cmnd_release(fake_req);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_err_free_req:
++ req_cmnd_release(fake_req);
++
++out_err:
++ mutex_unlock(&sess->target->target_mutex);
++ res = SCST_AEN_RES_FAILED;
++ goto out;
++}
++
++static int iscsi_cpu_mask_changed_aen(struct scst_aen *aen)
++{
++ int res = SCST_AEN_RES_SUCCESS;
++ struct scst_session *scst_sess = scst_aen_get_sess(aen);
++ struct iscsi_session *sess = scst_sess_get_tgt_priv(scst_sess);
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("CPU mask changed AEN to sess %p (initiator %s)", sess,
++ sess->initiator_name);
++
++ mutex_lock(&sess->target->target_mutex);
++ iscsi_sess_force_close(sess);
++ mutex_unlock(&sess->target->target_mutex);
++
++ scst_aen_done(aen);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int iscsi_report_aen(struct scst_aen *aen)
++{
++ int res;
++ int event_fn = scst_aen_get_event_fn(aen);
++
++ TRACE_ENTRY();
++
++ switch (event_fn) {
++ case SCST_AEN_SCSI:
++ res = iscsi_scsi_aen(aen);
++ break;
++ case SCST_AEN_CPU_MASK_CHANGED:
++ res = iscsi_cpu_mask_changed_aen(aen);
++ break;
++ default:
++ TRACE_MGMT_DBG("Unsupported AEN %d", event_fn);
++ res = SCST_AEN_RES_NOT_SUPPORTED;
++ break;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int iscsi_get_initiator_port_transport_id(struct scst_tgt *tgt,
++ struct scst_session *scst_sess, uint8_t **transport_id)
++{
++ struct iscsi_session *sess;
++ int res = 0;
++ union iscsi_sid sid;
++ int tr_id_size;
++ uint8_t *tr_id;
++ uint8_t q;
++
++ TRACE_ENTRY();
++
++ if (scst_sess == NULL) {
++ res = SCSI_TRANSPORTID_PROTOCOLID_ISCSI;
++ goto out;
++ }
++
++ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
++
++ sid = *(union iscsi_sid *)&sess->sid;
++ sid.id.tsih = 0;
++
++ tr_id_size = 4 + strlen(sess->initiator_name) + 5 +
++ snprintf(&q, sizeof(q), "%llx", sid.id64) + 1;
++ tr_id_size = (tr_id_size + 3) & -4;
++
++ tr_id = kzalloc(tr_id_size, GFP_KERNEL);
++ if (tr_id == NULL) {
++ PRINT_ERROR("Allocation of TransportID (size %d) failed",
++ tr_id_size);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ tr_id[0] = 0x40 | SCSI_TRANSPORTID_PROTOCOLID_ISCSI;
++ sprintf(&tr_id[4], "%s,i,0x%llx", sess->initiator_name, sid.id64);
++
++ put_unaligned(cpu_to_be16(tr_id_size - 4),
++ (__be16 *)&tr_id[2]);
++
++ *transport_id = tr_id;
++
++ TRACE_DBG("Created tid '%s'", &tr_id[4]);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++void iscsi_send_nop_in(struct iscsi_conn *conn)
++{
++ struct iscsi_cmnd *req, *rsp;
++ struct iscsi_nop_in_hdr *rsp_hdr;
++
++ TRACE_ENTRY();
++
++ req = cmnd_alloc(conn, NULL);
++ if (req == NULL) {
++ PRINT_ERROR("%s", "Unable to alloc fake Nop-In request");
++ goto out_err;
++ }
++
++ rsp = iscsi_alloc_main_rsp(req);
++ if (rsp == NULL) {
++ PRINT_ERROR("%s", "Unable to alloc Nop-In rsp");
++ goto out_err_free_req;
++ }
++
++ cmnd_get(rsp);
++
++ rsp_hdr = (struct iscsi_nop_in_hdr *)&rsp->pdu.bhs;
++ rsp_hdr->opcode = ISCSI_OP_NOP_IN;
++ rsp_hdr->flags = ISCSI_FLG_FINAL;
++ rsp_hdr->itt = ISCSI_RESERVED_TAG;
++ rsp_hdr->ttt = (__force __be32)conn->nop_in_ttt++;
++
++ if (conn->nop_in_ttt == ISCSI_RESERVED_TAG_CPU32)
++ conn->nop_in_ttt = 0;
++
++ /* Supposed that all other fields are zeroed */
++
++ TRACE_DBG("Sending Nop-In request (ttt 0x%08x)", rsp_hdr->ttt);
++ spin_lock_bh(&conn->nop_req_list_lock);
++ list_add_tail(&rsp->nop_req_list_entry, &conn->nop_req_list);
++ spin_unlock_bh(&conn->nop_req_list_lock);
++
++out_err_free_req:
++ req_cmnd_release(req);
++
++out_err:
++ TRACE_EXIT();
++ return;
++}
++
++static int iscsi_target_detect(struct scst_tgt_template *templ)
++{
++ /* Nothing to do */
++ return 0;
++}
++
++static int iscsi_target_release(struct scst_tgt *scst_tgt)
++{
++ /* Nothing to do */
++ return 0;
++}
++
++static struct scst_trace_log iscsi_local_trace_tbl[] = {
++ { TRACE_D_WRITE, "d_write" },
++ { TRACE_CONN_OC, "conn" },
++ { TRACE_CONN_OC_DBG, "conn_dbg" },
++ { TRACE_D_IOV, "iov" },
++ { TRACE_D_DUMP_PDU, "pdu" },
++ { TRACE_NET_PG, "net_page" },
++ { 0, NULL }
++};
++
++#define ISCSI_TRACE_TBL_HELP ", d_write, conn, conn_dbg, iov, pdu, net_page"
++
++static uint16_t iscsi_get_scsi_transport_version(struct scst_tgt *scst_tgt)
++{
++ return 0x0960; /* iSCSI */
++}
++
++struct scst_tgt_template iscsi_template = {
++ .name = "iscsi",
++ .sg_tablesize = 0xFFFF /* no limit */,
++ .threads_num = 0,
++ .no_clustering = 1,
++ .xmit_response_atomic = 0,
++ .tgtt_attrs = iscsi_attrs,
++ .tgt_attrs = iscsi_tgt_attrs,
++ .sess_attrs = iscsi_sess_attrs,
++ .enable_target = iscsi_enable_target,
++ .is_target_enabled = iscsi_is_target_enabled,
++ .add_target = iscsi_sysfs_add_target,
++ .del_target = iscsi_sysfs_del_target,
++ .mgmt_cmd = iscsi_sysfs_mgmt_cmd,
++ .tgtt_optional_attributes = "IncomingUser, OutgoingUser",
++ .tgt_optional_attributes = "IncomingUser, OutgoingUser, allowed_portal",
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = ISCSI_DEFAULT_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++ .trace_tbl = iscsi_local_trace_tbl,
++ .trace_tbl_help = ISCSI_TRACE_TBL_HELP,
++#endif
++ .detect = iscsi_target_detect,
++ .release = iscsi_target_release,
++ .xmit_response = iscsi_xmit_response,
++#if !defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ .alloc_data_buf = iscsi_alloc_data_buf,
++#endif
++ .preprocessing_done = iscsi_preprocessing_done,
++ .pre_exec = iscsi_pre_exec,
++ .task_mgmt_affected_cmds_done = iscsi_task_mgmt_affected_cmds_done,
++ .task_mgmt_fn_done = iscsi_task_mgmt_fn_done,
++ .on_abort_cmd = iscsi_on_abort_cmd,
++ .report_aen = iscsi_report_aen,
++ .get_initiator_port_transport_id = iscsi_get_initiator_port_transport_id,
++ .get_scsi_transport_version = iscsi_get_scsi_transport_version,
++};
++
++int iscsi_threads_pool_get(const cpumask_t *cpu_mask,
++ struct iscsi_thread_pool **out_pool)
++{
++ int res;
++ struct iscsi_thread_pool *p;
++ struct iscsi_thread *t, *tt;
++ int i, j, count;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&iscsi_threads_pool_mutex);
++
++ list_for_each_entry(p, &iscsi_thread_pools_list,
++ thread_pools_list_entry) {
++ if ((cpu_mask == NULL) ||
++ __cpus_equal(cpu_mask, &p->cpu_mask, nr_cpumask_bits)) {
++ p->thread_pool_ref++;
++ TRACE_DBG("iSCSI thread pool %p found (new ref %d)",
++ p, p->thread_pool_ref);
++ res = 0;
++ goto out_unlock;
++ }
++ }
++
++ TRACE_DBG("%s", "Creating new iSCSI thread pool");
++
++ p = kzalloc(sizeof(*p), GFP_KERNEL);
++ if (p == NULL) {
++ PRINT_ERROR("Unable to allocate iSCSI thread pool (size %zd)",
++ sizeof(*p));
++ res = -ENOMEM;
++ if (!list_empty(&iscsi_thread_pools_list)) {
++ PRINT_WARNING("%s", "Using global iSCSI thread pool "
++ "instead");
++ p = list_entry(iscsi_thread_pools_list.next,
++ struct iscsi_thread_pool,
++ thread_pools_list_entry);
++ } else
++ res = -ENOMEM;
++ goto out_unlock;
++ }
++
++ spin_lock_init(&p->rd_lock);
++ INIT_LIST_HEAD(&p->rd_list);
++ init_waitqueue_head(&p->rd_waitQ);
++ spin_lock_init(&p->wr_lock);
++ INIT_LIST_HEAD(&p->wr_list);
++ init_waitqueue_head(&p->wr_waitQ);
++ if (cpu_mask == NULL)
++ cpus_setall(p->cpu_mask);
++ else {
++ cpus_clear(p->cpu_mask);
++ for_each_cpu(i, cpu_mask)
++ cpu_set(i, p->cpu_mask);
++ }
++ p->thread_pool_ref = 1;
++ INIT_LIST_HEAD(&p->threads_list);
++
++ if (cpu_mask == NULL)
++ count = max((int)num_online_cpus(), 2);
++ else {
++ count = 0;
++ for_each_cpu(i, cpu_mask)
++ count++;
++ }
++
++ for (j = 0; j < 2; j++) {
++ int (*fn)(void *);
++ char name[25];
++ static int major;
++
++ if (j == 0)
++ fn = istrd;
++ else
++ fn = istwr;
++
++ for (i = 0; i < count; i++) {
++ if (j == 0) {
++ major++;
++ if (cpu_mask == NULL)
++ snprintf(name, sizeof(name), "iscsird%d", i);
++ else
++ snprintf(name, sizeof(name), "iscsird%d_%d",
++ major, i);
++ } else {
++ if (cpu_mask == NULL)
++ snprintf(name, sizeof(name), "iscsiwr%d", i);
++ else
++ snprintf(name, sizeof(name), "iscsiwr%d_%d",
++ major, i);
++ }
++
++ t = kmalloc(sizeof(*t), GFP_KERNEL);
++ if (t == NULL) {
++ res = -ENOMEM;
++ PRINT_ERROR("Failed to allocate thread %s "
++ "(size %zd)", name, sizeof(*t));
++ goto out_free;
++ }
++
++ t->thr = kthread_run(fn, p, name);
++ if (IS_ERR(t->thr)) {
++ res = PTR_ERR(t->thr);
++ PRINT_ERROR("kthread_run() for thread %s failed: %d",
++ name, res);
++ kfree(t);
++ goto out_free;
++ }
++ list_add_tail(&t->threads_list_entry, &p->threads_list);
++ }
++ }
++
++ list_add_tail(&p->thread_pools_list_entry, &iscsi_thread_pools_list);
++ res = 0;
++
++ TRACE_DBG("Created iSCSI thread pool %p", p);
++
++out_unlock:
++ mutex_unlock(&iscsi_threads_pool_mutex);
++
++ if (out_pool != NULL)
++ *out_pool = p;
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ list_for_each_entry_safe(t, tt, &p->threads_list, threads_list_entry) {
++ kthread_stop(t->thr);
++ list_del(&t->threads_list_entry);
++ kfree(t);
++ }
++ goto out_unlock;
++}
++
++void iscsi_threads_pool_put(struct iscsi_thread_pool *p)
++{
++ struct iscsi_thread *t, *tt;
++
++ TRACE_ENTRY();
++
++ mutex_lock(&iscsi_threads_pool_mutex);
++
++ p->thread_pool_ref--;
++ if (p->thread_pool_ref > 0) {
++ TRACE_DBG("iSCSI thread pool %p still has %d references)",
++ p, p->thread_pool_ref);
++ goto out_unlock;
++ }
++
++ TRACE_DBG("Freeing iSCSI thread pool %p", p);
++
++ list_for_each_entry_safe(t, tt, &p->threads_list, threads_list_entry) {
++ kthread_stop(t->thr);
++ list_del(&t->threads_list_entry);
++ kfree(t);
++ }
++
++ list_del(&p->thread_pools_list_entry);
++
++ kfree(p);
++
++out_unlock:
++ mutex_unlock(&iscsi_threads_pool_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int __init iscsi_init(void)
++{
++ int err = 0;
++
++ PRINT_INFO("iSCSI SCST Target - version %s", ISCSI_VERSION_STRING);
++
++ dummy_page = alloc_pages(GFP_KERNEL, 0);
++ if (dummy_page == NULL) {
++ PRINT_ERROR("%s", "Dummy page allocation failed");
++ goto out;
++ }
++
++ sg_init_table(&dummy_sg, 1);
++ sg_set_page(&dummy_sg, dummy_page, PAGE_SIZE, 0);
++
++ iscsi_cmnd_abort_mempool = mempool_create_kmalloc_pool(2500,
++ sizeof(struct iscsi_cmnd_abort_params));
++ if (iscsi_cmnd_abort_mempool == NULL) {
++ err = -ENOMEM;
++ goto out_free_dummy;
++ }
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ err = net_set_get_put_page_callbacks(iscsi_get_page_callback,
++ iscsi_put_page_callback);
++ if (err != 0) {
++ PRINT_INFO("Unable to set page callbackes: %d", err);
++ goto out_destroy_mempool;
++ }
++#else
++#ifndef GENERATING_UPSTREAM_PATCH
++ PRINT_WARNING("%s",
++ "CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION "
++ "not enabled in your kernel. ISCSI-SCST will be working with "
++ "not the best performance. Refer README file for details.");
++#endif
++#endif
++
++ ctr_major = register_chrdev(0, ctr_name, &ctr_fops);
++ if (ctr_major < 0) {
++ PRINT_ERROR("failed to register the control device %d",
++ ctr_major);
++ err = ctr_major;
++ goto out_callb;
++ }
++
++ err = event_init();
++ if (err < 0)
++ goto out_reg;
++
++ iscsi_cmnd_cache = KMEM_CACHE(iscsi_cmnd, SCST_SLAB_FLAGS);
++ if (!iscsi_cmnd_cache) {
++ err = -ENOMEM;
++ goto out_event;
++ }
++
++ err = scst_register_target_template(&iscsi_template);
++ if (err < 0)
++ goto out_kmem;
++
++ iscsi_conn_ktype.sysfs_ops = scst_sysfs_get_sysfs_ops();
++
++ err = iscsi_threads_pool_get(NULL, &iscsi_main_thread_pool);
++ if (err != 0)
++ goto out_thr;
++
++out:
++ return err;
++
++out_thr:
++
++ scst_unregister_target_template(&iscsi_template);
++
++out_kmem:
++ kmem_cache_destroy(iscsi_cmnd_cache);
++
++out_event:
++ event_exit();
++
++out_reg:
++ unregister_chrdev(ctr_major, ctr_name);
++
++out_callb:
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ net_set_get_put_page_callbacks(NULL, NULL);
++
++out_destroy_mempool:
++ mempool_destroy(iscsi_cmnd_abort_mempool);
++#endif
++
++out_free_dummy:
++ __free_pages(dummy_page, 0);
++ goto out;
++}
++
++static void __exit iscsi_exit(void)
++{
++ iscsi_threads_pool_put(iscsi_main_thread_pool);
++
++ BUG_ON(!list_empty(&iscsi_thread_pools_list));
++
++ unregister_chrdev(ctr_major, ctr_name);
++
++ event_exit();
++
++ kmem_cache_destroy(iscsi_cmnd_cache);
++
++ scst_unregister_target_template(&iscsi_template);
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ net_set_get_put_page_callbacks(NULL, NULL);
++#endif
++
++ mempool_destroy(iscsi_cmnd_abort_mempool);
++
++ __free_pages(dummy_page, 0);
++ return;
++}
++
++module_init(iscsi_init);
++module_exit(iscsi_exit);
++
++MODULE_VERSION(ISCSI_VERSION_STRING);
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("SCST iSCSI Target");
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/iscsi.h linux-3.2/drivers/scst/iscsi-scst/iscsi.h
+--- orig/linux-3.2/drivers/scst/iscsi-scst/iscsi.h
++++ linux-3.2/drivers/scst/iscsi-scst/iscsi.h
+@@ -0,0 +1,789 @@
++/*
++ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@ardistech.com>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#ifndef __ISCSI_H__
++#define __ISCSI_H__
++
++#include <linux/pagemap.h>
++#include <linux/mm.h>
++#include <linux/net.h>
++#include <linux/module.h>
++#include <net/sock.h>
++
++#include <scst/scst.h>
++#include <scst/iscsi_scst.h>
++#include "iscsi_hdr.h"
++#include "iscsi_dbg.h"
++
++#define iscsi_sense_crc_error ABORTED_COMMAND, 0x47, 0x05
++#define iscsi_sense_unexpected_unsolicited_data ABORTED_COMMAND, 0x0C, 0x0C
++#define iscsi_sense_incorrect_amount_of_data ABORTED_COMMAND, 0x0C, 0x0D
++
++struct iscsi_sess_params {
++ int initial_r2t;
++ int immediate_data;
++ int max_connections;
++ unsigned int max_recv_data_length;
++ unsigned int max_xmit_data_length;
++ unsigned int max_burst_length;
++ unsigned int first_burst_length;
++ int default_wait_time;
++ int default_retain_time;
++ unsigned int max_outstanding_r2t;
++ int data_pdu_inorder;
++ int data_sequence_inorder;
++ int error_recovery_level;
++ int header_digest;
++ int data_digest;
++ int ofmarker;
++ int ifmarker;
++ int ofmarkint;
++ int ifmarkint;
++};
++
++struct iscsi_tgt_params {
++ int queued_cmnds;
++ unsigned int rsp_timeout;
++ unsigned int nop_in_interval;
++ unsigned int nop_in_timeout;
++};
++
++struct iscsi_thread {
++ struct task_struct *thr;
++ struct list_head threads_list_entry;
++};
++
++struct iscsi_thread_pool {
++ spinlock_t rd_lock;
++ struct list_head rd_list;
++ wait_queue_head_t rd_waitQ;
++
++ spinlock_t wr_lock;
++ struct list_head wr_list;
++ wait_queue_head_t wr_waitQ;
++
++ cpumask_t cpu_mask;
++
++ int thread_pool_ref;
++
++ struct list_head threads_list;
++
++ struct list_head thread_pools_list_entry;
++};
++
++struct iscsi_target;
++struct iscsi_cmnd;
++
++struct iscsi_attr {
++ struct list_head attrs_list_entry;
++ struct kobj_attribute attr;
++ struct iscsi_target *target;
++ const char *name;
++};
++
++struct iscsi_target {
++ struct scst_tgt *scst_tgt;
++
++ struct mutex target_mutex;
++
++ struct list_head session_list; /* protected by target_mutex */
++
++ struct list_head target_list_entry;
++ u32 tid;
++
++ unsigned int tgt_enabled:1;
++
++ /* Protected by target_mutex */
++ struct list_head attrs_list;
++
++ char name[ISCSI_NAME_LEN];
++};
++
++#define ISCSI_HASH_ORDER 8
++#define cmnd_hashfn(itt) hash_32(itt, ISCSI_HASH_ORDER)
++
++struct iscsi_session {
++ struct iscsi_target *target;
++ struct scst_session *scst_sess;
++
++ struct list_head pending_list; /* protected by sn_lock */
++
++ /* Unprotected, since accessed only from a single read thread */
++ u32 next_ttt;
++
++ /* Read only, if there are connection(s) */
++ struct iscsi_tgt_params tgt_params;
++ atomic_t active_cmds;
++
++ spinlock_t sn_lock;
++ u32 exp_cmd_sn; /* protected by sn_lock */
++
++ /* All 3 protected by sn_lock */
++ int tm_active;
++ u32 tm_sn;
++ struct iscsi_cmnd *tm_rsp;
++
++ /* Read only, if there are connection(s) */
++ struct iscsi_sess_params sess_params;
++
++ /*
++ * In some corner cases commands can be deleted from the hash
++ * not from the corresponding read thread. So, let's simplify
++ * errors recovery and have this lock.
++ */
++ spinlock_t cmnd_data_wait_hash_lock;
++ struct list_head cmnd_data_wait_hash[1 << ISCSI_HASH_ORDER];
++
++ struct list_head conn_list; /* protected by target_mutex */
++
++ struct list_head session_list_entry;
++
++ /* All protected by target_mutex, where necessary */
++ struct iscsi_session *sess_reinst_successor;
++ unsigned int sess_reinstating:1;
++ unsigned int sess_shutting_down:1;
++
++ struct iscsi_thread_pool *sess_thr_pool;
++
++ /* All don't need any protection */
++ char *initiator_name;
++ u64 sid;
++};
++
++#define ISCSI_CONN_IOV_MAX (PAGE_SIZE/sizeof(struct iovec))
++
++#define ISCSI_CONN_RD_STATE_IDLE 0
++#define ISCSI_CONN_RD_STATE_IN_LIST 1
++#define ISCSI_CONN_RD_STATE_PROCESSING 2
++
++#define ISCSI_CONN_WR_STATE_IDLE 0
++#define ISCSI_CONN_WR_STATE_IN_LIST 1
++#define ISCSI_CONN_WR_STATE_SPACE_WAIT 2
++#define ISCSI_CONN_WR_STATE_PROCESSING 3
++
++struct iscsi_conn {
++ struct iscsi_session *session; /* owning session */
++
++ /* Both protected by session->sn_lock */
++ u32 stat_sn;
++ u32 exp_stat_sn;
++
++#define ISCSI_CONN_REINSTATING 1
++#define ISCSI_CONN_SHUTTINGDOWN 2
++ unsigned long conn_aflags;
++
++ spinlock_t cmd_list_lock; /* BH lock */
++
++ /* Protected by cmd_list_lock */
++ struct list_head cmd_list; /* in/outcoming pdus */
++
++ atomic_t conn_ref_cnt;
++
++ spinlock_t write_list_lock;
++ /* List of data pdus to be sent. Protected by write_list_lock */
++ struct list_head write_list;
++ /* List of data pdus being sent. Protected by write_list_lock */
++ struct list_head write_timeout_list;
++
++ /* Protected by write_list_lock */
++ struct timer_list rsp_timer;
++ unsigned int data_rsp_timeout; /* in jiffies */
++
++ /*
++ * All 2 protected by wr_lock. Modified independently to the
++ * above field, hence the alignment.
++ */
++ unsigned short wr_state __attribute__((aligned(sizeof(long))));
++ unsigned short wr_space_ready:1;
++
++ struct list_head wr_list_entry;
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ struct task_struct *wr_task;
++#endif
++
++ /*
++ * All are unprotected, since accessed only from a single write
++ * thread.
++ */
++ struct iscsi_cmnd *write_cmnd;
++ struct iovec *write_iop;
++ int write_iop_used;
++ struct iovec write_iov[2];
++ u32 write_size;
++ u32 write_offset;
++ int write_state;
++
++ /* Both don't need any protection */
++ struct file *file;
++ struct socket *sock;
++
++ void (*old_state_change)(struct sock *);
++ void (*old_data_ready)(struct sock *, int);
++ void (*old_write_space)(struct sock *);
++
++ /* Both read only. Stay here for better CPU cache locality. */
++ int hdigest_type;
++ int ddigest_type;
++
++ struct iscsi_thread_pool *conn_thr_pool;
++
++ /* All 6 protected by rd_lock */
++ unsigned short rd_state;
++ unsigned short rd_data_ready:1;
++ /* Let's save some cache footprint by putting them here */
++ unsigned short closing:1;
++ unsigned short active_close:1;
++ unsigned short deleting:1;
++ unsigned short conn_tm_active:1;
++
++ struct list_head rd_list_entry;
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ struct task_struct *rd_task;
++#endif
++
++ unsigned long last_rcv_time;
++
++ /*
++ * All are unprotected, since accessed only from a single read
++ * thread.
++ */
++ struct iscsi_cmnd *read_cmnd;
++ struct msghdr read_msg;
++ u32 read_size;
++ int read_state;
++ struct iovec *read_iov;
++ struct task_struct *rx_task;
++ uint32_t rpadding;
++
++ struct iscsi_target *target;
++
++ struct list_head conn_list_entry; /* list entry in session conn_list */
++
++ /* All protected by target_mutex, where necessary */
++ struct iscsi_conn *conn_reinst_successor;
++ struct list_head reinst_pending_cmd_list;
++
++ wait_queue_head_t read_state_waitQ;
++ struct completion ready_to_free;
++
++ /* Doesn't need any protection */
++ u16 cid;
++
++ struct delayed_work nop_in_delayed_work;
++ unsigned int nop_in_interval; /* in jiffies */
++ unsigned int nop_in_timeout; /* in jiffies */
++ struct list_head nop_req_list;
++ spinlock_t nop_req_list_lock;
++ u32 nop_in_ttt;
++
++ /* Don't need any protection */
++ struct kobject conn_kobj;
++ struct completion *conn_kobj_release_cmpl;
++};
++
++struct iscsi_pdu {
++ struct iscsi_hdr bhs;
++ void *ahs;
++ unsigned int ahssize;
++ unsigned int datasize;
++};
++
++typedef void (iscsi_show_info_t)(struct seq_file *seq,
++ struct iscsi_target *target);
++
++/** Commands' states **/
++
++/* New command and SCST processes it */
++#define ISCSI_CMD_STATE_NEW 0
++
++/* SCST processes cmd after scst_rx_cmd() */
++#define ISCSI_CMD_STATE_RX_CMD 1
++
++/* The command returned from preprocessing_done() */
++#define ISCSI_CMD_STATE_AFTER_PREPROC 2
++
++/* The command is waiting for session or connection reinstatement finished */
++#define ISCSI_CMD_STATE_REINST_PENDING 3
++
++/* scst_restart_cmd() called and SCST processing it */
++#define ISCSI_CMD_STATE_RESTARTED 4
++
++/* SCST done processing */
++#define ISCSI_CMD_STATE_PROCESSED 5
++
++/* AEN processing */
++#define ISCSI_CMD_STATE_AEN 6
++
++/* Out of SCST core preliminary completed */
++#define ISCSI_CMD_STATE_OUT_OF_SCST_PRELIM_COMPL 7
++
++/*
++ * Most of the fields don't need any protection, since accessed from only a
++ * single thread, except where noted.
++ *
++ * ToDo: Eventually divide request and response structures in 2 separate
++ * structures and stop this IET-derived garbage.
++ */
++struct iscsi_cmnd {
++ struct iscsi_conn *conn;
++
++ /*
++ * Some flags used under conn->write_list_lock, but all modified only
++ * from single read thread or when there are no references to cmd.
++ */
++ unsigned int hashed:1;
++ unsigned int should_close_conn:1;
++ unsigned int should_close_all_conn:1;
++ unsigned int pending:1;
++ unsigned int own_sg:1;
++ unsigned int on_write_list:1;
++ unsigned int write_processing_started:1;
++ unsigned int force_cleanup_done:1;
++ unsigned int dec_active_cmds:1;
++ unsigned int ddigest_checked:1;
++ /*
++ * Used to prevent release of original req while its related DATA OUT
++ * cmd is receiving data, i.e. stays between data_out_start() and
++ * data_out_end(). Ref counting can't be used for that, because
++ * req_cmnd_release() supposed to be called only once.
++ */
++ unsigned int data_out_in_data_receiving:1;
++#ifdef CONFIG_SCST_EXTRACHECKS
++ unsigned int on_rx_digest_list:1;
++ unsigned int release_called:1;
++#endif
++
++ /*
++ * We suppose that preliminary commands completion is tested by
++ * comparing prelim_compl_flags with 0. Otherwise, because of the
++ * gap between setting different flags a race is possible,
++ * like sending command in SCST core as PRELIM_COMPLETED, while it
++ * wasn't aborted in it yet and have as the result a wrong success
++ * status sent to the initiator.
++ */
++#define ISCSI_CMD_ABORTED 0
++#define ISCSI_CMD_PRELIM_COMPLETED 1
++ unsigned long prelim_compl_flags;
++
++ struct list_head hash_list_entry;
++
++ /*
++ * Unions are for readability and grepability and to save some
++ * cache footprint.
++ */
++
++ union {
++ /*
++ * Used only to abort not yet sent responses. Usage in
++ * cmnd_done() is only a side effect to have a lockless
++ * accesss to this list from always only a single thread
++ * at any time. So, all responses live in the parent
++ * until it has the last reference put.
++ */
++ struct list_head rsp_cmd_list;
++ struct list_head rsp_cmd_list_entry;
++ };
++
++ union {
++ struct list_head pending_list_entry;
++ struct list_head reinst_pending_cmd_list_entry;
++ };
++
++ union {
++ struct list_head write_list_entry;
++ struct list_head write_timeout_list_entry;
++ };
++
++ /* Both protected by conn->write_list_lock */
++ unsigned int on_write_timeout_list:1;
++ unsigned long write_start;
++
++ /*
++ * All unprotected, since could be accessed from only a single
++ * thread at time
++ */
++ struct iscsi_cmnd *parent_req;
++ struct iscsi_cmnd *cmd_req;
++
++ /*
++ * All unprotected, since could be accessed from only a single
++ * thread at time
++ */
++ union {
++ /* Request only fields */
++ struct {
++ struct list_head rx_ddigest_cmd_list;
++ struct list_head rx_ddigest_cmd_list_entry;
++
++ int scst_state;
++ union {
++ struct scst_cmd *scst_cmd;
++ struct scst_aen *scst_aen;
++ };
++
++ struct iscsi_cmnd *main_rsp;
++
++ /*
++ * Protected on modify by conn->write_list_lock, hence
++ * modified independently to the above field, hence the
++ * alignment.
++ */
++ int not_processed_rsp_cnt
++ __attribute__((aligned(sizeof(long))));
++ };
++
++ /* Response only fields */
++ struct {
++ struct scatterlist rsp_sg[2];
++ struct iscsi_sense_data sense_hdr;
++ };
++ };
++
++ atomic_t ref_cnt;
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ atomic_t net_ref_cnt;
++#endif
++
++ struct iscsi_pdu pdu;
++
++ struct scatterlist *sg;
++ int sg_cnt;
++ unsigned int bufflen;
++ u32 r2t_sn;
++ unsigned int r2t_len_to_receive;
++ unsigned int r2t_len_to_send;
++ unsigned int outstanding_r2t;
++ u32 target_task_tag;
++ __be32 hdigest;
++ __be32 ddigest;
++
++ struct list_head cmd_list_entry;
++ struct list_head nop_req_list_entry;
++
++ unsigned int not_received_data_len;
++};
++
++/* Max time to wait for our response satisfied for aborted commands */
++#define ISCSI_TM_DATA_WAIT_TIMEOUT (10 * HZ)
++
++/*
++ * Needed addition to all timeouts to complete a burst of commands at once.
++ * Otherwise, a part of the burst can be timeouted only in double timeout time.
++ */
++#define ISCSI_ADD_SCHED_TIME HZ
++
++#define ISCSI_CTR_OPEN_STATE_CLOSED 0
++#define ISCSI_CTR_OPEN_STATE_OPEN 1
++#define ISCSI_CTR_OPEN_STATE_CLOSING 2
++
++extern struct mutex target_mgmt_mutex;
++
++extern int ctr_open_state;
++extern const struct file_operations ctr_fops;
++
++/* iscsi.c */
++extern struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *,
++ struct iscsi_cmnd *parent);
++extern int cmnd_rx_start(struct iscsi_cmnd *);
++extern int cmnd_rx_continue(struct iscsi_cmnd *req);
++extern void cmnd_rx_end(struct iscsi_cmnd *);
++extern void cmnd_tx_start(struct iscsi_cmnd *);
++extern void cmnd_tx_end(struct iscsi_cmnd *);
++extern void req_cmnd_release_force(struct iscsi_cmnd *req);
++extern void rsp_cmnd_release(struct iscsi_cmnd *);
++extern void cmnd_done(struct iscsi_cmnd *cmnd);
++extern void conn_abort(struct iscsi_conn *conn);
++extern void iscsi_restart_cmnd(struct iscsi_cmnd *cmnd);
++extern void iscsi_fail_data_waiting_cmnd(struct iscsi_cmnd *cmnd);
++extern void iscsi_send_nop_in(struct iscsi_conn *conn);
++extern int iscsi_preliminary_complete(struct iscsi_cmnd *req,
++ struct iscsi_cmnd *orig_req, bool get_data);
++extern int set_scst_preliminary_status_rsp(struct iscsi_cmnd *req,
++ bool get_data, int key, int asc, int ascq);
++extern int iscsi_threads_pool_get(const cpumask_t *cpu_mask,
++ struct iscsi_thread_pool **out_pool);
++extern void iscsi_threads_pool_put(struct iscsi_thread_pool *p);
++
++/* conn.c */
++extern struct kobj_type iscsi_conn_ktype;
++extern struct iscsi_conn *conn_lookup(struct iscsi_session *, u16);
++extern void conn_reinst_finished(struct iscsi_conn *);
++extern int __add_conn(struct iscsi_session *, struct iscsi_kern_conn_info *);
++extern int __del_conn(struct iscsi_session *, struct iscsi_kern_conn_info *);
++extern int conn_free(struct iscsi_conn *);
++extern void iscsi_make_conn_rd_active(struct iscsi_conn *conn);
++#define ISCSI_CONN_ACTIVE_CLOSE 1
++#define ISCSI_CONN_DELETING 2
++extern void __mark_conn_closed(struct iscsi_conn *, int);
++extern void mark_conn_closed(struct iscsi_conn *);
++extern void iscsi_make_conn_wr_active(struct iscsi_conn *);
++extern void iscsi_check_tm_data_wait_timeouts(struct iscsi_conn *conn,
++ bool force);
++extern void __iscsi_write_space_ready(struct iscsi_conn *conn);
++
++/* nthread.c */
++extern int iscsi_send(struct iscsi_conn *conn);
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++extern void iscsi_get_page_callback(struct page *page);
++extern void iscsi_put_page_callback(struct page *page);
++#endif
++extern int istrd(void *arg);
++extern int istwr(void *arg);
++extern void iscsi_task_mgmt_affected_cmds_done(struct scst_mgmt_cmd *scst_mcmd);
++extern void req_add_to_write_timeout_list(struct iscsi_cmnd *req);
++
++/* target.c */
++extern const struct attribute *iscsi_tgt_attrs[];
++extern int iscsi_enable_target(struct scst_tgt *scst_tgt, bool enable);
++extern bool iscsi_is_target_enabled(struct scst_tgt *scst_tgt);
++extern ssize_t iscsi_sysfs_send_event(uint32_t tid,
++ enum iscsi_kern_event_code code,
++ const char *param1, const char *param2, void **data);
++extern struct iscsi_target *target_lookup_by_id(u32);
++extern int __add_target(struct iscsi_kern_target_info *);
++extern int __del_target(u32 id);
++extern ssize_t iscsi_sysfs_add_target(const char *target_name, char *params);
++extern ssize_t iscsi_sysfs_del_target(const char *target_name);
++extern ssize_t iscsi_sysfs_mgmt_cmd(char *cmd);
++extern void target_del_session(struct iscsi_target *target,
++ struct iscsi_session *session, int flags);
++extern void target_del_all_sess(struct iscsi_target *target, int flags);
++extern void target_del_all(void);
++
++/* config.c */
++extern const struct attribute *iscsi_attrs[];
++extern int iscsi_add_attr(struct iscsi_target *target,
++ const struct iscsi_kern_attr *user_info);
++extern void __iscsi_del_attr(struct iscsi_target *target,
++ struct iscsi_attr *tgt_attr);
++
++/* session.c */
++extern const struct attribute *iscsi_sess_attrs[];
++extern const struct file_operations session_seq_fops;
++extern struct iscsi_session *session_lookup(struct iscsi_target *, u64);
++extern void sess_reinst_finished(struct iscsi_session *);
++extern int __add_session(struct iscsi_target *,
++ struct iscsi_kern_session_info *);
++extern int __del_session(struct iscsi_target *, u64);
++extern int session_free(struct iscsi_session *session, bool del);
++extern void iscsi_sess_force_close(struct iscsi_session *sess);
++
++/* params.c */
++extern const char *iscsi_get_digest_name(int val, char *res);
++extern const char *iscsi_get_bool_value(int val);
++extern int iscsi_params_set(struct iscsi_target *,
++ struct iscsi_kern_params_info *, int);
++
++/* event.c */
++extern int event_send(u32, u64, u32, u32, enum iscsi_kern_event_code,
++ const char *param1, const char *param2);
++extern int event_init(void);
++extern void event_exit(void);
++
++#define get_pgcnt(size, offset) \
++ ((((size) + ((offset) & ~PAGE_MASK)) + PAGE_SIZE - 1) >> PAGE_SHIFT)
++
++static inline void iscsi_cmnd_get_length(struct iscsi_pdu *pdu)
++{
++#if defined(__BIG_ENDIAN)
++ pdu->ahssize = pdu->bhs.length.ahslength * 4;
++ pdu->datasize = pdu->bhs.length.datalength;
++#elif defined(__LITTLE_ENDIAN)
++ pdu->ahssize = ((__force __u32)pdu->bhs.length & 0xff) * 4;
++ pdu->datasize = be32_to_cpu((__force __be32)((__force __u32)pdu->bhs.length & ~0xff));
++#else
++#error
++#endif
++}
++
++static inline void iscsi_cmnd_set_length(struct iscsi_pdu *pdu)
++{
++#if defined(__BIG_ENDIAN)
++ pdu->bhs.length.ahslength = pdu->ahssize / 4;
++ pdu->bhs.length.datalength = pdu->datasize;
++#elif defined(__LITTLE_ENDIAN)
++ pdu->bhs.length = cpu_to_be32(pdu->datasize) | (__force __be32)(pdu->ahssize / 4);
++#else
++#error
++#endif
++}
++
++extern struct scst_tgt_template iscsi_template;
++
++/*
++ * Skip this command if result is true. Must be called under
++ * corresponding lock.
++ */
++static inline bool cmnd_get_check(struct iscsi_cmnd *cmnd)
++{
++ int r = atomic_inc_return(&cmnd->ref_cnt);
++ int res;
++ if (unlikely(r == 1)) {
++ TRACE_DBG("cmnd %p is being destroyed", cmnd);
++ atomic_dec(&cmnd->ref_cnt);
++ res = 1;
++ /* Necessary code is serialized by locks in cmnd_done() */
++ } else {
++ TRACE_DBG("cmnd %p, new ref_cnt %d", cmnd,
++ atomic_read(&cmnd->ref_cnt));
++ res = 0;
++ }
++ return res;
++}
++
++static inline void cmnd_get(struct iscsi_cmnd *cmnd)
++{
++ atomic_inc(&cmnd->ref_cnt);
++ TRACE_DBG("cmnd %p, new cmnd->ref_cnt %d", cmnd,
++ atomic_read(&cmnd->ref_cnt));
++ /*
++ * For the same reason as in kref_get(). Let's be safe and
++ * always do it.
++ */
++ smp_mb__after_atomic_inc();
++}
++
++static inline void cmnd_put(struct iscsi_cmnd *cmnd)
++{
++ TRACE_DBG("cmnd %p, new ref_cnt %d", cmnd,
++ atomic_read(&cmnd->ref_cnt)-1);
++
++ EXTRACHECKS_BUG_ON(atomic_read(&cmnd->ref_cnt) == 0);
++
++ if (atomic_dec_and_test(&cmnd->ref_cnt))
++ cmnd_done(cmnd);
++}
++
++/* conn->write_list_lock supposed to be locked and BHs off */
++static inline void cmd_add_on_write_list(struct iscsi_conn *conn,
++ struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_cmnd *parent = cmnd->parent_req;
++
++ TRACE_DBG("cmnd %p", cmnd);
++ /* See comment in iscsi_restart_cmnd() */
++ EXTRACHECKS_BUG_ON(cmnd->parent_req->hashed &&
++ (cmnd_opcode(cmnd) != ISCSI_OP_R2T));
++ list_add_tail(&cmnd->write_list_entry, &conn->write_list);
++ cmnd->on_write_list = 1;
++
++ parent->not_processed_rsp_cnt++;
++ TRACE_DBG("not processed rsp cnt %d (parent %p)",
++ parent->not_processed_rsp_cnt, parent);
++}
++
++/* conn->write_list_lock supposed to be locked and BHs off */
++static inline void cmd_del_from_write_list(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_cmnd *parent = cmnd->parent_req;
++
++ TRACE_DBG("%p", cmnd);
++ list_del(&cmnd->write_list_entry);
++ cmnd->on_write_list = 0;
++
++ parent->not_processed_rsp_cnt--;
++ TRACE_DBG("not processed rsp cnt %d (parent %p)",
++ parent->not_processed_rsp_cnt, parent);
++ EXTRACHECKS_BUG_ON(parent->not_processed_rsp_cnt < 0);
++}
++
++static inline void cmd_add_on_rx_ddigest_list(struct iscsi_cmnd *req,
++ struct iscsi_cmnd *cmnd)
++{
++ TRACE_DBG("Adding RX ddigest cmd %p to digest list "
++ "of req %p", cmnd, req);
++ list_add_tail(&cmnd->rx_ddigest_cmd_list_entry,
++ &req->rx_ddigest_cmd_list);
++#ifdef CONFIG_SCST_EXTRACHECKS
++ cmnd->on_rx_digest_list = 1;
++#endif
++}
++
++static inline void cmd_del_from_rx_ddigest_list(struct iscsi_cmnd *cmnd)
++{
++ TRACE_DBG("Deleting RX digest cmd %p from digest list", cmnd);
++ list_del(&cmnd->rx_ddigest_cmd_list_entry);
++#ifdef CONFIG_SCST_EXTRACHECKS
++ cmnd->on_rx_digest_list = 0;
++#endif
++}
++
++static inline unsigned long iscsi_get_timeout(struct iscsi_cmnd *req)
++{
++ unsigned long res;
++
++ res = (cmnd_opcode(req) == ISCSI_OP_NOP_OUT) ?
++ req->conn->nop_in_timeout : req->conn->data_rsp_timeout;
++
++ if (unlikely(test_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags)))
++ res = min_t(unsigned long, res, ISCSI_TM_DATA_WAIT_TIMEOUT);
++
++ return res;
++}
++
++static inline unsigned long iscsi_get_timeout_time(struct iscsi_cmnd *req)
++{
++ return req->write_start + iscsi_get_timeout(req);
++}
++
++static inline int test_write_ready(struct iscsi_conn *conn)
++{
++ /*
++ * No need for write_list protection, in the worst case we will be
++ * restarted again.
++ */
++ return !list_empty(&conn->write_list) || conn->write_cmnd;
++}
++
++static inline void conn_get(struct iscsi_conn *conn)
++{
++ atomic_inc(&conn->conn_ref_cnt);
++ TRACE_DBG("conn %p, new conn_ref_cnt %d", conn,
++ atomic_read(&conn->conn_ref_cnt));
++ /*
++ * For the same reason as in kref_get(). Let's be safe and
++ * always do it.
++ */
++ smp_mb__after_atomic_inc();
++}
++
++static inline void conn_put(struct iscsi_conn *conn)
++{
++ TRACE_DBG("conn %p, new conn_ref_cnt %d", conn,
++ atomic_read(&conn->conn_ref_cnt)-1);
++ BUG_ON(atomic_read(&conn->conn_ref_cnt) == 0);
++
++ /*
++ * Make it always ordered to protect from undesired side effects like
++ * accessing just destroyed by close_conn() conn caused by reordering
++ * of this atomic_dec().
++ */
++ smp_mb__before_atomic_dec();
++ atomic_dec(&conn->conn_ref_cnt);
++}
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++extern void iscsi_extracheck_is_rd_thread(struct iscsi_conn *conn);
++extern void iscsi_extracheck_is_wr_thread(struct iscsi_conn *conn);
++#else
++static inline void iscsi_extracheck_is_rd_thread(struct iscsi_conn *conn) {}
++static inline void iscsi_extracheck_is_wr_thread(struct iscsi_conn *conn) {}
++#endif
++
++#endif /* __ISCSI_H__ */
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/iscsi_dbg.h linux-3.2/drivers/scst/iscsi-scst/iscsi_dbg.h
+--- orig/linux-3.2/drivers/scst/iscsi-scst/iscsi_dbg.h
++++ linux-3.2/drivers/scst/iscsi-scst/iscsi_dbg.h
+@@ -0,0 +1,61 @@
++/*
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#ifndef ISCSI_DBG_H
++#define ISCSI_DBG_H
++
++#define LOG_PREFIX "iscsi-scst"
++
++#include <scst/scst_debug.h>
++
++#define TRACE_D_WRITE 0x80000000
++#define TRACE_CONN_OC 0x40000000
++#define TRACE_D_IOV 0x20000000
++#define TRACE_D_DUMP_PDU 0x10000000
++#define TRACE_NET_PG 0x08000000
++#define TRACE_CONN_OC_DBG 0x04000000
++
++#ifdef CONFIG_SCST_DEBUG
++#define ISCSI_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_LINE | TRACE_PID | \
++ TRACE_OUT_OF_MEM | TRACE_MGMT | TRACE_MGMT_DEBUG | \
++ TRACE_MINOR | TRACE_SPECIAL | TRACE_CONN_OC)
++#else
++#define ISCSI_DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \
++ TRACE_SPECIAL)
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++struct iscsi_pdu;
++struct iscsi_cmnd;
++extern void iscsi_dump_pdu(struct iscsi_pdu *pdu);
++extern unsigned long iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(
++ struct iscsi_cmnd *cmnd);
++#else
++#define iscsi_dump_pdu(x) do {} while (0)
++#define iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(x) do {} while (0)
++#endif
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++extern unsigned long iscsi_trace_flag;
++#define trace_flag iscsi_trace_flag
++#endif
++
++#define TRACE_CONN_CLOSE(args...) TRACE_DBG_FLAG(TRACE_DEBUG|TRACE_CONN_OC, args)
++#define TRACE_CONN_CLOSE_DBG(args...) TRACE(TRACE_CONN_OC_DBG, args)
++#define TRACE_NET_PAGE(args...) TRACE_DBG_FLAG(TRACE_NET_PG, args)
++#define TRACE_WRITE(args...) TRACE_DBG_FLAG(TRACE_DEBUG|TRACE_D_WRITE, args)
++
++#endif
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/iscsi_hdr.h linux-3.2/drivers/scst/iscsi-scst/iscsi_hdr.h
+--- orig/linux-3.2/drivers/scst/iscsi-scst/iscsi_hdr.h
++++ linux-3.2/drivers/scst/iscsi-scst/iscsi_hdr.h
+@@ -0,0 +1,526 @@
++/*
++ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@ardistech.com>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#ifndef __ISCSI_HDR_H__
++#define __ISCSI_HDR_H__
++
++#include <linux/types.h>
++#include <asm/byteorder.h>
++
++#define ISCSI_VERSION 0
++
++#ifndef __packed
++#define __packed __attribute__ ((packed))
++#endif
++
++/* iSCSI command PDU header. See also section 10.3 in RFC 3720. */
++struct iscsi_hdr {
++ u8 opcode; /* 0 */
++ u8 flags;
++ u8 spec1[2];
++#if defined(__BIG_ENDIAN_BITFIELD)
++ struct { /* 4 */
++ unsigned ahslength:8;
++ unsigned datalength:24;
++ } length;
++#elif defined(__LITTLE_ENDIAN_BITFIELD)
++ __be32 length; /* 4 */
++#endif
++ __be64 lun; /* 8 */
++ __be32 itt; /* 16 */
++ __be32 ttt; /* 20 */
++
++ /*
++ * SN fields most time stay converted to the CPU form and only received
++ * and send in the BE form.
++ */
++ u32 sn; /* 24 */
++ u32 exp_sn; /* 28 */
++ u32 max_sn; /* 32 */
++
++ __be32 spec3[3]; /* 36 */
++} __packed; /* 48 */
++
++/* Opcode encoding bits */
++#define ISCSI_OP_RETRY 0x80
++#define ISCSI_OP_IMMEDIATE 0x40
++#define ISCSI_OPCODE_MASK 0x3F
++
++/* Client to Server Message Opcode values */
++#define ISCSI_OP_NOP_OUT 0x00
++#define ISCSI_OP_SCSI_CMD 0x01
++#define ISCSI_OP_SCSI_TASK_MGT_MSG 0x02
++#define ISCSI_OP_LOGIN_CMD 0x03
++#define ISCSI_OP_TEXT_CMD 0x04
++#define ISCSI_OP_SCSI_DATA_OUT 0x05
++#define ISCSI_OP_LOGOUT_CMD 0x06
++#define ISCSI_OP_SNACK_CMD 0x10
++
++/* Server to Client Message Opcode values */
++#define ISCSI_OP_NOP_IN 0x20
++#define ISCSI_OP_SCSI_RSP 0x21
++#define ISCSI_OP_SCSI_TASK_MGT_RSP 0x22
++#define ISCSI_OP_LOGIN_RSP 0x23
++#define ISCSI_OP_TEXT_RSP 0x24
++#define ISCSI_OP_SCSI_DATA_IN 0x25
++#define ISCSI_OP_LOGOUT_RSP 0x26
++#define ISCSI_OP_R2T 0x31
++#define ISCSI_OP_ASYNC_MSG 0x32
++#define ISCSI_OP_REJECT 0x3f
++
++struct iscsi_ahs_hdr {
++ __be16 ahslength;
++ u8 ahstype;
++} __packed;
++
++#define ISCSI_AHSTYPE_CDB 1
++#define ISCSI_AHSTYPE_RLENGTH 2
++
++union iscsi_sid {
++ struct {
++ u8 isid[6]; /* Initiator Session ID */
++ __be16 tsih; /* Target Session ID */
++ } id;
++ __be64 id64;
++} __packed;
++
++struct iscsi_scsi_cmd_hdr {
++ u8 opcode;
++ u8 flags;
++ __be16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ __be64 lun;
++ __be32 itt;
++ __be32 data_length;
++ u32 cmd_sn;
++ u32 exp_stat_sn;
++ u8 scb[16];
++} __packed;
++
++#define ISCSI_CMD_FINAL 0x80
++#define ISCSI_CMD_READ 0x40
++#define ISCSI_CMD_WRITE 0x20
++#define ISCSI_CMD_ATTR_MASK 0x07
++#define ISCSI_CMD_UNTAGGED 0x00
++#define ISCSI_CMD_SIMPLE 0x01
++#define ISCSI_CMD_ORDERED 0x02
++#define ISCSI_CMD_HEAD_OF_QUEUE 0x03
++#define ISCSI_CMD_ACA 0x04
++
++struct iscsi_cdb_ahdr {
++ __be16 ahslength;
++ u8 ahstype;
++ u8 reserved;
++ u8 cdb[0];
++} __packed;
++
++struct iscsi_rlength_ahdr {
++ __be16 ahslength;
++ u8 ahstype;
++ u8 reserved;
++ __be32 read_length;
++} __packed;
++
++struct iscsi_scsi_rsp_hdr {
++ u8 opcode;
++ u8 flags;
++ u8 response;
++ u8 cmd_status;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd1[2];
++ __be32 itt;
++ __be32 snack;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ u32 exp_data_sn;
++ __be32 bi_residual_count;
++ __be32 residual_count;
++} __packed;
++
++#define ISCSI_FLG_RESIDUAL_UNDERFLOW 0x02
++#define ISCSI_FLG_RESIDUAL_OVERFLOW 0x04
++#define ISCSI_FLG_BIRESIDUAL_UNDERFLOW 0x08
++#define ISCSI_FLG_BIRESIDUAL_OVERFLOW 0x10
++
++#define ISCSI_RESPONSE_COMMAND_COMPLETED 0x00
++#define ISCSI_RESPONSE_TARGET_FAILURE 0x01
++
++struct iscsi_sense_data {
++ __be16 length;
++ u8 data[0];
++} __packed;
++
++struct iscsi_task_mgt_hdr {
++ u8 opcode;
++ u8 function;
++ __be16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ __be64 lun;
++ __be32 itt;
++ __be32 rtt;
++ u32 cmd_sn;
++ u32 exp_stat_sn;
++ u32 ref_cmd_sn;
++ u32 exp_data_sn;
++ u32 rsvd2[2];
++} __packed;
++
++#define ISCSI_FUNCTION_MASK 0x7f
++
++#define ISCSI_FUNCTION_ABORT_TASK 1
++#define ISCSI_FUNCTION_ABORT_TASK_SET 2
++#define ISCSI_FUNCTION_CLEAR_ACA 3
++#define ISCSI_FUNCTION_CLEAR_TASK_SET 4
++#define ISCSI_FUNCTION_LOGICAL_UNIT_RESET 5
++#define ISCSI_FUNCTION_TARGET_WARM_RESET 6
++#define ISCSI_FUNCTION_TARGET_COLD_RESET 7
++#define ISCSI_FUNCTION_TASK_REASSIGN 8
++
++struct iscsi_task_rsp_hdr {
++ u8 opcode;
++ u8 flags;
++ u8 response;
++ u8 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd2[2];
++ __be32 itt;
++ u32 rsvd3;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ u32 rsvd4[3];
++} __packed;
++
++#define ISCSI_RESPONSE_FUNCTION_COMPLETE 0
++#define ISCSI_RESPONSE_UNKNOWN_TASK 1
++#define ISCSI_RESPONSE_UNKNOWN_LUN 2
++#define ISCSI_RESPONSE_TASK_ALLEGIANT 3
++#define ISCSI_RESPONSE_ALLEGIANCE_REASSIGNMENT_UNSUPPORTED 4
++#define ISCSI_RESPONSE_FUNCTION_UNSUPPORTED 5
++#define ISCSI_RESPONSE_NO_AUTHORIZATION 6
++#define ISCSI_RESPONSE_FUNCTION_REJECTED 255
++
++struct iscsi_data_out_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ __be64 lun;
++ __be32 itt;
++ __be32 ttt;
++ u32 rsvd2;
++ u32 exp_stat_sn;
++ u32 rsvd3;
++ __be32 data_sn;
++ __be32 buffer_offset;
++ u32 rsvd4;
++} __packed;
++
++struct iscsi_data_in_hdr {
++ u8 opcode;
++ u8 flags;
++ u8 rsvd1;
++ u8 cmd_status;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd2[2];
++ __be32 itt;
++ __be32 ttt;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ __be32 data_sn;
++ __be32 buffer_offset;
++ __be32 residual_count;
++} __packed;
++
++#define ISCSI_FLG_STATUS 0x01
++
++struct iscsi_r2t_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ __be64 lun;
++ __be32 itt;
++ __be32 ttt;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ u32 r2t_sn;
++ __be32 buffer_offset;
++ __be32 data_length;
++} __packed;
++
++struct iscsi_async_msg_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ __be64 lun;
++ __be32 ffffffff;
++ u32 rsvd2;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ u8 async_event;
++ u8 async_vcode;
++ __be16 param1;
++ __be16 param2;
++ __be16 param3;
++ u32 rsvd3;
++} __packed;
++
++#define ISCSI_ASYNC_SCSI 0
++#define ISCSI_ASYNC_LOGOUT 1
++#define ISCSI_ASYNC_DROP_CONNECTION 2
++#define ISCSI_ASYNC_DROP_SESSION 3
++#define ISCSI_ASYNC_PARAM_REQUEST 4
++#define ISCSI_ASYNC_VENDOR 255
++
++struct iscsi_text_req_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd2[2];
++ __be32 itt;
++ __be32 ttt;
++ u32 cmd_sn;
++ u32 exp_stat_sn;
++ u32 rsvd3[4];
++} __packed;
++
++struct iscsi_text_rsp_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd2[2];
++ __be32 itt;
++ __be32 ttt;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ u32 rsvd3[3];
++} __packed;
++
++struct iscsi_login_req_hdr {
++ u8 opcode;
++ u8 flags;
++ u8 max_version; /* Max. version supported */
++ u8 min_version; /* Min. version supported */
++ u8 ahslength;
++ u8 datalength[3];
++ union iscsi_sid sid;
++ __be32 itt; /* Initiator Task Tag */
++ __be16 cid; /* Connection ID */
++ u16 rsvd1;
++ u32 cmd_sn;
++ u32 exp_stat_sn;
++ u32 rsvd2[4];
++} __packed;
++
++struct iscsi_login_rsp_hdr {
++ u8 opcode;
++ u8 flags;
++ u8 max_version; /* Max. version supported */
++ u8 active_version; /* Active version */
++ u8 ahslength;
++ u8 datalength[3];
++ union iscsi_sid sid;
++ __be32 itt; /* Initiator Task Tag */
++ u32 rsvd1;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ u8 status_class; /* see Login RSP Status classes below */
++ u8 status_detail; /* see Login RSP Status details below */
++ u8 rsvd2[10];
++} __packed;
++
++#define ISCSI_FLG_FINAL 0x80
++#define ISCSI_FLG_TRANSIT 0x80
++#define ISCSI_FLG_CSG_SECURITY 0x00
++#define ISCSI_FLG_CSG_LOGIN 0x04
++#define ISCSI_FLG_CSG_FULL_FEATURE 0x0c
++#define ISCSI_FLG_CSG_MASK 0x0c
++#define ISCSI_FLG_NSG_SECURITY 0x00
++#define ISCSI_FLG_NSG_LOGIN 0x01
++#define ISCSI_FLG_NSG_FULL_FEATURE 0x03
++#define ISCSI_FLG_NSG_MASK 0x03
++
++/* Login Status response classes */
++#define ISCSI_STATUS_SUCCESS 0x00
++#define ISCSI_STATUS_REDIRECT 0x01
++#define ISCSI_STATUS_INITIATOR_ERR 0x02
++#define ISCSI_STATUS_TARGET_ERR 0x03
++
++/* Login Status response detail codes */
++/* Class-0 (Success) */
++#define ISCSI_STATUS_ACCEPT 0x00
++
++/* Class-1 (Redirection) */
++#define ISCSI_STATUS_TGT_MOVED_TEMP 0x01
++#define ISCSI_STATUS_TGT_MOVED_PERM 0x02
++
++/* Class-2 (Initiator Error) */
++#define ISCSI_STATUS_INIT_ERR 0x00
++#define ISCSI_STATUS_AUTH_FAILED 0x01
++#define ISCSI_STATUS_TGT_FORBIDDEN 0x02
++#define ISCSI_STATUS_TGT_NOT_FOUND 0x03
++#define ISCSI_STATUS_TGT_REMOVED 0x04
++#define ISCSI_STATUS_NO_VERSION 0x05
++#define ISCSI_STATUS_TOO_MANY_CONN 0x06
++#define ISCSI_STATUS_MISSING_FIELDS 0x07
++#define ISCSI_STATUS_CONN_ADD_FAILED 0x08
++#define ISCSI_STATUS_INV_SESSION_TYPE 0x09
++#define ISCSI_STATUS_SESSION_NOT_FOUND 0x0a
++#define ISCSI_STATUS_INV_REQ_TYPE 0x0b
++
++/* Class-3 (Target Error) */
++#define ISCSI_STATUS_TARGET_ERROR 0x00
++#define ISCSI_STATUS_SVC_UNAVAILABLE 0x01
++#define ISCSI_STATUS_NO_RESOURCES 0x02
++
++struct iscsi_logout_req_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd2[2];
++ __be32 itt;
++ __be16 cid;
++ u16 rsvd3;
++ u32 cmd_sn;
++ u32 exp_stat_sn;
++ u32 rsvd4[4];
++} __packed;
++
++struct iscsi_logout_rsp_hdr {
++ u8 opcode;
++ u8 flags;
++ u8 response;
++ u8 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd2[2];
++ __be32 itt;
++ u32 rsvd3;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ u32 rsvd4;
++ __be16 time2wait;
++ __be16 time2retain;
++ u32 rsvd5;
++} __packed;
++
++struct iscsi_snack_req_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd2[2];
++ __be32 itt;
++ __be32 ttt;
++ u32 rsvd3;
++ u32 exp_stat_sn;
++ u32 rsvd4[2];
++ __be32 beg_run;
++ __be32 run_length;
++} __packed;
++
++struct iscsi_reject_hdr {
++ u8 opcode;
++ u8 flags;
++ u8 reason;
++ u8 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ u32 rsvd2[2];
++ __be32 ffffffff;
++ __be32 rsvd3;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ __be32 data_sn;
++ u32 rsvd4[2];
++} __packed;
++
++#define ISCSI_REASON_RESERVED 0x01
++#define ISCSI_REASON_DATA_DIGEST_ERROR 0x02
++#define ISCSI_REASON_DATA_SNACK_REJECT 0x03
++#define ISCSI_REASON_PROTOCOL_ERROR 0x04
++#define ISCSI_REASON_UNSUPPORTED_COMMAND 0x05
++#define ISCSI_REASON_IMMEDIATE_COMMAND_REJECT 0x06
++#define ISCSI_REASON_TASK_IN_PROGRESS 0x07
++#define ISCSI_REASON_INVALID_DATA_ACK 0x08
++#define ISCSI_REASON_INVALID_PDU_FIELD 0x09
++#define ISCSI_REASON_OUT_OF_RESOURCES 0x0a
++#define ISCSI_REASON_NEGOTIATION_RESET 0x0b
++#define ISCSI_REASON_WAITING_LOGOUT 0x0c
++
++struct iscsi_nop_out_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ __be64 lun;
++ __be32 itt;
++ __be32 ttt;
++ u32 cmd_sn;
++ u32 exp_stat_sn;
++ u32 rsvd2[4];
++} __packed;
++
++struct iscsi_nop_in_hdr {
++ u8 opcode;
++ u8 flags;
++ u16 rsvd1;
++ u8 ahslength;
++ u8 datalength[3];
++ __be64 lun;
++ __be32 itt;
++ __be32 ttt;
++ u32 stat_sn;
++ u32 exp_cmd_sn;
++ u32 max_cmd_sn;
++ u32 rsvd2[3];
++} __packed;
++
++#define ISCSI_RESERVED_TAG_CPU32 (0xffffffffU)
++#define ISCSI_RESERVED_TAG (__constant_cpu_to_be32(ISCSI_RESERVED_TAG_CPU32))
++
++#define cmnd_hdr(cmnd) ((struct iscsi_scsi_cmd_hdr *) (&((cmnd)->pdu.bhs)))
++#define cmnd_opcode(cmnd) ((cmnd)->pdu.bhs.opcode & ISCSI_OPCODE_MASK)
++#define cmnd_scsicode(cmnd) (cmnd_hdr((cmnd))->scb[0])
++
++#endif /* __ISCSI_HDR_H__ */
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/nthread.c linux-3.2/drivers/scst/iscsi-scst/nthread.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/nthread.c
++++ linux-3.2/drivers/scst/iscsi-scst/nthread.c
+@@ -0,0 +1,1891 @@
++/*
++ * Network threads.
++ *
++ * Copyright (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation.
++ *
++ * 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/sched.h>
++#include <linux/file.h>
++#include <linux/kthread.h>
++#include <linux/delay.h>
++#include <net/tcp.h>
++
++#include "iscsi.h"
++#include "digest.h"
++
++/* Read data states */
++enum rx_state {
++ RX_INIT_BHS, /* Must be zero for better "switch" optimization. */
++ RX_BHS,
++ RX_CMD_START,
++ RX_DATA,
++ RX_END,
++
++ RX_CMD_CONTINUE,
++ RX_INIT_HDIGEST,
++ RX_CHECK_HDIGEST,
++ RX_INIT_DDIGEST,
++ RX_CHECK_DDIGEST,
++ RX_AHS,
++ RX_PADDING,
++};
++
++enum tx_state {
++ TX_INIT = 0, /* Must be zero for better "switch" optimization. */
++ TX_BHS_DATA,
++ TX_INIT_PADDING,
++ TX_PADDING,
++ TX_INIT_DDIGEST,
++ TX_DDIGEST,
++ TX_END,
++};
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++static void iscsi_check_closewait(struct iscsi_conn *conn)
++{
++ struct iscsi_cmnd *cmnd;
++
++ TRACE_ENTRY();
++
++ TRACE_CONN_CLOSE_DBG("conn %p, sk_state %d", conn,
++ conn->sock->sk->sk_state);
++
++ if (conn->sock->sk->sk_state != TCP_CLOSE) {
++ TRACE_CONN_CLOSE_DBG("conn %p, skipping", conn);
++ goto out;
++ }
++
++ /*
++ * No data are going to be sent, so all queued buffers can be freed
++ * now. In many cases TCP does that only in close(), but we can't rely
++ * on user space on calling it.
++ */
++
++again:
++ spin_lock_bh(&conn->cmd_list_lock);
++ list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) {
++ struct iscsi_cmnd *rsp;
++ int restart = 0;
++
++ TRACE_CONN_CLOSE_DBG("cmd %p, scst_state %x, "
++ "r2t_len_to_receive %d, ref_cnt %d, parent_req %p, "
++ "net_ref_cnt %d, sg %p", cmnd, cmnd->scst_state,
++ cmnd->r2t_len_to_receive, atomic_read(&cmnd->ref_cnt),
++ cmnd->parent_req, atomic_read(&cmnd->net_ref_cnt),
++ cmnd->sg);
++
++ BUG_ON(cmnd->parent_req != NULL);
++
++ if (cmnd->sg != NULL) {
++ int i;
++
++ if (cmnd_get_check(cmnd))
++ continue;
++
++ for (i = 0; i < cmnd->sg_cnt; i++) {
++ struct page *page = sg_page(&cmnd->sg[i]);
++ TRACE_CONN_CLOSE_DBG("page %p, net_priv %p, "
++ "_count %d", page, page->net_priv,
++ atomic_read(&page->_count));
++
++ if (page->net_priv != NULL) {
++ if (restart == 0) {
++ spin_unlock_bh(&conn->cmd_list_lock);
++ restart = 1;
++ }
++ while (page->net_priv != NULL)
++ iscsi_put_page_callback(page);
++ }
++ }
++ cmnd_put(cmnd);
++
++ if (restart)
++ goto again;
++ }
++
++ list_for_each_entry(rsp, &cmnd->rsp_cmd_list,
++ rsp_cmd_list_entry) {
++ TRACE_CONN_CLOSE_DBG(" rsp %p, ref_cnt %d, "
++ "net_ref_cnt %d, sg %p",
++ rsp, atomic_read(&rsp->ref_cnt),
++ atomic_read(&rsp->net_ref_cnt), rsp->sg);
++
++ if ((rsp->sg != cmnd->sg) && (rsp->sg != NULL)) {
++ int i;
++
++ if (cmnd_get_check(rsp))
++ continue;
++
++ for (i = 0; i < rsp->sg_cnt; i++) {
++ struct page *page =
++ sg_page(&rsp->sg[i]);
++ TRACE_CONN_CLOSE_DBG(
++ " page %p, net_priv %p, "
++ "_count %d",
++ page, page->net_priv,
++ atomic_read(&page->_count));
++
++ if (page->net_priv != NULL) {
++ if (restart == 0) {
++ spin_unlock_bh(&conn->cmd_list_lock);
++ restart = 1;
++ }
++ while (page->net_priv != NULL)
++ iscsi_put_page_callback(page);
++ }
++ }
++ cmnd_put(rsp);
++
++ if (restart)
++ goto again;
++ }
++ }
++ }
++ spin_unlock_bh(&conn->cmd_list_lock);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++#else
++static inline void iscsi_check_closewait(struct iscsi_conn *conn) {};
++#endif
++
++static void free_pending_commands(struct iscsi_conn *conn)
++{
++ struct iscsi_session *session = conn->session;
++ struct list_head *pending_list = &session->pending_list;
++ int req_freed;
++ struct iscsi_cmnd *cmnd;
++
++ spin_lock(&session->sn_lock);
++ do {
++ req_freed = 0;
++ list_for_each_entry(cmnd, pending_list, pending_list_entry) {
++ TRACE_CONN_CLOSE_DBG("Pending cmd %p"
++ "(conn %p, cmd_sn %u, exp_cmd_sn %u)",
++ cmnd, conn, cmnd->pdu.bhs.sn,
++ session->exp_cmd_sn);
++ if ((cmnd->conn == conn) &&
++ (session->exp_cmd_sn == cmnd->pdu.bhs.sn)) {
++ TRACE_MGMT_DBG("Freeing pending cmd %p "
++ "(cmd_sn %u, exp_cmd_sn %u)",
++ cmnd, cmnd->pdu.bhs.sn,
++ session->exp_cmd_sn);
++
++ list_del(&cmnd->pending_list_entry);
++ cmnd->pending = 0;
++
++ session->exp_cmd_sn++;
++
++ spin_unlock(&session->sn_lock);
++
++ req_cmnd_release_force(cmnd);
++
++ req_freed = 1;
++ spin_lock(&session->sn_lock);
++ break;
++ }
++ }
++ } while (req_freed);
++ spin_unlock(&session->sn_lock);
++
++ return;
++}
++
++static void free_orphaned_pending_commands(struct iscsi_conn *conn)
++{
++ struct iscsi_session *session = conn->session;
++ struct list_head *pending_list = &session->pending_list;
++ int req_freed;
++ struct iscsi_cmnd *cmnd;
++
++ spin_lock(&session->sn_lock);
++ do {
++ req_freed = 0;
++ list_for_each_entry(cmnd, pending_list, pending_list_entry) {
++ TRACE_CONN_CLOSE_DBG("Pending cmd %p"
++ "(conn %p, cmd_sn %u, exp_cmd_sn %u)",
++ cmnd, conn, cmnd->pdu.bhs.sn,
++ session->exp_cmd_sn);
++ if (cmnd->conn == conn) {
++ TRACE_MGMT_DBG("Freeing orphaned pending "
++ "cmnd %p (cmd_sn %u, exp_cmd_sn %u)",
++ cmnd, cmnd->pdu.bhs.sn,
++ session->exp_cmd_sn);
++
++ list_del(&cmnd->pending_list_entry);
++ cmnd->pending = 0;
++
++ if (session->exp_cmd_sn == cmnd->pdu.bhs.sn)
++ session->exp_cmd_sn++;
++
++ spin_unlock(&session->sn_lock);
++
++ req_cmnd_release_force(cmnd);
++
++ req_freed = 1;
++ spin_lock(&session->sn_lock);
++ break;
++ }
++ }
++ } while (req_freed);
++ spin_unlock(&session->sn_lock);
++
++ return;
++}
++
++#ifdef CONFIG_SCST_DEBUG
++static void trace_conn_close(struct iscsi_conn *conn)
++{
++ struct iscsi_cmnd *cmnd;
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ struct iscsi_cmnd *rsp;
++#endif
++
++#if 0
++ if (time_after(jiffies, start_waiting + 10*HZ))
++ trace_flag |= TRACE_CONN_OC_DBG;
++#endif
++
++ spin_lock_bh(&conn->cmd_list_lock);
++ list_for_each_entry(cmnd, &conn->cmd_list,
++ cmd_list_entry) {
++ TRACE_CONN_CLOSE_DBG(
++ "cmd %p, scst_cmd %p, scst_state %x, scst_cmd state "
++ "%d, r2t_len_to_receive %d, ref_cnt %d, sn %u, "
++ "parent_req %p, pending %d",
++ cmnd, cmnd->scst_cmd, cmnd->scst_state,
++ ((cmnd->parent_req == NULL) && cmnd->scst_cmd) ?
++ cmnd->scst_cmd->state : -1,
++ cmnd->r2t_len_to_receive, atomic_read(&cmnd->ref_cnt),
++ cmnd->pdu.bhs.sn, cmnd->parent_req, cmnd->pending);
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ TRACE_CONN_CLOSE_DBG("net_ref_cnt %d, sg %p",
++ atomic_read(&cmnd->net_ref_cnt),
++ cmnd->sg);
++ if (cmnd->sg != NULL) {
++ int i;
++ for (i = 0; i < cmnd->sg_cnt; i++) {
++ struct page *page = sg_page(&cmnd->sg[i]);
++ TRACE_CONN_CLOSE_DBG("page %p, "
++ "net_priv %p, _count %d",
++ page, page->net_priv,
++ atomic_read(&page->_count));
++ }
++ }
++
++ BUG_ON(cmnd->parent_req != NULL);
++
++ list_for_each_entry(rsp, &cmnd->rsp_cmd_list,
++ rsp_cmd_list_entry) {
++ TRACE_CONN_CLOSE_DBG(" rsp %p, "
++ "ref_cnt %d, net_ref_cnt %d, sg %p",
++ rsp, atomic_read(&rsp->ref_cnt),
++ atomic_read(&rsp->net_ref_cnt), rsp->sg);
++ if (rsp->sg != cmnd->sg && rsp->sg) {
++ int i;
++ for (i = 0; i < rsp->sg_cnt; i++) {
++ TRACE_CONN_CLOSE_DBG(" page %p, "
++ "net_priv %p, _count %d",
++ sg_page(&rsp->sg[i]),
++ sg_page(&rsp->sg[i])->net_priv,
++ atomic_read(&sg_page(&rsp->sg[i])->
++ _count));
++ }
++ }
++ }
++#endif /* CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION */
++ }
++ spin_unlock_bh(&conn->cmd_list_lock);
++ return;
++}
++#else /* CONFIG_SCST_DEBUG */
++static void trace_conn_close(struct iscsi_conn *conn) {}
++#endif /* CONFIG_SCST_DEBUG */
++
++void iscsi_task_mgmt_affected_cmds_done(struct scst_mgmt_cmd *scst_mcmd)
++{
++ int fn = scst_mgmt_cmd_get_fn(scst_mcmd);
++ void *priv = scst_mgmt_cmd_get_tgt_priv(scst_mcmd);
++
++ TRACE_MGMT_DBG("scst_mcmd %p, fn %d, priv %p", scst_mcmd, fn, priv);
++
++ switch (fn) {
++ case SCST_NEXUS_LOSS_SESS:
++ case SCST_ABORT_ALL_TASKS_SESS:
++ {
++ struct iscsi_conn *conn = (struct iscsi_conn *)priv;
++ struct iscsi_session *sess = conn->session;
++ struct iscsi_conn *c;
++
++ if (sess->sess_reinst_successor != NULL)
++ scst_reassign_persistent_sess_states(
++ sess->sess_reinst_successor->scst_sess,
++ sess->scst_sess);
++
++ mutex_lock(&sess->target->target_mutex);
++
++ /*
++ * We can't mark sess as shutting down earlier, because until
++ * now it might have pending commands. Otherwise, in case of
++ * reinstatement, it might lead to data corruption, because
++ * commands in being reinstated session can be executed
++ * after commands in the new session.
++ */
++ sess->sess_shutting_down = 1;
++ list_for_each_entry(c, &sess->conn_list, conn_list_entry) {
++ if (!test_bit(ISCSI_CONN_SHUTTINGDOWN, &c->conn_aflags)) {
++ sess->sess_shutting_down = 0;
++ break;
++ }
++ }
++
++ if (conn->conn_reinst_successor != NULL) {
++ BUG_ON(!test_bit(ISCSI_CONN_REINSTATING,
++ &conn->conn_reinst_successor->conn_aflags));
++ conn_reinst_finished(conn->conn_reinst_successor);
++ conn->conn_reinst_successor = NULL;
++ } else if (sess->sess_reinst_successor != NULL) {
++ sess_reinst_finished(sess->sess_reinst_successor);
++ sess->sess_reinst_successor = NULL;
++ }
++ mutex_unlock(&sess->target->target_mutex);
++
++ complete_all(&conn->ready_to_free);
++ break;
++ }
++ default:
++ /* Nothing to do */
++ break;
++ }
++
++ return;
++}
++
++/* No locks */
++static void close_conn(struct iscsi_conn *conn)
++{
++ struct iscsi_session *session = conn->session;
++ struct iscsi_target *target = conn->target;
++ typeof(jiffies) start_waiting = jiffies;
++ typeof(jiffies) shut_start_waiting = start_waiting;
++ bool pending_reported = 0, wait_expired = 0, shut_expired = 0;
++ bool reinst;
++ uint32_t tid, cid;
++ uint64_t sid;
++
++#define CONN_PENDING_TIMEOUT ((typeof(jiffies))10*HZ)
++#define CONN_WAIT_TIMEOUT ((typeof(jiffies))10*HZ)
++#define CONN_REG_SHUT_TIMEOUT ((typeof(jiffies))125*HZ)
++#define CONN_DEL_SHUT_TIMEOUT ((typeof(jiffies))10*HZ)
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Closing connection %p (conn_ref_cnt=%d)", conn,
++ atomic_read(&conn->conn_ref_cnt));
++
++ iscsi_extracheck_is_rd_thread(conn);
++
++ BUG_ON(!conn->closing);
++
++ if (conn->active_close) {
++ /* We want all our already send operations to complete */
++ conn->sock->ops->shutdown(conn->sock, RCV_SHUTDOWN);
++ } else {
++ conn->sock->ops->shutdown(conn->sock,
++ RCV_SHUTDOWN|SEND_SHUTDOWN);
++ }
++
++ mutex_lock(&session->target->target_mutex);
++
++ set_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags);
++ reinst = (conn->conn_reinst_successor != NULL);
++
++ mutex_unlock(&session->target->target_mutex);
++
++ if (reinst) {
++ int rc;
++ int lun = 0;
++
++ /* Abort all outstanding commands */
++ rc = scst_rx_mgmt_fn_lun(session->scst_sess,
++ SCST_ABORT_ALL_TASKS_SESS, (uint8_t *)&lun, sizeof(lun),
++ SCST_NON_ATOMIC, conn);
++ if (rc != 0)
++ PRINT_ERROR("SCST_ABORT_ALL_TASKS_SESS failed %d", rc);
++ } else {
++ int rc;
++ int lun = 0;
++
++ rc = scst_rx_mgmt_fn_lun(session->scst_sess,
++ SCST_NEXUS_LOSS_SESS, (uint8_t *)&lun, sizeof(lun),
++ SCST_NON_ATOMIC, conn);
++ if (rc != 0)
++ PRINT_ERROR("SCST_NEXUS_LOSS_SESS failed %d", rc);
++ }
++
++ if (conn->read_state != RX_INIT_BHS) {
++ struct iscsi_cmnd *cmnd = conn->read_cmnd;
++
++ if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) {
++ TRACE_CONN_CLOSE_DBG("Going to wait for cmnd %p to "
++ "change state from RX_CMD", cmnd);
++ }
++ wait_event(conn->read_state_waitQ,
++ cmnd->scst_state != ISCSI_CMD_STATE_RX_CMD);
++
++ TRACE_CONN_CLOSE_DBG("Releasing conn->read_cmnd %p (conn %p)",
++ conn->read_cmnd, conn);
++
++ conn->read_cmnd = NULL;
++ conn->read_state = RX_INIT_BHS;
++ req_cmnd_release_force(cmnd);
++ }
++
++ conn_abort(conn);
++
++ /* ToDo: not the best way to wait */
++ while (atomic_read(&conn->conn_ref_cnt) != 0) {
++ if (conn->conn_tm_active)
++ iscsi_check_tm_data_wait_timeouts(conn, true);
++
++ mutex_lock(&target->target_mutex);
++ spin_lock(&session->sn_lock);
++ if (session->tm_rsp && session->tm_rsp->conn == conn) {
++ struct iscsi_cmnd *tm_rsp = session->tm_rsp;
++ TRACE_MGMT_DBG("Dropping delayed TM rsp %p", tm_rsp);
++ session->tm_rsp = NULL;
++ session->tm_active--;
++ WARN_ON(session->tm_active < 0);
++ spin_unlock(&session->sn_lock);
++ mutex_unlock(&target->target_mutex);
++
++ rsp_cmnd_release(tm_rsp);
++ } else {
++ spin_unlock(&session->sn_lock);
++ mutex_unlock(&target->target_mutex);
++ }
++
++ /* It's safe to check it without sn_lock */
++ if (!list_empty(&session->pending_list)) {
++ TRACE_CONN_CLOSE_DBG("Disposing pending commands on "
++ "connection %p (conn_ref_cnt=%d)", conn,
++ atomic_read(&conn->conn_ref_cnt));
++
++ free_pending_commands(conn);
++
++ if (time_after(jiffies,
++ start_waiting + CONN_PENDING_TIMEOUT)) {
++ if (!pending_reported) {
++ TRACE_CONN_CLOSE("%s",
++ "Pending wait time expired");
++ pending_reported = 1;
++ }
++ free_orphaned_pending_commands(conn);
++ }
++ }
++
++ iscsi_make_conn_wr_active(conn);
++
++ /* That's for active close only, actually */
++ if (time_after(jiffies, start_waiting + CONN_WAIT_TIMEOUT) &&
++ !wait_expired) {
++ TRACE_CONN_CLOSE("Wait time expired (conn %p, "
++ "sk_state %d)",
++ conn, conn->sock->sk->sk_state);
++ conn->sock->ops->shutdown(conn->sock, SEND_SHUTDOWN);
++ wait_expired = 1;
++ shut_start_waiting = jiffies;
++ }
++
++ if (wait_expired && !shut_expired &&
++ time_after(jiffies, shut_start_waiting +
++ conn->deleting ? CONN_DEL_SHUT_TIMEOUT :
++ CONN_REG_SHUT_TIMEOUT)) {
++ TRACE_CONN_CLOSE("Wait time after shutdown expired "
++ "(conn %p, sk_state %d)", conn,
++ conn->sock->sk->sk_state);
++ conn->sock->sk->sk_prot->disconnect(conn->sock->sk, 0);
++ shut_expired = 1;
++ }
++
++ if (conn->deleting)
++ msleep(200);
++ else
++ msleep(1000);
++
++ TRACE_CONN_CLOSE_DBG("conn %p, conn_ref_cnt %d left, "
++ "wr_state %d, exp_cmd_sn %u",
++ conn, atomic_read(&conn->conn_ref_cnt),
++ conn->wr_state, session->exp_cmd_sn);
++
++ trace_conn_close(conn);
++
++ /* It might never be called for being closed conn */
++ __iscsi_write_space_ready(conn);
++
++ iscsi_check_closewait(conn);
++ }
++
++ write_lock_bh(&conn->sock->sk->sk_callback_lock);
++ conn->sock->sk->sk_state_change = conn->old_state_change;
++ conn->sock->sk->sk_data_ready = conn->old_data_ready;
++ conn->sock->sk->sk_write_space = conn->old_write_space;
++ write_unlock_bh(&conn->sock->sk->sk_callback_lock);
++
++ while (1) {
++ bool t;
++
++ spin_lock_bh(&conn->conn_thr_pool->wr_lock);
++ t = (conn->wr_state == ISCSI_CONN_WR_STATE_IDLE);
++ spin_unlock_bh(&conn->conn_thr_pool->wr_lock);
++
++ if (t && (atomic_read(&conn->conn_ref_cnt) == 0))
++ break;
++
++ TRACE_CONN_CLOSE_DBG("Waiting for wr thread (conn %p), "
++ "wr_state %x", conn, conn->wr_state);
++ msleep(50);
++ }
++
++ wait_for_completion(&conn->ready_to_free);
++
++ tid = target->tid;
++ sid = session->sid;
++ cid = conn->cid;
++
++ mutex_lock(&target->target_mutex);
++ conn_free(conn);
++ mutex_unlock(&target->target_mutex);
++
++ /*
++ * We can't send E_CONN_CLOSE earlier, because otherwise we would have
++ * a race, when the user space tried to destroy session, which still
++ * has connections.
++ *
++ * !! All target, session and conn can be already dead here !!
++ */
++ TRACE_CONN_CLOSE("Notifying user space about closing connection %p",
++ conn);
++ event_send(tid, sid, cid, 0, E_CONN_CLOSE, NULL, NULL);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int close_conn_thr(void *arg)
++{
++ struct iscsi_conn *conn = (struct iscsi_conn *)arg;
++
++ TRACE_ENTRY();
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ /*
++ * To satisfy iscsi_extracheck_is_rd_thread() in functions called
++ * on the connection close. It is safe, because at this point conn
++ * can't be used by any other thread.
++ */
++ conn->rd_task = current;
++#endif
++ close_conn(conn);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++/* No locks */
++static void start_close_conn(struct iscsi_conn *conn)
++{
++ struct task_struct *t;
++
++ TRACE_ENTRY();
++
++ t = kthread_run(close_conn_thr, conn, "iscsi_conn_cleanup");
++ if (IS_ERR(t)) {
++ PRINT_ERROR("kthread_run() failed (%ld), closing conn %p "
++ "directly", PTR_ERR(t), conn);
++ close_conn(conn);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline void iscsi_conn_init_read(struct iscsi_conn *conn,
++ void __user *data, size_t len)
++{
++ conn->read_iov[0].iov_base = data;
++ conn->read_iov[0].iov_len = len;
++ conn->read_msg.msg_iov = conn->read_iov;
++ conn->read_msg.msg_iovlen = 1;
++ conn->read_size = len;
++ return;
++}
++
++static void iscsi_conn_prepare_read_ahs(struct iscsi_conn *conn,
++ struct iscsi_cmnd *cmnd)
++{
++ int asize = (cmnd->pdu.ahssize + 3) & -4;
++
++ /* ToDo: __GFP_NOFAIL ?? */
++ cmnd->pdu.ahs = kmalloc(asize, __GFP_NOFAIL|GFP_KERNEL);
++ BUG_ON(cmnd->pdu.ahs == NULL);
++ iscsi_conn_init_read(conn, (void __force __user *)cmnd->pdu.ahs, asize);
++ return;
++}
++
++static struct iscsi_cmnd *iscsi_get_send_cmnd(struct iscsi_conn *conn)
++{
++ struct iscsi_cmnd *cmnd = NULL;
++
++ spin_lock_bh(&conn->write_list_lock);
++ if (!list_empty(&conn->write_list)) {
++ cmnd = list_entry(conn->write_list.next, struct iscsi_cmnd,
++ write_list_entry);
++ cmd_del_from_write_list(cmnd);
++ cmnd->write_processing_started = 1;
++ } else {
++ spin_unlock_bh(&conn->write_list_lock);
++ goto out;
++ }
++ spin_unlock_bh(&conn->write_list_lock);
++
++ if (unlikely(test_bit(ISCSI_CMD_ABORTED,
++ &cmnd->parent_req->prelim_compl_flags))) {
++ TRACE_MGMT_DBG("Going to send acmd %p (scst cmd %p, "
++ "state %d, parent_req %p)", cmnd, cmnd->scst_cmd,
++ cmnd->scst_state, cmnd->parent_req);
++ }
++
++ if (unlikely(cmnd_opcode(cmnd) == ISCSI_OP_SCSI_TASK_MGT_RSP)) {
++#ifdef CONFIG_SCST_DEBUG
++ struct iscsi_task_mgt_hdr *req_hdr =
++ (struct iscsi_task_mgt_hdr *)&cmnd->parent_req->pdu.bhs;
++ struct iscsi_task_rsp_hdr *rsp_hdr =
++ (struct iscsi_task_rsp_hdr *)&cmnd->pdu.bhs;
++ TRACE_MGMT_DBG("Going to send TM response %p (status %d, "
++ "fn %d, parent_req %p)", cmnd, rsp_hdr->response,
++ req_hdr->function & ISCSI_FUNCTION_MASK,
++ cmnd->parent_req);
++#endif
++ }
++
++out:
++ return cmnd;
++}
++
++/* Returns number of bytes left to receive or <0 for error */
++static int do_recv(struct iscsi_conn *conn)
++{
++ int res;
++ mm_segment_t oldfs;
++ struct msghdr msg;
++ int first_len;
++
++ EXTRACHECKS_BUG_ON(conn->read_cmnd == NULL);
++
++ if (unlikely(conn->closing)) {
++ res = -EIO;
++ goto out;
++ }
++
++ /*
++ * We suppose that if sock_recvmsg() returned less data than requested,
++ * then next time it will return -EAGAIN, so there's no point to call
++ * it again.
++ */
++
++restart:
++ memset(&msg, 0, sizeof(msg));
++ msg.msg_iov = conn->read_msg.msg_iov;
++ msg.msg_iovlen = conn->read_msg.msg_iovlen;
++ first_len = msg.msg_iov->iov_len;
++
++ oldfs = get_fs();
++ set_fs(get_ds());
++ res = sock_recvmsg(conn->sock, &msg, conn->read_size,
++ MSG_DONTWAIT | MSG_NOSIGNAL);
++ set_fs(oldfs);
++
++ TRACE_DBG("msg_iovlen %zd, first_len %d, read_size %d, res %d",
++ msg.msg_iovlen, first_len, conn->read_size, res);
++
++ if (res > 0) {
++ /*
++ * To save some considerable effort and CPU power we
++ * suppose that TCP functions adjust
++ * conn->read_msg.msg_iov and conn->read_msg.msg_iovlen
++ * on amount of copied data. This BUG_ON is intended
++ * to catch if it is changed in the future.
++ */
++ BUG_ON((res >= first_len) &&
++ (conn->read_msg.msg_iov->iov_len != 0));
++ conn->read_size -= res;
++ if (conn->read_size != 0) {
++ if (res >= first_len) {
++ int done = 1 + ((res - first_len) >> PAGE_SHIFT);
++ TRACE_DBG("done %d", done);
++ conn->read_msg.msg_iov += done;
++ conn->read_msg.msg_iovlen -= done;
++ }
++ }
++ res = conn->read_size;
++ } else {
++ switch (res) {
++ case -EAGAIN:
++ TRACE_DBG("EAGAIN received for conn %p", conn);
++ res = conn->read_size;
++ break;
++ case -ERESTARTSYS:
++ TRACE_DBG("ERESTARTSYS received for conn %p", conn);
++ goto restart;
++ default:
++ if (!conn->closing) {
++ PRINT_ERROR("sock_recvmsg() failed: %d", res);
++ mark_conn_closed(conn);
++ }
++ if (res == 0)
++ res = -EIO;
++ break;
++ }
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int iscsi_rx_check_ddigest(struct iscsi_conn *conn)
++{
++ struct iscsi_cmnd *cmnd = conn->read_cmnd;
++ int res;
++
++ res = do_recv(conn);
++ if (res == 0) {
++ conn->read_state = RX_END;
++
++ if (cmnd->pdu.datasize <= 16*1024) {
++ /*
++ * It's cache hot, so let's compute it inline. The
++ * choice here about what will expose more latency:
++ * possible cache misses or the digest calculation.
++ */
++ TRACE_DBG("cmnd %p, opcode %x: checking RX "
++ "ddigest inline", cmnd, cmnd_opcode(cmnd));
++ cmnd->ddigest_checked = 1;
++ res = digest_rx_data(cmnd);
++ if (unlikely(res != 0)) {
++ struct iscsi_cmnd *orig_req;
++ if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_DATA_OUT)
++ orig_req = cmnd->cmd_req;
++ else
++ orig_req = cmnd;
++ if (unlikely(orig_req->scst_cmd == NULL)) {
++ /* Just drop it */
++ iscsi_preliminary_complete(cmnd, orig_req, false);
++ } else {
++ set_scst_preliminary_status_rsp(orig_req, false,
++ SCST_LOAD_SENSE(iscsi_sense_crc_error));
++ /*
++ * Let's prelim complete cmnd too to
++ * handle the DATA OUT case
++ */
++ iscsi_preliminary_complete(cmnd, orig_req, false);
++ }
++ res = 0;
++ }
++ } else if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_CMD) {
++ cmd_add_on_rx_ddigest_list(cmnd, cmnd);
++ cmnd_get(cmnd);
++ } else if (cmnd_opcode(cmnd) != ISCSI_OP_SCSI_DATA_OUT) {
++ /*
++ * We could get here only for Nop-Out. ISCSI RFC
++ * doesn't specify how to deal with digest errors in
++ * this case. Let's just drop the command.
++ */
++ TRACE_DBG("cmnd %p, opcode %x: checking NOP RX "
++ "ddigest", cmnd, cmnd_opcode(cmnd));
++ res = digest_rx_data(cmnd);
++ if (unlikely(res != 0)) {
++ iscsi_preliminary_complete(cmnd, cmnd, false);
++ res = 0;
++ }
++ }
++ }
++
++ return res;
++}
++
++/* No locks, conn is rd processing */
++static int process_read_io(struct iscsi_conn *conn, int *closed)
++{
++ struct iscsi_cmnd *cmnd = conn->read_cmnd;
++ int res;
++
++ TRACE_ENTRY();
++
++ /* In case of error cmnd will be freed in close_conn() */
++
++ do {
++ switch (conn->read_state) {
++ case RX_INIT_BHS:
++ EXTRACHECKS_BUG_ON(conn->read_cmnd != NULL);
++ cmnd = cmnd_alloc(conn, NULL);
++ conn->read_cmnd = cmnd;
++ iscsi_conn_init_read(cmnd->conn,
++ (void __force __user *)&cmnd->pdu.bhs,
++ sizeof(cmnd->pdu.bhs));
++ conn->read_state = RX_BHS;
++ /* go through */
++
++ case RX_BHS:
++ res = do_recv(conn);
++ if (res == 0) {
++ /*
++ * This command not yet received on the aborted
++ * time, so shouldn't be affected by any abort.
++ */
++ EXTRACHECKS_BUG_ON(cmnd->prelim_compl_flags != 0);
++
++ iscsi_cmnd_get_length(&cmnd->pdu);
++
++ if (cmnd->pdu.ahssize == 0) {
++ if ((conn->hdigest_type & DIGEST_NONE) == 0)
++ conn->read_state = RX_INIT_HDIGEST;
++ else
++ conn->read_state = RX_CMD_START;
++ } else {
++ iscsi_conn_prepare_read_ahs(conn, cmnd);
++ conn->read_state = RX_AHS;
++ }
++ }
++ break;
++
++ case RX_CMD_START:
++ res = cmnd_rx_start(cmnd);
++ if (res == 0) {
++ if (cmnd->pdu.datasize == 0)
++ conn->read_state = RX_END;
++ else
++ conn->read_state = RX_DATA;
++ } else if (res > 0)
++ conn->read_state = RX_CMD_CONTINUE;
++ else
++ BUG_ON(!conn->closing);
++ break;
++
++ case RX_CMD_CONTINUE:
++ if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) {
++ TRACE_DBG("cmnd %p is still in RX_CMD state",
++ cmnd);
++ res = 1;
++ break;
++ }
++ res = cmnd_rx_continue(cmnd);
++ if (unlikely(res != 0))
++ BUG_ON(!conn->closing);
++ else {
++ if (cmnd->pdu.datasize == 0)
++ conn->read_state = RX_END;
++ else
++ conn->read_state = RX_DATA;
++ }
++ break;
++
++ case RX_DATA:
++ res = do_recv(conn);
++ if (res == 0) {
++ int psz = ((cmnd->pdu.datasize + 3) & -4) - cmnd->pdu.datasize;
++ if (psz != 0) {
++ TRACE_DBG("padding %d bytes", psz);
++ iscsi_conn_init_read(conn,
++ (void __force __user *)&conn->rpadding, psz);
++ conn->read_state = RX_PADDING;
++ } else if ((conn->ddigest_type & DIGEST_NONE) != 0)
++ conn->read_state = RX_END;
++ else
++ conn->read_state = RX_INIT_DDIGEST;
++ }
++ break;
++
++ case RX_END:
++ if (unlikely(conn->read_size != 0)) {
++ PRINT_CRIT_ERROR("conn read_size !=0 on RX_END "
++ "(conn %p, op %x, read_size %d)", conn,
++ cmnd_opcode(cmnd), conn->read_size);
++ BUG();
++ }
++ conn->read_cmnd = NULL;
++ conn->read_state = RX_INIT_BHS;
++
++ cmnd_rx_end(cmnd);
++
++ EXTRACHECKS_BUG_ON(conn->read_size != 0);
++
++ /*
++ * To maintain fairness. Res must be 0 here anyway, the
++ * assignment is only to remove compiler warning about
++ * uninitialized variable.
++ */
++ res = 0;
++ goto out;
++
++ case RX_INIT_HDIGEST:
++ iscsi_conn_init_read(conn,
++ (void __force __user *)&cmnd->hdigest, sizeof(u32));
++ conn->read_state = RX_CHECK_HDIGEST;
++ /* go through */
++
++ case RX_CHECK_HDIGEST:
++ res = do_recv(conn);
++ if (res == 0) {
++ res = digest_rx_header(cmnd);
++ if (unlikely(res != 0)) {
++ PRINT_ERROR("rx header digest for "
++ "initiator %s failed (%d)",
++ conn->session->initiator_name,
++ res);
++ mark_conn_closed(conn);
++ } else
++ conn->read_state = RX_CMD_START;
++ }
++ break;
++
++ case RX_INIT_DDIGEST:
++ iscsi_conn_init_read(conn,
++ (void __force __user *)&cmnd->ddigest,
++ sizeof(u32));
++ conn->read_state = RX_CHECK_DDIGEST;
++ /* go through */
++
++ case RX_CHECK_DDIGEST:
++ res = iscsi_rx_check_ddigest(conn);
++ break;
++
++ case RX_AHS:
++ res = do_recv(conn);
++ if (res == 0) {
++ if ((conn->hdigest_type & DIGEST_NONE) == 0)
++ conn->read_state = RX_INIT_HDIGEST;
++ else
++ conn->read_state = RX_CMD_START;
++ }
++ break;
++
++ case RX_PADDING:
++ res = do_recv(conn);
++ if (res == 0) {
++ if ((conn->ddigest_type & DIGEST_NONE) == 0)
++ conn->read_state = RX_INIT_DDIGEST;
++ else
++ conn->read_state = RX_END;
++ }
++ break;
++
++ default:
++ PRINT_CRIT_ERROR("%d %x", conn->read_state, cmnd_opcode(cmnd));
++ res = -1; /* to keep compiler happy */
++ BUG();
++ }
++ } while (res == 0);
++
++ if (unlikely(conn->closing)) {
++ start_close_conn(conn);
++ *closed = 1;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * Called under rd_lock and BHs disabled, but will drop it inside,
++ * then reacquire.
++ */
++static void scst_do_job_rd(struct iscsi_thread_pool *p)
++ __acquires(&rd_lock)
++ __releases(&rd_lock)
++{
++ TRACE_ENTRY();
++
++ /*
++ * We delete/add to tail connections to maintain fairness between them.
++ */
++
++ while (!list_empty(&p->rd_list)) {
++ int closed = 0, rc;
++ struct iscsi_conn *conn = list_entry(p->rd_list.next,
++ typeof(*conn), rd_list_entry);
++
++ list_del(&conn->rd_list_entry);
++
++ BUG_ON(conn->rd_state == ISCSI_CONN_RD_STATE_PROCESSING);
++ conn->rd_data_ready = 0;
++ conn->rd_state = ISCSI_CONN_RD_STATE_PROCESSING;
++#ifdef CONFIG_SCST_EXTRACHECKS
++ conn->rd_task = current;
++#endif
++ spin_unlock_bh(&p->rd_lock);
++
++ rc = process_read_io(conn, &closed);
++
++ spin_lock_bh(&p->rd_lock);
++
++ if (unlikely(closed))
++ continue;
++
++ if (unlikely(conn->conn_tm_active)) {
++ spin_unlock_bh(&p->rd_lock);
++ iscsi_check_tm_data_wait_timeouts(conn, false);
++ spin_lock_bh(&p->rd_lock);
++ }
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ conn->rd_task = NULL;
++#endif
++ if ((rc == 0) || conn->rd_data_ready) {
++ list_add_tail(&conn->rd_list_entry, &p->rd_list);
++ conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST;
++ } else
++ conn->rd_state = ISCSI_CONN_RD_STATE_IDLE;
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline int test_rd_list(struct iscsi_thread_pool *p)
++{
++ int res = !list_empty(&p->rd_list) ||
++ unlikely(kthread_should_stop());
++ return res;
++}
++
++int istrd(void *arg)
++{
++ struct iscsi_thread_pool *p = arg;
++ int rc;
++
++ TRACE_ENTRY();
++
++ PRINT_INFO("Read thread for pool %p started, PID %d", p, current->pid);
++
++ current->flags |= PF_NOFREEZE;
++ rc = set_cpus_allowed_ptr(current, &p->cpu_mask);
++ if (rc != 0)
++ PRINT_ERROR("Setting CPU affinity failed: %d", rc);
++
++ spin_lock_bh(&p->rd_lock);
++ while (!kthread_should_stop()) {
++ wait_queue_t wait;
++ init_waitqueue_entry(&wait, current);
++
++ if (!test_rd_list(p)) {
++ add_wait_queue_exclusive_head(&p->rd_waitQ, &wait);
++ for (;;) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (test_rd_list(p))
++ break;
++ spin_unlock_bh(&p->rd_lock);
++ schedule();
++ spin_lock_bh(&p->rd_lock);
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&p->rd_waitQ, &wait);
++ }
++ scst_do_job_rd(p);
++ }
++ spin_unlock_bh(&p->rd_lock);
++
++ /*
++ * If kthread_should_stop() is true, we are guaranteed to be
++ * on the module unload, so rd_list must be empty.
++ */
++ BUG_ON(!list_empty(&p->rd_list));
++
++ PRINT_INFO("Read thread for PID %d for pool %p finished", current->pid, p);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++static inline void __iscsi_get_page_callback(struct iscsi_cmnd *cmd)
++{
++ int v;
++
++ TRACE_NET_PAGE("cmd %p, new net_ref_cnt %d",
++ cmd, atomic_read(&cmd->net_ref_cnt)+1);
++
++ v = atomic_inc_return(&cmd->net_ref_cnt);
++ if (v == 1) {
++ TRACE_NET_PAGE("getting cmd %p", cmd);
++ cmnd_get(cmd);
++ }
++ return;
++}
++
++void iscsi_get_page_callback(struct page *page)
++{
++ struct iscsi_cmnd *cmd = (struct iscsi_cmnd *)page->net_priv;
++
++ TRACE_NET_PAGE("page %p, _count %d", page,
++ atomic_read(&page->_count));
++
++ __iscsi_get_page_callback(cmd);
++ return;
++}
++
++static inline void __iscsi_put_page_callback(struct iscsi_cmnd *cmd)
++{
++ TRACE_NET_PAGE("cmd %p, new net_ref_cnt %d", cmd,
++ atomic_read(&cmd->net_ref_cnt)-1);
++
++ if (atomic_dec_and_test(&cmd->net_ref_cnt)) {
++ int i, sg_cnt = cmd->sg_cnt;
++ for (i = 0; i < sg_cnt; i++) {
++ struct page *page = sg_page(&cmd->sg[i]);
++ TRACE_NET_PAGE("Clearing page %p", page);
++ if (page->net_priv == cmd)
++ page->net_priv = NULL;
++ }
++ cmnd_put(cmd);
++ }
++ return;
++}
++
++void iscsi_put_page_callback(struct page *page)
++{
++ struct iscsi_cmnd *cmd = (struct iscsi_cmnd *)page->net_priv;
++
++ TRACE_NET_PAGE("page %p, _count %d", page,
++ atomic_read(&page->_count));
++
++ __iscsi_put_page_callback(cmd);
++ return;
++}
++
++static void check_net_priv(struct iscsi_cmnd *cmd, struct page *page)
++{
++ if ((atomic_read(&cmd->net_ref_cnt) == 1) && (page->net_priv == cmd)) {
++ TRACE_DBG("sendpage() not called get_page(), zeroing net_priv "
++ "%p (page %p)", page->net_priv, page);
++ page->net_priv = NULL;
++ }
++ return;
++}
++#else
++static inline void check_net_priv(struct iscsi_cmnd *cmd, struct page *page) {}
++static inline void __iscsi_get_page_callback(struct iscsi_cmnd *cmd) {}
++static inline void __iscsi_put_page_callback(struct iscsi_cmnd *cmd) {}
++#endif
++
++void req_add_to_write_timeout_list(struct iscsi_cmnd *req)
++{
++ struct iscsi_conn *conn;
++ bool set_conn_tm_active = false;
++
++ TRACE_ENTRY();
++
++ if (req->on_write_timeout_list)
++ goto out;
++
++ conn = req->conn;
++
++ TRACE_DBG("Adding req %p to conn %p write_timeout_list",
++ req, conn);
++
++ spin_lock_bh(&conn->write_list_lock);
++
++ /* Recheck, since it can be changed behind us */
++ if (unlikely(req->on_write_timeout_list)) {
++ spin_unlock_bh(&conn->write_list_lock);
++ goto out;
++ }
++
++ req->on_write_timeout_list = 1;
++ req->write_start = jiffies;
++
++ if (unlikely(cmnd_opcode(req) == ISCSI_OP_NOP_OUT)) {
++ unsigned long req_tt = iscsi_get_timeout_time(req);
++ struct iscsi_cmnd *r;
++ bool inserted = false;
++ list_for_each_entry(r, &conn->write_timeout_list,
++ write_timeout_list_entry) {
++ unsigned long tt = iscsi_get_timeout_time(r);
++ if (time_after(tt, req_tt)) {
++ TRACE_DBG("Add NOP IN req %p (tt %ld) before "
++ "req %p (tt %ld)", req, req_tt, r, tt);
++ list_add_tail(&req->write_timeout_list_entry,
++ &r->write_timeout_list_entry);
++ inserted = true;
++ break;
++ } else
++ TRACE_DBG("Skipping op %x req %p (tt %ld)",
++ cmnd_opcode(r), r, tt);
++ }
++ if (!inserted) {
++ TRACE_DBG("Add NOP IN req %p in the tail", req);
++ list_add_tail(&req->write_timeout_list_entry,
++ &conn->write_timeout_list);
++ }
++
++ /* We suppose that nop_in_timeout must be <= data_rsp_timeout */
++ req_tt += ISCSI_ADD_SCHED_TIME;
++ if (timer_pending(&conn->rsp_timer) &&
++ time_after(conn->rsp_timer.expires, req_tt)) {
++ TRACE_DBG("Timer adjusted for sooner expired NOP IN "
++ "req %p", req);
++ mod_timer(&conn->rsp_timer, req_tt);
++ }
++ } else
++ list_add_tail(&req->write_timeout_list_entry,
++ &conn->write_timeout_list);
++
++ if (!timer_pending(&conn->rsp_timer)) {
++ unsigned long timeout_time;
++ if (unlikely(conn->conn_tm_active ||
++ test_bit(ISCSI_CMD_ABORTED,
++ &req->prelim_compl_flags))) {
++ set_conn_tm_active = true;
++ timeout_time = req->write_start +
++ ISCSI_TM_DATA_WAIT_TIMEOUT;
++ } else
++ timeout_time = iscsi_get_timeout_time(req);
++
++ timeout_time += ISCSI_ADD_SCHED_TIME;
++
++ TRACE_DBG("Starting timer on %ld (con %p, write_start %ld)",
++ timeout_time, conn, req->write_start);
++
++ conn->rsp_timer.expires = timeout_time;
++ add_timer(&conn->rsp_timer);
++ } else if (unlikely(test_bit(ISCSI_CMD_ABORTED,
++ &req->prelim_compl_flags))) {
++ unsigned long timeout_time = jiffies +
++ ISCSI_TM_DATA_WAIT_TIMEOUT + ISCSI_ADD_SCHED_TIME;
++ set_conn_tm_active = true;
++ if (time_after(conn->rsp_timer.expires, timeout_time)) {
++ TRACE_MGMT_DBG("Mod timer on %ld (conn %p)",
++ timeout_time, conn);
++ mod_timer(&conn->rsp_timer, timeout_time);
++ }
++ }
++
++ spin_unlock_bh(&conn->write_list_lock);
++
++ /*
++ * conn_tm_active can be already cleared by
++ * iscsi_check_tm_data_wait_timeouts(). write_list_lock is an inner
++ * lock for rd_lock.
++ */
++ if (unlikely(set_conn_tm_active)) {
++ spin_lock_bh(&conn->conn_thr_pool->rd_lock);
++ TRACE_MGMT_DBG("Setting conn_tm_active for conn %p", conn);
++ conn->conn_tm_active = 1;
++ spin_unlock_bh(&conn->conn_thr_pool->rd_lock);
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int write_data(struct iscsi_conn *conn)
++{
++ mm_segment_t oldfs;
++ struct file *file;
++ struct iovec *iop;
++ struct socket *sock;
++ ssize_t (*sock_sendpage)(struct socket *, struct page *, int, size_t,
++ int);
++ ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int);
++ struct iscsi_cmnd *write_cmnd = conn->write_cmnd;
++ struct iscsi_cmnd *ref_cmd;
++ struct page *page;
++ struct scatterlist *sg;
++ int saved_size, size, sendsize;
++ int length, offset, idx;
++ int flags, res, count, sg_size;
++ bool do_put = false, ref_cmd_to_parent;
++
++ TRACE_ENTRY();
++
++ iscsi_extracheck_is_wr_thread(conn);
++
++ if (!write_cmnd->own_sg) {
++ ref_cmd = write_cmnd->parent_req;
++ ref_cmd_to_parent = true;
++ } else {
++ ref_cmd = write_cmnd;
++ ref_cmd_to_parent = false;
++ }
++
++ req_add_to_write_timeout_list(write_cmnd->parent_req);
++
++ file = conn->file;
++ size = conn->write_size;
++ saved_size = size;
++ iop = conn->write_iop;
++ count = conn->write_iop_used;
++
++ if (iop) {
++ while (1) {
++ loff_t off = 0;
++ int rest;
++
++ BUG_ON(count > (signed)(sizeof(conn->write_iov) /
++ sizeof(conn->write_iov[0])));
++retry:
++ oldfs = get_fs();
++ set_fs(KERNEL_DS);
++ res = vfs_writev(file,
++ (struct iovec __force __user *)iop,
++ count, &off);
++ set_fs(oldfs);
++ TRACE_WRITE("sid %#Lx, cid %u, res %d, iov_len %ld",
++ (long long unsigned int)conn->session->sid,
++ conn->cid, res, (long)iop->iov_len);
++ if (unlikely(res <= 0)) {
++ if (res == -EAGAIN) {
++ conn->write_iop = iop;
++ conn->write_iop_used = count;
++ goto out_iov;
++ } else if (res == -EINTR)
++ goto retry;
++ goto out_err;
++ }
++
++ rest = res;
++ size -= res;
++ while ((typeof(rest))iop->iov_len <= rest && rest) {
++ rest -= iop->iov_len;
++ iop++;
++ count--;
++ }
++ if (count == 0) {
++ conn->write_iop = NULL;
++ conn->write_iop_used = 0;
++ if (size)
++ break;
++ goto out_iov;
++ }
++ BUG_ON(iop > conn->write_iov + sizeof(conn->write_iov)
++ /sizeof(conn->write_iov[0]));
++ iop->iov_base += rest;
++ iop->iov_len -= rest;
++ }
++ }
++
++ sg = write_cmnd->sg;
++ if (unlikely(sg == NULL)) {
++ PRINT_INFO("WARNING: Data missed (cmd %p)!", write_cmnd);
++ res = 0;
++ goto out;
++ }
++
++ /* To protect from too early transfer completion race */
++ __iscsi_get_page_callback(ref_cmd);
++ do_put = true;
++
++ sock = conn->sock;
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ sock_sendpage = sock->ops->sendpage;
++#else
++ if ((write_cmnd->parent_req->scst_cmd != NULL) &&
++ scst_cmd_get_dh_data_buff_alloced(write_cmnd->parent_req->scst_cmd))
++ sock_sendpage = sock_no_sendpage;
++ else
++ sock_sendpage = sock->ops->sendpage;
++#endif
++
++ flags = MSG_DONTWAIT;
++ sg_size = size;
++
++ if (sg != write_cmnd->rsp_sg) {
++ offset = conn->write_offset + sg[0].offset;
++ idx = offset >> PAGE_SHIFT;
++ offset &= ~PAGE_MASK;
++ length = min(size, (int)PAGE_SIZE - offset);
++ TRACE_WRITE("write_offset %d, sg_size %d, idx %d, offset %d, "
++ "length %d", conn->write_offset, sg_size, idx, offset,
++ length);
++ } else {
++ idx = 0;
++ offset = conn->write_offset;
++ while (offset >= sg[idx].length) {
++ offset -= sg[idx].length;
++ idx++;
++ }
++ length = sg[idx].length - offset;
++ offset += sg[idx].offset;
++ sock_sendpage = sock_no_sendpage;
++ TRACE_WRITE("rsp_sg: write_offset %d, sg_size %d, idx %d, "
++ "offset %d, length %d", conn->write_offset, sg_size,
++ idx, offset, length);
++ }
++ page = sg_page(&sg[idx]);
++
++ while (1) {
++ sendpage = sock_sendpage;
++
++#if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION)
++ {
++ static DEFINE_SPINLOCK(net_priv_lock);
++ spin_lock(&net_priv_lock);
++ if (unlikely(page->net_priv != NULL)) {
++ if (page->net_priv != ref_cmd) {
++ /*
++ * This might happen if user space
++ * supplies to scst_user the same
++ * pages in different commands or in
++ * case of zero-copy FILEIO, when
++ * several initiators request the same
++ * data simultaneously.
++ */
++ TRACE_DBG("net_priv isn't NULL and != "
++ "ref_cmd (write_cmnd %p, ref_cmd "
++ "%p, sg %p, idx %d, page %p, "
++ "net_priv %p)",
++ write_cmnd, ref_cmd, sg, idx,
++ page, page->net_priv);
++ sendpage = sock_no_sendpage;
++ }
++ } else
++ page->net_priv = ref_cmd;
++ spin_unlock(&net_priv_lock);
++ }
++#endif
++ sendsize = min(size, length);
++ if (size <= sendsize) {
++retry2:
++ res = sendpage(sock, page, offset, size, flags);
++ TRACE_WRITE("Final %s sid %#Lx, cid %u, res %d (page "
++ "index %lu, offset %u, size %u, cmd %p, "
++ "page %p)", (sendpage != sock_no_sendpage) ?
++ "sendpage" : "sock_no_sendpage",
++ (long long unsigned int)conn->session->sid,
++ conn->cid, res, page->index,
++ offset, size, write_cmnd, page);
++ if (unlikely(res <= 0)) {
++ if (res == -EINTR)
++ goto retry2;
++ else
++ goto out_res;
++ }
++
++ check_net_priv(ref_cmd, page);
++ if (res == size) {
++ conn->write_size = 0;
++ res = saved_size;
++ goto out_put;
++ }
++
++ offset += res;
++ size -= res;
++ goto retry2;
++ }
++
++retry1:
++ res = sendpage(sock, page, offset, sendsize, flags | MSG_MORE);
++ TRACE_WRITE("%s sid %#Lx, cid %u, res %d (page index %lu, "
++ "offset %u, sendsize %u, size %u, cmd %p, page %p)",
++ (sendpage != sock_no_sendpage) ? "sendpage" :
++ "sock_no_sendpage",
++ (unsigned long long)conn->session->sid, conn->cid,
++ res, page->index, offset, sendsize, size,
++ write_cmnd, page);
++ if (unlikely(res <= 0)) {
++ if (res == -EINTR)
++ goto retry1;
++ else
++ goto out_res;
++ }
++
++ check_net_priv(ref_cmd, page);
++
++ size -= res;
++
++ if (res == sendsize) {
++ idx++;
++ EXTRACHECKS_BUG_ON(idx >= ref_cmd->sg_cnt);
++ page = sg_page(&sg[idx]);
++ length = sg[idx].length;
++ offset = sg[idx].offset;
++ } else {
++ offset += res;
++ sendsize -= res;
++ goto retry1;
++ }
++ }
++
++out_off:
++ conn->write_offset += sg_size - size;
++
++out_iov:
++ conn->write_size = size;
++ if ((saved_size == size) && res == -EAGAIN)
++ goto out_put;
++
++ res = saved_size - size;
++
++out_put:
++ if (do_put)
++ __iscsi_put_page_callback(ref_cmd);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_res:
++ check_net_priv(ref_cmd, page);
++ if (res == -EAGAIN)
++ goto out_off;
++ /* else go through */
++
++out_err:
++#ifndef CONFIG_SCST_DEBUG
++ if (!conn->closing)
++#endif
++ {
++ PRINT_ERROR("error %d at sid:cid %#Lx:%u, cmnd %p", res,
++ (long long unsigned int)conn->session->sid,
++ conn->cid, conn->write_cmnd);
++ }
++ if (ref_cmd_to_parent &&
++ ((ref_cmd->scst_cmd != NULL) || (ref_cmd->scst_aen != NULL))) {
++ if (ref_cmd->scst_state == ISCSI_CMD_STATE_AEN)
++ scst_set_aen_delivery_status(ref_cmd->scst_aen,
++ SCST_AEN_RES_FAILED);
++ else
++ scst_set_delivery_status(ref_cmd->scst_cmd,
++ SCST_CMD_DELIVERY_FAILED);
++ }
++ goto out_put;
++}
++
++static int exit_tx(struct iscsi_conn *conn, int res)
++{
++ iscsi_extracheck_is_wr_thread(conn);
++
++ switch (res) {
++ case -EAGAIN:
++ case -ERESTARTSYS:
++ break;
++ default:
++#ifndef CONFIG_SCST_DEBUG
++ if (!conn->closing)
++#endif
++ {
++ PRINT_ERROR("Sending data failed: initiator %s, "
++ "write_size %d, write_state %d, res %d",
++ conn->session->initiator_name,
++ conn->write_size,
++ conn->write_state, res);
++ }
++ conn->write_state = TX_END;
++ conn->write_size = 0;
++ mark_conn_closed(conn);
++ break;
++ }
++ return res;
++}
++
++static int tx_ddigest(struct iscsi_cmnd *cmnd, int state)
++{
++ int res, rest = cmnd->conn->write_size;
++ struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT};
++ struct kvec iov;
++
++ iscsi_extracheck_is_wr_thread(cmnd->conn);
++
++ TRACE_DBG("Sending data digest %x (cmd %p)", cmnd->ddigest, cmnd);
++
++ iov.iov_base = (char *)(&cmnd->ddigest) + (sizeof(u32) - rest);
++ iov.iov_len = rest;
++
++ res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest);
++ if (res > 0) {
++ cmnd->conn->write_size -= res;
++ if (!cmnd->conn->write_size)
++ cmnd->conn->write_state = state;
++ } else
++ res = exit_tx(cmnd->conn, res);
++
++ return res;
++}
++
++static void init_tx_hdigest(struct iscsi_cmnd *cmnd)
++{
++ struct iscsi_conn *conn = cmnd->conn;
++ struct iovec *iop;
++
++ iscsi_extracheck_is_wr_thread(conn);
++
++ digest_tx_header(cmnd);
++
++ BUG_ON(conn->write_iop_used >=
++ (signed)(sizeof(conn->write_iov)/sizeof(conn->write_iov[0])));
++
++ iop = &conn->write_iop[conn->write_iop_used];
++ conn->write_iop_used++;
++ iop->iov_base = (void __force __user *)&(cmnd->hdigest);
++ iop->iov_len = sizeof(u32);
++ conn->write_size += sizeof(u32);
++
++ return;
++}
++
++static int tx_padding(struct iscsi_cmnd *cmnd, int state)
++{
++ int res, rest = cmnd->conn->write_size;
++ struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT};
++ struct kvec iov;
++ static const uint32_t padding;
++
++ iscsi_extracheck_is_wr_thread(cmnd->conn);
++
++ TRACE_DBG("Sending %d padding bytes (cmd %p)", rest, cmnd);
++
++ iov.iov_base = (char *)(&padding) + (sizeof(uint32_t) - rest);
++ iov.iov_len = rest;
++
++ res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest);
++ if (res > 0) {
++ cmnd->conn->write_size -= res;
++ if (!cmnd->conn->write_size)
++ cmnd->conn->write_state = state;
++ } else
++ res = exit_tx(cmnd->conn, res);
++
++ return res;
++}
++
++static int iscsi_do_send(struct iscsi_conn *conn, int state)
++{
++ int res;
++
++ iscsi_extracheck_is_wr_thread(conn);
++
++ res = write_data(conn);
++ if (res > 0) {
++ if (!conn->write_size)
++ conn->write_state = state;
++ } else
++ res = exit_tx(conn, res);
++
++ return res;
++}
++
++/*
++ * No locks, conn is wr processing.
++ *
++ * IMPORTANT! Connection conn must be protected by additional conn_get()
++ * upon entrance in this function, because otherwise it could be destroyed
++ * inside as a result of cmnd release.
++ */
++int iscsi_send(struct iscsi_conn *conn)
++{
++ struct iscsi_cmnd *cmnd = conn->write_cmnd;
++ int ddigest, res = 0;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("conn %p, write_cmnd %p", conn, cmnd);
++
++ iscsi_extracheck_is_wr_thread(conn);
++
++ ddigest = conn->ddigest_type != DIGEST_NONE ? 1 : 0;
++
++ switch (conn->write_state) {
++ case TX_INIT:
++ BUG_ON(cmnd != NULL);
++ cmnd = conn->write_cmnd = iscsi_get_send_cmnd(conn);
++ if (!cmnd)
++ goto out;
++ cmnd_tx_start(cmnd);
++ if (!(conn->hdigest_type & DIGEST_NONE))
++ init_tx_hdigest(cmnd);
++ conn->write_state = TX_BHS_DATA;
++ case TX_BHS_DATA:
++ res = iscsi_do_send(conn, cmnd->pdu.datasize ?
++ TX_INIT_PADDING : TX_END);
++ if (res <= 0 || conn->write_state != TX_INIT_PADDING)
++ break;
++ case TX_INIT_PADDING:
++ cmnd->conn->write_size = ((cmnd->pdu.datasize + 3) & -4) -
++ cmnd->pdu.datasize;
++ if (cmnd->conn->write_size != 0)
++ conn->write_state = TX_PADDING;
++ else if (ddigest)
++ conn->write_state = TX_INIT_DDIGEST;
++ else
++ conn->write_state = TX_END;
++ break;
++ case TX_PADDING:
++ res = tx_padding(cmnd, ddigest ? TX_INIT_DDIGEST : TX_END);
++ if (res <= 0 || conn->write_state != TX_INIT_DDIGEST)
++ break;
++ case TX_INIT_DDIGEST:
++ cmnd->conn->write_size = sizeof(u32);
++ conn->write_state = TX_DDIGEST;
++ case TX_DDIGEST:
++ res = tx_ddigest(cmnd, TX_END);
++ break;
++ default:
++ PRINT_CRIT_ERROR("%d %d %x", res, conn->write_state,
++ cmnd_opcode(cmnd));
++ BUG();
++ }
++
++ if (res == 0)
++ goto out;
++
++ if (conn->write_state != TX_END)
++ goto out;
++
++ if (unlikely(conn->write_size)) {
++ PRINT_CRIT_ERROR("%d %x %u", res, cmnd_opcode(cmnd),
++ conn->write_size);
++ BUG();
++ }
++ cmnd_tx_end(cmnd);
++
++ rsp_cmnd_release(cmnd);
++
++ conn->write_cmnd = NULL;
++ conn->write_state = TX_INIT;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * Called under wr_lock and BHs disabled, but will drop it inside,
++ * then reacquire.
++ */
++static void scst_do_job_wr(struct iscsi_thread_pool *p)
++ __acquires(&wr_lock)
++ __releases(&wr_lock)
++{
++ TRACE_ENTRY();
++
++ /*
++ * We delete/add to tail connections to maintain fairness between them.
++ */
++
++ while (!list_empty(&p->wr_list)) {
++ int rc;
++ struct iscsi_conn *conn = list_entry(p->wr_list.next,
++ typeof(*conn), wr_list_entry);
++
++ TRACE_DBG("conn %p, wr_state %x, wr_space_ready %d, "
++ "write ready %d", conn, conn->wr_state,
++ conn->wr_space_ready, test_write_ready(conn));
++
++ list_del(&conn->wr_list_entry);
++
++ BUG_ON(conn->wr_state == ISCSI_CONN_WR_STATE_PROCESSING);
++
++ conn->wr_state = ISCSI_CONN_WR_STATE_PROCESSING;
++ conn->wr_space_ready = 0;
++#ifdef CONFIG_SCST_EXTRACHECKS
++ conn->wr_task = current;
++#endif
++ spin_unlock_bh(&p->wr_lock);
++
++ conn_get(conn);
++
++ rc = iscsi_send(conn);
++
++ spin_lock_bh(&p->wr_lock);
++#ifdef CONFIG_SCST_EXTRACHECKS
++ conn->wr_task = NULL;
++#endif
++ if ((rc == -EAGAIN) && !conn->wr_space_ready) {
++ TRACE_DBG("EAGAIN, setting WR_STATE_SPACE_WAIT "
++ "(conn %p)", conn);
++ conn->wr_state = ISCSI_CONN_WR_STATE_SPACE_WAIT;
++ } else if (test_write_ready(conn)) {
++ list_add_tail(&conn->wr_list_entry, &p->wr_list);
++ conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
++ } else
++ conn->wr_state = ISCSI_CONN_WR_STATE_IDLE;
++
++ conn_put(conn);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline int test_wr_list(struct iscsi_thread_pool *p)
++{
++ int res = !list_empty(&p->wr_list) ||
++ unlikely(kthread_should_stop());
++ return res;
++}
++
++int istwr(void *arg)
++{
++ struct iscsi_thread_pool *p = arg;
++ int rc;
++
++ TRACE_ENTRY();
++
++ PRINT_INFO("Write thread for pool %p started, PID %d", p, current->pid);
++
++ current->flags |= PF_NOFREEZE;
++ rc = set_cpus_allowed_ptr(current, &p->cpu_mask);
++ if (rc != 0)
++ PRINT_ERROR("Setting CPU affinity failed: %d", rc);
++
++ spin_lock_bh(&p->wr_lock);
++ while (!kthread_should_stop()) {
++ wait_queue_t wait;
++ init_waitqueue_entry(&wait, current);
++
++ if (!test_wr_list(p)) {
++ add_wait_queue_exclusive_head(&p->wr_waitQ, &wait);
++ for (;;) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ if (test_wr_list(p))
++ break;
++ spin_unlock_bh(&p->wr_lock);
++ schedule();
++ spin_lock_bh(&p->wr_lock);
++ }
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&p->wr_waitQ, &wait);
++ }
++ scst_do_job_wr(p);
++ }
++ spin_unlock_bh(&p->wr_lock);
++
++ /*
++ * If kthread_should_stop() is true, we are guaranteed to be
++ * on the module unload, so wr_list must be empty.
++ */
++ BUG_ON(!list_empty(&p->wr_list));
++
++ PRINT_INFO("Write thread PID %d for pool %p finished", current->pid, p);
++
++ TRACE_EXIT();
++ return 0;
++}
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/param.c linux-3.2/drivers/scst/iscsi-scst/param.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/param.c
++++ linux-3.2/drivers/scst/iscsi-scst/param.c
+@@ -0,0 +1,342 @@
++/*
++ * Copyright (C) 2005 FUJITA Tomonori <tomof@acm.org>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation.
++ *
++ * 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 "iscsi.h"
++#include "digest.h"
++
++#define CHECK_PARAM(info, iparams, word, min, max) \
++do { \
++ if (!(info)->partial || ((info)->partial & 1 << key_##word)) { \
++ TRACE_DBG("%s: %u", #word, (iparams)[key_##word]); \
++ if ((iparams)[key_##word] < (min) || \
++ (iparams)[key_##word] > (max)) { \
++ if ((iparams)[key_##word] < (min)) { \
++ (iparams)[key_##word] = (min); \
++ PRINT_WARNING("%s: %u is too small, resetting " \
++ "it to allowed min %u", \
++ #word, (iparams)[key_##word], (min)); \
++ } else { \
++ PRINT_WARNING("%s: %u is too big, resetting " \
++ "it to allowed max %u", \
++ #word, (iparams)[key_##word], (max)); \
++ (iparams)[key_##word] = (max); \
++ } \
++ } \
++ } \
++} while (0)
++
++#define SET_PARAM(params, info, iparams, word) \
++({ \
++ int changed = 0; \
++ if (!(info)->partial || ((info)->partial & 1 << key_##word)) { \
++ if ((params)->word != (iparams)[key_##word]) \
++ changed = 1; \
++ (params)->word = (iparams)[key_##word]; \
++ TRACE_DBG("%s set to %u", #word, (params)->word); \
++ } \
++ changed; \
++})
++
++#define GET_PARAM(params, info, iparams, word) \
++do { \
++ (iparams)[key_##word] = (params)->word; \
++} while (0)
++
++const char *iscsi_get_bool_value(int val)
++{
++ if (val)
++ return "Yes";
++ else
++ return "No";
++}
++
++const char *iscsi_get_digest_name(int val, char *res)
++{
++ int pos = 0;
++
++ if (val & DIGEST_NONE)
++ pos = sprintf(&res[pos], "%s", "None");
++
++ if (val & DIGEST_CRC32C)
++ pos += sprintf(&res[pos], "%s%s", (pos != 0) ? ", " : "",
++ "CRC32C");
++
++ if (pos == 0)
++ sprintf(&res[pos], "%s", "Unknown");
++
++ return res;
++}
++
++static void log_params(struct iscsi_sess_params *params)
++{
++ char digest_name[64];
++
++ PRINT_INFO("Negotiated parameters: InitialR2T %s, ImmediateData %s, "
++ "MaxConnections %d, MaxRecvDataSegmentLength %d, "
++ "MaxXmitDataSegmentLength %d, ",
++ iscsi_get_bool_value(params->initial_r2t),
++ iscsi_get_bool_value(params->immediate_data), params->max_connections,
++ params->max_recv_data_length, params->max_xmit_data_length);
++ PRINT_INFO(" MaxBurstLength %d, FirstBurstLength %d, "
++ "DefaultTime2Wait %d, DefaultTime2Retain %d, ",
++ params->max_burst_length, params->first_burst_length,
++ params->default_wait_time, params->default_retain_time);
++ PRINT_INFO(" MaxOutstandingR2T %d, DataPDUInOrder %s, "
++ "DataSequenceInOrder %s, ErrorRecoveryLevel %d, ",
++ params->max_outstanding_r2t,
++ iscsi_get_bool_value(params->data_pdu_inorder),
++ iscsi_get_bool_value(params->data_sequence_inorder),
++ params->error_recovery_level);
++ PRINT_INFO(" HeaderDigest %s, DataDigest %s, OFMarker %s, "
++ "IFMarker %s, OFMarkInt %d, IFMarkInt %d",
++ iscsi_get_digest_name(params->header_digest, digest_name),
++ iscsi_get_digest_name(params->data_digest, digest_name),
++ iscsi_get_bool_value(params->ofmarker),
++ iscsi_get_bool_value(params->ifmarker),
++ params->ofmarkint, params->ifmarkint);
++}
++
++/* target_mutex supposed to be locked */
++static void sess_params_check(struct iscsi_kern_params_info *info)
++{
++ int32_t *iparams = info->session_params;
++ const int max_len = ISCSI_CONN_IOV_MAX * PAGE_SIZE;
++
++ /*
++ * This is only kernel sanity check. Actual data validity checks
++ * performed in the user space.
++ */
++
++ CHECK_PARAM(info, iparams, initial_r2t, 0, 1);
++ CHECK_PARAM(info, iparams, immediate_data, 0, 1);
++ CHECK_PARAM(info, iparams, max_connections, 1, 1);
++ CHECK_PARAM(info, iparams, max_recv_data_length, 512, max_len);
++ CHECK_PARAM(info, iparams, max_xmit_data_length, 512, max_len);
++ CHECK_PARAM(info, iparams, max_burst_length, 512, max_len);
++ CHECK_PARAM(info, iparams, first_burst_length, 512, max_len);
++ CHECK_PARAM(info, iparams, max_outstanding_r2t, 1, 65535);
++ CHECK_PARAM(info, iparams, error_recovery_level, 0, 0);
++ CHECK_PARAM(info, iparams, data_pdu_inorder, 0, 1);
++ CHECK_PARAM(info, iparams, data_sequence_inorder, 0, 1);
++
++ digest_alg_available(&iparams[key_header_digest]);
++ digest_alg_available(&iparams[key_data_digest]);
++
++ CHECK_PARAM(info, iparams, ofmarker, 0, 0);
++ CHECK_PARAM(info, iparams, ifmarker, 0, 0);
++
++ return;
++}
++
++/* target_mutex supposed to be locked */
++static void sess_params_set(struct iscsi_sess_params *params,
++ struct iscsi_kern_params_info *info)
++{
++ int32_t *iparams = info->session_params;
++
++ SET_PARAM(params, info, iparams, initial_r2t);
++ SET_PARAM(params, info, iparams, immediate_data);
++ SET_PARAM(params, info, iparams, max_connections);
++ SET_PARAM(params, info, iparams, max_recv_data_length);
++ SET_PARAM(params, info, iparams, max_xmit_data_length);
++ SET_PARAM(params, info, iparams, max_burst_length);
++ SET_PARAM(params, info, iparams, first_burst_length);
++ SET_PARAM(params, info, iparams, default_wait_time);
++ SET_PARAM(params, info, iparams, default_retain_time);
++ SET_PARAM(params, info, iparams, max_outstanding_r2t);
++ SET_PARAM(params, info, iparams, data_pdu_inorder);
++ SET_PARAM(params, info, iparams, data_sequence_inorder);
++ SET_PARAM(params, info, iparams, error_recovery_level);
++ SET_PARAM(params, info, iparams, header_digest);
++ SET_PARAM(params, info, iparams, data_digest);
++ SET_PARAM(params, info, iparams, ofmarker);
++ SET_PARAM(params, info, iparams, ifmarker);
++ SET_PARAM(params, info, iparams, ofmarkint);
++ SET_PARAM(params, info, iparams, ifmarkint);
++ return;
++}
++
++static void sess_params_get(struct iscsi_sess_params *params,
++ struct iscsi_kern_params_info *info)
++{
++ int32_t *iparams = info->session_params;
++
++ GET_PARAM(params, info, iparams, initial_r2t);
++ GET_PARAM(params, info, iparams, immediate_data);
++ GET_PARAM(params, info, iparams, max_connections);
++ GET_PARAM(params, info, iparams, max_recv_data_length);
++ GET_PARAM(params, info, iparams, max_xmit_data_length);
++ GET_PARAM(params, info, iparams, max_burst_length);
++ GET_PARAM(params, info, iparams, first_burst_length);
++ GET_PARAM(params, info, iparams, default_wait_time);
++ GET_PARAM(params, info, iparams, default_retain_time);
++ GET_PARAM(params, info, iparams, max_outstanding_r2t);
++ GET_PARAM(params, info, iparams, data_pdu_inorder);
++ GET_PARAM(params, info, iparams, data_sequence_inorder);
++ GET_PARAM(params, info, iparams, error_recovery_level);
++ GET_PARAM(params, info, iparams, header_digest);
++ GET_PARAM(params, info, iparams, data_digest);
++ GET_PARAM(params, info, iparams, ofmarker);
++ GET_PARAM(params, info, iparams, ifmarker);
++ GET_PARAM(params, info, iparams, ofmarkint);
++ GET_PARAM(params, info, iparams, ifmarkint);
++ return;
++}
++
++/* target_mutex supposed to be locked */
++static void tgt_params_check(struct iscsi_session *session,
++ struct iscsi_kern_params_info *info)
++{
++ int32_t *iparams = info->target_params;
++ unsigned int rsp_timeout, nop_in_timeout;
++
++ /*
++ * This is only kernel sanity check. Actual data validity checks
++ * performed in the user space.
++ */
++
++ CHECK_PARAM(info, iparams, queued_cmnds, MIN_NR_QUEUED_CMNDS,
++ min_t(int, MAX_NR_QUEUED_CMNDS,
++ scst_get_max_lun_commands(session->scst_sess, NO_SUCH_LUN)));
++ CHECK_PARAM(info, iparams, rsp_timeout, MIN_RSP_TIMEOUT,
++ MAX_RSP_TIMEOUT);
++ CHECK_PARAM(info, iparams, nop_in_interval, MIN_NOP_IN_INTERVAL,
++ MAX_NOP_IN_INTERVAL);
++ CHECK_PARAM(info, iparams, nop_in_timeout, MIN_NOP_IN_TIMEOUT,
++ MAX_NOP_IN_TIMEOUT);
++
++ /*
++ * We adjust too long timeout in req_add_to_write_timeout_list()
++ * only for NOPs, so check and warn if this assumption isn't honored.
++ */
++ if (!info->partial || (info->partial & 1 << key_rsp_timeout))
++ rsp_timeout = iparams[key_rsp_timeout];
++ else
++ rsp_timeout = session->tgt_params.rsp_timeout;
++ if (!info->partial || (info->partial & 1 << key_nop_in_timeout))
++ nop_in_timeout = iparams[key_nop_in_timeout];
++ else
++ nop_in_timeout = session->tgt_params.nop_in_timeout;
++ if (nop_in_timeout > rsp_timeout)
++ PRINT_WARNING("%s", "RspTimeout should be >= NopInTimeout, "
++ "otherwise data transfer failure could take up to "
++ "NopInTimeout long to detect");
++
++ return;
++}
++
++/* target_mutex supposed to be locked */
++static int iscsi_tgt_params_set(struct iscsi_session *session,
++ struct iscsi_kern_params_info *info, int set)
++{
++ struct iscsi_tgt_params *params = &session->tgt_params;
++ int32_t *iparams = info->target_params;
++
++ if (set) {
++ struct iscsi_conn *conn;
++
++ tgt_params_check(session, info);
++
++ SET_PARAM(params, info, iparams, queued_cmnds);
++ SET_PARAM(params, info, iparams, rsp_timeout);
++ SET_PARAM(params, info, iparams, nop_in_interval);
++ SET_PARAM(params, info, iparams, nop_in_timeout);
++
++ PRINT_INFO("Target parameters set for session %llx: "
++ "QueuedCommands %d, Response timeout %d, Nop-In "
++ "interval %d, Nop-In timeout %d", session->sid,
++ params->queued_cmnds, params->rsp_timeout,
++ params->nop_in_interval, params->nop_in_timeout);
++
++ list_for_each_entry(conn, &session->conn_list,
++ conn_list_entry) {
++ conn->data_rsp_timeout = session->tgt_params.rsp_timeout * HZ;
++ conn->nop_in_interval = session->tgt_params.nop_in_interval * HZ;
++ conn->nop_in_timeout = session->tgt_params.nop_in_timeout * HZ;
++ spin_lock_bh(&conn->conn_thr_pool->rd_lock);
++ if (!conn->closing && (conn->nop_in_interval > 0)) {
++ TRACE_DBG("Schedule Nop-In work for conn %p", conn);
++ schedule_delayed_work(&conn->nop_in_delayed_work,
++ conn->nop_in_interval + ISCSI_ADD_SCHED_TIME);
++ }
++ spin_unlock_bh(&conn->conn_thr_pool->rd_lock);
++ }
++ } else {
++ GET_PARAM(params, info, iparams, queued_cmnds);
++ GET_PARAM(params, info, iparams, rsp_timeout);
++ GET_PARAM(params, info, iparams, nop_in_interval);
++ GET_PARAM(params, info, iparams, nop_in_timeout);
++ }
++
++ return 0;
++}
++
++/* target_mutex supposed to be locked */
++static int iscsi_sess_params_set(struct iscsi_session *session,
++ struct iscsi_kern_params_info *info, int set)
++{
++ struct iscsi_sess_params *params;
++
++ if (set)
++ sess_params_check(info);
++
++ params = &session->sess_params;
++
++ if (set) {
++ sess_params_set(params, info);
++ log_params(params);
++ } else
++ sess_params_get(params, info);
++
++ return 0;
++}
++
++/* target_mutex supposed to be locked */
++int iscsi_params_set(struct iscsi_target *target,
++ struct iscsi_kern_params_info *info, int set)
++{
++ int err;
++ struct iscsi_session *session;
++
++ if (info->sid == 0) {
++ PRINT_ERROR("sid must not be %d", 0);
++ err = -EINVAL;
++ goto out;
++ }
++
++ session = session_lookup(target, info->sid);
++ if (session == NULL) {
++ PRINT_ERROR("Session for sid %llx not found", info->sid);
++ err = -ENOENT;
++ goto out;
++ }
++
++ if (set && !list_empty(&session->conn_list) &&
++ (info->params_type != key_target)) {
++ err = -EBUSY;
++ goto out;
++ }
++
++ if (info->params_type == key_session)
++ err = iscsi_sess_params_set(session, info, set);
++ else if (info->params_type == key_target)
++ err = iscsi_tgt_params_set(session, info, set);
++ else
++ err = -EINVAL;
++
++out:
++ return err;
++}
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/session.c linux-3.2/drivers/scst/iscsi-scst/session.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/session.c
++++ linux-3.2/drivers/scst/iscsi-scst/session.c
+@@ -0,0 +1,527 @@
++/*
++ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@ardistech.com>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include "iscsi.h"
++
++#include <linux/export.h>
++
++/* target_mutex supposed to be locked */
++struct iscsi_session *session_lookup(struct iscsi_target *target, u64 sid)
++{
++ struct iscsi_session *session;
++
++ list_for_each_entry(session, &target->session_list,
++ session_list_entry) {
++ if (session->sid == sid)
++ return session;
++ }
++ return NULL;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int iscsi_session_alloc(struct iscsi_target *target,
++ struct iscsi_kern_session_info *info, struct iscsi_session **result)
++{
++ int err;
++ unsigned int i;
++ struct iscsi_session *session;
++ char *name = NULL;
++
++ session = kzalloc(sizeof(*session), GFP_KERNEL);
++ if (!session)
++ return -ENOMEM;
++
++ session->target = target;
++ session->sid = info->sid;
++ atomic_set(&session->active_cmds, 0);
++ session->exp_cmd_sn = info->exp_cmd_sn;
++
++ session->initiator_name = kstrdup(info->initiator_name, GFP_KERNEL);
++ if (!session->initiator_name) {
++ err = -ENOMEM;
++ goto err;
++ }
++
++ name = info->full_initiator_name;
++
++ INIT_LIST_HEAD(&session->conn_list);
++ INIT_LIST_HEAD(&session->pending_list);
++
++ spin_lock_init(&session->sn_lock);
++
++ spin_lock_init(&session->cmnd_data_wait_hash_lock);
++ for (i = 0; i < ARRAY_SIZE(session->cmnd_data_wait_hash); i++)
++ INIT_LIST_HEAD(&session->cmnd_data_wait_hash[i]);
++
++ session->next_ttt = 1;
++
++ session->scst_sess = scst_register_session(target->scst_tgt, 0,
++ name, session, NULL, NULL);
++ if (session->scst_sess == NULL) {
++ PRINT_ERROR("%s", "scst_register_session() failed");
++ err = -ENOMEM;
++ goto err;
++ }
++
++ err = iscsi_threads_pool_get(&session->scst_sess->acg->acg_cpu_mask,
++ &session->sess_thr_pool);
++ if (err != 0)
++ goto err_unreg;
++
++ TRACE_MGMT_DBG("Session %p created: target %p, tid %u, sid %#Lx",
++ session, target, target->tid, info->sid);
++
++ *result = session;
++ return 0;
++
++err_unreg:
++ scst_unregister_session(session->scst_sess, 1, NULL);
++
++err:
++ if (session) {
++ kfree(session->initiator_name);
++ kfree(session);
++ }
++ return err;
++}
++
++/* target_mutex supposed to be locked */
++void sess_reinst_finished(struct iscsi_session *sess)
++{
++ struct iscsi_conn *c;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Enabling reinstate successor sess %p", sess);
++
++ BUG_ON(!sess->sess_reinstating);
++
++ list_for_each_entry(c, &sess->conn_list, conn_list_entry) {
++ conn_reinst_finished(c);
++ }
++ sess->sess_reinstating = 0;
++
++ TRACE_EXIT();
++ return;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++int __add_session(struct iscsi_target *target,
++ struct iscsi_kern_session_info *info)
++{
++ struct iscsi_session *new_sess = NULL, *sess, *old_sess;
++ int err = 0, i;
++ union iscsi_sid sid;
++ bool reinstatement = false;
++ struct iscsi_kern_params_info *params_info;
++
++ TRACE_MGMT_DBG("Adding session SID %llx", info->sid);
++
++ err = iscsi_session_alloc(target, info, &new_sess);
++ if (err != 0)
++ goto out;
++
++ mutex_lock(&target->target_mutex);
++
++ sess = session_lookup(target, info->sid);
++ if (sess != NULL) {
++ PRINT_ERROR("Attempt to add session with existing SID %llx",
++ info->sid);
++ err = -EEXIST;
++ goto out_err_unlock;
++ }
++
++ params_info = kmalloc(sizeof(*params_info), GFP_KERNEL);
++ if (params_info == NULL) {
++ PRINT_ERROR("Unable to allocate params info (size %zd)",
++ sizeof(*params_info));
++ err = -ENOMEM;
++ goto out_err_unlock;
++ }
++
++ sid = *(union iscsi_sid *)&info->sid;
++ sid.id.tsih = 0;
++ old_sess = NULL;
++
++ /*
++ * We need to find the latest session to correctly handle
++ * multi-reinstatements
++ */
++ list_for_each_entry_reverse(sess, &target->session_list,
++ session_list_entry) {
++ union iscsi_sid s = *(union iscsi_sid *)&sess->sid;
++ s.id.tsih = 0;
++ if ((sid.id64 == s.id64) &&
++ (strcmp(info->initiator_name, sess->initiator_name) == 0)) {
++ if (!sess->sess_shutting_down) {
++ /* session reinstatement */
++ old_sess = sess;
++ }
++ break;
++ }
++ }
++ sess = NULL;
++
++ list_add_tail(&new_sess->session_list_entry, &target->session_list);
++
++ memset(params_info, 0, sizeof(*params_info));
++ params_info->tid = target->tid;
++ params_info->sid = info->sid;
++ params_info->params_type = key_session;
++ for (i = 0; i < session_key_last; i++)
++ params_info->session_params[i] = info->session_params[i];
++
++ err = iscsi_params_set(target, params_info, 1);
++ if (err != 0)
++ goto out_del;
++
++ memset(params_info, 0, sizeof(*params_info));
++ params_info->tid = target->tid;
++ params_info->sid = info->sid;
++ params_info->params_type = key_target;
++ for (i = 0; i < target_key_last; i++)
++ params_info->target_params[i] = info->target_params[i];
++
++ err = iscsi_params_set(target, params_info, 1);
++ if (err != 0)
++ goto out_del;
++
++ kfree(params_info);
++ params_info = NULL;
++
++ if (old_sess != NULL) {
++ reinstatement = true;
++
++ TRACE_MGMT_DBG("Reinstating sess %p with SID %llx (old %p, "
++ "SID %llx)", new_sess, new_sess->sid, old_sess,
++ old_sess->sid);
++
++ new_sess->sess_reinstating = 1;
++ old_sess->sess_reinst_successor = new_sess;
++
++ target_del_session(old_sess->target, old_sess, 0);
++ }
++
++ mutex_unlock(&target->target_mutex);
++
++ if (reinstatement) {
++ /*
++ * Mutex target_mgmt_mutex won't allow to add connections to
++ * the new session after target_mutex was dropped, so it's safe
++ * to replace the initial UA without it. We can't do it under
++ * target_mutex, because otherwise we can establish a
++ * circular locking dependency between target_mutex and
++ * scst_mutex in SCST core (iscsi_report_aen() called by
++ * SCST core under scst_mutex).
++ */
++ scst_set_initial_UA(new_sess->scst_sess,
++ SCST_LOAD_SENSE(scst_sense_nexus_loss_UA));
++ }
++
++out:
++ return err;
++
++out_del:
++ list_del(&new_sess->session_list_entry);
++ kfree(params_info);
++
++out_err_unlock:
++ mutex_unlock(&target->target_mutex);
++
++ scst_unregister_session(new_sess->scst_sess, 1, NULL);
++ new_sess->scst_sess = NULL;
++
++ mutex_lock(&target->target_mutex);
++ session_free(new_sess, false);
++ mutex_unlock(&target->target_mutex);
++ goto out;
++}
++
++static void __session_free(struct iscsi_session *session)
++{
++ kfree(session->initiator_name);
++ kfree(session);
++}
++
++static void iscsi_unreg_sess_done(struct scst_session *scst_sess)
++{
++ struct iscsi_session *session;
++
++ TRACE_ENTRY();
++
++ session = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
++
++ session->scst_sess = NULL;
++ __session_free(session);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* target_mutex supposed to be locked */
++int session_free(struct iscsi_session *session, bool del)
++{
++ unsigned int i;
++
++ TRACE_MGMT_DBG("Freeing session %p (SID %llx)",
++ session, session->sid);
++
++ BUG_ON(!list_empty(&session->conn_list));
++ if (unlikely(atomic_read(&session->active_cmds) != 0)) {
++ PRINT_CRIT_ERROR("active_cmds not 0 (%d)!!",
++ atomic_read(&session->active_cmds));
++ BUG();
++ }
++
++ for (i = 0; i < ARRAY_SIZE(session->cmnd_data_wait_hash); i++)
++ BUG_ON(!list_empty(&session->cmnd_data_wait_hash[i]));
++
++ if (session->sess_reinst_successor != NULL)
++ sess_reinst_finished(session->sess_reinst_successor);
++
++ if (session->sess_reinstating) {
++ struct iscsi_session *s;
++ TRACE_MGMT_DBG("Freeing being reinstated sess %p", session);
++ list_for_each_entry(s, &session->target->session_list,
++ session_list_entry) {
++ if (s->sess_reinst_successor == session) {
++ s->sess_reinst_successor = NULL;
++ break;
++ }
++ }
++ }
++
++ if (del)
++ list_del(&session->session_list_entry);
++
++ if (session->sess_thr_pool != NULL) {
++ iscsi_threads_pool_put(session->sess_thr_pool);
++ session->sess_thr_pool = NULL;
++ }
++
++ if (session->scst_sess != NULL) {
++ /*
++ * We must NOT call scst_unregister_session() in the waiting
++ * mode, since we are under target_mutex. Otherwise we can
++ * establish a circular locking dependency between target_mutex
++ * and scst_mutex in SCST core (iscsi_report_aen() called by
++ * SCST core under scst_mutex).
++ */
++ scst_unregister_session(session->scst_sess, 0,
++ iscsi_unreg_sess_done);
++ } else
++ __session_free(session);
++
++ return 0;
++}
++
++/* target_mutex supposed to be locked */
++int __del_session(struct iscsi_target *target, u64 sid)
++{
++ struct iscsi_session *session;
++
++ session = session_lookup(target, sid);
++ if (!session)
++ return -ENOENT;
++
++ if (!list_empty(&session->conn_list)) {
++ PRINT_ERROR("%llx still have connections",
++ (long long unsigned int)session->sid);
++ return -EBUSY;
++ }
++
++ return session_free(session, true);
++}
++
++/* Must be called under target_mutex */
++void iscsi_sess_force_close(struct iscsi_session *sess)
++{
++ struct iscsi_conn *conn;
++
++ TRACE_ENTRY();
++
++ PRINT_INFO("Deleting session %llx with initiator %s (%p)",
++ (long long unsigned int)sess->sid, sess->initiator_name, sess);
++
++ list_for_each_entry(conn, &sess->conn_list, conn_list_entry) {
++ TRACE_MGMT_DBG("Deleting connection with initiator %p", conn);
++ __mark_conn_closed(conn, ISCSI_CONN_ACTIVE_CLOSE|ISCSI_CONN_DELETING);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++#define ISCSI_SESS_BOOL_PARAM_ATTR(name, exported_name) \
++static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \
++ struct kobj_attribute *attr, char *buf) \
++{ \
++ int pos; \
++ struct scst_session *scst_sess; \
++ struct iscsi_session *sess; \
++ \
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj); \
++ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \
++ \
++ pos = sprintf(buf, "%s\n", \
++ iscsi_get_bool_value(sess->sess_params.name)); \
++ \
++ return pos; \
++} \
++ \
++static struct kobj_attribute iscsi_sess_attr_##name = \
++ __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL);
++
++#define ISCSI_SESS_INT_PARAM_ATTR(name, exported_name) \
++static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \
++ struct kobj_attribute *attr, char *buf) \
++{ \
++ int pos; \
++ struct scst_session *scst_sess; \
++ struct iscsi_session *sess; \
++ \
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj); \
++ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \
++ \
++ pos = sprintf(buf, "%d\n", sess->sess_params.name); \
++ \
++ return pos; \
++} \
++ \
++static struct kobj_attribute iscsi_sess_attr_##name = \
++ __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL);
++
++#define ISCSI_SESS_DIGEST_PARAM_ATTR(name, exported_name) \
++static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \
++ struct kobj_attribute *attr, char *buf) \
++{ \
++ int pos; \
++ struct scst_session *scst_sess; \
++ struct iscsi_session *sess; \
++ char digest_name[64]; \
++ \
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj); \
++ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \
++ \
++ pos = sprintf(buf, "%s\n", iscsi_get_digest_name( \
++ sess->sess_params.name, digest_name)); \
++ \
++ return pos; \
++} \
++ \
++static struct kobj_attribute iscsi_sess_attr_##name = \
++ __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL);
++
++ISCSI_SESS_BOOL_PARAM_ATTR(initial_r2t, InitialR2T);
++ISCSI_SESS_BOOL_PARAM_ATTR(immediate_data, ImmediateData);
++ISCSI_SESS_INT_PARAM_ATTR(max_recv_data_length, MaxRecvDataSegmentLength);
++ISCSI_SESS_INT_PARAM_ATTR(max_xmit_data_length, MaxXmitDataSegmentLength);
++ISCSI_SESS_INT_PARAM_ATTR(max_burst_length, MaxBurstLength);
++ISCSI_SESS_INT_PARAM_ATTR(first_burst_length, FirstBurstLength);
++ISCSI_SESS_INT_PARAM_ATTR(max_outstanding_r2t, MaxOutstandingR2T);
++ISCSI_SESS_DIGEST_PARAM_ATTR(header_digest, HeaderDigest);
++ISCSI_SESS_DIGEST_PARAM_ATTR(data_digest, DataDigest);
++
++static ssize_t iscsi_sess_sid_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos;
++ struct scst_session *scst_sess;
++ struct iscsi_session *sess;
++
++ TRACE_ENTRY();
++
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
++ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
++
++ pos = sprintf(buf, "%llx\n", sess->sid);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static struct kobj_attribute iscsi_attr_sess_sid =
++ __ATTR(sid, S_IRUGO, iscsi_sess_sid_show, NULL);
++
++static ssize_t iscsi_sess_reinstating_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos;
++ struct scst_session *scst_sess;
++ struct iscsi_session *sess;
++
++ TRACE_ENTRY();
++
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
++ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
++
++ pos = sprintf(buf, "%d\n", sess->sess_reinstating ? 1 : 0);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static struct kobj_attribute iscsi_sess_attr_reinstating =
++ __ATTR(reinstating, S_IRUGO, iscsi_sess_reinstating_show, NULL);
++
++static ssize_t iscsi_sess_force_close_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buf, size_t count)
++{
++ int res;
++ struct scst_session *scst_sess;
++ struct iscsi_session *sess;
++
++ TRACE_ENTRY();
++
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
++ sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
++
++ if (mutex_lock_interruptible(&sess->target->target_mutex) != 0) {
++ res = -EINTR;
++ goto out;
++ }
++
++ iscsi_sess_force_close(sess);
++
++ mutex_unlock(&sess->target->target_mutex);
++
++ res = count;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static struct kobj_attribute iscsi_sess_attr_force_close =
++ __ATTR(force_close, S_IWUSR, NULL, iscsi_sess_force_close_store);
++
++const struct attribute *iscsi_sess_attrs[] = {
++ &iscsi_sess_attr_initial_r2t.attr,
++ &iscsi_sess_attr_immediate_data.attr,
++ &iscsi_sess_attr_max_recv_data_length.attr,
++ &iscsi_sess_attr_max_xmit_data_length.attr,
++ &iscsi_sess_attr_max_burst_length.attr,
++ &iscsi_sess_attr_first_burst_length.attr,
++ &iscsi_sess_attr_max_outstanding_r2t.attr,
++ &iscsi_sess_attr_header_digest.attr,
++ &iscsi_sess_attr_data_digest.attr,
++ &iscsi_attr_sess_sid.attr,
++ &iscsi_sess_attr_reinstating.attr,
++ &iscsi_sess_attr_force_close.attr,
++ NULL,
++};
++
+diff -uprN orig/linux-3.2/drivers/scst/iscsi-scst/target.c linux-3.2/drivers/scst/iscsi-scst/target.c
+--- orig/linux-3.2/drivers/scst/iscsi-scst/target.c
++++ linux-3.2/drivers/scst/iscsi-scst/target.c
+@@ -0,0 +1,533 @@
++/*
++ * Copyright (C) 2002 - 2003 Ardis Technolgies <roman@ardistech.com>
++ * Copyright (C) 2007 - 2011 Vladislav Bolkhovitin
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/delay.h>
++#include <linux/module.h>
++
++#include "iscsi.h"
++#include "digest.h"
++
++#define MAX_NR_TARGETS (1UL << 30)
++
++DEFINE_MUTEX(target_mgmt_mutex);
++
++/* All 3 protected by target_mgmt_mutex */
++static LIST_HEAD(target_list);
++static u32 next_target_id;
++static u32 nr_targets;
++
++/* target_mgmt_mutex supposed to be locked */
++struct iscsi_target *target_lookup_by_id(u32 id)
++{
++ struct iscsi_target *target;
++
++ list_for_each_entry(target, &target_list, target_list_entry) {
++ if (target->tid == id)
++ return target;
++ }
++ return NULL;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static struct iscsi_target *target_lookup_by_name(const char *name)
++{
++ struct iscsi_target *target;
++
++ list_for_each_entry(target, &target_list, target_list_entry) {
++ if (!strcmp(target->name, name))
++ return target;
++ }
++ return NULL;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++static int iscsi_target_create(struct iscsi_kern_target_info *info, u32 tid,
++ struct iscsi_target **out_target)
++{
++ int err = -EINVAL, len;
++ char *name = info->name;
++ struct iscsi_target *target;
++
++ TRACE_MGMT_DBG("Creating target tid %u, name %s", tid, name);
++
++ len = strlen(name);
++ if (!len) {
++ PRINT_ERROR("The length of the target name is zero %u", tid);
++ goto out;
++ }
++
++ if (!try_module_get(THIS_MODULE)) {
++ PRINT_ERROR("Fail to get module %u", tid);
++ goto out;
++ }
++
++ target = kzalloc(sizeof(*target), GFP_KERNEL);
++ if (!target) {
++ err = -ENOMEM;
++ goto out_put;
++ }
++
++ target->tid = info->tid = tid;
++
++ strlcpy(target->name, name, sizeof(target->name));
++
++ mutex_init(&target->target_mutex);
++ INIT_LIST_HEAD(&target->session_list);
++ INIT_LIST_HEAD(&target->attrs_list);
++
++ target->scst_tgt = scst_register_target(&iscsi_template, target->name);
++ if (!target->scst_tgt) {
++ PRINT_ERROR("%s", "scst_register_target() failed");
++ err = -EBUSY;
++ goto out_free;
++ }
++
++ scst_tgt_set_tgt_priv(target->scst_tgt, target);
++
++ list_add_tail(&target->target_list_entry, &target_list);
++
++ *out_target = target;
++
++ return 0;
++
++out_free:
++ kfree(target);
++
++out_put:
++ module_put(THIS_MODULE);
++
++out:
++ return err;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++int __add_target(struct iscsi_kern_target_info *info)
++{
++ int err;
++ u32 tid = info->tid;
++ struct iscsi_target *target = NULL; /* to calm down sparse */
++ struct iscsi_kern_attr *attr_info;
++ union add_info_union {
++ struct iscsi_kern_params_info params_info;
++ struct iscsi_kern_attr attr_info;
++ } *add_info;
++ int i, rc;
++ unsigned long attrs_ptr_long;
++ struct iscsi_kern_attr __user *attrs_ptr;
++
++ if (nr_targets > MAX_NR_TARGETS) {
++ err = -EBUSY;
++ goto out;
++ }
++
++ if (target_lookup_by_name(info->name)) {
++ PRINT_ERROR("Target %s already exist!", info->name);
++ err = -EEXIST;
++ goto out;
++ }
++
++ if (tid && target_lookup_by_id(tid)) {
++ PRINT_ERROR("Target %u already exist!", tid);
++ err = -EEXIST;
++ goto out;
++ }
++
++ add_info = kmalloc(sizeof(*add_info), GFP_KERNEL);
++ if (add_info == NULL) {
++ PRINT_ERROR("Unable to allocate additional info (size %zd)",
++ sizeof(*add_info));
++ err = -ENOMEM;
++ goto out;
++ }
++ attr_info = (struct iscsi_kern_attr *)add_info;
++
++ if (tid == 0) {
++ do {
++ if (!++next_target_id)
++ ++next_target_id;
++ } while (target_lookup_by_id(next_target_id));
++
++ tid = next_target_id;
++ }
++
++ err = iscsi_target_create(info, tid, &target);
++ if (err != 0)
++ goto out_free;
++
++ nr_targets++;
++
++ mutex_lock(&target->target_mutex);
++
++ attrs_ptr_long = info->attrs_ptr;
++ attrs_ptr = (struct iscsi_kern_attr __user *)attrs_ptr_long;
++ for (i = 0; i < info->attrs_num; i++) {
++ memset(attr_info, 0, sizeof(*attr_info));
++
++ rc = copy_from_user(attr_info, attrs_ptr, sizeof(*attr_info));
++ if (rc != 0) {
++ PRINT_ERROR("Failed to copy users of target %s "
++ "failed", info->name);
++ err = -EFAULT;
++ goto out_del_unlock;
++ }
++
++ attr_info->name[sizeof(attr_info->name)-1] = '\0';
++
++ err = iscsi_add_attr(target, attr_info);
++ if (err != 0)
++ goto out_del_unlock;
++
++ attrs_ptr++;
++ }
++
++ mutex_unlock(&target->target_mutex);
++
++ err = tid;
++
++out_free:
++ kfree(add_info);
++
++out:
++ return err;
++
++out_del_unlock:
++ mutex_unlock(&target->target_mutex);
++ __del_target(tid);
++ goto out_free;
++}
++
++static void target_destroy(struct iscsi_target *target)
++{
++ struct iscsi_attr *attr, *t;
++
++ TRACE_MGMT_DBG("Destroying target tid %u", target->tid);
++
++ list_for_each_entry_safe(attr, t, &target->attrs_list,
++ attrs_list_entry) {
++ __iscsi_del_attr(target, attr);
++ }
++
++ scst_unregister_target(target->scst_tgt);
++
++ kfree(target);
++
++ module_put(THIS_MODULE);
++ return;
++}
++
++/* target_mgmt_mutex supposed to be locked */
++int __del_target(u32 id)
++{
++ struct iscsi_target *target;
++ int err;
++
++ target = target_lookup_by_id(id);
++ if (!target) {
++ err = -ENOENT;
++ goto out;
++ }
++
++ mutex_lock(&target->target_mutex);
++
++ if (!list_empty(&target->session_list)) {
++ err = -EBUSY;
++ goto out_unlock;
++ }
++
++ list_del(&target->target_list_entry);
++ nr_targets--;
++
++ mutex_unlock(&target->target_mutex);
++
++ target_destroy(target);
++ return 0;
++
++out_unlock:
++ mutex_unlock(&target->target_mutex);
++
++out:
++ return err;
++}
++
++/* target_mutex supposed to be locked */
++void target_del_session(struct iscsi_target *target,
++ struct iscsi_session *session, int flags)
++{
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Deleting session %p", session);
++
++ if (!list_empty(&session->conn_list)) {
++ struct iscsi_conn *conn, *tc;
++ list_for_each_entry_safe(conn, tc, &session->conn_list,
++ conn_list_entry) {
++ TRACE_MGMT_DBG("Mark conn %p closing", conn);
++ __mark_conn_closed(conn, flags);
++ }
++ } else {
++ TRACE_MGMT_DBG("Freeing session %p without connections",
++ session);
++ __del_session(target, session->sid);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* target_mutex supposed to be locked */
++void target_del_all_sess(struct iscsi_target *target, int flags)
++{
++ struct iscsi_session *session, *ts;
++
++ TRACE_ENTRY();
++
++ if (!list_empty(&target->session_list)) {
++ TRACE_MGMT_DBG("Deleting all sessions from target %p", target);
++ list_for_each_entry_safe(session, ts, &target->session_list,
++ session_list_entry) {
++ target_del_session(target, session, flags);
++ }
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++void target_del_all(void)
++{
++ struct iscsi_target *target, *t;
++ bool first = true;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("%s", "Deleting all targets");
++
++ /* Not the best, ToDo */
++ while (1) {
++ mutex_lock(&target_mgmt_mutex);
++
++ if (list_empty(&target_list))
++ break;
++
++ /*
++ * In the first iteration we won't delete targets to go at
++ * first through all sessions of all targets and close their
++ * connections. Otherwise we can stuck for noticeable time
++ * waiting during a target's unregistration for the activities
++ * suspending over active connection. This can especially got
++ * bad if any being wait connection itself stuck waiting for
++ * something and can be recovered only by connection close.
++ * Let's for such cases not wait while such connection recover
++ * theyself, but act in advance.
++ */
++
++ list_for_each_entry_safe(target, t, &target_list,
++ target_list_entry) {
++ mutex_lock(&target->target_mutex);
++
++ if (!list_empty(&target->session_list)) {
++ target_del_all_sess(target,
++ ISCSI_CONN_ACTIVE_CLOSE |
++ ISCSI_CONN_DELETING);
++ } else if (!first) {
++ TRACE_MGMT_DBG("Deleting target %p", target);
++ list_del(&target->target_list_entry);
++ nr_targets--;
++ mutex_unlock(&target->target_mutex);
++ target_destroy(target);
++ continue;
++ }
++
++ mutex_unlock(&target->target_mutex);
++ }
++ mutex_unlock(&target_mgmt_mutex);
++ msleep(100);
++
++ first = false;
++ }
++
++ mutex_unlock(&target_mgmt_mutex);
++
++ TRACE_MGMT_DBG("%s", "Deleting all targets finished");
++
++ TRACE_EXIT();
++ return;
++}
++
++static ssize_t iscsi_tgt_tid_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ int pos;
++ struct scst_tgt *scst_tgt;
++ struct iscsi_target *tgt;
++
++ TRACE_ENTRY();
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = (struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
++
++ pos = sprintf(buf, "%u\n", tgt->tid);
++
++ TRACE_EXIT_RES(pos);
++ return pos;
++}
++
++static struct kobj_attribute iscsi_tgt_attr_tid =
++ __ATTR(tid, S_IRUGO, iscsi_tgt_tid_show, NULL);
++
++const struct attribute *iscsi_tgt_attrs[] = {
++ &iscsi_tgt_attr_tid.attr,
++ NULL,
++};
++
++ssize_t iscsi_sysfs_send_event(uint32_t tid, enum iscsi_kern_event_code code,
++ const char *param1, const char *param2, void **data)
++{
++ int res;
++ struct scst_sysfs_user_info *info;
++
++ TRACE_ENTRY();
++
++ if (ctr_open_state != ISCSI_CTR_OPEN_STATE_OPEN) {
++ PRINT_ERROR("%s", "User space process not connected");
++ res = -EPERM;
++ goto out;
++ }
++
++ res = scst_sysfs_user_add_info(&info);
++ if (res != 0)
++ goto out;
++
++ TRACE_DBG("Sending event %d (tid %d, param1 %s, param2 %s, cookie %d, "
++ "info %p)", tid, code, param1, param2, info->info_cookie, info);
++
++ res = event_send(tid, 0, 0, info->info_cookie, code, param1, param2);
++ if (res <= 0) {
++ PRINT_ERROR("event_send() failed: %d", res);
++ if (res == 0)
++ res = -EFAULT;
++ goto out_free;
++ }
++
++ /*
++ * It may wait 30 secs in blocking connect to an unreacheable
++ * iSNS server. It must be fixed, but not now. ToDo.
++ */
++ res = scst_wait_info_completion(info, 31 * HZ);
++
++ if (data != NULL)
++ *data = info->data;
++
++out_free:
++ scst_sysfs_user_del_info(info);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++int iscsi_enable_target(struct scst_tgt *scst_tgt, bool enable)
++{
++ struct iscsi_target *tgt =
++ (struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
++ int res;
++ uint32_t type;
++
++ TRACE_ENTRY();
++
++ if (enable)
++ type = E_ENABLE_TARGET;
++ else
++ type = E_DISABLE_TARGET;
++
++ TRACE_DBG("%s target %d", enable ? "Enabling" : "Disabling", tgt->tid);
++
++ res = iscsi_sysfs_send_event(tgt->tid, type, NULL, NULL, NULL);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++bool iscsi_is_target_enabled(struct scst_tgt *scst_tgt)
++{
++ struct iscsi_target *tgt =
++ (struct iscsi_target *)scst_tgt_get_tgt_priv(scst_tgt);
++
++ return tgt->tgt_enabled;
++}
++
++ssize_t iscsi_sysfs_add_target(const char *target_name, char *params)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ res = iscsi_sysfs_send_event(0, E_ADD_TARGET, target_name,
++ params, NULL);
++ if (res > 0) {
++ /* It's tid */
++ res = 0;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++ssize_t iscsi_sysfs_del_target(const char *target_name)
++{
++ int res = 0, tid;
++
++ TRACE_ENTRY();
++
++ /* We don't want to have tgt visible after the mutex unlock */
++ {
++ struct iscsi_target *tgt;
++ mutex_lock(&target_mgmt_mutex);
++ tgt = target_lookup_by_name(target_name);
++ if (tgt == NULL) {
++ PRINT_ERROR("Target %s not found", target_name);
++ mutex_unlock(&target_mgmt_mutex);
++ res = -ENOENT;
++ goto out;
++ }
++ tid = tgt->tid;
++ mutex_unlock(&target_mgmt_mutex);
++ }
++
++ TRACE_DBG("Deleting target %s (tid %d)", target_name, tid);
++
++ res = iscsi_sysfs_send_event(tid, E_DEL_TARGET, NULL, NULL, NULL);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++ssize_t iscsi_sysfs_mgmt_cmd(char *cmd)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending mgmt cmd %s", cmd);
++
++ res = iscsi_sysfs_send_event(0, E_MGMT_CMD, cmd, NULL, NULL);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
+diff -uprN orig/linux-3.2/Documentation/scst/README.iscsi linux-3.2/Documentation/scst/README.iscsi
+--- orig/linux-3.2/Documentation/scst/README.iscsi
++++ linux-3.2/Documentation/scst/README.iscsi
+@@ -0,0 +1,748 @@
++iSCSI SCST target driver
++========================
++
++ISCSI-SCST is a deeply reworked fork of iSCSI Enterprise Target (IET)
++(http://iscsitarget.sourceforge.net). Reasons of the fork were:
++
++ - To be able to use full power of SCST core.
++
++ - To fix all the problems, corner cases issues and iSCSI standard
++ violations which IET has.
++
++See for more info http://iscsi-scst.sourceforge.net.
++
++Usage
++-----
++
++See in http://iscsi-scst.sourceforge.net/iscsi-scst-howto.txt how to
++configure iSCSI-SCST.
++
++If you want to use Intel CRC32 offload and have corresponding hardware,
++you should load crc32c-intel module. Then iSCSI-SCST will do all digest
++calculations using this facility.
++
++In 2.0.0 usage of iscsi-scstd.conf as well as iscsi-scst-adm utility is
++obsolete. Use the sysfs interface facilities instead.
++
++The flow of iSCSI-SCST inialization should be as the following:
++
++1. Load of SCST and iSCSI-SCST kernel modules with necessary module
++parameters, if needed.
++
++2. Start iSCSI-SCST service.
++
++3. Configure targets, devices, LUNs, etc. either using scstadmin
++(recommended), or using the sysfs interface directly as described below.
++
++It is recommended to use TEST UNIT READY ("tur") command to check if
++iSCSI-SCST target is alive in MPIO configurations.
++
++Also see SCST README file how to tune for the best performance.
++
++CAUTION: Working of target and initiator on the same host isn't fully
++======= supported. See SCST README file for details.
++
++
++Sysfs interface
++---------------
++
++Root of SCST sysfs interface is /sys/kernel/scst_tgt. Root of iSCSI-SCST
++is /sys/kernel/scst_tgt/targets/iscsi. It has the following entries:
++
++ - None, one or more subdirectories for targets with name equal to names
++ of the corresponding targets.
++
++ - IncomingUser[num] - optional one or more attributes containing user
++ name and password for incoming discovery user name. Not exist by
++ default and can be added through "mgmt" entry, see below.
++
++ - OutgoingUser - optional attribute containing user name and password
++ for outgoing discovery user name. Not exist by default and can be
++ added through "mgmt" entry, see below.
++
++ - iSNSServer - contains name or IP address of iSNS server with optional
++ "AccessControl" attribute, which allows to enable iSNS access
++ control. Empty by default.
++
++ - allowed_portal[num] - optional attribute, which specifies, on which
++ portals (target's IP addresses) this target will be available. If not
++ specified (default) the target will be available on all all portals.
++ As soon as at least one allowed_portal specified, the target will be
++ accessible for initiators only on the specified portals. There might
++ be any number of the allowed_portal attributes. The portals
++ specification in the allowed_portal attributes can be a simple
++ DOS-type patterns, containing '*' and '?' symbols. '*' means match
++ all any symbols, '?' means match only any single symbol. For
++ instance, "10.170.77.2" will match "10.170.7?.*". Additionally, you
++ can use negative sign '!' to revert the value of the pattern. For
++ instance, "10.170.67.2" will match "!10.170.7?.*". See examples
++ below.
++
++ - enabled - using this attribute you can enable or disable iSCSI-SCST
++ accept new connections. It allows to finish configuring global
++ iSCSI-SCST attributes before it starts accepting new connections. 0
++ by default.
++
++ - open_state - read-only attribute, which allows to see if the user
++ space part of iSCSI-SCST connected to the kernel part.
++
++ - per_portal_acl - if set, makes iSCSI-SCST work in the per-portal
++ access control mode. In this mode iSCSI-SCST registers all initiators
++ in SCST core as "initiator_name#portal_IP_address" pattern, like
++ "iqn.2006-10.net.vlnb:ini#10.170.77.2" for initiator
++ iqn.2006-10.net.vlnb connected through portal 10.170.77.2. This mode
++ allows to make particular initiators be able to use only particular
++ portals on the target and don't see/be able to connect through
++ others. See below for more details.
++
++ - trace_level - allows to enable and disable various tracing
++ facilities. See content of this file for help how to use it.
++
++ - version - read-only attribute, which allows to see version of
++ iSCSI-SCST and enabled optional features.
++
++ - mgmt - main management entry, which allows to configure iSCSI-SCST.
++ Namely, add/delete targets as well as add/delete optional global and
++ per-target attributes. See content of this file for help how to use
++ it.
++
++Each iSCSI-SCST sysfs file (attribute) can contain in the last line mark
++"[key]". It is automatically added mark used to allow scstadmin to see
++which attributes it should save in the config file. You can ignore it.
++
++Each target subdirectory contains the following entries:
++
++ - ini_groups - subdirectory defining initiator groups for this target,
++ used to define per-initiator access control. See SCST core README for
++ more details.
++
++ - luns - subdirectory defining LUNs of this target. See SCST core
++ README for more details.
++
++ - sessions - subdirectory containing connected to this target sessions.
++
++ - IncomingUser[num] - optional one or more attributes containing user
++ name and password for incoming user name. Not exist by default and can
++ be added through the "mgmt" entry, see above.
++
++ - OutgoingUser - optional attribute containing user name and password
++ for outgoing user name. Not exist by default and can be added through
++ the "mgmt" entry, see above.
++
++ - Entries defining default iSCSI parameters values used during iSCSI
++ parameters negotiation. Only entries which can be changed or make
++ sense are listed there.
++
++ - QueuedCommands - defines maximum number of commands queued to any
++ session of this target. Default is 32 commands.
++
++ - NopInInterval - defines interval between NOP-In requests, which the
++ target will send on idle connections to check if the initiator is
++ still alive. If there is no NOP-Out reply from the initiator in
++ RspTimeout time, the corresponding connection will be closed. Default
++ is 30 seconds. If it's set to 0, then NOP-In requests are disabled.
++
++ - NopInTimeout - defines the maximum time in seconds a NOP-In request
++ can wait for response from initiator, otherwise the corresponding
++ connection will be closed. Default is 30 seconds.
++
++ - RspTimeout - defines the maximum time in seconds a command can wait for
++ response from initiator, otherwise the corresponding connection will
++ be closed. Default is 90 seconds.
++
++ - enabled - using this attribute you can enable or disable iSCSI-SCST
++ accept new connections to this target. It allows to finish
++ configuring it before it starts accepting new connections. 0 by
++ default.
++
++ - redirect - allows to temporarily or permanently redirect login to the
++ target to another portal. Discovery sessions will not be impacted,
++ but normal sessions will be redirected before security negotiation.
++ The destination should be specified using format "<ip_addr>[:port] temp|perm".
++ IPv6 addresses need to be enclosed in [] brackets. To remove
++ redirection, provide an empty string. For example:
++ echo "10.170.77.2:32600 temp" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/redirect
++ will temporarily redirect login to portal 10.170.77.2 and port 32600.
++
++ - tid - TID of this target.
++
++Subdirectory "sessions" contains one subdirectory for each connected
++session with name equal to name of the connected initiator.
++
++Each session subdirectory contains the following entries:
++
++ - One subdirectory for each TCP connection in this session. ISCSI-SCST
++ supports 1 connection per session, but the session subdirectory can
++ contain several connections: one active and other being closed.
++
++ - Entries defining negotiated iSCSI parameters. Only parameters which
++ can be changed or make sense are listed there.
++
++ - initiator_name - contains initiator name
++
++ - sid - contains SID of this session
++
++ - reinstating - contains reinstatement state of this session
++
++ - force_close - write-only attribute, which allows to force close this
++ session. This is the only writable session attribute.
++
++ - active_commands - contains number of active, i.e. not yet or being
++ executed, SCSI commands in this session.
++
++ - commands - contains overall number of SCSI commands in this session.
++
++Each connection subdirectory contains the following entries:
++
++ - cid - contains CID of this connection.
++
++ - ip - contains IP address of the connected initiator.
++
++ - state - contains processing state of this connection.
++
++See SCST README for info about other attributes.
++
++Below is a sample script, which configures 1 virtual disk "disk1" using
++/disk1 image and one target iqn.2006-10.net.vlnb:tgt with all default
++parameters:
++
++#!/bin/bash
++
++modprobe scst
++modprobe scst_vdisk
++
++echo "add_device disk1 filename=/disk1; nv_cache=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++
++service iscsi-scst start
++
++echo "add_target iqn.2006-10.net.vlnb:tgt" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo "add disk1 0" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/mgmt
++
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/enabled
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/enabled
++
++Below is another sample script, which configures 1 real local SCSI disk
++0:0:1:0 and one target iqn.2006-10.net.vlnb:tgt with all default parameters:
++
++#!/bin/bash
++
++modprobe scst
++modprobe scst_disk
++
++echo "add_device 0:0:1:0" >/sys/kernel/scst_tgt/handlers/dev_disk/mgmt
++
++service iscsi-scst start
++
++echo "add_target iqn.2006-10.net.vlnb:tgt" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo "add 0:0:1:0 0" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/mgmt
++
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/enabled
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/enabled
++
++Below is an advanced sample script, which configures more virtual
++devices of various types, including virtual CDROM and 2 targets, one
++with all default parameters, another one with some not default
++parameters, incoming and outgoing user names for CHAP authentification,
++and special permissions for initiator iqn.2005-03.org.open-iscsi:cacdcd2520,
++which will see another set of devices. Also this sample configures CHAP
++authentication for discovery sessions and iSNS server with access
++control.
++
++#!/bin/bash
++
++modprobe scst
++modprobe scst_vdisk
++
++echo "add_device disk1 filename=/disk1; nv_cache=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++echo "add_device disk2 filename=/disk2; blocksize=4096; nv_cache=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++echo "add_device blockio filename=/dev/sda5" >/sys/kernel/scst_tgt/handlers/vdisk_blockio/mgmt
++echo "add_device nullio" >/sys/kernel/scst_tgt/handlers/vdisk_nullio/mgmt
++echo "add_device cdrom" >/sys/kernel/scst_tgt/handlers/vcdrom/mgmt
++
++service iscsi-scst start
++
++echo "192.168.1.16 AccessControl" >/sys/kernel/scst_tgt/targets/iscsi/iSNSServer
++echo "add_attribute IncomingUser joeD 12charsecret" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo "add_attribute OutgoingUser jackD 12charsecret1" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++
++echo "add_target iqn.2006-10.net.vlnb:tgt" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++
++echo "add disk1 0" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/mgmt
++echo "add cdrom 1" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/mgmt
++
++echo "add_target iqn.2006-10.net.vlnb:tgt1" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo "add_target_attribute iqn.2006-10.net.vlnb:tgt1 IncomingUser1 joe2 12charsecret2" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo "add_target_attribute iqn.2006-10.net.vlnb:tgt1 IncomingUser joe 12charsecret" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo "add_target_attribute iqn.2006-10.net.vlnb:tgt1 OutgoingUser jim1 12charpasswd" >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo "No" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/InitialR2T
++echo "Yes" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ImmediateData
++echo "8192" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/MaxRecvDataSegmentLength
++echo "8192" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/MaxXmitDataSegmentLength
++echo "131072" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/MaxBurstLength
++echo "32768" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/FirstBurstLength
++echo "1" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/MaxOutstandingR2T
++echo "CRC32C,None" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/HeaderDigest
++echo "CRC32C,None" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/DataDigest
++echo "32" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/QueuedCommands
++
++echo "add disk2 0" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/luns/mgmt
++echo "add nullio 26" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/luns/mgmt
++
++echo "create special_ini" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/mgmt
++echo "add blockio 0 read_only=1" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/special_ini/luns/mgmt
++echo "add iqn.2005-03.org.open-iscsi:cacdcd2520" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/special_ini/initiators/mgmt
++
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/enabled
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt1/enabled
++
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/enabled
++
++The resulting overall SCST sysfs hierarchy with an initiator connected to
++both iSCSI-SCST targets will look like:
++
++/sys/kernel/scst_tgt
++|-- devices
++| |-- blockio
++| | |-- blocksize
++| | |-- exported
++| | | `-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt1/ini_groups/special_ini/luns/0
++| | |-- filename
++| | |-- handler -> ../../handlers/vdisk_blockio
++| | |-- nv_cache
++| | |-- read_only
++| | |-- removable
++| | |-- resync_size
++| | |-- size_mb
++| | |-- t10_dev_id
++| | |-- threads_num
++| | |-- threads_pool_type
++| | |-- type
++| | `-- usn
++| |-- cdrom
++| | |-- exported
++| | | `-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/1
++| | |-- filename
++| | |-- handler -> ../../handlers/vcdrom
++| | |-- size_mb
++| | |-- t10_dev_id
++| | |-- threads_num
++| | |-- threads_pool_type
++| | |-- type
++| | `-- usn
++| |-- disk1
++| | |-- blocksize
++| | |-- exported
++| | | `-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/0
++| | |-- filename
++| | |-- handler -> ../../handlers/vdisk_fileio
++| | |-- nv_cache
++| | |-- o_direct
++| | |-- read_only
++| | |-- removable
++| | |-- resync_size
++| | |-- size_mb
++| | |-- t10_dev_id
++| | |-- type
++| | |-- usn
++| | `-- write_through
++| |-- disk2
++| | |-- blocksize
++| | |-- exported
++| | | `-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt1/luns/0
++| | |-- filename
++| | |-- handler -> ../../handlers/vdisk_fileio
++| | |-- nv_cache
++| | |-- o_direct
++| | |-- read_only
++| | |-- removable
++| | |-- resync_size
++| | |-- size_mb
++| | |-- t10_dev_id
++| | |-- threads_num
++| | |-- threads_pool_type
++| | |-- threads_num
++| | |-- threads_pool_type
++| | |-- type
++| | |-- usn
++| | `-- write_through
++| `-- nullio
++| |-- blocksize
++| |-- exported
++| | `-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt1/luns/26
++| |-- handler -> ../../handlers/vdisk_nullio
++| |-- read_only
++| |-- removable
++| |-- size_mb
++| |-- t10_dev_id
++| |-- threads_num
++| |-- threads_pool_type
++| |-- type
++| `-- usn
++|-- handlers
++| |-- vcdrom
++| | |-- cdrom -> ../../devices/cdrom
++| | |-- mgmt
++| | |-- trace_level
++| | `-- type
++| |-- vdisk_blockio
++| | |-- blockio -> ../../devices/blockio
++| | |-- mgmt
++| | |-- trace_level
++| | `-- type
++| |-- vdisk_fileio
++| | |-- disk1 -> ../../devices/disk1
++| | |-- disk2 -> ../../devices/disk2
++| | |-- mgmt
++| | |-- trace_level
++| | `-- type
++| `-- vdisk_nullio
++| |-- mgmt
++| |-- nullio -> ../../devices/nullio
++| |-- trace_level
++| `-- type
++|-- sgv
++| |-- global_stats
++| |-- sgv
++| | `-- stats
++| |-- sgv-clust
++| | `-- stats
++| `-- sgv-dma
++| `-- stats
++|-- targets
++| `-- iscsi
++| |-- IncomingUser
++| |-- OutgoingUser
++| |-- enabled
++| |-- iSNSServer
++| |-- iqn.2006-10.net.vlnb:tgt
++| | |-- DataDigest
++| | |-- FirstBurstLength
++| | |-- HeaderDigest
++| | |-- ImmediateData
++| | |-- InitialR2T
++| | |-- MaxBurstLength
++| | |-- MaxOutstandingR2T
++| | |-- MaxRecvDataSegmentLength
++| | |-- MaxXmitDataSegmentLength
++| | |-- NopInInterval
++| | |-- QueuedCommands
++| | |-- RspTimeout
++| | |-- enabled
++| | |-- ini_groups
++| | | `-- mgmt
++| | |-- luns
++| | | |-- 0
++| | | | |-- device -> ../../../../../devices/disk1
++| | | | `-- read_only
++| | | |-- 1
++| | | | |-- device -> ../../../../../devices/cdrom
++| | | | `-- read_only
++| | | `-- mgmt
++| | |-- per_portal_acl
++| | |-- redirect
++| | |-- rel_tgt_id
++| | |-- sessions
++| | | `-- iqn.2005-03.org.open-iscsi:cacdcd2520
++| | | |-- 10.170.75.2
++| | | | |-- cid
++| | | | |-- ip
++| | | | `-- state
++| | | |-- DataDigest
++| | | |-- FirstBurstLength
++| | | |-- HeaderDigest
++| | | |-- ImmediateData
++| | | |-- InitialR2T
++| | | |-- MaxBurstLength
++| | | |-- MaxOutstandingR2T
++| | | |-- MaxRecvDataSegmentLength
++| | | |-- MaxXmitDataSegmentLength
++| | | |-- active_commands
++| | | |-- commands
++| | | |-- force_close
++| | | |-- initiator_name
++| | | |-- luns -> ../../luns
++| | | |-- reinstating
++| | | `-- sid
++| | `-- tid
++| |-- iqn.2006-10.net.vlnb:tgt1
++| | |-- DataDigest
++| | |-- FirstBurstLength
++| | |-- HeaderDigest
++| | |-- ImmediateData
++| | |-- IncomingUser
++| | |-- IncomingUser1
++| | |-- InitialR2T
++| | |-- MaxBurstLength
++| | |-- MaxOutstandingR2T
++| | |-- MaxRecvDataSegmentLength
++| | |-- MaxXmitDataSegmentLength
++| | |-- OutgoingUser
++| | |-- NopInInterval
++| | |-- QueuedCommands
++| | |-- RspTimeout
++| | |-- enabled
++| | |-- ini_groups
++| | | |-- mgmt
++| | | `-- special_ini
++| | | |-- initiators
++| | | | |-- iqn.2005-03.org.open-iscsi:cacdcd2520
++| | | | `-- mgmt
++| | | `-- luns
++| | | |-- 0
++| | | | |-- device -> ../../../../../../../devices/blockio
++| | | | `-- read_only
++| | | `-- mgmt
++| | |-- luns
++| | | |-- 0
++| | | | |-- device -> ../../../../../devices/disk2
++| | | | `-- read_only
++| | | |-- 26
++| | | | |-- device -> ../../../../../devices/nullio
++| | | | `-- read_only
++| | | `-- mgmt
++| | |-- per_portal_acl
++| | |-- redirect
++| | |-- rel_tgt_id
++| | |-- sessions
++| | | `-- iqn.2005-03.org.open-iscsi:cacdcd2520
++| | | |-- 10.170.75.2
++| | | | |-- cid
++| | | | |-- ip
++| | | | `-- state
++| | | |-- DataDigest
++| | | |-- FirstBurstLength
++| | | |-- HeaderDigest
++| | | |-- ImmediateData
++| | | |-- InitialR2T
++| | | |-- MaxBurstLength
++| | | |-- MaxOutstandingR2T
++| | | |-- MaxRecvDataSegmentLength
++| | | |-- MaxXmitDataSegmentLength
++| | | |-- active_commands
++| | | |-- commands
++| | | |-- force_close
++| | | |-- initiator_name
++| | | |-- luns -> ../../ini_groups/special_ini/luns
++| | | |-- reinstating
++| | | `-- sid
++| | `-- tid
++| |-- mgmt
++| |-- open_state
++| |-- trace_level
++| `-- version
++|-- threads
++|-- trace_level
++`-- version
++
++
++Advanced initiators access control
++----------------------------------
++
++ISCSI-SCST allows you to optionally control visibility and accessibility
++of your target and its portals (IP addresses) to remote initiators. This
++control includes both the target's portals SendTargets discovery as well
++as regular LUNs access.
++
++This facility supersedes the obsolete initiators.[allow,deny] method,
++which is going to be removed in one of the future versions.
++
++This facility is available only in the sysfs build of iSCSI-SCST.
++
++By default, all portals are available for the initiators.
++
++1. If you want to enable/disable one or more target's portals for all
++initiators, you should define one ore more allowed_portal attributes.
++For example:
++
++echo 'add_target_attribute iqn.2006-10.net.vlnb:tgt allowed_portal 10.170.77.2' >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++
++will enable only portal 10.170.77.2 and disable all other portals
++
++echo 'add_target_attribute iqn.2006-10.net.vlnb:tgt allowed_portal 10.170.77.2' >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo 'add_target_attribute iqn.2006-10.net.vlnb:tgt allowed_portal 10.170.75.2' >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++
++will enable only portals 10.170.77.2 and 10.170.75.2 and disable all
++other portals.
++
++echo 'add_target_attribute iqn.2006-10.net.vlnb:tgt allowed_portal 10.170.7?.2' >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++
++will enable only portals 10.170.7x.2 and disable all other portals.
++
++echo 'add_target_attribute iqn.2006-10.net.vlnb:tgt allowed_portal !*' >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++
++will disable all portals.
++
++2. If you want to want to allow only only specific set of initiators be
++able to connect to your target, you should don't add any default LUNs
++for the target and create for allowed initiators a security group to
++which they will be assigned.
++
++For example, we want initiator iqn.2005-03.org.vlnb:cacdcd2520 and only
++it be able to access target iqn.2006-10.net.vlnb:tgt:
++
++echo 'add_target iqn.2006-10.net.vlnb:tgt' >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo 'create allowed_ini' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/mgmt
++echo 'add dev1 0' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/allowed_ini/luns/mgmt
++echo 'add iqn.2005-03.org.vlnb:cacdcd2520' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/allowed_ini/initiators/mgmt
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/enabled
++
++Since there will be no default LUNs for the target, all initiators other
++than iqn.2005-03.org.vlnb:cacdcd2520 will be blocked from accessing it.
++
++Alternatively, you can create an empty security group and filter out in
++it all initiators except the allowed one:
++
++echo 'add_target iqn.2006-10.net.vlnb:tgt' >/sys/kernel/scst_tgt/targets/iscsi/mgmt
++echo 'add dev1 0' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/mgmt
++echo 'create denied_inis' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/mgmt
++echo 'add !iqn.2005-03.org.vlnb:cacdcd2520' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/denied_inis/initiators/mgmt
++echo 1 >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/enabled
++
++3. If you want to enable/disable one or more target's portals for
++particular initiators, you should set per_portal_acl attribute to 1 and
++specify SCST access control to those initiators. If an SCST security
++group doesn't have any LUNs, all the initiator, which should be assigned
++to it, will not see this target and/or its portal. For example:
++
++(We assume that an empty group "BLOCKING_GROUP" is already created by for
++target iqn.2006-10.net.vlnb:tgt by command (see above for more information):
++"echo 'create BLOCKING_GROUP' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/mgmt)
++
++echo 'add iqn.2005-03.org.vlnb:cacdcd2520#10.170.77.2' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/BLOCKING_GROUP/initiators/mgmt
++
++will block access of initiator iqn.2005-03.org.vlnb:cacdcd2520 to
++target iqn.2006-10.net.vlnb:tgt portal 10.170.77.2.
++
++Another example:
++
++echo 'add iqn.2005-03.org.vlnb:cacdcd2520*' >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/ini_groups/BLOCKING_GROUP/initiators/mgmt
++
++will block access of initiator iqn.2005-03.org.vlnb:cacdcd2520 to
++all target iqn.2006-10.net.vlnb:tgt portals.
++
++
++Troubleshooting
++---------------
++
++If you have any problems, start troubleshooting from looking at the
++kernel and system logs. In the kernel log iSCSI-SCST and SCST core send
++their messages, in the system log iscsi-scstd sends its messages. In
++most Linux distributions both those logs are put to /var/log/messages
++file.
++
++Then, it might be helpful to increase level of logging. For kernel
++modules you should make the debug build by enabling CONFIG_SCST_DEBUG.
++
++If after looking on the logs the reason of your problem is still unclear
++for you, report to SCST mailing list scst-devel@lists.sourceforge.net.
++
++
++Work if target's backstorage or link is too slow
++------------------------------------------------
++
++In some cases you can experience I/O stalls or see in the kernel log
++abort or reset messages. It can happen under high I/O load, when your
++target's backstorage gets overloaded, or working over a slow link, when
++the link can't serve all the queued commands on time,
++
++To workaround it you can reduce QueuedCommands parameter for the
++corresponding target to some lower value, like 8 (default is 32).
++
++Also see SCST README file for more details about that issue and ways to
++prevent it.
++
++
++Performance advices
++-------------------
++
++1. If you use Windows XP or Windows 2003+ as initiators, you can
++consider to decrease TcpAckFrequency parameter to 1. See
++http://support.microsoft.com/kb/328890/ or google for "TcpAckFrequency"
++for more details.
++
++2. See how to get the maximum throughput from iSCSI, for instance, at
++http://virtualgeek.typepad.com/virtual_geek/2009/01/a-multivendor-post-to-help-our-mutual-iscsi-customers-using-vmware.html.
++It's about VMware, but its recommendations apply to other environments
++as well.
++
++3. ISCSI initiators built in pre-CentOS/RHEL 5 reported to have some
++performance problems. If you use it, it is strongly advised to upgrade.
++
++4. If you are going to use your target in an VM environment, for
++instance as a shared storage with VMware, make sure all your VMs
++connected to the target via *separate* sessions, i.e. each VM has own
++connection to the target, not all VMs connected using a single
++connection. You can check it using SCST sysfs interface. If you
++miss it, you can greatly loose performance of parallel access to your
++target from different VMs. This isn't related to the case if your VMs
++are using the same shared storage, like with VMFS, for instance. In this
++case all your VM hosts will be connected to the target via separate
++sessions, which is enough.
++
++5. Many dual port network adapters are not able to transfer data
++simultaneously on both ports, i.e. they transfer data via both ports on
++the same speed as via any single port. Thus, using such adapters in MPIO
++configuration can't improve performance. To allow MPIO to have double
++performance you should either use separate network adapters, or find a
++dual-port adapter capable to to transfer data simultaneously on both
++ports. You can check it by running 2 iperf's through both ports in
++parallel.
++
++6. Since network offload works much better in the write direction, than
++for reading (simplifying, in the read direction often there's additional
++data copy) in many cases with 10GbE in a single initiator-target pair
++the initiator's CPU is a bottleneck, so you can see the initiator can
++read data on much slower rate, than write. You can check it by watching
++*each particular* CPU load to find out if any of them is close to 100%
++load, including IRQ processing load. Note, many tools like vmstat give
++aggregate load on all CPUs, so with 4 cores 25% corresponds to 100% load
++of any single CPU.
++
++7. For high speed network adapters it can be better if you configure
++them to serve connections, e.g., from initiator on CPU0 and from
++initiator Y on CPU1. Then you can bind threads processing them also to
++CPU0 and CPU1 correspondingly using cpu_mask attribute of their targets
++or security groups. In NUMA-like configurations it can signficantly
++boost IOPS performance.
++
++8. See SCST core's README for more advices. Especially pay attention to
++have io_grouping_type option set correctly.
++
++
++Compilation options
++-------------------
++
++There are the following compilation options, that could be commented
++in/out in the kernel's module Makefile:
++
++ - CONFIG_SCST_DEBUG - turns on some debugging code, including some logging.
++ Makes the driver considerably bigger and slower, producing large amount of
++ log data.
++
++ - CONFIG_SCST_TRACING - turns on ability to log events. Makes the driver
++ considerably bigger and leads to some performance loss.
++
++ - CONFIG_SCST_EXTRACHECKS - adds extra validity checks in the various places.
++
++ - CONFIG_SCST_ISCSI_DEBUG_DIGEST_FAILURES - simulates digest failures in
++ random places.
++
++
++Credits
++-------
++
++Thanks to:
++
++ * Ming Zhang <blackmagic02881@gmail.com> for fixes
++
++ * Krzysztof Blaszkowski <kb@sysmikro.com.pl> for many fixes
++
++ * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru> for comments and help in
++ debugging
++
++ * Tomasz Chmielewski <mangoo@wpkg.org> for testing and suggestions
++
++ * Bart Van Assche <bvanassche@acm.org> for a lot of help
++
++Vladislav Bolkhovitin <vst@vlnb.net>, http://scst.sourceforge.net
++
+diff -uprN orig/linux-3.2/drivers/scsi/qla2xxx/qla2x_tgt.h linux-3.2/drivers/scsi/qla2xxx/qla2x_tgt.h
+--- orig/linux-3.2/drivers/scsi/qla2xxx/qla2x_tgt.h
++++ linux-3.2/drivers/scsi/qla2xxx/qla2x_tgt.h
+@@ -0,0 +1,137 @@
++/*
++ * qla2x_tgt.h
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2006 Nathaniel Clark <nate@misrule.us>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * Additional file for the target driver support.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++/*
++ * This should be included only from within qla2xxx module.
++ */
++
++#ifndef __QLA2X_TGT_H
++#define __QLA2X_TGT_H
++
++#include <linux/version.h>
++
++extern request_t *qla2x00_req_pkt(scsi_qla_host_t *ha);
++
++#ifdef CONFIG_SCSI_QLA2XXX_TARGET
++
++#include "qla2x_tgt_def.h"
++
++extern struct qla_tgt_data qla_target;
++
++void qla_set_tgt_mode(scsi_qla_host_t *ha);
++void qla_clear_tgt_mode(scsi_qla_host_t *ha);
++
++static inline bool qla_tgt_mode_enabled(scsi_qla_host_t *ha)
++{
++ return ha->host->active_mode & MODE_TARGET;
++}
++
++static inline bool qla_ini_mode_enabled(scsi_qla_host_t *ha)
++{
++ return ha->host->active_mode & MODE_INITIATOR;
++}
++
++static inline void qla_reverse_ini_mode(scsi_qla_host_t *ha)
++{
++ if (ha->host->active_mode & MODE_INITIATOR)
++ ha->host->active_mode &= ~MODE_INITIATOR;
++ else
++ ha->host->active_mode |= MODE_INITIATOR;
++}
++
++/********************************************************************\
++ * ISP Queue types left out of new QLogic driver (from old version)
++\********************************************************************/
++
++/*
++ * qla2x00_do_en_dis_lun
++ * Issue enable or disable LUN entry IOCB.
++ *
++ * Input:
++ * ha = adapter block pointer.
++ *
++ * Caller MUST have hardware lock held. This function might release it,
++ * then reacquire.
++ */
++static inline void
++__qla2x00_send_enable_lun(scsi_qla_host_t *ha, int enable)
++{
++ elun_entry_t *pkt;
++
++ BUG_ON(IS_FWI2_CAPABLE(ha));
++
++ pkt = (elun_entry_t *)qla2x00_req_pkt(ha);
++ if (pkt != NULL) {
++ pkt->entry_type = ENABLE_LUN_TYPE;
++ if (enable) {
++ pkt->command_count = QLA2X00_COMMAND_COUNT_INIT;
++ pkt->immed_notify_count = QLA2X00_IMMED_NOTIFY_COUNT_INIT;
++ pkt->timeout = 0xffff;
++ } else {
++ pkt->command_count = 0;
++ pkt->immed_notify_count = 0;
++ pkt->timeout = 0;
++ }
++ DEBUG2(printk(KERN_DEBUG
++ "scsi%lu:ENABLE_LUN IOCB imm %u cmd %u timeout %u\n",
++ ha->host_no, pkt->immed_notify_count,
++ pkt->command_count, pkt->timeout));
++
++ /* Issue command to ISP */
++ qla2x00_isp_cmd(ha);
++
++ } else
++ qla_clear_tgt_mode(ha);
++#if defined(QL_DEBUG_LEVEL_2) || defined(QL_DEBUG_LEVEL_3)
++ if (!pkt)
++ printk(KERN_ERR "%s: **** FAILED ****\n", __func__);
++#endif
++
++ return;
++}
++
++/*
++ * qla2x00_send_enable_lun
++ * Issue enable LUN entry IOCB.
++ *
++ * Input:
++ * ha = adapter block pointer.
++ * enable = enable/disable flag.
++ */
++static inline void
++qla2x00_send_enable_lun(scsi_qla_host_t *ha, bool enable)
++{
++ if (!IS_FWI2_CAPABLE(ha)) {
++ unsigned long flags;
++ spin_lock_irqsave(&ha->hardware_lock, flags);
++ __qla2x00_send_enable_lun(ha, enable);
++ spin_unlock_irqrestore(&ha->hardware_lock, flags);
++ }
++}
++
++extern void qla2xxx_add_targets(void);
++extern size_t
++qla2xxx_add_vtarget(u64 *port_name, u64 *node_name, u64 *parent_host);
++extern size_t qla2xxx_del_vtarget(u64 *port_name);
++
++#endif /* CONFIG_SCSI_QLA2XXX_TARGET */
++
++#endif /* __QLA2X_TGT_H */
+diff -uprN orig/linux-3.2/drivers/scsi/qla2xxx/qla2x_tgt_def.h linux-3.2/drivers/scsi/qla2xxx/qla2x_tgt_def.h
+--- orig/linux-3.2/drivers/scsi/qla2xxx/qla2x_tgt_def.h
++++ linux-3.2/drivers/scsi/qla2xxx/qla2x_tgt_def.h
+@@ -0,0 +1,771 @@
++/*
++ * qla2x_tgt_def.h
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2006 Nathaniel Clark <nate@misrule.us>
++ * Copyright (C) 2007 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * Additional file for the target driver support.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++/*
++ * This is the global def file that is useful for including from the
++ * target portion.
++ */
++
++#ifndef __QLA2X_TGT_DEF_H
++#define __QLA2X_TGT_DEF_H
++
++#include "qla_def.h"
++
++#ifndef CONFIG_SCSI_QLA2XXX_TARGET
++#error __FILE__ " included without CONFIG_SCSI_QLA2XXX_TARGET"
++#endif
++
++#ifndef ENTER
++#define ENTER(a)
++#endif
++
++#ifndef LEAVE
++#define LEAVE(a)
++#endif
++
++/*
++ * Must be changed on any change in any initiator visible interfaces or
++ * data in the target add-on
++ */
++#define QLA2X_TARGET_MAGIC 270
++
++/*
++ * Must be changed on any change in any target visible interfaces or
++ * data in the initiator
++ */
++#define QLA2X_INITIATOR_MAGIC 57224
++
++#define QLA2X_INI_MODE_STR_EXCLUSIVE "exclusive"
++#define QLA2X_INI_MODE_STR_DISABLED "disabled"
++#define QLA2X_INI_MODE_STR_ENABLED "enabled"
++
++#define QLA2X_INI_MODE_EXCLUSIVE 0
++#define QLA2X_INI_MODE_DISABLED 1
++#define QLA2X_INI_MODE_ENABLED 2
++
++#define QLA2X00_COMMAND_COUNT_INIT 250
++#define QLA2X00_IMMED_NOTIFY_COUNT_INIT 250
++
++/*
++ * Used to mark which completion handles (for RIO Status's) are for CTIO's
++ * vs. regular (non-target) info. This is checked for in
++ * qla2x00_process_response_queue() to see if a handle coming back in a
++ * multi-complete should come to the tgt driver or be handled there by qla2xxx
++ */
++#define CTIO_COMPLETION_HANDLE_MARK BIT_29
++#if (CTIO_COMPLETION_HANDLE_MARK <= MAX_OUTSTANDING_COMMANDS)
++#error "Hackish CTIO_COMPLETION_HANDLE_MARK no longer larger than MAX_OUTSTANDING_COMMANDS"
++#endif
++#define HANDLE_IS_CTIO_COMP(h) (h & CTIO_COMPLETION_HANDLE_MARK)
++
++/* Used to mark CTIO as intermediate */
++#define CTIO_INTERMEDIATE_HANDLE_MARK BIT_30
++
++#ifndef OF_SS_MODE_0
++/*
++ * ISP target entries - Flags bit definitions.
++ */
++#define OF_SS_MODE_0 0
++#define OF_SS_MODE_1 1
++#define OF_SS_MODE_2 2
++#define OF_SS_MODE_3 3
++
++#define OF_EXPL_CONF BIT_5 /* Explicit Confirmation Requested */
++#define OF_DATA_IN BIT_6 /* Data in to initiator */
++ /* (data from target to initiator) */
++#define OF_DATA_OUT BIT_7 /* Data out from initiator */
++ /* (data from initiator to target) */
++#define OF_NO_DATA (BIT_7 | BIT_6)
++#define OF_INC_RC BIT_8 /* Increment command resource count */
++#define OF_FAST_POST BIT_9 /* Enable mailbox fast posting. */
++#define OF_CONF_REQ BIT_13 /* Confirmation Requested */
++#define OF_TERM_EXCH BIT_14 /* Terminate exchange */
++#define OF_SSTS BIT_15 /* Send SCSI status */
++#endif
++
++#ifndef DATASEGS_PER_COMMAND32
++#define DATASEGS_PER_COMMAND32 3
++#define DATASEGS_PER_CONT32 7
++#define QLA_MAX_SG32(ql) \
++ (((ql) > 0) ? \
++ (DATASEGS_PER_COMMAND32 + DATASEGS_PER_CONT32 * ((ql) - 1)) : 0)
++
++#define DATASEGS_PER_COMMAND64 2
++#define DATASEGS_PER_CONT64 5
++#define QLA_MAX_SG64(ql) \
++ (((ql) > 0) ? \
++ (DATASEGS_PER_COMMAND64 + DATASEGS_PER_CONT64 * ((ql) - 1)) : 0)
++#endif
++
++#ifndef DATASEGS_PER_COMMAND_24XX
++#define DATASEGS_PER_COMMAND_24XX 1
++#define DATASEGS_PER_CONT_24XX 5
++#define QLA_MAX_SG_24XX(ql) \
++ (min(1270, ((ql) > 0) ? \
++ (DATASEGS_PER_COMMAND_24XX + DATASEGS_PER_CONT_24XX * ((ql) - 1)) : 0))
++#endif
++
++/********************************************************************\
++ * ISP Queue types left out of new QLogic driver (from old version)
++\********************************************************************/
++
++#ifndef ENABLE_LUN_TYPE
++#define ENABLE_LUN_TYPE 0x0B /* Enable LUN entry. */
++/*
++ * ISP queue - enable LUN entry structure definition.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t sys_define_2; /* System defined. */
++ uint8_t reserved_8;
++ uint8_t reserved_1;
++ uint16_t reserved_2;
++ uint32_t reserved_3;
++ uint8_t status;
++ uint8_t reserved_4;
++ uint8_t command_count; /* Number of ATIOs allocated. */
++ uint8_t immed_notify_count; /* Number of Immediate Notify entries allocated. */
++ uint16_t reserved_5;
++ uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */
++ uint16_t reserved_6[20];
++} __attribute__((packed)) elun_entry_t;
++#define ENABLE_LUN_SUCCESS 0x01
++#define ENABLE_LUN_RC_NONZERO 0x04
++#define ENABLE_LUN_INVALID_REQUEST 0x06
++#define ENABLE_LUN_ALREADY_ENABLED 0x3E
++#endif
++
++#ifndef MODIFY_LUN_TYPE
++#define MODIFY_LUN_TYPE 0x0C /* Modify LUN entry. */
++/*
++ * ISP queue - modify LUN entry structure definition.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t sys_define_2; /* System defined. */
++ uint8_t reserved_8;
++ uint8_t reserved_1;
++ uint8_t operators;
++ uint8_t reserved_2;
++ uint32_t reserved_3;
++ uint8_t status;
++ uint8_t reserved_4;
++ uint8_t command_count; /* Number of ATIOs allocated. */
++ uint8_t immed_notify_count; /* Number of Immediate Notify */
++ /* entries allocated. */
++ uint16_t reserved_5;
++ uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */
++ uint16_t reserved_7[20];
++} __attribute__((packed)) modify_lun_entry_t;
++#define MODIFY_LUN_SUCCESS 0x01
++#define MODIFY_LUN_CMD_ADD BIT_0
++#define MODIFY_LUN_CMD_SUB BIT_1
++#define MODIFY_LUN_IMM_ADD BIT_2
++#define MODIFY_LUN_IMM_SUB BIT_3
++#endif
++
++#define GET_TARGET_ID(ha, iocb) ((HAS_EXTENDED_IDS(ha)) \
++ ? le16_to_cpu((iocb)->target.extended) \
++ : (uint16_t)(iocb)->target.id.standard)
++
++#ifndef IMMED_NOTIFY_TYPE
++#define IMMED_NOTIFY_TYPE 0x0D /* Immediate notify entry. */
++/*
++ * ISP queue - immediate notify entry structure definition.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t sys_define_2; /* System defined. */
++ target_id_t target;
++ uint16_t lun;
++ uint8_t target_id;
++ uint8_t reserved_1;
++ uint16_t status_modifier;
++ uint16_t status;
++ uint16_t task_flags;
++ uint16_t seq_id;
++ uint16_t srr_rx_id;
++ uint32_t srr_rel_offs;
++ uint16_t srr_ui;
++#define SRR_IU_DATA_IN 0x1
++#define SRR_IU_DATA_OUT 0x5
++#define SRR_IU_STATUS 0x7
++ uint16_t srr_ox_id;
++ uint8_t reserved_2[30];
++ uint16_t ox_id;
++} __attribute__((packed)) notify_entry_t;
++#endif
++
++#ifndef NOTIFY_ACK_TYPE
++#define NOTIFY_ACK_TYPE 0x0E /* Notify acknowledge entry. */
++/*
++ * ISP queue - notify acknowledge entry structure definition.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t sys_define_2; /* System defined. */
++ target_id_t target;
++ uint8_t target_id;
++ uint8_t reserved_1;
++ uint16_t flags;
++ uint16_t resp_code;
++ uint16_t status;
++ uint16_t task_flags;
++ uint16_t seq_id;
++ uint16_t srr_rx_id;
++ uint32_t srr_rel_offs;
++ uint16_t srr_ui;
++ uint16_t srr_flags;
++ uint16_t srr_reject_code;
++ uint8_t srr_reject_vendor_uniq;
++ uint8_t srr_reject_code_expl;
++ uint8_t reserved_2[26];
++ uint16_t ox_id;
++} __attribute__((packed)) nack_entry_t;
++#define NOTIFY_ACK_SRR_FLAGS_ACCEPT 0
++#define NOTIFY_ACK_SRR_FLAGS_REJECT 1
++
++#define NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM 0x9
++
++#define NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL 0
++#define NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_UNABLE_TO_SUPPLY_DATA 0x2a
++
++#define NOTIFY_ACK_SUCCESS 0x01
++#endif
++
++#ifndef ACCEPT_TGT_IO_TYPE
++#define ACCEPT_TGT_IO_TYPE 0x16 /* Accept target I/O entry. */
++/*
++ * ISP queue - Accept Target I/O (ATIO) entry structure definition.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t sys_define_2; /* System defined. */
++ target_id_t target;
++ uint16_t rx_id;
++ uint16_t flags;
++ uint16_t status;
++ uint8_t command_ref;
++ uint8_t task_codes;
++ uint8_t task_flags;
++ uint8_t execution_codes;
++ uint8_t cdb[MAX_CMDSZ];
++ uint32_t data_length;
++ uint16_t lun;
++ uint8_t initiator_port_name[WWN_SIZE]; /* on qla23xx */
++ uint16_t reserved_32[6];
++ uint16_t ox_id;
++} __attribute__((packed)) atio_entry_t;
++#endif
++
++#ifndef CONTINUE_TGT_IO_TYPE
++#define CONTINUE_TGT_IO_TYPE 0x17
++/*
++ * ISP queue - Continue Target I/O (CTIO) entry for status mode 0
++ * structure definition.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t handle; /* System defined handle */
++ target_id_t target;
++ uint16_t rx_id;
++ uint16_t flags;
++ uint16_t status;
++ uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */
++ uint16_t dseg_count; /* Data segment count. */
++ uint32_t relative_offset;
++ uint32_t residual;
++ uint16_t reserved_1[3];
++ uint16_t scsi_status;
++ uint32_t transfer_length;
++ uint32_t dseg_0_address[0];
++} __attribute__((packed)) ctio_common_entry_t;
++#define ATIO_PATH_INVALID 0x07
++#define ATIO_CANT_PROV_CAP 0x16
++#define ATIO_CDB_VALID 0x3D
++
++#define ATIO_EXEC_READ BIT_1
++#define ATIO_EXEC_WRITE BIT_0
++#endif
++
++#ifndef CTIO_A64_TYPE
++#define CTIO_A64_TYPE 0x1F
++typedef struct {
++ ctio_common_entry_t common;
++ uint32_t dseg_0_address; /* Data segment 0 address. */
++ uint32_t dseg_0_length; /* Data segment 0 length. */
++ uint32_t dseg_1_address; /* Data segment 1 address. */
++ uint32_t dseg_1_length; /* Data segment 1 length. */
++ uint32_t dseg_2_address; /* Data segment 2 address. */
++ uint32_t dseg_2_length; /* Data segment 2 length. */
++} __attribute__((packed)) ctio_entry_t;
++#define CTIO_SUCCESS 0x01
++#define CTIO_ABORTED 0x02
++#define CTIO_INVALID_RX_ID 0x08
++#define CTIO_TIMEOUT 0x0B
++#define CTIO_LIP_RESET 0x0E
++#define CTIO_TARGET_RESET 0x17
++#define CTIO_PORT_UNAVAILABLE 0x28
++#define CTIO_PORT_LOGGED_OUT 0x29
++#define CTIO_PORT_CONF_CHANGED 0x2A
++#define CTIO_SRR_RECEIVED 0x45
++
++#endif
++
++#ifndef CTIO_RET_TYPE
++#define CTIO_RET_TYPE 0x17 /* CTIO return entry */
++/*
++ * ISP queue - CTIO returned entry structure definition.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t handle; /* System defined handle. */
++ target_id_t target;
++ uint16_t rx_id;
++ uint16_t flags;
++ uint16_t status;
++ uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */
++ uint16_t dseg_count; /* Data segment count. */
++ uint32_t relative_offset;
++ uint32_t residual;
++ uint16_t reserved_1[2];
++ uint16_t sense_length;
++ uint16_t scsi_status;
++ uint16_t response_length;
++ uint8_t sense_data[26];
++} __attribute__((packed)) ctio_ret_entry_t;
++#endif
++
++#define ATIO_TYPE7 0x06 /* Accept target I/O entry for 24xx */
++
++typedef struct {
++ uint8_t r_ctl;
++ uint8_t d_id[3];
++ uint8_t cs_ctl;
++ uint8_t s_id[3];
++ uint8_t type;
++ uint8_t f_ctl[3];
++ uint8_t seq_id;
++ uint8_t df_ctl;
++ uint16_t seq_cnt;
++ uint16_t ox_id;
++ uint16_t rx_id;
++ uint32_t parameter;
++} __attribute__((packed)) fcp_hdr_t;
++
++typedef struct {
++ uint8_t d_id[3];
++ uint8_t r_ctl;
++ uint8_t s_id[3];
++ uint8_t cs_ctl;
++ uint8_t f_ctl[3];
++ uint8_t type;
++ uint16_t seq_cnt;
++ uint8_t df_ctl;
++ uint8_t seq_id;
++ uint16_t rx_id;
++ uint16_t ox_id;
++ uint32_t parameter;
++} __attribute__((packed)) fcp_hdr_le_t;
++
++#define F_CTL_EXCH_CONTEXT_RESP BIT_23
++#define F_CTL_SEQ_CONTEXT_RESIP BIT_22
++#define F_CTL_LAST_SEQ BIT_20
++#define F_CTL_END_SEQ BIT_19
++#define F_CTL_SEQ_INITIATIVE BIT_16
++
++#define R_CTL_BASIC_LINK_SERV 0x80
++#define R_CTL_B_ACC 0x4
++#define R_CTL_B_RJT 0x5
++
++typedef struct {
++ uint64_t lun;
++ uint8_t cmnd_ref;
++#ifdef __LITTLE_ENDIAN
++ uint8_t task_attr:3;
++ uint8_t reserved:5;
++#else
++ uint8_t reserved:5;
++ uint8_t task_attr:3;
++#endif
++ uint8_t task_mgmt_flags;
++#define FCP_CMND_TASK_MGMT_CLEAR_ACA 6
++#define FCP_CMND_TASK_MGMT_TARGET_RESET 5
++#define FCP_CMND_TASK_MGMT_LU_RESET 4
++#define FCP_CMND_TASK_MGMT_CLEAR_TASK_SET 2
++#define FCP_CMND_TASK_MGMT_ABORT_TASK_SET 1
++#ifdef __LITTLE_ENDIAN
++ uint8_t wrdata:1;
++ uint8_t rddata:1;
++ uint8_t add_cdb_len:6;
++#else
++ uint8_t add_cdb_len:6;
++ uint8_t rddata:1;
++ uint8_t wrdata:1;
++#endif
++ uint8_t cdb[16];
++ /*
++ * add_cdb is optional and can absent from fcp_cmnd_t. Size 4 only to
++ * make sizeof(fcp_cmnd_t) be as expected by BUILD_BUG_ON() in
++ * q2t_init().
++ */
++ uint8_t add_cdb[4];
++ /* uint32_t data_length; */
++} __attribute__((packed)) fcp_cmnd_t;
++
++/*
++ * ISP queue - Accept Target I/O (ATIO) type 7 entry for 24xx structure
++ * definition.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t fcp_cmnd_len_low;
++#ifdef __LITTLE_ENDIAN
++ uint8_t fcp_cmnd_len_high:4;
++ uint8_t attr:4;
++#else
++ uint8_t attr:4;
++ uint8_t fcp_cmnd_len_high:4;
++#endif
++ uint32_t exchange_addr;
++#define ATIO_EXCHANGE_ADDRESS_UNKNOWN 0xFFFFFFFF
++ fcp_hdr_t fcp_hdr;
++ fcp_cmnd_t fcp_cmnd;
++} __attribute__((packed)) atio7_entry_t;
++
++#define CTIO_TYPE7 0x12 /* Continue target I/O entry (for 24xx) */
++
++/*
++ * ISP queue - Continue Target I/O (ATIO) type 7 entry (for 24xx) structure
++ * definition.
++ */
++
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t handle; /* System defined handle */
++ uint16_t nport_handle;
++#define CTIO7_NHANDLE_UNRECOGNIZED 0xFFFF
++ uint16_t timeout;
++ uint16_t dseg_count; /* Data segment count. */
++ uint8_t vp_index;
++ uint8_t add_flags;
++ uint8_t initiator_id[3];
++ uint8_t reserved;
++ uint32_t exchange_addr;
++} __attribute__((packed)) ctio7_common_entry_t;
++
++typedef struct {
++ ctio7_common_entry_t common;
++ uint16_t reserved1;
++ uint16_t flags;
++ uint32_t residual;
++ uint16_t ox_id;
++ uint16_t scsi_status;
++ uint32_t relative_offset;
++ uint32_t reserved2;
++ uint32_t transfer_length;
++ uint32_t reserved3;
++ uint32_t dseg_0_address[2]; /* Data segment 0 address. */
++ uint32_t dseg_0_length; /* Data segment 0 length. */
++} __attribute__((packed)) ctio7_status0_entry_t;
++
++typedef struct {
++ ctio7_common_entry_t common;
++ uint16_t sense_length;
++ uint16_t flags;
++ uint32_t residual;
++ uint16_t ox_id;
++ uint16_t scsi_status;
++ uint16_t response_len;
++ uint16_t reserved;
++ uint8_t sense_data[24];
++} __attribute__((packed)) ctio7_status1_entry_t;
++
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t handle; /* System defined handle */
++ uint16_t status;
++ uint16_t timeout;
++ uint16_t dseg_count; /* Data segment count. */
++ uint8_t vp_index;
++ uint8_t reserved1[5];
++ uint32_t exchange_address;
++ uint16_t reserved2;
++ uint16_t flags;
++ uint32_t residual;
++ uint16_t ox_id;
++ uint16_t reserved3;
++ uint32_t relative_offset;
++ uint8_t reserved4[24];
++} __attribute__((packed)) ctio7_fw_entry_t;
++
++/* CTIO7 flags values */
++#define CTIO7_FLAGS_SEND_STATUS BIT_15
++#define CTIO7_FLAGS_TERMINATE BIT_14
++#define CTIO7_FLAGS_CONFORM_REQ BIT_13
++#define CTIO7_FLAGS_DONT_RET_CTIO BIT_8
++#define CTIO7_FLAGS_STATUS_MODE_0 0
++#define CTIO7_FLAGS_STATUS_MODE_1 BIT_6
++#define CTIO7_FLAGS_EXPLICIT_CONFORM BIT_5
++#define CTIO7_FLAGS_CONFIRM_SATISF BIT_4
++#define CTIO7_FLAGS_DSD_PTR BIT_2
++#define CTIO7_FLAGS_DATA_IN BIT_1
++#define CTIO7_FLAGS_DATA_OUT BIT_0
++
++/*
++ * ISP queue - immediate notify entry structure definition for 24xx.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t reserved;
++ uint16_t nport_handle;
++ uint16_t reserved_2;
++ uint16_t flags;
++#define NOTIFY24XX_FLAGS_GLOBAL_TPRLO BIT_1
++#define NOTIFY24XX_FLAGS_PUREX_IOCB BIT_0
++ uint16_t srr_rx_id;
++ uint16_t status;
++ uint8_t status_subcode;
++ uint8_t reserved_3;
++ uint32_t exchange_address;
++ uint32_t srr_rel_offs;
++ uint16_t srr_ui;
++ uint16_t srr_ox_id;
++ uint8_t reserved_4[19];
++ uint8_t vp_index;
++ uint32_t reserved_5;
++ uint8_t port_id[3];
++ uint8_t reserved_6;
++ uint16_t reserved_7;
++ uint16_t ox_id;
++} __attribute__((packed)) notify24xx_entry_t;
++
++#define ELS_PLOGI 0x3
++#define ELS_FLOGI 0x4
++#define ELS_LOGO 0x5
++#define ELS_PRLI 0x20
++#define ELS_PRLO 0x21
++#define ELS_TPRLO 0x24
++#define ELS_PDISC 0x50
++#define ELS_ADISC 0x52
++
++/*
++ * ISP queue - notify acknowledge entry structure definition for 24xx.
++ */
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t handle;
++ uint16_t nport_handle;
++ uint16_t reserved_1;
++ uint16_t flags;
++ uint16_t srr_rx_id;
++ uint16_t status;
++ uint8_t status_subcode;
++ uint8_t reserved_3;
++ uint32_t exchange_address;
++ uint32_t srr_rel_offs;
++ uint16_t srr_ui;
++ uint16_t srr_flags;
++ uint8_t reserved_4[19];
++ uint8_t vp_index;
++ uint8_t srr_reject_vendor_uniq;
++ uint8_t srr_reject_code_expl;
++ uint8_t srr_reject_code;
++ uint8_t reserved_5[7];
++ uint16_t ox_id;
++} __attribute__((packed)) nack24xx_entry_t;
++
++/*
++ * ISP queue - ABTS received/response entries structure definition for 24xx.
++ */
++#define ABTS_RECV_24XX 0x54 /* ABTS received (for 24xx) */
++#define ABTS_RESP_24XX 0x55 /* ABTS responce (for 24xx) */
++
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint8_t reserved_1[6];
++ uint16_t nport_handle;
++ uint8_t reserved_2[2];
++ uint8_t vp_index;
++#ifdef __LITTLE_ENDIAN
++ uint8_t reserved_3:4;
++ uint8_t sof_type:4;
++#else
++ uint8_t sof_type:4;
++ uint8_t reserved_3:4;
++#endif
++ uint32_t exchange_address;
++ fcp_hdr_le_t fcp_hdr_le;
++ uint8_t reserved_4[16];
++ uint32_t exchange_addr_to_abort;
++} __attribute__((packed)) abts24_recv_entry_t;
++
++#define ABTS_PARAM_ABORT_SEQ BIT_0
++
++typedef struct {
++ uint16_t reserved;
++ uint8_t seq_id_last;
++ uint8_t seq_id_valid;
++#define SEQ_ID_VALID 0x80
++#define SEQ_ID_INVALID 0x00
++ uint16_t rx_id;
++ uint16_t ox_id;
++ uint16_t high_seq_cnt;
++ uint16_t low_seq_cnt;
++} __attribute__((packed)) ba_acc_le_t;
++
++typedef struct {
++ uint8_t vendor_uniq;
++ uint8_t reason_expl;
++ uint8_t reason_code;
++#define BA_RJT_REASON_CODE_INVALID_COMMAND 0x1
++#define BA_RJT_REASON_CODE_UNABLE_TO_PERFORM 0x9
++ uint8_t reserved;
++} __attribute__((packed)) ba_rjt_le_t;
++
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t handle;
++ uint16_t reserved_1;
++ uint16_t nport_handle;
++ uint16_t control_flags;
++#define ABTS_CONTR_FLG_TERM_EXCHG BIT_0
++ uint8_t vp_index;
++#ifdef __LITTLE_ENDIAN
++ uint8_t reserved_3:4;
++ uint8_t sof_type:4;
++#else
++ uint8_t sof_type:4;
++ uint8_t reserved_3:4;
++#endif
++ uint32_t exchange_address;
++ fcp_hdr_le_t fcp_hdr_le;
++ union {
++ ba_acc_le_t ba_acct;
++ ba_rjt_le_t ba_rjt;
++ } __attribute__((packed)) payload;
++ uint32_t reserved_4;
++ uint32_t exchange_addr_to_abort;
++} __attribute__((packed)) abts24_resp_entry_t;
++
++typedef struct {
++ uint8_t entry_type; /* Entry type. */
++ uint8_t entry_count; /* Entry count. */
++ uint8_t sys_define; /* System defined. */
++ uint8_t entry_status; /* Entry Status. */
++ uint32_t handle;
++ uint16_t compl_status;
++#define ABTS_RESP_COMPL_SUCCESS 0
++#define ABTS_RESP_COMPL_SUBCODE_ERROR 0x31
++ uint16_t nport_handle;
++ uint16_t reserved_1;
++ uint8_t reserved_2;
++#ifdef __LITTLE_ENDIAN
++ uint8_t reserved_3:4;
++ uint8_t sof_type:4;
++#else
++ uint8_t sof_type:4;
++ uint8_t reserved_3:4;
++#endif
++ uint32_t exchange_address;
++ fcp_hdr_le_t fcp_hdr_le;
++ uint8_t reserved_4[8];
++ uint32_t error_subcode1;
++#define ABTS_RESP_SUBCODE_ERR_ABORTED_EXCH_NOT_TERM 0x1E
++ uint32_t error_subcode2;
++ uint32_t exchange_addr_to_abort;
++} __attribute__((packed)) abts24_resp_fw_entry_t;
++
++/********************************************************************\
++ * Type Definitions used by initiator & target halves
++\********************************************************************/
++
++typedef enum {
++ ADD_TARGET = 0,
++ REMOVE_TARGET,
++ DISABLE_TARGET_MODE,
++ ENABLE_TARGET_MODE,
++} qla2x_tgt_host_action_t;
++
++/* Changing it don't forget to change QLA2X_TARGET_MAGIC! */
++struct qla_tgt_data {
++ int magic;
++
++ /* Callbacks */
++ void (*tgt24_atio_pkt)(scsi_qla_host_t *ha, atio7_entry_t *pkt);
++ void (*tgt_response_pkt)(scsi_qla_host_t *ha, response_t *pkt);
++ void (*tgt2x_ctio_completion)(scsi_qla_host_t *ha, uint32_t handle);
++ void (*tgt_async_event)(uint16_t code, scsi_qla_host_t *ha,
++ uint16_t *mailbox);
++ int (*tgt_host_action)(scsi_qla_host_t *ha, qla2x_tgt_host_action_t
++ action);
++ void (*tgt_fc_port_added)(scsi_qla_host_t *ha, fc_port_t *fcport);
++ void (*tgt_fc_port_deleted)(scsi_qla_host_t *ha, fc_port_t *fcport);
++};
++
++int qla2xxx_tgt_register_driver(struct qla_tgt_data *tgt);
++
++void qla2xxx_tgt_unregister_driver(void);
++
++int qla2x00_wait_for_loop_ready(scsi_qla_host_t *ha);
++int qla2x00_wait_for_hba_online(scsi_qla_host_t *ha);
++
++#endif /* __QLA2X_TGT_DEF_H */
+diff -uprN orig/linux-3.2/drivers/scst/qla2xxx-target/Makefile linux-3.2/drivers/scst/qla2xxx-target/Makefile
+--- orig/linux-3.2/drivers/scst/qla2xxx-target/Makefile
++++ linux-3.2/drivers/scst/qla2xxx-target/Makefile
+@@ -0,0 +1,5 @@
++ccflags-y += -Idrivers/scsi/qla2xxx
++
++qla2x00tgt-y := qla2x00t.o
++
++obj-$(CONFIG_SCST_QLA_TGT_ADDON) += qla2x00tgt.o
+diff -uprN orig/linux-3.2/drivers/scst/qla2xxx-target/Kconfig linux-3.2/drivers/scst/qla2xxx-target/Kconfig
+--- orig/linux-3.2/drivers/scst/qla2xxx-target/Kconfig
++++ linux-3.2/drivers/scst/qla2xxx-target/Kconfig
+@@ -0,0 +1,30 @@
++config SCST_QLA_TGT_ADDON
++ tristate "QLogic 2XXX Target Mode Add-On"
++ depends on SCST && SCSI_QLA_FC && SCSI_QLA2XXX_TARGET
++ default SCST
++ help
++ Target mode add-on driver for QLogic 2xxx Fibre Channel host adapters.
++ Visit http://scst.sourceforge.net for more info about this driver.
++
++config QLA_TGT_DEBUG_WORK_IN_THREAD
++ bool "Use threads context only"
++ depends on SCST_QLA_TGT_ADDON
++ help
++ Makes SCST process incoming commands from the qla2x00t target
++ driver and call the driver's callbacks in internal SCST
++ threads context instead of SIRQ context, where thise commands
++ were received. Useful for debugging and lead to some
++ performance loss.
++
++ If unsure, say "N".
++
++config QLA_TGT_DEBUG_SRR
++ bool "SRR debugging"
++ depends on SCST_QLA_TGT_ADDON
++ help
++ Turns on retransmitting packets (SRR)
++ debugging. In this mode some CTIOs will be "broken" to force the
++ initiator to issue a retransmit request. Useful for debugging and lead to big
++ performance loss.
++
++ If unsure, say "N".
+diff -uprN orig/linux-3.2/drivers/scst/qla2xxx-target/qla2x00t.c linux-3.2/drivers/scst/qla2xxx-target/qla2x00t.c
+--- orig/linux-3.2/drivers/scst/qla2xxx-target/qla2x00t.c
++++ linux-3.2/drivers/scst/qla2xxx-target/qla2x00t.c
+@@ -0,0 +1,6448 @@
++/*
++ * qla2x00t.c
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2006 Nathaniel Clark <nate@misrule.us>
++ * Copyright (C) 2006 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * QLogic 22xx/23xx/24xx/25xx FC target driver.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation, version 2
++ * of the License.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/types.h>
++#include <linux/version.h>
++#include <linux/blkdev.h>
++#include <linux/interrupt.h>
++#include <scsi/scsi.h>
++#include <scsi/scsi_host.h>
++#include <linux/pci.h>
++#include <linux/delay.h>
++#include <linux/list.h>
++#include <asm/unaligned.h>
++
++#include <scst/scst.h>
++
++#include "qla2x00t.h"
++
++/*
++ * This driver calls qla2x00_req_pkt() and qla2x00_issue_marker(), which
++ * must be called under HW lock and could unlock/lock it inside.
++ * It isn't an issue, since in the current implementation on the time when
++ * those functions are called:
++ *
++ * - Either context is IRQ and only IRQ handler can modify HW data,
++ * including rings related fields,
++ *
++ * - Or access to target mode variables from struct q2t_tgt doesn't
++ * cross those functions boundaries, except tgt_stop, which
++ * additionally protected by irq_cmd_count.
++ */
++
++#ifndef CONFIG_SCSI_QLA2XXX_TARGET
++#error "CONFIG_SCSI_QLA2XXX_TARGET is NOT DEFINED"
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++#define Q2T_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_LINE | TRACE_PID | \
++ TRACE_OUT_OF_MEM | TRACE_MGMT | TRACE_MGMT_DEBUG | \
++ TRACE_MINOR | TRACE_SPECIAL)
++#else
++# ifdef CONFIG_SCST_TRACING
++#define Q2T_DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \
++ TRACE_SPECIAL)
++# endif
++#endif
++
++static int q2t_target_detect(struct scst_tgt_template *templ);
++static int q2t_target_release(struct scst_tgt *scst_tgt);
++static int q2x_xmit_response(struct scst_cmd *scst_cmd);
++static int __q24_xmit_response(struct q2t_cmd *cmd, int xmit_type);
++static int q2t_rdy_to_xfer(struct scst_cmd *scst_cmd);
++static void q2t_on_free_cmd(struct scst_cmd *scst_cmd);
++static void q2t_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd);
++static int q2t_get_initiator_port_transport_id(struct scst_tgt *tgt,
++ struct scst_session *scst_sess, uint8_t **transport_id);
++
++/* Predefs for callbacks handed to qla2xxx(target) */
++static void q24_atio_pkt(scsi_qla_host_t *ha, atio7_entry_t *pkt);
++static void q2t_response_pkt(scsi_qla_host_t *ha, response_t *pkt);
++static void q2t_async_event(uint16_t code, scsi_qla_host_t *ha,
++ uint16_t *mailbox);
++static void q2x_ctio_completion(scsi_qla_host_t *ha, uint32_t handle);
++static int q2t_host_action(scsi_qla_host_t *ha,
++ qla2x_tgt_host_action_t action);
++static void q2t_fc_port_added(scsi_qla_host_t *ha, fc_port_t *fcport);
++static void q2t_fc_port_deleted(scsi_qla_host_t *ha, fc_port_t *fcport);
++static int q2t_issue_task_mgmt(struct q2t_sess *sess, uint8_t *lun,
++ int lun_size, int fn, void *iocb, int flags);
++static void q2x_send_term_exchange(scsi_qla_host_t *ha, struct q2t_cmd *cmd,
++ atio_entry_t *atio, int ha_locked);
++static void q24_send_term_exchange(scsi_qla_host_t *ha, struct q2t_cmd *cmd,
++ atio7_entry_t *atio, int ha_locked);
++static void q2t_reject_free_srr_imm(scsi_qla_host_t *ha, struct srr_imm *imm,
++ int ha_lock);
++static int q2t_cut_cmd_data_head(struct q2t_cmd *cmd, unsigned int offset);
++static void q2t_clear_tgt_db(struct q2t_tgt *tgt, bool local_only);
++static void q2t_on_hw_pending_cmd_timeout(struct scst_cmd *scst_cmd);
++static int q2t_unreg_sess(struct q2t_sess *sess);
++static uint16_t q2t_get_scsi_transport_version(struct scst_tgt *scst_tgt);
++static uint16_t q2t_get_phys_transport_version(struct scst_tgt *scst_tgt);
++
++/** SYSFS **/
++
++static ssize_t q2t_version_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++
++struct kobj_attribute q2t_version_attr =
++ __ATTR(version, S_IRUGO, q2t_version_show, NULL);
++
++static const struct attribute *q2t_attrs[] = {
++ &q2t_version_attr.attr,
++ NULL,
++};
++
++static ssize_t q2t_show_expl_conf_enabled(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buffer);
++static ssize_t q2t_store_expl_conf_enabled(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size);
++
++struct kobj_attribute q2t_expl_conf_attr =
++ __ATTR(explicit_confirmation, S_IRUGO|S_IWUSR,
++ q2t_show_expl_conf_enabled, q2t_store_expl_conf_enabled);
++
++static ssize_t q2t_abort_isp_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size);
++
++struct kobj_attribute q2t_abort_isp_attr =
++ __ATTR(abort_isp, S_IWUSR, NULL, q2t_abort_isp_store);
++
++static ssize_t q2t_hw_target_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++
++static struct kobj_attribute q2t_hw_target_attr =
++ __ATTR(hw_target, S_IRUGO, q2t_hw_target_show, NULL);
++
++static ssize_t q2t_node_name_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++
++static struct kobj_attribute q2t_vp_node_name_attr =
++ __ATTR(node_name, S_IRUGO, q2t_node_name_show, NULL);
++
++static ssize_t q2t_node_name_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size);
++
++static struct kobj_attribute q2t_hw_node_name_attr =
++ __ATTR(node_name, S_IRUGO|S_IWUSR, q2t_node_name_show,
++ q2t_node_name_store);
++
++static ssize_t q2t_vp_parent_host_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf);
++
++static struct kobj_attribute q2t_vp_parent_host_attr =
++ __ATTR(parent_host, S_IRUGO, q2t_vp_parent_host_show, NULL);
++
++static const struct attribute *q2t_tgt_attrs[] = {
++ &q2t_expl_conf_attr.attr,
++ &q2t_abort_isp_attr.attr,
++ NULL,
++};
++
++static int q2t_enable_tgt(struct scst_tgt *tgt, bool enable);
++static bool q2t_is_tgt_enabled(struct scst_tgt *tgt);
++static ssize_t q2t_add_vtarget(const char *target_name, char *params);
++static ssize_t q2t_del_vtarget(const char *target_name);
++
++/*
++ * Global Variables
++ */
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++#define trace_flag q2t_trace_flag
++static unsigned long q2t_trace_flag = Q2T_DEFAULT_LOG_FLAGS;
++#endif
++
++static struct scst_tgt_template tgt2x_template = {
++ .name = "qla2x00t",
++ .sg_tablesize = 0,
++ .use_clustering = 1,
++#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
++ .xmit_response_atomic = 0,
++ .rdy_to_xfer_atomic = 0,
++#else
++ .xmit_response_atomic = 1,
++ .rdy_to_xfer_atomic = 1,
++#endif
++ .max_hw_pending_time = Q2T_MAX_HW_PENDING_TIME,
++ .detect = q2t_target_detect,
++ .release = q2t_target_release,
++ .xmit_response = q2x_xmit_response,
++ .rdy_to_xfer = q2t_rdy_to_xfer,
++ .on_free_cmd = q2t_on_free_cmd,
++ .task_mgmt_fn_done = q2t_task_mgmt_fn_done,
++ .get_initiator_port_transport_id = q2t_get_initiator_port_transport_id,
++ .get_scsi_transport_version = q2t_get_scsi_transport_version,
++ .get_phys_transport_version = q2t_get_phys_transport_version,
++ .on_hw_pending_cmd_timeout = q2t_on_hw_pending_cmd_timeout,
++ .enable_target = q2t_enable_tgt,
++ .is_target_enabled = q2t_is_tgt_enabled,
++ .add_target = q2t_add_vtarget,
++ .del_target = q2t_del_vtarget,
++ .add_target_parameters = "node_name, parent_host",
++ .tgtt_attrs = q2t_attrs,
++ .tgt_attrs = q2t_tgt_attrs,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = Q2T_DEFAULT_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static struct kmem_cache *q2t_cmd_cachep;
++static struct kmem_cache *q2t_mgmt_cmd_cachep;
++static mempool_t *q2t_mgmt_cmd_mempool;
++
++static DECLARE_RWSEM(q2t_unreg_rwsem);
++
++/* It's not yet supported */
++static inline int scst_cmd_get_ppl_offset(struct scst_cmd *scst_cmd)
++{
++ return 0;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static inline void q2t_sess_get(struct q2t_sess *sess)
++{
++ sess->sess_ref++;
++ TRACE_DBG("sess %p, new sess_ref %d", sess, sess->sess_ref);
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static inline void q2t_sess_put(struct q2t_sess *sess)
++{
++ TRACE_DBG("sess %p, new sess_ref %d", sess, sess->sess_ref-1);
++ BUG_ON(sess->sess_ref == 0);
++
++ sess->sess_ref--;
++ if (sess->sess_ref == 0)
++ q2t_unreg_sess(sess);
++}
++
++/* pha->hardware_lock supposed to be held on entry (to protect tgt->sess_list) */
++static inline struct q2t_sess *q2t_find_sess_by_loop_id(struct q2t_tgt *tgt,
++ uint16_t loop_id)
++{
++ struct q2t_sess *sess;
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ if ((loop_id == sess->loop_id) && !sess->deleted)
++ return sess;
++ }
++ return NULL;
++}
++
++/* pha->hardware_lock supposed to be held on entry (to protect tgt->sess_list) */
++static inline struct q2t_sess *q2t_find_sess_by_s_id_include_deleted(
++ struct q2t_tgt *tgt, const uint8_t *s_id)
++{
++ struct q2t_sess *sess;
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ if ((sess->s_id.b.al_pa == s_id[2]) &&
++ (sess->s_id.b.area == s_id[1]) &&
++ (sess->s_id.b.domain == s_id[0]))
++ return sess;
++ }
++ return NULL;
++}
++
++/* pha->hardware_lock supposed to be held on entry (to protect tgt->sess_list) */
++static inline struct q2t_sess *q2t_find_sess_by_s_id(struct q2t_tgt *tgt,
++ const uint8_t *s_id)
++{
++ struct q2t_sess *sess;
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ if ((sess->s_id.b.al_pa == s_id[2]) &&
++ (sess->s_id.b.area == s_id[1]) &&
++ (sess->s_id.b.domain == s_id[0]) &&
++ !sess->deleted)
++ return sess;
++ }
++ return NULL;
++}
++
++/* pha->hardware_lock supposed to be held on entry (to protect tgt->sess_list) */
++static inline struct q2t_sess *q2t_find_sess_by_s_id_le(struct q2t_tgt *tgt,
++ const uint8_t *s_id)
++{
++ struct q2t_sess *sess;
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ if ((sess->s_id.b.al_pa == s_id[0]) &&
++ (sess->s_id.b.area == s_id[1]) &&
++ (sess->s_id.b.domain == s_id[2]) &&
++ !sess->deleted)
++ return sess;
++ }
++ return NULL;
++}
++
++/* pha->hardware_lock supposed to be held on entry (to protect tgt->sess_list) */
++static inline struct q2t_sess *q2t_find_sess_by_port_name(struct q2t_tgt *tgt,
++ const uint8_t *port_name)
++{
++ struct q2t_sess *sess;
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ if ((sess->port_name[0] == port_name[0]) &&
++ (sess->port_name[1] == port_name[1]) &&
++ (sess->port_name[2] == port_name[2]) &&
++ (sess->port_name[3] == port_name[3]) &&
++ (sess->port_name[4] == port_name[4]) &&
++ (sess->port_name[5] == port_name[5]) &&
++ (sess->port_name[6] == port_name[6]) &&
++ (sess->port_name[7] == port_name[7]))
++ return sess;
++ }
++ return NULL;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static inline void q2t_exec_queue(scsi_qla_host_t *ha)
++{
++ qla2x00_isp_cmd(to_qla_parent(ha));
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static inline request_t *q2t_req_pkt(scsi_qla_host_t *ha)
++{
++ return qla2x00_req_pkt(to_qla_parent(ha));
++}
++
++/* Might release hw lock, then reacquire!! */
++static inline int q2t_issue_marker(scsi_qla_host_t *ha, int ha_locked)
++{
++ /* Send marker if required */
++ if (unlikely(to_qla_parent(ha)->marker_needed != 0)) {
++ int rc = qla2x00_issue_marker(ha, ha_locked);
++ if (rc != QLA_SUCCESS) {
++ PRINT_ERROR("qla2x00t(%ld): issue_marker() "
++ "failed", ha->instance);
++ }
++ return rc;
++ }
++ return QLA_SUCCESS;
++}
++
++static inline
++scsi_qla_host_t *q2t_find_host_by_d_id(scsi_qla_host_t *ha, uint8_t *d_id)
++{
++ if ((ha->d_id.b.area != d_id[1]) || (ha->d_id.b.domain != d_id[0]))
++ return NULL;
++
++ if (ha->d_id.b.al_pa == d_id[2])
++ return ha;
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ uint8_t vp_idx;
++ BUG_ON(ha->tgt_vp_map == NULL);
++ vp_idx = ha->tgt_vp_map[d_id[2]].idx;
++ if (likely(test_bit(vp_idx, ha->vp_idx_map)))
++ return ha->tgt_vp_map[vp_idx].vha;
++ }
++
++ return NULL;
++}
++
++static inline
++scsi_qla_host_t *q2t_find_host_by_vp_idx(scsi_qla_host_t *ha, uint16_t vp_idx)
++{
++ if (ha->vp_idx == vp_idx)
++ return ha;
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ BUG_ON(ha->tgt_vp_map == NULL);
++ if (likely(test_bit(vp_idx, ha->vp_idx_map)))
++ return ha->tgt_vp_map[vp_idx].vha;
++ }
++
++ return NULL;
++}
++
++static void q24_atio_pkt_all_vps(scsi_qla_host_t *ha, atio7_entry_t *atio)
++{
++ TRACE_ENTRY();
++
++ BUG_ON(ha == NULL);
++
++ switch (atio->entry_type) {
++ case ATIO_TYPE7:
++ {
++ scsi_qla_host_t *host = q2t_find_host_by_d_id(ha, atio->fcp_hdr.d_id);
++ if (unlikely(NULL == host)) {
++ /*
++ * It might happen, because there is a small gap between
++ * requesting the DPC thread to update loop and actual
++ * update. It is harmless and on the next retry should
++ * work well.
++ */
++ PRINT_WARNING("qla2x00t(%ld): Received ATIO_TYPE7 "
++ "with unknown d_id %x:%x:%x", ha->instance,
++ atio->fcp_hdr.d_id[0], atio->fcp_hdr.d_id[1],
++ atio->fcp_hdr.d_id[2]);
++ break;
++ }
++ q24_atio_pkt(host, atio);
++ break;
++ }
++
++ case IMMED_NOTIFY_TYPE:
++ {
++ scsi_qla_host_t *host = ha;
++ if (IS_FWI2_CAPABLE(ha)) {
++ notify24xx_entry_t *entry = (notify24xx_entry_t *)atio;
++ if ((entry->vp_index != 0xFF) &&
++ (entry->nport_handle != 0xFFFF)) {
++ host = q2t_find_host_by_vp_idx(ha,
++ entry->vp_index);
++ if (unlikely(!host)) {
++ PRINT_ERROR("qla2x00t(%ld): Received "
++ "ATIO (IMMED_NOTIFY_TYPE) "
++ "with unknown vp_index %d",
++ ha->instance, entry->vp_index);
++ break;
++ }
++ }
++ }
++ q24_atio_pkt(host, atio);
++ break;
++ }
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Received unknown ATIO atio "
++ "type %x", ha->instance, atio->entry_type);
++ break;
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static void q2t_response_pkt_all_vps(scsi_qla_host_t *ha, response_t *pkt)
++{
++ TRACE_ENTRY();
++
++ BUG_ON(ha == NULL);
++
++ switch (pkt->entry_type) {
++ case CTIO_TYPE7:
++ {
++ ctio7_fw_entry_t *entry = (ctio7_fw_entry_t *)pkt;
++ scsi_qla_host_t *host = q2t_find_host_by_vp_idx(ha,
++ entry->vp_index);
++ if (unlikely(!host)) {
++ PRINT_ERROR("qla2x00t(%ld): Response pkt (CTIO_TYPE7) "
++ "received, with unknown vp_index %d",
++ ha->instance, entry->vp_index);
++ break;
++ }
++ q2t_response_pkt(host, pkt);
++ break;
++ }
++
++ case IMMED_NOTIFY_TYPE:
++ {
++ scsi_qla_host_t *host = ha;
++ if (IS_FWI2_CAPABLE(ha)) {
++ notify24xx_entry_t *entry = (notify24xx_entry_t *)pkt;
++ host = q2t_find_host_by_vp_idx(ha, entry->vp_index);
++ if (unlikely(!host)) {
++ PRINT_ERROR("qla2x00t(%ld): Response pkt "
++ "(IMMED_NOTIFY_TYPE) received, "
++ "with unknown vp_index %d",
++ ha->instance, entry->vp_index);
++ break;
++ }
++ }
++ q2t_response_pkt(host, pkt);
++ break;
++ }
++
++ case NOTIFY_ACK_TYPE:
++ {
++ scsi_qla_host_t *host = ha;
++ if (IS_FWI2_CAPABLE(ha)) {
++ nack24xx_entry_t *entry = (nack24xx_entry_t *)pkt;
++ if (0xFF != entry->vp_index) {
++ host = q2t_find_host_by_vp_idx(ha,
++ entry->vp_index);
++ if (unlikely(!host)) {
++ PRINT_ERROR("qla2x00t(%ld): Response "
++ "pkt (NOTIFY_ACK_TYPE) "
++ "received, with unknown "
++ "vp_index %d", ha->instance,
++ entry->vp_index);
++ break;
++ }
++ }
++ }
++ q2t_response_pkt(host, pkt);
++ break;
++ }
++
++ case ABTS_RECV_24XX:
++ {
++ abts24_recv_entry_t *entry = (abts24_recv_entry_t *)pkt;
++ scsi_qla_host_t *host = q2t_find_host_by_vp_idx(ha,
++ entry->vp_index);
++ if (unlikely(!host)) {
++ PRINT_ERROR("qla2x00t(%ld): Response pkt "
++ "(ABTS_RECV_24XX) received, with unknown "
++ "vp_index %d", ha->instance, entry->vp_index);
++ break;
++ }
++ q2t_response_pkt(host, pkt);
++ break;
++ }
++
++ case ABTS_RESP_24XX:
++ {
++ abts24_resp_entry_t *entry = (abts24_resp_entry_t *)pkt;
++ scsi_qla_host_t *host = q2t_find_host_by_vp_idx(ha,
++ entry->vp_index);
++ if (unlikely(!host)) {
++ PRINT_ERROR("qla2x00t(%ld): Response pkt "
++ "(ABTS_RECV_24XX) received, with unknown "
++ "vp_index %d", ha->instance, entry->vp_index);
++ break;
++ }
++ q2t_response_pkt(host, pkt);
++ break;
++ }
++
++ default:
++ q2t_response_pkt(ha, pkt);
++ break;
++ }
++
++ TRACE_EXIT();
++ return;
++}
++/*
++ * Registers with initiator driver (but target mode isn't enabled till
++ * it's turned on via sysfs)
++ */
++static int q2t_target_detect(struct scst_tgt_template *tgtt)
++{
++ int res, rc;
++ struct qla_tgt_data t = {
++ .magic = QLA2X_TARGET_MAGIC,
++ .tgt24_atio_pkt = q24_atio_pkt_all_vps,
++ .tgt_response_pkt = q2t_response_pkt_all_vps,
++ .tgt2x_ctio_completion = q2x_ctio_completion,
++ .tgt_async_event = q2t_async_event,
++ .tgt_host_action = q2t_host_action,
++ .tgt_fc_port_added = q2t_fc_port_added,
++ .tgt_fc_port_deleted = q2t_fc_port_deleted,
++ };
++
++ TRACE_ENTRY();
++
++ rc = qla2xxx_tgt_register_driver(&t);
++ if (rc < 0) {
++ res = rc;
++ PRINT_ERROR("qla2x00t: Unable to register driver: %d", res);
++ goto out;
++ }
++
++ if (rc != QLA2X_INITIATOR_MAGIC) {
++ PRINT_ERROR("qla2x00t: Wrong version of the initiator part: "
++ "%d", rc);
++ res = -EINVAL;
++ goto out;
++ }
++
++ qla2xxx_add_targets();
++
++ res = 0;
++
++ PRINT_INFO("qla2x00t: %s", "Target mode driver for QLogic 2x00 controller "
++ "registered successfully");
++
++out:
++ TRACE_EXIT();
++ return res;
++}
++
++static void q2t_free_session_done(struct scst_session *scst_sess)
++{
++ struct q2t_sess *sess;
++ struct q2t_tgt *tgt;
++ scsi_qla_host_t *ha, *pha;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ BUG_ON(scst_sess == NULL);
++ sess = (struct q2t_sess *)scst_sess_get_tgt_priv(scst_sess);
++ BUG_ON(sess == NULL);
++ tgt = sess->tgt;
++
++ TRACE_MGMT_DBG("Unregistration of sess %p finished", sess);
++
++ kfree(sess);
++
++ if (tgt == NULL)
++ goto out;
++
++ TRACE_DBG("empty(sess_list) %d sess_count %d",
++ list_empty(&tgt->sess_list), tgt->sess_count);
++
++ ha = tgt->ha;
++ pha = to_qla_parent(ha);
++
++ /*
++ * We need to protect against race, when tgt is freed before or
++ * inside wake_up()
++ */
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++ tgt->sess_count--;
++ if (tgt->sess_count == 0)
++ wake_up_all(&tgt->waitQ);
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_unreg_sess(struct q2t_sess *sess)
++{
++ int res = 1;
++
++ TRACE_ENTRY();
++
++ BUG_ON(sess == NULL);
++ BUG_ON(sess->sess_ref != 0);
++
++ TRACE_MGMT_DBG("Deleting sess %p from tgt %p", sess, sess->tgt);
++ list_del(&sess->sess_list_entry);
++
++ if (sess->deleted)
++ list_del(&sess->del_list_entry);
++
++ PRINT_INFO("qla2x00t(%ld): %ssession for loop_id %d deleted",
++ sess->tgt->ha->instance, sess->local ? "local " : "",
++ sess->loop_id);
++
++ scst_unregister_session(sess->scst_sess, 0, q2t_free_session_done);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_reset(scsi_qla_host_t *ha, void *iocb, int mcmd)
++{
++ struct q2t_sess *sess;
++ int loop_id;
++ uint16_t lun = 0;
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ notify24xx_entry_t *n = (notify24xx_entry_t *)iocb;
++ loop_id = le16_to_cpu(n->nport_handle);
++ } else
++ loop_id = GET_TARGET_ID(ha, (notify_entry_t *)iocb);
++
++ if (loop_id == 0xFFFF) {
++ /* Global event */
++ atomic_inc(&ha->tgt->tgt_global_resets_count);
++ q2t_clear_tgt_db(ha->tgt, 1);
++ if (!list_empty(&ha->tgt->sess_list)) {
++ sess = list_entry(ha->tgt->sess_list.next,
++ typeof(*sess), sess_list_entry);
++ switch (mcmd) {
++ case Q2T_NEXUS_LOSS_SESS:
++ mcmd = Q2T_NEXUS_LOSS;
++ break;
++
++ case Q2T_ABORT_ALL_SESS:
++ mcmd = Q2T_ABORT_ALL;
++ break;
++
++ case Q2T_NEXUS_LOSS:
++ case Q2T_ABORT_ALL:
++ break;
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Not allowed "
++ "command %x in %s", ha->instance,
++ mcmd, __func__);
++ sess = NULL;
++ break;
++ }
++ } else
++ sess = NULL;
++ } else
++ sess = q2t_find_sess_by_loop_id(ha->tgt, loop_id);
++
++ if (sess == NULL) {
++ res = -ESRCH;
++ ha->tgt->tm_to_unknown = 1;
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("scsi(%ld): resetting (session %p from port "
++ "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, "
++ "mcmd %x, loop_id %d)", ha->host_no, sess,
++ sess->port_name[0], sess->port_name[1],
++ sess->port_name[2], sess->port_name[3],
++ sess->port_name[4], sess->port_name[5],
++ sess->port_name[6], sess->port_name[7],
++ mcmd, loop_id);
++
++ res = q2t_issue_task_mgmt(sess, (uint8_t *)&lun, sizeof(lun),
++ mcmd, iocb, Q24_MGMT_SEND_NACK);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static void q2t_schedule_sess_for_deletion(struct q2t_sess *sess)
++{
++ struct q2t_tgt *tgt = sess->tgt;
++ uint32_t dev_loss_tmo = tgt->ha->port_down_retry_count + 5;
++ bool schedule;
++
++ TRACE_ENTRY();
++
++ if (sess->deleted)
++ goto out;
++
++ /*
++ * If the list is empty, then, most likely, the work isn't
++ * scheduled.
++ */
++ schedule = list_empty(&tgt->del_sess_list);
++
++ TRACE_MGMT_DBG("Scheduling sess %p for deletion (schedule %d)", sess,
++ schedule);
++ list_add_tail(&sess->del_list_entry, &tgt->del_sess_list);
++ sess->deleted = 1;
++ sess->expires = jiffies + dev_loss_tmo * HZ;
++
++ PRINT_INFO("qla2x00t(%ld): session for port %02x:%02x:%02x:"
++ "%02x:%02x:%02x:%02x:%02x (loop ID %d) scheduled for "
++ "deletion in %d secs", tgt->ha->instance,
++ sess->port_name[0], sess->port_name[1],
++ sess->port_name[2], sess->port_name[3],
++ sess->port_name[4], sess->port_name[5],
++ sess->port_name[6], sess->port_name[7],
++ sess->loop_id, dev_loss_tmo);
++
++ if (schedule)
++ schedule_delayed_work(&tgt->sess_del_work,
++ jiffies - sess->expires);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static void q2t_clear_tgt_db(struct q2t_tgt *tgt, bool local_only)
++{
++ struct q2t_sess *sess, *sess_tmp;
++
++ TRACE_ENTRY();
++
++ TRACE(TRACE_MGMT, "qla2x00t: Clearing targets DB for target %p", tgt);
++
++ list_for_each_entry_safe(sess, sess_tmp, &tgt->sess_list,
++ sess_list_entry) {
++ if (local_only) {
++ if (!sess->local)
++ continue;
++ q2t_schedule_sess_for_deletion(sess);
++ } else
++ q2t_sess_put(sess);
++ }
++
++ /* At this point tgt could be already dead */
++
++ TRACE_MGMT_DBG("Finished clearing tgt %p DB", tgt);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Called in a thread context */
++static void q2t_alloc_session_done(struct scst_session *scst_sess,
++ void *data, int result)
++{
++ TRACE_ENTRY();
++
++ if (result != 0) {
++ struct q2t_sess *sess = (struct q2t_sess *)data;
++ struct q2t_tgt *tgt = sess->tgt;
++ scsi_qla_host_t *ha = tgt->ha;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++ unsigned long flags;
++
++ PRINT_INFO("qla2x00t(%ld): Session initialization failed",
++ ha->instance);
++
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++ q2t_sess_put(sess);
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static int q24_get_loop_id(scsi_qla_host_t *ha, const uint8_t *s_id,
++ uint16_t *loop_id)
++{
++ dma_addr_t gid_list_dma;
++ struct gid_list_info *gid_list;
++ char *id_iter;
++ int res, rc, i, retries = 0;
++ uint16_t entries;
++
++ TRACE_ENTRY();
++
++ gid_list = dma_alloc_coherent(&ha->pdev->dev, GID_LIST_SIZE,
++ &gid_list_dma, GFP_KERNEL);
++ if (gid_list == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): DMA Alloc failed of %zd",
++ ha->instance, GID_LIST_SIZE);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ /* Get list of logged in devices */
++retry:
++ rc = qla2x00_get_id_list(ha, gid_list, gid_list_dma, &entries);
++ if (rc != QLA_SUCCESS) {
++ if (rc == QLA_FW_NOT_READY) {
++ retries++;
++ if (retries < 3) {
++ msleep(1000);
++ goto retry;
++ }
++ }
++ TRACE_MGMT_DBG("qla2x00t(%ld): get_id_list() failed: %x",
++ ha->instance, rc);
++ res = -rc;
++ goto out_free_id_list;
++ }
++
++ id_iter = (char *)gid_list;
++ res = -1;
++ for (i = 0; i < entries; i++) {
++ struct gid_list_info *gid = (struct gid_list_info *)id_iter;
++ if ((gid->al_pa == s_id[2]) &&
++ (gid->area == s_id[1]) &&
++ (gid->domain == s_id[0])) {
++ *loop_id = le16_to_cpu(gid->loop_id);
++ res = 0;
++ break;
++ }
++ id_iter += ha->gid_list_info_size;
++ }
++
++out_free_id_list:
++ dma_free_coherent(&ha->pdev->dev, GID_LIST_SIZE, gid_list, gid_list_dma);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static bool q2t_check_fcport_exist(scsi_qla_host_t *ha, struct q2t_sess *sess)
++{
++ bool res, found = false;
++ int rc, i;
++ uint16_t loop_id = 0xFFFF; /* to eliminate compiler's warning */
++ uint16_t entries;
++ void *pmap;
++ int pmap_len;
++ fc_port_t *fcport;
++ int global_resets;
++
++ TRACE_ENTRY();
++
++retry:
++ global_resets = atomic_read(&ha->tgt->tgt_global_resets_count);
++
++ rc = qla2x00_get_node_name_list(ha, &pmap, &pmap_len);
++ if (rc != QLA_SUCCESS) {
++ res = false;
++ goto out;
++ }
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ struct qla_port24_data *pmap24 = pmap;
++
++ entries = pmap_len/sizeof(*pmap24);
++
++ for (i = 0; i < entries; ++i) {
++ if ((sess->port_name[0] == pmap24[i].port_name[0]) &&
++ (sess->port_name[1] == pmap24[i].port_name[1]) &&
++ (sess->port_name[2] == pmap24[i].port_name[2]) &&
++ (sess->port_name[3] == pmap24[i].port_name[3]) &&
++ (sess->port_name[4] == pmap24[i].port_name[4]) &&
++ (sess->port_name[5] == pmap24[i].port_name[5]) &&
++ (sess->port_name[6] == pmap24[i].port_name[6]) &&
++ (sess->port_name[7] == pmap24[i].port_name[7])) {
++ loop_id = le16_to_cpu(pmap24[i].loop_id);
++ found = true;
++ break;
++ }
++ }
++ } else {
++ struct qla_port23_data *pmap2x = pmap;
++
++ entries = pmap_len/sizeof(*pmap2x);
++
++ for (i = 0; i < entries; ++i) {
++ if ((sess->port_name[0] == pmap2x[i].port_name[0]) &&
++ (sess->port_name[1] == pmap2x[i].port_name[1]) &&
++ (sess->port_name[2] == pmap2x[i].port_name[2]) &&
++ (sess->port_name[3] == pmap2x[i].port_name[3]) &&
++ (sess->port_name[4] == pmap2x[i].port_name[4]) &&
++ (sess->port_name[5] == pmap2x[i].port_name[5]) &&
++ (sess->port_name[6] == pmap2x[i].port_name[6]) &&
++ (sess->port_name[7] == pmap2x[i].port_name[7])) {
++ loop_id = le16_to_cpu(pmap2x[i].loop_id);
++ found = true;
++ break;
++ }
++ }
++ }
++
++ kfree(pmap);
++
++ if (!found) {
++ res = false;
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("loop_id %d", loop_id);
++
++ fcport = kzalloc(sizeof(*fcport), GFP_KERNEL);
++ if (fcport == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): Allocation of tmp FC port failed",
++ ha->instance);
++ res = false;
++ goto out;
++ }
++
++ fcport->loop_id = loop_id;
++
++ rc = qla2x00_get_port_database(ha, fcport, 0);
++ if (rc != QLA_SUCCESS) {
++ PRINT_ERROR("qla2x00t(%ld): Failed to retrieve fcport "
++ "information -- get_port_database() returned %x "
++ "(loop_id=0x%04x)", ha->instance, rc, loop_id);
++ res = false;
++ goto out_free_fcport;
++ }
++
++ if (global_resets != atomic_read(&ha->tgt->tgt_global_resets_count)) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): global reset during session "
++ "discovery (counter was %d, new %d), retrying",
++ ha->instance, global_resets,
++ atomic_read(&ha->tgt->tgt_global_resets_count));
++ goto retry;
++ }
++
++ TRACE_MGMT_DBG("Updating sess %p s_id %x:%x:%x, "
++ "loop_id %d) to d_id %x:%x:%x, loop_id %d", sess,
++ sess->s_id.b.domain, sess->s_id.b.area,
++ sess->s_id.b.al_pa, sess->loop_id, fcport->d_id.b.domain,
++ fcport->d_id.b.area, fcport->d_id.b.al_pa, fcport->loop_id);
++
++ sess->s_id = fcport->d_id;
++ sess->loop_id = fcport->loop_id;
++ sess->conf_compl_supported = fcport->conf_compl_supported;
++
++ res = true;
++
++out_free_fcport:
++ kfree(fcport);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static void q2t_undelete_sess(struct q2t_sess *sess)
++{
++ BUG_ON(!sess->deleted);
++
++ list_del(&sess->del_list_entry);
++ sess->deleted = 0;
++}
++
++static void q2t_del_sess_work_fn(struct delayed_work *work)
++{
++ struct q2t_tgt *tgt = container_of(work, struct q2t_tgt,
++ sess_del_work);
++ scsi_qla_host_t *ha = tgt->ha;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++ struct q2t_sess *sess;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++ while (!list_empty(&tgt->del_sess_list)) {
++ sess = list_entry(tgt->del_sess_list.next, typeof(*sess),
++ del_list_entry);
++ if (time_after_eq(jiffies, sess->expires)) {
++ bool cancel;
++
++ q2t_undelete_sess(sess);
++
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++ cancel = q2t_check_fcport_exist(ha, sess);
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++
++ if (cancel) {
++ if (sess->deleted) {
++ /*
++ * sess was again deleted while we were
++ * discovering it
++ */
++ continue;
++ }
++
++ PRINT_INFO("qla2x00t(%ld): cancel deletion of "
++ "session for port %02x:%02x:%02x:"
++ "%02x:%02x:%02x:%02x:%02x (loop ID %d), "
++ "because it isn't deleted by firmware",
++ ha->instance,
++ sess->port_name[0], sess->port_name[1],
++ sess->port_name[2], sess->port_name[3],
++ sess->port_name[4], sess->port_name[5],
++ sess->port_name[6], sess->port_name[7],
++ sess->loop_id);
++ } else {
++ TRACE_MGMT_DBG("Timeout: sess %p about to be "
++ "deleted", sess);
++ q2t_sess_put(sess);
++ }
++ } else {
++ schedule_delayed_work(&tgt->sess_del_work,
++ jiffies - sess->expires);
++ break;
++ }
++ }
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * Must be called under tgt_mutex.
++ *
++ * Adds an extra ref to allow to drop hw lock after adding sess to the list.
++ * Caller must put it.
++ */
++static struct q2t_sess *q2t_create_sess(scsi_qla_host_t *ha, fc_port_t *fcport,
++ bool local)
++{
++ char *wwn_str;
++ const int wwn_str_len = 3*WWN_SIZE+2;
++ struct q2t_tgt *tgt = ha->tgt;
++ struct q2t_sess *sess;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ /* Check to avoid double sessions */
++ spin_lock_irq(&pha->hardware_lock);
++ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) {
++ if ((sess->port_name[0] == fcport->port_name[0]) &&
++ (sess->port_name[1] == fcport->port_name[1]) &&
++ (sess->port_name[2] == fcport->port_name[2]) &&
++ (sess->port_name[3] == fcport->port_name[3]) &&
++ (sess->port_name[4] == fcport->port_name[4]) &&
++ (sess->port_name[5] == fcport->port_name[5]) &&
++ (sess->port_name[6] == fcport->port_name[6]) &&
++ (sess->port_name[7] == fcport->port_name[7])) {
++ TRACE_MGMT_DBG("Double sess %p found (s_id %x:%x:%x, "
++ "loop_id %d), updating to d_id %x:%x:%x, "
++ "loop_id %d", sess, sess->s_id.b.domain,
++ sess->s_id.b.area, sess->s_id.b.al_pa,
++ sess->loop_id, fcport->d_id.b.domain,
++ fcport->d_id.b.area, fcport->d_id.b.al_pa,
++ fcport->loop_id);
++
++ if (sess->deleted)
++ q2t_undelete_sess(sess);
++
++ q2t_sess_get(sess);
++ sess->s_id = fcport->d_id;
++ sess->loop_id = fcport->loop_id;
++ sess->conf_compl_supported = fcport->conf_compl_supported;
++ if (sess->local && !local)
++ sess->local = 0;
++ spin_unlock_irq(&pha->hardware_lock);
++ goto out;
++ }
++ }
++ spin_unlock_irq(&pha->hardware_lock);
++
++ /* We are under tgt_mutex, so a new sess can't be added behind us */
++
++ sess = kzalloc(sizeof(*sess), GFP_KERNEL);
++ if (sess == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): session allocation failed, "
++ "all commands from port %02x:%02x:%02x:%02x:"
++ "%02x:%02x:%02x:%02x will be refused", ha->instance,
++ fcport->port_name[0], fcport->port_name[1],
++ fcport->port_name[2], fcport->port_name[3],
++ fcport->port_name[4], fcport->port_name[5],
++ fcport->port_name[6], fcport->port_name[7]);
++ goto out;
++ }
++
++ sess->sess_ref = 2; /* plus 1 extra ref, see above */
++ sess->tgt = ha->tgt;
++ sess->s_id = fcport->d_id;
++ sess->loop_id = fcport->loop_id;
++ sess->conf_compl_supported = fcport->conf_compl_supported;
++ sess->local = local;
++ BUILD_BUG_ON(sizeof(sess->port_name) != sizeof(fcport->port_name));
++ memcpy(sess->port_name, fcport->port_name, sizeof(sess->port_name));
++
++ wwn_str = kmalloc(wwn_str_len, GFP_KERNEL);
++ if (wwn_str == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): Allocation of wwn_str failed. "
++ "All commands from port %02x:%02x:%02x:%02x:%02x:%02x:"
++ "%02x:%02x will be refused", ha->instance,
++ fcport->port_name[0], fcport->port_name[1],
++ fcport->port_name[2], fcport->port_name[3],
++ fcport->port_name[4], fcport->port_name[5],
++ fcport->port_name[6], fcport->port_name[7]);
++ goto out_free_sess;
++ }
++
++ sprintf(wwn_str, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
++ fcport->port_name[0], fcport->port_name[1],
++ fcport->port_name[2], fcport->port_name[3],
++ fcport->port_name[4], fcport->port_name[5],
++ fcport->port_name[6], fcport->port_name[7]);
++
++ /* Let's do the session creation async'ly */
++ sess->scst_sess = scst_register_session(tgt->scst_tgt, 1, wwn_str,
++ sess, sess, q2t_alloc_session_done);
++ if (sess->scst_sess == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): scst_register_session() "
++ "failed for host %ld (wwn %s, loop_id %d), all "
++ "commands from it will be refused", ha->instance,
++ ha->host_no, wwn_str, fcport->loop_id);
++ goto out_free_sess_wwn;
++ }
++
++ TRACE_MGMT_DBG("Adding sess %p to tgt %p", sess, tgt);
++
++ spin_lock_irq(&pha->hardware_lock);
++ list_add_tail(&sess->sess_list_entry, &tgt->sess_list);
++ tgt->sess_count++;
++ spin_unlock_irq(&pha->hardware_lock);
++
++ PRINT_INFO("qla2x00t(%ld): %ssession for wwn %s (loop_id %d, "
++ "s_id %x:%x:%x, confirmed completion %ssupported) added",
++ ha->instance, local ? "local " : "", wwn_str, fcport->loop_id,
++ sess->s_id.b.domain, sess->s_id.b.area, sess->s_id.b.al_pa,
++ sess->conf_compl_supported ? "" : "not ");
++
++ kfree(wwn_str);
++
++out:
++ TRACE_EXIT_HRES(sess);
++ return sess;
++
++out_free_sess_wwn:
++ kfree(wwn_str);
++ /* go through */
++
++out_free_sess:
++ kfree(sess);
++ sess = NULL;
++ goto out;
++}
++
++static void q2t_fc_port_added(scsi_qla_host_t *ha, fc_port_t *fcport)
++{
++ struct q2t_tgt *tgt;
++ struct q2t_sess *sess;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ mutex_lock(&ha->tgt_mutex);
++
++ tgt = ha->tgt;
++
++ if ((tgt == NULL) || (fcport->port_type != FCT_INITIATOR))
++ goto out_unlock;
++
++ if (tgt->tgt_stop)
++ goto out_unlock;
++
++ spin_lock_irq(&pha->hardware_lock);
++
++ sess = q2t_find_sess_by_port_name(tgt, fcport->port_name);
++ if (sess == NULL) {
++ spin_unlock_irq(&pha->hardware_lock);
++ sess = q2t_create_sess(ha, fcport, false);
++ spin_lock_irq(&pha->hardware_lock);
++ if (sess != NULL)
++ q2t_sess_put(sess); /* put the extra creation ref */
++ } else {
++ if (sess->deleted) {
++ q2t_undelete_sess(sess);
++
++ PRINT_INFO("qla2x00t(%ld): %ssession for port %02x:"
++ "%02x:%02x:%02x:%02x:%02x:%02x:%02x (loop ID %d) "
++ "reappeared", sess->tgt->ha->instance,
++ sess->local ? "local " : "", sess->port_name[0],
++ sess->port_name[1], sess->port_name[2],
++ sess->port_name[3], sess->port_name[4],
++ sess->port_name[5], sess->port_name[6],
++ sess->port_name[7], sess->loop_id);
++
++ TRACE_MGMT_DBG("Reappeared sess %p", sess);
++ }
++ sess->s_id = fcport->d_id;
++ sess->loop_id = fcport->loop_id;
++ sess->conf_compl_supported = fcport->conf_compl_supported;
++ }
++
++ if (sess->local) {
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): local session for "
++ "port %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x "
++ "(loop ID %d) became global", ha->instance,
++ fcport->port_name[0], fcport->port_name[1],
++ fcport->port_name[2], fcport->port_name[3],
++ fcport->port_name[4], fcport->port_name[5],
++ fcport->port_name[6], fcport->port_name[7],
++ sess->loop_id);
++ sess->local = 0;
++ }
++
++ spin_unlock_irq(&pha->hardware_lock);
++
++out_unlock:
++ mutex_unlock(&ha->tgt_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void q2t_fc_port_deleted(scsi_qla_host_t *ha, fc_port_t *fcport)
++{
++ struct q2t_tgt *tgt;
++ struct q2t_sess *sess;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ mutex_lock(&ha->tgt_mutex);
++
++ tgt = ha->tgt;
++
++ if ((tgt == NULL) || (fcport->port_type != FCT_INITIATOR))
++ goto out_unlock;
++
++ if (tgt->tgt_stop)
++ goto out_unlock;
++
++ spin_lock_irq(&pha->hardware_lock);
++
++ sess = q2t_find_sess_by_port_name(tgt, fcport->port_name);
++ if (sess == NULL)
++ goto out_unlock_ha;
++
++ TRACE_MGMT_DBG("sess %p", sess);
++
++ sess->local = 1;
++ q2t_schedule_sess_for_deletion(sess);
++
++out_unlock_ha:
++ spin_unlock_irq(&pha->hardware_lock);
++
++out_unlock:
++ mutex_unlock(&ha->tgt_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++static inline int test_tgt_sess_count(struct q2t_tgt *tgt)
++{
++ unsigned long flags;
++ int res;
++ scsi_qla_host_t *pha = to_qla_parent(tgt->ha);
++
++ /*
++ * We need to protect against race, when tgt is freed before or
++ * inside wake_up()
++ */
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++ TRACE_DBG("tgt %p, empty(sess_list)=%d sess_count=%d",
++ tgt, list_empty(&tgt->sess_list), tgt->sess_count);
++ res = (tgt->sess_count == 0);
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++ return res;
++}
++
++/* Must be called under tgt_host_action_mutex or q2t_unreg_rwsem write locked */
++static void q2t_target_stop(struct scst_tgt *scst_tgt)
++{
++ struct q2t_tgt *tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ scsi_qla_host_t *ha = tgt->ha;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Stopping target for host %ld(%p)", ha->host_no, ha);
++
++ /*
++ * Mutex needed to sync with q2t_fc_port_[added,deleted].
++ * Lock is needed, because we still can get an incoming packet.
++ */
++
++ mutex_lock(&ha->tgt_mutex);
++ spin_lock_irq(&pha->hardware_lock);
++ tgt->tgt_stop = 1;
++ q2t_clear_tgt_db(tgt, false);
++ spin_unlock_irq(&pha->hardware_lock);
++ mutex_unlock(&ha->tgt_mutex);
++
++ cancel_delayed_work_sync(&tgt->sess_del_work);
++
++ TRACE_MGMT_DBG("Waiting for sess works (tgt %p)", tgt);
++ spin_lock_irq(&tgt->sess_work_lock);
++ while (!list_empty(&tgt->sess_works_list)) {
++ spin_unlock_irq(&tgt->sess_work_lock);
++ flush_scheduled_work();
++ spin_lock_irq(&tgt->sess_work_lock);
++ }
++ spin_unlock_irq(&tgt->sess_work_lock);
++
++ TRACE_MGMT_DBG("Waiting for tgt %p: list_empty(sess_list)=%d "
++ "sess_count=%d", tgt, list_empty(&tgt->sess_list),
++ tgt->sess_count);
++
++ wait_event(tgt->waitQ, test_tgt_sess_count(tgt));
++
++ /* Big hammer */
++ if (!pha->host_shutting_down && qla_tgt_mode_enabled(ha))
++ qla2x00_disable_tgt_mode(ha);
++
++ /* Wait for sessions to clear out (just in case) */
++ wait_event(tgt->waitQ, test_tgt_sess_count(tgt));
++
++ TRACE_MGMT_DBG("Waiting for %d IRQ commands to complete (tgt %p)",
++ tgt->irq_cmd_count, tgt);
++
++ mutex_lock(&ha->tgt_mutex);
++ spin_lock_irq(&pha->hardware_lock);
++ while (tgt->irq_cmd_count != 0) {
++ spin_unlock_irq(&pha->hardware_lock);
++ udelay(2);
++ spin_lock_irq(&pha->hardware_lock);
++ }
++ ha->tgt = NULL;
++ spin_unlock_irq(&pha->hardware_lock);
++ mutex_unlock(&ha->tgt_mutex);
++
++ TRACE_MGMT_DBG("Stop of tgt %p finished", tgt);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under tgt_host_action_mutex or q2t_unreg_rwsem write locked */
++static int q2t_target_release(struct scst_tgt *scst_tgt)
++{
++ struct q2t_tgt *tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ scsi_qla_host_t *ha = tgt->ha;
++
++ TRACE_ENTRY();
++
++ q2t_target_stop(scst_tgt);
++
++ ha->q2t_tgt = NULL;
++ scst_tgt_set_tgt_priv(scst_tgt, NULL);
++
++ TRACE_MGMT_DBG("Release of tgt %p finished", tgt);
++
++ kfree(tgt);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_sched_sess_work(struct q2t_tgt *tgt, int type,
++ const void *param, unsigned int param_size)
++{
++ int res;
++ struct q2t_sess_work_param *prm;
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ prm = kzalloc(sizeof(*prm), GFP_ATOMIC);
++ if (prm == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): Unable to create session "
++ "work, command will be refused", tgt->ha->instance);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("Scheduling work (type %d, prm %p) to find session for "
++ "param %p (size %d, tgt %p)", type, prm, param, param_size, tgt);
++
++ BUG_ON(param_size > (sizeof(*prm) -
++ offsetof(struct q2t_sess_work_param, cmd)));
++
++ prm->type = type;
++ memcpy(&prm->cmd, param, param_size);
++
++ spin_lock_irqsave(&tgt->sess_work_lock, flags);
++ if (!tgt->sess_works_pending)
++ tgt->tm_to_unknown = 0;
++ list_add_tail(&prm->sess_works_list_entry, &tgt->sess_works_list);
++ tgt->sess_works_pending = 1;
++ spin_unlock_irqrestore(&tgt->sess_work_lock, flags);
++
++ schedule_work(&tgt->sess_work);
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q2x_modify_command_count(scsi_qla_host_t *ha, int cmd_count,
++ int imm_count)
++{
++ modify_lun_entry_t *pkt;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending MODIFY_LUN (ha=%p, cmd=%d, imm=%d)",
++ ha, cmd_count, imm_count);
++
++ /* Sending marker isn't necessary, since we called from ISR */
++
++ pkt = (modify_lun_entry_t *)q2t_req_pkt(ha);
++ if (pkt == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out;
++ }
++
++ ha->tgt->modify_lun_expected++;
++
++ pkt->entry_type = MODIFY_LUN_TYPE;
++ pkt->entry_count = 1;
++ if (cmd_count < 0) {
++ pkt->operators = MODIFY_LUN_CMD_SUB; /* Subtract from command count */
++ pkt->command_count = -cmd_count;
++ } else if (cmd_count > 0) {
++ pkt->operators = MODIFY_LUN_CMD_ADD; /* Add to command count */
++ pkt->command_count = cmd_count;
++ }
++
++ if (imm_count < 0) {
++ pkt->operators |= MODIFY_LUN_IMM_SUB;
++ pkt->immed_notify_count = -imm_count;
++ } else if (imm_count > 0) {
++ pkt->operators |= MODIFY_LUN_IMM_ADD;
++ pkt->immed_notify_count = imm_count;
++ }
++
++ pkt->timeout = 0; /* Use default */
++
++ TRACE_BUFFER("MODIFY LUN packet data", pkt, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q2x_send_notify_ack(scsi_qla_host_t *ha, notify_entry_t *iocb,
++ uint32_t add_flags, uint16_t resp_code, int resp_code_valid,
++ uint16_t srr_flags, uint16_t srr_reject_code, uint8_t srr_explan)
++{
++ nack_entry_t *ntfy;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending NOTIFY_ACK (ha=%p)", ha);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, 1) != QLA_SUCCESS)
++ goto out;
++
++ ntfy = (nack_entry_t *)q2t_req_pkt(ha);
++ if (ntfy == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out;
++ }
++
++ if (ha->tgt != NULL)
++ ha->tgt->notify_ack_expected++;
++
++ ntfy->entry_type = NOTIFY_ACK_TYPE;
++ ntfy->entry_count = 1;
++ SET_TARGET_ID(ha, ntfy->target, GET_TARGET_ID(ha, iocb));
++ ntfy->status = iocb->status;
++ ntfy->task_flags = iocb->task_flags;
++ ntfy->seq_id = iocb->seq_id;
++ /* Do not increment here, the chip isn't decrementing */
++ /* ntfy->flags = __constant_cpu_to_le16(NOTIFY_ACK_RES_COUNT); */
++ ntfy->flags |= cpu_to_le16(add_flags);
++ ntfy->srr_rx_id = iocb->srr_rx_id;
++ ntfy->srr_rel_offs = iocb->srr_rel_offs;
++ ntfy->srr_ui = iocb->srr_ui;
++ ntfy->srr_flags = cpu_to_le16(srr_flags);
++ ntfy->srr_reject_code = cpu_to_le16(srr_reject_code);
++ ntfy->srr_reject_code_expl = srr_explan;
++ ntfy->ox_id = iocb->ox_id;
++
++ if (resp_code_valid) {
++ ntfy->resp_code = cpu_to_le16(resp_code);
++ ntfy->flags |= __constant_cpu_to_le16(
++ NOTIFY_ACK_TM_RESP_CODE_VALID);
++ }
++
++ TRACE(TRACE_SCSI, "qla2x00t(%ld): Sending Notify Ack Seq %#x -> I %#x "
++ "St %#x RC %#x", ha->instance,
++ le16_to_cpu(iocb->seq_id), GET_TARGET_ID(ha, iocb),
++ le16_to_cpu(iocb->status), le16_to_cpu(ntfy->resp_code));
++ TRACE_BUFFER("Notify Ack packet data", ntfy, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q24_send_abts_resp(scsi_qla_host_t *ha,
++ const abts24_recv_entry_t *abts, uint32_t status, bool ids_reversed)
++{
++ abts24_resp_entry_t *resp;
++ uint32_t f_ctl;
++ uint8_t *p;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending task mgmt ABTS response (ha=%p, atio=%p, "
++ "status=%x", ha, abts, status);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, 1) != QLA_SUCCESS)
++ goto out;
++
++ resp = (abts24_resp_entry_t *)q2t_req_pkt(ha);
++ if (resp == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out;
++ }
++
++ resp->entry_type = ABTS_RESP_24XX;
++ resp->entry_count = 1;
++ resp->nport_handle = abts->nport_handle;
++ resp->vp_index = ha->vp_idx;
++ resp->sof_type = abts->sof_type;
++ resp->exchange_address = abts->exchange_address;
++ resp->fcp_hdr_le = abts->fcp_hdr_le;
++ f_ctl = __constant_cpu_to_le32(F_CTL_EXCH_CONTEXT_RESP |
++ F_CTL_LAST_SEQ | F_CTL_END_SEQ |
++ F_CTL_SEQ_INITIATIVE);
++ p = (uint8_t *)&f_ctl;
++ resp->fcp_hdr_le.f_ctl[0] = *p++;
++ resp->fcp_hdr_le.f_ctl[1] = *p++;
++ resp->fcp_hdr_le.f_ctl[2] = *p;
++ if (ids_reversed) {
++ resp->fcp_hdr_le.d_id[0] = abts->fcp_hdr_le.d_id[0];
++ resp->fcp_hdr_le.d_id[1] = abts->fcp_hdr_le.d_id[1];
++ resp->fcp_hdr_le.d_id[2] = abts->fcp_hdr_le.d_id[2];
++ resp->fcp_hdr_le.s_id[0] = abts->fcp_hdr_le.s_id[0];
++ resp->fcp_hdr_le.s_id[1] = abts->fcp_hdr_le.s_id[1];
++ resp->fcp_hdr_le.s_id[2] = abts->fcp_hdr_le.s_id[2];
++ } else {
++ resp->fcp_hdr_le.d_id[0] = abts->fcp_hdr_le.s_id[0];
++ resp->fcp_hdr_le.d_id[1] = abts->fcp_hdr_le.s_id[1];
++ resp->fcp_hdr_le.d_id[2] = abts->fcp_hdr_le.s_id[2];
++ resp->fcp_hdr_le.s_id[0] = abts->fcp_hdr_le.d_id[0];
++ resp->fcp_hdr_le.s_id[1] = abts->fcp_hdr_le.d_id[1];
++ resp->fcp_hdr_le.s_id[2] = abts->fcp_hdr_le.d_id[2];
++ }
++ resp->exchange_addr_to_abort = abts->exchange_addr_to_abort;
++ if (status == SCST_MGMT_STATUS_SUCCESS) {
++ resp->fcp_hdr_le.r_ctl = R_CTL_BASIC_LINK_SERV | R_CTL_B_ACC;
++ resp->payload.ba_acct.seq_id_valid = SEQ_ID_INVALID;
++ resp->payload.ba_acct.low_seq_cnt = 0x0000;
++ resp->payload.ba_acct.high_seq_cnt = 0xFFFF;
++ resp->payload.ba_acct.ox_id = abts->fcp_hdr_le.ox_id;
++ resp->payload.ba_acct.rx_id = abts->fcp_hdr_le.rx_id;
++ } else {
++ resp->fcp_hdr_le.r_ctl = R_CTL_BASIC_LINK_SERV | R_CTL_B_RJT;
++ resp->payload.ba_rjt.reason_code =
++ BA_RJT_REASON_CODE_UNABLE_TO_PERFORM;
++ /* Other bytes are zero */
++ }
++
++ TRACE_BUFFER("ABTS RESP packet data", resp, REQUEST_ENTRY_SIZE);
++
++ ha->tgt->abts_resp_expected++;
++
++ q2t_exec_queue(ha);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q24_retry_term_exchange(scsi_qla_host_t *ha,
++ abts24_resp_fw_entry_t *entry)
++{
++ ctio7_status1_entry_t *ctio;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending retry TERM EXCH CTIO7 (ha=%p)", ha);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, 1) != QLA_SUCCESS)
++ goto out;
++
++ ctio = (ctio7_status1_entry_t *)q2t_req_pkt(ha);
++ if (ctio == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out;
++ }
++
++ /*
++ * We've got on entrance firmware's response on by us generated
++ * ABTS response. So, in it ID fields are reversed.
++ */
++
++ ctio->common.entry_type = CTIO_TYPE7;
++ ctio->common.entry_count = 1;
++ ctio->common.nport_handle = entry->nport_handle;
++ ctio->common.handle = Q2T_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK;
++ ctio->common.timeout = __constant_cpu_to_le16(Q2T_TIMEOUT);
++ ctio->common.vp_index = ha->vp_idx;
++ ctio->common.initiator_id[0] = entry->fcp_hdr_le.d_id[0];
++ ctio->common.initiator_id[1] = entry->fcp_hdr_le.d_id[1];
++ ctio->common.initiator_id[2] = entry->fcp_hdr_le.d_id[2];
++ ctio->common.exchange_addr = entry->exchange_addr_to_abort;
++ ctio->flags = __constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_1 | CTIO7_FLAGS_TERMINATE);
++ ctio->ox_id = entry->fcp_hdr_le.ox_id;
++
++ TRACE_BUFFER("CTIO7 retry TERM EXCH packet data", ctio, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++ q24_send_abts_resp(ha, (abts24_recv_entry_t *)entry,
++ SCST_MGMT_STATUS_SUCCESS, true);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int __q24_handle_abts(scsi_qla_host_t *ha, abts24_recv_entry_t *abts,
++ struct q2t_sess *sess)
++{
++ int res;
++ uint32_t tag = abts->exchange_addr_to_abort;
++ struct q2t_mgmt_cmd *mcmd;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("qla2x00t(%ld): task abort (tag=%d)", ha->instance,
++ tag);
++
++ mcmd = mempool_alloc(q2t_mgmt_cmd_mempool, GFP_ATOMIC);
++ if (mcmd == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s: Allocation of ABORT cmd failed",
++ ha->instance, __func__);
++ res = -ENOMEM;
++ goto out;
++ }
++ memset(mcmd, 0, sizeof(*mcmd));
++
++ mcmd->sess = sess;
++ memcpy(&mcmd->orig_iocb.abts, abts, sizeof(mcmd->orig_iocb.abts));
++
++ res = scst_rx_mgmt_fn_tag(sess->scst_sess, SCST_ABORT_TASK, tag,
++ SCST_ATOMIC, mcmd);
++ if (res != 0) {
++ PRINT_ERROR("qla2x00t(%ld): scst_rx_mgmt_fn_tag() failed: %d",
++ ha->instance, res);
++ goto out_free;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ mempool_free(mcmd, q2t_mgmt_cmd_mempool);
++ goto out;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q24_handle_abts(scsi_qla_host_t *ha, abts24_recv_entry_t *abts)
++{
++ int rc;
++ uint32_t tag = abts->exchange_addr_to_abort;
++ struct q2t_sess *sess;
++
++ TRACE_ENTRY();
++
++ if (le32_to_cpu(abts->fcp_hdr_le.parameter) & ABTS_PARAM_ABORT_SEQ) {
++ PRINT_ERROR("qla2x00t(%ld): ABTS: Abort Sequence not "
++ "supported", ha->instance);
++ goto out_err;
++ }
++
++ if (tag == ATIO_EXCHANGE_ADDRESS_UNKNOWN) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): ABTS: Unknown Exchange "
++ "Address received", ha->instance);
++ goto out_err;
++ }
++
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): task abort (s_id=%x:%x:%x, "
++ "tag=%d, param=%x)", ha->instance, abts->fcp_hdr_le.s_id[2],
++ abts->fcp_hdr_le.s_id[1], abts->fcp_hdr_le.s_id[0], tag,
++ le32_to_cpu(abts->fcp_hdr_le.parameter));
++
++ sess = q2t_find_sess_by_s_id_le(ha->tgt, abts->fcp_hdr_le.s_id);
++ if (sess == NULL) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): task abort for unexisting "
++ "session", ha->instance);
++ rc = q2t_sched_sess_work(ha->tgt, Q2T_SESS_WORK_ABORT, abts,
++ sizeof(*abts));
++ if (rc != 0) {
++ ha->tgt->tm_to_unknown = 1;
++ goto out_err;
++ }
++ goto out;
++ }
++
++ rc = __q24_handle_abts(ha, abts, sess);
++ if (rc != 0) {
++ PRINT_ERROR("qla2x00t(%ld): scst_rx_mgmt_fn_tag() failed: %d",
++ ha->instance, rc);
++ goto out_err;
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_err:
++ q24_send_abts_resp(ha, abts, SCST_MGMT_STATUS_REJECTED, false);
++ goto out;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q24_send_task_mgmt_ctio(scsi_qla_host_t *ha,
++ struct q2t_mgmt_cmd *mcmd, uint32_t resp_code)
++{
++ const atio7_entry_t *atio = &mcmd->orig_iocb.atio7;
++ ctio7_status1_entry_t *ctio;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending task mgmt CTIO7 (ha=%p, atio=%p, resp_code=%x",
++ ha, atio, resp_code);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, 1) != QLA_SUCCESS)
++ goto out;
++
++ ctio = (ctio7_status1_entry_t *)q2t_req_pkt(ha);
++ if (ctio == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out;
++ }
++
++ ctio->common.entry_type = CTIO_TYPE7;
++ ctio->common.entry_count = 1;
++ ctio->common.handle = Q2T_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK;
++ ctio->common.nport_handle = mcmd->sess->loop_id;
++ ctio->common.timeout = __constant_cpu_to_le16(Q2T_TIMEOUT);
++ ctio->common.vp_index = ha->vp_idx;
++ ctio->common.initiator_id[0] = atio->fcp_hdr.s_id[2];
++ ctio->common.initiator_id[1] = atio->fcp_hdr.s_id[1];
++ ctio->common.initiator_id[2] = atio->fcp_hdr.s_id[0];
++ ctio->common.exchange_addr = atio->exchange_addr;
++ ctio->flags = (atio->attr << 9) | __constant_cpu_to_le16(
++ CTIO7_FLAGS_STATUS_MODE_1 | CTIO7_FLAGS_SEND_STATUS);
++ ctio->ox_id = swab16(atio->fcp_hdr.ox_id);
++ ctio->scsi_status = __constant_cpu_to_le16(SS_RESPONSE_INFO_LEN_VALID);
++ ctio->response_len = __constant_cpu_to_le16(8);
++ ((uint32_t *)ctio->sense_data)[0] = cpu_to_be32(resp_code);
++
++ TRACE_BUFFER("CTIO7 TASK MGMT packet data", ctio, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q24_send_notify_ack(scsi_qla_host_t *ha,
++ notify24xx_entry_t *iocb, uint16_t srr_flags,
++ uint8_t srr_reject_code, uint8_t srr_explan)
++{
++ nack24xx_entry_t *nack;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending NOTIFY_ACK24 (ha=%p)", ha);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, 1) != QLA_SUCCESS)
++ goto out;
++
++ if (ha->tgt != NULL)
++ ha->tgt->notify_ack_expected++;
++
++ nack = (nack24xx_entry_t *)q2t_req_pkt(ha);
++ if (nack == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out;
++ }
++
++ nack->entry_type = NOTIFY_ACK_TYPE;
++ nack->entry_count = 1;
++ nack->nport_handle = iocb->nport_handle;
++ if (le16_to_cpu(iocb->status) == IMM_NTFY_ELS) {
++ nack->flags = iocb->flags &
++ __constant_cpu_to_le32(NOTIFY24XX_FLAGS_PUREX_IOCB);
++ }
++ nack->srr_rx_id = iocb->srr_rx_id;
++ nack->status = iocb->status;
++ nack->status_subcode = iocb->status_subcode;
++ nack->exchange_address = iocb->exchange_address;
++ nack->srr_rel_offs = iocb->srr_rel_offs;
++ nack->srr_ui = iocb->srr_ui;
++ nack->srr_flags = cpu_to_le16(srr_flags);
++ nack->srr_reject_code = srr_reject_code;
++ nack->srr_reject_code_expl = srr_explan;
++ nack->ox_id = iocb->ox_id;
++ nack->vp_index = iocb->vp_index;
++
++ TRACE(TRACE_SCSI, "qla2x00t(%ld): Sending 24xx Notify Ack %d",
++ ha->instance, nack->status);
++ TRACE_BUFFER("24xx Notify Ack packet data", nack, sizeof(*nack));
++
++ q2t_exec_queue(ha);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static uint32_t q2t_convert_to_fc_tm_status(int scst_mstatus)
++{
++ uint32_t res;
++
++ switch (scst_mstatus) {
++ case SCST_MGMT_STATUS_SUCCESS:
++ res = FC_TM_SUCCESS;
++ break;
++ case SCST_MGMT_STATUS_TASK_NOT_EXIST:
++ res = FC_TM_BAD_CMD;
++ break;
++ case SCST_MGMT_STATUS_FN_NOT_SUPPORTED:
++ case SCST_MGMT_STATUS_REJECTED:
++ res = FC_TM_REJECT;
++ break;
++ case SCST_MGMT_STATUS_LUN_NOT_EXIST:
++ case SCST_MGMT_STATUS_FAILED:
++ default:
++ res = FC_TM_FAILED;
++ break;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* SCST Callback */
++static void q2t_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd)
++{
++ struct q2t_mgmt_cmd *mcmd;
++ unsigned long flags;
++ scsi_qla_host_t *ha, *pha;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("scst_mcmd (%p) status %#x state %#x", scst_mcmd,
++ scst_mcmd->status, scst_mcmd->state);
++
++ mcmd = scst_mgmt_cmd_get_tgt_priv(scst_mcmd);
++ if (unlikely(mcmd == NULL)) {
++ PRINT_ERROR("qla2x00t: scst_mcmd %p tgt_spec is NULL", mcmd);
++ goto out;
++ }
++
++ ha = mcmd->sess->tgt->ha;
++ pha = to_qla_parent(ha);
++
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++ if (IS_FWI2_CAPABLE(ha)) {
++ if (mcmd->flags == Q24_MGMT_SEND_NACK) {
++ q24_send_notify_ack(ha,
++ &mcmd->orig_iocb.notify_entry24, 0, 0, 0);
++ } else {
++ if (scst_mcmd->fn == SCST_ABORT_TASK)
++ q24_send_abts_resp(ha, &mcmd->orig_iocb.abts,
++ scst_mgmt_cmd_get_status(scst_mcmd),
++ false);
++ else
++ q24_send_task_mgmt_ctio(ha, mcmd,
++ q2t_convert_to_fc_tm_status(
++ scst_mgmt_cmd_get_status(scst_mcmd)));
++ }
++ } else {
++ uint32_t resp_code = q2t_convert_to_fc_tm_status(
++ scst_mgmt_cmd_get_status(scst_mcmd));
++ q2x_send_notify_ack(ha, &mcmd->orig_iocb.notify_entry, 0,
++ resp_code, 1, 0, 0, 0);
++ }
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++ scst_mgmt_cmd_set_tgt_priv(scst_mcmd, NULL);
++ mempool_free(mcmd, q2t_mgmt_cmd_mempool);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* No locks */
++static int q2t_pci_map_calc_cnt(struct q2t_prm *prm)
++{
++ int res = 0;
++
++ BUG_ON(prm->cmd->sg_cnt == 0);
++
++ prm->sg = (struct scatterlist *)prm->cmd->sg;
++ prm->seg_cnt = pci_map_sg(prm->tgt->ha->pdev, prm->cmd->sg,
++ prm->cmd->sg_cnt, prm->cmd->dma_data_direction);
++ if (unlikely(prm->seg_cnt == 0))
++ goto out_err;
++
++ prm->cmd->sg_mapped = 1;
++
++ /*
++ * If greater than four sg entries then we need to allocate
++ * the continuation entries
++ */
++ if (prm->seg_cnt > prm->tgt->datasegs_per_cmd) {
++ prm->req_cnt += (uint16_t)(prm->seg_cnt -
++ prm->tgt->datasegs_per_cmd) /
++ prm->tgt->datasegs_per_cont;
++ if (((uint16_t)(prm->seg_cnt - prm->tgt->datasegs_per_cmd)) %
++ prm->tgt->datasegs_per_cont)
++ prm->req_cnt++;
++ }
++
++out:
++ TRACE_DBG("seg_cnt=%d, req_cnt=%d, res=%d", prm->seg_cnt,
++ prm->req_cnt, res);
++ return res;
++
++out_err:
++ PRINT_ERROR("qla2x00t(%ld): PCI mapping failed: sg_cnt=%d",
++ prm->tgt->ha->instance, prm->cmd->sg_cnt);
++ res = -1;
++ goto out;
++}
++
++static inline void q2t_unmap_sg(scsi_qla_host_t *ha, struct q2t_cmd *cmd)
++{
++ EXTRACHECKS_BUG_ON(!cmd->sg_mapped);
++ pci_unmap_sg(ha->pdev, cmd->sg, cmd->sg_cnt, cmd->dma_data_direction);
++ cmd->sg_mapped = 0;
++}
++
++static int q2t_check_reserve_free_req(scsi_qla_host_t *ha, uint32_t req_cnt)
++{
++ int res = SCST_TGT_RES_SUCCESS;
++ device_reg_t __iomem *reg;
++ uint32_t cnt;
++
++ TRACE_ENTRY();
++
++ ha = to_qla_parent(ha);
++ reg = ha->iobase;
++
++ if (ha->req_q_cnt < (req_cnt + 2)) {
++ if (IS_FWI2_CAPABLE(ha))
++ cnt = (uint16_t)RD_REG_DWORD(
++ &reg->isp24.req_q_out);
++ else
++ cnt = qla2x00_debounce_register(
++ ISP_REQ_Q_OUT(ha, &reg->isp));
++ TRACE_DBG("Request ring circled: cnt=%d, "
++ "ha->req_ring_index=%d, ha->req_q_cnt=%d, req_cnt=%d",
++ cnt, ha->req_ring_index, ha->req_q_cnt, req_cnt);
++ if (ha->req_ring_index < cnt)
++ ha->req_q_cnt = cnt - ha->req_ring_index;
++ else
++ ha->req_q_cnt = ha->request_q_length -
++ (ha->req_ring_index - cnt);
++ }
++
++ if (unlikely(ha->req_q_cnt < (req_cnt + 2))) {
++ TRACE(TRACE_OUT_OF_MEM, "qla2x00t(%ld): There is no room in the "
++ "request ring: ha->req_ring_index=%d, ha->req_q_cnt=%d, "
++ "req_cnt=%d", ha->instance, ha->req_ring_index,
++ ha->req_q_cnt, req_cnt);
++ res = SCST_TGT_RES_QUEUE_FULL;
++ goto out;
++ }
++
++ ha->req_q_cnt -= req_cnt;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static inline void *q2t_get_req_pkt(scsi_qla_host_t *ha)
++{
++ ha = to_qla_parent(ha);
++
++ /* Adjust ring index. */
++ ha->req_ring_index++;
++ if (ha->req_ring_index == ha->request_q_length) {
++ ha->req_ring_index = 0;
++ ha->request_ring_ptr = ha->request_ring;
++ } else {
++ ha->request_ring_ptr++;
++ }
++ return (cont_entry_t *)ha->request_ring_ptr;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static inline uint32_t q2t_make_handle(scsi_qla_host_t *ha)
++{
++ uint32_t h;
++
++ h = ha->current_handle;
++ /* always increment cmd handle */
++ do {
++ ++h;
++ if (h > MAX_OUTSTANDING_COMMANDS)
++ h = 1; /* 0 is Q2T_NULL_HANDLE */
++ if (h == ha->current_handle) {
++ TRACE(TRACE_OUT_OF_MEM, "qla2x00t(%ld): Ran out of "
++ "empty cmd slots in ha %p", ha->instance, ha);
++ h = Q2T_NULL_HANDLE;
++ break;
++ }
++ } while ((h == Q2T_NULL_HANDLE) ||
++ (h == Q2T_SKIP_HANDLE) ||
++ (ha->cmds[h-1] != NULL));
++
++ if (h != Q2T_NULL_HANDLE)
++ ha->current_handle = h;
++
++ return h;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static void q2x_build_ctio_pkt(struct q2t_prm *prm)
++{
++ uint32_t h;
++ ctio_entry_t *pkt;
++ scsi_qla_host_t *ha = prm->tgt->ha;
++
++ pkt = (ctio_entry_t *)ha->request_ring_ptr;
++ prm->pkt = pkt;
++ memset(pkt, 0, sizeof(*pkt));
++
++ if (prm->tgt->tgt_enable_64bit_addr)
++ pkt->common.entry_type = CTIO_A64_TYPE;
++ else
++ pkt->common.entry_type = CONTINUE_TGT_IO_TYPE;
++
++ pkt->common.entry_count = (uint8_t)prm->req_cnt;
++
++ h = q2t_make_handle(ha);
++ if (h != Q2T_NULL_HANDLE)
++ ha->cmds[h-1] = prm->cmd;
++
++ pkt->common.handle = h | CTIO_COMPLETION_HANDLE_MARK;
++ pkt->common.timeout = __constant_cpu_to_le16(Q2T_TIMEOUT);
++
++ /* Set initiator ID */
++ h = GET_TARGET_ID(ha, &prm->cmd->atio.atio2x);
++ SET_TARGET_ID(ha, pkt->common.target, h);
++
++ pkt->common.rx_id = prm->cmd->atio.atio2x.rx_id;
++ pkt->common.relative_offset = cpu_to_le32(prm->cmd->offset);
++
++ TRACE(TRACE_DEBUG|TRACE_SCSI, "qla2x00t(%ld): handle(scst_cmd) -> %08x, "
++ "timeout %d L %#x -> I %#x E %#x", ha->instance,
++ pkt->common.handle, Q2T_TIMEOUT,
++ le16_to_cpu(prm->cmd->atio.atio2x.lun),
++ GET_TARGET_ID(ha, &pkt->common), pkt->common.rx_id);
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q24_build_ctio_pkt(struct q2t_prm *prm)
++{
++ uint32_t h;
++ ctio7_status0_entry_t *pkt;
++ scsi_qla_host_t *ha = prm->tgt->ha;
++ atio7_entry_t *atio = &prm->cmd->atio.atio7;
++ int res = SCST_TGT_RES_SUCCESS;
++
++ TRACE_ENTRY();
++
++ pkt = (ctio7_status0_entry_t *)to_qla_parent(ha)->request_ring_ptr;
++ prm->pkt = pkt;
++ memset(pkt, 0, sizeof(*pkt));
++
++ pkt->common.entry_type = CTIO_TYPE7;
++ pkt->common.entry_count = (uint8_t)prm->req_cnt;
++ pkt->common.vp_index = ha->vp_idx;
++
++ h = q2t_make_handle(ha);
++ if (unlikely(h == Q2T_NULL_HANDLE)) {
++ /*
++ * CTIO type 7 from the firmware doesn't provide a way to
++ * know the initiator's LOOP ID, hence we can't find
++ * the session and, so, the command.
++ */
++ res = SCST_TGT_RES_QUEUE_FULL;
++ goto out;
++ } else
++ ha->cmds[h-1] = prm->cmd;
++
++ pkt->common.handle = h | CTIO_COMPLETION_HANDLE_MARK;
++ pkt->common.nport_handle = cpu_to_le16(prm->cmd->loop_id);
++ pkt->common.timeout = __constant_cpu_to_le16(Q2T_TIMEOUT);
++ pkt->common.initiator_id[0] = atio->fcp_hdr.s_id[2];
++ pkt->common.initiator_id[1] = atio->fcp_hdr.s_id[1];
++ pkt->common.initiator_id[2] = atio->fcp_hdr.s_id[0];
++ pkt->common.exchange_addr = atio->exchange_addr;
++ pkt->flags |= (atio->attr << 9);
++ pkt->ox_id = swab16(atio->fcp_hdr.ox_id);
++ pkt->relative_offset = cpu_to_le32(prm->cmd->offset);
++
++out:
++ TRACE(TRACE_DEBUG|TRACE_SCSI, "qla2x00t(%ld): handle(scst_cmd) -> %08x, "
++ "timeout %d, ox_id %#x", ha->instance, pkt->common.handle,
++ Q2T_TIMEOUT, le16_to_cpu(pkt->ox_id));
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. We have already made sure
++ * that there is sufficient amount of request entries to not drop it.
++ */
++static void q2t_load_cont_data_segments(struct q2t_prm *prm)
++{
++ int cnt;
++ uint32_t *dword_ptr;
++ int enable_64bit_addressing = prm->tgt->tgt_enable_64bit_addr;
++
++ TRACE_ENTRY();
++
++ /* Build continuation packets */
++ while (prm->seg_cnt > 0) {
++ cont_a64_entry_t *cont_pkt64 =
++ (cont_a64_entry_t *)q2t_get_req_pkt(prm->tgt->ha);
++
++ /*
++ * Make sure that from cont_pkt64 none of
++ * 64-bit specific fields used for 32-bit
++ * addressing. Cast to (cont_entry_t *) for
++ * that.
++ */
++
++ memset(cont_pkt64, 0, sizeof(*cont_pkt64));
++
++ cont_pkt64->entry_count = 1;
++ cont_pkt64->sys_define = 0;
++
++ if (enable_64bit_addressing) {
++ cont_pkt64->entry_type = CONTINUE_A64_TYPE;
++ dword_ptr =
++ (uint32_t *)&cont_pkt64->dseg_0_address;
++ } else {
++ cont_pkt64->entry_type = CONTINUE_TYPE;
++ dword_ptr =
++ (uint32_t *)&((cont_entry_t *)
++ cont_pkt64)->dseg_0_address;
++ }
++
++ /* Load continuation entry data segments */
++ for (cnt = 0;
++ cnt < prm->tgt->datasegs_per_cont && prm->seg_cnt;
++ cnt++, prm->seg_cnt--) {
++ *dword_ptr++ =
++ cpu_to_le32(pci_dma_lo32
++ (sg_dma_address(prm->sg)));
++ if (enable_64bit_addressing) {
++ *dword_ptr++ =
++ cpu_to_le32(pci_dma_hi32
++ (sg_dma_address
++ (prm->sg)));
++ }
++ *dword_ptr++ = cpu_to_le32(sg_dma_len(prm->sg));
++
++ TRACE_SG("S/G Segment Cont. phys_addr=%llx:%llx, len=%d",
++ (long long unsigned int)pci_dma_hi32(sg_dma_address(prm->sg)),
++ (long long unsigned int)pci_dma_lo32(sg_dma_address(prm->sg)),
++ (int)sg_dma_len(prm->sg));
++
++ prm->sg++;
++ }
++
++ TRACE_BUFFER("Continuation packet data",
++ cont_pkt64, REQUEST_ENTRY_SIZE);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. We have already made sure
++ * that there is sufficient amount of request entries to not drop it.
++ */
++static void q2x_load_data_segments(struct q2t_prm *prm)
++{
++ int cnt;
++ uint32_t *dword_ptr;
++ int enable_64bit_addressing = prm->tgt->tgt_enable_64bit_addr;
++ ctio_common_entry_t *pkt = (ctio_common_entry_t *)prm->pkt;
++
++ TRACE_DBG("iocb->scsi_status=%x, iocb->flags=%x",
++ le16_to_cpu(pkt->scsi_status), le16_to_cpu(pkt->flags));
++
++ pkt->transfer_length = cpu_to_le32(prm->cmd->bufflen);
++
++ /* Setup packet address segment pointer */
++ dword_ptr = pkt->dseg_0_address;
++
++ if (prm->seg_cnt == 0) {
++ /* No data transfer */
++ *dword_ptr++ = 0;
++ *dword_ptr = 0;
++
++ TRACE_BUFFER("No data, CTIO packet data", pkt,
++ REQUEST_ENTRY_SIZE);
++ goto out;
++ }
++
++ /* Set total data segment count */
++ pkt->dseg_count = cpu_to_le16(prm->seg_cnt);
++
++ /* If scatter gather */
++ TRACE_SG("%s", "Building S/G data segments...");
++ /* Load command entry data segments */
++ for (cnt = 0;
++ (cnt < prm->tgt->datasegs_per_cmd) && prm->seg_cnt;
++ cnt++, prm->seg_cnt--) {
++ *dword_ptr++ =
++ cpu_to_le32(pci_dma_lo32(sg_dma_address(prm->sg)));
++ if (enable_64bit_addressing) {
++ *dword_ptr++ =
++ cpu_to_le32(pci_dma_hi32
++ (sg_dma_address(prm->sg)));
++ }
++ *dword_ptr++ = cpu_to_le32(sg_dma_len(prm->sg));
++
++ TRACE_SG("S/G Segment phys_addr=%llx:%llx, len=%d",
++ (long long unsigned int)pci_dma_hi32(sg_dma_address(prm->sg)),
++ (long long unsigned int)pci_dma_lo32(sg_dma_address(prm->sg)),
++ (int)sg_dma_len(prm->sg));
++
++ prm->sg++;
++ }
++
++ TRACE_BUFFER("Scatter/gather, CTIO packet data", pkt,
++ REQUEST_ENTRY_SIZE);
++
++ q2t_load_cont_data_segments(prm);
++
++out:
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. We have already made sure
++ * that there is sufficient amount of request entries to not drop it.
++ */
++static void q24_load_data_segments(struct q2t_prm *prm)
++{
++ int cnt;
++ uint32_t *dword_ptr;
++ int enable_64bit_addressing = prm->tgt->tgt_enable_64bit_addr;
++ ctio7_status0_entry_t *pkt = (ctio7_status0_entry_t *)prm->pkt;
++
++ TRACE_DBG("iocb->scsi_status=%x, iocb->flags=%x",
++ le16_to_cpu(pkt->scsi_status), le16_to_cpu(pkt->flags));
++
++ pkt->transfer_length = cpu_to_le32(prm->cmd->bufflen);
++
++ /* Setup packet address segment pointer */
++ dword_ptr = pkt->dseg_0_address;
++
++ if (prm->seg_cnt == 0) {
++ /* No data transfer */
++ *dword_ptr++ = 0;
++ *dword_ptr = 0;
++
++ TRACE_BUFFER("No data, CTIO7 packet data", pkt,
++ REQUEST_ENTRY_SIZE);
++ goto out;
++ }
++
++ /* Set total data segment count */
++ pkt->common.dseg_count = cpu_to_le16(prm->seg_cnt);
++
++ /* If scatter gather */
++ TRACE_SG("%s", "Building S/G data segments...");
++ /* Load command entry data segments */
++ for (cnt = 0;
++ (cnt < prm->tgt->datasegs_per_cmd) && prm->seg_cnt;
++ cnt++, prm->seg_cnt--) {
++ *dword_ptr++ =
++ cpu_to_le32(pci_dma_lo32(sg_dma_address(prm->sg)));
++ if (enable_64bit_addressing) {
++ *dword_ptr++ =
++ cpu_to_le32(pci_dma_hi32(
++ sg_dma_address(prm->sg)));
++ }
++ *dword_ptr++ = cpu_to_le32(sg_dma_len(prm->sg));
++
++ TRACE_SG("S/G Segment phys_addr=%llx:%llx, len=%d",
++ (long long unsigned int)pci_dma_hi32(sg_dma_address(
++ prm->sg)),
++ (long long unsigned int)pci_dma_lo32(sg_dma_address(
++ prm->sg)),
++ (int)sg_dma_len(prm->sg));
++
++ prm->sg++;
++ }
++
++ q2t_load_cont_data_segments(prm);
++
++out:
++ return;
++}
++
++static inline int q2t_has_data(struct q2t_cmd *cmd)
++{
++ return cmd->bufflen > 0;
++}
++
++static int q2t_pre_xmit_response(struct q2t_cmd *cmd,
++ struct q2t_prm *prm, int xmit_type, unsigned long *flags)
++{
++ int res;
++ struct q2t_tgt *tgt = cmd->tgt;
++ scsi_qla_host_t *ha = tgt->ha;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++ uint16_t full_req_cnt;
++ struct scst_cmd *scst_cmd = cmd->scst_cmd;
++
++ TRACE_ENTRY();
++
++ if (unlikely(cmd->aborted)) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): terminating exchange "
++ "for aborted cmd=%p (scst_cmd=%p, tag=%d)",
++ ha->instance, cmd, scst_cmd, cmd->tag);
++
++ cmd->state = Q2T_STATE_ABORTED;
++ scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_ABORTED);
++
++ if (IS_FWI2_CAPABLE(ha))
++ q24_send_term_exchange(ha, cmd, &cmd->atio.atio7, 0);
++ else
++ q2x_send_term_exchange(ha, cmd, &cmd->atio.atio2x, 0);
++ /* !! At this point cmd could be already freed !! */
++ res = Q2T_PRE_XMIT_RESP_CMD_ABORTED;
++ goto out;
++ }
++
++ TRACE(TRACE_SCSI, "qla2x00t(%ld): tag=%lld", ha->instance,
++ scst_cmd_get_tag(scst_cmd));
++
++ prm->cmd = cmd;
++ prm->tgt = tgt;
++ prm->rq_result = scst_cmd_get_status(scst_cmd);
++ prm->sense_buffer = scst_cmd_get_sense_buffer(scst_cmd);
++ prm->sense_buffer_len = scst_cmd_get_sense_buffer_len(scst_cmd);
++ prm->sg = NULL;
++ prm->seg_cnt = -1;
++ prm->req_cnt = 1;
++ prm->add_status_pkt = 0;
++
++ TRACE_DBG("rq_result=%x, xmit_type=%x", prm->rq_result, xmit_type);
++ if (prm->rq_result != 0)
++ TRACE_BUFFER("Sense", prm->sense_buffer, prm->sense_buffer_len);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, 0) != QLA_SUCCESS) {
++ res = SCST_TGT_RES_FATAL_ERROR;
++ goto out;
++ }
++
++ TRACE_DBG("CTIO start: ha(%d)", (int)ha->instance);
++
++ if ((xmit_type & Q2T_XMIT_DATA) && q2t_has_data(cmd)) {
++ if (q2t_pci_map_calc_cnt(prm) != 0) {
++ res = SCST_TGT_RES_QUEUE_FULL;
++ goto out;
++ }
++ }
++
++ full_req_cnt = prm->req_cnt;
++
++ if (xmit_type & Q2T_XMIT_STATUS) {
++ /* Bidirectional transfers not supported (yet) */
++ if (unlikely(scst_get_resid(scst_cmd, &prm->residual, NULL))) {
++ if (prm->residual > 0) {
++ TRACE_DBG("Residual underflow: %d (tag %lld, "
++ "op %x, bufflen %d, rq_result %x)",
++ prm->residual, scst_cmd->tag,
++ scst_cmd->cdb[0], cmd->bufflen,
++ prm->rq_result);
++ prm->rq_result |= SS_RESIDUAL_UNDER;
++ } else if (prm->residual < 0) {
++ TRACE_DBG("Residual overflow: %d (tag %lld, "
++ "op %x, bufflen %d, rq_result %x)",
++ prm->residual, scst_cmd->tag,
++ scst_cmd->cdb[0], cmd->bufflen,
++ prm->rq_result);
++ prm->rq_result |= SS_RESIDUAL_OVER;
++ prm->residual = -prm->residual;
++ }
++ }
++
++ /*
++ * If Q2T_XMIT_DATA is not set, add_status_pkt will be ignored
++ * in *xmit_response() below
++ */
++ if (q2t_has_data(cmd)) {
++ if (SCST_SENSE_VALID(prm->sense_buffer) ||
++ (IS_FWI2_CAPABLE(ha) &&
++ (prm->rq_result != 0))) {
++ prm->add_status_pkt = 1;
++ full_req_cnt++;
++ }
++ }
++ }
++
++ TRACE_DBG("req_cnt=%d, full_req_cnt=%d, add_status_pkt=%d",
++ prm->req_cnt, full_req_cnt, prm->add_status_pkt);
++
++ /* Acquire ring specific lock */
++ spin_lock_irqsave(&pha->hardware_lock, *flags);
++
++ /* Does F/W have an IOCBs for this request */
++ res = q2t_check_reserve_free_req(ha, full_req_cnt);
++ if (unlikely(res != SCST_TGT_RES_SUCCESS) &&
++ (xmit_type & Q2T_XMIT_DATA))
++ goto out_unlock_free_unmap;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unlock_free_unmap:
++ if (cmd->sg_mapped)
++ q2t_unmap_sg(ha, cmd);
++
++ /* Release ring specific lock */
++ spin_unlock_irqrestore(&pha->hardware_lock, *flags);
++ goto out;
++}
++
++static inline int q2t_need_explicit_conf(scsi_qla_host_t *ha,
++ struct q2t_cmd *cmd, int sending_sense)
++{
++ if (ha->enable_class_2)
++ return 0;
++
++ if (sending_sense)
++ return cmd->conf_compl_supported;
++ else
++ return ha->enable_explicit_conf && cmd->conf_compl_supported;
++}
++
++static void q2x_init_ctio_ret_entry(ctio_ret_entry_t *ctio_m1,
++ struct q2t_prm *prm)
++{
++ TRACE_ENTRY();
++
++ prm->sense_buffer_len = min((uint32_t)prm->sense_buffer_len,
++ (uint32_t)sizeof(ctio_m1->sense_data));
++
++ ctio_m1->flags = __constant_cpu_to_le16(OF_SSTS | OF_FAST_POST |
++ OF_NO_DATA | OF_SS_MODE_1);
++ ctio_m1->flags |= __constant_cpu_to_le16(OF_INC_RC);
++ if (q2t_need_explicit_conf(prm->tgt->ha, prm->cmd, 0)) {
++ ctio_m1->flags |= __constant_cpu_to_le16(OF_EXPL_CONF |
++ OF_CONF_REQ);
++ }
++ ctio_m1->scsi_status = cpu_to_le16(prm->rq_result);
++ ctio_m1->residual = cpu_to_le32(prm->residual);
++ if (SCST_SENSE_VALID(prm->sense_buffer)) {
++ if (q2t_need_explicit_conf(prm->tgt->ha, prm->cmd, 1)) {
++ ctio_m1->flags |= __constant_cpu_to_le16(OF_EXPL_CONF |
++ OF_CONF_REQ);
++ }
++ ctio_m1->scsi_status |= __constant_cpu_to_le16(
++ SS_SENSE_LEN_VALID);
++ ctio_m1->sense_length = cpu_to_le16(prm->sense_buffer_len);
++ memcpy(ctio_m1->sense_data, prm->sense_buffer,
++ prm->sense_buffer_len);
++ } else {
++ memset(ctio_m1->sense_data, 0, sizeof(ctio_m1->sense_data));
++ ctio_m1->sense_length = 0;
++ }
++
++ /* Sense with len > 26, is it possible ??? */
++
++ TRACE_EXIT();
++ return;
++}
++
++static int __q2x_xmit_response(struct q2t_cmd *cmd, int xmit_type)
++{
++ int res;
++ unsigned long flags;
++ scsi_qla_host_t *ha, *pha;
++ struct q2t_prm prm;
++ ctio_common_entry_t *pkt;
++
++ TRACE_ENTRY();
++
++ memset(&prm, 0, sizeof(prm));
++
++ res = q2t_pre_xmit_response(cmd, &prm, xmit_type, &flags);
++ if (unlikely(res != SCST_TGT_RES_SUCCESS)) {
++ if (res == Q2T_PRE_XMIT_RESP_CMD_ABORTED)
++ res = SCST_TGT_RES_SUCCESS;
++ goto out;
++ }
++
++ /* Here pha->hardware_lock already locked */
++
++ ha = prm.tgt->ha;
++ pha = to_qla_parent(ha);
++
++ q2x_build_ctio_pkt(&prm);
++ pkt = (ctio_common_entry_t *)prm.pkt;
++
++ if (q2t_has_data(cmd) && (xmit_type & Q2T_XMIT_DATA)) {
++ pkt->flags |= __constant_cpu_to_le16(OF_FAST_POST | OF_DATA_IN);
++ pkt->flags |= __constant_cpu_to_le16(OF_INC_RC);
++
++ q2x_load_data_segments(&prm);
++
++ if (prm.add_status_pkt == 0) {
++ if (xmit_type & Q2T_XMIT_STATUS) {
++ pkt->scsi_status = cpu_to_le16(prm.rq_result);
++ pkt->residual = cpu_to_le32(prm.residual);
++ pkt->flags |= __constant_cpu_to_le16(OF_SSTS);
++ if (q2t_need_explicit_conf(ha, cmd, 0)) {
++ pkt->flags |= __constant_cpu_to_le16(
++ OF_EXPL_CONF |
++ OF_CONF_REQ);
++ }
++ }
++ } else {
++ /*
++ * We have already made sure that there is sufficient
++ * amount of request entries to not drop HW lock in
++ * req_pkt().
++ */
++ ctio_ret_entry_t *ctio_m1 =
++ (ctio_ret_entry_t *)q2t_get_req_pkt(ha);
++
++ TRACE_DBG("%s", "Building additional status packet");
++
++ memcpy(ctio_m1, pkt, sizeof(*ctio_m1));
++ ctio_m1->entry_count = 1;
++ ctio_m1->dseg_count = 0;
++
++ /* Real finish is ctio_m1's finish */
++ pkt->handle |= CTIO_INTERMEDIATE_HANDLE_MARK;
++ pkt->flags &= ~__constant_cpu_to_le16(OF_INC_RC);
++
++ q2x_init_ctio_ret_entry(ctio_m1, &prm);
++ TRACE_BUFFER("Status CTIO packet data", ctio_m1,
++ REQUEST_ENTRY_SIZE);
++ }
++ } else
++ q2x_init_ctio_ret_entry((ctio_ret_entry_t *)pkt, &prm);
++
++ cmd->state = Q2T_STATE_PROCESSED; /* Mid-level is done processing */
++
++ TRACE_BUFFER("Xmitting", pkt, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++ /* Release ring specific lock */
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++#ifdef CONFIG_QLA_TGT_DEBUG_SRR
++static void q2t_check_srr_debug(struct q2t_cmd *cmd, int *xmit_type)
++{
++#if 0 /* This is not a real status packets lost, so it won't lead to SRR */
++ if ((*xmit_type & Q2T_XMIT_STATUS) && (scst_random() % 200) == 50) {
++ *xmit_type &= ~Q2T_XMIT_STATUS;
++ TRACE_MGMT_DBG("Dropping cmd %p (tag %d) status", cmd,
++ cmd->tag);
++ }
++#endif
++
++ if (q2t_has_data(cmd) && (cmd->sg_cnt > 1) &&
++ ((scst_random() % 100) == 20)) {
++ int i, leave = 0;
++ unsigned int tot_len = 0;
++
++ while (leave == 0)
++ leave = scst_random() % cmd->sg_cnt;
++
++ for (i = 0; i < leave; i++)
++ tot_len += cmd->sg[i].length;
++
++ TRACE_MGMT_DBG("Cutting cmd %p (tag %d) buffer tail to len %d, "
++ "sg_cnt %d (cmd->bufflen %d, cmd->sg_cnt %d)", cmd,
++ cmd->tag, tot_len, leave, cmd->bufflen, cmd->sg_cnt);
++
++ cmd->bufflen = tot_len;
++ cmd->sg_cnt = leave;
++ }
++
++ if (q2t_has_data(cmd) && ((scst_random() % 100) == 70)) {
++ unsigned int offset = scst_random() % cmd->bufflen;
++
++ TRACE_MGMT_DBG("Cutting cmd %p (tag %d) buffer head "
++ "to offset %d (cmd->bufflen %d)", cmd, cmd->tag,
++ offset, cmd->bufflen);
++ if (offset == 0)
++ *xmit_type &= ~Q2T_XMIT_DATA;
++ else if (q2t_cut_cmd_data_head(cmd, offset)) {
++ TRACE_MGMT_DBG("q2t_cut_cmd_data_head() failed (tag %d)",
++ cmd->tag);
++ }
++ }
++}
++#else
++static inline void q2t_check_srr_debug(struct q2t_cmd *cmd, int *xmit_type) {}
++#endif
++
++static int q2x_xmit_response(struct scst_cmd *scst_cmd)
++{
++ int xmit_type = Q2T_XMIT_DATA, res;
++ int is_send_status = scst_cmd_get_is_send_status(scst_cmd);
++ struct q2t_cmd *cmd = (struct q2t_cmd *)scst_cmd_get_tgt_priv(scst_cmd);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ BUG_ON(!q2t_has_data(cmd) && !is_send_status);
++#endif
++
++#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
++ EXTRACHECKS_BUG_ON(scst_cmd_atomic(scst_cmd));
++#endif
++
++ if (is_send_status)
++ xmit_type |= Q2T_XMIT_STATUS;
++
++ cmd->bufflen = scst_cmd_get_adjusted_resp_data_len(scst_cmd);
++ cmd->sg = scst_cmd_get_sg(scst_cmd);
++ cmd->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
++ cmd->data_direction = scst_cmd_get_data_direction(scst_cmd);
++ cmd->dma_data_direction = scst_to_tgt_dma_dir(cmd->data_direction);
++ cmd->offset = scst_cmd_get_ppl_offset(scst_cmd);
++ cmd->aborted = scst_cmd_aborted(scst_cmd);
++
++ q2t_check_srr_debug(cmd, &xmit_type);
++
++ TRACE_DBG("is_send_status=%x, cmd->bufflen=%d, cmd->sg_cnt=%d, "
++ "cmd->data_direction=%d", is_send_status, cmd->bufflen,
++ cmd->sg_cnt, cmd->data_direction);
++
++ if (IS_FWI2_CAPABLE(cmd->tgt->ha))
++ res = __q24_xmit_response(cmd, xmit_type);
++ else
++ res = __q2x_xmit_response(cmd, xmit_type);
++
++ return res;
++}
++
++static void q24_init_ctio_ret_entry(ctio7_status0_entry_t *ctio,
++ struct q2t_prm *prm)
++{
++ ctio7_status1_entry_t *ctio1;
++
++ TRACE_ENTRY();
++
++ prm->sense_buffer_len = min((uint32_t)prm->sense_buffer_len,
++ (uint32_t)sizeof(ctio1->sense_data));
++ ctio->flags |= __constant_cpu_to_le16(CTIO7_FLAGS_SEND_STATUS);
++ if (q2t_need_explicit_conf(prm->tgt->ha, prm->cmd, 0)) {
++ ctio->flags |= __constant_cpu_to_le16(
++ CTIO7_FLAGS_EXPLICIT_CONFORM |
++ CTIO7_FLAGS_CONFORM_REQ);
++ }
++ ctio->residual = cpu_to_le32(prm->residual);
++ ctio->scsi_status = cpu_to_le16(prm->rq_result);
++ if (SCST_SENSE_VALID(prm->sense_buffer)) {
++ int i;
++ ctio1 = (ctio7_status1_entry_t *)ctio;
++ if (q2t_need_explicit_conf(prm->tgt->ha, prm->cmd, 1)) {
++ ctio1->flags |= __constant_cpu_to_le16(
++ CTIO7_FLAGS_EXPLICIT_CONFORM |
++ CTIO7_FLAGS_CONFORM_REQ);
++ }
++ ctio1->flags &= ~__constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_0);
++ ctio1->flags |= __constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_1);
++ ctio1->scsi_status |= __constant_cpu_to_le16(SS_SENSE_LEN_VALID);
++ ctio1->sense_length = cpu_to_le16(prm->sense_buffer_len);
++ for (i = 0; i < prm->sense_buffer_len/4; i++)
++ ((uint32_t *)ctio1->sense_data)[i] =
++ cpu_to_be32(((uint32_t *)prm->sense_buffer)[i]);
++#if 0
++ if (unlikely((prm->sense_buffer_len % 4) != 0)) {
++ static int q;
++ if (q < 10) {
++ PRINT_INFO("qla2x00t(%ld): %d bytes of sense "
++ "lost", prm->tgt->ha->instance,
++ prm->sense_buffer_len % 4);
++ q++;
++ }
++ }
++#endif
++ } else {
++ ctio1 = (ctio7_status1_entry_t *)ctio;
++ ctio1->flags &= ~__constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_0);
++ ctio1->flags |= __constant_cpu_to_le16(CTIO7_FLAGS_STATUS_MODE_1);
++ ctio1->sense_length = 0;
++ memset(ctio1->sense_data, 0, sizeof(ctio1->sense_data));
++ }
++
++ /* Sense with len > 24, is it possible ??? */
++
++ TRACE_EXIT();
++ return;
++}
++
++static int __q24_xmit_response(struct q2t_cmd *cmd, int xmit_type)
++{
++ int res;
++ unsigned long flags;
++ scsi_qla_host_t *ha, *pha;
++ struct q2t_prm prm;
++ ctio7_status0_entry_t *pkt;
++
++ TRACE_ENTRY();
++
++ memset(&prm, 0, sizeof(prm));
++
++ res = q2t_pre_xmit_response(cmd, &prm, xmit_type, &flags);
++ if (unlikely(res != SCST_TGT_RES_SUCCESS)) {
++ if (res == Q2T_PRE_XMIT_RESP_CMD_ABORTED)
++ res = SCST_TGT_RES_SUCCESS;
++ goto out;
++ }
++
++ /* Here pha->hardware_lock already locked */
++
++ ha = prm.tgt->ha;
++ pha = to_qla_parent(ha);
++
++ res = q24_build_ctio_pkt(&prm);
++ if (unlikely(res != SCST_TGT_RES_SUCCESS))
++ goto out_unmap_unlock;
++
++ pkt = (ctio7_status0_entry_t *)prm.pkt;
++
++ if (q2t_has_data(cmd) && (xmit_type & Q2T_XMIT_DATA)) {
++ pkt->flags |= __constant_cpu_to_le16(CTIO7_FLAGS_DATA_IN |
++ CTIO7_FLAGS_STATUS_MODE_0);
++
++ q24_load_data_segments(&prm);
++
++ if (prm.add_status_pkt == 0) {
++ if (xmit_type & Q2T_XMIT_STATUS) {
++ pkt->scsi_status = cpu_to_le16(prm.rq_result);
++ pkt->residual = cpu_to_le32(prm.residual);
++ pkt->flags |= __constant_cpu_to_le16(
++ CTIO7_FLAGS_SEND_STATUS);
++ if (q2t_need_explicit_conf(ha, cmd, 0)) {
++ pkt->flags |= __constant_cpu_to_le16(
++ CTIO7_FLAGS_EXPLICIT_CONFORM |
++ CTIO7_FLAGS_CONFORM_REQ);
++ }
++ }
++ } else {
++ /*
++ * We have already made sure that there is sufficient
++ * amount of request entries to not drop HW lock in
++ * req_pkt().
++ */
++ ctio7_status1_entry_t *ctio =
++ (ctio7_status1_entry_t *)q2t_get_req_pkt(ha);
++
++ TRACE_DBG("%s", "Building additional status packet");
++
++ memcpy(ctio, pkt, sizeof(*ctio));
++ ctio->common.entry_count = 1;
++ ctio->common.dseg_count = 0;
++ ctio->flags &= ~__constant_cpu_to_le16(
++ CTIO7_FLAGS_DATA_IN);
++
++ /* Real finish is ctio_m1's finish */
++ pkt->common.handle |= CTIO_INTERMEDIATE_HANDLE_MARK;
++ pkt->flags |= __constant_cpu_to_le16(
++ CTIO7_FLAGS_DONT_RET_CTIO);
++ q24_init_ctio_ret_entry((ctio7_status0_entry_t *)ctio,
++ &prm);
++ TRACE_BUFFER("Status CTIO7", ctio, REQUEST_ENTRY_SIZE);
++ }
++ } else
++ q24_init_ctio_ret_entry(pkt, &prm);
++
++ cmd->state = Q2T_STATE_PROCESSED; /* Mid-level is done processing */
++
++ TRACE_BUFFER("Xmitting CTIO7", pkt, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out_unlock:
++ /* Release ring specific lock */
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unmap_unlock:
++ if (cmd->sg_mapped)
++ q2t_unmap_sg(ha, cmd);
++ goto out_unlock;
++}
++
++static int __q2t_rdy_to_xfer(struct q2t_cmd *cmd)
++{
++ int res = SCST_TGT_RES_SUCCESS;
++ unsigned long flags;
++ scsi_qla_host_t *ha, *pha;
++ struct q2t_tgt *tgt = cmd->tgt;
++ struct q2t_prm prm;
++ void *p;
++
++ TRACE_ENTRY();
++
++ memset(&prm, 0, sizeof(prm));
++ prm.cmd = cmd;
++ prm.tgt = tgt;
++ prm.sg = NULL;
++ prm.req_cnt = 1;
++ ha = tgt->ha;
++ pha = to_qla_parent(ha);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, 0) != QLA_SUCCESS) {
++ res = SCST_TGT_RES_FATAL_ERROR;
++ goto out;
++ }
++
++ TRACE_DBG("CTIO_start: ha(%d)", (int)ha->instance);
++
++ /* Calculate number of entries and segments required */
++ if (q2t_pci_map_calc_cnt(&prm) != 0) {
++ res = SCST_TGT_RES_QUEUE_FULL;
++ goto out;
++ }
++
++ /* Acquire ring specific lock */
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++
++ /* Does F/W have an IOCBs for this request */
++ res = q2t_check_reserve_free_req(ha, prm.req_cnt);
++ if (res != SCST_TGT_RES_SUCCESS)
++ goto out_unlock_free_unmap;
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ ctio7_status0_entry_t *pkt;
++ res = q24_build_ctio_pkt(&prm);
++ if (unlikely(res != SCST_TGT_RES_SUCCESS))
++ goto out_unlock_free_unmap;
++ pkt = (ctio7_status0_entry_t *)prm.pkt;
++ pkt->flags |= __constant_cpu_to_le16(CTIO7_FLAGS_DATA_OUT |
++ CTIO7_FLAGS_STATUS_MODE_0);
++ q24_load_data_segments(&prm);
++ p = pkt;
++ } else {
++ ctio_common_entry_t *pkt;
++ q2x_build_ctio_pkt(&prm);
++ pkt = (ctio_common_entry_t *)prm.pkt;
++ pkt->flags = __constant_cpu_to_le16(OF_FAST_POST | OF_DATA_OUT);
++ q2x_load_data_segments(&prm);
++ p = pkt;
++ }
++
++ cmd->state = Q2T_STATE_NEED_DATA;
++
++ TRACE_BUFFER("Xfering", p, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out_unlock:
++ /* Release ring specific lock */
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_unlock_free_unmap:
++ if (cmd->sg_mapped)
++ q2t_unmap_sg(ha, cmd);
++ goto out_unlock;
++}
++
++static int q2t_rdy_to_xfer(struct scst_cmd *scst_cmd)
++{
++ int res;
++ struct q2t_cmd *cmd;
++
++ TRACE_ENTRY();
++
++ TRACE(TRACE_SCSI, "qla2x00t: tag=%lld", scst_cmd_get_tag(scst_cmd));
++
++ cmd = (struct q2t_cmd *)scst_cmd_get_tgt_priv(scst_cmd);
++ cmd->bufflen = scst_cmd_get_write_fields(scst_cmd, &cmd->sg,
++ &cmd->sg_cnt);
++ cmd->data_direction = scst_cmd_get_data_direction(scst_cmd);
++ cmd->dma_data_direction = scst_to_tgt_dma_dir(cmd->data_direction);
++
++ res = __q2t_rdy_to_xfer(cmd);
++
++ TRACE_EXIT();
++ return res;
++}
++
++/* If hardware_lock held on entry, might drop it, then reacquire */
++static void q2x_send_term_exchange(scsi_qla_host_t *ha, struct q2t_cmd *cmd,
++ atio_entry_t *atio, int ha_locked)
++{
++ ctio_ret_entry_t *ctio;
++ unsigned long flags = 0; /* to stop compiler's warning */
++ int do_tgt_cmd_done = 0;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending TERM EXCH CTIO (ha=%p)", ha);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, ha_locked) != QLA_SUCCESS)
++ goto out;
++
++ if (!ha_locked)
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++
++ ctio = (ctio_ret_entry_t *)q2t_req_pkt(ha);
++ if (ctio == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out_unlock;
++ }
++
++ ctio->entry_type = CTIO_RET_TYPE;
++ ctio->entry_count = 1;
++ if (cmd != NULL) {
++ if (cmd->state < Q2T_STATE_PROCESSED) {
++ PRINT_ERROR("qla2x00t(%ld): Terminating cmd %p with "
++ "incorrect state %d", ha->instance, cmd,
++ cmd->state);
++ } else
++ do_tgt_cmd_done = 1;
++ }
++ ctio->handle = Q2T_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK;
++
++ /* Set IDs */
++ SET_TARGET_ID(ha, ctio->target, GET_TARGET_ID(ha, atio));
++ ctio->rx_id = atio->rx_id;
++
++ /* Most likely, it isn't needed */
++ ctio->residual = atio->data_length;
++ if (ctio->residual != 0)
++ ctio->scsi_status |= SS_RESIDUAL_UNDER;
++
++ ctio->flags = __constant_cpu_to_le16(OF_FAST_POST | OF_TERM_EXCH |
++ OF_NO_DATA | OF_SS_MODE_1);
++ ctio->flags |= __constant_cpu_to_le16(OF_INC_RC);
++
++ TRACE_BUFFER("CTIO TERM EXCH packet data", ctio, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out_unlock:
++ if (!ha_locked)
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++ if (do_tgt_cmd_done) {
++ if (!ha_locked && !in_interrupt()) {
++ msleep(250); /* just in case */
++ scst_tgt_cmd_done(cmd->scst_cmd, SCST_CONTEXT_DIRECT);
++ } else
++ scst_tgt_cmd_done(cmd->scst_cmd, SCST_CONTEXT_TASKLET);
++ /* !! At this point cmd could be already freed !! */
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* If hardware_lock held on entry, might drop it, then reacquire */
++static void q24_send_term_exchange(scsi_qla_host_t *ha, struct q2t_cmd *cmd,
++ atio7_entry_t *atio, int ha_locked)
++{
++ ctio7_status1_entry_t *ctio;
++ unsigned long flags = 0; /* to stop compiler's warning */
++ int do_tgt_cmd_done = 0;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Sending TERM EXCH CTIO7 (ha=%p)", ha);
++
++ /* Send marker if required */
++ if (q2t_issue_marker(ha, ha_locked) != QLA_SUCCESS)
++ goto out;
++
++ if (!ha_locked)
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++
++ ctio = (ctio7_status1_entry_t *)q2t_req_pkt(ha);
++ if (ctio == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out_unlock;
++ }
++
++ ctio->common.entry_type = CTIO_TYPE7;
++ ctio->common.entry_count = 1;
++ if (cmd != NULL) {
++ ctio->common.nport_handle = cmd->loop_id;
++ if (cmd->state < Q2T_STATE_PROCESSED) {
++ PRINT_ERROR("qla2x00t(%ld): Terminating cmd %p with "
++ "incorrect state %d", ha->instance, cmd,
++ cmd->state);
++ } else
++ do_tgt_cmd_done = 1;
++ } else
++ ctio->common.nport_handle = CTIO7_NHANDLE_UNRECOGNIZED;
++ ctio->common.handle = Q2T_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK;
++ ctio->common.timeout = __constant_cpu_to_le16(Q2T_TIMEOUT);
++ ctio->common.vp_index = ha->vp_idx;
++ ctio->common.initiator_id[0] = atio->fcp_hdr.s_id[2];
++ ctio->common.initiator_id[1] = atio->fcp_hdr.s_id[1];
++ ctio->common.initiator_id[2] = atio->fcp_hdr.s_id[0];
++ ctio->common.exchange_addr = atio->exchange_addr;
++ ctio->flags = (atio->attr << 9) | __constant_cpu_to_le16(
++ CTIO7_FLAGS_STATUS_MODE_1 | CTIO7_FLAGS_TERMINATE);
++ ctio->ox_id = swab16(atio->fcp_hdr.ox_id);
++
++ /* Most likely, it isn't needed */
++ ctio->residual = get_unaligned((uint32_t *)
++ &atio->fcp_cmnd.add_cdb[atio->fcp_cmnd.add_cdb_len]);
++ if (ctio->residual != 0)
++ ctio->scsi_status |= SS_RESIDUAL_UNDER;
++
++ TRACE_BUFFER("CTIO7 TERM EXCH packet data", ctio, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out_unlock:
++ if (!ha_locked)
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++ if (do_tgt_cmd_done) {
++ if (!ha_locked && !in_interrupt()) {
++ msleep(250); /* just in case */
++ scst_tgt_cmd_done(cmd->scst_cmd, SCST_CONTEXT_DIRECT);
++ } else
++ scst_tgt_cmd_done(cmd->scst_cmd, SCST_CONTEXT_TASKLET);
++ /* !! At this point cmd could be already freed !! */
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static inline void q2t_free_cmd(struct q2t_cmd *cmd)
++{
++ EXTRACHECKS_BUG_ON(cmd->sg_mapped);
++
++ if (unlikely(cmd->free_sg))
++ kfree(cmd->sg);
++ kmem_cache_free(q2t_cmd_cachep, cmd);
++}
++
++static void q2t_on_free_cmd(struct scst_cmd *scst_cmd)
++{
++ struct q2t_cmd *cmd;
++
++ TRACE_ENTRY();
++
++ TRACE(TRACE_SCSI, "qla2x00t: Freeing command %p, tag %lld",
++ scst_cmd, scst_cmd_get_tag(scst_cmd));
++
++ cmd = (struct q2t_cmd *)scst_cmd_get_tgt_priv(scst_cmd);
++ scst_cmd_set_tgt_priv(scst_cmd, NULL);
++
++ q2t_free_cmd(cmd);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_prepare_srr_ctio(scsi_qla_host_t *ha, struct q2t_cmd *cmd,
++ void *ctio)
++{
++ struct srr_ctio *sc;
++ struct q2t_tgt *tgt = ha->tgt;
++ int res = 0;
++ struct srr_imm *imm;
++
++ tgt->ctio_srr_id++;
++
++ TRACE_MGMT_DBG("qla2x00t(%ld): CTIO with SRR "
++ "status received", ha->instance);
++
++ if (ctio == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): SRR CTIO, "
++ "but ctio is NULL", ha->instance);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (cmd->scst_cmd != NULL)
++ scst_update_hw_pending_start(cmd->scst_cmd);
++
++ sc = kzalloc(sizeof(*sc), GFP_ATOMIC);
++ if (sc != NULL) {
++ sc->cmd = cmd;
++ /* IRQ is already OFF */
++ spin_lock(&tgt->srr_lock);
++ sc->srr_id = tgt->ctio_srr_id;
++ list_add_tail(&sc->srr_list_entry,
++ &tgt->srr_ctio_list);
++ TRACE_MGMT_DBG("CTIO SRR %p added (id %d)",
++ sc, sc->srr_id);
++ if (tgt->imm_srr_id == tgt->ctio_srr_id) {
++ int found = 0;
++ list_for_each_entry(imm, &tgt->srr_imm_list,
++ srr_list_entry) {
++ if (imm->srr_id == sc->srr_id) {
++ found = 1;
++ break;
++ }
++ }
++ if (found) {
++ TRACE_MGMT_DBG("%s", "Scheduling srr work");
++ schedule_work(&tgt->srr_work);
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): imm_srr_id "
++ "== ctio_srr_id (%d), but there is no "
++ "corresponding SRR IMM, deleting CTIO "
++ "SRR %p", ha->instance, tgt->ctio_srr_id,
++ sc);
++ list_del(&sc->srr_list_entry);
++ spin_unlock(&tgt->srr_lock);
++
++ kfree(sc);
++ res = -EINVAL;
++ goto out;
++ }
++ }
++ spin_unlock(&tgt->srr_lock);
++ } else {
++ struct srr_imm *ti;
++ PRINT_ERROR("qla2x00t(%ld): Unable to allocate SRR CTIO entry",
++ ha->instance);
++ spin_lock(&tgt->srr_lock);
++ list_for_each_entry_safe(imm, ti, &tgt->srr_imm_list,
++ srr_list_entry) {
++ if (imm->srr_id == tgt->ctio_srr_id) {
++ TRACE_MGMT_DBG("IMM SRR %p deleted "
++ "(id %d)", imm, imm->srr_id);
++ list_del(&imm->srr_list_entry);
++ q2t_reject_free_srr_imm(ha, imm, 1);
++ }
++ }
++ spin_unlock(&tgt->srr_lock);
++ res = -ENOMEM;
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static int q2t_term_ctio_exchange(scsi_qla_host_t *ha, void *ctio,
++ struct q2t_cmd *cmd, uint32_t status)
++{
++ int term = 0;
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ if (ctio != NULL) {
++ ctio7_fw_entry_t *c = (ctio7_fw_entry_t *)ctio;
++ term = !(c->flags &
++ __constant_cpu_to_le16(OF_TERM_EXCH));
++ } else
++ term = 1;
++ if (term) {
++ q24_send_term_exchange(ha, cmd,
++ &cmd->atio.atio7, 1);
++ }
++ } else {
++ if (status != CTIO_SUCCESS)
++ q2x_modify_command_count(ha, 1, 0);
++#if 0 /* seems, it isn't needed */
++ if (ctio != NULL) {
++ ctio_common_entry_t *c = (ctio_common_entry_t *)ctio;
++ term = !(c->flags &
++ __constant_cpu_to_le16(
++ CTIO7_FLAGS_TERMINATE));
++ } else
++ term = 1;
++ if (term) {
++ q2x_send_term_exchange(ha, cmd,
++ &cmd->atio.atio2x, 1);
++ }
++#endif
++ }
++ return term;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static inline struct q2t_cmd *q2t_get_cmd(scsi_qla_host_t *ha, uint32_t handle)
++{
++ handle--;
++ if (ha->cmds[handle] != NULL) {
++ struct q2t_cmd *cmd = ha->cmds[handle];
++ ha->cmds[handle] = NULL;
++ return cmd;
++ } else
++ return NULL;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static struct q2t_cmd *q2t_ctio_to_cmd(scsi_qla_host_t *ha, uint32_t handle,
++ void *ctio)
++{
++ struct q2t_cmd *cmd = NULL;
++
++ /* Clear out internal marks */
++ handle &= ~(CTIO_COMPLETION_HANDLE_MARK | CTIO_INTERMEDIATE_HANDLE_MARK);
++
++ if (handle != Q2T_NULL_HANDLE) {
++ if (unlikely(handle == Q2T_SKIP_HANDLE)) {
++ TRACE_DBG("%s", "SKIP_HANDLE CTIO");
++ goto out;
++ }
++ /* handle-1 is actually used */
++ if (unlikely(handle > MAX_OUTSTANDING_COMMANDS)) {
++ PRINT_ERROR("qla2x00t(%ld): Wrong handle %x "
++ "received", ha->instance, handle);
++ goto out;
++ }
++ cmd = q2t_get_cmd(ha, handle);
++ if (unlikely(cmd == NULL)) {
++ PRINT_WARNING("qla2x00t(%ld): Suspicious: unable to "
++ "find the command with handle %x",
++ ha->instance, handle);
++ goto out;
++ }
++ } else if (ctio != NULL) {
++ uint16_t loop_id;
++ int tag;
++ struct q2t_sess *sess;
++ struct scst_cmd *scst_cmd;
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ /* We can't get loop ID from CTIO7 */
++ PRINT_ERROR("qla2x00t(%ld): Wrong CTIO received: "
++ "QLA24xx doesn't support NULL handles",
++ ha->instance);
++ goto out;
++ } else {
++ ctio_common_entry_t *c = (ctio_common_entry_t *)ctio;
++ loop_id = GET_TARGET_ID(ha, c);
++ tag = c->rx_id;
++ }
++
++ sess = q2t_find_sess_by_loop_id(ha->tgt, loop_id);
++ if (sess == NULL) {
++ PRINT_WARNING("qla2x00t(%ld): Suspicious: "
++ "ctio_completion for non-existing session "
++ "(loop_id %d, tag %d)",
++ ha->instance, loop_id, tag);
++ goto out;
++ }
++
++ scst_cmd = scst_find_cmd_by_tag(sess->scst_sess, tag);
++ if (scst_cmd == NULL) {
++ PRINT_WARNING("qla2x00t(%ld): Suspicious: unable to "
++ "find the command with tag %d (loop_id %d)",
++ ha->instance, tag, loop_id);
++ goto out;
++ }
++
++ cmd = (struct q2t_cmd *)scst_cmd_get_tgt_priv(scst_cmd);
++ TRACE_DBG("Found q2t_cmd %p (tag %d)", cmd, tag);
++ }
++
++out:
++ return cmd;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q2t_do_ctio_completion(scsi_qla_host_t *ha, uint32_t handle,
++ uint32_t status, void *ctio)
++{
++ struct scst_cmd *scst_cmd;
++ struct q2t_cmd *cmd;
++ enum scst_exec_context context;
++
++ TRACE_ENTRY();
++
++#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
++ context = SCST_CONTEXT_THREAD;
++#else
++ context = SCST_CONTEXT_TASKLET;
++#endif
++
++ TRACE(TRACE_DEBUG|TRACE_SCSI, "qla2x00t(%ld): handle(ctio %p "
++ "status %#x) <- %08x", ha->instance, ctio, status, handle);
++
++ if (handle & CTIO_INTERMEDIATE_HANDLE_MARK) {
++ /* That could happen only in case of an error/reset/abort */
++ if (status != CTIO_SUCCESS) {
++ TRACE_MGMT_DBG("Intermediate CTIO received (status %x)",
++ status);
++ }
++ goto out;
++ }
++
++ cmd = q2t_ctio_to_cmd(ha, handle, ctio);
++ if (cmd == NULL) {
++ if (status != CTIO_SUCCESS)
++ q2t_term_ctio_exchange(ha, ctio, NULL, status);
++ goto out;
++ }
++
++ scst_cmd = cmd->scst_cmd;
++
++ if (cmd->sg_mapped)
++ q2t_unmap_sg(ha, cmd);
++
++ if (unlikely(status != CTIO_SUCCESS)) {
++ switch (status & 0xFFFF) {
++ case CTIO_LIP_RESET:
++ case CTIO_TARGET_RESET:
++ case CTIO_ABORTED:
++ case CTIO_TIMEOUT:
++ case CTIO_INVALID_RX_ID:
++ /* They are OK */
++ TRACE(TRACE_MINOR_AND_MGMT_DBG,
++ "qla2x00t(%ld): CTIO with "
++ "status %#x received, state %x, scst_cmd %p, "
++ "op %x (LIP_RESET=e, ABORTED=2, TARGET_RESET=17, "
++ "TIMEOUT=b, INVALID_RX_ID=8)", ha->instance,
++ status, cmd->state, scst_cmd, scst_cmd->cdb[0]);
++ break;
++
++ case CTIO_PORT_LOGGED_OUT:
++ case CTIO_PORT_UNAVAILABLE:
++ PRINT_INFO("qla2x00t(%ld): CTIO with PORT LOGGED "
++ "OUT (29) or PORT UNAVAILABLE (28) status %x "
++ "received (state %x, scst_cmd %p, op %x)",
++ ha->instance, status, cmd->state, scst_cmd,
++ scst_cmd->cdb[0]);
++ break;
++
++ case CTIO_SRR_RECEIVED:
++ if (q2t_prepare_srr_ctio(ha, cmd, ctio) != 0)
++ break;
++ else
++ goto out;
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): CTIO with error status "
++ "0x%x received (state %x, scst_cmd %p, op %x)",
++ ha->instance, status, cmd->state, scst_cmd,
++ scst_cmd->cdb[0]);
++ break;
++ }
++
++ if (cmd->state != Q2T_STATE_NEED_DATA)
++ if (q2t_term_ctio_exchange(ha, ctio, cmd, status))
++ goto out;
++ }
++
++ if (cmd->state == Q2T_STATE_PROCESSED) {
++ TRACE_DBG("Command %p finished", cmd);
++ } else if (cmd->state == Q2T_STATE_NEED_DATA) {
++ int rx_status = SCST_RX_STATUS_SUCCESS;
++
++ cmd->state = Q2T_STATE_DATA_IN;
++
++ if (unlikely(status != CTIO_SUCCESS))
++ rx_status = SCST_RX_STATUS_ERROR;
++ else
++ cmd->write_data_transferred = 1;
++
++ TRACE_DBG("Data received, context %x, rx_status %d",
++ context, rx_status);
++
++ scst_rx_data(scst_cmd, rx_status, context);
++ goto out;
++ } else if (cmd->state == Q2T_STATE_ABORTED) {
++ TRACE_MGMT_DBG("Aborted command %p (tag %d) finished", cmd,
++ cmd->tag);
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): A command in state (%d) should "
++ "not return a CTIO complete", ha->instance, cmd->state);
++ }
++
++ if (unlikely(status != CTIO_SUCCESS)) {
++ TRACE_MGMT_DBG("%s", "Finishing failed CTIO");
++ scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_FAILED);
++ }
++
++ scst_tgt_cmd_done(scst_cmd, context);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++/* called via callback from qla2xxx */
++static void q2x_ctio_completion(scsi_qla_host_t *ha, uint32_t handle)
++{
++ struct q2t_tgt *tgt = ha->tgt;
++
++ TRACE_ENTRY();
++
++ if (likely(tgt != NULL)) {
++ tgt->irq_cmd_count++;
++ q2t_do_ctio_completion(ha, handle, CTIO_SUCCESS, NULL);
++ tgt->irq_cmd_count--;
++ } else {
++ TRACE_DBG("CTIO, but target mode not enabled (ha %p handle "
++ "%#x)", ha, handle);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2x_do_send_cmd_to_scst(struct q2t_cmd *cmd)
++{
++ int res = 0;
++ struct q2t_sess *sess = cmd->sess;
++ uint16_t lun;
++ atio_entry_t *atio = &cmd->atio.atio2x;
++ scst_data_direction dir;
++ int context;
++
++ TRACE_ENTRY();
++
++ /* make it be in network byte order */
++ lun = swab16(le16_to_cpu(atio->lun));
++ cmd->scst_cmd = scst_rx_cmd(sess->scst_sess, (uint8_t *)&lun,
++ sizeof(lun), atio->cdb, Q2T_MAX_CDB_LEN,
++ SCST_ATOMIC);
++
++ if (cmd->scst_cmd == NULL) {
++ PRINT_ERROR("%s", "qla2x00t: scst_rx_cmd() failed");
++ res = -EFAULT;
++ goto out;
++ }
++
++ cmd->tag = atio->rx_id;
++ scst_cmd_set_tag(cmd->scst_cmd, cmd->tag);
++ scst_cmd_set_tgt_priv(cmd->scst_cmd, cmd);
++
++ if ((atio->execution_codes & (ATIO_EXEC_READ | ATIO_EXEC_WRITE)) ==
++ (ATIO_EXEC_READ | ATIO_EXEC_WRITE))
++ dir = SCST_DATA_BIDI;
++ else if (atio->execution_codes & ATIO_EXEC_READ)
++ dir = SCST_DATA_READ;
++ else if (atio->execution_codes & ATIO_EXEC_WRITE)
++ dir = SCST_DATA_WRITE;
++ else
++ dir = SCST_DATA_NONE;
++ scst_cmd_set_expected(cmd->scst_cmd, dir,
++ le32_to_cpu(atio->data_length));
++
++ switch (atio->task_codes) {
++ case ATIO_SIMPLE_QUEUE:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_SIMPLE);
++ break;
++ case ATIO_HEAD_OF_QUEUE:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE);
++ break;
++ case ATIO_ORDERED_QUEUE:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ case ATIO_ACA_QUEUE:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_ACA);
++ break;
++ case ATIO_UNTAGGED:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_UNTAGGED);
++ break;
++ default:
++ PRINT_ERROR("qla2x00t: unknown task code %x, use "
++ "ORDERED instead", atio->task_codes);
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ }
++
++#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
++ context = SCST_CONTEXT_THREAD;
++#else
++ context = SCST_CONTEXT_TASKLET;
++#endif
++
++ TRACE_DBG("Context %x", context);
++ TRACE(TRACE_SCSI, "qla2x00t: START Command (tag %d, queue_type %d)",
++ cmd->tag, scst_cmd_get_queue_type(cmd->scst_cmd));
++ scst_cmd_init_done(cmd->scst_cmd, context);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q24_do_send_cmd_to_scst(struct q2t_cmd *cmd)
++{
++ int res = 0;
++ struct q2t_sess *sess = cmd->sess;
++ atio7_entry_t *atio = &cmd->atio.atio7;
++ scst_data_direction dir;
++ int context;
++
++ TRACE_ENTRY();
++
++ cmd->scst_cmd = scst_rx_cmd(sess->scst_sess,
++ (uint8_t *)&atio->fcp_cmnd.lun, sizeof(atio->fcp_cmnd.lun),
++ atio->fcp_cmnd.cdb, sizeof(atio->fcp_cmnd.cdb) +
++ atio->fcp_cmnd.add_cdb_len, SCST_ATOMIC);
++
++ if (cmd->scst_cmd == NULL) {
++ PRINT_ERROR("%s", "qla2x00t: scst_rx_cmd() failed");
++ res = -EFAULT;
++ goto out;
++ }
++
++ cmd->tag = atio->exchange_addr;
++ scst_cmd_set_tag(cmd->scst_cmd, cmd->tag);
++ scst_cmd_set_tgt_priv(cmd->scst_cmd, cmd);
++
++ if (atio->fcp_cmnd.rddata && atio->fcp_cmnd.wrdata)
++ dir = SCST_DATA_BIDI;
++ else if (atio->fcp_cmnd.rddata)
++ dir = SCST_DATA_READ;
++ else if (atio->fcp_cmnd.wrdata)
++ dir = SCST_DATA_WRITE;
++ else
++ dir = SCST_DATA_NONE;
++ scst_cmd_set_expected(cmd->scst_cmd, dir,
++ be32_to_cpu(get_unaligned((uint32_t *)
++ &atio->fcp_cmnd.add_cdb[atio->fcp_cmnd.add_cdb_len])));
++
++ switch (atio->fcp_cmnd.task_attr) {
++ case ATIO_SIMPLE_QUEUE:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_SIMPLE);
++ break;
++ case ATIO_HEAD_OF_QUEUE:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE);
++ break;
++ case ATIO_ORDERED_QUEUE:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ case ATIO_ACA_QUEUE:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_ACA);
++ break;
++ case ATIO_UNTAGGED:
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_UNTAGGED);
++ break;
++ default:
++ PRINT_ERROR("qla2x00t: unknown task code %x, use "
++ "ORDERED instead", atio->fcp_cmnd.task_attr);
++ scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ }
++
++#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
++ context = SCST_CONTEXT_THREAD;
++#else
++ context = SCST_CONTEXT_TASKLET;
++#endif
++
++ TRACE_DBG("Context %x", context);
++ TRACE(TRACE_SCSI, "qla2x00t: START Command %p (tag %d, queue type %x)",
++ cmd, cmd->tag, scst_cmd_get_queue_type(cmd->scst_cmd));
++ scst_cmd_init_done(cmd->scst_cmd, context);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_do_send_cmd_to_scst(scsi_qla_host_t *ha,
++ struct q2t_cmd *cmd, struct q2t_sess *sess)
++{
++ int res;
++
++ TRACE_ENTRY();
++
++ cmd->sess = sess;
++ cmd->loop_id = sess->loop_id;
++ cmd->conf_compl_supported = sess->conf_compl_supported;
++
++ if (IS_FWI2_CAPABLE(ha))
++ res = q24_do_send_cmd_to_scst(cmd);
++ else
++ res = q2x_do_send_cmd_to_scst(cmd);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_send_cmd_to_scst(scsi_qla_host_t *ha, atio_t *atio)
++{
++ int res = 0;
++ struct q2t_tgt *tgt = ha->tgt;
++ struct q2t_sess *sess;
++ struct q2t_cmd *cmd;
++
++ TRACE_ENTRY();
++
++ if (unlikely(tgt->tgt_stop)) {
++ TRACE_MGMT_DBG("New command while device %p is shutting "
++ "down", tgt);
++ res = -EFAULT;
++ goto out;
++ }
++
++ cmd = kmem_cache_zalloc(q2t_cmd_cachep, GFP_ATOMIC);
++ if (cmd == NULL) {
++ TRACE(TRACE_OUT_OF_MEM, "qla2x00t(%ld): Allocation of cmd "
++ "failed", ha->instance);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ memcpy(&cmd->atio.atio2x, atio, sizeof(*atio));
++ cmd->state = Q2T_STATE_NEW;
++ cmd->tgt = ha->tgt;
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ atio7_entry_t *a = (atio7_entry_t *)atio;
++ sess = q2t_find_sess_by_s_id(tgt, a->fcp_hdr.s_id);
++ if (unlikely(sess == NULL)) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): Unable to find "
++ "wwn login (s_id %x:%x:%x), trying to create "
++ "it manually", ha->instance,
++ a->fcp_hdr.s_id[0], a->fcp_hdr.s_id[1],
++ a->fcp_hdr.s_id[2]);
++ goto out_sched;
++ }
++ } else {
++ sess = q2t_find_sess_by_loop_id(tgt,
++ GET_TARGET_ID(ha, (atio_entry_t *)atio));
++ if (unlikely(sess == NULL)) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): Unable to find "
++ "wwn login (loop_id=%d), trying to create it "
++ "manually", ha->instance,
++ GET_TARGET_ID(ha, (atio_entry_t *)atio));
++ goto out_sched;
++ }
++ }
++
++ res = q2t_do_send_cmd_to_scst(ha, cmd, sess);
++ if (unlikely(res != 0))
++ goto out_free_cmd;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free_cmd:
++ q2t_free_cmd(cmd);
++ goto out;
++
++out_sched:
++ if (atio->entry_count > 1) {
++ TRACE_MGMT_DBG("Dropping multy entry cmd %p", cmd);
++ res = -EBUSY;
++ goto out_free_cmd;
++ }
++ res = q2t_sched_sess_work(tgt, Q2T_SESS_WORK_CMD, &cmd, sizeof(cmd));
++ if (res != 0)
++ goto out_free_cmd;
++ goto out;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_issue_task_mgmt(struct q2t_sess *sess, uint8_t *lun,
++ int lun_size, int fn, void *iocb, int flags)
++{
++ int res = 0, rc = -1;
++ struct q2t_mgmt_cmd *mcmd;
++
++ TRACE_ENTRY();
++
++ mcmd = mempool_alloc(q2t_mgmt_cmd_mempool, GFP_ATOMIC);
++ if (mcmd == NULL) {
++ PRINT_CRIT_ERROR("qla2x00t(%ld): Allocation of management "
++ "command failed, some commands and their data could "
++ "leak", sess->tgt->ha->instance);
++ res = -ENOMEM;
++ goto out;
++ }
++ memset(mcmd, 0, sizeof(*mcmd));
++
++ mcmd->sess = sess;
++ if (iocb) {
++ memcpy(&mcmd->orig_iocb.notify_entry, iocb,
++ sizeof(mcmd->orig_iocb.notify_entry));
++ }
++ mcmd->flags = flags;
++
++ switch (fn) {
++ case Q2T_CLEAR_ACA:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): CLEAR_ACA received",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_CLEAR_ACA,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ case Q2T_TARGET_RESET:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): TARGET_RESET received",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_TARGET_RESET,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ case Q2T_LUN_RESET:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): LUN_RESET received",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_LUN_RESET,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ case Q2T_CLEAR_TS:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): CLEAR_TS received",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_CLEAR_TASK_SET,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ case Q2T_ABORT_TS:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): ABORT_TS received",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_ABORT_TASK_SET,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ case Q2T_ABORT_ALL:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Doing ABORT_ALL_TASKS",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess,
++ SCST_ABORT_ALL_TASKS,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ case Q2T_ABORT_ALL_SESS:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Doing ABORT_ALL_TASKS_SESS",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess,
++ SCST_ABORT_ALL_TASKS_SESS,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ case Q2T_NEXUS_LOSS_SESS:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Doing NEXUS_LOSS_SESS",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_NEXUS_LOSS_SESS,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ case Q2T_NEXUS_LOSS:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Doing NEXUS_LOSS",
++ sess->tgt->ha->instance);
++ rc = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_NEXUS_LOSS,
++ lun, lun_size, SCST_ATOMIC, mcmd);
++ break;
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Unknown task mgmt fn 0x%x",
++ sess->tgt->ha->instance, fn);
++ rc = -1;
++ break;
++ }
++
++ if (rc != 0) {
++ PRINT_ERROR("qla2x00t(%ld): scst_rx_mgmt_fn_lun() failed: %d",
++ sess->tgt->ha->instance, rc);
++ res = -EFAULT;
++ goto out_free;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ mempool_free(mcmd, q2t_mgmt_cmd_mempool);
++ goto out;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_handle_task_mgmt(scsi_qla_host_t *ha, void *iocb)
++{
++ int res = 0;
++ struct q2t_tgt *tgt;
++ struct q2t_sess *sess;
++ uint8_t *lun;
++ uint16_t lun_data;
++ int lun_size;
++ int fn;
++
++ TRACE_ENTRY();
++
++ tgt = ha->tgt;
++ if (IS_FWI2_CAPABLE(ha)) {
++ atio7_entry_t *a = (atio7_entry_t *)iocb;
++ lun = (uint8_t *)&a->fcp_cmnd.lun;
++ lun_size = sizeof(a->fcp_cmnd.lun);
++ fn = a->fcp_cmnd.task_mgmt_flags;
++ sess = q2t_find_sess_by_s_id(tgt, a->fcp_hdr.s_id);
++ } else {
++ notify_entry_t *n = (notify_entry_t *)iocb;
++ /* make it be in network byte order */
++ lun_data = swab16(le16_to_cpu(n->lun));
++ lun = (uint8_t *)&lun_data;
++ lun_size = sizeof(lun_data);
++ fn = n->task_flags >> IMM_NTFY_TASK_MGMT_SHIFT;
++ sess = q2t_find_sess_by_loop_id(tgt, GET_TARGET_ID(ha, n));
++ }
++
++ if (sess == NULL) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): task mgmt fn 0x%x for "
++ "non-existant session", ha->instance, fn);
++ res = q2t_sched_sess_work(tgt, Q2T_SESS_WORK_TM, iocb,
++ IS_FWI2_CAPABLE(ha) ? sizeof(atio7_entry_t) :
++ sizeof(notify_entry_t));
++ if (res != 0)
++ tgt->tm_to_unknown = 1;
++ goto out;
++ }
++
++ res = q2t_issue_task_mgmt(sess, lun, lun_size, fn, iocb, 0);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int __q2t_abort_task(scsi_qla_host_t *ha, notify_entry_t *iocb,
++ struct q2t_sess *sess)
++{
++ int res, rc;
++ struct q2t_mgmt_cmd *mcmd;
++
++ TRACE_ENTRY();
++
++ mcmd = mempool_alloc(q2t_mgmt_cmd_mempool, GFP_ATOMIC);
++ if (mcmd == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s: Allocation of ABORT cmd failed",
++ ha->instance, __func__);
++ res = -ENOMEM;
++ goto out;
++ }
++ memset(mcmd, 0, sizeof(*mcmd));
++
++ mcmd->sess = sess;
++ memcpy(&mcmd->orig_iocb.notify_entry, iocb,
++ sizeof(mcmd->orig_iocb.notify_entry));
++
++ rc = scst_rx_mgmt_fn_tag(sess->scst_sess, SCST_ABORT_TASK,
++ le16_to_cpu(iocb->seq_id), SCST_ATOMIC, mcmd);
++ if (rc != 0) {
++ PRINT_ERROR("qla2x00t(%ld): scst_rx_mgmt_fn_tag() failed: %d",
++ ha->instance, rc);
++ res = -EFAULT;
++ goto out_free;
++ }
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ mempool_free(mcmd, q2t_mgmt_cmd_mempool);
++ goto out;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static int q2t_abort_task(scsi_qla_host_t *ha, notify_entry_t *iocb)
++{
++ int res;
++ struct q2t_sess *sess;
++ int loop_id;
++
++ TRACE_ENTRY();
++
++ loop_id = GET_TARGET_ID(ha, iocb);
++
++ sess = q2t_find_sess_by_loop_id(ha->tgt, loop_id);
++ if (sess == NULL) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): task abort for unexisting "
++ "session", ha->instance);
++ res = q2t_sched_sess_work(sess->tgt, Q2T_SESS_WORK_ABORT, iocb,
++ sizeof(*iocb));
++ if (res != 0)
++ sess->tgt->tm_to_unknown = 1;
++ goto out;
++ }
++
++ res = __q2t_abort_task(ha, iocb, sess);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static int q24_handle_els(scsi_qla_host_t *ha, notify24xx_entry_t *iocb)
++{
++ int res = 1; /* send notify ack */
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("qla2x00t(%ld): ELS opcode %x", ha->instance,
++ iocb->status_subcode);
++
++ switch (iocb->status_subcode) {
++ case ELS_PLOGI:
++ case ELS_FLOGI:
++ case ELS_PRLI:
++ break;
++
++ case ELS_LOGO:
++ case ELS_PRLO:
++ res = q2t_reset(ha, iocb, Q2T_NEXUS_LOSS_SESS);
++ break;
++
++ case ELS_PDISC:
++ case ELS_ADISC:
++ {
++ struct q2t_tgt *tgt = ha->tgt;
++ if (tgt->link_reinit_iocb_pending) {
++ q24_send_notify_ack(ha, &tgt->link_reinit_iocb, 0, 0, 0);
++ tgt->link_reinit_iocb_pending = 0;
++ }
++ break;
++ }
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Unsupported ELS command %x "
++ "received", ha->instance, iocb->status_subcode);
++#if 0
++ res = q2t_reset(ha, iocb, Q2T_NEXUS_LOSS_SESS);
++#endif
++ break;
++ }
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int q2t_cut_cmd_data_head(struct q2t_cmd *cmd, unsigned int offset)
++{
++ int res = 0;
++ int cnt, first_sg, first_page = 0, first_page_offs = 0, i;
++ unsigned int l;
++ int cur_dst, cur_src;
++ struct scatterlist *sg;
++ size_t bufflen = 0;
++
++ TRACE_ENTRY();
++
++ first_sg = -1;
++ cnt = 0;
++ l = 0;
++ for (i = 0; i < cmd->sg_cnt; i++) {
++ l += cmd->sg[i].length;
++ if (l > offset) {
++ int sg_offs = l - cmd->sg[i].length;
++ first_sg = i;
++ if (cmd->sg[i].offset == 0) {
++ first_page_offs = offset % PAGE_SIZE;
++ first_page = (offset - sg_offs) >> PAGE_SHIFT;
++ } else {
++ TRACE_SG("i=%d, sg[i].offset=%d, "
++ "sg_offs=%d", i, cmd->sg[i].offset, sg_offs);
++ if ((cmd->sg[i].offset + sg_offs) > offset) {
++ first_page_offs = offset - sg_offs;
++ first_page = 0;
++ } else {
++ int sec_page_offs = sg_offs +
++ (PAGE_SIZE - cmd->sg[i].offset);
++ first_page_offs = sec_page_offs % PAGE_SIZE;
++ first_page = 1 +
++ ((offset - sec_page_offs) >>
++ PAGE_SHIFT);
++ }
++ }
++ cnt = cmd->sg_cnt - i + (first_page_offs != 0);
++ break;
++ }
++ }
++ if (first_sg == -1) {
++ PRINT_ERROR("qla2x00t(%ld): Wrong offset %d, buf length %d",
++ cmd->tgt->ha->instance, offset, cmd->bufflen);
++ res = -EINVAL;
++ goto out;
++ }
++
++ TRACE_SG("offset=%d, first_sg=%d, first_page=%d, "
++ "first_page_offs=%d, cmd->bufflen=%d, cmd->sg_cnt=%d", offset,
++ first_sg, first_page, first_page_offs, cmd->bufflen,
++ cmd->sg_cnt);
++
++ sg = kmalloc(cnt * sizeof(sg[0]), GFP_KERNEL);
++ if (sg == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): Unable to allocate cut "
++ "SG (len %zd)", cmd->tgt->ha->instance,
++ cnt * sizeof(sg[0]));
++ res = -ENOMEM;
++ goto out;
++ }
++ sg_init_table(sg, cnt);
++
++ cur_dst = 0;
++ cur_src = first_sg;
++ if (first_page_offs != 0) {
++ int fpgs;
++ sg_set_page(&sg[cur_dst], &sg_page(&cmd->sg[cur_src])[first_page],
++ PAGE_SIZE - first_page_offs, first_page_offs);
++ bufflen += sg[cur_dst].length;
++ TRACE_SG("cur_dst=%d, cur_src=%d, sg[].page=%p, "
++ "sg[].offset=%d, sg[].length=%d, bufflen=%zu",
++ cur_dst, cur_src, sg_page(&sg[cur_dst]), sg[cur_dst].offset,
++ sg[cur_dst].length, bufflen);
++ cur_dst++;
++
++ fpgs = (cmd->sg[cur_src].length >> PAGE_SHIFT) +
++ ((cmd->sg[cur_src].length & ~PAGE_MASK) != 0);
++ first_page++;
++ if (fpgs > first_page) {
++ sg_set_page(&sg[cur_dst],
++ &sg_page(&cmd->sg[cur_src])[first_page],
++ cmd->sg[cur_src].length - PAGE_SIZE*first_page,
++ 0);
++ TRACE_SG("fpgs=%d, cur_dst=%d, cur_src=%d, "
++ "sg[].page=%p, sg[].length=%d, bufflen=%zu",
++ fpgs, cur_dst, cur_src, sg_page(&sg[cur_dst]),
++ sg[cur_dst].length, bufflen);
++ bufflen += sg[cur_dst].length;
++ cur_dst++;
++ }
++ cur_src++;
++ }
++
++ while (cur_src < cmd->sg_cnt) {
++ sg_set_page(&sg[cur_dst], sg_page(&cmd->sg[cur_src]),
++ cmd->sg[cur_src].length, cmd->sg[cur_src].offset);
++ TRACE_SG("cur_dst=%d, cur_src=%d, "
++ "sg[].page=%p, sg[].length=%d, sg[].offset=%d, "
++ "bufflen=%zu", cur_dst, cur_src, sg_page(&sg[cur_dst]),
++ sg[cur_dst].length, sg[cur_dst].offset, bufflen);
++ bufflen += sg[cur_dst].length;
++ cur_dst++;
++ cur_src++;
++ }
++
++ if (cmd->free_sg)
++ kfree(cmd->sg);
++
++ cmd->sg = sg;
++ cmd->free_sg = 1;
++ cmd->sg_cnt = cur_dst;
++ cmd->bufflen = bufflen;
++ cmd->offset += offset;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static inline int q2t_srr_adjust_data(struct q2t_cmd *cmd,
++ uint32_t srr_rel_offs, int *xmit_type)
++{
++ int res = 0;
++ int rel_offs;
++
++ rel_offs = srr_rel_offs - cmd->offset;
++ TRACE_MGMT_DBG("srr_rel_offs=%d, rel_offs=%d", srr_rel_offs, rel_offs);
++
++ *xmit_type = Q2T_XMIT_ALL;
++
++ if (rel_offs < 0) {
++ PRINT_ERROR("qla2x00t(%ld): SRR rel_offs (%d) "
++ "< 0", cmd->tgt->ha->instance, rel_offs);
++ res = -1;
++ } else if (rel_offs == cmd->bufflen)
++ *xmit_type = Q2T_XMIT_STATUS;
++ else if (rel_offs > 0)
++ res = q2t_cut_cmd_data_head(cmd, rel_offs);
++
++ return res;
++}
++
++/* No locks, thread context */
++static void q24_handle_srr(scsi_qla_host_t *ha, struct srr_ctio *sctio,
++ struct srr_imm *imm)
++{
++ notify24xx_entry_t *ntfy = &imm->imm.notify_entry24;
++ struct q2t_cmd *cmd = sctio->cmd;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("SRR cmd %p, srr_ui %x", cmd, ntfy->srr_ui);
++
++ switch (ntfy->srr_ui) {
++ case SRR_IU_STATUS:
++ spin_lock_irq(&pha->hardware_lock);
++ q24_send_notify_ack(ha, ntfy,
++ NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0);
++ spin_unlock_irq(&pha->hardware_lock);
++ __q24_xmit_response(cmd, Q2T_XMIT_STATUS);
++ break;
++ case SRR_IU_DATA_IN:
++ cmd->bufflen = scst_cmd_get_adjusted_resp_data_len(cmd->scst_cmd);
++ if (q2t_has_data(cmd)) {
++ uint32_t offset;
++ int xmit_type;
++ offset = le32_to_cpu(imm->imm.notify_entry24.srr_rel_offs);
++ if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0)
++ goto out_reject;
++ spin_lock_irq(&pha->hardware_lock);
++ q24_send_notify_ack(ha, ntfy,
++ NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0);
++ spin_unlock_irq(&pha->hardware_lock);
++ __q24_xmit_response(cmd, xmit_type);
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): SRR for in data for cmd "
++ "without them (tag %d, SCSI status %d), "
++ "reject", ha->instance, cmd->tag,
++ scst_cmd_get_status(cmd->scst_cmd));
++ goto out_reject;
++ }
++ break;
++ case SRR_IU_DATA_OUT:
++ cmd->bufflen = scst_cmd_get_write_fields(cmd->scst_cmd,
++ &cmd->sg, &cmd->sg_cnt);
++ if (q2t_has_data(cmd)) {
++ uint32_t offset;
++ int xmit_type;
++ offset = le32_to_cpu(imm->imm.notify_entry24.srr_rel_offs);
++ if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0)
++ goto out_reject;
++ spin_lock_irq(&pha->hardware_lock);
++ q24_send_notify_ack(ha, ntfy,
++ NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0);
++ spin_unlock_irq(&pha->hardware_lock);
++ if (xmit_type & Q2T_XMIT_DATA)
++ __q2t_rdy_to_xfer(cmd);
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): SRR for out data for cmd "
++ "without them (tag %d, SCSI status %d), "
++ "reject", ha->instance, cmd->tag,
++ scst_cmd_get_status(cmd->scst_cmd));
++ goto out_reject;
++ }
++ break;
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Unknown srr_ui value %x",
++ ha->instance, ntfy->srr_ui);
++ goto out_reject;
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_reject:
++ spin_lock_irq(&pha->hardware_lock);
++ q24_send_notify_ack(ha, ntfy, NOTIFY_ACK_SRR_FLAGS_REJECT,
++ NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM,
++ NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL);
++ if (cmd->state == Q2T_STATE_NEED_DATA) {
++ cmd->state = Q2T_STATE_DATA_IN;
++ scst_rx_data(cmd->scst_cmd, SCST_RX_STATUS_ERROR,
++ SCST_CONTEXT_THREAD);
++ } else
++ q24_send_term_exchange(ha, cmd, &cmd->atio.atio7, 1);
++ spin_unlock_irq(&pha->hardware_lock);
++ goto out;
++}
++
++/* No locks, thread context */
++static void q2x_handle_srr(scsi_qla_host_t *ha, struct srr_ctio *sctio,
++ struct srr_imm *imm)
++{
++ notify_entry_t *ntfy = &imm->imm.notify_entry;
++ struct q2t_cmd *cmd = sctio->cmd;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("SRR cmd %p, srr_ui %x", cmd, ntfy->srr_ui);
++
++ switch (ntfy->srr_ui) {
++ case SRR_IU_STATUS:
++ spin_lock_irq(&pha->hardware_lock);
++ q2x_send_notify_ack(ha, ntfy, 0, 0, 0,
++ NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0);
++ spin_unlock_irq(&pha->hardware_lock);
++ __q2x_xmit_response(cmd, Q2T_XMIT_STATUS);
++ break;
++ case SRR_IU_DATA_IN:
++ cmd->bufflen = scst_cmd_get_adjusted_resp_data_len(cmd->scst_cmd);
++ if (q2t_has_data(cmd)) {
++ uint32_t offset;
++ int xmit_type;
++ offset = le32_to_cpu(imm->imm.notify_entry.srr_rel_offs);
++ if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0)
++ goto out_reject;
++ spin_lock_irq(&pha->hardware_lock);
++ q2x_send_notify_ack(ha, ntfy, 0, 0, 0,
++ NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0);
++ spin_unlock_irq(&pha->hardware_lock);
++ __q2x_xmit_response(cmd, xmit_type);
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): SRR for in data for cmd "
++ "without them (tag %d, SCSI status %d), "
++ "reject", ha->instance, cmd->tag,
++ scst_cmd_get_status(cmd->scst_cmd));
++ goto out_reject;
++ }
++ break;
++ case SRR_IU_DATA_OUT:
++ cmd->bufflen = scst_cmd_get_write_fields(cmd->scst_cmd,
++ &cmd->sg, &cmd->sg_cnt);
++ if (q2t_has_data(cmd)) {
++ uint32_t offset;
++ int xmit_type;
++ offset = le32_to_cpu(imm->imm.notify_entry.srr_rel_offs);
++ if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0)
++ goto out_reject;
++ spin_lock_irq(&pha->hardware_lock);
++ q2x_send_notify_ack(ha, ntfy, 0, 0, 0,
++ NOTIFY_ACK_SRR_FLAGS_ACCEPT, 0, 0);
++ spin_unlock_irq(&pha->hardware_lock);
++ if (xmit_type & Q2T_XMIT_DATA)
++ __q2t_rdy_to_xfer(cmd);
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): SRR for out data for cmd "
++ "without them (tag %d, SCSI status %d), "
++ "reject", ha->instance, cmd->tag,
++ scst_cmd_get_status(cmd->scst_cmd));
++ goto out_reject;
++ }
++ break;
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Unknown srr_ui value %x",
++ ha->instance, ntfy->srr_ui);
++ goto out_reject;
++ }
++
++out:
++ TRACE_EXIT();
++ return;
++
++out_reject:
++ spin_lock_irq(&pha->hardware_lock);
++ q2x_send_notify_ack(ha, ntfy, 0, 0, 0, NOTIFY_ACK_SRR_FLAGS_REJECT,
++ NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM,
++ NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL);
++ if (cmd->state == Q2T_STATE_NEED_DATA) {
++ cmd->state = Q2T_STATE_DATA_IN;
++ scst_rx_data(cmd->scst_cmd, SCST_RX_STATUS_ERROR,
++ SCST_CONTEXT_THREAD);
++ } else
++ q2x_send_term_exchange(ha, cmd, &cmd->atio.atio2x, 1);
++ spin_unlock_irq(&pha->hardware_lock);
++ goto out;
++}
++
++static void q2t_reject_free_srr_imm(scsi_qla_host_t *ha, struct srr_imm *imm,
++ int ha_locked)
++{
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ if (!ha_locked)
++ spin_lock_irq(&pha->hardware_lock);
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ q24_send_notify_ack(ha, &imm->imm.notify_entry24,
++ NOTIFY_ACK_SRR_FLAGS_REJECT,
++ NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM,
++ NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL);
++ } else {
++ q2x_send_notify_ack(ha, &imm->imm.notify_entry,
++ 0, 0, 0, NOTIFY_ACK_SRR_FLAGS_REJECT,
++ NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM,
++ NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL);
++ }
++
++ if (!ha_locked)
++ spin_unlock_irq(&pha->hardware_lock);
++
++ kfree(imm);
++ return;
++}
++
++static void q2t_handle_srr_work(struct work_struct *work)
++{
++ struct q2t_tgt *tgt = container_of(work, struct q2t_tgt, srr_work);
++ scsi_qla_host_t *ha = tgt->ha;
++ struct srr_ctio *sctio;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("SRR work (tgt %p)", tgt);
++
++restart:
++ spin_lock_irq(&tgt->srr_lock);
++ list_for_each_entry(sctio, &tgt->srr_ctio_list, srr_list_entry) {
++ struct srr_imm *imm;
++ struct q2t_cmd *cmd;
++ struct srr_imm *i, *ti;
++
++ imm = NULL;
++ list_for_each_entry_safe(i, ti, &tgt->srr_imm_list,
++ srr_list_entry) {
++ if (i->srr_id == sctio->srr_id) {
++ list_del(&i->srr_list_entry);
++ if (imm) {
++ PRINT_ERROR("qla2x00t(%ld): There must "
++ "be only one IMM SRR per CTIO SRR "
++ "(IMM SRR %p, id %d, CTIO %p",
++ ha->instance, i, i->srr_id, sctio);
++ q2t_reject_free_srr_imm(ha, i, 0);
++ } else
++ imm = i;
++ }
++ }
++
++ TRACE_MGMT_DBG("IMM SRR %p, CTIO SRR %p (id %d)", imm, sctio,
++ sctio->srr_id);
++
++ if (imm == NULL) {
++ TRACE_MGMT_DBG("Not found matching IMM for SRR CTIO "
++ "(id %d)", sctio->srr_id);
++ continue;
++ } else
++ list_del(&sctio->srr_list_entry);
++
++ spin_unlock_irq(&tgt->srr_lock);
++
++ cmd = sctio->cmd;
++
++ /* Restore the originals, except bufflen */
++ cmd->offset = scst_cmd_get_ppl_offset(cmd->scst_cmd);
++ if (cmd->free_sg) {
++ kfree(cmd->sg);
++ cmd->free_sg = 0;
++ }
++ cmd->sg = scst_cmd_get_sg(cmd->scst_cmd);
++ cmd->sg_cnt = scst_cmd_get_sg_cnt(cmd->scst_cmd);
++
++ TRACE_MGMT_DBG("SRR cmd %p (scst_cmd %p, tag %d, op %x), "
++ "sg_cnt=%d, offset=%d", cmd, cmd->scst_cmd,
++ cmd->tag, cmd->scst_cmd->cdb[0], cmd->sg_cnt,
++ cmd->offset);
++
++ if (IS_FWI2_CAPABLE(ha))
++ q24_handle_srr(ha, sctio, imm);
++ else
++ q2x_handle_srr(ha, sctio, imm);
++
++ kfree(imm);
++ kfree(sctio);
++ goto restart;
++ }
++ spin_unlock_irq(&tgt->srr_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++static void q2t_prepare_srr_imm(scsi_qla_host_t *ha, void *iocb)
++{
++ struct srr_imm *imm;
++ struct q2t_tgt *tgt = ha->tgt;
++ notify_entry_t *iocb2x = (notify_entry_t *)iocb;
++ notify24xx_entry_t *iocb24 = (notify24xx_entry_t *)iocb;
++ struct srr_ctio *sctio;
++
++ tgt->imm_srr_id++;
++
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): SRR received", ha->instance);
++
++ imm = kzalloc(sizeof(*imm), GFP_ATOMIC);
++ if (imm != NULL) {
++ memcpy(&imm->imm.notify_entry, iocb,
++ sizeof(imm->imm.notify_entry));
++
++ /* IRQ is already OFF */
++ spin_lock(&tgt->srr_lock);
++ imm->srr_id = tgt->imm_srr_id;
++ list_add_tail(&imm->srr_list_entry,
++ &tgt->srr_imm_list);
++ TRACE_MGMT_DBG("IMM NTFY SRR %p added (id %d, ui %x)", imm,
++ imm->srr_id, iocb24->srr_ui);
++ if (tgt->imm_srr_id == tgt->ctio_srr_id) {
++ int found = 0;
++ list_for_each_entry(sctio, &tgt->srr_ctio_list,
++ srr_list_entry) {
++ if (sctio->srr_id == imm->srr_id) {
++ found = 1;
++ break;
++ }
++ }
++ if (found) {
++ TRACE_MGMT_DBG("%s", "Scheduling srr work");
++ schedule_work(&tgt->srr_work);
++ } else {
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): imm_srr_id "
++ "== ctio_srr_id (%d), but there is no "
++ "corresponding SRR CTIO, deleting IMM "
++ "SRR %p", ha->instance, tgt->ctio_srr_id,
++ imm);
++ list_del(&imm->srr_list_entry);
++
++ kfree(imm);
++
++ spin_unlock(&tgt->srr_lock);
++ goto out_reject;
++ }
++ }
++ spin_unlock(&tgt->srr_lock);
++ } else {
++ struct srr_ctio *ts;
++
++ PRINT_ERROR("qla2x00t(%ld): Unable to allocate SRR IMM "
++ "entry, SRR request will be rejected", ha->instance);
++
++ /* IRQ is already OFF */
++ spin_lock(&tgt->srr_lock);
++ list_for_each_entry_safe(sctio, ts, &tgt->srr_ctio_list,
++ srr_list_entry) {
++ if (sctio->srr_id == tgt->imm_srr_id) {
++ TRACE_MGMT_DBG("CTIO SRR %p deleted "
++ "(id %d)", sctio, sctio->srr_id);
++ list_del(&sctio->srr_list_entry);
++ if (IS_FWI2_CAPABLE(ha)) {
++ q24_send_term_exchange(ha, sctio->cmd,
++ &sctio->cmd->atio.atio7, 1);
++ } else {
++ q2x_send_term_exchange(ha, sctio->cmd,
++ &sctio->cmd->atio.atio2x, 1);
++ }
++ kfree(sctio);
++ }
++ }
++ spin_unlock(&tgt->srr_lock);
++ goto out_reject;
++ }
++
++out:
++ return;
++
++out_reject:
++ if (IS_FWI2_CAPABLE(ha)) {
++ q24_send_notify_ack(ha, iocb24,
++ NOTIFY_ACK_SRR_FLAGS_REJECT,
++ NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM,
++ NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL);
++ } else {
++ q2x_send_notify_ack(ha, iocb2x,
++ 0, 0, 0, NOTIFY_ACK_SRR_FLAGS_REJECT,
++ NOTIFY_ACK_SRR_REJECT_REASON_UNABLE_TO_PERFORM,
++ NOTIFY_ACK_SRR_FLAGS_REJECT_EXPL_NO_EXPL);
++ }
++ goto out;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q2t_handle_imm_notify(scsi_qla_host_t *ha, void *iocb)
++{
++ uint16_t status;
++ uint32_t add_flags = 0;
++ int send_notify_ack = 1;
++ notify_entry_t *iocb2x = (notify_entry_t *)iocb;
++ notify24xx_entry_t *iocb24 = (notify24xx_entry_t *)iocb;
++
++ TRACE_ENTRY();
++
++ status = le16_to_cpu(iocb2x->status);
++
++ TRACE_BUFF_FLAG(TRACE_BUFF, "IMMED Notify Coming Up",
++ iocb, sizeof(*iocb2x));
++
++ switch (status) {
++ case IMM_NTFY_LIP_RESET:
++ {
++ if (IS_FWI2_CAPABLE(ha)) {
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): LIP reset (loop %#x), "
++ "subcode %x", ha->instance,
++ le16_to_cpu(iocb24->nport_handle),
++ iocb24->status_subcode);
++ } else {
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): LIP reset (I %#x)",
++ ha->instance, GET_TARGET_ID(ha, iocb2x));
++ /* set the Clear LIP reset event flag */
++ add_flags |= NOTIFY_ACK_CLEAR_LIP_RESET;
++ }
++ /*
++ * No additional resets or aborts are needed, because firmware
++ * will as required by FCP either generate TARGET RESET or
++ * reject all affected commands with LIP_RESET status.
++ */
++ break;
++ }
++
++ case IMM_NTFY_LIP_LINK_REINIT:
++ {
++ struct q2t_tgt *tgt = ha->tgt;
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): LINK REINIT (loop %#x, "
++ "subcode %x)", ha->instance,
++ le16_to_cpu(iocb24->nport_handle),
++ iocb24->status_subcode);
++ if (tgt->link_reinit_iocb_pending)
++ q24_send_notify_ack(ha, &tgt->link_reinit_iocb, 0, 0, 0);
++ memcpy(&tgt->link_reinit_iocb, iocb24, sizeof(*iocb24));
++ tgt->link_reinit_iocb_pending = 1;
++ /*
++ * QLogic requires to wait after LINK REINIT for possible
++ * PDISC or ADISC ELS commands
++ */
++ send_notify_ack = 0;
++ break;
++ }
++
++ case IMM_NTFY_PORT_LOGOUT:
++ if (IS_FWI2_CAPABLE(ha)) {
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Port logout (loop "
++ "%#x, subcode %x)", ha->instance,
++ le16_to_cpu(iocb24->nport_handle),
++ iocb24->status_subcode);
++ } else {
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Port logout (S "
++ "%08x -> L %#x)", ha->instance,
++ le16_to_cpu(iocb2x->seq_id),
++ le16_to_cpu(iocb2x->lun));
++ }
++ if (q2t_reset(ha, iocb, Q2T_NEXUS_LOSS_SESS) == 0)
++ send_notify_ack = 0;
++ /* The sessions will be cleared in the callback, if needed */
++ break;
++
++ case IMM_NTFY_GLBL_TPRLO:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Global TPRLO (%x)",
++ ha->instance, status);
++ if (q2t_reset(ha, iocb, Q2T_NEXUS_LOSS) == 0)
++ send_notify_ack = 0;
++ /* The sessions will be cleared in the callback, if needed */
++ break;
++
++ case IMM_NTFY_PORT_CONFIG:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Port config changed (%x)",
++ ha->instance, status);
++ break;
++
++ case IMM_NTFY_GLBL_LOGO:
++ PRINT_WARNING("qla2x00t(%ld): Link failure detected",
++ ha->instance);
++ /* I_T nexus loss */
++ if (q2t_reset(ha, iocb, Q2T_NEXUS_LOSS) == 0)
++ send_notify_ack = 0;
++ break;
++
++ case IMM_NTFY_IOCB_OVERFLOW:
++ PRINT_ERROR("qla2x00t(%ld): Cannot provide requested "
++ "capability (IOCB overflowed the immediate notify "
++ "resource count)", ha->instance);
++ break;
++
++ case IMM_NTFY_ABORT_TASK:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Abort Task (S %08x I %#x -> "
++ "L %#x)", ha->instance, le16_to_cpu(iocb2x->seq_id),
++ GET_TARGET_ID(ha, iocb2x), le16_to_cpu(iocb2x->lun));
++ if (q2t_abort_task(ha, iocb2x) == 0)
++ send_notify_ack = 0;
++ break;
++
++ case IMM_NTFY_RESOURCE:
++ PRINT_ERROR("qla2x00t(%ld): Out of resources, host %ld",
++ ha->instance, ha->host_no);
++ break;
++
++ case IMM_NTFY_MSG_RX:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Immediate notify task %x",
++ ha->instance, iocb2x->task_flags);
++ if (q2t_handle_task_mgmt(ha, iocb2x) == 0)
++ send_notify_ack = 0;
++ break;
++
++ case IMM_NTFY_ELS:
++ if (q24_handle_els(ha, iocb24) == 0)
++ send_notify_ack = 0;
++ break;
++
++ case IMM_NTFY_SRR:
++ q2t_prepare_srr_imm(ha, iocb);
++ send_notify_ack = 0;
++ break;
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Received unknown immediate "
++ "notify status %x", ha->instance, status);
++ break;
++ }
++
++ if (send_notify_ack) {
++ if (IS_FWI2_CAPABLE(ha))
++ q24_send_notify_ack(ha, iocb24, 0, 0, 0);
++ else
++ q2x_send_notify_ack(ha, iocb2x, add_flags, 0, 0, 0,
++ 0, 0);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q2x_send_busy(scsi_qla_host_t *ha, atio_entry_t *atio)
++{
++ ctio_ret_entry_t *ctio;
++
++ TRACE_ENTRY();
++
++ /* Sending marker isn't necessary, since we called from ISR */
++
++ ctio = (ctio_ret_entry_t *)q2t_req_pkt(ha);
++ if (ctio == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out;
++ }
++
++ ctio->entry_type = CTIO_RET_TYPE;
++ ctio->entry_count = 1;
++ ctio->handle = Q2T_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK;
++ ctio->scsi_status = __constant_cpu_to_le16(SAM_STAT_BUSY);
++ ctio->residual = atio->data_length;
++ if (ctio->residual != 0)
++ ctio->scsi_status |= SS_RESIDUAL_UNDER;
++
++ /* Set IDs */
++ SET_TARGET_ID(ha, ctio->target, GET_TARGET_ID(ha, atio));
++ ctio->rx_id = atio->rx_id;
++
++ ctio->flags = __constant_cpu_to_le16(OF_SSTS | OF_FAST_POST |
++ OF_NO_DATA | OF_SS_MODE_1);
++ ctio->flags |= __constant_cpu_to_le16(OF_INC_RC);
++ /*
++ * CTIO from fw w/o scst_cmd doesn't provide enough info to retry it,
++ * if the explicit conformation is used.
++ */
++
++ TRACE_BUFFER("CTIO BUSY packet data", ctio, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q24_send_busy(scsi_qla_host_t *ha, atio7_entry_t *atio,
++ uint16_t status)
++{
++ ctio7_status1_entry_t *ctio;
++ struct q2t_sess *sess;
++ uint16_t loop_id;
++
++ TRACE_ENTRY();
++
++ /*
++ * In some cases, for instance for ATIO_EXCHANGE_ADDRESS_UNKNOWN, the
++ * spec requires to issue queue full SCSI status. So, let's search among
++ * being deleted sessions as well and use CTIO7_NHANDLE_UNRECOGNIZED,
++ * if we can't find sess.
++ */
++ sess = q2t_find_sess_by_s_id_include_deleted(ha->tgt,
++ atio->fcp_hdr.s_id);
++ if (sess != NULL)
++ loop_id = sess->loop_id;
++ else
++ loop_id = CTIO7_NHANDLE_UNRECOGNIZED;
++
++ /* Sending marker isn't necessary, since we called from ISR */
++
++ ctio = (ctio7_status1_entry_t *)q2t_req_pkt(ha);
++ if (ctio == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): %s failed: unable to allocate "
++ "request packet", ha->instance, __func__);
++ goto out;
++ }
++
++ ctio->common.entry_type = CTIO_TYPE7;
++ ctio->common.entry_count = 1;
++ ctio->common.handle = Q2T_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK;
++ ctio->common.nport_handle = loop_id;
++ ctio->common.timeout = __constant_cpu_to_le16(Q2T_TIMEOUT);
++ ctio->common.vp_index = ha->vp_idx;
++ ctio->common.initiator_id[0] = atio->fcp_hdr.s_id[2];
++ ctio->common.initiator_id[1] = atio->fcp_hdr.s_id[1];
++ ctio->common.initiator_id[2] = atio->fcp_hdr.s_id[0];
++ ctio->common.exchange_addr = atio->exchange_addr;
++ ctio->flags = (atio->attr << 9) | __constant_cpu_to_le16(
++ CTIO7_FLAGS_STATUS_MODE_1 | CTIO7_FLAGS_SEND_STATUS |
++ CTIO7_FLAGS_DONT_RET_CTIO);
++ /*
++ * CTIO from fw w/o scst_cmd doesn't provide enough info to retry it,
++ * if the explicit conformation is used.
++ */
++ ctio->ox_id = swab16(atio->fcp_hdr.ox_id);
++ ctio->scsi_status = cpu_to_le16(status);
++ ctio->residual = get_unaligned((uint32_t *)
++ &atio->fcp_cmnd.add_cdb[atio->fcp_cmnd.add_cdb_len]);
++ if (ctio->residual != 0)
++ ctio->scsi_status |= SS_RESIDUAL_UNDER;
++
++ TRACE_BUFFER("CTIO7 BUSY packet data", ctio, REQUEST_ENTRY_SIZE);
++
++ q2t_exec_queue(ha);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++/* called via callback from qla2xxx */
++static void q24_atio_pkt(scsi_qla_host_t *ha, atio7_entry_t *atio)
++{
++ int rc;
++ struct q2t_tgt *tgt = ha->tgt;
++
++ TRACE_ENTRY();
++
++ if (unlikely(tgt == NULL)) {
++ TRACE_MGMT_DBG("ATIO pkt, but no tgt (ha %p)", ha);
++ goto out;
++ }
++
++ TRACE(TRACE_SCSI, "qla2x00t(%ld): ATIO pkt %p: type %02x count %02x",
++ ha->instance, atio, atio->entry_type, atio->entry_count);
++
++ /*
++ * In tgt_stop mode we also should allow all requests to pass.
++ * Otherwise, some commands can stuck.
++ */
++
++ tgt->irq_cmd_count++;
++
++ switch (atio->entry_type) {
++ case ATIO_TYPE7:
++ TRACE_DBG("ATIO_TYPE7 instance %ld, lun %Lx, read/write %d/%d, "
++ "add_cdb_len %d, data_length %04x, s_id %x:%x:%x",
++ ha->instance, atio->fcp_cmnd.lun, atio->fcp_cmnd.rddata,
++ atio->fcp_cmnd.wrdata, atio->fcp_cmnd.add_cdb_len,
++ be32_to_cpu(get_unaligned((uint32_t *)
++ &atio->fcp_cmnd.add_cdb[atio->fcp_cmnd.add_cdb_len])),
++ atio->fcp_hdr.s_id[0], atio->fcp_hdr.s_id[1],
++ atio->fcp_hdr.s_id[2]);
++ TRACE_BUFFER("Incoming ATIO7 packet data", atio,
++ REQUEST_ENTRY_SIZE);
++ PRINT_BUFF_FLAG(TRACE_SCSI, "FCP CDB", atio->fcp_cmnd.cdb,
++ sizeof(atio->fcp_cmnd.cdb));
++ if (unlikely(atio->exchange_addr ==
++ ATIO_EXCHANGE_ADDRESS_UNKNOWN)) {
++ TRACE(TRACE_OUT_OF_MEM, "qla2x00t(%ld): ATIO_TYPE7 "
++ "received with UNKNOWN exchange address, "
++ "sending QUEUE_FULL", ha->instance);
++ q24_send_busy(ha, atio, SAM_STAT_TASK_SET_FULL);
++ break;
++ }
++ if (likely(atio->fcp_cmnd.task_mgmt_flags == 0))
++ rc = q2t_send_cmd_to_scst(ha, (atio_t *)atio);
++ else
++ rc = q2t_handle_task_mgmt(ha, atio);
++ if (unlikely(rc != 0)) {
++ if (rc == -ESRCH) {
++#if 1 /* With TERM EXCHANGE some FC cards refuse to boot */
++ q24_send_busy(ha, atio, SAM_STAT_BUSY);
++#else
++ q24_send_term_exchange(ha, NULL, atio, 1);
++#endif
++ } else {
++ PRINT_INFO("qla2x00t(%ld): Unable to send "
++ "command to SCST, sending BUSY status",
++ ha->instance);
++ q24_send_busy(ha, atio, SAM_STAT_BUSY);
++ }
++ }
++ break;
++
++ case IMMED_NOTIFY_TYPE:
++ {
++ notify_entry_t *pkt = (notify_entry_t *)atio;
++ if (unlikely(pkt->entry_status != 0)) {
++ PRINT_ERROR("qla2x00t(%ld): Received ATIO packet %x "
++ "with error status %x", ha->instance,
++ pkt->entry_type, pkt->entry_status);
++ break;
++ }
++ TRACE_DBG("%s", "IMMED_NOTIFY ATIO");
++ q2t_handle_imm_notify(ha, pkt);
++ break;
++ }
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Received unknown ATIO atio "
++ "type %x", ha->instance, atio->entry_type);
++ break;
++ }
++
++ tgt->irq_cmd_count--;
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held on entry */
++/* called via callback from qla2xxx */
++static void q2t_response_pkt(scsi_qla_host_t *ha, response_t *pkt)
++{
++ struct q2t_tgt *tgt = ha->tgt;
++
++ TRACE_ENTRY();
++
++ if (unlikely(tgt == NULL)) {
++ PRINT_ERROR("qla2x00t(%ld): Response pkt %x received, but no "
++ "tgt (ha %p)", ha->instance, pkt->entry_type, ha);
++ goto out;
++ }
++
++ TRACE(TRACE_SCSI, "qla2x00t(%ld): pkt %p: T %02x C %02x S %02x "
++ "handle %#x", ha->instance, pkt, pkt->entry_type,
++ pkt->entry_count, pkt->entry_status, pkt->handle);
++
++ /*
++ * In tgt_stop mode we also should allow all requests to pass.
++ * Otherwise, some commands can stuck.
++ */
++
++ if (unlikely(pkt->entry_status != 0)) {
++ PRINT_ERROR("qla2x00t(%ld): Received response packet %x "
++ "with error status %x", ha->instance, pkt->entry_type,
++ pkt->entry_status);
++ switch (pkt->entry_type) {
++ case ACCEPT_TGT_IO_TYPE:
++ case IMMED_NOTIFY_TYPE:
++ case ABTS_RECV_24XX:
++ goto out;
++ default:
++ break;
++ }
++ }
++
++ tgt->irq_cmd_count++;
++
++ switch (pkt->entry_type) {
++ case CTIO_TYPE7:
++ {
++ ctio7_fw_entry_t *entry = (ctio7_fw_entry_t *)pkt;
++ TRACE_DBG("CTIO_TYPE7: instance %ld",
++ ha->instance);
++ TRACE_BUFFER("Incoming CTIO7 packet data", entry,
++ REQUEST_ENTRY_SIZE);
++ q2t_do_ctio_completion(ha, entry->handle,
++ le16_to_cpu(entry->status)|(pkt->entry_status << 16),
++ entry);
++ break;
++ }
++
++ case ACCEPT_TGT_IO_TYPE:
++ {
++ atio_entry_t *atio;
++ int rc;
++ atio = (atio_entry_t *)pkt;
++ TRACE_DBG("ACCEPT_TGT_IO instance %ld status %04x "
++ "lun %04x read/write %d data_length %04x "
++ "target_id %02x rx_id %04x ",
++ ha->instance, le16_to_cpu(atio->status),
++ le16_to_cpu(atio->lun),
++ atio->execution_codes,
++ le32_to_cpu(atio->data_length),
++ GET_TARGET_ID(ha, atio), atio->rx_id);
++ TRACE_BUFFER("Incoming ATIO packet data", atio,
++ REQUEST_ENTRY_SIZE);
++ if (atio->status != __constant_cpu_to_le16(ATIO_CDB_VALID)) {
++ PRINT_ERROR("qla2x00t(%ld): ATIO with error "
++ "status %x received", ha->instance,
++ le16_to_cpu(atio->status));
++ break;
++ }
++ TRACE_BUFFER("Incoming ATIO packet data", atio, REQUEST_ENTRY_SIZE);
++ PRINT_BUFF_FLAG(TRACE_SCSI, "FCP CDB", atio->cdb,
++ sizeof(atio->cdb));
++ rc = q2t_send_cmd_to_scst(ha, (atio_t *)atio);
++ if (unlikely(rc != 0)) {
++ if (rc == -ESRCH) {
++#if 1 /* With TERM EXCHANGE some FC cards refuse to boot */
++ q2x_send_busy(ha, atio);
++#else
++ q2x_send_term_exchange(ha, NULL, atio, 1);
++#endif
++ } else {
++ PRINT_INFO("qla2x00t(%ld): Unable to send "
++ "command to SCST, sending BUSY status",
++ ha->instance);
++ q2x_send_busy(ha, atio);
++ }
++ }
++ }
++ break;
++
++ case CONTINUE_TGT_IO_TYPE:
++ {
++ ctio_common_entry_t *entry = (ctio_common_entry_t *)pkt;
++ TRACE_DBG("CONTINUE_TGT_IO: instance %ld", ha->instance);
++ TRACE_BUFFER("Incoming CTIO packet data", entry,
++ REQUEST_ENTRY_SIZE);
++ q2t_do_ctio_completion(ha, entry->handle,
++ le16_to_cpu(entry->status)|(pkt->entry_status << 16),
++ entry);
++ break;
++ }
++
++ case CTIO_A64_TYPE:
++ {
++ ctio_common_entry_t *entry = (ctio_common_entry_t *)pkt;
++ TRACE_DBG("CTIO_A64: instance %ld", ha->instance);
++ TRACE_BUFFER("Incoming CTIO_A64 packet data", entry,
++ REQUEST_ENTRY_SIZE);
++ q2t_do_ctio_completion(ha, entry->handle,
++ le16_to_cpu(entry->status)|(pkt->entry_status << 16),
++ entry);
++ break;
++ }
++
++ case IMMED_NOTIFY_TYPE:
++ TRACE_DBG("%s", "IMMED_NOTIFY");
++ q2t_handle_imm_notify(ha, (notify_entry_t *)pkt);
++ break;
++
++ case NOTIFY_ACK_TYPE:
++ if (tgt->notify_ack_expected > 0) {
++ nack_entry_t *entry = (nack_entry_t *)pkt;
++ TRACE_DBG("NOTIFY_ACK seq %08x status %x",
++ le16_to_cpu(entry->seq_id),
++ le16_to_cpu(entry->status));
++ TRACE_BUFFER("Incoming NOTIFY_ACK packet data", pkt,
++ RESPONSE_ENTRY_SIZE);
++ tgt->notify_ack_expected--;
++ if (entry->status != __constant_cpu_to_le16(NOTIFY_ACK_SUCCESS)) {
++ PRINT_ERROR("qla2x00t(%ld): NOTIFY_ACK "
++ "failed %x", ha->instance,
++ le16_to_cpu(entry->status));
++ }
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): Unexpected NOTIFY_ACK "
++ "received", ha->instance);
++ }
++ break;
++
++ case ABTS_RECV_24XX:
++ TRACE_DBG("ABTS_RECV_24XX: instance %ld", ha->instance);
++ TRACE_BUFF_FLAG(TRACE_BUFF, "Incoming ABTS_RECV "
++ "packet data", pkt, REQUEST_ENTRY_SIZE);
++ q24_handle_abts(ha, (abts24_recv_entry_t *)pkt);
++ break;
++
++ case ABTS_RESP_24XX:
++ if (tgt->abts_resp_expected > 0) {
++ abts24_resp_fw_entry_t *entry =
++ (abts24_resp_fw_entry_t *)pkt;
++ TRACE_DBG("ABTS_RESP_24XX: compl_status %x",
++ entry->compl_status);
++ TRACE_BUFF_FLAG(TRACE_BUFF, "Incoming ABTS_RESP "
++ "packet data", pkt, REQUEST_ENTRY_SIZE);
++ tgt->abts_resp_expected--;
++ if (le16_to_cpu(entry->compl_status) != ABTS_RESP_COMPL_SUCCESS) {
++ if ((entry->error_subcode1 == 0x1E) &&
++ (entry->error_subcode2 == 0)) {
++ /*
++ * We've got a race here: aborted exchange not
++ * terminated, i.e. response for the aborted
++ * command was sent between the abort request
++ * was received and processed. Unfortunately,
++ * the firmware has a silly requirement that
++ * all aborted exchanges must be explicitely
++ * terminated, otherwise it refuses to send
++ * responses for the abort requests. So, we
++ * have to (re)terminate the exchange and
++ * retry the abort response.
++ */
++ q24_retry_term_exchange(ha, entry);
++ } else
++ PRINT_ERROR("qla2x00t(%ld): ABTS_RESP_24XX "
++ "failed %x (subcode %x:%x)", ha->instance,
++ entry->compl_status, entry->error_subcode1,
++ entry->error_subcode2);
++ }
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): Unexpected ABTS_RESP_24XX "
++ "received", ha->instance);
++ }
++ break;
++
++ case MODIFY_LUN_TYPE:
++ if (tgt->modify_lun_expected > 0) {
++ modify_lun_entry_t *entry = (modify_lun_entry_t *)pkt;
++ TRACE_DBG("MODIFY_LUN %x, imm %c%d, cmd %c%d",
++ entry->status,
++ (entry->operators & MODIFY_LUN_IMM_ADD) ? '+'
++ : (entry->operators & MODIFY_LUN_IMM_SUB) ? '-'
++ : ' ',
++ entry->immed_notify_count,
++ (entry->operators & MODIFY_LUN_CMD_ADD) ? '+'
++ : (entry->operators & MODIFY_LUN_CMD_SUB) ? '-'
++ : ' ',
++ entry->command_count);
++ tgt->modify_lun_expected--;
++ if (entry->status != MODIFY_LUN_SUCCESS) {
++ PRINT_ERROR("qla2x00t(%ld): MODIFY_LUN "
++ "failed %x", ha->instance,
++ entry->status);
++ }
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): Unexpected MODIFY_LUN "
++ "received", (ha != NULL) ? (long)ha->instance : -1);
++ }
++ break;
++
++ case ENABLE_LUN_TYPE:
++ {
++ elun_entry_t *entry = (elun_entry_t *)pkt;
++ TRACE_DBG("ENABLE_LUN %x imm %u cmd %u ",
++ entry->status, entry->immed_notify_count,
++ entry->command_count);
++ if (entry->status == ENABLE_LUN_ALREADY_ENABLED) {
++ TRACE_DBG("LUN is already enabled: %#x",
++ entry->status);
++ entry->status = ENABLE_LUN_SUCCESS;
++ } else if (entry->status == ENABLE_LUN_RC_NONZERO) {
++ TRACE_DBG("ENABLE_LUN succeeded, but with "
++ "error: %#x", entry->status);
++ entry->status = ENABLE_LUN_SUCCESS;
++ } else if (entry->status != ENABLE_LUN_SUCCESS) {
++ PRINT_ERROR("qla2x00t(%ld): ENABLE_LUN "
++ "failed %x", ha->instance, entry->status);
++ qla_clear_tgt_mode(ha);
++ } /* else success */
++ break;
++ }
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): Received unknown response pkt "
++ "type %x", ha->instance, pkt->entry_type);
++ break;
++ }
++
++ tgt->irq_cmd_count--;
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * pha->hardware_lock supposed to be held on entry. Might drop it, then reacquire
++ */
++static void q2t_async_event(uint16_t code, scsi_qla_host_t *ha,
++ uint16_t *mailbox)
++{
++ struct q2t_tgt *tgt = ha->tgt;
++
++ TRACE_ENTRY();
++
++ if (unlikely(tgt == NULL)) {
++ TRACE_DBG("ASYNC EVENT %#x, but no tgt (ha %p)", code, ha);
++ goto out;
++ }
++
++ /*
++ * In tgt_stop mode we also should allow all requests to pass.
++ * Otherwise, some commands can stuck.
++ */
++
++ tgt->irq_cmd_count++;
++
++ switch (code) {
++ case MBA_RESET: /* Reset */
++ case MBA_SYSTEM_ERR: /* System Error */
++ case MBA_REQ_TRANSFER_ERR: /* Request Transfer Error */
++ case MBA_RSP_TRANSFER_ERR: /* Response Transfer Error */
++ case MBA_ATIO_TRANSFER_ERR: /* ATIO Queue Transfer Error */
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): System error async event %#x "
++ "occured", ha->instance, code);
++ break;
++
++ case MBA_LOOP_UP:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Loop up occured",
++ ha->instance);
++ if (tgt->link_reinit_iocb_pending) {
++ q24_send_notify_ack(ha, &tgt->link_reinit_iocb, 0, 0, 0);
++ tgt->link_reinit_iocb_pending = 0;
++ }
++ break;
++
++ case MBA_LIP_OCCURRED:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): LIP occured", ha->instance);
++ break;
++
++ case MBA_LOOP_DOWN:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Loop down occured",
++ ha->instance);
++ break;
++
++ case MBA_LIP_RESET:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): LIP reset occured",
++ ha->instance);
++ break;
++
++ case MBA_PORT_UPDATE:
++ case MBA_RSCN_UPDATE:
++ TRACE_MGMT_DBG("qla2x00t(%ld): Port update async event %#x "
++ "occured", ha->instance, code);
++ /* .mark_all_devices_lost() is handled by the initiator driver */
++ break;
++
++ default:
++ TRACE(TRACE_MGMT, "qla2x00t(%ld): Async event %#x occured: "
++ "ignoring (m[1]=%x, m[2]=%x, m[3]=%x, m[4]=%x)",
++ ha->instance, code,
++ le16_to_cpu(mailbox[1]), le16_to_cpu(mailbox[2]),
++ le16_to_cpu(mailbox[3]), le16_to_cpu(mailbox[4]));
++ break;
++ }
++
++ tgt->irq_cmd_count--;
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int q2t_get_target_name(uint8_t *wwn, char **ppwwn_name)
++{
++ const int wwn_len = 3*WWN_SIZE+2;
++ int res = 0;
++ char *name;
++
++ name = kmalloc(wwn_len, GFP_KERNEL);
++ if (name == NULL) {
++ PRINT_ERROR("qla2x00t: Allocation of tgt wwn name (size %d) "
++ "failed", wwn_len);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ sprintf(name, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
++ wwn[0], wwn[1], wwn[2], wwn[3],
++ wwn[4], wwn[5], wwn[6], wwn[7]);
++
++ *ppwwn_name = name;
++
++out:
++ return res;
++}
++
++/* Must be called under tgt_mutex */
++static struct q2t_sess *q2t_make_local_sess(scsi_qla_host_t *ha,
++ const uint8_t *s_id, uint16_t loop_id)
++{
++ struct q2t_sess *sess = NULL;
++ fc_port_t *fcport = NULL;
++ int rc, global_resets;
++
++ TRACE_ENTRY();
++
++retry:
++ global_resets = atomic_read(&ha->tgt->tgt_global_resets_count);
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ BUG_ON(s_id == NULL);
++
++ rc = q24_get_loop_id(ha, s_id, &loop_id);
++ if (rc != 0) {
++ if ((s_id[0] == 0xFF) &&
++ (s_id[1] == 0xFC)) {
++ /*
++ * This is Domain Controller, so it should be
++ * OK to drop SCSI commands from it.
++ */
++ TRACE_MGMT_DBG("Unable to find initiator with "
++ "S_ID %x:%x:%x", s_id[0], s_id[1],
++ s_id[2]);
++ } else
++ PRINT_ERROR("qla2x00t(%ld): Unable to find "
++ "initiator with S_ID %x:%x:%x",
++ ha->instance, s_id[0], s_id[1],
++ s_id[2]);
++ goto out;
++ }
++ }
++
++ fcport = kzalloc(sizeof(*fcport), GFP_KERNEL);
++ if (fcport == NULL) {
++ PRINT_ERROR("qla2x00t(%ld): Allocation of tmp FC port failed",
++ ha->instance);
++ goto out;
++ }
++
++ TRACE_MGMT_DBG("loop_id %d", loop_id);
++
++ fcport->loop_id = loop_id;
++
++ rc = qla2x00_get_port_database(ha, fcport, 0);
++ if (rc != QLA_SUCCESS) {
++ PRINT_ERROR("qla2x00t(%ld): Failed to retrieve fcport "
++ "information -- get_port_database() returned %x "
++ "(loop_id=0x%04x)", ha->instance, rc, loop_id);
++ goto out_free_fcport;
++ }
++
++ if (global_resets != atomic_read(&ha->tgt->tgt_global_resets_count)) {
++ TRACE_MGMT_DBG("qla2x00t(%ld): global reset during session "
++ "discovery (counter was %d, new %d), retrying",
++ ha->instance, global_resets,
++ atomic_read(&ha->tgt->tgt_global_resets_count));
++ kfree(fcport);
++ fcport = NULL;
++ goto retry;
++ }
++
++ sess = q2t_create_sess(ha, fcport, true);
++
++out_free_fcport:
++ kfree(fcport);
++
++out:
++ TRACE_EXIT_HRES((unsigned long)sess);
++ return sess;
++}
++
++static void q2t_exec_sess_work(struct q2t_tgt *tgt,
++ struct q2t_sess_work_param *prm)
++{
++ scsi_qla_host_t *ha = tgt->ha;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++ int rc;
++ struct q2t_sess *sess = NULL;
++ uint8_t *s_id = NULL; /* to hide compiler warnings */
++ uint8_t local_s_id[3];
++ int loop_id = -1; /* to hide compiler warnings */
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("prm %p", prm);
++
++ mutex_lock(&ha->tgt_mutex);
++ spin_lock_irq(&pha->hardware_lock);
++
++ if (tgt->tgt_stop)
++ goto send;
++
++ switch (prm->type) {
++ case Q2T_SESS_WORK_CMD:
++ {
++ struct q2t_cmd *cmd = prm->cmd;
++ if (IS_FWI2_CAPABLE(ha)) {
++ atio7_entry_t *a = (atio7_entry_t *)&cmd->atio;
++ s_id = a->fcp_hdr.s_id;
++ } else
++ loop_id = GET_TARGET_ID(ha, (atio_entry_t *)&cmd->atio);
++ break;
++ }
++ case Q2T_SESS_WORK_ABORT:
++ if (IS_FWI2_CAPABLE(ha)) {
++ sess = q2t_find_sess_by_s_id_le(tgt,
++ prm->abts.fcp_hdr_le.s_id);
++ if (sess == NULL) {
++ s_id = local_s_id;
++ s_id[0] = prm->abts.fcp_hdr_le.s_id[2];
++ s_id[1] = prm->abts.fcp_hdr_le.s_id[1];
++ s_id[2] = prm->abts.fcp_hdr_le.s_id[0];
++ }
++ goto after_find;
++ } else
++ loop_id = GET_TARGET_ID(ha, &prm->tm_iocb);
++ break;
++ case Q2T_SESS_WORK_TM:
++ if (IS_FWI2_CAPABLE(ha))
++ s_id = prm->tm_iocb2.fcp_hdr.s_id;
++ else
++ loop_id = GET_TARGET_ID(ha, &prm->tm_iocb);
++ break;
++ default:
++ BUG_ON(1);
++ break;
++ }
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ BUG_ON(s_id == NULL);
++ sess = q2t_find_sess_by_s_id(tgt, s_id);
++ } else
++ sess = q2t_find_sess_by_loop_id(tgt, loop_id);
++
++after_find:
++ if (sess != NULL) {
++ TRACE_MGMT_DBG("sess %p found", sess);
++ q2t_sess_get(sess);
++ } else {
++ /*
++ * We are under tgt_mutex, so a new sess can't be added
++ * behind us.
++ */
++ spin_unlock_irq(&pha->hardware_lock);
++ sess = q2t_make_local_sess(ha, s_id, loop_id);
++ spin_lock_irq(&pha->hardware_lock);
++ /* sess has got an extra creation ref */
++ }
++
++send:
++ if ((sess == NULL) || tgt->tgt_stop)
++ goto out_term;
++
++ switch (prm->type) {
++ case Q2T_SESS_WORK_CMD:
++ {
++ struct q2t_cmd *cmd = prm->cmd;
++ if (tgt->tm_to_unknown) {
++ /*
++ * Cmd might be already aborted behind us, so be safe
++ * and abort it. It should be OK, initiator will retry
++ * it.
++ */
++ goto out_term;
++ }
++ TRACE_MGMT_DBG("Sending work cmd %p to SCST", cmd);
++ rc = q2t_do_send_cmd_to_scst(ha, cmd, sess);
++ break;
++ }
++ case Q2T_SESS_WORK_ABORT:
++ if (IS_FWI2_CAPABLE(ha))
++ rc = __q24_handle_abts(ha, &prm->abts, sess);
++ else
++ rc = __q2t_abort_task(ha, &prm->tm_iocb, sess);
++ break;
++ case Q2T_SESS_WORK_TM:
++ {
++ uint8_t *lun;
++ uint16_t lun_data;
++ int lun_size, fn;
++ void *iocb;
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ atio7_entry_t *a = &prm->tm_iocb2;
++ iocb = a;
++ lun = (uint8_t *)&a->fcp_cmnd.lun;
++ lun_size = sizeof(a->fcp_cmnd.lun);
++ fn = a->fcp_cmnd.task_mgmt_flags;
++ } else {
++ notify_entry_t *n = &prm->tm_iocb;
++ iocb = n;
++ /* make it be in network byte order */
++ lun_data = swab16(le16_to_cpu(n->lun));
++ lun = (uint8_t *)&lun_data;
++ lun_size = sizeof(lun_data);
++ fn = n->task_flags >> IMM_NTFY_TASK_MGMT_SHIFT;
++ }
++ rc = q2t_issue_task_mgmt(sess, lun, lun_size, fn, iocb, 0);
++ break;
++ }
++ default:
++ BUG_ON(1);
++ break;
++ }
++
++ if (rc != 0)
++ goto out_term;
++
++out_put:
++ if (sess != NULL)
++ q2t_sess_put(sess);
++
++ spin_unlock_irq(&pha->hardware_lock);
++ mutex_unlock(&ha->tgt_mutex);
++
++ TRACE_EXIT();
++ return;
++
++out_term:
++ switch (prm->type) {
++ case Q2T_SESS_WORK_CMD:
++ {
++ struct q2t_cmd *cmd = prm->cmd;
++ TRACE_MGMT_DBG("Terminating work cmd %p", cmd);
++ /*
++ * cmd has not sent to SCST yet, so pass NULL as the second
++ * argument
++ */
++ if (IS_FWI2_CAPABLE(ha))
++ q24_send_term_exchange(ha, NULL, &cmd->atio.atio7, 1);
++ else
++ q2x_send_term_exchange(ha, NULL, &cmd->atio.atio2x, 1);
++ q2t_free_cmd(cmd);
++ break;
++ }
++ case Q2T_SESS_WORK_ABORT:
++ if (IS_FWI2_CAPABLE(ha))
++ q24_send_abts_resp(ha, &prm->abts,
++ SCST_MGMT_STATUS_REJECTED, false);
++ else
++ q2x_send_notify_ack(ha, &prm->tm_iocb, 0,
++ 0, 0, 0, 0, 0);
++ break;
++ case Q2T_SESS_WORK_TM:
++ if (IS_FWI2_CAPABLE(ha))
++ q24_send_term_exchange(ha, NULL, &prm->tm_iocb2, 1);
++ else
++ q2x_send_notify_ack(ha, &prm->tm_iocb, 0,
++ 0, 0, 0, 0, 0);
++ break;
++ default:
++ BUG_ON(1);
++ break;
++ }
++ goto out_put;
++}
++
++static void q2t_sess_work_fn(struct work_struct *work)
++{
++ struct q2t_tgt *tgt = container_of(work, struct q2t_tgt, sess_work);
++ scsi_qla_host_t *pha = to_qla_parent(tgt->ha);
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Sess work (tgt %p)", tgt);
++
++ spin_lock_irq(&tgt->sess_work_lock);
++ while (!list_empty(&tgt->sess_works_list)) {
++ struct q2t_sess_work_param *prm = list_entry(
++ tgt->sess_works_list.next, typeof(*prm),
++ sess_works_list_entry);
++
++ /*
++ * This work can be scheduled on several CPUs at time, so we
++ * must delete the entry to eliminate double processing
++ */
++ list_del(&prm->sess_works_list_entry);
++
++ spin_unlock_irq(&tgt->sess_work_lock);
++
++ q2t_exec_sess_work(tgt, prm);
++
++ spin_lock_irq(&tgt->sess_work_lock);
++
++ kfree(prm);
++ }
++ spin_unlock_irq(&tgt->sess_work_lock);
++
++ spin_lock_irq(&pha->hardware_lock);
++ spin_lock(&tgt->sess_work_lock);
++ if (list_empty(&tgt->sess_works_list)) {
++ tgt->sess_works_pending = 0;
++ tgt->tm_to_unknown = 0;
++ }
++ spin_unlock(&tgt->sess_work_lock);
++ spin_unlock_irq(&pha->hardware_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++/* pha->hardware_lock supposed to be held and IRQs off */
++static void q2t_cleanup_hw_pending_cmd(scsi_qla_host_t *ha, struct q2t_cmd *cmd)
++{
++ uint32_t h;
++
++ for (h = 0; h < MAX_OUTSTANDING_COMMANDS; h++) {
++ if (ha->cmds[h] == cmd) {
++ TRACE_DBG("Clearing handle %d for cmd %p", h, cmd);
++ ha->cmds[h] = NULL;
++ break;
++ }
++ }
++ return;
++}
++
++static void q2t_on_hw_pending_cmd_timeout(struct scst_cmd *scst_cmd)
++{
++ struct q2t_cmd *cmd = (struct q2t_cmd *)scst_cmd_get_tgt_priv(scst_cmd);
++ struct q2t_tgt *tgt = cmd->tgt;
++ scsi_qla_host_t *ha = tgt->ha;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++ unsigned long flags;
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Cmd %p HW pending for too long (state %x)", cmd,
++ cmd->state);
++
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++
++ if (cmd->sg_mapped)
++ q2t_unmap_sg(ha, cmd);
++
++ if (cmd->state == Q2T_STATE_PROCESSED) {
++ TRACE_MGMT_DBG("Force finishing cmd %p", cmd);
++ } else if (cmd->state == Q2T_STATE_NEED_DATA) {
++ TRACE_MGMT_DBG("Force rx_data cmd %p", cmd);
++
++ q2t_cleanup_hw_pending_cmd(ha, cmd);
++
++ scst_rx_data(scst_cmd, SCST_RX_STATUS_ERROR_FATAL,
++ SCST_CONTEXT_THREAD);
++ goto out_unlock;
++ } else if (cmd->state == Q2T_STATE_ABORTED) {
++ TRACE_MGMT_DBG("Force finishing aborted cmd %p (tag %d)",
++ cmd, cmd->tag);
++ } else {
++ PRINT_ERROR("qla2x00t(%ld): A command in state (%d) should "
++ "not be HW pending", ha->instance, cmd->state);
++ goto out_unlock;
++ }
++
++ q2t_cleanup_hw_pending_cmd(ha, cmd);
++
++ scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_FAILED);
++ scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_THREAD);
++
++out_unlock:
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++ TRACE_EXIT();
++ return;
++}
++
++/* Must be called under tgt_host_action_mutex */
++static int q2t_add_target(scsi_qla_host_t *ha)
++{
++ int res;
++ int rc;
++ char *wwn;
++ int sg_tablesize;
++ struct q2t_tgt *tgt;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Registering target for host %ld(%p)", ha->host_no, ha);
++
++ BUG_ON((ha->q2t_tgt != NULL) || (ha->tgt != NULL));
++
++ tgt = kzalloc(sizeof(*tgt), GFP_KERNEL);
++ if (tgt == NULL) {
++ PRINT_ERROR("qla2x00t: %s", "Allocation of tgt failed");
++ res = -ENOMEM;
++ goto out;
++ }
++
++ tgt->ha = ha;
++ init_waitqueue_head(&tgt->waitQ);
++ INIT_LIST_HEAD(&tgt->sess_list);
++ INIT_LIST_HEAD(&tgt->del_sess_list);
++ INIT_DELAYED_WORK(&tgt->sess_del_work,
++ (void (*)(struct work_struct *))q2t_del_sess_work_fn);
++ spin_lock_init(&tgt->sess_work_lock);
++ INIT_WORK(&tgt->sess_work, q2t_sess_work_fn);
++ INIT_LIST_HEAD(&tgt->sess_works_list);
++ spin_lock_init(&tgt->srr_lock);
++ INIT_LIST_HEAD(&tgt->srr_ctio_list);
++ INIT_LIST_HEAD(&tgt->srr_imm_list);
++ INIT_WORK(&tgt->srr_work, q2t_handle_srr_work);
++ atomic_set(&tgt->tgt_global_resets_count, 0);
++
++ ha->q2t_tgt = tgt;
++
++ res = q2t_get_target_name(ha->port_name, &wwn);
++ if (res != 0)
++ goto out_free;
++
++ tgt->scst_tgt = scst_register_target(&tgt2x_template, wwn);
++
++ kfree(wwn);
++
++ if (!tgt->scst_tgt) {
++ PRINT_ERROR("qla2x00t(%ld): scst_register_target() "
++ "failed for host %ld(%p)", ha->instance,
++ ha->host_no, ha);
++ res = -ENOMEM;
++ goto out_free;
++ }
++
++ if (IS_FWI2_CAPABLE(ha)) {
++ PRINT_INFO("qla2x00t(%ld): using 64 Bit PCI "
++ "addressing", ha->instance);
++ tgt->tgt_enable_64bit_addr = 1;
++ /* 3 is reserved */
++ sg_tablesize =
++ QLA_MAX_SG_24XX(ha->request_q_length - 3);
++ tgt->datasegs_per_cmd = DATASEGS_PER_COMMAND_24XX;
++ tgt->datasegs_per_cont = DATASEGS_PER_CONT_24XX;
++ } else {
++ if (ha->flags.enable_64bit_addressing) {
++ PRINT_INFO("qla2x00t(%ld): 64 Bit PCI "
++ "addressing enabled", ha->instance);
++ tgt->tgt_enable_64bit_addr = 1;
++ /* 3 is reserved */
++ sg_tablesize =
++ QLA_MAX_SG64(ha->request_q_length - 3);
++ tgt->datasegs_per_cmd = DATASEGS_PER_COMMAND64;
++ tgt->datasegs_per_cont = DATASEGS_PER_CONT64;
++ } else {
++ PRINT_INFO("qla2x00t(%ld): Using 32 Bit "
++ "PCI addressing", ha->instance);
++ sg_tablesize =
++ QLA_MAX_SG32(ha->request_q_length - 3);
++ tgt->datasegs_per_cmd = DATASEGS_PER_COMMAND32;
++ tgt->datasegs_per_cont = DATASEGS_PER_CONT32;
++ }
++ }
++
++ rc = sysfs_create_link(scst_sysfs_get_tgt_kobj(tgt->scst_tgt),
++ &ha->host->shost_dev.kobj, "host");
++ if (rc != 0)
++ PRINT_ERROR("qla2x00t(%ld): Unable to create \"host\" link for "
++ "target %s", ha->instance,
++ scst_get_tgt_name(tgt->scst_tgt));
++ if (!ha->parent) {
++ rc = sysfs_create_file(scst_sysfs_get_tgt_kobj(tgt->scst_tgt),
++ &q2t_hw_target_attr.attr);
++ if (rc != 0)
++ PRINT_ERROR("qla2x00t(%ld): Unable to create "
++ "\"hw_target\" file for target %s",
++ ha->instance, scst_get_tgt_name(tgt->scst_tgt));
++
++ rc = sysfs_create_file(scst_sysfs_get_tgt_kobj(tgt->scst_tgt),
++ &q2t_hw_node_name_attr.attr);
++ if (rc != 0)
++ PRINT_ERROR("qla2x00t(%ld): Unable to create "
++ "\"node_name\" file for HW target %s",
++ ha->instance, scst_get_tgt_name(tgt->scst_tgt));
++ } else {
++ rc = sysfs_create_file(scst_sysfs_get_tgt_kobj(tgt->scst_tgt),
++ &q2t_vp_node_name_attr.attr);
++ if (rc != 0)
++ PRINT_ERROR("qla2x00t(%ld): Unable to create "
++ "\"node_name\" file for NPIV target %s",
++ ha->instance, scst_get_tgt_name(tgt->scst_tgt));
++
++ rc = sysfs_create_file(scst_sysfs_get_tgt_kobj(tgt->scst_tgt),
++ &q2t_vp_parent_host_attr.attr);
++ if (rc != 0)
++ PRINT_ERROR("qla2x00t(%ld): Unable to create "
++ "\"parent_host\" file for NPIV target %s",
++ ha->instance, scst_get_tgt_name(tgt->scst_tgt));
++ }
++
++ scst_tgt_set_sg_tablesize(tgt->scst_tgt, sg_tablesize);
++ scst_tgt_set_tgt_priv(tgt->scst_tgt, tgt);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ ha->q2t_tgt = NULL;
++ kfree(tgt);
++ goto out;
++}
++
++/* Must be called under tgt_host_action_mutex */
++static int q2t_remove_target(scsi_qla_host_t *ha)
++{
++ TRACE_ENTRY();
++
++ if ((ha->q2t_tgt == NULL) || (ha->tgt != NULL)) {
++ PRINT_ERROR("qla2x00t(%ld): Can't remove "
++ "existing target", ha->instance);
++ }
++
++ TRACE_DBG("Unregistering target for host %ld(%p)", ha->host_no, ha);
++ scst_unregister_target(ha->q2t_tgt->scst_tgt);
++ /*
++ * Free of tgt happens via callback q2t_target_release
++ * called from scst_unregister_target, so we shouldn't touch
++ * it again.
++ */
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static int q2t_host_action(scsi_qla_host_t *ha,
++ qla2x_tgt_host_action_t action)
++{
++ int res = 0;
++ scsi_qla_host_t *pha = to_qla_parent(ha);
++
++ TRACE_ENTRY();
++
++ BUG_ON(ha == NULL);
++
++ /* To sync with q2t_exit() */
++ if (down_read_trylock(&q2t_unreg_rwsem) == 0)
++ goto out;
++
++ mutex_lock(&ha->tgt_host_action_mutex);
++
++ switch (action) {
++ case ADD_TARGET:
++ res = q2t_add_target(ha);
++ break;
++ case REMOVE_TARGET:
++ res = q2t_remove_target(ha);
++ break;
++ case ENABLE_TARGET_MODE:
++ {
++ fc_port_t *fcport;
++
++ if (qla_tgt_mode_enabled(ha)) {
++ PRINT_INFO("qla2x00t(%ld): Target mode already "
++ "enabled", ha->instance);
++ break;
++ }
++
++ if ((ha->q2t_tgt == NULL) || (ha->tgt != NULL)) {
++ PRINT_ERROR("qla2x00t(%ld): Can't enable target mode "
++ "for not existing target", ha->instance);
++ break;
++ }
++
++ PRINT_INFO("qla2x00t(%ld): Enabling target mode",
++ ha->instance);
++
++ spin_lock_irq(&pha->hardware_lock);
++ ha->tgt = ha->q2t_tgt;
++ ha->tgt->tgt_stop = 0;
++ spin_unlock_irq(&pha->hardware_lock);
++ list_for_each_entry_rcu(fcport, &ha->fcports, list) {
++ q2t_fc_port_added(ha, fcport);
++ }
++ TRACE_DBG("Enable tgt mode for host %ld(%ld,%p)",
++ ha->host_no, ha->instance, ha);
++ qla2x00_enable_tgt_mode(ha);
++ break;
++ }
++
++ case DISABLE_TARGET_MODE:
++ if (!qla_tgt_mode_enabled(ha)) {
++ PRINT_INFO("qla2x00t(%ld): Target mode already "
++ "disabled", ha->instance);
++ break;
++ }
++
++ PRINT_INFO("qla2x00t(%ld): Disabling target mode",
++ ha->instance);
++
++ BUG_ON(ha->tgt == NULL);
++
++ q2t_target_stop(ha->tgt->scst_tgt);
++ break;
++
++ default:
++ PRINT_ERROR("qla2x00t(%ld): %s: unsupported action %d",
++ ha->instance, __func__, action);
++ res = -EINVAL;
++ break;
++ }
++
++ mutex_unlock(&ha->tgt_host_action_mutex);
++
++ up_read(&q2t_unreg_rwsem);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int q2t_enable_tgt(struct scst_tgt *scst_tgt, bool enable)
++{
++ struct q2t_tgt *tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ scsi_qla_host_t *ha = tgt->ha;
++ int res;
++
++ if (enable)
++ res = q2t_host_action(ha, ENABLE_TARGET_MODE);
++ else
++ res = q2t_host_action(ha, DISABLE_TARGET_MODE);
++
++ return res;
++}
++
++static bool q2t_is_tgt_enabled(struct scst_tgt *scst_tgt)
++{
++ struct q2t_tgt *tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ scsi_qla_host_t *ha = tgt->ha;
++
++ return qla_tgt_mode_enabled(ha);
++}
++
++static int q2t_parse_wwn(const char *ns, u64 *nm)
++{
++ unsigned int i, j;
++ u8 wwn[8];
++
++ /* validate we have enough characters for WWPN */
++ if (strnlen(ns, 23) != 23)
++ return -EINVAL;
++
++ memset(wwn, 0, sizeof(wwn));
++
++ /* Validate and store the new name */
++ for (i = 0, j = 0; i < 16; i++) {
++ if ((*ns >= 'a') && (*ns <= 'f'))
++ j = ((j << 4) | ((*ns++ - 'a') + 10));
++ else if ((*ns >= 'A') && (*ns <= 'F'))
++ j = ((j << 4) | ((*ns++ - 'A') + 10));
++ else if ((*ns >= '0') && (*ns <= '9'))
++ j = ((j << 4) | (*ns++ - '0'));
++ else
++ return -EINVAL;
++ if (i % 2) {
++ wwn[i/2] = j & 0xff;
++ j = 0;
++ if ((i < 15) && (':' != *ns++))
++ return -EINVAL;
++ }
++ }
++
++ *nm = wwn_to_u64(wwn);
++
++ return 0;
++}
++
++static ssize_t q2t_add_vtarget(const char *target_name, char *params)
++{
++ int res;
++ char *param, *p, *pp;
++ u64 port_name, node_name, *pnode_name = NULL;
++ u64 parent_host, *pparent_host = NULL;
++
++ TRACE_ENTRY();
++
++ res = q2t_parse_wwn(target_name, &port_name);
++ if (res) {
++ PRINT_ERROR("qla2x00t: Syntax error at target name %s",
++ target_name);
++ goto out;
++ }
++
++ while (1) {
++ param = scst_get_next_token_str(&params);
++ if (param == NULL)
++ break;
++
++ p = scst_get_next_lexem(&param);
++ if (*p == '\0') {
++ PRINT_ERROR("qla2x00t: Syntax error at %s (target %s)",
++ param, target_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ pp = scst_get_next_lexem(&param);
++ if (*pp == '\0') {
++ PRINT_ERROR("qla2x00t: Parameter %s value missed for "
++ "target %s", p, target_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (scst_get_next_lexem(&param)[0] != '\0') {
++ PRINT_ERROR("qla2x00t: Too many parameter's %s values "
++ "(target %s)", p, target_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (!strcasecmp("node_name", p)) {
++ res = q2t_parse_wwn(pp, &node_name);
++ if (res) {
++ PRINT_ERROR("qla2x00t: Illegal node_name %s "
++ "(target %s)", pp, target_name);
++ res = -EINVAL;
++ goto out;
++ }
++ pnode_name = &node_name;
++ continue;
++ }
++
++ if (!strcasecmp("parent_host", p)) {
++ res = q2t_parse_wwn(pp, &parent_host);
++ if (res != 0) {
++ PRINT_ERROR("qla2x00t: Illegal parent_host %s"
++ " (target %s)", pp, target_name);
++ goto out;
++ }
++ pparent_host = &parent_host;
++ continue;
++ }
++
++ PRINT_ERROR("qla2x00t: Unknown parameter %s (target %s)", p,
++ target_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (!pnode_name) {
++ PRINT_ERROR("qla2x00t: Missing parameter node_name (target %s)",
++ target_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ if (!pparent_host) {
++ PRINT_ERROR("qla2x00t: Missing parameter parent_host "
++ "(target %s)", target_name);
++ res = -EINVAL;
++ goto out;
++ }
++
++ res = qla2xxx_add_vtarget(&port_name, pnode_name, pparent_host);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t q2t_del_vtarget(const char *target_name)
++{
++ int res;
++ u64 port_name;
++
++ TRACE_ENTRY();
++
++ res = q2t_parse_wwn(target_name, &port_name);
++ if (res) {
++ PRINT_ERROR("qla2x00t: Syntax error at target name %s",
++ target_name);
++ goto out;
++ }
++
++ res = qla2xxx_del_vtarget(&port_name);
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int q2t_get_initiator_port_transport_id(struct scst_tgt *tgt,
++ struct scst_session *scst_sess, uint8_t **transport_id)
++{
++ struct q2t_sess *sess;
++ int res = 0;
++ int tr_id_size;
++ uint8_t *tr_id;
++
++ TRACE_ENTRY();
++
++ if (scst_sess == NULL) {
++ res = SCSI_TRANSPORTID_PROTOCOLID_FCP2;
++ goto out;
++ }
++
++ sess = (struct q2t_sess *)scst_sess_get_tgt_priv(scst_sess);
++
++ tr_id_size = 24;
++
++ tr_id = kzalloc(tr_id_size, GFP_KERNEL);
++ if (tr_id == NULL) {
++ PRINT_ERROR("qla2x00t: Allocation of TransportID (size %d) "
++ "failed", tr_id_size);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ tr_id[0] = SCSI_TRANSPORTID_PROTOCOLID_FCP2;
++
++ BUILD_BUG_ON(sizeof(sess->port_name) != 8);
++ memcpy(&tr_id[8], sess->port_name, 8);
++
++ *transport_id = tr_id;
++
++ TRACE_BUFF_FLAG(TRACE_DEBUG, "Created tid", tr_id, tr_id_size);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t q2t_show_expl_conf_enabled(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buffer)
++{
++ struct scst_tgt *scst_tgt;
++ struct q2t_tgt *tgt;
++ scsi_qla_host_t *ha;
++ ssize_t size;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ ha = tgt->ha;
++
++ size = scnprintf(buffer, PAGE_SIZE, "%d\n%s", ha->enable_explicit_conf,
++ ha->enable_explicit_conf ? SCST_SYSFS_KEY_MARK "\n" : "");
++
++ return size;
++}
++
++static ssize_t q2t_store_expl_conf_enabled(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size)
++{
++ struct scst_tgt *scst_tgt;
++ struct q2t_tgt *tgt;
++ scsi_qla_host_t *ha, *pha;
++ unsigned long flags;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ ha = tgt->ha;
++ pha = to_qla_parent(ha);
++
++ spin_lock_irqsave(&pha->hardware_lock, flags);
++
++ switch (buffer[0]) {
++ case '0':
++ ha->enable_explicit_conf = 0;
++ PRINT_INFO("qla2x00t(%ld): explicit conformations disabled",
++ ha->instance);
++ break;
++ case '1':
++ ha->enable_explicit_conf = 1;
++ PRINT_INFO("qla2x00t(%ld): explicit conformations enabled",
++ ha->instance);
++ break;
++ default:
++ PRINT_ERROR("%s: qla2x00t(%ld): Requested action not "
++ "understood: %s", __func__, ha->instance, buffer);
++ break;
++ }
++
++ spin_unlock_irqrestore(&pha->hardware_lock, flags);
++
++ return size;
++}
++
++static ssize_t q2t_abort_isp_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size)
++{
++ struct scst_tgt *scst_tgt;
++ struct q2t_tgt *tgt;
++ scsi_qla_host_t *ha;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ ha = tgt->ha;
++
++ PRINT_INFO("qla2x00t(%ld): Aborting ISP", ha->instance);
++
++ set_bit(ISP_ABORT_NEEDED, &ha->dpc_flags);
++ qla2x00_wait_for_hba_online(ha);
++
++ return size;
++}
++
++static ssize_t q2t_version_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ sprintf(buf, "%s\n", Q2T_VERSION_STRING);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ strcat(buf, "EXTRACHECKS\n");
++#endif
++
++#ifdef CONFIG_SCST_TRACING
++ strcat(buf, "TRACING\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++ strcat(buf, "DEBUG\n");
++#endif
++
++#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
++ strcat(buf, "QLA_TGT_DEBUG_WORK_IN_THREAD\n");
++#endif
++
++ TRACE_EXIT();
++ return strlen(buf);
++}
++
++static ssize_t q2t_hw_target_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ return sprintf(buf, "%d\n", 1);
++}
++
++static ssize_t q2t_node_name_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt *scst_tgt;
++ struct q2t_tgt *tgt;
++ scsi_qla_host_t *ha;
++ ssize_t res;
++ char *wwn;
++ uint8_t *node_name;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ ha = tgt->ha;
++
++ if (ha->parent == NULL) {
++ if (qla_tgt_mode_enabled(ha) || !ha->node_name_set)
++ node_name = ha->node_name;
++ else
++ node_name = ha->tgt_node_name;
++ } else
++ node_name = ha->node_name;
++
++ res = q2t_get_target_name(node_name, &wwn);
++ if (res != 0)
++ goto out;
++
++ res = sprintf(buf, "%s\n", wwn);
++ if ((ha->parent != NULL) || ha->node_name_set)
++ res += sprintf(&buf[res], "%s\n", SCST_SYSFS_KEY_MARK);
++
++ kfree(wwn);
++
++out:
++ return res;
++}
++
++static ssize_t q2t_node_name_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size)
++{
++ struct scst_tgt *scst_tgt;
++ struct q2t_tgt *tgt;
++ scsi_qla_host_t *ha;
++ u64 node_name, old_node_name;
++ int res;
++
++ TRACE_ENTRY();
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ ha = tgt->ha;
++
++ BUG_ON(ha->parent != NULL);
++
++ if (size == 0)
++ goto out_default;
++
++ res = q2t_parse_wwn(buffer, &node_name);
++ if (res != 0) {
++ if ((buffer[0] == '\0') || (buffer[0] == '\n'))
++ goto out_default;
++ PRINT_ERROR("qla2x00t(%ld): Wrong node name", ha->instance);
++ goto out;
++ }
++
++ old_node_name = wwn_to_u64(ha->node_name);
++ if (old_node_name == node_name)
++ goto out_success;
++
++ u64_to_wwn(node_name, ha->tgt_node_name);
++ ha->node_name_set = 1;
++
++abort:
++ if (qla_tgt_mode_enabled(ha)) {
++ set_bit(ISP_ABORT_NEEDED, &ha->dpc_flags);
++ qla2x00_wait_for_hba_online(ha);
++ }
++
++out_success:
++ res = size;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_default:
++ ha->node_name_set = 0;
++ goto abort;
++}
++
++static ssize_t q2t_vp_parent_host_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt *scst_tgt;
++ struct q2t_tgt *tgt;
++ scsi_qla_host_t *ha;
++ ssize_t res;
++ char *wwn;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = (struct q2t_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++ ha = to_qla_parent(tgt->ha);
++
++ res = q2t_get_target_name(ha->port_name, &wwn);
++ if (res != 0)
++ goto out;
++
++ res = sprintf(buf, "%s\n%s\n", wwn, SCST_SYSFS_KEY_MARK);
++
++ kfree(wwn);
++
++out:
++ return res;
++}
++
++static uint16_t q2t_get_scsi_transport_version(struct scst_tgt *scst_tgt)
++{
++ /* FCP-2 */
++ return 0x0900;
++}
++
++static uint16_t q2t_get_phys_transport_version(struct scst_tgt *scst_tgt)
++{
++ return 0x0DA0; /* FC-FS */
++}
++
++static int __init q2t_init(void)
++{
++ int res = 0;
++
++ TRACE_ENTRY();
++
++ BUILD_BUG_ON(sizeof(atio7_entry_t) != sizeof(atio_entry_t));
++
++ PRINT_INFO("qla2x00t: Initializing QLogic Fibre Channel HBA Driver "
++ "target mode addon version %s", Q2T_VERSION_STRING);
++
++ q2t_cmd_cachep = KMEM_CACHE(q2t_cmd, SCST_SLAB_FLAGS);
++ if (q2t_cmd_cachep == NULL) {
++ res = -ENOMEM;
++ goto out;
++ }
++
++ q2t_mgmt_cmd_cachep = KMEM_CACHE(q2t_mgmt_cmd, SCST_SLAB_FLAGS);
++ if (q2t_mgmt_cmd_cachep == NULL) {
++ res = -ENOMEM;
++ goto out_cmd_free;
++ }
++
++ q2t_mgmt_cmd_mempool = mempool_create(25, mempool_alloc_slab,
++ mempool_free_slab, q2t_mgmt_cmd_cachep);
++ if (q2t_mgmt_cmd_mempool == NULL) {
++ res = -ENOMEM;
++ goto out_kmem_free;
++ }
++
++ res = scst_register_target_template(&tgt2x_template);
++ if (res < 0)
++ goto out_mempool_free;
++
++ /*
++ * qla2xxx_tgt_register_driver() happens in q2t_target_detect
++ * called via scst_register_target_template()
++ */
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++ scst_unregister_target_template(&tgt2x_template);
++ qla2xxx_tgt_unregister_driver();
++
++out_mempool_free:
++ mempool_destroy(q2t_mgmt_cmd_mempool);
++
++out_kmem_free:
++ kmem_cache_destroy(q2t_mgmt_cmd_cachep);
++
++out_cmd_free:
++ kmem_cache_destroy(q2t_cmd_cachep);
++ goto out;
++}
++
++static void __exit q2t_exit(void)
++{
++ TRACE_ENTRY();
++
++ PRINT_INFO("qla2x00t: %s", "Unloading QLogic Fibre Channel HBA Driver "
++ "target mode addon driver");
++
++ /* To sync with q2t_host_action() */
++ down_write(&q2t_unreg_rwsem);
++
++ scst_unregister_target_template(&tgt2x_template);
++
++ /*
++ * Now we have everywhere target mode disabled and no possibilities
++ * to call us through sysfs, so we can safely remove all the references
++ * to our functions.
++ */
++ qla2xxx_tgt_unregister_driver();
++
++ mempool_destroy(q2t_mgmt_cmd_mempool);
++ kmem_cache_destroy(q2t_mgmt_cmd_cachep);
++ kmem_cache_destroy(q2t_cmd_cachep);
++
++ /* Let's make lockdep happy */
++ up_write(&q2t_unreg_rwsem);
++
++ TRACE_EXIT();
++ return;
++}
++
++module_init(q2t_init);
++module_exit(q2t_exit);
++
++MODULE_AUTHOR("Vladislav Bolkhovitin and others");
++MODULE_DESCRIPTION("Target mode addon for qla2[2,3,4,5+]xx");
++MODULE_LICENSE("GPL");
++MODULE_VERSION(Q2T_VERSION_STRING);
+diff -uprN orig/linux-3.2/drivers/scst/qla2xxx-target/qla2x00t.h linux-3.2/drivers/scst/qla2xxx-target/qla2x00t.h
+--- orig/linux-3.2/drivers/scst/qla2xxx-target/qla2x00t.h
++++ linux-3.2/drivers/scst/qla2xxx-target/qla2x00t.h
+@@ -0,0 +1,287 @@
++/*
++ * qla2x00t.h
++ *
++ * Copyright (C) 2004 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ * Copyright (C) 2004 - 2005 Leonid Stoljar
++ * Copyright (C) 2006 Nathaniel Clark <nate@misrule.us>
++ * Copyright (C) 2006 - 2010 ID7 Ltd.
++ * Copyright (C) 2010 - 2011 SCST Ltd.
++ *
++ * QLogic 22xx/23xx/24xx/25xx FC target driver.
++ *
++ * 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 __QLA2X00T_H
++#define __QLA2X00T_H
++
++#include <qla_def.h>
++#include <qla2x_tgt.h>
++#include <qla2x_tgt_def.h>
++
++#include <scst_debug.h>
++
++/* Version numbers, the same as for the kernel */
++#define Q2T_VERSION(a, b, c, d) (((a) << 030) + ((b) << 020) + (c) << 010 + (d))
++#define Q2T_VERSION_CODE Q2T_VERSION(2, 2, 0, 0)
++#define Q2T_VERSION_STRING "2.2.0-pre"
++#define Q2T_PROC_VERSION_NAME "version"
++
++#define Q2T_MAX_CDB_LEN 16
++#define Q2T_TIMEOUT 10 /* in seconds */
++
++#define Q2T_MAX_HW_PENDING_TIME 60 /* in seconds */
++
++/* Immediate notify status constants */
++#define IMM_NTFY_LIP_RESET 0x000E
++#define IMM_NTFY_LIP_LINK_REINIT 0x000F
++#define IMM_NTFY_IOCB_OVERFLOW 0x0016
++#define IMM_NTFY_ABORT_TASK 0x0020
++#define IMM_NTFY_PORT_LOGOUT 0x0029
++#define IMM_NTFY_PORT_CONFIG 0x002A
++#define IMM_NTFY_GLBL_TPRLO 0x002D
++#define IMM_NTFY_GLBL_LOGO 0x002E
++#define IMM_NTFY_RESOURCE 0x0034
++#define IMM_NTFY_MSG_RX 0x0036
++#define IMM_NTFY_SRR 0x0045
++#define IMM_NTFY_ELS 0x0046
++
++/* Immediate notify task flags */
++#define IMM_NTFY_TASK_MGMT_SHIFT 8
++
++#define Q2T_CLEAR_ACA 0x40
++#define Q2T_TARGET_RESET 0x20
++#define Q2T_LUN_RESET 0x10
++#define Q2T_CLEAR_TS 0x04
++#define Q2T_ABORT_TS 0x02
++#define Q2T_ABORT_ALL_SESS 0xFFFF
++#define Q2T_ABORT_ALL 0xFFFE
++#define Q2T_NEXUS_LOSS_SESS 0xFFFD
++#define Q2T_NEXUS_LOSS 0xFFFC
++
++/* Notify Acknowledge flags */
++#define NOTIFY_ACK_RES_COUNT BIT_8
++#define NOTIFY_ACK_CLEAR_LIP_RESET BIT_5
++#define NOTIFY_ACK_TM_RESP_CODE_VALID BIT_4
++
++/* Command's states */
++#define Q2T_STATE_NEW 0 /* New command and SCST processing it */
++#define Q2T_STATE_NEED_DATA 1 /* SCST needs data to continue */
++#define Q2T_STATE_DATA_IN 2 /* Data arrived and SCST processing it */
++#define Q2T_STATE_PROCESSED 3 /* SCST done processing */
++#define Q2T_STATE_ABORTED 4 /* Command aborted */
++
++/* Special handles */
++#define Q2T_NULL_HANDLE 0
++#define Q2T_SKIP_HANDLE (0xFFFFFFFF & ~CTIO_COMPLETION_HANDLE_MARK)
++
++/* ATIO task_codes field */
++#define ATIO_SIMPLE_QUEUE 0
++#define ATIO_HEAD_OF_QUEUE 1
++#define ATIO_ORDERED_QUEUE 2
++#define ATIO_ACA_QUEUE 4
++#define ATIO_UNTAGGED 5
++
++/* TM failed response codes, see FCP (9.4.11 FCP_RSP_INFO) */
++#define FC_TM_SUCCESS 0
++#define FC_TM_BAD_FCP_DATA 1
++#define FC_TM_BAD_CMD 2
++#define FC_TM_FCP_DATA_MISMATCH 3
++#define FC_TM_REJECT 4
++#define FC_TM_FAILED 5
++
++/*
++ * Error code of q2t_pre_xmit_response() meaning that cmd's exchange was
++ * terminated, so no more actions is needed and success should be returned
++ * to SCST. Must be different from any SCST_TGT_RES_* codes.
++ */
++#define Q2T_PRE_XMIT_RESP_CMD_ABORTED 0x1717
++
++#if (BITS_PER_LONG > 32) || defined(CONFIG_HIGHMEM64G)
++#define pci_dma_lo32(a) (a & 0xffffffff)
++#define pci_dma_hi32(a) ((((a) >> 16)>>16) & 0xffffffff)
++#else
++#define pci_dma_lo32(a) (a & 0xffffffff)
++#define pci_dma_hi32(a) 0
++#endif
++
++struct q2t_tgt {
++ struct scst_tgt *scst_tgt;
++ scsi_qla_host_t *ha;
++
++ /*
++ * To sync between IRQ handlers and q2t_target_release(). Needed,
++ * because req_pkt() can drop/reacquire HW lock inside. Protected by
++ * HW lock.
++ */
++ int irq_cmd_count;
++
++ int datasegs_per_cmd, datasegs_per_cont;
++
++ /* Target's flags, serialized by pha->hardware_lock */
++ unsigned int tgt_enable_64bit_addr:1; /* 64-bits PCI addressing enabled */
++ unsigned int link_reinit_iocb_pending:1;
++ unsigned int tm_to_unknown:1; /* TM to unknown session was sent */
++ unsigned int sess_works_pending:1; /* there are sess_work entries */
++
++ /*
++ * Protected by tgt_mutex AND hardware_lock for writing and tgt_mutex
++ * OR hardware_lock for reading.
++ */
++ unsigned long tgt_stop; /* the driver is being stopped */
++
++ /* Count of sessions refering q2t_tgt. Protected by hardware_lock. */
++ int sess_count;
++
++ /* Protected by hardware_lock. Addition also protected by tgt_mutex. */
++ struct list_head sess_list;
++
++ /* Protected by hardware_lock */
++ struct list_head del_sess_list;
++ struct delayed_work sess_del_work;
++
++ spinlock_t sess_work_lock;
++ struct list_head sess_works_list;
++ struct work_struct sess_work;
++
++ notify24xx_entry_t link_reinit_iocb;
++ wait_queue_head_t waitQ;
++ int notify_ack_expected;
++ int abts_resp_expected;
++ int modify_lun_expected;
++
++ int ctio_srr_id;
++ int imm_srr_id;
++ spinlock_t srr_lock;
++ struct list_head srr_ctio_list;
++ struct list_head srr_imm_list;
++ struct work_struct srr_work;
++
++ atomic_t tgt_global_resets_count;
++
++ struct list_head tgt_list_entry;
++};
++
++/*
++ * Equivilant to IT Nexus (Initiator-Target)
++ */
++struct q2t_sess {
++ uint16_t loop_id;
++ port_id_t s_id;
++
++ unsigned int conf_compl_supported:1;
++ unsigned int deleted:1;
++ unsigned int local:1;
++
++ struct scst_session *scst_sess;
++ struct q2t_tgt *tgt;
++
++ int sess_ref; /* protected by hardware_lock */
++
++ struct list_head sess_list_entry;
++ unsigned long expires;
++ struct list_head del_list_entry;
++
++ uint8_t port_name[WWN_SIZE];
++};
++
++struct q2t_cmd {
++ struct q2t_sess *sess;
++ int state;
++ struct scst_cmd *scst_cmd;
++
++ unsigned int conf_compl_supported:1;/* to save extra sess dereferences */
++ unsigned int sg_mapped:1;
++ unsigned int free_sg:1;
++ unsigned int aborted:1; /* Needed in case of SRR */
++ unsigned int write_data_transferred:1;
++
++ struct scatterlist *sg; /* cmd data buffer SG vector */
++ int sg_cnt; /* SG segments count */
++ int bufflen; /* cmd buffer length */
++ int offset;
++ scst_data_direction data_direction;
++ uint32_t tag;
++ dma_addr_t dma_handle;
++ enum dma_data_direction dma_data_direction;
++
++ uint16_t loop_id; /* to save extra sess dereferences */
++ struct q2t_tgt *tgt; /* to save extra sess dereferences */
++
++ union {
++ atio7_entry_t atio7;
++ atio_entry_t atio2x;
++ } __attribute__((packed)) atio;
++};
++
++struct q2t_sess_work_param {
++ struct list_head sess_works_list_entry;
++
++#define Q2T_SESS_WORK_CMD 0
++#define Q2T_SESS_WORK_ABORT 1
++#define Q2T_SESS_WORK_TM 2
++ int type;
++
++ union {
++ struct q2t_cmd *cmd;
++ abts24_recv_entry_t abts;
++ notify_entry_t tm_iocb;
++ atio7_entry_t tm_iocb2;
++ };
++};
++
++struct q2t_mgmt_cmd {
++ struct q2t_sess *sess;
++ unsigned int flags;
++#define Q24_MGMT_SEND_NACK 1
++ union {
++ atio7_entry_t atio7;
++ notify_entry_t notify_entry;
++ notify24xx_entry_t notify_entry24;
++ abts24_recv_entry_t abts;
++ } __attribute__((packed)) orig_iocb;
++};
++
++struct q2t_prm {
++ struct q2t_cmd *cmd;
++ struct q2t_tgt *tgt;
++ void *pkt;
++ struct scatterlist *sg; /* cmd data buffer SG vector */
++ int seg_cnt;
++ int req_cnt;
++ uint16_t rq_result;
++ uint16_t scsi_status;
++ unsigned char *sense_buffer;
++ int sense_buffer_len;
++ int residual;
++ int add_status_pkt;
++};
++
++struct srr_imm {
++ struct list_head srr_list_entry;
++ int srr_id;
++ union {
++ notify_entry_t notify_entry;
++ notify24xx_entry_t notify_entry24;
++ } __attribute__((packed)) imm;
++};
++
++struct srr_ctio {
++ struct list_head srr_list_entry;
++ int srr_id;
++ struct q2t_cmd *cmd;
++};
++
++#define Q2T_XMIT_DATA 1
++#define Q2T_XMIT_STATUS 2
++#define Q2T_XMIT_ALL (Q2T_XMIT_STATUS|Q2T_XMIT_DATA)
++
++#endif /* __QLA2X00T_H */
+diff -uprN orig/linux-3.2/Documentation/scst/README.qla2x00t linux-3.2/Documentation/scst/README.qla2x00t
+--- orig/linux-3.2/Documentation/scst/README.qla2x00t
++++ linux-3.2/Documentation/scst/README.qla2x00t
+@@ -0,0 +1,572 @@
++Target driver for QLogic 22xx/23xx/24xx/25xx Fibre Channel cards
++================================================================
++
++Version 2.1.0
++-------------
++
++This driver consists from two parts: the target mode driver itself and
++the changed initiator driver from Linux kernel, which is, particularly,
++intended to perform all the initialization and shutdown tasks. The
++initiator driver was changed to provide the target mode support and all
++necessary callbacks, but it's still capable to work as initiator only.
++Mode, when a host acts as the initiator and the target simultaneously,
++is supported as well.
++
++This version is compatible with SCST core version 2.0.0 and higher and
++Linux kernel 2.6.26 and higher. Sorry, kernels below 2.6.26 are not
++supported, because it's too hard to backport used initiator driver to
++older kernels.
++
++The original initiator driver was taken from the kernel 2.6.26. Also the
++following 2.6.26.x commits have been applied to it (upstream ID):
++048feec5548c0582ee96148c61b87cccbcb5f9be,
++031e134e5f95233d80fb1b62fdaf5e1be587597c,
++5f3a9a207f1fccde476dd31b4c63ead2967d934f,
++85821c906cf3563a00a3d98fa380a2581a7a5ff1,
++3c01b4f9fbb43fc911acd33ea7a14ea7a4f9866b,
++8eca3f39c4b11320787f7b216f63214aee8415a9,
++0f19bc681ed0849a2b95778460a0a8132e3700e2.
++
++See also "ToDo" file for list of known issues and unimplemented
++features.
++
++
++Installation
++------------
++
++Only vanilla kernels from kernel.org and RHEL/CentOS 5.2 kernels are
++supported, but SCST should work on other (vendors') kernels, if you
++manage to successfully compile it on them. The main problem with
++vendors' kernels is that they often contain patches, which will appear
++only in the next version of the vanilla kernel, therefore it's quite
++hard to track such changes. Thus, if during compilation for some vendor
++kernel your compiler complains about redefinition of some symbol, you
++should either switch to vanilla kernel, or add or change as necessary
++the corresponding to that symbol "#if LINUX_VERSION_CODE" statement.
++
++Before installation make sure that the link
++"/lib/modules/`you_kernel_version`/build" points to the source code for
++your currently running kernel.
++
++If your kernel version is <2.6.28, then you should consider applying
++kernel patch scst_fc_vport_create.patch from the "kernel" subdirectory.
++Without it, creating and removing NPIV targets using SCST sysfs
++interface will be disabled. NOTE: you will still be able to create and
++remove NPIV targets using the standard Linux interface (i.e. echoing
++wwpn:wwnn into /sys/class/fc_host/hostX/vport_create and
++/sys/class/fc_host/hostX/vport_delete).
++
++Then you should replace (or link) by the initiator driver from this
++package "qla2xxx" subdirectory in kernel_source/drivers/scsi/ of the
++currently running kernel and using your favorite kernel configuration
++tool enable in the QLogic QLA2XXX Fibre Channel driver target mode
++support (CONFIG_SCSI_QLA2XXX_TARGET). Then rebuild the kernel and its
++modules. During this step you will compile the initiator driver. To
++install it, install the built kernel and its modules.
++
++Then edit qla2x00-target/Makefile and set SCST_INC_DIR variable to point
++to the directory, where SCST's public include files are located. If you
++install QLA2x00 target driver's source code in the SCST's directory,
++then SCST_INC_DIR will be set correctly for you.
++
++Also you can set SCST_DIR variable to the directory, where SCST was
++built, but this is optional. If you don't set it or set incorrectly,
++during the compilation you will get a bunch of harmless warnings like
++"WARNING: "scst_rx_data" [/XXX/qla2x00tgt.ko] undefined!"
++
++To compile the target driver, type 'make' in qla2x00-target/
++subdirectory. It will build qla2x00tgt.ko module.
++
++To install the target driver, type 'make install' in qla2x00-target/
++subdirectory. The target driver will be installed in
++/lib/modules/`you_kernel_version`/extra. To uninstall it, type 'make
++uninstall'.
++
++
++Usage
++-----
++
++After the drivers are loaded and adapters successfully initialized by
++the initiator driver, including firmware image load, you should
++configure exported devices using the corresponding interface of SCST
++core. It is highly recommended to use scstadmin utility for that
++purpose.
++
++Then target mode should be enabled via a sysfs interface on a per card
++basis, like:
++
++echo "1" >/sys/kernel/scst_tgt/targets/qla2x00t/target/enabled
++
++See below for full description of the driver's sysfs interface.
++
++With the obsolete proc interface you should instead use
++target_mode_enabled under the appropriate scsi_host entry, like:
++
++echo "1" >/sys/class/scsi_host/host0/target_mode_enabled
++
++You can find some installation and configuration HOWTOs in
++http://scst.sourceforge.net/qla2x00t-howto.html and
++https://forums.openfiler.com/viewtopic.php?id=3422.
++
++
++IMPORTANT USAGE NOTES
++---------------------
++
++1. It is strongly recommended to use firmware version 5.x or higher
++for 24xx/25xx adapters. See
++http://sourceforge.net/mailarchive/forum.php?thread_name=4B4CD39F.6020401%40vlnb.net&forum_name=scst-devel
++for more details why.
++
++2. If you reload qla2x00tgt module, you should also reload qla2xxx
++module, otherwise your initiators could not see the target, when it is
++enabled after qla2x00tgt module load.
++
++3. You need to issue LIP after you enabled a target, if you enabled it
++after one or more its initiators already started.
++
++
++Initiator and target modes
++--------------------------
++
++When qla2xxx compiled with CONFIG_SCSI_QLA2XXX_TARGET enabled, it has
++parameter "qlini_mode", which determines when initiator mode will be
++enabled. Possible values:
++
++ - "exclusive" (default) - initiator mode will be enabled on load,
++disabled on enabling target mode and then on disabling target mode
++enabled back.
++
++ - "disabled" - initiator mode will never be enabled.
++
++ - "enabled" - initiator mode will always stay enabled.
++
++Usage of mode "disabled" is recommended, if you have incorrectly
++functioning your target's initiators, which if once seen a port in
++initiator mode, later refuse to see it as a target.
++
++Use mode "enabled" if you need your QLA adapters to work in both
++initiator and target modes at the same time.
++
++You can always see which modes are currently active in active_mode sysfs
++attribute.
++
++In all the modes you can at any time use sysfs attribute
++ini_mode_force_reverse to force enable or disable initiator mode on any
++particular port. Setting this attribute to 1 will reverse current status
++of the initiator mode from enabled to disabled and vice versa.
++
++
++Explicit conformation
++---------------------
++
++This option should (actually, almost always must) be enabled by echoing
++"1" in /sys/kernel/scst_tgt/targets/qla2x00t/target/host/explicit_conform_enabled,
++if a target card exports at least one stateful SCSI device, like tape,
++and class 2 isn't used, otherwise link-level errors could lead to loss
++of the target/initiator state synchronization. Also check if initiator
++supports this feature, it is reported in the kernel logs ("confirmed
++completion supported" or not). No major performance degradation was
++noticed, if it is enabled. Supported only for 23xx+. Disabled by
++default.
++
++
++Class 2
++-------
++
++Class 2 is the close equivalent of TCP in the network world. If you
++enable it, all the Fibre Channel packets will be acknowledged. By
++default, class 3 is used, which is UDP-like. Enable class 2 by echoing
++"1" in /sys/kernel/scst_tgt/targets/qla2x00t/target/host/class2_enabled.
++This option needs a special firmware with class 2 support. Disabled by
++default.
++
++
++N_Port ID Virtualization
++------------------------
++
++N_Port ID Virtualization (NPIV) is a Fibre Channel facility allowing
++multiple N_Port IDs to share a single physical N_Port. NPIV is fully
++supported by this driver. You must have 24xx+ ISPs with NPIV-supporting
++and NPIV-switches switch(es) to use this facility.
++
++You can add NPIV targets by echoing:
++
++add_target target_name node_name=node_name_value; parent_host=parent_host_value
++
++in /sys/kernel/scst_tgt/targets/qla2x00t/mgmt.
++
++Removing NPIV targets is done by echoing:
++
++del_target target_name
++
++in/sys/kernel/scst_tgt/targets/qla2x00t/mgmt.
++
++Also, you can create and remove NPIV targets using the standard Linux
++interface (i.e. echoing wwpn:wwnn into /sys/class/fc_host/hostX/vport_create
++and /sys/class/fc_host/hostX/vport_delete).
++
++It is recommended to use scstadmin utility and its config file to
++configure virtual NPIV targets instead of the above direct interface.
++
++
++Compilation options
++-------------------
++
++There are the following compilation options, that could be commented
++in/out in Makefile:
++
++ - CONFIG_SCST_DEBUG - turns on some debugging code, including some logging.
++ Makes the driver considerably bigger and slower, producing large amount of
++ log data.
++
++ - CONFIG_SCST_TRACING - turns on ability to log events. Makes the driver
++ considerably bigger and leads to some performance loss.
++
++ - CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD - makes SCST process incoming
++ commands from the qla2x00t target driver and call the driver's
++ callbacks in internal SCST threads context instead of SIRQ context,
++ where those commands were received. Useful for debugging and lead to
++ some performance loss.
++
++ - CONFIG_QLA_TGT_DEBUG_SRR - turns on retransmitting packets (SRR)
++ debugging. In this mode some CTIOs will be "broken" to force the
++ initiator to issue a retransmit request.
++
++
++Sysfs interface
++---------------
++
++Starting from 2.0.0 this driver has sysfs interface. The procfs
++interface from version 2.0.0 is obsolete and will be removed in one of
++the next versions.
++
++Root of SCST sysfs interface is /sys/kernel/scst_tgt. Root of this
++driver is /sys/kernel/scst_tgt/targets/qla2x00t. It has the following
++entries:
++
++ - None, one or more subdirectories for targets with name equal to port
++ names of the corresponding targets.
++
++ - trace_level - allows to enable and disable various tracing
++ facilities. See content of this file for help how to use it.
++
++ - version - read-only attribute, which allows to see version of
++ this driver and enabled optional features.
++
++ - mgmt - main management entry, which allows to configure NPIV targets.
++ See content of this file for help how to use it.
++
++ - hw_target (hardware target only) - read-only attribute with value 1.
++ It allows to distinguish hardware and virtual targets.
++
++Each target subdirectory contains the following entries:
++
++ - host - link pointing on the corresponding scsi_host of the initiator
++ driver
++
++ - ini_groups - subdirectory defining initiator groups for this target,
++ used to define per-initiator access control. See SCST core README for
++ more details.
++
++ - luns - subdirectory defining LUNs of this target. See SCST core
++ README for more details.
++
++ - sessions - subdirectory containing connected to this target sessions.
++
++ - enabled - using this attribute you can enable or disable target mode
++ of this FC port. It allows to finish configuring it before it starts
++ accepting new connections. 0 by default.
++
++ - explicit_confirmation - allows to enable explicit conformations, see
++ above.
++
++ - rel_tgt_id - allows to read or write SCSI Relative Target Port
++ Identifier attribute. This identifier is used to identify SCSI Target
++ Ports by some SCSI commands, mainly by Persistent Reservations
++ commands. This identifier must be unique among all SCST targets, but
++ for convenience SCST allows disabled targets to have not unique
++ rel_tgt_id. In this case SCST will not allow to enable this target
++ until rel_tgt_id becomes unique. This attribute initialized unique by
++ SCST by default.
++
++ - node_name (NPIV targets only) - read-only attribute, which allows to see
++ the target World Wide Node Name.
++
++ - parent_host (NPIV target only) - read-only attribute, which allows to see
++ the parent HBA World Wide Port Name (WWPN).
++
++Subdirectory "sessions" contains one subdirectory for each connected
++session with name equal to port name of the connected initiator.
++
++Each session subdirectory contains the following entries:
++
++ - initiator_name - contains initiator's port name
++
++ - active_commands - contains number of active, i.e. not yet or being
++ executed, SCSI commands in this session.
++
++ - commands - contains overall number of SCSI commands in this session.
++
++Below is a sample script, which configures 2 virtual disk "disk1" using
++/disk1 image for usage with 25:00:00:f0:98:87:92:f3 hardware target, and
++"disk2" using /disk2 image for usage with 50:50:00:00:00:00:00:11 NPIV
++target. All initiators connected to this targets will see those devices.
++
++#!/bin/bash
++
++modprobe scst
++modprobe scst_vdisk
++
++echo "add_device disk1 filename=/disk1; nv_cache=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++echo "add_device disk2 filename=/disk2; nv_cache=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++
++modprobe qla2x00tgt
++
++echo "add_target 50:50:00:00:00:00:00:11 node_name=50:50:00:00:00:00:00:00;parent_host=25:00:00:f0:98:87:92:f3" >\
++/sys/kernel/scst_tgt/targets/qla2x00t/mgmt
++
++echo "add disk1 0" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/luns/mgmt
++echo "add disk2 0" >/sys/kernel/scst_tgt/targets/qla2x00t/50:50:00:00:00:00:00:11/luns/mgmt
++echo 1 >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/enabled
++echo 1 >/sys/kernel/scst_tgt/targets/qla2x00t/50:50:00:00:00:00:00:11/enabled
++
++Below is another sample script, which configures 1 real local SCSI disk
++0:0:1:0 for usage with 25:00:00:f0:98:87:92:f3 target:
++
++#!/bin/bash
++
++modprobe scst
++modprobe scst_disk
++
++echo "add_device 0:0:1:0" >/sys/kernel/scst_tgt/handlers/dev_disk/mgmt
++
++modprobe qla2x00tgt
++
++echo "add 0:0:1:0 0" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/luns/mgmt
++echo 1 >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/enabled
++
++Below is an advanced sample script, which configures more virtual
++devices of various types, including virtual CDROM. In this script
++initiator 25:00:00:f0:99:87:94:a3 will see disk1 and disk2 devices, all
++other initiators will see read only blockio, nullio and cdrom devices.
++
++#!/bin/bash
++
++modprobe scst
++modprobe scst_vdisk
++
++echo "add_device disk1 filename=/disk1; nv_cache=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++echo "add_device disk2 filename=/disk2; blocksize=4096; nv_cache=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++echo "add_device blockio filename=/dev/sda5" >/sys/kernel/scst_tgt/handlers/vdisk_blockio/mgmt
++echo "add_device nullio" >/sys/kernel/scst_tgt/handlers/vdisk_nullio/mgmt
++echo "add_device cdrom" >/sys/kernel/scst_tgt/handlers/vcdrom/mgmt
++
++modprobe qla2x00tgt
++
++echo "add blockio 0 read_only=1" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/luns/mgmt
++echo "add nullio 1" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/luns/mgmt
++echo "add cdrom 2" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/luns/mgmt
++
++echo "create 25:00:00:f0:99:87:94:a3" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/ini_groups/mgmt
++echo "add disk1 0" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/ini_groups/25:00:00:f0:99:87:94:a3/luns/mgmt
++echo "add disk2 1" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/ini_groups/25:00:00:f0:99:87:94:a3/luns/mgmt
++echo "add 25:00:00:f0:99:87:94:a3" >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/ini_groups/25:00:00:f0:99:87:94:a3/initiators/mgmt
++
++echo 1 >/sys/kernel/scst_tgt/targets/qla2x00t/25:00:00:f0:98:87:92:f3/enabled
++
++The resulting overall SCST sysfs hierarchy with initiator
++25:00:00:f0:99:87:94:a3 connected will look like:
++
++/sys/kernel/scst_tgt
++|-- devices
++| |-- blockio
++| | |-- blocksize
++| | |-- exported
++| | | `-- export0 -> ../../../targets/qla2x00t/25:00:00:f0:98:87:92:f3/luns/0
++| | |-- filename
++| | |-- handler -> ../../handlers/vdisk_blockio
++| | |-- nv_cache
++| | |-- read_only
++| | |-- removable
++| | |-- resync_size
++| | |-- size_mb
++| | |-- t10_dev_id
++| | |-- threads_num
++| | |-- threads_pool_type
++| | |-- type
++| | `-- usn
++| |-- cdrom
++| | |-- exported
++| | | `-- export0 -> ../../../targets/qla2x00t/25:00:00:f0:98:87:92:f3/luns/2
++| | |-- filename
++| | |-- handler -> ../../handlers/vcdrom
++| | |-- size_mb
++| | |-- t10_dev_id
++| | |-- threads_num
++| | |-- threads_pool_type
++| | |-- type
++| | `-- usn
++| |-- disk1
++| | |-- blocksize
++| | |-- exported
++| | | `-- export0 -> ../../../targets/qla2x00t/25:00:00:f0:98:87:92:f3/ini_groups/25:00:00:f0:99:87:94:a3/luns/0
++| | |-- filename
++| | |-- handler -> ../../handlers/vdisk_fileio
++| | |-- nv_cache
++| | |-- o_direct
++| | |-- read_only
++| | |-- removable
++| | |-- resync_size
++| | |-- size_mb
++| | |-- t10_dev_id
++| | |-- threads_num
++| | |-- threads_pool_type
++| | |-- type
++| | |-- usn
++| | `-- write_through
++| |-- disk2
++| | |-- blocksize
++| | |-- exported
++| | | `-- export0 -> ../../../targets/qla2x00t/25:00:00:f0:98:87:92:f3/ini_groups/25:00:00:f0:99:87:94:a3/luns/1
++| | |-- filename
++| | |-- handler -> ../../handlers/vdisk_fileio
++| | |-- nv_cache
++| | |-- o_direct
++| | |-- read_only
++| | |-- removable
++| | |-- resync_size
++| | |-- size_mb
++| | |-- t10_dev_id
++| | |-- threads_num
++| | |-- threads_pool_type
++| | |-- type
++| | |-- usn
++| | `-- write_through
++| `-- nullio
++| |-- blocksize
++| |-- exported
++| | `-- export0 -> ../../../targets/qla2x00t/25:00:00:f0:98:87:92:f3/luns/1
++| |-- handler -> ../../handlers/vdisk_nullio
++| |-- read_only
++| |-- removable
++| |-- size_mb
++| |-- t10_dev_id
++| |-- threads_num
++| |-- threads_pool_type
++| |-- type
++| `-- usn
++|-- handlers
++| |-- vcdrom
++| | |-- cdrom -> ../../devices/cdrom
++| | |-- mgmt
++| | |-- trace_level
++| | `-- type
++| |-- vdisk_blockio
++| | |-- blockio -> ../../devices/blockio
++| | |-- mgmt
++| | |-- trace_level
++| | `-- type
++| |-- vdisk_fileio
++| | |-- disk1 -> ../../devices/disk1
++| | |-- disk2 -> ../../devices/disk2
++| | |-- mgmt
++| | |-- trace_level
++| | `-- type
++| `-- vdisk_nullio
++| |-- mgmt
++| |-- nullio -> ../../devices/nullio
++| |-- trace_level
++| `-- type
++|-- sgv
++| |-- global_stats
++| |-- sgv
++| | `-- stats
++| |-- sgv-clust
++| | `-- stats
++| `-- sgv-dma
++| `-- stats
++|-- targets
++| `-- qla2x00t
++| |-- 25:00:00:f0:98:87:92:f3
++| | |-- enabled
++| | |-- explicit_confirmation
++| | |-- host -> ../../../../../class/scsi_host/host4
++| | |-- ini_groups
++| | | |-- 25:00:00:f0:99:87:94:a3
++| | | | |-- initiators
++| | | | | |-- 25:00:00:f0:99:87:94:a3
++| | | | | `-- mgmt
++| | | | `-- luns
++| | | | |-- 0
++| | | | | |-- device -> ../../../../../../../devices/disk1
++| | | | | `-- read_only
++| | | | |-- 1
++| | | | | |-- device -> ../../../../../../../devices/disk2
++| | | | | `-- read_only
++| | | | `-- mgmt
++| | | `-- mgmt
++| | |-- luns
++| | | |-- 0
++| | | | |-- device -> ../../../../../devices/blockio
++| | | | `-- read_only
++| | | |-- 1
++| | | | |-- device -> ../../../../../devices/nullio
++| | | | `-- read_only
++| | | |-- 2
++| | | | |-- device -> ../../../../../devices/cdrom
++| | | | `-- read_only
++| | | `-- mgmt
++| | |-- rel_tgt_id
++| | |-- hw_target
++| | `-- sessions
++| | `-- 25:00:00:f0:99:87:94:a3
++| | |-- active_commands
++| | |-- commands
++| | |-- initiator_name
++| | `-- luns -> ../../ini_groups/25:00:00:f0:99:87:94:a3/luns
++| |-- trace_level
++| |-- version
++| `-- mgmt
++|-- threads
++|-- trace_level
++`-- version
++
++
++Performance advices
++-------------------
++
++1. If you are going to use your target in an VM environment, for
++instance as a shared storage with VMware, make sure all your VMs
++connected to the target via *separate* sessions. You can check it using
++SCST proc or sysfs interface. You should use available facilities, like
++NPIV, to make separate sessions for each VM. If you miss it, you can
++greatly loose performance of parallel access to your target from
++different VMs. This isn't related to the case if your VMs are using the
++same shared storage, like with VMFS, for instance. In this case all your
++VM hosts will be connected to the target via separate sessions, which is
++enough.
++
++2. See SCST core's README for more advices. Especially pay attention to
++have io_grouping_type option set correctly.
++
++
++Credits
++-------
++
++Thanks to:
++
++ * QLogic support for their invaluable help.
++
++ * Nathaniel Clark <nate@misrule.us> for porting to new 2.6 kernel
++initiator driver.
++
++ * Mark Buechler <mark.buechler@gmail.com> for the original
++WWN-based authentification, a lot of useful suggestions, bug reports and
++help in debugging.
++
++ * Ming Zhang <mingz@ele.uri.edu> for fixes.
++
++ * Uri Yanai <Uri.Yanai@ngsoft.com> and Dorit Halsadi
++<Dorit.Halsadi@dothill.com> for adding full NPIV support.
++
++Vladislav Bolkhovitin <vst@vlnb.net>, http://scst.sourceforge.net
+This patch adds the kernel module ib_srpt, which is a SCSI RDMA Protocol (SRP)
+target implementation. This driver uses the InfiniBand stack and the SCST core.
+
+It is a high performance driver capable of handling 600K+ 4K random write
+IOPS by a single target as well as 2.5+ GB/s sequential throughput over
+a single QDR IB port.
+
+It was originally developed by Vu Pham (Mellanox) and has been optimized by
+Bart Van Assche.
+
+Signed-off-by: Bart Van Assche <bvanassche@acm.org>
+Cc: Vu Pham <vu@mellanox.com>
+Cc: Roland Dreier <rdreier@cisco.com>
+Cc: David Dillow <dillowda@ornl.gov>
+diff -uprN orig/linux-3.2/Documentation/scst/README.srpt linux-3.2/Documentation/scst/README.srpt
+--- orig/linux-3.2/Documentation/scst/README.srpt
++++ linux-3.2/Documentation/scst/README.srpt
+@@ -0,0 +1,112 @@
++SCSI RDMA Protocol (SRP) Target driver for Linux
++=================================================
++
++The SRP Target driver is designed to work directly on top of the
++OpenFabrics OFED-1.x software stack (http://www.openfabrics.org) or
++the Infiniband drivers in the Linux kernel tree
++(http://www.kernel.org). The SRP target driver also interfaces with
++the generic SCSI target mid-level driver called SCST
++(http://scst.sourceforge.net).
++
++How-to run
++-----------
++
++A. On srp target machine
++1. Please refer to SCST's README for loading scst driver and its
++dev_handlers drivers (scst_disk, scst_vdisk block or file IO mode, nullio, ...)
++
++Example 1: working with real back-end scsi disks
++a. modprobe scst
++b. modprobe scst_disk
++c. cat /proc/scsi_tgt/scsi_tgt
++
++ibstor00:~ # cat /proc/scsi_tgt/scsi_tgt
++Device (host:ch:id:lun or name) Device handler
++0:0:0:0 dev_disk
++4:0:0:0 dev_disk
++5:0:0:0 dev_disk
++6:0:0:0 dev_disk
++7:0:0:0 dev_disk
++
++Now you want to exclude the first scsi disk and expose the last 4 scsi disks as
++IB/SRP luns for I/O
++echo "add 4:0:0:0 0" >/proc/scsi_tgt/groups/Default/devices
++echo "add 5:0:0:0 1" >/proc/scsi_tgt/groups/Default/devices
++echo "add 6:0:0:0 2" >/proc/scsi_tgt/groups/Default/devices
++echo "add 7:0:0:0 3" >/proc/scsi_tgt/groups/Default/devices
++
++Example 2: working with VDISK FILEIO mode (using md0 device and file 10G-file)
++a. modprobe scst
++b. modprobe scst_vdisk
++c. echo "open vdisk0 /dev/md0" > /proc/scsi_tgt/vdisk/vdisk
++d. echo "open vdisk1 /10G-file" > /proc/scsi_tgt/vdisk/vdisk
++e. echo "add vdisk0 0" >/proc/scsi_tgt/groups/Default/devices
++f. echo "add vdisk1 1" >/proc/scsi_tgt/groups/Default/devices
++
++Example 3: working with VDISK BLOCKIO mode (using md0 device, sda, and cciss/c1d0)
++a. modprobe scst
++b. modprobe scst_vdisk
++c. echo "open vdisk0 /dev/md0 BLOCKIO" > /proc/scsi_tgt/vdisk/vdisk
++d. echo "open vdisk1 /dev/sda BLOCKIO" > /proc/scsi_tgt/vdisk/vdisk
++e. echo "open vdisk2 /dev/cciss/c1d0 BLOCKIO" > /proc/scsi_tgt/vdisk/vdisk
++f. echo "add vdisk0 0" >/proc/scsi_tgt/groups/Default/devices
++g. echo "add vdisk1 1" >/proc/scsi_tgt/groups/Default/devices
++h. echo "add vdisk2 2" >/proc/scsi_tgt/groups/Default/devices
++
++2. modprobe ib_srpt
++
++
++B. On initiator machines you can manualy do the following steps:
++1. modprobe ib_srp
++2. ibsrpdm -c (to discover new SRP target)
++3. echo <new target info> > /sys/class/infiniband_srp/srp-mthca0-1/add_target
++4. fdisk -l (will show new discovered scsi disks)
++
++Example:
++Assume that you use port 1 of first HCA in the system ie. mthca0
++
++[root@lab104 ~]# ibsrpdm -c -d /dev/infiniband/umad0
++id_ext=0002c90200226cf4,ioc_guid=0002c90200226cf4,
++dgid=fe800000000000000002c90200226cf5,pkey=ffff,service_id=0002c90200226cf4
++[root@lab104 ~]# echo id_ext=0002c90200226cf4,ioc_guid=0002c90200226cf4,
++dgid=fe800000000000000002c90200226cf5,pkey=ffff,service_id=0002c90200226cf4 >
++/sys/class/infiniband_srp/srp-mthca0-1/add_target
++
++OR
++
+++ You can edit /etc/infiniband/openib.conf to load srp driver and srp HA daemon
++automatically ie. set SRP_LOAD=yes, and SRPHA_ENABLE=yes
+++ To set up and use high availability feature you need dm-multipath driver
++and multipath tool
+++ Please refer to OFED-1.x SRP's user manual for more in-details instructions
++on how-to enable/use HA feature
++
++To minimize QUEUE_FULL conditions, you can apply scst_increase_max_tgt_cmds
++patch from SRPT package from http://sourceforge.net/project/showfiles.php?group_id=110471
++
++
++Performance notes
++-----------------
++
++In some cases, for instance working with SSD devices, which consume 100%
++of a single CPU load for data transfers in their internal threads, to
++maximize IOPS it can be needed to assign for those threads dedicated
++CPUs using Linux CPU affinity facilities. No IRQ processing should be
++done on those CPUs. Check that using /proc/interrupts. See taskset
++command and Documentation/IRQ-affinity.txt in your kernel's source tree
++for how to assign CPU affinity to tasks and IRQs.
++
++The reason for that is that processing of coming commands in SIRQ context
++can be done on the same CPUs as SSD devices' threads doing data
++transfers. As the result, those threads won't receive all the CPU power
++and perform worse.
++
++Alternatively to CPU affinity assignment, you can try to enable SRP
++target's internal thread. It will allows Linux CPU scheduler to better
++distribute load among available CPUs. To enable SRP target driver's
++internal thread you should load ib_srpt module with parameter
++"thread=1".
++
++
++Send questions about this driver to scst-devel@lists.sourceforge.net, CC:
++Vu Pham <vuhuong@mellanox.com> and Bart Van Assche <bvanassche@acm.org>.
+diff -uprN orig/linux-3.2/drivers/scst/srpt/Kconfig linux-3.2/drivers/scst/srpt/Kconfig
+--- orig/linux-3.2/drivers/scst/srpt/Kconfig
++++ linux-3.2/drivers/scst/srpt/Kconfig
+@@ -0,0 +1,12 @@
++config SCST_SRPT
++ tristate "InfiniBand SCSI RDMA Protocol target support"
++ depends on INFINIBAND && SCST
++ ---help---
++
++ Support for the SCSI RDMA Protocol (SRP) Target driver. The
++ SRP protocol is a protocol that allows an initiator to access
++ a block storage device on another host (target) over a network
++ that supports the RDMA protocol. Currently the RDMA protocol is
++ supported by InfiniBand and by iWarp network hardware. More
++ information about the SRP protocol can be found on the website
++ of the INCITS T10 technical committee (http://www.t10.org/).
+diff -uprN orig/linux-3.2/drivers/scst/srpt/Makefile linux-3.2/drivers/scst/srpt/Makefile
+--- orig/linux-3.2/drivers/scst/srpt/Makefile
++++ linux-3.2/drivers/scst/srpt/Makefile
+@@ -0,0 +1,1 @@
++obj-$(CONFIG_SCST_SRPT) += ib_srpt.o
+diff -uprN orig/linux-3.2/drivers/scst/srpt/ib_dm_mad.h linux-3.2/drivers/scst/srpt/ib_dm_mad.h
+--- orig/linux-3.2/drivers/scst/srpt/ib_dm_mad.h
++++ linux-3.2/drivers/scst/srpt/ib_dm_mad.h
+@@ -0,0 +1,139 @@
++/*
++ * Copyright (c) 2006 - 2009 Mellanox Technology Inc. All rights reserved.
++ *
++ * This software is available to you under a choice of one of two
++ * licenses. You may choose to be licensed under the terms of the GNU
++ * General Public License (GPL) Version 2, available from the file
++ * COPYING in the main directory of this source tree, or the
++ * OpenIB.org BSD license below:
++ *
++ * Redistribution and use in source and binary forms, with or
++ * without modification, are permitted provided that the following
++ * conditions are met:
++ *
++ * - Redistributions of source code must retain the above
++ * copyright notice, this list of conditions and the following
++ * disclaimer.
++ *
++ * - Redistributions in binary form must reproduce the above
++ * copyright notice, this list of conditions and the following
++ * disclaimer in the documentation and/or other materials
++ * provided with the distribution.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
++ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
++ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ *
++ */
++
++#ifndef IB_DM_MAD_H
++#define IB_DM_MAD_H
++
++#include <linux/types.h>
++
++#include <rdma/ib_mad.h>
++
++enum {
++ /*
++ * See also section 13.4.7 Status Field, table 115 MAD Common Status
++ * Field Bit Values and also section 16.3.1.1 Status Field in the
++ * InfiniBand Architecture Specification.
++ */
++ DM_MAD_STATUS_UNSUP_METHOD = 0x0008,
++ DM_MAD_STATUS_UNSUP_METHOD_ATTR = 0x000c,
++ DM_MAD_STATUS_INVALID_FIELD = 0x001c,
++ DM_MAD_STATUS_NO_IOC = 0x0100,
++
++ /*
++ * See also the Device Management chapter, section 16.3.3 Attributes,
++ * table 279 Device Management Attributes in the InfiniBand
++ * Architecture Specification.
++ */
++ DM_ATTR_CLASS_PORT_INFO = 0x01,
++ DM_ATTR_IOU_INFO = 0x10,
++ DM_ATTR_IOC_PROFILE = 0x11,
++ DM_ATTR_SVC_ENTRIES = 0x12
++};
++
++struct ib_dm_hdr {
++ u8 reserved[28];
++};
++
++/*
++ * Structure of management datagram sent by the SRP target implementation.
++ * Contains a management datagram header, reliable multi-packet transaction
++ * protocol (RMPP) header and ib_dm_hdr. Notes:
++ * - The SRP target implementation does not use RMPP or ib_dm_hdr when sending
++ * management datagrams.
++ * - The header size must be exactly 64 bytes (IB_MGMT_DEVICE_HDR), since this
++ * is the header size that is passed to ib_create_send_mad() in ib_srpt.c.
++ * - The maximum supported size for a management datagram when not using RMPP
++ * is 256 bytes -- 64 bytes header and 192 (IB_MGMT_DEVICE_DATA) bytes data.
++ */
++struct ib_dm_mad {
++ struct ib_mad_hdr mad_hdr;
++ struct ib_rmpp_hdr rmpp_hdr;
++ struct ib_dm_hdr dm_hdr;
++ u8 data[IB_MGMT_DEVICE_DATA];
++};
++
++/*
++ * IOUnitInfo as defined in section 16.3.3.3 IOUnitInfo of the InfiniBand
++ * Architecture Specification.
++ */
++struct ib_dm_iou_info {
++ __be16 change_id;
++ u8 max_controllers;
++ u8 op_rom;
++ u8 controller_list[128];
++};
++
++/*
++ * IOControllerprofile as defined in section 16.3.3.4 IOControllerProfile of
++ * the InfiniBand Architecture Specification.
++ */
++struct ib_dm_ioc_profile {
++ __be64 guid;
++ __be32 vendor_id;
++ __be32 device_id;
++ __be16 device_version;
++ __be16 reserved1;
++ __be32 subsys_vendor_id;
++ __be32 subsys_device_id;
++ __be16 io_class;
++ __be16 io_subclass;
++ __be16 protocol;
++ __be16 protocol_version;
++ __be16 service_conn;
++ __be16 initiators_supported;
++ __be16 send_queue_depth;
++ u8 reserved2;
++ u8 rdma_read_depth;
++ __be32 send_size;
++ __be32 rdma_size;
++ u8 op_cap_mask;
++ u8 svc_cap_mask;
++ u8 num_svc_entries;
++ u8 reserved3[9];
++ u8 id_string[64];
++};
++
++struct ib_dm_svc_entry {
++ u8 name[40];
++ __be64 id;
++};
++
++/*
++ * See also section 16.3.3.5 ServiceEntries in the InfiniBand Architecture
++ * Specification. See also section B.7, table B.8 in the T10 SRP r16a document.
++ */
++struct ib_dm_svc_entries {
++ struct ib_dm_svc_entry service_entries[4];
++};
++
++#endif
+diff -uprN orig/linux-3.2/drivers/scst/srpt/ib_srpt.c linux-3.2/drivers/scst/srpt/ib_srpt.c
+--- orig/linux-3.2/drivers/scst/srpt/ib_srpt.c
++++ linux-3.2/drivers/scst/srpt/ib_srpt.c
+@@ -0,0 +1,3850 @@
++/*
++ * Copyright (c) 2006 - 2009 Mellanox Technology Inc. All rights reserved.
++ * Copyright (C) 2008 - 2011 Bart Van Assche <bvanassche@acm.org>.
++ * Copyright (C) 2008 Vladislav Bolkhovitin <vst@vlnb.net>
++ *
++ * This software is available to you under a choice of one of two
++ * licenses. You may choose to be licensed under the terms of the GNU
++ * General Public License (GPL) Version 2, available from the file
++ * COPYING in the main directory of this source tree, or the
++ * OpenIB.org BSD license below:
++ *
++ * Redistribution and use in source and binary forms, with or
++ * without modification, are permitted provided that the following
++ * conditions are met:
++ *
++ * - Redistributions of source code must retain the above
++ * copyright notice, this list of conditions and the following
++ * disclaimer.
++ *
++ * - Redistributions in binary form must reproduce the above
++ * copyright notice, this list of conditions and the following
++ * disclaimer in the documentation and/or other materials
++ * provided with the distribution.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
++ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
++ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ *
++ */
++
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/slab.h>
++#include <linux/err.h>
++#include <linux/ctype.h>
++#include <linux/kthread.h>
++#include <linux/string.h>
++#include <linux/delay.h>
++#include <asm/atomic.h>
++#include "ib_srpt.h"
++#define LOG_PREFIX "ib_srpt" /* Prefix for SCST tracing macros. */
++#include <scst/scst_debug.h>
++
++/* Name of this kernel module. */
++#define DRV_NAME "ib_srpt"
++#define DRV_VERSION "2.2.1-pre"
++#define DRV_RELDATE "(not yet released)"
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++/* Flags to be used in SCST debug tracing statements. */
++#define DEFAULT_SRPT_TRACE_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR \
++ | TRACE_MGMT | TRACE_SPECIAL)
++/* Name of the entry that will be created under /proc/scsi_tgt/ib_srpt. */
++#define SRPT_PROC_TRACE_LEVEL_NAME "trace_level"
++#endif
++
++#define SRPT_ID_STRING "SCST SRP target"
++
++MODULE_AUTHOR("Vu Pham and Bart Van Assche");
++MODULE_DESCRIPTION("InfiniBand SCSI RDMA Protocol target "
++ "v" DRV_VERSION " (" DRV_RELDATE ")");
++MODULE_LICENSE("Dual BSD/GPL");
++
++/*
++ * Global Variables
++ */
++
++static u64 srpt_service_guid;
++/* List of srpt_device structures. */
++static atomic_t srpt_device_count;
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++static unsigned long trace_flag = DEFAULT_SRPT_TRACE_FLAGS;
++module_param(trace_flag, long, 0644);
++MODULE_PARM_DESC(trace_flag, "SCST trace flags.");
++#endif
++
++static unsigned srp_max_rdma_size = DEFAULT_MAX_RDMA_SIZE;
++module_param(srp_max_rdma_size, int, 0644);
++MODULE_PARM_DESC(srp_max_rdma_size,
++ "Maximum size of SRP RDMA transfers for new connections.");
++
++static unsigned srp_max_req_size = DEFAULT_MAX_REQ_SIZE;
++module_param(srp_max_req_size, int, 0444);
++MODULE_PARM_DESC(srp_max_req_size,
++ "Maximum size of SRP request messages in bytes.");
++
++static unsigned int srp_max_rsp_size = DEFAULT_MAX_RSP_SIZE;
++module_param(srp_max_rsp_size, int, S_IRUGO | S_IWUSR);
++MODULE_PARM_DESC(srp_max_rsp_size,
++ "Maximum size of SRP response messages in bytes.");
++
++static int srpt_srq_size = DEFAULT_SRPT_SRQ_SIZE;
++module_param(srpt_srq_size, int, S_IRUGO | S_IWUSR);
++MODULE_PARM_DESC(srpt_srq_size,
++ "Shared receive queue (SRQ) size.");
++
++static int srpt_sq_size = DEF_SRPT_SQ_SIZE;
++module_param(srpt_sq_size, int, 0444);
++MODULE_PARM_DESC(srpt_sq_size,
++ "Per-channel send queue (SQ) size.");
++
++static bool use_port_guid_in_session_name;
++module_param(use_port_guid_in_session_name, bool, 0444);
++MODULE_PARM_DESC(use_port_guid_in_session_name,
++ "Use target port ID in the session name such that"
++ " redundant paths between multiport systems can be masked.");
++
++static bool use_node_guid_in_target_name;
++module_param(use_node_guid_in_target_name, bool, 0444);
++MODULE_PARM_DESC(use_node_guid_in_target_name,
++ "Use target node GUIDs of HCAs as SCST target names.");
++
++static int srpt_get_u64_x(char *buffer, struct kernel_param *kp)
++{
++ return sprintf(buffer, "0x%016llx", *(u64 *)kp->arg);
++}
++module_param_call(srpt_service_guid, NULL, srpt_get_u64_x, &srpt_service_guid,
++ 0444);
++MODULE_PARM_DESC(srpt_service_guid,
++ "Using this value for ioc_guid, id_ext, and cm_listen_id"
++ " instead of using the node_guid of the first HCA.");
++
++static struct ib_client srpt_client;
++static void srpt_unregister_mad_agent(struct srpt_device *sdev);
++static void srpt_unmap_sg_to_ib_sge(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx);
++static void srpt_drain_channel(struct ib_cm_id *cm_id);
++static void srpt_free_ch(struct scst_session *sess);
++
++static enum rdma_ch_state srpt_set_ch_state_to_disc(struct srpt_rdma_ch *ch)
++{
++ unsigned long flags;
++ enum rdma_ch_state prev;
++ bool changed = false;
++
++ spin_lock_irqsave(&ch->spinlock, flags);
++ prev = ch->state;
++ switch (prev) {
++ case CH_CONNECTING:
++ case CH_LIVE:
++ ch->state = CH_DISCONNECTING;
++ wake_up_process(ch->thread);
++ changed = true;
++ break;
++ default:
++ break;
++ }
++ spin_unlock_irqrestore(&ch->spinlock, flags);
++
++ return prev;
++}
++
++static bool srpt_set_ch_state_to_draining(struct srpt_rdma_ch *ch)
++{
++ unsigned long flags;
++ bool changed = false;
++
++ spin_lock_irqsave(&ch->spinlock, flags);
++ switch (ch->state) {
++ case CH_CONNECTING:
++ case CH_LIVE:
++ case CH_DISCONNECTING:
++ ch->state = CH_DRAINING;
++ wake_up_process(ch->thread);
++ changed = true;
++ break;
++ default:
++ break;
++ }
++ spin_unlock_irqrestore(&ch->spinlock, flags);
++
++ return changed;
++}
++
++/**
++ * srpt_test_and_set_ch_state() - Test and set the channel state.
++ *
++ * Returns true if and only if the channel state has been set to the new state.
++ */
++static bool srpt_test_and_set_ch_state(struct srpt_rdma_ch *ch,
++ enum rdma_ch_state old,
++ enum rdma_ch_state new)
++{
++ unsigned long flags;
++ bool changed = false;
++
++ spin_lock_irqsave(&ch->spinlock, flags);
++ if (ch->state == old) {
++ ch->state = new;
++ wake_up_process(ch->thread);
++ changed = true;
++ }
++ spin_unlock_irqrestore(&ch->spinlock, flags);
++
++ return changed;
++}
++
++/**
++ * srpt_adjust_req_lim() - Adjust ch->req_lim and ch->req_lim_delta atomically.
++ *
++ * Returns the new value of ch->req_lim.
++ */
++static int srpt_adjust_req_lim(struct srpt_rdma_ch *ch, int req_lim_change,
++ int req_lim_delta_change)
++{
++ int req_lim;
++ unsigned long flags;
++
++ spin_lock_irqsave(&ch->spinlock, flags);
++ ch->req_lim += req_lim_change;
++ req_lim = ch->req_lim;
++ ch->req_lim_delta += req_lim_delta_change;
++ spin_unlock_irqrestore(&ch->spinlock, flags);
++
++ return req_lim;
++}
++
++/**
++ * srpt_inc_req_lim() - Increase ch->req_lim and decrease ch->req_lim_delta.
++ *
++ * Returns one more than the previous value of ch->req_lim_delta.
++ */
++static int srpt_inc_req_lim(struct srpt_rdma_ch *ch)
++{
++ int req_lim_delta;
++ unsigned long flags;
++
++ spin_lock_irqsave(&ch->spinlock, flags);
++ req_lim_delta = ch->req_lim_delta + 1;
++ ch->req_lim += req_lim_delta;
++ ch->req_lim_delta = 0;
++ spin_unlock_irqrestore(&ch->spinlock, flags);
++
++ return req_lim_delta;
++}
++
++/**
++ * srpt_undo_inc_req_lim() - Undo the effect of srpt_inc_req_lim.
++ */
++static int srpt_undo_inc_req_lim(struct srpt_rdma_ch *ch, int req_lim_delta)
++{
++ return srpt_adjust_req_lim(ch, -req_lim_delta, req_lim_delta - 1);
++}
++
++/**
++ * srpt_event_handler() - Asynchronous IB event callback function.
++ *
++ * Callback function called by the InfiniBand core when an asynchronous IB
++ * event occurs. This callback may occur in interrupt context. See also
++ * section 11.5.2, Set Asynchronous Event Handler in the InfiniBand
++ * Architecture Specification.
++ */
++static void srpt_event_handler(struct ib_event_handler *handler,
++ struct ib_event *event)
++{
++ struct srpt_device *sdev;
++ struct srpt_port *sport;
++ u8 port_num;
++
++ TRACE_ENTRY();
++
++ sdev = ib_get_client_data(event->device, &srpt_client);
++ if (!sdev || sdev->device != event->device)
++ return;
++
++ TRACE_DBG("ASYNC event= %d on device= %s",
++ event->event, sdev->device->name);
++
++ switch (event->event) {
++ case IB_EVENT_PORT_ERR:
++ port_num = event->element.port_num - 1;
++ if (port_num < sdev->device->phys_port_cnt) {
++ sport = &sdev->port[port_num];
++ sport->lid = 0;
++ sport->sm_lid = 0;
++ } else {
++ WARN(true, "event %d: port_num %d out of range 1..%d\n",
++ event->event, port_num + 1,
++ sdev->device->phys_port_cnt);
++ }
++ break;
++ case IB_EVENT_PORT_ACTIVE:
++ case IB_EVENT_LID_CHANGE:
++ case IB_EVENT_PKEY_CHANGE:
++ case IB_EVENT_SM_CHANGE:
++ case IB_EVENT_CLIENT_REREGISTER:
++ /* Refresh port data asynchronously. */
++ port_num = event->element.port_num - 1;
++ if (port_num < sdev->device->phys_port_cnt) {
++ sport = &sdev->port[port_num];
++ if (!sport->lid && !sport->sm_lid)
++ schedule_work(&sport->work);
++ } else {
++ WARN(true, "event %d: port_num %d out of range 1..%d\n",
++ event->event, port_num + 1,
++ sdev->device->phys_port_cnt);
++ }
++ break;
++ default:
++ PRINT_ERROR("received unrecognized IB event %d", event->event);
++ break;
++ }
++
++ TRACE_EXIT();
++}
++
++/**
++ * srpt_srq_event() - IB SRQ event callback function.
++ */
++static void srpt_srq_event(struct ib_event *event, void *ctx)
++{
++ TRACE_DBG("SRQ event %d", event->event);
++}
++
++static const char *get_ch_state_name(enum rdma_ch_state s)
++{
++ switch (s) {
++ case CH_CONNECTING:
++ return "connecting";
++ case CH_LIVE:
++ return "live";
++ case CH_DISCONNECTING:
++ return "disconnecting";
++ case CH_DRAINING:
++ return "draining";
++ case CH_FREEING:
++ return "freeing";
++ }
++ return "???";
++}
++
++/**
++ * srpt_qp_event() - IB QP event callback function.
++ */
++static void srpt_qp_event(struct ib_event *event, struct srpt_rdma_ch *ch)
++{
++ TRACE_DBG("QP event %d on cm_id=%p sess_name=%s state=%s",
++ event->event, ch->cm_id, ch->sess_name,
++ get_ch_state_name(ch->state));
++
++ switch (event->event) {
++ case IB_EVENT_COMM_EST:
++ ib_cm_notify(ch->cm_id, event->event);
++ break;
++ case IB_EVENT_QP_LAST_WQE_REACHED:
++ TRACE_DBG("%s, state %s: received Last WQE event.",
++ ch->sess_name, get_ch_state_name(ch->state));
++ ch->last_wqe_received = true;
++ BUG_ON(!ch->thread);
++ wake_up_process(ch->thread);
++ break;
++ default:
++ PRINT_ERROR("received unrecognized IB QP event %d",
++ event->event);
++ break;
++ }
++}
++
++/**
++ * srpt_set_ioc() - Helper function for initializing an IOUnitInfo structure.
++ *
++ * @slot: one-based slot number.
++ * @value: four-bit value.
++ *
++ * Copies the lowest four bits of value in element slot of the array of four
++ * bit elements called c_list (controller list). The index slot is one-based.
++ */
++static void srpt_set_ioc(u8 *c_list, u32 slot, u8 value)
++{
++ u16 id;
++ u8 tmp;
++
++ id = (slot - 1) / 2;
++ if (slot & 0x1) {
++ tmp = c_list[id] & 0xf;
++ c_list[id] = (value << 4) | tmp;
++ } else {
++ tmp = c_list[id] & 0xf0;
++ c_list[id] = (value & 0xf) | tmp;
++ }
++}
++
++/**
++ * srpt_get_class_port_info() - Copy ClassPortInfo to a management datagram.
++ *
++ * See also section 16.3.3.1 ClassPortInfo in the InfiniBand Architecture
++ * Specification.
++ */
++static void srpt_get_class_port_info(struct ib_dm_mad *mad)
++{
++ struct ib_class_port_info *cif;
++
++ cif = (struct ib_class_port_info *)mad->data;
++ memset(cif, 0, sizeof *cif);
++ cif->base_version = 1;
++ cif->class_version = 1;
++ cif->resp_time_value = 20;
++
++ mad->mad_hdr.status = 0;
++}
++
++/**
++ * srpt_get_iou() - Write IOUnitInfo to a management datagram.
++ *
++ * See also section 16.3.3.3 IOUnitInfo in the InfiniBand Architecture
++ * Specification. See also section B.7, table B.6 in the SRP r16a document.
++ */
++static void srpt_get_iou(struct ib_dm_mad *mad)
++{
++ struct ib_dm_iou_info *ioui;
++ u8 slot;
++ int i;
++
++ ioui = (struct ib_dm_iou_info *)mad->data;
++ ioui->change_id = cpu_to_be16(1);
++ ioui->max_controllers = 16;
++
++ /* set present for slot 1 and empty for the rest */
++ srpt_set_ioc(ioui->controller_list, 1, 1);
++ for (i = 1, slot = 2; i < 16; i++, slot++)
++ srpt_set_ioc(ioui->controller_list, slot, 0);
++
++ mad->mad_hdr.status = 0;
++}
++
++/**
++ * srpt_get_ioc() - Write IOControllerprofile to a management datagram.
++ *
++ * See also section 16.3.3.4 IOControllerProfile in the InfiniBand
++ * Architecture Specification. See also section B.7, table B.7 in the SRP
++ * r16a document.
++ */
++static void srpt_get_ioc(struct srpt_device *sdev, u32 slot,
++ struct ib_dm_mad *mad)
++{
++ struct ib_dm_ioc_profile *iocp;
++
++ iocp = (struct ib_dm_ioc_profile *)mad->data;
++
++ if (!slot || slot > 16) {
++ mad->mad_hdr.status = cpu_to_be16(DM_MAD_STATUS_INVALID_FIELD);
++ return;
++ }
++
++ if (slot > 2) {
++ mad->mad_hdr.status = cpu_to_be16(DM_MAD_STATUS_NO_IOC);
++ return;
++ }
++
++ memset(iocp, 0, sizeof *iocp);
++ strcpy(iocp->id_string, SRPT_ID_STRING);
++ iocp->guid = cpu_to_be64(srpt_service_guid);
++ iocp->vendor_id = cpu_to_be32(sdev->dev_attr.vendor_id);
++ iocp->device_id = cpu_to_be32(sdev->dev_attr.vendor_part_id);
++ iocp->device_version = cpu_to_be16(sdev->dev_attr.hw_ver);
++ iocp->subsys_vendor_id = cpu_to_be32(sdev->dev_attr.vendor_id);
++ iocp->subsys_device_id = 0x0;
++ iocp->io_class = cpu_to_be16(SRP_REV16A_IB_IO_CLASS);
++ iocp->io_subclass = cpu_to_be16(SRP_IO_SUBCLASS);
++ iocp->protocol = cpu_to_be16(SRP_PROTOCOL);
++ iocp->protocol_version = cpu_to_be16(SRP_PROTOCOL_VERSION);
++ iocp->send_queue_depth = cpu_to_be16(sdev->srq_size);
++ iocp->rdma_read_depth = 4;
++ iocp->send_size = cpu_to_be32(srp_max_req_size);
++ iocp->rdma_size = cpu_to_be32(min(max(srp_max_rdma_size, 256U),
++ 1U << 24));
++ iocp->num_svc_entries = 1;
++ iocp->op_cap_mask = SRP_SEND_TO_IOC | SRP_SEND_FROM_IOC |
++ SRP_RDMA_READ_FROM_IOC | SRP_RDMA_WRITE_FROM_IOC;
++
++ mad->mad_hdr.status = 0;
++}
++
++/**
++ * srpt_get_svc_entries() - Write ServiceEntries to a management datagram.
++ *
++ * See also section 16.3.3.5 ServiceEntries in the InfiniBand Architecture
++ * Specification. See also section B.7, table B.8 in the SRP r16a document.
++ */
++static void srpt_get_svc_entries(u64 ioc_guid,
++ u16 slot, u8 hi, u8 lo, struct ib_dm_mad *mad)
++{
++ struct ib_dm_svc_entries *svc_entries;
++
++ WARN_ON(!ioc_guid);
++
++ if (!slot || slot > 16) {
++ mad->mad_hdr.status = cpu_to_be16(DM_MAD_STATUS_INVALID_FIELD);
++ return;
++ }
++
++ if (slot > 2 || lo > hi || hi > 1) {
++ mad->mad_hdr.status = cpu_to_be16(DM_MAD_STATUS_NO_IOC);
++ return;
++ }
++
++ svc_entries = (struct ib_dm_svc_entries *)mad->data;
++ memset(svc_entries, 0, sizeof *svc_entries);
++ svc_entries->service_entries[0].id = cpu_to_be64(ioc_guid);
++ snprintf(svc_entries->service_entries[0].name,
++ sizeof(svc_entries->service_entries[0].name),
++ "%s%016llx",
++ SRP_SERVICE_NAME_PREFIX,
++ ioc_guid);
++
++ mad->mad_hdr.status = 0;
++}
++
++/**
++ * srpt_mgmt_method_get() - Process a received management datagram.
++ * @sp: source port through which the MAD has been received.
++ * @rq_mad: received MAD.
++ * @rsp_mad: response MAD.
++ */
++static void srpt_mgmt_method_get(struct srpt_port *sp, struct ib_mad *rq_mad,
++ struct ib_dm_mad *rsp_mad)
++{
++ u16 attr_id;
++ u32 slot;
++ u8 hi, lo;
++
++ attr_id = be16_to_cpu(rq_mad->mad_hdr.attr_id);
++ switch (attr_id) {
++ case DM_ATTR_CLASS_PORT_INFO:
++ srpt_get_class_port_info(rsp_mad);
++ break;
++ case DM_ATTR_IOU_INFO:
++ srpt_get_iou(rsp_mad);
++ break;
++ case DM_ATTR_IOC_PROFILE:
++ slot = be32_to_cpu(rq_mad->mad_hdr.attr_mod);
++ srpt_get_ioc(sp->sdev, slot, rsp_mad);
++ break;
++ case DM_ATTR_SVC_ENTRIES:
++ slot = be32_to_cpu(rq_mad->mad_hdr.attr_mod);
++ hi = (u8) ((slot >> 8) & 0xff);
++ lo = (u8) (slot & 0xff);
++ slot = (u16) ((slot >> 16) & 0xffff);
++ srpt_get_svc_entries(srpt_service_guid,
++ slot, hi, lo, rsp_mad);
++ break;
++ default:
++ rsp_mad->mad_hdr.status =
++ cpu_to_be16(DM_MAD_STATUS_UNSUP_METHOD_ATTR);
++ break;
++ }
++}
++
++/**
++ * srpt_mad_send_handler() - Post MAD-send callback function.
++ */
++static void srpt_mad_send_handler(struct ib_mad_agent *mad_agent,
++ struct ib_mad_send_wc *mad_wc)
++{
++ ib_destroy_ah(mad_wc->send_buf->ah);
++ ib_free_send_mad(mad_wc->send_buf);
++}
++
++/**
++ * srpt_mad_recv_handler() - MAD reception callback function.
++ */
++static void srpt_mad_recv_handler(struct ib_mad_agent *mad_agent,
++ struct ib_mad_recv_wc *mad_wc)
++{
++ struct srpt_port *sport = (struct srpt_port *)mad_agent->context;
++ struct ib_ah *ah;
++ struct ib_mad_send_buf *rsp;
++ struct ib_dm_mad *dm_mad;
++
++ if (!mad_wc || !mad_wc->recv_buf.mad)
++ return;
++
++ ah = ib_create_ah_from_wc(mad_agent->qp->pd, mad_wc->wc,
++ mad_wc->recv_buf.grh, mad_agent->port_num);
++ if (IS_ERR(ah))
++ goto err;
++
++ BUILD_BUG_ON(offsetof(struct ib_dm_mad, data) != IB_MGMT_DEVICE_HDR);
++
++ rsp = ib_create_send_mad(mad_agent, mad_wc->wc->src_qp,
++ mad_wc->wc->pkey_index, 0,
++ IB_MGMT_DEVICE_HDR, IB_MGMT_DEVICE_DATA,
++ GFP_KERNEL);
++ if (IS_ERR(rsp))
++ goto err_rsp;
++
++ rsp->ah = ah;
++
++ dm_mad = rsp->mad;
++ memcpy(dm_mad, mad_wc->recv_buf.mad, sizeof *dm_mad);
++ dm_mad->mad_hdr.method = IB_MGMT_METHOD_GET_RESP;
++ dm_mad->mad_hdr.status = 0;
++
++ switch (mad_wc->recv_buf.mad->mad_hdr.method) {
++ case IB_MGMT_METHOD_GET:
++ srpt_mgmt_method_get(sport, mad_wc->recv_buf.mad, dm_mad);
++ break;
++ case IB_MGMT_METHOD_SET:
++ dm_mad->mad_hdr.status =
++ cpu_to_be16(DM_MAD_STATUS_UNSUP_METHOD_ATTR);
++ break;
++ default:
++ dm_mad->mad_hdr.status =
++ cpu_to_be16(DM_MAD_STATUS_UNSUP_METHOD);
++ break;
++ }
++
++ if (!ib_post_send_mad(rsp, NULL)) {
++ ib_free_recv_mad(mad_wc);
++ /* will destroy_ah & free_send_mad in send completion */
++ return;
++ }
++
++ ib_free_send_mad(rsp);
++
++err_rsp:
++ ib_destroy_ah(ah);
++err:
++ ib_free_recv_mad(mad_wc);
++}
++
++/**
++ * srpt_refresh_port() - Configure a HCA port.
++ *
++ * Enable InfiniBand management datagram processing, update the cached sm_lid,
++ * lid and gid values, and register a callback function for processing MADs
++ * on the specified port.
++ *
++ * Note: It is safe to call this function more than once for the same port.
++ */
++static int srpt_refresh_port(struct srpt_port *sport)
++{
++ struct ib_mad_reg_req reg_req;
++ struct ib_port_modify port_modify;
++ struct ib_port_attr port_attr;
++ int ret;
++
++ TRACE_ENTRY();
++
++ memset(&port_modify, 0, sizeof port_modify);
++ port_modify.set_port_cap_mask = IB_PORT_DEVICE_MGMT_SUP;
++ port_modify.clr_port_cap_mask = 0;
++
++ ret = ib_modify_port(sport->sdev->device, sport->port, 0, &port_modify);
++ if (ret)
++ goto err_mod_port;
++
++ ret = ib_query_port(sport->sdev->device, sport->port, &port_attr);
++ if (ret)
++ goto err_query_port;
++
++ sport->sm_lid = port_attr.sm_lid;
++ sport->lid = port_attr.lid;
++
++ ret = ib_query_gid(sport->sdev->device, sport->port, 0, &sport->gid);
++ if (ret)
++ goto err_query_port;
++
++ if (!sport->mad_agent) {
++ memset(&reg_req, 0, sizeof reg_req);
++ reg_req.mgmt_class = IB_MGMT_CLASS_DEVICE_MGMT;
++ reg_req.mgmt_class_version = IB_MGMT_BASE_VERSION;
++ set_bit(IB_MGMT_METHOD_GET, reg_req.method_mask);
++ set_bit(IB_MGMT_METHOD_SET, reg_req.method_mask);
++
++ sport->mad_agent = ib_register_mad_agent(sport->sdev->device,
++ sport->port,
++ IB_QPT_GSI,
++ &reg_req, 0,
++ srpt_mad_send_handler,
++ srpt_mad_recv_handler,
++ sport);
++ if (IS_ERR(sport->mad_agent)) {
++ ret = PTR_ERR(sport->mad_agent);
++ sport->mad_agent = NULL;
++ goto err_query_port;
++ }
++ }
++
++ TRACE_EXIT_RES(0);
++
++ return 0;
++
++err_query_port:
++ port_modify.set_port_cap_mask = 0;
++ port_modify.clr_port_cap_mask = IB_PORT_DEVICE_MGMT_SUP;
++ ib_modify_port(sport->sdev->device, sport->port, 0, &port_modify);
++
++err_mod_port:
++ TRACE_EXIT_RES(ret);
++
++ return ret;
++}
++
++/**
++ * srpt_unregister_mad_agent() - Unregister MAD callback functions.
++ *
++ * Note: It is safe to call this function more than once for the same device.
++ */
++static void srpt_unregister_mad_agent(struct srpt_device *sdev)
++{
++ struct ib_port_modify port_modify = {
++ .clr_port_cap_mask = IB_PORT_DEVICE_MGMT_SUP,
++ };
++ struct srpt_port *sport;
++ int i;
++
++ for (i = 1; i <= sdev->device->phys_port_cnt; i++) {
++ sport = &sdev->port[i - 1];
++ WARN_ON(sport->port != i);
++ if (ib_modify_port(sdev->device, i, 0, &port_modify) < 0)
++ PRINT_ERROR("%s", "disabling MAD processing failed.");
++ if (sport->mad_agent) {
++ ib_unregister_mad_agent(sport->mad_agent);
++ sport->mad_agent = NULL;
++ }
++ }
++}
++
++/**
++ * srpt_alloc_ioctx() - Allocate an SRPT I/O context structure.
++ */
++static struct srpt_ioctx *srpt_alloc_ioctx(struct srpt_device *sdev,
++ int ioctx_size, int dma_size,
++ enum dma_data_direction dir)
++{
++ struct srpt_ioctx *ioctx;
++
++ ioctx = kmalloc(ioctx_size, GFP_KERNEL);
++ if (!ioctx)
++ goto err;
++
++ ioctx->buf = kmalloc(dma_size, GFP_KERNEL);
++ if (!ioctx->buf)
++ goto err_free_ioctx;
++
++ ioctx->dma = ib_dma_map_single(sdev->device, ioctx->buf, dma_size, dir);
++ if (ib_dma_mapping_error(sdev->device, ioctx->dma))
++ goto err_free_buf;
++
++ return ioctx;
++
++err_free_buf:
++ kfree(ioctx->buf);
++err_free_ioctx:
++ kfree(ioctx);
++err:
++ return NULL;
++}
++
++/**
++ * srpt_free_ioctx() - Free an SRPT I/O context structure.
++ */
++static void srpt_free_ioctx(struct srpt_device *sdev, struct srpt_ioctx *ioctx,
++ int dma_size, enum dma_data_direction dir)
++{
++ if (!ioctx)
++ return;
++
++ ib_dma_unmap_single(sdev->device, ioctx->dma, dma_size, dir);
++ kfree(ioctx->buf);
++ kfree(ioctx);
++}
++
++/**
++ * srpt_alloc_ioctx_ring() - Allocate a ring of SRPT I/O context structures.
++ * @sdev: Device to allocate the I/O context ring for.
++ * @ring_size: Number of elements in the I/O context ring.
++ * @ioctx_size: I/O context size.
++ * @dma_size: DMA buffer size.
++ * @dir: DMA data direction.
++ */
++static struct srpt_ioctx **srpt_alloc_ioctx_ring(struct srpt_device *sdev,
++ int ring_size, int ioctx_size,
++ int dma_size, enum dma_data_direction dir)
++{
++ struct srpt_ioctx **ring;
++ int i;
++
++ TRACE_ENTRY();
++
++ WARN_ON(ioctx_size != sizeof(struct srpt_recv_ioctx) &&
++ ioctx_size != sizeof(struct srpt_send_ioctx));
++
++ ring = kmalloc(ring_size * sizeof(ring[0]), GFP_KERNEL);
++ if (!ring)
++ goto out;
++ for (i = 0; i < ring_size; ++i) {
++ ring[i] = srpt_alloc_ioctx(sdev, ioctx_size, dma_size, dir);
++ if (!ring[i])
++ goto err;
++ ring[i]->index = i;
++ }
++ goto out;
++
++err:
++ while (--i >= 0)
++ srpt_free_ioctx(sdev, ring[i], dma_size, dir);
++ kfree(ring);
++ ring = NULL;
++out:
++ TRACE_EXIT_HRES(ring);
++ return ring;
++}
++
++/**
++ * srpt_free_ioctx_ring() - Free the ring of SRPT I/O context structures.
++ */
++static void srpt_free_ioctx_ring(struct srpt_ioctx **ioctx_ring,
++ struct srpt_device *sdev, int ring_size,
++ int dma_size, enum dma_data_direction dir)
++{
++ int i;
++
++ for (i = 0; i < ring_size; ++i)
++ srpt_free_ioctx(sdev, ioctx_ring[i], dma_size, dir);
++ kfree(ioctx_ring);
++}
++
++/**
++ * srpt_set_cmd_state() - Set the state of a SCSI command.
++ * @new: New state.
++ *
++ * Does not modify the state of aborted commands. Returns the previous command
++ * state.
++ */
++static enum srpt_command_state srpt_set_cmd_state(struct srpt_send_ioctx *ioctx,
++ enum srpt_command_state new)
++{
++ enum srpt_command_state previous;
++
++ BUG_ON(!ioctx);
++
++ spin_lock(&ioctx->spinlock);
++ previous = ioctx->state;
++ if (previous != SRPT_STATE_DONE)
++ ioctx->state = new;
++ spin_unlock(&ioctx->spinlock);
++
++ return previous;
++}
++
++/**
++ * srpt_test_and_set_cmd_state() - Test and set the state of a command.
++ *
++ * Returns true if and only if the previous command state was equal to 'old'.
++ */
++static bool srpt_test_and_set_cmd_state(struct srpt_send_ioctx *ioctx,
++ enum srpt_command_state old,
++ enum srpt_command_state new)
++{
++ enum srpt_command_state previous;
++
++ WARN_ON(!ioctx);
++ WARN_ON(old == SRPT_STATE_DONE);
++ WARN_ON(new == SRPT_STATE_NEW);
++
++ spin_lock(&ioctx->spinlock);
++ previous = ioctx->state;
++ if (previous == old)
++ ioctx->state = new;
++ spin_unlock(&ioctx->spinlock);
++
++ return previous == old;
++}
++
++/**
++ * srpt_post_recv() - Post an IB receive request.
++ */
++static int srpt_post_recv(struct srpt_device *sdev,
++ struct srpt_recv_ioctx *ioctx)
++{
++ struct ib_sge list;
++ struct ib_recv_wr wr, *bad_wr;
++
++ BUG_ON(!sdev);
++ wr.wr_id = encode_wr_id(SRPT_RECV, ioctx->ioctx.index);
++
++ list.addr = ioctx->ioctx.dma;
++ list.length = srp_max_req_size;
++ list.lkey = sdev->mr->lkey;
++
++ wr.next = NULL;
++ wr.sg_list = &list;
++ wr.num_sge = 1;
++
++ return ib_post_srq_recv(sdev->srq, &wr, &bad_wr);
++}
++
++static int srpt_adjust_srq_wr_avail(struct srpt_rdma_ch *ch, int delta)
++{
++ int res;
++ unsigned long flags;
++
++ spin_lock_irqsave(&ch->spinlock, flags);
++ ch->sq_wr_avail += delta;
++ res = ch->sq_wr_avail;
++ spin_unlock_irqrestore(&ch->spinlock, flags);
++
++ return res;
++}
++
++/**
++ * srpt_post_send() - Post an IB send request.
++ *
++ * Returns zero upon success and a non-zero value upon failure.
++ */
++static int srpt_post_send(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx, int len)
++{
++ struct ib_sge list;
++ struct ib_send_wr wr, *bad_wr;
++ struct srpt_device *sdev = ch->sport->sdev;
++ int ret;
++
++ ret = -ENOMEM;
++ if (srpt_adjust_srq_wr_avail(ch, -1) < 0) {
++ PRINT_WARNING("%s", "IB send queue full (needed 1)");
++ goto out;
++ }
++
++ ib_dma_sync_single_for_device(sdev->device, ioctx->ioctx.dma, len,
++ DMA_TO_DEVICE);
++
++ list.addr = ioctx->ioctx.dma;
++ list.length = len;
++ list.lkey = sdev->mr->lkey;
++
++ wr.next = NULL;
++ wr.wr_id = encode_wr_id(SRPT_SEND, ioctx->ioctx.index);
++ wr.sg_list = &list;
++ wr.num_sge = 1;
++ wr.opcode = IB_WR_SEND;
++ wr.send_flags = IB_SEND_SIGNALED;
++
++ ret = ib_post_send(ch->qp, &wr, &bad_wr);
++
++out:
++ if (ret < 0)
++ srpt_adjust_srq_wr_avail(ch, 1);
++ return ret;
++}
++
++/**
++ * srpt_get_desc_tbl() - Parse the data descriptors of an SRP_CMD request.
++ * @ioctx: Pointer to the I/O context associated with the request.
++ * @srp_cmd: Pointer to the SRP_CMD request data.
++ * @dir: Pointer to the variable to which the transfer direction will be
++ * written.
++ * @data_len: Pointer to the variable to which the total data length of all
++ * descriptors in the SRP_CMD request will be written.
++ *
++ * This function initializes ioctx->nrbuf and ioctx->r_bufs.
++ *
++ * Returns -EINVAL when the SRP_CMD request contains inconsistent descriptors;
++ * -ENOMEM when memory allocation fails and zero upon success.
++ */
++static int srpt_get_desc_tbl(struct srpt_send_ioctx *ioctx,
++ struct srp_cmd *srp_cmd,
++ scst_data_direction *dir, u64 *data_len)
++{
++ struct srp_indirect_buf *idb;
++ struct srp_direct_buf *db;
++ unsigned add_cdb_offset;
++ int ret;
++
++ /*
++ * The pointer computations below will only be compiled correctly
++ * if srp_cmd::add_data is declared as s8*, u8*, s8[] or u8[], so check
++ * whether srp_cmd::add_data has been declared as a byte pointer.
++ */
++ BUILD_BUG_ON(!__same_type(srp_cmd->add_data[0], (s8)0)
++ && !__same_type(srp_cmd->add_data[0], (u8)0));
++
++ BUG_ON(!dir);
++ BUG_ON(!data_len);
++
++ ret = 0;
++ *data_len = 0;
++
++ /*
++ * The lower four bits of the buffer format field contain the DATA-IN
++ * buffer descriptor format, and the highest four bits contain the
++ * DATA-OUT buffer descriptor format.
++ */
++ *dir = SCST_DATA_NONE;
++ if (srp_cmd->buf_fmt & 0xf)
++ /* DATA-IN: transfer data from target to initiator (read). */
++ *dir = SCST_DATA_READ;
++ else if (srp_cmd->buf_fmt >> 4)
++ /* DATA-OUT: transfer data from initiator to target (write). */
++ *dir = SCST_DATA_WRITE;
++
++ /*
++ * According to the SRP spec, the lower two bits of the 'ADDITIONAL
++ * CDB LENGTH' field are reserved and the size in bytes of this field
++ * is four times the value specified in bits 3..7. Hence the "& ~3".
++ */
++ add_cdb_offset = srp_cmd->add_cdb_len & ~3;
++ if (((srp_cmd->buf_fmt & 0xf) == SRP_DATA_DESC_DIRECT) ||
++ ((srp_cmd->buf_fmt >> 4) == SRP_DATA_DESC_DIRECT)) {
++ ioctx->n_rbuf = 1;
++ ioctx->rbufs = &ioctx->single_rbuf;
++
++ db = (struct srp_direct_buf *)(srp_cmd->add_data
++ + add_cdb_offset);
++ memcpy(ioctx->rbufs, db, sizeof *db);
++ *data_len = be32_to_cpu(db->len);
++ } else if (((srp_cmd->buf_fmt & 0xf) == SRP_DATA_DESC_INDIRECT) ||
++ ((srp_cmd->buf_fmt >> 4) == SRP_DATA_DESC_INDIRECT)) {
++ idb = (struct srp_indirect_buf *)(srp_cmd->add_data
++ + add_cdb_offset);
++
++ ioctx->n_rbuf = be32_to_cpu(idb->table_desc.len) / sizeof *db;
++
++ if (ioctx->n_rbuf >
++ (srp_cmd->data_out_desc_cnt + srp_cmd->data_in_desc_cnt)) {
++ PRINT_ERROR("received unsupported SRP_CMD request type"
++ " (%u out + %u in != %u / %zu)",
++ srp_cmd->data_out_desc_cnt,
++ srp_cmd->data_in_desc_cnt,
++ be32_to_cpu(idb->table_desc.len),
++ sizeof(*db));
++ ioctx->n_rbuf = 0;
++ ret = -EINVAL;
++ goto out;
++ }
++
++ if (ioctx->n_rbuf == 1)
++ ioctx->rbufs = &ioctx->single_rbuf;
++ else {
++ ioctx->rbufs =
++ kmalloc(ioctx->n_rbuf * sizeof *db, GFP_ATOMIC);
++ if (!ioctx->rbufs) {
++ ioctx->n_rbuf = 0;
++ ret = -ENOMEM;
++ goto out;
++ }
++ }
++
++ db = idb->desc_list;
++ memcpy(ioctx->rbufs, db, ioctx->n_rbuf * sizeof *db);
++ *data_len = be32_to_cpu(idb->len);
++ }
++out:
++ return ret;
++}
++
++/**
++ * srpt_init_ch_qp() - Initialize queue pair attributes.
++ *
++ * Initialized the attributes of queue pair 'qp' by allowing local write,
++ * remote read and remote write. Also transitions 'qp' to state IB_QPS_INIT.
++ */
++static int srpt_init_ch_qp(struct srpt_rdma_ch *ch, struct ib_qp *qp)
++{
++ struct ib_qp_attr *attr;
++ int ret;
++
++ attr = kzalloc(sizeof *attr, GFP_KERNEL);
++ if (!attr)
++ return -ENOMEM;
++
++ attr->qp_state = IB_QPS_INIT;
++ attr->qp_access_flags = IB_ACCESS_LOCAL_WRITE | IB_ACCESS_REMOTE_READ |
++ IB_ACCESS_REMOTE_WRITE;
++ attr->port_num = ch->sport->port;
++ attr->pkey_index = 0;
++
++ ret = ib_modify_qp(qp, attr,
++ IB_QP_STATE | IB_QP_ACCESS_FLAGS | IB_QP_PORT |
++ IB_QP_PKEY_INDEX);
++
++ kfree(attr);
++ return ret;
++}
++
++/**
++ * srpt_ch_qp_rtr() - Change the state of a channel to 'ready to receive' (RTR).
++ * @ch: channel of the queue pair.
++ * @qp: queue pair to change the state of.
++ *
++ * Returns zero upon success and a negative value upon failure.
++ */
++static int srpt_ch_qp_rtr(struct srpt_rdma_ch *ch, struct ib_qp *qp)
++{
++ struct ib_qp_attr *attr;
++ int attr_mask;
++ int ret;
++
++ attr = kzalloc(sizeof *attr, GFP_KERNEL);
++ if (!attr)
++ return -ENOMEM;
++
++ attr->qp_state = IB_QPS_RTR;
++ ret = ib_cm_init_qp_attr(ch->cm_id, attr, &attr_mask);
++ if (ret)
++ goto out;
++
++ attr->max_dest_rd_atomic = 4;
++ TRACE_DBG("qp timeout = %d", attr->timeout);
++
++ ret = ib_modify_qp(qp, attr, attr_mask);
++
++out:
++ kfree(attr);
++ return ret;
++}
++
++/**
++ * srpt_ch_qp_rts() - Change the state of a channel to 'ready to send' (RTS).
++ * @ch: channel of the queue pair.
++ * @qp: queue pair to change the state of.
++ *
++ * Returns zero upon success and a negative value upon failure.
++ */
++static int srpt_ch_qp_rts(struct srpt_rdma_ch *ch, struct ib_qp *qp)
++{
++ struct ib_qp_attr *attr;
++ int attr_mask;
++ int ret;
++ uint64_t T_tr_ns;
++ uint32_t T_tr_ms, max_compl_time_ms;
++
++ attr = kzalloc(sizeof *attr, GFP_KERNEL);
++ if (!attr)
++ return -ENOMEM;
++
++ attr->qp_state = IB_QPS_RTS;
++ ret = ib_cm_init_qp_attr(ch->cm_id, attr, &attr_mask);
++ if (ret)
++ goto out;
++
++ attr->max_rd_atomic = 4;
++
++ /*
++ * From IBTA C9-140: Transport Timer timeout interval
++ * T_tr = 4.096 us * 2**(local ACK timeout) where the local ACK timeout
++ * is a five-bit value, with zero meaning that the timer is disabled.
++ */
++ WARN_ON(attr->timeout >= (1 << 5));
++ if (attr->timeout) {
++ T_tr_ns = 1ULL << (12 + attr->timeout);
++ max_compl_time_ms = attr->retry_cnt * 4 * T_tr_ns;
++ do_div(max_compl_time_ms, 1000000);
++ T_tr_ms = T_tr_ns;
++ do_div(T_tr_ms, 1000000);
++ TRACE_DBG("Session %s: QP local ack timeout = %d or T_tr ="
++ " %u ms; retry_cnt = %d; max compl. time = %d ms",
++ ch->sess_name, attr->timeout, T_tr_ms,
++ attr->retry_cnt, max_compl_time_ms);
++
++ if (max_compl_time_ms >= RDMA_COMPL_TIMEOUT_S * 1000) {
++ PRINT_ERROR("Maximum RDMA completion time (%d ms)"
++ " exceeds ib_srpt timeout (%d ms)",
++ max_compl_time_ms,
++ 1000 * RDMA_COMPL_TIMEOUT_S);
++ }
++ }
++
++ ret = ib_modify_qp(qp, attr, attr_mask);
++
++out:
++ kfree(attr);
++ return ret;
++}
++
++/**
++ * srpt_ch_qp_err() - Set the channel queue pair state to 'error'.
++ */
++static int srpt_ch_qp_err(struct srpt_rdma_ch *ch)
++{
++ struct ib_qp_attr *attr;
++ int ret;
++
++ attr = kzalloc(sizeof *attr, GFP_KERNEL);
++ if (!attr)
++ return -ENOMEM;
++
++ attr->qp_state = IB_QPS_ERR;
++ ret = ib_modify_qp(ch->qp, attr, IB_QP_STATE);
++ kfree(attr);
++ return ret;
++}
++
++/**
++ * srpt_get_send_ioctx() - Obtain an I/O context for sending to the initiator.
++ */
++static struct srpt_send_ioctx *srpt_get_send_ioctx(struct srpt_rdma_ch *ch)
++{
++ struct srpt_send_ioctx *ioctx;
++ unsigned long flags;
++
++ BUG_ON(!ch);
++
++ ioctx = NULL;
++ spin_lock_irqsave(&ch->spinlock, flags);
++ if (!list_empty(&ch->free_list)) {
++ ioctx = list_first_entry(&ch->free_list,
++ struct srpt_send_ioctx, free_list);
++ list_del(&ioctx->free_list);
++ }
++ spin_unlock_irqrestore(&ch->spinlock, flags);
++
++ if (!ioctx)
++ return ioctx;
++
++ BUG_ON(ioctx->ch != ch);
++ spin_lock_init(&ioctx->spinlock);
++ ioctx->state = SRPT_STATE_NEW;
++ ioctx->n_rbuf = 0;
++ ioctx->rbufs = NULL;
++ ioctx->n_rdma = 0;
++ ioctx->n_rdma_ius = 0;
++ ioctx->rdma_ius = NULL;
++ ioctx->mapped_sg_count = 0;
++ ioctx->scmnd = NULL;
++
++ return ioctx;
++}
++
++/**
++ * srpt_put_send_ioctx() - Free up resources.
++ */
++static void srpt_put_send_ioctx(struct srpt_send_ioctx *ioctx)
++{
++ struct srpt_rdma_ch *ch;
++ unsigned long flags;
++
++ BUG_ON(!ioctx);
++ ch = ioctx->ch;
++ BUG_ON(!ch);
++
++ ioctx->scmnd = NULL;
++
++ /*
++ * If the WARN_ON() below gets triggered this means that
++ * srpt_unmap_sg_to_ib_sge() has not been called before
++ * scst_tgt_cmd_done().
++ */
++ WARN_ON(ioctx->mapped_sg_count);
++
++ if (ioctx->n_rbuf > 1) {
++ kfree(ioctx->rbufs);
++ ioctx->rbufs = NULL;
++ ioctx->n_rbuf = 0;
++ }
++
++ spin_lock_irqsave(&ch->spinlock, flags);
++ list_add(&ioctx->free_list, &ch->free_list);
++ spin_unlock_irqrestore(&ch->spinlock, flags);
++}
++
++/**
++ * srpt_abort_cmd() - Abort a SCSI command.
++ * @ioctx: I/O context associated with the SCSI command.
++ * @context: Preferred execution context.
++ */
++static void srpt_abort_cmd(struct srpt_send_ioctx *ioctx,
++ enum scst_exec_context context)
++{
++ struct scst_cmd *scmnd;
++ enum srpt_command_state state;
++
++ TRACE_ENTRY();
++
++ BUG_ON(!ioctx);
++
++ /*
++ * If the command is in a state where the target core is waiting for
++ * the ib_srpt driver, change the state to the next state. Changing
++ * the state of the command from SRPT_STATE_NEED_DATA to
++ * SRPT_STATE_DATA_IN ensures that srpt_xmit_response() will call this
++ * function a second time.
++ */
++ spin_lock(&ioctx->spinlock);
++ state = ioctx->state;
++ switch (state) {
++ case SRPT_STATE_NEED_DATA:
++ ioctx->state = SRPT_STATE_DATA_IN;
++ break;
++ case SRPT_STATE_DATA_IN:
++ case SRPT_STATE_CMD_RSP_SENT:
++ case SRPT_STATE_MGMT_RSP_SENT:
++ ioctx->state = SRPT_STATE_DONE;
++ break;
++ default:
++ break;
++ }
++ spin_unlock(&ioctx->spinlock);
++
++ if (state == SRPT_STATE_DONE)
++ goto out;
++
++ scmnd = ioctx->scmnd;
++ WARN_ON(!scmnd);
++ if (!scmnd)
++ goto out;
++
++ WARN_ON(ioctx != scst_cmd_get_tgt_priv(scmnd));
++
++ TRACE_DBG("Aborting cmd with state %d and tag %lld",
++ state, scst_cmd_get_tag(scmnd));
++
++ switch (state) {
++ case SRPT_STATE_NEW:
++ case SRPT_STATE_DATA_IN:
++ case SRPT_STATE_MGMT:
++ /*
++ * Do nothing - defer abort processing until
++ * srpt_xmit_response() is invoked.
++ */
++ WARN_ON(!scst_cmd_aborted(scmnd));
++ break;
++ case SRPT_STATE_NEED_DATA:
++ /* SCST_DATA_WRITE - RDMA read error or RDMA read timeout. */
++ scst_rx_data(ioctx->scmnd, SCST_RX_STATUS_ERROR, context);
++ break;
++ case SRPT_STATE_CMD_RSP_SENT:
++ /*
++ * SRP_RSP sending failed or the SRP_RSP send completion has
++ * not been received in time.
++ */
++ srpt_unmap_sg_to_ib_sge(ioctx->ch, ioctx);
++ srpt_put_send_ioctx(ioctx);
++ scst_set_delivery_status(scmnd, SCST_CMD_DELIVERY_ABORTED);
++ scst_tgt_cmd_done(scmnd, context);
++ break;
++ case SRPT_STATE_MGMT_RSP_SENT:
++ /*
++ * Management command response sending failed. This state is
++ * never reached since there is no scmnd associated with
++ * management commands. Note: the SCST core frees these
++ * commands immediately after srpt_tsk_mgmt_done() returned.
++ */
++ WARN_ON("ERROR: unexpected command state");
++ break;
++ default:
++ WARN_ON("ERROR: unexpected command state");
++ break;
++ }
++
++out:
++ ;
++
++ TRACE_EXIT();
++}
++
++/**
++ * srpt_handle_send_err_comp() - Process an IB_WC_SEND error completion.
++ */
++static void srpt_handle_send_err_comp(struct srpt_rdma_ch *ch, u64 wr_id,
++ enum scst_exec_context context)
++{
++ struct srpt_send_ioctx *ioctx;
++ enum srpt_command_state state;
++ struct scst_cmd *scmnd;
++ u32 index;
++
++ srpt_adjust_srq_wr_avail(ch, 1);
++
++ index = idx_from_wr_id(wr_id);
++ ioctx = ch->ioctx_ring[index];
++ state = ioctx->state;
++ scmnd = ioctx->scmnd;
++
++ EXTRACHECKS_WARN_ON(state != SRPT_STATE_CMD_RSP_SENT
++ && state != SRPT_STATE_MGMT_RSP_SENT
++ && state != SRPT_STATE_NEED_DATA
++ && state != SRPT_STATE_DONE);
++
++ /*
++ * If SRP_RSP sending failed, undo the ch->req_lim and ch->req_lim_delta
++ * changes.
++ */
++ if (state == SRPT_STATE_CMD_RSP_SENT
++ || state == SRPT_STATE_MGMT_RSP_SENT)
++ srpt_undo_inc_req_lim(ch, ioctx->req_lim_delta);
++ if (state != SRPT_STATE_DONE) {
++ if (scmnd)
++ srpt_abort_cmd(ioctx, context);
++ else
++ srpt_put_send_ioctx(ioctx);
++ } else
++ PRINT_ERROR("Received more than one IB error completion"
++ " for wr_id = %u.", (unsigned)index);
++}
++
++/**
++ * srpt_handle_send_comp() - Process an IB send completion notification.
++ */
++static void srpt_handle_send_comp(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx,
++ enum scst_exec_context context)
++{
++ enum srpt_command_state state;
++
++ srpt_adjust_srq_wr_avail(ch, 1);
++
++ state = srpt_set_cmd_state(ioctx, SRPT_STATE_DONE);
++
++ EXTRACHECKS_WARN_ON(state != SRPT_STATE_CMD_RSP_SENT
++ && state != SRPT_STATE_MGMT_RSP_SENT
++ && state != SRPT_STATE_DONE);
++
++ if (state != SRPT_STATE_DONE) {
++ struct scst_cmd *scmnd;
++
++ scmnd = ioctx->scmnd;
++ EXTRACHECKS_WARN_ON((state == SRPT_STATE_MGMT_RSP_SENT)
++ != (scmnd == NULL));
++ if (scmnd) {
++ srpt_unmap_sg_to_ib_sge(ch, ioctx);
++ srpt_put_send_ioctx(ioctx);
++ scst_tgt_cmd_done(scmnd, context);
++ } else
++ srpt_put_send_ioctx(ioctx);
++ } else {
++ PRINT_ERROR("IB completion has been received too late for"
++ " wr_id = %u.", ioctx->ioctx.index);
++ }
++}
++
++/**
++ * srpt_handle_rdma_comp() - Process an IB RDMA completion notification.
++ */
++static void srpt_handle_rdma_comp(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx,
++ enum srpt_opcode opcode,
++ enum scst_exec_context context)
++{
++ struct scst_cmd *scmnd;
++
++ EXTRACHECKS_WARN_ON(ioctx->n_rdma <= 0);
++ srpt_adjust_srq_wr_avail(ch, ioctx->n_rdma);
++
++ scmnd = ioctx->scmnd;
++ if (opcode == SRPT_RDMA_READ_LAST && scmnd) {
++ if (srpt_test_and_set_cmd_state(ioctx, SRPT_STATE_NEED_DATA,
++ SRPT_STATE_DATA_IN))
++ scst_rx_data(ioctx->scmnd, SCST_RX_STATUS_SUCCESS,
++ context);
++ else
++ PRINT_ERROR("%s[%d]: wrong state = %d", __func__,
++ __LINE__, ioctx->state);
++ } else if (opcode == SRPT_RDMA_ABORT) {
++ ioctx->rdma_aborted = true;
++ } else {
++ WARN(true, "scmnd == NULL (opcode %d)", opcode);
++ }
++}
++
++/**
++ * srpt_handle_rdma_err_comp() - Process an IB RDMA error completion.
++ */
++static void srpt_handle_rdma_err_comp(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx,
++ enum srpt_opcode opcode,
++ enum scst_exec_context context)
++{
++ struct scst_cmd *scmnd;
++ enum srpt_command_state state;
++
++ scmnd = ioctx->scmnd;
++ state = ioctx->state;
++ if (scmnd) {
++ switch (opcode) {
++ case SRPT_RDMA_READ_LAST:
++ if (ioctx->n_rdma <= 0) {
++ PRINT_ERROR("Received invalid RDMA read error"
++ " completion with idx %d",
++ ioctx->ioctx.index);
++ break;
++ }
++ srpt_adjust_srq_wr_avail(ch, ioctx->n_rdma);
++ if (state == SRPT_STATE_NEED_DATA)
++ srpt_abort_cmd(ioctx, context);
++ else
++ PRINT_ERROR("%s[%d]: wrong state = %d",
++ __func__, __LINE__, state);
++ break;
++ case SRPT_RDMA_WRITE_LAST:
++ scst_set_delivery_status(scmnd,
++ SCST_CMD_DELIVERY_ABORTED);
++ break;
++ default:
++ PRINT_ERROR("%s[%d]: opcode = %u", __func__, __LINE__,
++ opcode);
++ break;
++ }
++ } else
++ PRINT_ERROR("%s[%d]: scmnd == NULL", __func__, __LINE__);
++}
++
++/**
++ * srpt_build_cmd_rsp() - Build an SRP_RSP response.
++ * @ch: RDMA channel through which the request has been received.
++ * @ioctx: I/O context associated with the SRP_CMD request. The response will
++ * be built in the buffer ioctx->buf points at and hence this function will
++ * overwrite the request data.
++ * @tag: tag of the request for which this response is being generated.
++ * @status: value for the STATUS field of the SRP_RSP information unit.
++ * @sense_data: pointer to sense data to be included in the response.
++ * @sense_data_len: length in bytes of the sense data.
++ *
++ * Returns the size in bytes of the SRP_RSP response.
++ *
++ * An SRP_RSP response contains a SCSI status or service response. See also
++ * section 6.9 in the SRP r16a document for the format of an SRP_RSP
++ * response. See also SPC-2 for more information about sense data.
++ */
++static int srpt_build_cmd_rsp(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx, u64 tag,
++ int status, const u8 *sense_data,
++ int sense_data_len)
++{
++ struct srp_rsp *srp_rsp;
++ int max_sense_len;
++
++ /*
++ * The lowest bit of all SAM-3 status codes is zero (see also
++ * paragraph 5.3 in SAM-3).
++ */
++ EXTRACHECKS_WARN_ON(status & 1);
++
++ srp_rsp = ioctx->ioctx.buf;
++ BUG_ON(!srp_rsp);
++ memset(srp_rsp, 0, sizeof *srp_rsp);
++
++ srp_rsp->opcode = SRP_RSP;
++ srp_rsp->req_lim_delta = cpu_to_be32(ioctx->req_lim_delta);
++ srp_rsp->tag = tag;
++ srp_rsp->status = status;
++
++ if (!SCST_SENSE_VALID(sense_data))
++ sense_data_len = 0;
++ else {
++ BUILD_BUG_ON(MIN_MAX_RSP_SIZE <= sizeof(*srp_rsp));
++ max_sense_len = ch->max_ti_iu_len - sizeof(*srp_rsp);
++ if (sense_data_len > max_sense_len) {
++ PRINT_WARNING("truncated sense data from %d to %d"
++ " bytes", sense_data_len, max_sense_len);
++ sense_data_len = max_sense_len;
++ }
++
++ srp_rsp->flags |= SRP_RSP_FLAG_SNSVALID;
++ srp_rsp->sense_data_len = cpu_to_be32(sense_data_len);
++ memcpy(srp_rsp + 1, sense_data, sense_data_len);
++ }
++
++ return sizeof(*srp_rsp) + sense_data_len;
++}
++
++/**
++ * srpt_build_tskmgmt_rsp() - Build a task management response.
++ * @ch: RDMA channel through which the request has been received.
++ * @ioctx: I/O context in which the SRP_RSP response will be built.
++ * @rsp_code: RSP_CODE that will be stored in the response.
++ * @tag: Tag of the request for which this response is being generated.
++ *
++ * Returns the size in bytes of the SRP_RSP response.
++ *
++ * An SRP_RSP response contains a SCSI status or service response. See also
++ * section 6.9 in the SRP r16a document for the format of an SRP_RSP
++ * response.
++ */
++static int srpt_build_tskmgmt_rsp(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx,
++ u8 rsp_code, u64 tag)
++{
++ struct srp_rsp *srp_rsp;
++ int resp_data_len;
++ int resp_len;
++
++ resp_data_len = (rsp_code == SRP_TSK_MGMT_SUCCESS) ? 0 : 4;
++ resp_len = sizeof(*srp_rsp) + resp_data_len;
++
++ srp_rsp = ioctx->ioctx.buf;
++ BUG_ON(!srp_rsp);
++ memset(srp_rsp, 0, sizeof *srp_rsp);
++
++ srp_rsp->opcode = SRP_RSP;
++ srp_rsp->req_lim_delta = cpu_to_be32(ioctx->req_lim_delta);
++ srp_rsp->tag = tag;
++
++ if (rsp_code != SRP_TSK_MGMT_SUCCESS) {
++ srp_rsp->flags |= SRP_RSP_FLAG_RSPVALID;
++ srp_rsp->resp_data_len = cpu_to_be32(resp_data_len);
++ srp_rsp->data[3] = rsp_code;
++ }
++
++ return resp_len;
++}
++
++/**
++ * srpt_handle_cmd() - Process SRP_CMD.
++ */
++static int srpt_handle_cmd(struct srpt_rdma_ch *ch,
++ struct srpt_recv_ioctx *recv_ioctx,
++ struct srpt_send_ioctx *send_ioctx,
++ enum scst_exec_context context)
++{
++ struct scst_cmd *scmnd;
++ struct srp_cmd *srp_cmd;
++ scst_data_direction dir;
++ u64 data_len;
++ int ret;
++ int atomic;
++
++ BUG_ON(!send_ioctx);
++
++ srp_cmd = recv_ioctx->ioctx.buf;
++
++ atomic = context == SCST_CONTEXT_TASKLET ? SCST_ATOMIC
++ : SCST_NON_ATOMIC;
++ scmnd = scst_rx_cmd(ch->scst_sess, (u8 *) &srp_cmd->lun,
++ sizeof srp_cmd->lun, srp_cmd->cdb,
++ sizeof srp_cmd->cdb, atomic);
++ if (!scmnd) {
++ PRINT_ERROR("0x%llx: allocation of an SCST command failed",
++ srp_cmd->tag);
++ goto err;
++ }
++
++ send_ioctx->scmnd = scmnd;
++
++ ret = srpt_get_desc_tbl(send_ioctx, srp_cmd, &dir, &data_len);
++ if (ret) {
++ PRINT_ERROR("0x%llx: parsing SRP descriptor table failed.",
++ srp_cmd->tag);
++ scst_set_cmd_error(scmnd,
++ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
++ }
++
++ switch (srp_cmd->task_attr) {
++ case SRP_CMD_HEAD_OF_Q:
++ scst_cmd_set_queue_type(scmnd, SCST_CMD_QUEUE_HEAD_OF_QUEUE);
++ break;
++ case SRP_CMD_ORDERED_Q:
++ scst_cmd_set_queue_type(scmnd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ case SRP_CMD_SIMPLE_Q:
++ scst_cmd_set_queue_type(scmnd, SCST_CMD_QUEUE_SIMPLE);
++ break;
++ case SRP_CMD_ACA:
++ scst_cmd_set_queue_type(scmnd, SCST_CMD_QUEUE_ACA);
++ break;
++ default:
++ scst_cmd_set_queue_type(scmnd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ }
++
++ scst_cmd_set_tag(scmnd, srp_cmd->tag);
++ scst_cmd_set_tgt_priv(scmnd, send_ioctx);
++ scst_cmd_set_expected(scmnd, dir, data_len);
++ scst_cmd_init_done(scmnd, context);
++
++ return 0;
++
++err:
++ srpt_put_send_ioctx(send_ioctx);
++ return -1;
++}
++
++/**
++ * srpt_handle_tsk_mgmt() - Process an SRP_TSK_MGMT information unit.
++ *
++ * Returns SCST_MGMT_STATUS_SUCCESS upon success.
++ *
++ * Each task management function is performed by calling one of the
++ * scst_rx_mgmt_fn*() functions. These functions will either report failure
++ * or process the task management function asynchronously. The function
++ * srpt_tsk_mgmt_done() will be called by the SCST core upon completion of the
++ * task management function. When srpt_handle_tsk_mgmt() reports failure
++ * (i.e. returns -1) a response will have been built in ioctx->buf. This
++ * information unit has to be sent back by the caller.
++ *
++ * For more information about SRP_TSK_MGMT information units, see also section
++ * 6.7 in the SRP r16a document.
++ */
++static u8 srpt_handle_tsk_mgmt(struct srpt_rdma_ch *ch,
++ struct srpt_recv_ioctx *recv_ioctx,
++ struct srpt_send_ioctx *send_ioctx)
++{
++ struct srp_tsk_mgmt *srp_tsk;
++ int ret;
++
++ ret = SCST_MGMT_STATUS_FAILED;
++
++ BUG_ON(!send_ioctx);
++ BUG_ON(send_ioctx->ch != ch);
++
++ srpt_set_cmd_state(send_ioctx, SRPT_STATE_MGMT);
++
++ srp_tsk = recv_ioctx->ioctx.buf;
++
++ TRACE_DBG("recv_tsk_mgmt= %d for task_tag= %lld"
++ " using tag= %lld cm_id= %p sess= %p",
++ srp_tsk->tsk_mgmt_func, srp_tsk->task_tag, srp_tsk->tag,
++ ch->cm_id, ch->scst_sess);
++
++ send_ioctx->tsk_mgmt.tag = srp_tsk->tag;
++
++ switch (srp_tsk->tsk_mgmt_func) {
++ case SRP_TSK_ABORT_TASK:
++ TRACE_DBG("%s", "Processing SRP_TSK_ABORT_TASK");
++ ret = scst_rx_mgmt_fn_tag(ch->scst_sess,
++ SCST_ABORT_TASK,
++ srp_tsk->task_tag,
++ SCST_ATOMIC, send_ioctx);
++ break;
++ case SRP_TSK_ABORT_TASK_SET:
++ TRACE_DBG("%s", "Processing SRP_TSK_ABORT_TASK_SET");
++ ret = scst_rx_mgmt_fn_lun(ch->scst_sess,
++ SCST_ABORT_TASK_SET,
++ (u8 *) &srp_tsk->lun,
++ sizeof srp_tsk->lun,
++ SCST_ATOMIC, send_ioctx);
++ break;
++ case SRP_TSK_CLEAR_TASK_SET:
++ TRACE_DBG("%s", "Processing SRP_TSK_CLEAR_TASK_SET");
++ ret = scst_rx_mgmt_fn_lun(ch->scst_sess,
++ SCST_CLEAR_TASK_SET,
++ (u8 *) &srp_tsk->lun,
++ sizeof srp_tsk->lun,
++ SCST_ATOMIC, send_ioctx);
++ break;
++ case SRP_TSK_LUN_RESET:
++ TRACE_DBG("%s", "Processing SRP_TSK_LUN_RESET");
++ ret = scst_rx_mgmt_fn_lun(ch->scst_sess,
++ SCST_LUN_RESET,
++ (u8 *) &srp_tsk->lun,
++ sizeof srp_tsk->lun,
++ SCST_ATOMIC, send_ioctx);
++ break;
++ case SRP_TSK_CLEAR_ACA:
++ TRACE_DBG("%s", "Processing SRP_TSK_CLEAR_ACA");
++ ret = scst_rx_mgmt_fn_lun(ch->scst_sess,
++ SCST_CLEAR_ACA,
++ (u8 *) &srp_tsk->lun,
++ sizeof srp_tsk->lun,
++ SCST_ATOMIC, send_ioctx);
++ break;
++ default:
++ TRACE_DBG("%s", "Unsupported task management function.");
++ ret = SCST_MGMT_STATUS_FN_NOT_SUPPORTED;
++ }
++
++ if (ret != SCST_MGMT_STATUS_SUCCESS)
++ srpt_put_send_ioctx(send_ioctx);
++
++ return ret;
++}
++
++static u8 scst_to_srp_tsk_mgmt_status(const int scst_mgmt_status)
++{
++ switch (scst_mgmt_status) {
++ case SCST_MGMT_STATUS_SUCCESS:
++ return SRP_TSK_MGMT_SUCCESS;
++ case SCST_MGMT_STATUS_FN_NOT_SUPPORTED:
++ return SRP_TSK_MGMT_FUNC_NOT_SUPP;
++ case SCST_MGMT_STATUS_TASK_NOT_EXIST:
++ case SCST_MGMT_STATUS_LUN_NOT_EXIST:
++ case SCST_MGMT_STATUS_REJECTED:
++ case SCST_MGMT_STATUS_FAILED:
++ default:
++ break;
++ }
++ return SRP_TSK_MGMT_FAILED;
++}
++
++/**
++ * srpt_handle_new_iu() - Process a newly received information unit.
++ * @ch: RDMA channel through which the information unit has been received.
++ * @ioctx: SRPT I/O context associated with the information unit.
++ */
++static void srpt_handle_new_iu(struct srpt_rdma_ch *ch,
++ struct srpt_recv_ioctx *recv_ioctx,
++ struct srpt_send_ioctx *send_ioctx,
++ enum scst_exec_context context)
++{
++ struct srp_cmd *srp_cmd;
++ enum rdma_ch_state ch_state;
++
++ BUG_ON(!ch);
++ BUG_ON(!recv_ioctx);
++
++ ib_dma_sync_single_for_cpu(ch->sport->sdev->device,
++ recv_ioctx->ioctx.dma, srp_max_req_size,
++ DMA_FROM_DEVICE);
++
++ ch_state = ch->state;
++ srp_cmd = recv_ioctx->ioctx.buf;
++ if (unlikely(ch_state == CH_CONNECTING)) {
++ list_add_tail(&recv_ioctx->wait_list, &ch->cmd_wait_list);
++ goto out;
++ }
++
++ if (srp_cmd->opcode == SRP_CMD || srp_cmd->opcode == SRP_TSK_MGMT) {
++ if (!send_ioctx)
++ send_ioctx = srpt_get_send_ioctx(ch);
++ if (unlikely(!send_ioctx)) {
++ list_add_tail(&recv_ioctx->wait_list,
++ &ch->cmd_wait_list);
++ goto out;
++ }
++ }
++
++ switch (srp_cmd->opcode) {
++ case SRP_CMD:
++ srpt_handle_cmd(ch, recv_ioctx, send_ioctx, context);
++ break;
++ case SRP_TSK_MGMT:
++ srpt_handle_tsk_mgmt(ch, recv_ioctx, send_ioctx);
++ break;
++ case SRP_I_LOGOUT:
++ PRINT_ERROR("%s", "Not yet implemented: SRP_I_LOGOUT");
++ break;
++ case SRP_CRED_RSP:
++ TRACE_DBG("%s", "received SRP_CRED_RSP");
++ break;
++ case SRP_AER_RSP:
++ TRACE_DBG("%s", "received SRP_AER_RSP");
++ break;
++ case SRP_RSP:
++ PRINT_ERROR("%s", "Received SRP_RSP");
++ break;
++ default:
++ PRINT_ERROR("received IU with unknown opcode 0x%x",
++ srp_cmd->opcode);
++ break;
++ }
++
++ srpt_post_recv(ch->sport->sdev, recv_ioctx);
++out:
++ return;
++}
++
++static void srpt_process_rcv_completion(struct ib_cq *cq,
++ struct srpt_rdma_ch *ch,
++ enum scst_exec_context context,
++ struct ib_wc *wc)
++{
++ struct srpt_device *sdev = ch->sport->sdev;
++ struct srpt_recv_ioctx *ioctx;
++ u32 index;
++
++ index = idx_from_wr_id(wc->wr_id);
++ if (wc->status == IB_WC_SUCCESS) {
++ int req_lim;
++
++ req_lim = srpt_adjust_req_lim(ch, -1, 0);
++ if (unlikely(req_lim < 0))
++ PRINT_ERROR("req_lim = %d < 0", req_lim);
++ ioctx = sdev->ioctx_ring[index];
++ srpt_handle_new_iu(ch, ioctx, NULL, context);
++ } else {
++ PRINT_INFO("receiving failed for idx %u with status %d",
++ index, wc->status);
++ }
++}
++
++/**
++ * srpt_process_send_completion() - Process an IB send completion.
++ *
++ * Note: Although this has not yet been observed during tests, at least in
++ * theory it is possible that the srpt_get_send_ioctx() call invoked by
++ * srpt_handle_new_iu() fails. This is possible because the req_lim_delta
++ * value in each response is set to at least one, and it is possible that this
++ * response makes the initiator send a new request before the send completion
++ * for that response has been processed. This could e.g. happen if the call to
++ * srpt_put_send_iotcx() is delayed because of a higher priority interrupt or
++ * if IB retransmission causes generation of the send completion to be
++ * delayed. Incoming information units for which srpt_get_send_ioctx() fails
++ * are queued on cmd_wait_list. The code below processes these delayed
++ * requests one at a time.
++ */
++static void srpt_process_send_completion(struct ib_cq *cq,
++ struct srpt_rdma_ch *ch,
++ enum scst_exec_context context,
++ struct ib_wc *wc)
++{
++ struct srpt_send_ioctx *send_ioctx;
++ uint32_t index;
++ enum srpt_opcode opcode;
++
++ index = idx_from_wr_id(wc->wr_id);
++ opcode = opcode_from_wr_id(wc->wr_id);
++ send_ioctx = ch->ioctx_ring[index];
++ if (wc->status == IB_WC_SUCCESS) {
++ if (opcode == SRPT_SEND)
++ srpt_handle_send_comp(ch, send_ioctx, context);
++ else {
++ EXTRACHECKS_WARN_ON(opcode != SRPT_RDMA_ABORT &&
++ wc->opcode != IB_WC_RDMA_READ);
++ srpt_handle_rdma_comp(ch, send_ioctx, opcode, context);
++ }
++ } else {
++ if (opcode == SRPT_SEND) {
++ PRINT_INFO("sending response for idx %u failed with"
++ " status %d", index, wc->status);
++ srpt_handle_send_err_comp(ch, wc->wr_id, context);
++ } else if (opcode != SRPT_RDMA_MID) {
++ PRINT_INFO("RDMA t %d for idx %u failed with status %d",
++ opcode, index, wc->status);
++ srpt_handle_rdma_err_comp(ch, send_ioctx, opcode,
++ context);
++ }
++ }
++
++ while (unlikely(opcode == SRPT_SEND
++ && !list_empty(&ch->cmd_wait_list)
++ && ch->state == CH_LIVE
++ && (send_ioctx = srpt_get_send_ioctx(ch)) != NULL)) {
++ struct srpt_recv_ioctx *recv_ioctx;
++
++ recv_ioctx = list_first_entry(&ch->cmd_wait_list,
++ struct srpt_recv_ioctx,
++ wait_list);
++ list_del(&recv_ioctx->wait_list);
++ srpt_handle_new_iu(ch, recv_ioctx, send_ioctx, context);
++ }
++}
++
++static void srpt_process_completion(struct ib_cq *cq,
++ struct srpt_rdma_ch *ch,
++ enum scst_exec_context rcv_context,
++ enum scst_exec_context send_context)
++{
++ struct ib_wc *const wc = ch->wc;
++ int i, n;
++
++ EXTRACHECKS_WARN_ON(cq != ch->cq);
++
++ ib_req_notify_cq(cq, IB_CQ_NEXT_COMP);
++ while ((n = ib_poll_cq(cq, ARRAY_SIZE(ch->wc), wc)) > 0) {
++ for (i = 0; i < n; i++) {
++ if (opcode_from_wr_id(wc[i].wr_id) == SRPT_RECV)
++ srpt_process_rcv_completion(cq, ch, rcv_context,
++ &wc[i]);
++ else
++ srpt_process_send_completion(cq, ch,
++ send_context,
++ &wc[i]);
++ }
++ }
++}
++
++/**
++ * srpt_completion() - IB completion queue callback function.
++ */
++static void srpt_completion(struct ib_cq *cq, void *ctx)
++{
++ struct srpt_rdma_ch *ch = ctx;
++
++ BUG_ON(!ch->thread);
++ wake_up_process(ch->thread);
++}
++
++static int srpt_compl_thread(void *arg)
++{
++ struct srpt_rdma_ch *ch;
++
++ /* Hibernation / freezing of the SRPT kernel thread is not supported. */
++ current->flags |= PF_NOFREEZE;
++
++ ch = arg;
++ BUG_ON(!ch);
++ while (!kthread_should_stop() && !ch->last_wqe_received) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ srpt_process_completion(ch->cq, ch, SCST_CONTEXT_THREAD,
++ SCST_CONTEXT_DIRECT);
++ schedule();
++ }
++ set_current_state(TASK_RUNNING);
++
++ srpt_process_completion(ch->cq, ch, SCST_CONTEXT_THREAD,
++ SCST_CONTEXT_DIRECT);
++
++ /*
++ * Note: scst_unregister_session() must only be invoked after the last
++ * WQE event has been received.
++ */
++ TRACE_DBG("ch %s: about to invoke scst_unregister_session()",
++ ch->sess_name);
++ scst_unregister_session(ch->scst_sess, false, srpt_free_ch);
++
++ /*
++ * Some HCAs can queue send completions after the Last WQE
++ * event. Make sure to process these work completions.
++ */
++ while (ch->state < CH_FREEING) {
++ set_current_state(TASK_INTERRUPTIBLE);
++ srpt_process_completion(ch->cq, ch, SCST_CONTEXT_THREAD,
++ SCST_CONTEXT_DIRECT);
++ schedule();
++ }
++
++ complete(&ch->finished_processing_completions);
++
++ while (!kthread_should_stop())
++ schedule();
++
++ return 0;
++}
++
++/**
++ * srpt_create_ch_ib() - Create receive and send completion queues.
++ */
++static int srpt_create_ch_ib(struct srpt_rdma_ch *ch)
++{
++ struct ib_qp_init_attr *qp_init;
++ struct srpt_device *sdev = ch->sport->sdev;
++ int ret;
++
++ EXTRACHECKS_WARN_ON(ch->rq_size < 1);
++
++ ret = -ENOMEM;
++ qp_init = kzalloc(sizeof *qp_init, GFP_KERNEL);
++ if (!qp_init)
++ goto out;
++
++ ch->cq = ib_create_cq(sdev->device, srpt_completion, NULL, ch,
++ ch->rq_size + srpt_sq_size, 0);
++ if (IS_ERR(ch->cq)) {
++ ret = PTR_ERR(ch->cq);
++ PRINT_ERROR("failed to create CQ cqe= %d ret= %d",
++ ch->rq_size + srpt_sq_size, ret);
++ goto out;
++ }
++
++ qp_init->qp_context = (void *)ch;
++ qp_init->event_handler
++ = (void(*)(struct ib_event *, void*))srpt_qp_event;
++ qp_init->send_cq = ch->cq;
++ qp_init->recv_cq = ch->cq;
++ qp_init->srq = sdev->srq;
++ qp_init->sq_sig_type = IB_SIGNAL_REQ_WR;
++ qp_init->qp_type = IB_QPT_RC;
++ qp_init->cap.max_send_wr = srpt_sq_size;
++ /*
++ * A quote from the OFED 1.5.3.1 release notes
++ * (docs/release_notes/mthca_release_notes.txt), section "Known Issues":
++ * In mem-free devices, RC QPs can be created with a maximum of
++ * (max_sge - 1) entries only; UD QPs can be created with a maximum of
++ * (max_sge - 3) entries.
++ * A quote from the OFED 1.2.5 release notes
++ * (docs/mthca_release_notes.txt), section "Known Issues":
++ * In mem-free devices, RC QPs can be created with a maximum of
++ * (max_sge - 3) entries only.
++ */
++ ch->max_sge = sdev->dev_attr.max_sge - 3;
++ WARN_ON(ch->max_sge < 1);
++ qp_init->cap.max_send_sge = ch->max_sge;
++
++ ch->qp = ib_create_qp(sdev->pd, qp_init);
++ if (IS_ERR(ch->qp)) {
++ ret = PTR_ERR(ch->qp);
++ PRINT_ERROR("failed to create_qp ret= %d", ret);
++ goto err_destroy_cq;
++ }
++
++ ch->sq_wr_avail = qp_init->cap.max_send_wr;
++
++ TRACE_DBG("%s: max_cqe= %d max_sge= %d sq_size = %d"
++ " cm_id= %p", __func__, ch->cq->cqe,
++ qp_init->cap.max_send_sge, qp_init->cap.max_send_wr,
++ ch->cm_id);
++
++ ret = srpt_init_ch_qp(ch, ch->qp);
++ if (ret) {
++ PRINT_ERROR("srpt_init_ch_qp() failed (%d)", ret);
++ goto err_destroy_qp;
++ }
++
++out:
++ kfree(qp_init);
++ return ret;
++
++err_destroy_qp:
++ ib_destroy_qp(ch->qp);
++err_destroy_cq:
++ ib_destroy_cq(ch->cq);
++ goto out;
++}
++
++static void srpt_destroy_ch_ib(struct srpt_rdma_ch *ch)
++{
++ TRACE_ENTRY();
++
++ while (ib_poll_cq(ch->cq, ARRAY_SIZE(ch->wc), ch->wc) > 0)
++ ;
++
++ ib_destroy_qp(ch->qp);
++ ib_destroy_cq(ch->cq);
++
++ TRACE_EXIT();
++}
++
++/**
++ * __srpt_close_ch() - Close an RDMA channel by setting the QP error state.
++ *
++ * Reset the QP and make sure all resources associated with the channel will
++ * be deallocated at an appropriate time.
++ *
++ * Returns true if and only if the channel state has been modified from
++ * CH_CONNECTING or CH_LIVE into CH_DISCONNECTING.
++ *
++ * Note: The caller must hold ch->sport->sdev->spinlock.
++ */
++static bool __srpt_close_ch(struct srpt_rdma_ch *ch)
++{
++ struct srpt_device *sdev;
++ enum rdma_ch_state prev_state;
++ bool was_live;
++
++ sdev = ch->sport->sdev;
++ was_live = false;
++
++ prev_state = srpt_set_ch_state_to_disc(ch);
++
++ switch (prev_state) {
++ case CH_CONNECTING:
++ ib_send_cm_rej(ch->cm_id, IB_CM_REJ_NO_RESOURCES, NULL, 0,
++ NULL, 0);
++ /* fall through */
++ case CH_LIVE:
++ was_live = true;
++ if (ib_send_cm_dreq(ch->cm_id, NULL, 0) < 0)
++ PRINT_ERROR("%s", "sending CM DREQ failed.");
++ break;
++ case CH_DISCONNECTING:
++ case CH_DRAINING:
++ case CH_FREEING:
++ break;
++ }
++
++ return was_live;
++}
++
++/**
++ * srpt_close_ch() - Close an RDMA channel.
++ */
++static void srpt_close_ch(struct srpt_rdma_ch *ch)
++{
++ struct srpt_device *sdev;
++
++ sdev = ch->sport->sdev;
++ spin_lock_irq(&sdev->spinlock);
++ __srpt_close_ch(ch);
++ spin_unlock_irq(&sdev->spinlock);
++}
++
++/**
++ * srpt_drain_channel() - Drain a channel by resetting the IB queue pair.
++ * @cm_id: Pointer to the CM ID of the channel to be drained.
++ *
++ * Note: Must be called from inside srpt_cm_handler to avoid a race between
++ * accessing sdev->spinlock and the call to kfree(sdev) in srpt_remove_one()
++ * (the caller of srpt_cm_handler holds the cm_id spinlock; srpt_remove_one()
++ * waits until all target sessions for the associated IB device have been
++ * unregistered and target session registration involves a call to
++ * ib_destroy_cm_id(), which locks the cm_id spinlock and hence waits until
++ * this function has finished).
++ */
++static void srpt_drain_channel(struct ib_cm_id *cm_id)
++{
++ struct srpt_rdma_ch *ch;
++ int ret;
++
++ WARN_ON_ONCE(irqs_disabled());
++
++ ch = cm_id->context;
++ if (srpt_set_ch_state_to_draining(ch)) {
++ ret = srpt_ch_qp_err(ch);
++ if (ret < 0)
++ PRINT_ERROR("Setting queue pair in error state"
++ " failed: %d", ret);
++ }
++}
++
++static void srpt_free_ch(struct scst_session *sess)
++{
++ struct srpt_rdma_ch *ch;
++ struct srpt_device *sdev;
++
++ TRACE_ENTRY();
++
++ ch = scst_sess_get_tgt_priv(sess);
++ BUG_ON(ch->scst_sess != sess);
++ sdev = ch->sport->sdev;
++ BUG_ON(!sdev);
++
++ WARN_ON(!srpt_test_and_set_ch_state(ch, CH_DRAINING, CH_FREEING));
++ WARN_ON(!ch->last_wqe_received);
++
++ BUG_ON(!ch->thread);
++ BUG_ON(ch->thread == current);
++
++ while (wait_for_completion_timeout(&ch->finished_processing_completions,
++ 10 * HZ) == 0)
++ PRINT_INFO("%s", "Waiting for completion processing thread ...");
++
++ srpt_destroy_ch_ib(ch);
++
++ srpt_free_ioctx_ring((struct srpt_ioctx **)ch->ioctx_ring,
++ sdev, ch->rq_size,
++ ch->max_rsp_size, DMA_TO_DEVICE);
++
++ spin_lock_irq(&sdev->spinlock);
++ list_del(&ch->list);
++ spin_unlock_irq(&sdev->spinlock);
++
++ ib_destroy_cm_id(ch->cm_id);
++
++ kthread_stop(ch->thread);
++ ch->thread = NULL;
++
++ kfree(ch);
++
++ wake_up(&sdev->ch_releaseQ);
++
++ TRACE_EXIT();
++}
++
++/**
++ * srpt_enable_target() - Allows to enable a target via sysfs.
++ */
++static int srpt_enable_target(struct scst_tgt *scst_tgt, bool enable)
++{
++ struct srpt_device *sdev = scst_tgt_get_tgt_priv(scst_tgt);
++
++ EXTRACHECKS_WARN_ON_ONCE(irqs_disabled());
++
++ if (!sdev)
++ return -ENOENT;
++
++ TRACE_DBG("%s target %s", enable ? "Enabling" : "Disabling",
++ sdev->device->name);
++
++ spin_lock_irq(&sdev->spinlock);
++ sdev->enabled = enable;
++ spin_unlock_irq(&sdev->spinlock);
++
++ return 0;
++}
++
++/**
++ * srpt_is_target_enabled() - Allows to query a targets status via sysfs.
++ */
++static bool srpt_is_target_enabled(struct scst_tgt *scst_tgt)
++{
++ struct srpt_device *sdev = scst_tgt_get_tgt_priv(scst_tgt);
++ bool res;
++
++ EXTRACHECKS_WARN_ON_ONCE(irqs_disabled());
++
++ if (!sdev)
++ return false;
++
++ spin_lock_irq(&sdev->spinlock);
++ res = sdev->enabled;
++ spin_unlock_irq(&sdev->spinlock);
++ return res;
++}
++
++/**
++ * srpt_cm_req_recv() - Process the event IB_CM_REQ_RECEIVED.
++ *
++ * Ownership of the cm_id is transferred to the SCST session if this function
++ * returns zero. Otherwise the caller remains the owner of cm_id.
++ */
++static int srpt_cm_req_recv(struct ib_cm_id *cm_id,
++ struct ib_cm_req_event_param *param,
++ void *private_data)
++{
++ struct srpt_device *sdev = cm_id->context;
++ struct srp_login_req *req;
++ struct srp_login_rsp *rsp;
++ struct srp_login_rej *rej;
++ struct ib_cm_rep_param *rep_param;
++ struct srpt_rdma_ch *ch;
++ struct task_struct *thread;
++ u32 it_iu_len;
++ int i;
++ int ret = 0;
++
++ EXTRACHECKS_WARN_ON_ONCE(irqs_disabled());
++
++ if (WARN_ON(!sdev || !private_data))
++ return -EINVAL;
++
++ req = (struct srp_login_req *)private_data;
++
++ it_iu_len = be32_to_cpu(req->req_it_iu_len);
++
++ PRINT_INFO("Received SRP_LOGIN_REQ with"
++ " i_port_id 0x%llx:0x%llx, t_port_id 0x%llx:0x%llx and it_iu_len %d"
++ " on port %d (guid=0x%llx:0x%llx)",
++ be64_to_cpu(*(__be64 *)&req->initiator_port_id[0]),
++ be64_to_cpu(*(__be64 *)&req->initiator_port_id[8]),
++ be64_to_cpu(*(__be64 *)&req->target_port_id[0]),
++ be64_to_cpu(*(__be64 *)&req->target_port_id[8]),
++ it_iu_len,
++ param->port,
++ be64_to_cpu(*(__be64 *)&sdev->port[param->port - 1].gid.raw[0]),
++ be64_to_cpu(*(__be64 *)&sdev->port[param->port - 1].gid.raw[8]));
++
++ rsp = kzalloc(sizeof *rsp, GFP_KERNEL);
++ rej = kzalloc(sizeof *rej, GFP_KERNEL);
++ rep_param = kzalloc(sizeof *rep_param, GFP_KERNEL);
++
++ if (!rsp || !rej || !rep_param) {
++ ret = -ENOMEM;
++ goto out;
++ }
++
++ if (it_iu_len > srp_max_req_size || it_iu_len < 64) {
++ rej->reason = cpu_to_be32(
++ SRP_LOGIN_REJ_REQ_IT_IU_LENGTH_TOO_LARGE);
++ ret = -EINVAL;
++ PRINT_ERROR("rejected SRP_LOGIN_REQ because its"
++ " length (%d bytes) is out of range (%d .. %d)",
++ it_iu_len, 64, srp_max_req_size);
++ goto reject;
++ }
++
++ if (!srpt_is_target_enabled(sdev->scst_tgt)) {
++ rej->reason = cpu_to_be32(
++ SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
++ ret = -EINVAL;
++ PRINT_ERROR("rejected SRP_LOGIN_REQ because the target %s (%s)"
++ " has not yet been enabled",
++ sdev->scst_tgt->tgt_name, sdev->device->name);
++ goto reject;
++ }
++
++ if (*(__be64 *)req->target_port_id != cpu_to_be64(srpt_service_guid)
++ || *(__be64 *)(req->target_port_id + 8) !=
++ cpu_to_be64(srpt_service_guid)) {
++ rej->reason = cpu_to_be32(
++ SRP_LOGIN_REJ_UNABLE_ASSOCIATE_CHANNEL);
++ ret = -ENOMEM;
++ PRINT_ERROR("%s", "rejected SRP_LOGIN_REQ because it"
++ " has an invalid target port identifier.");
++ goto reject;
++ }
++
++ ch = kzalloc(sizeof *ch, GFP_KERNEL);
++ if (!ch) {
++ rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
++ PRINT_ERROR("%s",
++ "rejected SRP_LOGIN_REQ because out of memory.");
++ ret = -ENOMEM;
++ goto reject;
++ }
++
++ memcpy(ch->i_port_id, req->initiator_port_id, 16);
++ memcpy(ch->t_port_id, req->target_port_id, 16);
++ ch->sport = &sdev->port[param->port - 1];
++ ch->cm_id = cm_id;
++ cm_id->context = ch;
++ /*
++ * Avoid QUEUE_FULL conditions by limiting the number of buffers used
++ * for the SRP protocol to the SCST SCSI command queue size.
++ */
++ ch->rq_size = min(SRPT_RQ_SIZE, scst_get_max_lun_commands(NULL, 0));
++ spin_lock_init(&ch->spinlock);
++ ch->state = CH_CONNECTING;
++ INIT_LIST_HEAD(&ch->cmd_wait_list);
++ init_waitqueue_head(&ch->state_wq);
++ init_completion(&ch->finished_processing_completions);
++ ch->max_rsp_size = max_t(uint32_t, srp_max_rsp_size, MIN_MAX_RSP_SIZE);
++ ch->ioctx_ring = (struct srpt_send_ioctx **)
++ srpt_alloc_ioctx_ring(ch->sport->sdev, ch->rq_size,
++ sizeof(*ch->ioctx_ring[0]),
++ ch->max_rsp_size, DMA_TO_DEVICE);
++ if (!ch->ioctx_ring) {
++ rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
++ goto free_ch;
++ }
++
++ INIT_LIST_HEAD(&ch->free_list);
++ for (i = 0; i < ch->rq_size; i++) {
++ ch->ioctx_ring[i]->ch = ch;
++ list_add_tail(&ch->ioctx_ring[i]->free_list, &ch->free_list);
++ }
++
++ ret = srpt_create_ch_ib(ch);
++ if (ret) {
++ rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
++ PRINT_ERROR("%s", "rejected SRP_LOGIN_REQ because creating"
++ " a new RDMA channel failed.");
++ goto free_ring;
++ }
++
++ if (use_port_guid_in_session_name) {
++ /*
++ * If the kernel module parameter use_port_guid_in_session_name
++ * has been specified, use a combination of the target port
++ * GUID and the initiator port ID as the session name. This
++ * was the original behavior of the SRP target implementation
++ * (i.e. before the SRPT was included in OFED 1.3).
++ */
++ snprintf(ch->sess_name, sizeof(ch->sess_name),
++ "0x%016llx%016llx",
++ be64_to_cpu(*(__be64 *)
++ &sdev->port[param->port - 1].gid.raw[8]),
++ be64_to_cpu(*(__be64 *)(ch->i_port_id + 8)));
++ } else {
++ /*
++ * Default behavior: use the initator port identifier as the
++ * session name.
++ */
++ snprintf(ch->sess_name, sizeof(ch->sess_name),
++ "0x%016llx%016llx",
++ be64_to_cpu(*(__be64 *)ch->i_port_id),
++ be64_to_cpu(*(__be64 *)(ch->i_port_id + 8)));
++ }
++
++ TRACE_DBG("registering session %s", ch->sess_name);
++
++ BUG_ON(!sdev->scst_tgt);
++ ch->scst_sess = scst_register_session(sdev->scst_tgt, 0, ch->sess_name,
++ ch, NULL, NULL);
++ if (!ch->scst_sess) {
++ rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
++ TRACE_DBG("%s", "Failed to create SCST session");
++ goto destroy_ib;
++ }
++
++ thread = kthread_run(srpt_compl_thread, ch, "srpt_%s",
++ ch->sport->sdev->device->name);
++ if (IS_ERR(thread)) {
++ rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
++ PRINT_ERROR("failed to create kernel thread %ld", PTR_ERR(ch->thread));
++ goto unreg_ch;
++ }
++
++ spin_lock_irq(&sdev->spinlock);
++ if ((req->req_flags & SRP_MTCH_ACTION) == SRP_MULTICHAN_SINGLE) {
++ struct srpt_rdma_ch *ch2;
++
++ rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_NO_CHAN;
++ list_for_each_entry(ch2, &sdev->rch_list, list) {
++ if (!memcmp(ch2->i_port_id, req->initiator_port_id, 16)
++ && !memcmp(ch2->t_port_id, req->target_port_id, 16)
++ && param->port == ch2->sport->port
++ && param->listen_id == ch2->sport->sdev->cm_id
++ && ch2->cm_id) {
++ if (!__srpt_close_ch(ch2))
++ continue;
++
++ PRINT_INFO("Relogin - closed existing channel"
++ " %s; cm_id = %p", ch2->sess_name,
++ ch2->cm_id);
++
++ rsp->rsp_flags =
++ SRP_LOGIN_RSP_MULTICHAN_TERMINATED;
++ }
++ }
++ } else {
++ rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_MAINTAINED;
++ }
++ list_add_tail(&ch->list, &sdev->rch_list);
++ ch->thread = thread;
++ spin_unlock_irq(&sdev->spinlock);
++
++ ret = srpt_ch_qp_rtr(ch, ch->qp);
++ if (ret) {
++ rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
++ PRINT_ERROR("rejected SRP_LOGIN_REQ because enabling"
++ " RTR failed (error code = %d)", ret);
++ goto reject_and_release;
++ }
++
++ TRACE_DBG("Establish connection sess=%p name=%s cm_id=%p",
++ ch->scst_sess, ch->sess_name, ch->cm_id);
++
++ /* create srp_login_response */
++ rsp->opcode = SRP_LOGIN_RSP;
++ rsp->tag = req->tag;
++ rsp->max_it_iu_len = req->req_it_iu_len;
++ rsp->max_ti_iu_len = req->req_it_iu_len;
++ ch->max_ti_iu_len = it_iu_len;
++ rsp->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT |
++ SRP_BUF_FORMAT_INDIRECT);
++ rsp->req_lim_delta = cpu_to_be32(ch->rq_size);
++ ch->req_lim = ch->rq_size;
++ ch->req_lim_delta = 0;
++
++ /* create cm reply */
++ rep_param->qp_num = ch->qp->qp_num;
++ rep_param->private_data = (void *)rsp;
++ rep_param->private_data_len = sizeof *rsp;
++ rep_param->rnr_retry_count = 7;
++ rep_param->flow_control = 1;
++ rep_param->failover_accepted = 0;
++ rep_param->srq = 1;
++ rep_param->responder_resources = 4;
++ rep_param->initiator_depth = 4;
++
++ spin_lock_irq(&sdev->spinlock);
++ if (ch->state == CH_CONNECTING)
++ ret = ib_send_cm_rep(cm_id, rep_param);
++ else
++ ret = -ECONNABORTED;
++ spin_unlock_irq(&sdev->spinlock);
++
++ switch (ret) {
++ case 0:
++ break;
++ case -ECONNABORTED:
++ goto out_keep_cm_id;
++ default:
++ rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
++ PRINT_ERROR("sending SRP_LOGIN_REQ response failed"
++ " (error code = %d)", ret);
++ goto reject_and_release;
++ }
++
++ goto out;
++
++reject_and_release:
++ PRINT_INFO("Rejecting login with reason %#x", be32_to_cpu(rej->reason));
++ rej->opcode = SRP_LOGIN_REJ;
++ rej->tag = req->tag;
++ rej->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT |
++ SRP_BUF_FORMAT_INDIRECT);
++ ib_send_cm_rej(cm_id, IB_CM_REJ_CONSUMER_DEFINED, NULL, 0,
++ (void *)rej, sizeof *rej);
++
++ srpt_close_ch(ch);
++out_keep_cm_id:
++ /*
++ * Tell the caller not to free cm_id since srpt_free_ch() will do that.
++ */
++ ret = 0;
++ goto out;
++
++unreg_ch:
++ scst_unregister_session(ch->scst_sess, true, NULL);
++
++destroy_ib:
++ srpt_destroy_ch_ib(ch);
++
++free_ring:
++ srpt_free_ioctx_ring((struct srpt_ioctx **)ch->ioctx_ring,
++ ch->sport->sdev, ch->rq_size,
++ ch->max_rsp_size, DMA_TO_DEVICE);
++
++free_ch:
++ cm_id->context = NULL;
++ kfree(ch);
++
++reject:
++ PRINT_INFO("Rejecting login with reason %#x", be32_to_cpu(rej->reason));
++ rej->opcode = SRP_LOGIN_REJ;
++ rej->tag = req->tag;
++ rej->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT |
++ SRP_BUF_FORMAT_INDIRECT);
++ ib_send_cm_rej(cm_id, IB_CM_REJ_CONSUMER_DEFINED, NULL, 0,
++ (void *)rej, sizeof *rej);
++
++out:
++ kfree(rep_param);
++ kfree(rsp);
++ kfree(rej);
++
++ return ret;
++}
++
++static void srpt_cm_rej_recv(struct ib_cm_id *cm_id)
++{
++ PRINT_INFO("Received InfiniBand REJ packet for cm_id %p.", cm_id);
++ srpt_drain_channel(cm_id);
++}
++
++/**
++ * srpt_cm_rtu_recv() - Process IB CM RTU_RECEIVED and USER_ESTABLISHED events.
++ *
++ * An IB_CM_RTU_RECEIVED message indicates that the connection is established
++ * and that the recipient may begin transmitting (RTU = ready to use).
++ */
++static void srpt_cm_rtu_recv(struct ib_cm_id *cm_id)
++{
++ struct srpt_rdma_ch *ch;
++ int ret;
++
++ ch = cm_id->context;
++ BUG_ON(!ch);
++
++ if (srpt_test_and_set_ch_state(ch, CH_CONNECTING, CH_LIVE)) {
++ struct srpt_recv_ioctx *ioctx, *ioctx_tmp;
++
++ ret = srpt_ch_qp_rts(ch, ch->qp);
++
++ list_for_each_entry_safe(ioctx, ioctx_tmp, &ch->cmd_wait_list,
++ wait_list) {
++ list_del(&ioctx->wait_list);
++ srpt_handle_new_iu(ch, ioctx, NULL,
++ SCST_CONTEXT_THREAD);
++ }
++ if (ret)
++ srpt_close_ch(ch);
++ }
++}
++
++static void srpt_cm_timewait_exit(struct ib_cm_id *cm_id)
++{
++ PRINT_INFO("Received InfiniBand TimeWait exit for cm_id %p.", cm_id);
++ srpt_drain_channel(cm_id);
++}
++
++static void srpt_cm_rep_error(struct ib_cm_id *cm_id)
++{
++ PRINT_INFO("Received InfiniBand REP error for cm_id %p.", cm_id);
++ srpt_drain_channel(cm_id);
++}
++
++/**
++ * srpt_cm_dreq_recv() - Process reception of a DREQ message.
++ */
++static void srpt_cm_dreq_recv(struct ib_cm_id *cm_id)
++{
++ struct srpt_rdma_ch *ch;
++
++ ch = cm_id->context;
++
++ switch (srpt_set_ch_state_to_disc(ch)) {
++ case CH_CONNECTING:
++ case CH_LIVE:
++ if (ib_send_cm_drep(ch->cm_id, NULL, 0) >= 0)
++ PRINT_INFO("Received DREQ and sent DREP for session %s",
++ ch->sess_name);
++ else
++ PRINT_ERROR("%s", "Sending DREP failed");
++ break;
++ default:
++ WARN_ON(true);
++ break;
++ }
++}
++
++/**
++ * srpt_cm_drep_recv() - Process reception of a DREP message.
++ */
++static void srpt_cm_drep_recv(struct ib_cm_id *cm_id)
++{
++ PRINT_INFO("Received InfiniBand DREP message for cm_id %p.", cm_id);
++ srpt_drain_channel(cm_id);
++}
++
++/**
++ * srpt_cm_handler() - IB connection manager callback function.
++ *
++ * A non-zero return value will cause the caller destroy the CM ID.
++ *
++ * Note: srpt_cm_handler() must only return a non-zero value when transferring
++ * ownership of the cm_id to a channel if srpt_cm_req_recv() failed. Returning
++ * a non-zero value in any other case will trigger a race with the
++ * ib_destroy_cm_id() call in srpt_free_ch().
++ */
++static int srpt_cm_handler(struct ib_cm_id *cm_id, struct ib_cm_event *event)
++{
++ int ret;
++
++ BUG_ON(!cm_id->context);
++
++ ret = 0;
++ switch (event->event) {
++ case IB_CM_REQ_RECEIVED:
++ ret = srpt_cm_req_recv(cm_id, &event->param.req_rcvd,
++ event->private_data);
++ break;
++ case IB_CM_REJ_RECEIVED:
++ srpt_cm_rej_recv(cm_id);
++ break;
++ case IB_CM_RTU_RECEIVED:
++ case IB_CM_USER_ESTABLISHED:
++ srpt_cm_rtu_recv(cm_id);
++ break;
++ case IB_CM_DREQ_RECEIVED:
++ srpt_cm_dreq_recv(cm_id);
++ break;
++ case IB_CM_DREP_RECEIVED:
++ srpt_cm_drep_recv(cm_id);
++ break;
++ case IB_CM_TIMEWAIT_EXIT:
++ srpt_cm_timewait_exit(cm_id);
++ break;
++ case IB_CM_REP_ERROR:
++ srpt_cm_rep_error(cm_id);
++ break;
++ case IB_CM_DREQ_ERROR:
++ PRINT_INFO("%s", "Received IB DREQ ERROR event.");
++ break;
++ case IB_CM_MRA_RECEIVED:
++ PRINT_INFO("%s", "Received IB MRA event");
++ break;
++ default:
++ PRINT_ERROR("received unrecognized IB CM event %d",
++ event->event);
++ break;
++ }
++
++ return ret;
++}
++
++/**
++ * srpt_map_sg_to_ib_sge() - Map an SG list to an IB SGE list.
++ */
++static int srpt_map_sg_to_ib_sge(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx,
++ struct scst_cmd *scmnd)
++{
++ struct scatterlist *sg;
++ int sg_cnt;
++ scst_data_direction dir;
++ struct rdma_iu *riu;
++ struct srp_direct_buf *db;
++ dma_addr_t dma_addr;
++ struct ib_sge *sge_array, *sge;
++ u64 raddr;
++ u32 rsize;
++ u32 tsize;
++ u32 dma_len;
++ int count;
++ int i, j, k;
++ int max_sge, nsge;
++
++ BUG_ON(!ch);
++ BUG_ON(!ioctx);
++ BUG_ON(!scmnd);
++ max_sge = ch->max_sge;
++ dir = scst_cmd_get_data_direction(scmnd);
++ BUG_ON(dir == SCST_DATA_NONE);
++ /*
++ * Cache 'dir' because it is needed in srpt_unmap_sg_to_ib_sge()
++ * and because scst_set_cmd_error_status() resets scmnd->data_direction.
++ */
++ ioctx->dir = dir;
++ if (dir == SCST_DATA_WRITE) {
++ scst_cmd_get_write_fields(scmnd, &sg, &sg_cnt);
++ WARN_ON(!sg);
++ } else {
++ sg = scst_cmd_get_sg(scmnd);
++ sg_cnt = scst_cmd_get_sg_cnt(scmnd);
++ WARN_ON(!sg);
++ }
++ ioctx->sg = sg;
++ ioctx->sg_cnt = sg_cnt;
++ count = ib_dma_map_sg(ch->sport->sdev->device, sg, sg_cnt,
++ scst_to_tgt_dma_dir(dir));
++ if (unlikely(!count))
++ return -EBUSY;
++
++ ioctx->mapped_sg_count = count;
++
++ {
++ int size, nrdma;
++
++ nrdma = (count + max_sge - 1) / max_sge + ioctx->n_rbuf;
++ nsge = count + ioctx->n_rbuf;
++ size = nrdma * sizeof(*riu) + nsge * sizeof(*sge);
++ ioctx->rdma_ius = size <= sizeof(ioctx->rdma_ius_buf) ?
++ ioctx->rdma_ius_buf : kmalloc(size,
++ scst_cmd_atomic(scmnd) ? GFP_ATOMIC : GFP_KERNEL);
++ if (!ioctx->rdma_ius)
++ goto free_mem;
++
++ ioctx->n_rdma_ius = nrdma;
++ sge_array = (struct ib_sge *)(ioctx->rdma_ius + nrdma);
++ }
++
++ db = ioctx->rbufs;
++ tsize = (dir == SCST_DATA_READ)
++ ? scst_cmd_get_adjusted_resp_data_len(scmnd)
++ : scst_cmd_get_bufflen(scmnd);
++ dma_len = sg_dma_len(&sg[0]);
++ riu = ioctx->rdma_ius;
++ sge = sge_array;
++
++ /*
++ * For each remote desc - calculate the #ib_sge.
++ * If #ib_sge < SRPT_DEF_SG_PER_WQE per rdma operation then
++ * each remote desc rdma_iu is required a rdma wr;
++ * else
++ * we need to allocate extra rdma_iu to carry extra #ib_sge in
++ * another rdma wr
++ */
++ for (i = 0, j = 0;
++ j < count && i < ioctx->n_rbuf && tsize > 0; ++i, ++riu, ++db) {
++ rsize = be32_to_cpu(db->len);
++ raddr = be64_to_cpu(db->va);
++ riu->raddr = raddr;
++ riu->rkey = be32_to_cpu(db->key);
++ riu->sge_cnt = 0;
++ riu->sge = sge;
++
++ /* calculate how many sge required for this remote_buf */
++ while (rsize > 0 && tsize > 0) {
++
++ if (rsize >= dma_len) {
++ tsize -= dma_len;
++ rsize -= dma_len;
++ raddr += dma_len;
++
++ if (tsize > 0) {
++ ++j;
++ if (j < count)
++ dma_len = sg_dma_len(&sg[j]);
++ }
++ } else {
++ tsize -= rsize;
++ dma_len -= rsize;
++ rsize = 0;
++ }
++
++ ++riu->sge_cnt;
++ ++sge;
++
++ if (rsize > 0 && riu->sge_cnt == max_sge) {
++ ++riu;
++ riu->raddr = raddr;
++ riu->rkey = be32_to_cpu(db->key);
++ riu->sge_cnt = 0;
++ riu->sge = sge;
++ }
++ }
++ }
++
++ ioctx->n_rdma = riu - ioctx->rdma_ius;
++ EXTRACHECKS_WARN_ON(ioctx->n_rdma > ioctx->n_rdma_ius);
++ EXTRACHECKS_WARN_ON(sge - sge_array > nsge);
++
++ db = ioctx->rbufs;
++ tsize = (dir == SCST_DATA_READ)
++ ? scst_cmd_get_adjusted_resp_data_len(scmnd)
++ : scst_cmd_get_bufflen(scmnd);
++ riu = ioctx->rdma_ius;
++ dma_len = sg_dma_len(&sg[0]);
++ dma_addr = sg_dma_address(&sg[0]);
++
++ /* this second loop is really mapped sg_addres to rdma_iu->ib_sge */
++ for (i = 0, j = 0;
++ j < count && i < ioctx->n_rbuf && tsize > 0; ++i, ++riu, ++db) {
++ rsize = be32_to_cpu(db->len);
++ sge = riu->sge;
++ k = 0;
++
++ while (rsize > 0 && tsize > 0) {
++ sge->addr = dma_addr;
++ sge->lkey = ch->sport->sdev->mr->lkey;
++
++ if (rsize >= dma_len) {
++ sge->length =
++ (tsize < dma_len) ? tsize : dma_len;
++ tsize -= dma_len;
++ rsize -= dma_len;
++
++ if (tsize > 0) {
++ ++j;
++ if (j < count) {
++ dma_len = sg_dma_len(&sg[j]);
++ dma_addr =
++ sg_dma_address(&sg[j]);
++ }
++ }
++ } else {
++ sge->length = (tsize < rsize) ? tsize : rsize;
++ tsize -= rsize;
++ dma_len -= rsize;
++ dma_addr += rsize;
++ rsize = 0;
++ }
++
++ ++k;
++ if (k == riu->sge_cnt && rsize > 0 && tsize > 0) {
++ ++riu;
++ sge = riu->sge;
++ k = 0;
++ } else if (rsize > 0 && tsize > 0)
++ ++sge;
++ }
++ }
++
++ EXTRACHECKS_WARN_ON(riu - ioctx->rdma_ius != ioctx->n_rdma);
++
++ return 0;
++
++free_mem:
++ srpt_unmap_sg_to_ib_sge(ch, ioctx);
++
++ return -ENOMEM;
++}
++
++/**
++ * srpt_unmap_sg_to_ib_sge() - Unmap an IB SGE list.
++ */
++static void srpt_unmap_sg_to_ib_sge(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx)
++{
++ struct scatterlist *sg;
++ scst_data_direction dir;
++
++ EXTRACHECKS_BUG_ON(!ch);
++ EXTRACHECKS_BUG_ON(!ioctx);
++ EXTRACHECKS_BUG_ON(ioctx->n_rdma && !ioctx->rdma_ius);
++
++ if (ioctx->rdma_ius != (void *)ioctx->rdma_ius_buf)
++ kfree(ioctx->rdma_ius);
++ ioctx->rdma_ius = NULL;
++ ioctx->n_rdma = 0;
++
++ if (ioctx->mapped_sg_count) {
++ EXTRACHECKS_BUG_ON(!ioctx->scmnd);
++ EXTRACHECKS_WARN_ON(ioctx
++ != scst_cmd_get_tgt_priv(ioctx->scmnd));
++ sg = ioctx->sg;
++ EXTRACHECKS_WARN_ON(!sg);
++ dir = ioctx->dir;
++ EXTRACHECKS_BUG_ON(dir == SCST_DATA_NONE);
++ ib_dma_unmap_sg(ch->sport->sdev->device, sg, ioctx->sg_cnt,
++ scst_to_tgt_dma_dir(dir));
++ ioctx->mapped_sg_count = 0;
++ }
++}
++
++/**
++ * srpt_perform_rdmas() - Perform IB RDMA.
++ *
++ * Returns zero upon success or a negative number upon failure.
++ */
++static int srpt_perform_rdmas(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx,
++ scst_data_direction dir)
++{
++ struct ib_send_wr wr;
++ struct ib_send_wr *bad_wr;
++ struct rdma_iu *riu;
++ int i;
++ int ret;
++ int sq_wr_avail;
++ const int n_rdma = ioctx->n_rdma;
++
++ if (dir == SCST_DATA_WRITE) {
++ ret = -ENOMEM;
++ sq_wr_avail = srpt_adjust_srq_wr_avail(ch, -n_rdma);
++ if (sq_wr_avail < 0) {
++ PRINT_WARNING("IB send queue full (needed %d)",
++ n_rdma);
++ goto out;
++ }
++ }
++
++ ioctx->rdma_aborted = false;
++ ret = 0;
++ riu = ioctx->rdma_ius;
++ memset(&wr, 0, sizeof wr);
++
++ for (i = 0; i < n_rdma; ++i, ++riu) {
++ if (dir == SCST_DATA_READ) {
++ wr.opcode = IB_WR_RDMA_WRITE;
++ wr.wr_id = encode_wr_id(i == n_rdma - 1 ?
++ SRPT_RDMA_WRITE_LAST :
++ SRPT_RDMA_MID,
++ ioctx->ioctx.index);
++ } else {
++ wr.opcode = IB_WR_RDMA_READ;
++ wr.wr_id = encode_wr_id(i == n_rdma - 1 ?
++ SRPT_RDMA_READ_LAST :
++ SRPT_RDMA_MID,
++ ioctx->ioctx.index);
++ }
++ wr.next = NULL;
++ wr.wr.rdma.remote_addr = riu->raddr;
++ wr.wr.rdma.rkey = riu->rkey;
++ wr.num_sge = riu->sge_cnt;
++ wr.sg_list = riu->sge;
++
++ /* only get completion event for the last rdma wr */
++ if (i == (n_rdma - 1) && dir == SCST_DATA_WRITE)
++ wr.send_flags = IB_SEND_SIGNALED;
++
++ ret = ib_post_send(ch->qp, &wr, &bad_wr);
++ if (ret)
++ break;
++ }
++
++ if (ret)
++ PRINT_ERROR("%s[%d]: ib_post_send() returned %d for %d/%d",
++ __func__, __LINE__, ret, i, n_rdma);
++ if (ret && i > 0) {
++ wr.num_sge = 0;
++ wr.wr_id = encode_wr_id(SRPT_RDMA_ABORT, ioctx->ioctx.index);
++ wr.send_flags = IB_SEND_SIGNALED;
++ while (ch->state == CH_LIVE &&
++ ib_post_send(ch->qp, &wr, &bad_wr) != 0) {
++ PRINT_INFO("Trying to abort failed RDMA transfer [%d]",
++ ioctx->ioctx.index);
++ msleep(1000);
++ }
++ while (ch->state != CH_DRAINING && !ioctx->rdma_aborted) {
++ PRINT_INFO("Waiting until RDMA abort finished [%d]",
++ ioctx->ioctx.index);
++ msleep(1000);
++ }
++ PRINT_INFO("%s[%d]: done", __func__, __LINE__);
++ }
++
++out:
++ if (unlikely(dir == SCST_DATA_WRITE && ret < 0))
++ srpt_adjust_srq_wr_avail(ch, n_rdma);
++ return ret;
++}
++
++/**
++ * srpt_xfer_data() - Start data transfer from initiator to target.
++ *
++ * Returns an SCST_TGT_RES_... status code.
++ *
++ * Note: Must not block.
++ */
++static int srpt_xfer_data(struct srpt_rdma_ch *ch,
++ struct srpt_send_ioctx *ioctx,
++ struct scst_cmd *scmnd)
++{
++ int ret;
++
++ ret = srpt_map_sg_to_ib_sge(ch, ioctx, scmnd);
++ if (ret) {
++ PRINT_ERROR("%s[%d] ret=%d", __func__, __LINE__, ret);
++ ret = SCST_TGT_RES_QUEUE_FULL;
++ goto out;
++ }
++
++ ret = srpt_perform_rdmas(ch, ioctx, scst_cmd_get_data_direction(scmnd));
++ if (ret) {
++ if (ret == -EAGAIN || ret == -ENOMEM) {
++ PRINT_INFO("%s[%d] queue full -- ret=%d",
++ __func__, __LINE__, ret);
++ ret = SCST_TGT_RES_QUEUE_FULL;
++ } else {
++ PRINT_ERROR("%s[%d] fatal error -- ret=%d",
++ __func__, __LINE__, ret);
++ ret = SCST_TGT_RES_FATAL_ERROR;
++ }
++ goto out_unmap;
++ }
++
++ ret = SCST_TGT_RES_SUCCESS;
++
++out:
++ return ret;
++out_unmap:
++ srpt_unmap_sg_to_ib_sge(ch, ioctx);
++ goto out;
++}
++
++/**
++ * srpt_pending_cmd_timeout() - SCST command HCA processing timeout callback.
++ *
++ * Called by the SCST core if no IB completion notification has been received
++ * within RDMA_COMPL_TIMEOUT_S seconds.
++ */
++static void srpt_pending_cmd_timeout(struct scst_cmd *scmnd)
++{
++ struct srpt_send_ioctx *ioctx;
++ enum srpt_command_state state;
++
++ ioctx = scst_cmd_get_tgt_priv(scmnd);
++ BUG_ON(!ioctx);
++
++ state = ioctx->state;
++ switch (state) {
++ case SRPT_STATE_NEW:
++ case SRPT_STATE_DATA_IN:
++ case SRPT_STATE_DONE:
++ /*
++ * srpt_pending_cmd_timeout() should never be invoked for
++ * commands in this state.
++ */
++ PRINT_ERROR("Processing SCST command %p (SRPT state %d) took"
++ " too long -- aborting", scmnd, state);
++ break;
++ case SRPT_STATE_NEED_DATA:
++ case SRPT_STATE_CMD_RSP_SENT:
++ case SRPT_STATE_MGMT_RSP_SENT:
++ default:
++ PRINT_ERROR("Command %p: IB completion for idx %u has not"
++ " been received in time (SRPT command state %d)",
++ scmnd, ioctx->ioctx.index, state);
++ break;
++ }
++
++ srpt_abort_cmd(ioctx, SCST_CONTEXT_SAME);
++}
++
++/**
++ * srpt_rdy_to_xfer() - Transfers data from initiator to target.
++ *
++ * Called by the SCST core to transfer data from the initiator to the target
++ * (SCST_DATA_WRITE). Must not block.
++ */
++static int srpt_rdy_to_xfer(struct scst_cmd *scmnd)
++{
++ struct srpt_send_ioctx *ioctx;
++ enum srpt_command_state prev_cmd_state;
++ int ret;
++
++ ioctx = scst_cmd_get_tgt_priv(scmnd);
++ prev_cmd_state = srpt_set_cmd_state(ioctx, SRPT_STATE_NEED_DATA);
++ ret = srpt_xfer_data(ioctx->ch, ioctx, scmnd);
++ if (unlikely(ret != SCST_TGT_RES_SUCCESS))
++ srpt_set_cmd_state(ioctx, prev_cmd_state);
++
++ return ret;
++}
++
++/**
++ * srpt_xmit_response() - Transmits the response to a SCSI command.
++ *
++ * Callback function called by the SCST core. Must not block. Must ensure that
++ * scst_tgt_cmd_done() will get invoked when returning SCST_TGT_RES_SUCCESS.
++ */
++static int srpt_xmit_response(struct scst_cmd *scmnd)
++{
++ struct srpt_rdma_ch *ch;
++ struct srpt_send_ioctx *ioctx;
++ enum srpt_command_state state;
++ int ret;
++ scst_data_direction dir;
++ int resp_len;
++
++ ret = SCST_TGT_RES_SUCCESS;
++
++ ioctx = scst_cmd_get_tgt_priv(scmnd);
++ BUG_ON(!ioctx);
++
++ ch = scst_sess_get_tgt_priv(scst_cmd_get_session(scmnd));
++ BUG_ON(!ch);
++
++ spin_lock(&ioctx->spinlock);
++ state = ioctx->state;
++ switch (state) {
++ case SRPT_STATE_NEW:
++ case SRPT_STATE_DATA_IN:
++ ioctx->state = SRPT_STATE_CMD_RSP_SENT;
++ break;
++ default:
++ WARN(true, "Unexpected command state %d", state);
++ break;
++ }
++ spin_unlock(&ioctx->spinlock);
++
++ if (unlikely(scst_cmd_aborted(scmnd))) {
++ srpt_adjust_req_lim(ch, 0, 1);
++ srpt_abort_cmd(ioctx, SCST_CONTEXT_SAME);
++ goto out;
++ }
++
++ EXTRACHECKS_BUG_ON(scst_cmd_atomic(scmnd));
++
++ dir = scst_cmd_get_data_direction(scmnd);
++
++ /* For read commands, transfer the data to the initiator. */
++ if (dir == SCST_DATA_READ
++ && scst_cmd_get_adjusted_resp_data_len(scmnd)) {
++ ret = srpt_xfer_data(ch, ioctx, scmnd);
++ if (unlikely(ret != SCST_TGT_RES_SUCCESS)) {
++ srpt_set_cmd_state(ioctx, state);
++ PRINT_WARNING("xfer_data failed for tag %llu"
++ " - %s", scst_cmd_get_tag(scmnd),
++ ret == SCST_TGT_RES_QUEUE_FULL ?
++ "retrying" : "failing");
++ goto out;
++ }
++ }
++
++ ioctx->req_lim_delta = srpt_inc_req_lim(ch);
++ resp_len = srpt_build_cmd_rsp(ch, ioctx,
++ scst_cmd_get_tag(scmnd),
++ scst_cmd_get_status(scmnd),
++ scst_cmd_get_sense_buffer(scmnd),
++ scst_cmd_get_sense_buffer_len(scmnd));
++
++ if (srpt_post_send(ch, ioctx, resp_len)) {
++ srpt_unmap_sg_to_ib_sge(ch, ioctx);
++ srpt_set_cmd_state(ioctx, state);
++ srpt_undo_inc_req_lim(ch, ioctx->req_lim_delta);
++ PRINT_WARNING("sending response failed for tag %llu - retrying",
++ scst_cmd_get_tag(scmnd));
++ ret = SCST_TGT_RES_QUEUE_FULL;
++ }
++
++out:
++ return ret;
++}
++
++/**
++ * srpt_tsk_mgmt_done() - SCST callback function that sends back the response
++ * for a task management request.
++ *
++ * Must not block.
++ */
++static void srpt_tsk_mgmt_done(struct scst_mgmt_cmd *mcmnd)
++{
++ struct srpt_rdma_ch *ch;
++ struct srpt_send_ioctx *ioctx;
++ int rsp_len;
++
++ ioctx = scst_mgmt_cmd_get_tgt_priv(mcmnd);
++ BUG_ON(!ioctx);
++
++ ch = ioctx->ch;
++ BUG_ON(!ch);
++
++ TRACE_DBG("%s: tsk_mgmt_done for tag= %lld status=%d",
++ __func__, ioctx->tsk_mgmt.tag,
++ scst_mgmt_cmd_get_status(mcmnd));
++
++ WARN_ON(in_irq());
++
++ srpt_set_cmd_state(ioctx, SRPT_STATE_MGMT_RSP_SENT);
++ WARN_ON(ioctx->state == SRPT_STATE_DONE);
++
++ ioctx->req_lim_delta = srpt_inc_req_lim(ch);
++ rsp_len = srpt_build_tskmgmt_rsp(ch, ioctx,
++ scst_to_srp_tsk_mgmt_status(
++ scst_mgmt_cmd_get_status(mcmnd)),
++ ioctx->tsk_mgmt.tag);
++ /*
++ * Note: the srpt_post_send() call below sends the task management
++ * response asynchronously. It is possible that the SCST core has
++ * already freed the struct scst_mgmt_cmd structure before the
++ * response is sent. This is fine however.
++ */
++ if (srpt_post_send(ch, ioctx, rsp_len)) {
++ PRINT_ERROR("%s", "Sending SRP_RSP response failed.");
++ srpt_put_send_ioctx(ioctx);
++ srpt_undo_inc_req_lim(ch, ioctx->req_lim_delta);
++ }
++}
++
++/**
++ * srpt_get_initiator_port_transport_id() - SCST TransportID callback function.
++ *
++ * See also SPC-3, section 7.5.4.5, TransportID for initiator ports using SRP.
++ */
++static int srpt_get_initiator_port_transport_id(struct scst_tgt *tgt,
++ struct scst_session *scst_sess, uint8_t **transport_id)
++{
++ struct srpt_rdma_ch *ch;
++ struct spc_rdma_transport_id {
++ uint8_t protocol_identifier;
++ uint8_t reserved[7];
++ uint8_t i_port_id[16];
++ };
++ struct spc_rdma_transport_id *tr_id;
++ int res;
++
++ TRACE_ENTRY();
++
++ if (!scst_sess) {
++ res = SCSI_TRANSPORTID_PROTOCOLID_SRP;
++ goto out;
++ }
++
++ ch = scst_sess_get_tgt_priv(scst_sess);
++ BUG_ON(!ch);
++
++ BUILD_BUG_ON(sizeof(*tr_id) != 24);
++
++ tr_id = kzalloc(sizeof(struct spc_rdma_transport_id), GFP_KERNEL);
++ if (!tr_id) {
++ PRINT_ERROR("%s", "Allocation of TransportID failed");
++ res = -ENOMEM;
++ goto out;
++ }
++
++ res = 0;
++ tr_id->protocol_identifier = SCSI_TRANSPORTID_PROTOCOLID_SRP;
++ memcpy(tr_id->i_port_id, ch->i_port_id, sizeof(ch->i_port_id));
++
++ *transport_id = (uint8_t *)tr_id;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ * srpt_on_free_cmd() - Free command-private data.
++ *
++ * Called by the SCST core. May be called in IRQ context.
++ */
++static void srpt_on_free_cmd(struct scst_cmd *scmnd)
++{
++}
++
++static void srpt_refresh_port_work(struct work_struct *work)
++{
++ struct srpt_port *sport = container_of(work, struct srpt_port, work);
++
++ srpt_refresh_port(sport);
++}
++
++/**
++ * srpt_detect() - Returns the number of target adapters.
++ *
++ * Callback function called by the SCST core.
++ */
++static int srpt_detect(struct scst_tgt_template *tp)
++{
++ int device_count;
++
++ TRACE_ENTRY();
++
++ device_count = atomic_read(&srpt_device_count);
++
++ TRACE_EXIT_RES(device_count);
++
++ return device_count;
++}
++
++static int srpt_ch_list_empty(struct srpt_device *sdev)
++{
++ int res;
++
++ spin_lock_irq(&sdev->spinlock);
++ res = list_empty(&sdev->rch_list);
++ spin_unlock_irq(&sdev->spinlock);
++
++ return res;
++}
++
++/**
++ * srpt_release_sdev() - Free channel resources associated with a target.
++ */
++static int srpt_release_sdev(struct srpt_device *sdev)
++{
++ struct srpt_rdma_ch *ch, *next_ch;
++
++ TRACE_ENTRY();
++
++ WARN_ON_ONCE(irqs_disabled());
++ BUG_ON(!sdev);
++
++ spin_lock_irq(&sdev->spinlock);
++ list_for_each_entry_safe(ch, next_ch, &sdev->rch_list, list)
++ __srpt_close_ch(ch);
++ spin_unlock_irq(&sdev->spinlock);
++
++ while (wait_event_timeout(sdev->ch_releaseQ,
++ srpt_ch_list_empty(sdev), 5 * HZ) <= 0) {
++ PRINT_INFO("%s: waiting for session unregistration ...",
++ sdev->device->name);
++ spin_lock_irq(&sdev->spinlock);
++ list_for_each_entry_safe(ch, next_ch, &sdev->rch_list, list) {
++ PRINT_INFO("%s: state %s; %d commands in progress",
++ ch->sess_name, get_ch_state_name(ch->state),
++ atomic_read(&ch->scst_sess->sess_cmd_count));
++ }
++ spin_unlock_irq(&sdev->spinlock);
++ }
++
++ TRACE_EXIT();
++ return 0;
++}
++
++/**
++ * srpt_release() - Free the resources associated with an SCST target.
++ *
++ * Callback function called by the SCST core from scst_unregister_target().
++ */
++static int srpt_release(struct scst_tgt *scst_tgt)
++{
++ struct srpt_device *sdev = scst_tgt_get_tgt_priv(scst_tgt);
++
++ TRACE_ENTRY();
++
++ EXTRACHECKS_WARN_ON_ONCE(irqs_disabled());
++
++ BUG_ON(!scst_tgt);
++ if (WARN_ON(!sdev))
++ return -ENODEV;
++
++ srpt_release_sdev(sdev);
++
++ scst_tgt_set_tgt_priv(scst_tgt, NULL);
++
++ TRACE_EXIT();
++
++ return 0;
++}
++
++/**
++ * srpt_get_scsi_transport_version() - Returns the SCSI transport version.
++ * This function is called from scst_pres.c, the code that implements
++ * persistent reservation support.
++ */
++static uint16_t srpt_get_scsi_transport_version(struct scst_tgt *scst_tgt)
++{
++ return 0x0940; /* SRP */
++}
++
++static ssize_t show_login_info(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt *scst_tgt;
++ struct srpt_device *sdev;
++ struct srpt_port *sport;
++ int i;
++ int len;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ sdev = scst_tgt_get_tgt_priv(scst_tgt);
++ len = 0;
++ for (i = 0; i < sdev->device->phys_port_cnt; i++) {
++ sport = &sdev->port[i];
++
++ len += sprintf(buf + len,
++ "tid_ext=%016llx,ioc_guid=%016llx,pkey=ffff,"
++ "dgid=%04x%04x%04x%04x%04x%04x%04x%04x,"
++ "service_id=%016llx\n",
++ srpt_service_guid,
++ srpt_service_guid,
++ be16_to_cpu(((__be16 *) sport->gid.raw)[0]),
++ be16_to_cpu(((__be16 *) sport->gid.raw)[1]),
++ be16_to_cpu(((__be16 *) sport->gid.raw)[2]),
++ be16_to_cpu(((__be16 *) sport->gid.raw)[3]),
++ be16_to_cpu(((__be16 *) sport->gid.raw)[4]),
++ be16_to_cpu(((__be16 *) sport->gid.raw)[5]),
++ be16_to_cpu(((__be16 *) sport->gid.raw)[6]),
++ be16_to_cpu(((__be16 *) sport->gid.raw)[7]),
++ srpt_service_guid);
++ }
++
++ return len;
++}
++
++static struct kobj_attribute srpt_show_login_info_attr =
++ __ATTR(login_info, S_IRUGO, show_login_info, NULL);
++
++static const struct attribute *srpt_tgt_attrs[] = {
++ &srpt_show_login_info_attr.attr,
++ NULL
++};
++
++static ssize_t show_req_lim(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_session *scst_sess;
++ struct srpt_rdma_ch *ch;
++
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
++ ch = scst_sess_get_tgt_priv(scst_sess);
++ if (!ch)
++ return -ENOENT;
++ return sprintf(buf, "%d\n", ch->req_lim);
++}
++
++static ssize_t show_req_lim_delta(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_session *scst_sess;
++ struct srpt_rdma_ch *ch;
++
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
++ ch = scst_sess_get_tgt_priv(scst_sess);
++ if (!ch)
++ return -ENOENT;
++ return sprintf(buf, "%d\n", ch->req_lim_delta);
++}
++
++static ssize_t show_ch_state(struct kobject *kobj, struct kobj_attribute *attr,
++ char *buf)
++{
++ struct scst_session *scst_sess;
++ struct srpt_rdma_ch *ch;
++
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
++ ch = scst_sess_get_tgt_priv(scst_sess);
++ if (!ch)
++ return -ENOENT;
++ return sprintf(buf, "%s\n", get_ch_state_name(ch->state));
++}
++
++static const struct kobj_attribute srpt_req_lim_attr =
++ __ATTR(req_lim, S_IRUGO, show_req_lim, NULL);
++static const struct kobj_attribute srpt_req_lim_delta_attr =
++ __ATTR(req_lim_delta, S_IRUGO, show_req_lim_delta, NULL);
++static const struct kobj_attribute srpt_ch_state_attr =
++ __ATTR(ch_state, S_IRUGO, show_ch_state, NULL);
++
++static const struct attribute *srpt_sess_attrs[] = {
++ &srpt_req_lim_attr.attr,
++ &srpt_req_lim_delta_attr.attr,
++ &srpt_ch_state_attr.attr,
++ NULL
++};
++
++/* SCST target template for the SRP target implementation. */
++static struct scst_tgt_template srpt_template = {
++ .name = DRV_NAME,
++ .sg_tablesize = SRPT_DEF_SG_TABLESIZE,
++ .max_hw_pending_time = RDMA_COMPL_TIMEOUT_S,
++ .enable_target = srpt_enable_target,
++ .is_target_enabled = srpt_is_target_enabled,
++ .tgt_attrs = srpt_tgt_attrs,
++ .sess_attrs = srpt_sess_attrs,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = DEFAULT_SRPT_TRACE_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++ .detect = srpt_detect,
++ .release = srpt_release,
++ .xmit_response = srpt_xmit_response,
++ .rdy_to_xfer = srpt_rdy_to_xfer,
++ .on_hw_pending_cmd_timeout = srpt_pending_cmd_timeout,
++ .on_free_cmd = srpt_on_free_cmd,
++ .task_mgmt_fn_done = srpt_tsk_mgmt_done,
++ .get_initiator_port_transport_id = srpt_get_initiator_port_transport_id,
++ .get_scsi_transport_version = srpt_get_scsi_transport_version,
++};
++
++/**
++ * srpt_add_one() - Infiniband device addition callback function.
++ */
++static void srpt_add_one(struct ib_device *device)
++{
++ struct srpt_device *sdev;
++ struct srpt_port *sport;
++ struct ib_srq_init_attr srq_attr;
++ char tgt_name[24];
++ int i, ret;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("device = %p, device->dma_ops = %p", device, device->dma_ops);
++
++ sdev = kzalloc(sizeof *sdev, GFP_KERNEL);
++ if (!sdev)
++ goto err;
++
++ sdev->device = device;
++ INIT_LIST_HEAD(&sdev->rch_list);
++ init_waitqueue_head(&sdev->ch_releaseQ);
++ spin_lock_init(&sdev->spinlock);
++
++ if (use_node_guid_in_target_name) {
++ snprintf(tgt_name, sizeof(tgt_name), "%04x:%04x:%04x:%04x",
++ be16_to_cpu(((__be16 *)&device->node_guid)[0]),
++ be16_to_cpu(((__be16 *)&device->node_guid)[1]),
++ be16_to_cpu(((__be16 *)&device->node_guid)[2]),
++ be16_to_cpu(((__be16 *)&device->node_guid)[3]));
++ sdev->scst_tgt = scst_register_target(&srpt_template, tgt_name);
++ } else
++ sdev->scst_tgt = scst_register_target(&srpt_template, NULL);
++ if (!sdev->scst_tgt) {
++ PRINT_ERROR("SCST registration failed for %s.",
++ sdev->device->name);
++ goto free_dev;
++ }
++
++ scst_tgt_set_tgt_priv(sdev->scst_tgt, sdev);
++
++ ret = ib_query_device(device, &sdev->dev_attr);
++ if (ret) {
++ PRINT_ERROR("ib_query_device() failed: %d", ret);
++ goto unregister_tgt;
++ }
++
++ sdev->pd = ib_alloc_pd(device);
++ if (IS_ERR(sdev->pd)) {
++ PRINT_ERROR("ib_alloc_pd() failed: %ld", PTR_ERR(sdev->pd));
++ goto unregister_tgt;
++ }
++
++ sdev->mr = ib_get_dma_mr(sdev->pd, IB_ACCESS_LOCAL_WRITE);
++ if (IS_ERR(sdev->mr)) {
++ PRINT_ERROR("ib_get_dma_mr() failed: %ld", PTR_ERR(sdev->mr));
++ goto err_pd;
++ }
++
++ sdev->srq_size = min(max(srpt_srq_size, MIN_SRPT_SRQ_SIZE),
++ sdev->dev_attr.max_srq_wr);
++
++ memset(&srq_attr, 0, sizeof(srq_attr));
++ srq_attr.event_handler = srpt_srq_event;
++ srq_attr.srq_context = (void *)sdev;
++ srq_attr.attr.max_wr = sdev->srq_size;
++ srq_attr.attr.max_sge = 1;
++ srq_attr.attr.srq_limit = 0;
++ srq_attr.srq_type = IB_SRQT_BASIC;
++
++ sdev->srq = ib_create_srq(sdev->pd, &srq_attr);
++ if (IS_ERR(sdev->srq)) {
++ PRINT_ERROR("ib_create_srq() failed: %ld", PTR_ERR(sdev->srq));
++ goto err_mr;
++ }
++
++ TRACE_DBG("%s: create SRQ #wr= %d max_allow=%d dev= %s", __func__,
++ sdev->srq_size, sdev->dev_attr.max_srq_wr, device->name);
++
++ if (!srpt_service_guid)
++ srpt_service_guid = be64_to_cpu(device->node_guid) &
++ ~be64_to_cpu(IB_SERVICE_ID_AGN_MASK);
++
++ sdev->cm_id = ib_create_cm_id(device, srpt_cm_handler, sdev);
++ if (IS_ERR(sdev->cm_id)) {
++ PRINT_ERROR("ib_create_cm_id() failed: %ld",
++ PTR_ERR(sdev->cm_id));
++ goto err_srq;
++ }
++
++ /* print out target login information */
++ TRACE_DBG("Target login info: id_ext=%016llx,"
++ "ioc_guid=%016llx,pkey=ffff,service_id=%016llx",
++ srpt_service_guid, srpt_service_guid, srpt_service_guid);
++
++ /*
++ * We do not have a consistent service_id (ie. also id_ext of target_id)
++ * to identify this target. We currently use the guid of the first HCA
++ * in the system as service_id; therefore, the target_id will change
++ * if this HCA is gone bad and replaced by different HCA
++ */
++ ret = ib_cm_listen(sdev->cm_id, cpu_to_be64(srpt_service_guid), 0,
++ NULL);
++ if (ret) {
++ PRINT_ERROR("ib_cm_listen() failed: %d (cm_id state = %d)",
++ ret, sdev->cm_id->state);
++ goto err_cm;
++ }
++
++ INIT_IB_EVENT_HANDLER(&sdev->event_handler, sdev->device,
++ srpt_event_handler);
++ ret = ib_register_event_handler(&sdev->event_handler);
++ if (ret) {
++ PRINT_ERROR("ib_register_event_handler() failed: %d", ret);
++ goto err_cm;
++ }
++
++ sdev->ioctx_ring = (struct srpt_recv_ioctx **)
++ srpt_alloc_ioctx_ring(sdev, sdev->srq_size,
++ sizeof(*sdev->ioctx_ring[0]),
++ srp_max_req_size, DMA_FROM_DEVICE);
++ if (!sdev->ioctx_ring) {
++ PRINT_ERROR("%s", "srpt_alloc_ioctx_ring() failed");
++ goto err_event;
++ }
++
++ for (i = 0; i < sdev->srq_size; ++i)
++ srpt_post_recv(sdev, sdev->ioctx_ring[i]);
++
++ WARN_ON(sdev->device->phys_port_cnt > ARRAY_SIZE(sdev->port));
++
++ for (i = 1; i <= sdev->device->phys_port_cnt; i++) {
++ sport = &sdev->port[i - 1];
++ sport->sdev = sdev;
++ sport->port = i;
++ INIT_WORK(&sport->work, srpt_refresh_port_work);
++ if (srpt_refresh_port(sport)) {
++ PRINT_ERROR("MAD registration failed for %s-%d.",
++ sdev->device->name, i);
++ goto err_ring;
++ }
++ }
++
++ atomic_inc(&srpt_device_count);
++out:
++ ib_set_client_data(device, &srpt_client, sdev);
++
++ TRACE_EXIT();
++ return;
++
++err_ring:
++ srpt_free_ioctx_ring((struct srpt_ioctx **)sdev->ioctx_ring, sdev,
++ sdev->srq_size, srp_max_req_size,
++ DMA_FROM_DEVICE);
++err_event:
++ ib_unregister_event_handler(&sdev->event_handler);
++err_cm:
++ ib_destroy_cm_id(sdev->cm_id);
++err_srq:
++ ib_destroy_srq(sdev->srq);
++err_mr:
++ ib_dereg_mr(sdev->mr);
++err_pd:
++ ib_dealloc_pd(sdev->pd);
++unregister_tgt:
++ scst_unregister_target(sdev->scst_tgt);
++free_dev:
++ kfree(sdev);
++err:
++ sdev = NULL;
++ PRINT_INFO("%s(%s) failed.", __func__, device->name);
++ goto out;
++}
++
++/**
++ * srpt_remove_one() - InfiniBand device removal callback function.
++ */
++static void srpt_remove_one(struct ib_device *device)
++{
++ int i;
++ struct srpt_device *sdev;
++
++ TRACE_ENTRY();
++
++ sdev = ib_get_client_data(device, &srpt_client);
++ if (!sdev) {
++ PRINT_INFO("%s(%s): nothing to do.", __func__, device->name);
++ return;
++ }
++
++ srpt_unregister_mad_agent(sdev);
++
++ ib_unregister_event_handler(&sdev->event_handler);
++
++ /* Cancel any work queued by the just unregistered IB event handler. */
++ for (i = 0; i < sdev->device->phys_port_cnt; i++)
++ cancel_work_sync(&sdev->port[i].work);
++
++ ib_destroy_cm_id(sdev->cm_id);
++
++ /*
++ * Unregistering an SCST target must happen after destroying sdev->cm_id
++ * such that no new SRP_LOGIN_REQ information units can arrive while
++ * destroying the SCST target.
++ */
++ scst_unregister_target(sdev->scst_tgt);
++ sdev->scst_tgt = NULL;
++
++ ib_destroy_srq(sdev->srq);
++ ib_dereg_mr(sdev->mr);
++ ib_dealloc_pd(sdev->pd);
++
++ srpt_free_ioctx_ring((struct srpt_ioctx **)sdev->ioctx_ring, sdev,
++ sdev->srq_size, srp_max_req_size, DMA_FROM_DEVICE);
++ sdev->ioctx_ring = NULL;
++ kfree(sdev);
++
++ TRACE_EXIT();
++}
++
++static struct ib_client srpt_client = {
++ .name = DRV_NAME,
++ .add = srpt_add_one,
++ .remove = srpt_remove_one
++};
++
++/**
++ * srpt_init_module() - Kernel module initialization.
++ *
++ * Note: Since ib_register_client() registers callback functions, and since at
++ * least one of these callback functions (srpt_add_one()) calls SCST functions,
++ * the SCST target template must be registered before ib_register_client() is
++ * called.
++ */
++static int __init srpt_init_module(void)
++{
++ int ret;
++
++ ret = -EINVAL;
++ if (srp_max_req_size < MIN_MAX_REQ_SIZE) {
++ PRINT_ERROR("invalid value %d for kernel module parameter"
++ " srp_max_req_size -- must be at least %d.",
++ srp_max_req_size,
++ MIN_MAX_REQ_SIZE);
++ goto out;
++ }
++
++ if (srp_max_rsp_size < MIN_MAX_RSP_SIZE) {
++ PRINT_ERROR("invalid value %d for kernel module parameter"
++ " srp_max_rsp_size -- must be at least %d.",
++ srp_max_rsp_size,
++ MIN_MAX_RSP_SIZE);
++ goto out;
++ }
++
++ if (srpt_srq_size < MIN_SRPT_SRQ_SIZE
++ || srpt_srq_size > MAX_SRPT_SRQ_SIZE) {
++ PRINT_ERROR("invalid value %d for kernel module parameter"
++ " srpt_srq_size -- must be in the range [%d..%d].",
++ srpt_srq_size, MIN_SRPT_SRQ_SIZE,
++ MAX_SRPT_SRQ_SIZE);
++ goto out;
++ }
++
++ if (srpt_sq_size < MIN_SRPT_SQ_SIZE) {
++ PRINT_ERROR("invalid value %d for kernel module parameter"
++ " srpt_sq_size -- must be at least %d.",
++ srpt_srq_size, MIN_SRPT_SQ_SIZE);
++ goto out;
++ }
++
++ if (!use_node_guid_in_target_name)
++ PRINT_WARNING("%s", "Usage of HCA numbers as SCST target names "
++ "is deprecated and will be removed in one of the next "
++ "versions. It is strongly recommended to set "
++ "use_node_guid_in_target_name parameter in 1 and "
++ "update your SCST config file accordingly to use HCAs "
++ "GUIDs.");
++
++ ret = scst_register_target_template(&srpt_template);
++ if (ret < 0) {
++ PRINT_ERROR("%s", "couldn't register with scst");
++ ret = -ENODEV;
++ goto out;
++ }
++
++ ret = ib_register_client(&srpt_client);
++ if (ret) {
++ PRINT_ERROR("%s", "couldn't register IB client");
++ goto out_unregister_target;
++ }
++
++ return 0;
++
++out_unregister_target:
++ scst_unregister_target_template(&srpt_template);
++out:
++ return ret;
++}
++
++static void __exit srpt_cleanup_module(void)
++{
++ TRACE_ENTRY();
++
++ ib_unregister_client(&srpt_client);
++ scst_unregister_target_template(&srpt_template);
++
++ TRACE_EXIT();
++}
++
++module_init(srpt_init_module);
++module_exit(srpt_cleanup_module);
++
++/*
++ * Local variables:
++ * c-basic-offset: 8
++ * indent-tabs-mode: t
++ * End:
++ */
+diff -uprN orig/linux-3.2/drivers/scst/srpt/ib_srpt.h linux-3.2/drivers/scst/srpt/ib_srpt.h
+--- orig/linux-3.2/drivers/scst/srpt/ib_srpt.h
++++ linux-3.2/drivers/scst/srpt/ib_srpt.h
+@@ -0,0 +1,407 @@
++/*
++ * Copyright (c) 2006 - 2009 Mellanox Technology Inc. All rights reserved.
++ * Copyright (C) 2009 - 2011 Bart Van Assche <bvanassche@acm.org>.
++ *
++ * This software is available to you under a choice of one of two
++ * licenses. You may choose to be licensed under the terms of the GNU
++ * General Public License (GPL) Version 2, available from the file
++ * COPYING in the main directory of this source tree, or the
++ * OpenIB.org BSD license below:
++ *
++ * Redistribution and use in source and binary forms, with or
++ * without modification, are permitted provided that the following
++ * conditions are met:
++ *
++ * - Redistributions of source code must retain the above
++ * copyright notice, this list of conditions and the following
++ * disclaimer.
++ *
++ * - Redistributions in binary form must reproduce the above
++ * copyright notice, this list of conditions and the following
++ * disclaimer in the documentation and/or other materials
++ * provided with the distribution.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
++ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
++ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ *
++ */
++
++#ifndef IB_SRPT_H
++#define IB_SRPT_H
++
++#include <linux/types.h>
++#include <linux/list.h>
++#include <linux/wait.h>
++#include <rdma/ib_verbs.h>
++#include <rdma/ib_sa.h>
++#include <rdma/ib_cm.h>
++#include <scsi/srp.h>
++#include <scst/scst.h>
++#include "ib_dm_mad.h"
++
++/*
++ * The prefix the ServiceName field must start with in the device management
++ * ServiceEntries attribute pair. See also the SRP specification.
++ */
++#define SRP_SERVICE_NAME_PREFIX "SRP.T10:"
++
++enum {
++ /*
++ * SRP IOControllerProfile attributes for SRP target ports that have
++ * not been defined in <scsi/srp.h>. Source: section B.7, table B.7
++ * in the SRP specification.
++ */
++ SRP_PROTOCOL = 0x0108,
++ SRP_PROTOCOL_VERSION = 0x0001,
++ SRP_IO_SUBCLASS = 0x609e,
++ SRP_SEND_TO_IOC = 0x01,
++ SRP_SEND_FROM_IOC = 0x02,
++ SRP_RDMA_READ_FROM_IOC = 0x08,
++ SRP_RDMA_WRITE_FROM_IOC = 0x20,
++
++ /*
++ * srp_login_cmd.req_flags bitmasks. See also table 9 in the SRP r16a
++ * document.
++ */
++ SRP_MTCH_ACTION = 0x03, /* MULTI-CHANNEL ACTION */
++ SRP_LOSOLNT = 0x10, /* logout solicited notification */
++ SRP_CRSOLNT = 0x20, /* credit request solicited notification */
++ SRP_AESOLNT = 0x40, /* asynchronous event solicited notification */
++
++ /*
++ * srp_cmd.sol_nt / srp_tsk_mgmt.sol_not bitmasks. See also tables
++ * 18 and 20 in the SRP specification.
++ */
++ SRP_SCSOLNT = 0x02, /* SCSOLNT = successful solicited notification */
++ SRP_UCSOLNT = 0x04, /* UCSOLNT = unsuccessful solicited notification */
++
++ /*
++ * srp_rsp.sol_not / srp_t_logout.sol_not bitmasks. See also tables
++ * 16 and 22 in the SRP specification.
++ */
++ SRP_SOLNT = 0x01, /* SOLNT = solicited notification */
++
++ /* See also table 24 in the SRP specification. */
++ SRP_TSK_MGMT_SUCCESS = 0x00,
++ SRP_TSK_MGMT_FUNC_NOT_SUPP = 0x04,
++ SRP_TSK_MGMT_FAILED = 0x05,
++
++ /* See also table 21 in the SRP specification. */
++ SRP_CMD_SIMPLE_Q = 0x0,
++ SRP_CMD_HEAD_OF_Q = 0x1,
++ SRP_CMD_ORDERED_Q = 0x2,
++ SRP_CMD_ACA = 0x4,
++
++ SRP_LOGIN_RSP_MULTICHAN_NO_CHAN = 0x0,
++ SRP_LOGIN_RSP_MULTICHAN_TERMINATED = 0x1,
++ SRP_LOGIN_RSP_MULTICHAN_MAINTAINED = 0x2,
++
++ SRPT_DEF_SG_TABLESIZE = 128,
++
++ MIN_SRPT_SQ_SIZE = 16,
++ DEF_SRPT_SQ_SIZE = 4096,
++ SRPT_RQ_SIZE = 128,
++ MIN_SRPT_SRQ_SIZE = 4,
++ DEFAULT_SRPT_SRQ_SIZE = 4095,
++ MAX_SRPT_SRQ_SIZE = 65535,
++
++ MIN_MAX_REQ_SIZE = 996,
++ DEFAULT_MAX_REQ_SIZE
++ = sizeof(struct srp_cmd)/*48*/
++ + sizeof(struct srp_indirect_buf)/*20*/
++ + 255 * sizeof(struct srp_direct_buf)/*16*/,
++
++ MIN_MAX_RSP_SIZE = sizeof(struct srp_rsp)/*36*/ + 4,
++ DEFAULT_MAX_RSP_SIZE = 256, /* leaves 220 bytes for sense data */
++
++ DEFAULT_MAX_RDMA_SIZE = 65536,
++
++ RDMA_COMPL_TIMEOUT_S = 80,
++};
++
++enum srpt_opcode {
++ SRPT_RECV,
++ SRPT_SEND,
++ SRPT_RDMA_MID,
++ SRPT_RDMA_ABORT,
++ SRPT_RDMA_READ_LAST,
++ SRPT_RDMA_WRITE_LAST,
++};
++
++static inline u64 encode_wr_id(enum srpt_opcode opcode, u32 idx)
++{
++ return ((u64)opcode << 32) | idx;
++}
++
++static inline enum srpt_opcode opcode_from_wr_id(u64 wr_id)
++{
++ return wr_id >> 32;
++}
++
++static inline u32 idx_from_wr_id(u64 wr_id)
++{
++ return (u32)wr_id;
++}
++
++struct rdma_iu {
++ u64 raddr;
++ u32 rkey;
++ struct ib_sge *sge;
++ u32 sge_cnt;
++};
++
++/**
++ * enum srpt_command_state - SCSI command state managed by SRPT.
++ * @SRPT_STATE_NEW: New command arrived and is being processed.
++ * @SRPT_STATE_NEED_DATA: Processing a write or bidir command and waiting
++ * for data arrival.
++ * @SRPT_STATE_DATA_IN: Data for the write or bidir command arrived and is
++ * being processed.
++ * @SRPT_STATE_CMD_RSP_SENT: SRP_RSP for SRP_CMD has been sent.
++ * @SRPT_STATE_MGMT: Processing a SCSI task management command.
++ * @SRPT_STATE_MGMT_RSP_SENT: SRP_RSP for SRP_TSK_MGMT has been sent.
++ * @SRPT_STATE_DONE: Command processing finished successfully, command
++ * processing has been aborted or command processing
++ * failed.
++ */
++enum srpt_command_state {
++ SRPT_STATE_NEW = 0,
++ SRPT_STATE_NEED_DATA = 1,
++ SRPT_STATE_DATA_IN = 2,
++ SRPT_STATE_CMD_RSP_SENT = 3,
++ SRPT_STATE_MGMT = 4,
++ SRPT_STATE_MGMT_RSP_SENT = 5,
++ SRPT_STATE_DONE = 6,
++};
++
++/**
++ * struct srpt_ioctx - Shared SRPT I/O context information.
++ * @buf: Pointer to the buffer.
++ * @dma: DMA address of the buffer.
++ * @index: Index of the I/O context in its ioctx_ring array.
++ */
++struct srpt_ioctx {
++ void *buf;
++ dma_addr_t dma;
++ uint32_t index;
++};
++
++/**
++ * struct srpt_recv_ioctx - SRPT receive I/O context.
++ * @ioctx: See above.
++ * @wait_list: Node for insertion in srpt_rdma_ch.cmd_wait_list.
++ */
++struct srpt_recv_ioctx {
++ struct srpt_ioctx ioctx;
++ struct list_head wait_list;
++};
++
++/**
++ * struct srpt_tsk_mgmt - SCST management command context information.
++ * @tag: SCSI tag of the management command.
++ */
++struct srpt_tsk_mgmt {
++ u64 tag;
++};
++
++/**
++ * struct srpt_send_ioctx - SRPT send I/O context.
++ * @ioctx: See above.
++ * @ch: Channel pointer.
++ * @rdma_ius: Array with information about the RDMA mapping.
++ * @rbufs: Pointer to SRP data buffer array.
++ * @single_rbuf: SRP data buffer if the command has only a single buffer.
++ * @sg: Pointer to sg-list associated with this I/O context.
++ * @spinlock: Protects 'state'.
++ * @state: I/O context state.
++ * @rdma_aborted: If initiating a multipart RDMA transfer failed, whether
++ * the already initiated transfers have finished.
++ * @scmnd: SCST command data structure.
++ * @dir:
++ * @free_list: Node in srpt_rdma_ch.free_list.
++ * @sg_cnt: SG-list size.
++ * @mapped_sg_count: ib_dma_map_sg() return value.
++ * @n_rdma_ius: Size of the rdma_ius array.
++ * @n_rdma: Number of elements used of the rdma_ius array.
++ * @n_rbuf: Number of data buffers in the received SRP command.
++ * @req_lim_delta: Value of the req_lim_delta value field in the latest
++ * SRP response sent.
++ * @tsk_mgmt:
++ */
++struct srpt_send_ioctx {
++ struct srpt_ioctx ioctx;
++ struct srpt_rdma_ch *ch;
++ struct rdma_iu *rdma_ius;
++ struct srp_direct_buf *rbufs;
++ struct srp_direct_buf single_rbuf;
++ struct scatterlist *sg;
++ struct list_head free_list;
++ spinlock_t spinlock;
++ enum srpt_command_state state;
++ bool rdma_aborted;
++ struct scst_cmd *scmnd;
++ scst_data_direction dir;
++ int sg_cnt;
++ int mapped_sg_count;
++ u16 n_rdma_ius;
++ u8 n_rdma;
++ u8 n_rbuf;
++ int req_lim_delta;
++ struct srpt_tsk_mgmt tsk_mgmt;
++ u8 rdma_ius_buf[2 * sizeof(struct rdma_iu)
++ + 2 * sizeof(struct ib_sge)]
++ __aligned(sizeof(uint64_t));
++};
++
++/**
++ * enum rdma_ch_state - SRP channel state.
++ * @CH_CONNECTING: QP is in RTR state; waiting for RTU.
++ * @CH_LIVE: QP is in RTS state.
++ * @CH_DISCONNECTING: DREQ has been received and waiting for DREP or DREQ has
++ * been sent and waiting for DREP or channel is being closed
++ * for another reason.
++ * @CH_DRAINING: QP is in ERR state.
++ * @CH_FREEING: QP resources are being freed.
++ */
++enum rdma_ch_state {
++ CH_CONNECTING,
++ CH_LIVE,
++ CH_DISCONNECTING,
++ CH_DRAINING,
++ CH_FREEING,
++};
++
++/**
++ * struct srpt_rdma_ch - RDMA channel.
++ * @thread: Kernel thread that processes the IB queues associated with
++ * the channel.
++ * @cm_id: IB CM ID associated with the channel.
++ * @qp: IB queue pair used for communicating over this channel.
++ * @cq: IB completion queue for this channel.
++ * @rq_size: IB receive queue size.
++ * @max_sge: Maximum length of RDMA scatter list.
++ * @sq_wr_avail: number of work requests available in the send queue.
++ * @sport: pointer to the information of the HCA port used by this
++ * channel.
++ * @i_port_id: 128-bit initiator port identifier copied from SRP_LOGIN_REQ.
++ * @t_port_id: 128-bit target port identifier copied from SRP_LOGIN_REQ.
++ * @max_ti_iu_len: maximum target-to-initiator information unit length.
++ * @req_lim: request limit: maximum number of requests that may be sent
++ * by the initiator without having received a response.
++ * @req_lim_delta: One less than the req_lim_delta value that will be included
++ * in the next reply sent to the initiator. See also the SRP
++ * credit algorithm in the SRP spec.
++ * @spinlock: Protects free_list.
++ * @free_list: Head of list with free send I/O contexts.
++ * @ioctx_ring:
++ * @wc:
++ * @state: channel state. See also enum rdma_ch_state.
++ * @list: node for insertion in the srpt_device.rch_list list.
++ * @cmd_wait_list: list of SCST commands that arrived before the RTU event. This
++ * list contains struct srpt_ioctx elements and is protected
++ * against concurrent modification by the cm_id spinlock.
++ * @scst_sess: SCST session information associated with this SRP channel.
++ * @sess_name: SCST session name.
++ */
++struct srpt_rdma_ch {
++ struct task_struct *thread;
++ struct ib_cm_id *cm_id;
++ struct ib_qp *qp;
++ struct ib_cq *cq;
++ int rq_size;
++ int max_sge;
++ int max_rsp_size;
++ int sq_wr_avail;
++ struct srpt_port *sport;
++ u8 i_port_id[16];
++ u8 t_port_id[16];
++ int max_ti_iu_len;
++ int req_lim;
++ int req_lim_delta;
++ spinlock_t spinlock;
++ struct list_head free_list;
++ struct srpt_send_ioctx **ioctx_ring;
++ struct ib_wc wc[16];
++ enum rdma_ch_state state;
++ wait_queue_head_t state_wq;
++ struct list_head list;
++ struct list_head cmd_wait_list;
++ struct completion finished_processing_completions;
++ bool last_wqe_received;
++
++ struct scst_session *scst_sess;
++ u8 sess_name[36];
++};
++
++/**
++ * struct srpt_port - Information associated by SRPT with a single IB port.
++ * @sdev: backpointer to the HCA information.
++ * @mad_agent: per-port management datagram processing information.
++ * @port: one-based port number.
++ * @sm_lid: cached value of the port's sm_lid.
++ * @lid: cached value of the port's lid.
++ * @gid: cached value of the port's gid.
++ * @work: work structure for refreshing the aforementioned cached values.
++ */
++struct srpt_port {
++ struct srpt_device *sdev;
++ struct ib_mad_agent *mad_agent;
++ u8 port;
++ u16 sm_lid;
++ u16 lid;
++ union ib_gid gid;
++ struct work_struct work;
++};
++
++/**
++ * struct srpt_device - Information associated by SRPT with a single HCA.
++ * @device: Backpointer to the struct ib_device managed by the IB core.
++ * @pd: IB protection domain.
++ * @mr: L_Key (local key) with write access to all local memory.
++ * @srq: Per-HCA SRQ (shared receive queue).
++ * @cm_id: Connection identifier.
++ * @dev_attr: Attributes of the InfiniBand device as obtained during the
++ * ib_client.add() callback.
++ * @srq_size: SRQ size.
++ * @ioctx_ring: Per-HCA SRQ.
++ * @rch_list: Per-device channel list -- see also srpt_rdma_ch.list.
++ * @ch_releaseQ: Enables waiting for removal from rch_list.
++ * @spinlock: Protects rch_list.
++ * @port: Information about the ports owned by this HCA.
++ * @event_handler: Per-HCA asynchronous IB event handler.
++ * @dev: Per-port srpt-<portname> device instance.
++ * @scst_tgt: SCST target information associated with this HCA.
++ * @enabled: Whether or not this SCST target is enabled.
++ */
++struct srpt_device {
++ struct ib_device *device;
++ struct ib_pd *pd;
++ struct ib_mr *mr;
++ struct ib_srq *srq;
++ struct ib_cm_id *cm_id;
++ struct ib_device_attr dev_attr;
++ int srq_size;
++ struct srpt_recv_ioctx **ioctx_ring;
++ struct list_head rch_list;
++ wait_queue_head_t ch_releaseQ;
++ spinlock_t spinlock;
++ struct srpt_port port[2];
++ struct ib_event_handler event_handler;
++ struct scst_tgt *scst_tgt;
++ bool enabled;
++};
++
++#endif /* IB_SRPT_H */
++
++/*
++ * Local variables:
++ * c-basic-offset: 8
++ * indent-tabs-mode: t
++ * End:
++ */
+diff -uprN orig/linux-3.2/Documentation/scst/README.scst_local linux-3.2/Documentation/scst/README.scst_local
+--- orig/linux-3.2/Documentation/scst/README.scst_local
++++ linux-3.2/Documentation/scst/README.scst_local
+@@ -0,0 +1,286 @@
++SCST Local ...
++Richard Sharpe, 30-Nov-2008
++
++This is the SCST Local driver. Its function is to allow you to access devices
++that are exported via SCST directly on the same Linux system that they are
++exported from.
++
++No assumptions are made in the code about the device types on the target, so
++any device handlers that you load in SCST should be visible, including tapes
++and so forth.
++
++You can freely use any sg, sd, st, etc. devices imported from target,
++except the following: you can't mount file systems or put swap on them
++for all dev handlers, except BLOCKIO and pass-through, because it can
++lead to recursive memory allocation deadlock. This is a limitation of
++Linux memory/cache manager. See SCST README file for details. For
++BLOCKIO and pass-through dev handlers there's no such limitation, so you
++can freely mount file systems over them.
++
++To build, simply issue 'make' in the scst_local directory.
++
++Try 'modinfo scst_local' for a listing of module parameters so far.
++
++Here is how I have used it so far:
++
++1. Load up scst:
++
++ modprobe scst
++ modprobe scst_vdisk
++
++2. Create a virtual disk (or your own device handler):
++
++ dd if=/dev/zero of=/some/path/vdisk1.img bs=16384 count=1000000
++ echo "add_device vm_disk1 filename=/some/path/vdisk1.img" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
++
++3. Load the scst_local driver:
++
++ insmod scst_local
++ echo "add vm_disk1 0" >/sys/kernel/scst_tgt/targets/scst_local/scst_local_tgt/luns/mgmt
++
++4. Check what you have
++
++ cat /proc/scsi/scsi
++ Attached devices:
++ Host: scsi0 Channel: 00 Id: 00 Lun: 00
++ Vendor: ATA Model: ST9320320AS Rev: 0303
++ Type: Direct-Access ANSI SCSI revision: 05
++ Host: scsi4 Channel: 00 Id: 00 Lun: 00
++ Vendor: TSSTcorp Model: CD/DVDW TS-L632D Rev: TO04
++ Type: CD-ROM ANSI SCSI revision: 05
++ Host: scsi7 Channel: 00 Id: 00 Lun: 00
++ Vendor: SCST_FIO Model: vm_disk1 Rev: 200
++ Type: Direct-Access ANSI SCSI revision: 04
++
++Or instead of manually "add_device" in (2) and step (3) write a
++scstadmin config:
++
++HANDLER vdisk_fileio {
++ DEVICE vm_disk1 {
++ filename /some/path/vdisk1.img
++ }
++}
++
++TARGET_DRIVER scst_local {
++ TARGET scst_local_tgt {
++ LUN 0 vm_disk1
++ }
++}
++
++then:
++
++ insmod scst_local
++ scstadmin -config conf_file.cfg
++
++More advanced examples:
++
++For (3) you can:
++
++ insmod scst_local add_default_tgt=0
++ echo "add_target scst_local_tgt session_name=scst_local_host" >/sys/kernel/scst_tgt/targets/scst_local//mgmt
++ echo "add vm_disk1 0" >/sys/kernel/scst_tgt/targets/scst_local/scst_local_tgt/luns/mgmt
++
++Scst_local module's parameter add_default_tgt disables creation of
++default target "scst_local_tgt" and session "scst_local_host", so you
++needed to create it manually.
++
++There can be any number of targets and sessions created. Each SCST
++session corresponds to SCSI host. You can change which LUNs assigned to
++each session by using SCST access control. This mode is intended for
++user space target drivers (see below).
++
++Alternatively, you can write an scstadmin's config file conf_file.cfg:
++
++HANDLER vdisk_fileio {
++ DEVICE vm_disk1 {
++ filename /some/path/vdisk1.img
++ }
++}
++
++TARGET_DRIVER scst_local {
++ TARGET scst_local_tgt {
++ session_name scst_local_host
++
++ LUN 0 vm_disk1
++ }
++}
++
++then:
++
++ insmod scst_local add_default_tgt=0
++ scstadmin -config conf_file.cfg
++
++NOTE! Although scstadmin allows to create scst_local's sessions using
++"session_name" expression, it doesn't save existing sessions during
++writing config file by "write_config" command. If you need this
++functionality, feel free to send a request for it in SCST development
++mailing list.
++
++5. Have fun.
++
++Some of this was coded while in Santa Clara, some in Bangalore, and some in
++Hyderabad. Noe doubt some will be coded on the way back to Santa Clara.
++
++The code still has bugs, so if you encounter any, email me the fixes at:
++
++ realrichardsharpe@gmail.com
++
++I am thinking of renaming this to something more interesting.
++
++
++Sysfs interface
++===============
++
++See SCST's README for a common SCST sysfs description.
++
++Root of this driver is /sys/kernel/scst_tgt/targets/scst_local. It has
++the following additional entry:
++
++ - stats - read-only attribute with some statistical information.
++
++Each target subdirectory contains the following additional entries:
++
++ - phys_transport_version - contains and allows to change physical
++ transport version descriptor. It determines by which physical
++ interface this target will look like. See SPC for more details. By
++ default, it is not defined (0).
++
++ - scsi_transport_version - contains and allows to change SCSI
++ transport version descriptor. It determines by which SCSI
++ transport this target will look like. See SPC for more details. By
++ default, it is SAS.
++
++Each session subdirectory contains the following additional entries:
++
++ - transport_id - contains this host's TransportID. This TransportID
++ used to identify initiator in Persisten Reservation commands. If you
++ change scsi_transport_version for a target, make sure you set for all
++ its sessions correct TransportID. See SPC for more details.
++
++ - host - links to the corresponding SCSI host. Using it you can find
++ local sg/bsg/sd/etc. devices of this session. For instance, this
++ links points out to host12, so you can find your sg devices by:
++
++$ lsscsi -g|grep "\[12:"
++[12:0:0:0] disk SCST_FIO rd1 200 /dev/sdc /dev/sg2
++[12:0:0:1] disk SCST_FIO nullio 200 /dev/sdd /dev/sg3
++
++They are /dev/sg2 and /dev/sg3.
++
++The following management commands available via /sys/kernel/scst_tgt/targets/scst_local/mgmt:
++
++ - add_target target_name [session_name=sess_name; [session_name=sess_name1;] [...]] -
++ creates a target with optionally one or more sessions.
++
++ - del_target target_name - deletes a target.
++
++ - add_session target_name session_name - adds to target target_name
++ session (SCSI host) with name session_name.
++
++ - del_session target_name session_name - deletes session session_name
++ from target target_name.
++
++
++Note on performance
++===================
++
++Although this driver implemented in the most performance effective way,
++including zero-copy passing data between SCSI/block subsystems and SCST,
++in many cases it is NOT suited to measure performance as a NULL link.
++For example, it is not suited for max IOPS measurements. This is because
++for such cases not performance of the link between the target and
++initiator is the bottleneck, but CPU or memory speed on the target or
++initiator. For scst_local you have both initiator and target on the same
++system, which means each your initiator and target are much less
++CPU/memory powerful.
++
++
++User space target drivers
++=========================
++
++Scst_local can be used to write full featured SCST target drivers in
++user space:
++
++1. For each SCSI target a user space target driver should create an
++ scst_local's target using "add_target" command.
++
++2. Then the user space target driver should, if needed, set its SCSI and
++ physical transport version descriptors using attributes
++ scsi_transport_version and phys_transport_version correspondingly in
++ /sys/kernel/scst_tgt/targets/scst_local/target_name directory.
++
++3. For incoming session (I_T nexus) from an initiator the user space
++ target driver should create scst_local's session using "add_session"
++ command.
++
++4. Then, if needed, the user space target driver should set TransportID
++ for this session (I_T nexus) using attribute
++ /sys/kernel/scst_tgt/targets/scst_local/target_name/sessions/session_name/transport_id
++
++5. Then the user space target driver should find out sg/bsg devices for
++ the LUNs the created session has using link
++ /sys/kernel/scst_tgt/targets/scst_local/target_name/sessions/session_name/host
++ as described above.
++
++6. Then the user space target driver can start serving the initiator using
++ found sg/bsg devices.
++
++For other connected initiators steps 3-6 should be repeated.
++
++
++Compilation options
++===================
++
++There are the following compilation options, that could be commented
++in/out in Makefile:
++
++ - CONFIG_SCST_LOCAL_FORCE_DIRECT_PROCESSING - by default, when this option
++ is not defined, scst_local reschedules all commands for processing in
++ one of the SCST threads. If this option is defined, scst_local tries
++ to not do it, if possible (sometimes queuecommand() called under
++ various locks held), but instead process them in the submitter's
++ context. This is to increase performance, but as on 2.6.37 and below
++ Linux block layer doesn't work with such kind of reentrance, hence
++ this option disabled by default. Note! At the moment in
++ scst_estimate_context*() returning DIRECT contexts disabled, so this
++ option doesn't have any real effect.
++
++
++Change log
++==========
++
++V0.1 24-Sep-2008 (Hyderabad) Initial coding, pretty chatty and messy,
++ but worked.
++
++V0.2 25-Sep-2008 (Hong Kong) Cleaned up the code a lot, reduced the log
++ chatter, fixed a bug where multiple LUNs did not
++ work. Also, added logging control. Tested with
++ five virtual disks. They all came up as /dev/sdb
++ through /dev/sdf and I could dd to them. Also
++ fixed a bug preventing multiple adapters.
++
++V0.3 26-Sep-2008 (Santa Clara) Added back a copyright plus cleaned up some
++ unused functions and structures.
++
++V0.4 5-Oct-2008 (Santa Clara) Changed name to scst_local as suggested, cleaned
++ up some unused variables (made them used) and
++ change allocation to a kmem_cache pool.
++
++V0.5 5-Oct-2008 (Santa Clara) Added mgmt commands to handle dev reset and
++ aborts. Not sure if aborts works. Also corrected
++ the version info and renamed readme to README.
++
++V0.6 7-Oct-2008 (Santa Clara) Removed some redundant code and made some
++ changes suggested by Vladislav.
++
++V0.7 11-Oct-2008 (Santa Clara) Moved into the scst tree. Cleaned up some
++ unused functions, used TRACE macros etc.
++
++V0.9 30-Nov-2008 (Mtn View) Cleaned up an additional problem with symbols not
++ being defined in older version of the kernel. Also
++ fixed some English and cleaned up this doc.
++
++V1.0 10-Sep-2010 (Moscow) Sysfs management added. Reviewed and cleaned up.
++
++V2.1 Update for kernels up to 3.0. Cleanups.
++
+diff -uprN orig/linux-3.2/drivers/scst/scst_local/Kconfig linux-3.2/drivers/scst/scst_local/Kconfig
+--- orig/linux-3.2/drivers/scst/scst_local/Kconfig
++++ linux-3.2/drivers/scst/scst_local/Kconfig
+@@ -0,0 +1,22 @@
++config SCST_LOCAL
++ tristate "SCST Local driver"
++ depends on SCST && !HIGHMEM4G && !HIGHMEM64G
++ ---help---
++ This module provides a LLD SCSI driver that connects to
++ the SCST target mode subsystem in a loop-back manner.
++ It allows you to test target-mode device-handlers locally.
++ You will need the SCST subsystem as well.
++
++ If unsure whether you really want or need this, say N.
++
++config SCST_LOCAL_FORCE_DIRECT_PROCESSING
++ bool "Force local processing"
++ depends on SCST_LOCAL
++ help
++ This experimental option forces scst_local to make SCST process
++ SCSI commands in the same context, in which they was submitted.
++ Otherwise, they will be processed in SCST threads. Setting this
++ option to "Y" will give some performance increase, but might be
++ unsafe.
++
++ If unsure, say "N".
+diff -uprN orig/linux-3.2/drivers/scst/scst_local/Makefile linux-3.2/drivers/scst/scst_local/Makefile
+--- orig/linux-3.2/drivers/scst/scst_local/Makefile
++++ linux-3.2/drivers/scst/scst_local/Makefile
+@@ -0,0 +1,2 @@
++obj-$(CONFIG_SCST_LOCAL) += scst_local.o
++
+diff -uprN orig/linux-3.2/drivers/scst/scst_local/scst_local.c linux-3.2/drivers/scst/scst_local/scst_local.c
+--- orig/linux-3.2/drivers/scst/scst_local/scst_local.c
++++ linux-3.2/drivers/scst/scst_local/scst_local.c
+@@ -0,0 +1,1587 @@
++/*
++ * Copyright (C) 2008 - 2010 Richard Sharpe
++ * Copyright (C) 1992 Eric Youngdale
++ * Copyright (C) 2008 - 2011 Vladislav Bolkhovitin <vst@vlnb.net>
++ *
++ * Simulate a host adapter and an SCST target adapter back to back
++ *
++ * Based on the scsi_debug.c driver originally by Eric Youngdale and
++ * others, including D Gilbert et al
++ *
++ */
++
++#include <linux/module.h>
++
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/types.h>
++#include <linux/init.h>
++#include <linux/moduleparam.h>
++#include <linux/scatterlist.h>
++#include <linux/slab.h>
++#include <linux/completion.h>
++#include <linux/spinlock.h>
++
++#include <scsi/scsi.h>
++#include <scsi/scsi_cmnd.h>
++#include <scsi/scsi_host.h>
++#include <scsi/scsi_tcq.h>
++
++#define LOG_PREFIX "scst_local"
++
++/* SCST includes ... */
++#include <scst/scst.h>
++#include <scst/scst_debug.h>
++
++#ifdef CONFIG_SCST_DEBUG
++#define SCST_LOCAL_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_PID | \
++ TRACE_LINE | TRACE_OUT_OF_MEM | TRACE_MGMT | TRACE_MGMT_DEBUG | \
++ TRACE_MINOR | TRACE_SPECIAL)
++#else
++# ifdef CONFIG_SCST_TRACING
++#define SCST_LOCAL_DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \
++ TRACE_SPECIAL)
++# endif
++#endif
++
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++#define trace_flag scst_local_trace_flag
++static unsigned long scst_local_trace_flag = SCST_LOCAL_DEFAULT_LOG_FLAGS;
++#endif
++
++#define SCST_LOCAL_VERSION "2.2.0-pre"
++static const char *scst_local_version_date = "20110901";
++
++/* Some statistics */
++static atomic_t num_aborts = ATOMIC_INIT(0);
++static atomic_t num_dev_resets = ATOMIC_INIT(0);
++static atomic_t num_target_resets = ATOMIC_INIT(0);
++
++static bool scst_local_add_default_tgt = true;
++module_param_named(add_default_tgt, scst_local_add_default_tgt, bool, S_IRUGO);
++MODULE_PARM_DESC(add_default_tgt, "add (default) or not on start default "
++ "target scst_local_tgt with default session scst_local_host");
++
++struct scst_aen_work_item {
++ struct list_head work_list_entry;
++ struct scst_aen *aen;
++};
++
++struct scst_local_tgt {
++ struct scst_tgt *scst_tgt;
++ struct list_head sessions_list; /* protected by scst_local_mutex */
++ struct list_head tgts_list_entry;
++
++ /* SCSI version descriptors */
++ uint16_t scsi_transport_version;
++ uint16_t phys_transport_version;
++};
++
++struct scst_local_sess {
++ struct scst_session *scst_sess;
++
++ unsigned int unregistering:1;
++
++ struct device dev;
++ struct Scsi_Host *shost;
++ struct scst_local_tgt *tgt;
++
++ int number;
++
++ struct mutex tr_id_mutex;
++ uint8_t *transport_id;
++ int transport_id_len;
++
++ struct work_struct aen_work;
++ spinlock_t aen_lock;
++ struct list_head aen_work_list; /* protected by aen_lock */
++
++ struct list_head sessions_list_entry;
++};
++
++#define to_scst_lcl_sess(d) \
++ container_of(d, struct scst_local_sess, dev)
++
++static int __scst_local_add_adapter(struct scst_local_tgt *tgt,
++ const char *initiator_name, bool locked);
++static int scst_local_add_adapter(struct scst_local_tgt *tgt,
++ const char *initiator_name);
++static void scst_local_remove_adapter(struct scst_local_sess *sess);
++static int scst_local_add_target(const char *target_name,
++ struct scst_local_tgt **out_tgt);
++static void __scst_local_remove_target(struct scst_local_tgt *tgt);
++static void scst_local_remove_target(struct scst_local_tgt *tgt);
++
++static atomic_t scst_local_sess_num = ATOMIC_INIT(0);
++
++static LIST_HEAD(scst_local_tgts_list);
++static DEFINE_MUTEX(scst_local_mutex);
++
++static DECLARE_RWSEM(scst_local_exit_rwsem);
++
++MODULE_AUTHOR("Richard Sharpe, Vladislav Bolkhovitin + ideas from SCSI_DEBUG");
++MODULE_DESCRIPTION("SCSI+SCST local adapter driver");
++MODULE_LICENSE("GPL");
++MODULE_VERSION(SCST_LOCAL_VERSION);
++
++static int scst_local_get_sas_transport_id(struct scst_local_sess *sess,
++ uint8_t **transport_id, int *len)
++{
++ int res = 0;
++ int tr_id_size = 0;
++ uint8_t *tr_id = NULL;
++
++ TRACE_ENTRY();
++
++ tr_id_size = 24; /* A SAS TransportID */
++
++ tr_id = kzalloc(tr_id_size, GFP_KERNEL);
++ if (tr_id == NULL) {
++ PRINT_ERROR("Allocation of TransportID (size %d) failed",
++ tr_id_size);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ tr_id[0] = 0x00 | SCSI_TRANSPORTID_PROTOCOLID_SAS;
++
++ /*
++ * Assemble a valid SAS address = 0x5OOUUIIR12345678 ... Does SCST
++ * have one?
++ */
++
++ tr_id[4] = 0x5F;
++ tr_id[5] = 0xEE;
++ tr_id[6] = 0xDE;
++ tr_id[7] = 0x40 | ((sess->number >> 4) & 0x0F);
++ tr_id[8] = 0x0F | (sess->number & 0xF0);
++ tr_id[9] = 0xAD;
++ tr_id[10] = 0xE0;
++ tr_id[11] = 0x50;
++
++ *transport_id = tr_id;
++ *len = tr_id_size;
++
++ TRACE_DBG("Created tid '%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'",
++ tr_id[4], tr_id[5], tr_id[6], tr_id[7],
++ tr_id[8], tr_id[9], tr_id[10], tr_id[11]);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_local_get_initiator_port_transport_id(
++ struct scst_tgt *tgt, struct scst_session *scst_sess,
++ uint8_t **transport_id)
++{
++ int res = 0;
++ int tr_id_size = 0;
++ uint8_t *tr_id = NULL;
++ struct scst_local_sess *sess;
++
++ TRACE_ENTRY();
++
++ if (scst_sess == NULL) {
++ res = SCSI_TRANSPORTID_PROTOCOLID_SAS;
++ goto out;
++ }
++
++ sess = (struct scst_local_sess *)scst_sess_get_tgt_priv(scst_sess);
++
++ mutex_lock(&sess->tr_id_mutex);
++
++ if (sess->transport_id == NULL) {
++ res = scst_local_get_sas_transport_id(sess,
++ transport_id, &tr_id_size);
++ goto out_unlock;
++ }
++
++ tr_id_size = sess->transport_id_len;
++ BUG_ON(tr_id_size == 0);
++
++ tr_id = kzalloc(tr_id_size, GFP_KERNEL);
++ if (tr_id == NULL) {
++ PRINT_ERROR("Allocation of TransportID (size %d) failed",
++ tr_id_size);
++ res = -ENOMEM;
++ goto out;
++ }
++
++ memcpy(tr_id, sess->transport_id, sess->transport_id_len);
++
++out_unlock:
++ mutex_unlock(&sess->tr_id_mutex);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/**
++ ** Tgtt attributes
++ **/
++
++static ssize_t scst_local_version_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ sprintf(buf, "%s/%s\n", SCST_LOCAL_VERSION, scst_local_version_date);
++
++#ifdef CONFIG_SCST_EXTRACHECKS
++ strcat(buf, "EXTRACHECKS\n");
++#endif
++
++#ifdef CONFIG_SCST_TRACING
++ strcat(buf, "TRACING\n");
++#endif
++
++#ifdef CONFIG_SCST_DEBUG
++ strcat(buf, "DEBUG\n");
++#endif
++
++ TRACE_EXIT();
++ return strlen(buf);
++}
++
++static struct kobj_attribute scst_local_version_attr =
++ __ATTR(version, S_IRUGO, scst_local_version_show, NULL);
++
++static ssize_t scst_local_stats_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++
++{
++ return sprintf(buf, "Aborts: %d, Device Resets: %d, Target Resets: %d",
++ atomic_read(&num_aborts), atomic_read(&num_dev_resets),
++ atomic_read(&num_target_resets));
++}
++
++static struct kobj_attribute scst_local_stats_attr =
++ __ATTR(stats, S_IRUGO, scst_local_stats_show, NULL);
++
++static const struct attribute *scst_local_tgtt_attrs[] = {
++ &scst_local_version_attr.attr,
++ &scst_local_stats_attr.attr,
++ NULL,
++};
++
++/**
++ ** Tgt attributes
++ **/
++
++static ssize_t scst_local_scsi_transport_version_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt *scst_tgt;
++ struct scst_local_tgt *tgt;
++ ssize_t res = -ENOENT;
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ goto out;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = scst_tgt_get_tgt_priv(scst_tgt);
++ if (!tgt)
++ goto out_up;
++
++ if (tgt->scsi_transport_version != 0)
++ res = sprintf(buf, "0x%x\n%s", tgt->scsi_transport_version,
++ SCST_SYSFS_KEY_MARK "\n");
++ else
++ res = sprintf(buf, "0x%x\n", 0x0BE0); /* SAS */
++
++out_up:
++ up_read(&scst_local_exit_rwsem);
++out:
++ return res;
++}
++
++static ssize_t scst_local_scsi_transport_version_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size)
++{
++ ssize_t res = -ENOENT;
++ struct scst_tgt *scst_tgt;
++ struct scst_local_tgt *tgt;
++ unsigned long val;
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ goto out;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = scst_tgt_get_tgt_priv(scst_tgt);
++ if (!tgt)
++ goto out_up;
++
++ res = strict_strtoul(buffer, 0, &val);
++ if (res != 0) {
++ PRINT_ERROR("strict_strtoul() for %s failed: %zd", buffer, res);
++ goto out_up;
++ }
++
++ tgt->scsi_transport_version = val;
++
++ res = size;
++
++out_up:
++ up_read(&scst_local_exit_rwsem);
++out:
++ return res;
++}
++
++static struct kobj_attribute scst_local_scsi_transport_version_attr =
++ __ATTR(scsi_transport_version, S_IRUGO | S_IWUSR,
++ scst_local_scsi_transport_version_show,
++ scst_local_scsi_transport_version_store);
++
++static ssize_t scst_local_phys_transport_version_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ struct scst_tgt *scst_tgt;
++ struct scst_local_tgt *tgt;
++ ssize_t res = -ENOENT;
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ goto out;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = scst_tgt_get_tgt_priv(scst_tgt);
++ if (!tgt)
++ goto out_up;
++
++ res = sprintf(buf, "0x%x\n%s", tgt->phys_transport_version,
++ (tgt->phys_transport_version != 0) ?
++ SCST_SYSFS_KEY_MARK "\n" : "");
++
++out_up:
++ up_read(&scst_local_exit_rwsem);
++out:
++ return res;
++}
++
++static ssize_t scst_local_phys_transport_version_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size)
++{
++ ssize_t res = -ENOENT;
++ struct scst_tgt *scst_tgt;
++ struct scst_local_tgt *tgt;
++ unsigned long val;
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ goto out;
++
++ scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
++ tgt = scst_tgt_get_tgt_priv(scst_tgt);
++ if (!tgt)
++ goto out_up;
++
++ res = strict_strtoul(buffer, 0, &val);
++ if (res != 0) {
++ PRINT_ERROR("strict_strtoul() for %s failed: %zd", buffer, res);
++ goto out_up;
++ }
++
++ tgt->phys_transport_version = val;
++
++ res = size;
++
++out_up:
++ up_read(&scst_local_exit_rwsem);
++out:
++ return res;
++}
++
++static struct kobj_attribute scst_local_phys_transport_version_attr =
++ __ATTR(phys_transport_version, S_IRUGO | S_IWUSR,
++ scst_local_phys_transport_version_show,
++ scst_local_phys_transport_version_store);
++
++static const struct attribute *scst_local_tgt_attrs[] = {
++ &scst_local_scsi_transport_version_attr.attr,
++ &scst_local_phys_transport_version_attr.attr,
++ NULL,
++};
++
++/**
++ ** Session attributes
++ **/
++
++static ssize_t scst_local_transport_id_show(struct kobject *kobj,
++ struct kobj_attribute *attr, char *buf)
++{
++ ssize_t res;
++ struct scst_session *scst_sess;
++ struct scst_local_sess *sess;
++ uint8_t *tr_id;
++ int tr_id_len, i;
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ return -ENOENT;
++
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
++ sess = (struct scst_local_sess *)scst_sess_get_tgt_priv(scst_sess);
++
++ mutex_lock(&sess->tr_id_mutex);
++
++ if (sess->transport_id != NULL) {
++ tr_id = sess->transport_id;
++ tr_id_len = sess->transport_id_len;
++ } else {
++ res = scst_local_get_sas_transport_id(sess, &tr_id, &tr_id_len);
++ if (res != 0)
++ goto out_unlock;
++ }
++
++ res = 0;
++ for (i = 0; i < tr_id_len; i++)
++ res += sprintf(&buf[res], "%c", tr_id[i]);
++
++ if (sess->transport_id == NULL)
++ kfree(tr_id);
++
++out_unlock:
++ mutex_unlock(&sess->tr_id_mutex);
++ up_read(&scst_local_exit_rwsem);
++ return res;
++}
++
++static ssize_t scst_local_transport_id_store(struct kobject *kobj,
++ struct kobj_attribute *attr, const char *buffer, size_t size)
++{
++ ssize_t res;
++ struct scst_session *scst_sess;
++ struct scst_local_sess *sess;
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ return -ENOENT;
++
++ scst_sess = container_of(kobj, struct scst_session, sess_kobj);
++ sess = (struct scst_local_sess *)scst_sess_get_tgt_priv(scst_sess);
++
++ mutex_lock(&sess->tr_id_mutex);
++
++ if (sess->transport_id != NULL) {
++ kfree(sess->transport_id);
++ sess->transport_id = NULL;
++ sess->transport_id_len = 0;
++ }
++
++ if (size == 0)
++ goto out_res;
++
++ sess->transport_id = kzalloc(size, GFP_KERNEL);
++ if (sess->transport_id == NULL) {
++ PRINT_ERROR("Allocation of transport_id (size %zd) failed",
++ size);
++ res = -ENOMEM;
++ goto out_unlock;
++ }
++
++ sess->transport_id_len = size;
++
++ memcpy(sess->transport_id, buffer, sess->transport_id_len);
++
++out_res:
++ res = size;
++
++out_unlock:
++ mutex_unlock(&sess->tr_id_mutex);
++ up_read(&scst_local_exit_rwsem);
++ return res;
++}
++
++static struct kobj_attribute scst_local_transport_id_attr =
++ __ATTR(transport_id, S_IRUGO | S_IWUSR,
++ scst_local_transport_id_show,
++ scst_local_transport_id_store);
++
++static const struct attribute *scst_local_sess_attrs[] = {
++ &scst_local_transport_id_attr.attr,
++ NULL,
++};
++
++static ssize_t scst_local_sysfs_add_target(const char *target_name, char *params)
++{
++ int res;
++ struct scst_local_tgt *tgt;
++ char *param, *p;
++
++ TRACE_ENTRY();
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ return -ENOENT;
++
++ res = scst_local_add_target(target_name, &tgt);
++ if (res != 0)
++ goto out_up;
++
++ while (1) {
++ param = scst_get_next_token_str(&params);
++ if (param == NULL)
++ break;
++
++ p = scst_get_next_lexem(&param);
++ if (*p == '\0')
++ break;
++
++ if (strcasecmp("session_name", p) != 0) {
++ PRINT_ERROR("Unknown parameter %s", p);
++ res = -EINVAL;
++ goto out_remove;
++ }
++
++ p = scst_get_next_lexem(&param);
++ if (*p == '\0') {
++ PRINT_ERROR("Wrong session name %s", p);
++ res = -EINVAL;
++ goto out_remove;
++ }
++
++ res = scst_local_add_adapter(tgt, p);
++ if (res != 0)
++ goto out_remove;
++ }
++
++out_up:
++ up_read(&scst_local_exit_rwsem);
++
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_remove:
++ scst_local_remove_target(tgt);
++ goto out_up;
++}
++
++static ssize_t scst_local_sysfs_del_target(const char *target_name)
++{
++ int res;
++ struct scst_local_tgt *tgt;
++ bool deleted = false;
++
++ TRACE_ENTRY();
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ return -ENOENT;
++
++ mutex_lock(&scst_local_mutex);
++ list_for_each_entry(tgt, &scst_local_tgts_list, tgts_list_entry) {
++ if (strcmp(target_name, tgt->scst_tgt->tgt_name) == 0) {
++ __scst_local_remove_target(tgt);
++ deleted = true;
++ break;
++ }
++ }
++ mutex_unlock(&scst_local_mutex);
++
++ if (!deleted) {
++ PRINT_ERROR("Target %s not found", target_name);
++ res = -ENOENT;
++ goto out_up;
++ }
++
++ res = 0;
++
++out_up:
++ up_read(&scst_local_exit_rwsem);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static ssize_t scst_local_sysfs_mgmt_cmd(char *buf)
++{
++ ssize_t res;
++ char *command, *target_name, *session_name;
++ struct scst_local_tgt *t, *tgt;
++
++ TRACE_ENTRY();
++
++ if (down_read_trylock(&scst_local_exit_rwsem) == 0)
++ return -ENOENT;
++
++ command = scst_get_next_lexem(&buf);
++
++ target_name = scst_get_next_lexem(&buf);
++ if (*target_name == '\0') {
++ PRINT_ERROR("%s", "Target name required");
++ res = -EINVAL;
++ goto out_up;
++ }
++
++ mutex_lock(&scst_local_mutex);
++
++ tgt = NULL;
++ list_for_each_entry(t, &scst_local_tgts_list, tgts_list_entry) {
++ if (strcmp(t->scst_tgt->tgt_name, target_name) == 0) {
++ tgt = t;
++ break;
++ }
++ }
++ if (tgt == NULL) {
++ PRINT_ERROR("Target %s not found", target_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ session_name = scst_get_next_lexem(&buf);
++ if (*session_name == '\0') {
++ PRINT_ERROR("%s", "Session name required");
++ res = -EINVAL;
++ goto out_unlock;
++ }
++
++ if (strcasecmp("add_session", command) == 0) {
++ res = __scst_local_add_adapter(tgt, session_name, true);
++ } else if (strcasecmp("del_session", command) == 0) {
++ struct scst_local_sess *s, *sess = NULL;
++ list_for_each_entry(s, &tgt->sessions_list,
++ sessions_list_entry) {
++ if (strcmp(s->scst_sess->initiator_name, session_name) == 0) {
++ sess = s;
++ break;
++ }
++ }
++ if (sess == NULL) {
++ PRINT_ERROR("Session %s not found (target %s)",
++ session_name, target_name);
++ res = -EINVAL;
++ goto out_unlock;
++ }
++ scst_local_remove_adapter(sess);
++ }
++
++ res = 0;
++
++out_unlock:
++ mutex_unlock(&scst_local_mutex);
++
++out_up:
++ up_read(&scst_local_exit_rwsem);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_local_abort(struct scsi_cmnd *SCpnt)
++{
++ struct scst_local_sess *sess;
++ int ret;
++ DECLARE_COMPLETION_ONSTACK(dev_reset_completion);
++
++ TRACE_ENTRY();
++
++ sess = to_scst_lcl_sess(scsi_get_device(SCpnt->device->host));
++
++ ret = scst_rx_mgmt_fn_tag(sess->scst_sess, SCST_ABORT_TASK, SCpnt->tag,
++ false, &dev_reset_completion);
++
++ /* Now wait for the completion ... */
++ wait_for_completion_interruptible(&dev_reset_completion);
++
++ atomic_inc(&num_aborts);
++
++ if (ret == 0)
++ ret = SUCCESS;
++
++ TRACE_EXIT_RES(ret);
++ return ret;
++}
++
++static int scst_local_device_reset(struct scsi_cmnd *SCpnt)
++{
++ struct scst_local_sess *sess;
++ __be16 lun;
++ int ret;
++ DECLARE_COMPLETION_ONSTACK(dev_reset_completion);
++
++ TRACE_ENTRY();
++
++ sess = to_scst_lcl_sess(scsi_get_device(SCpnt->device->host));
++
++ lun = cpu_to_be16(SCpnt->device->lun);
++
++ ret = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_LUN_RESET,
++ (const uint8_t *)&lun, sizeof(lun), false,
++ &dev_reset_completion);
++
++ /* Now wait for the completion ... */
++ wait_for_completion_interruptible(&dev_reset_completion);
++
++ atomic_inc(&num_dev_resets);
++
++ if (ret == 0)
++ ret = SUCCESS;
++
++ TRACE_EXIT_RES(ret);
++ return ret;
++}
++
++static int scst_local_target_reset(struct scsi_cmnd *SCpnt)
++{
++ struct scst_local_sess *sess;
++ __be16 lun;
++ int ret;
++ DECLARE_COMPLETION_ONSTACK(dev_reset_completion);
++
++ TRACE_ENTRY();
++
++ sess = to_scst_lcl_sess(scsi_get_device(SCpnt->device->host));
++
++ lun = cpu_to_be16(SCpnt->device->lun);
++
++ ret = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_TARGET_RESET,
++ (const uint8_t *)&lun, sizeof(lun), false,
++ &dev_reset_completion);
++
++ /* Now wait for the completion ... */
++ wait_for_completion_interruptible(&dev_reset_completion);
++
++ atomic_inc(&num_target_resets);
++
++ if (ret == 0)
++ ret = SUCCESS;
++
++ TRACE_EXIT_RES(ret);
++ return ret;
++}
++
++static void copy_sense(struct scsi_cmnd *cmnd, struct scst_cmd *scst_cmnd)
++{
++ int scst_cmnd_sense_len = scst_cmd_get_sense_buffer_len(scst_cmnd);
++
++ TRACE_ENTRY();
++
++ scst_cmnd_sense_len = (SCSI_SENSE_BUFFERSIZE < scst_cmnd_sense_len ?
++ SCSI_SENSE_BUFFERSIZE : scst_cmnd_sense_len);
++ memcpy(cmnd->sense_buffer, scst_cmd_get_sense_buffer(scst_cmnd),
++ scst_cmnd_sense_len);
++
++ TRACE_BUFFER("Sense set", cmnd->sense_buffer, scst_cmnd_sense_len);
++
++ TRACE_EXIT();
++ return;
++}
++
++/*
++ * Utility function to handle processing of done and allow
++ * easy insertion of error injection if desired
++ */
++static int scst_local_send_resp(struct scsi_cmnd *cmnd,
++ struct scst_cmd *scst_cmnd,
++ void (*done)(struct scsi_cmnd *),
++ int scsi_result)
++{
++ int ret = 0;
++
++ TRACE_ENTRY();
++
++ if (scst_cmnd) {
++ /* The buffer isn't ours, so let's be safe and restore it */
++ scst_check_restore_sg_buff(scst_cmnd);
++
++ /* Simulate autosense by this driver */
++ if (unlikely(SCST_SENSE_VALID(scst_cmnd->sense)))
++ copy_sense(cmnd, scst_cmnd);
++ }
++
++ cmnd->result = scsi_result;
++
++ done(cmnd);
++
++ TRACE_EXIT_RES(ret);
++ return ret;
++}
++
++/*
++ * This does the heavy lifting ... we pass all the commands on to the
++ * target driver and have it do its magic ...
++ */
++#ifdef CONFIG_SCST_LOCAL_FORCE_DIRECT_PROCESSING
++static int scst_local_queuecommand(struct Scsi_Host *host,
++ struct scsi_cmnd *SCpnt)
++#else
++static int scst_local_queuecommand_lck(struct scsi_cmnd *SCpnt,
++ void (*done)(struct scsi_cmnd *))
++ __acquires(&h->host_lock)
++ __releases(&h->host_lock)
++#endif
++{
++ struct scst_local_sess *sess;
++ struct scatterlist *sgl = NULL;
++ int sgl_count = 0;
++ __be16 lun;
++ struct scst_cmd *scst_cmd = NULL;
++ scst_data_direction dir;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("lun %d, cmd: 0x%02X", SCpnt->device->lun, SCpnt->cmnd[0]);
++
++ sess = to_scst_lcl_sess(scsi_get_device(SCpnt->device->host));
++
++ scsi_set_resid(SCpnt, 0);
++
++ /*
++ * Tell the target that we have a command ... but first we need
++ * to get the LUN into a format that SCST understand
++ *
++ * NOTE! We need to call it with atomic parameter true to not
++ * get into mem alloc deadlock when mounting file systems over
++ * our devices.
++ */
++ lun = cpu_to_be16(SCpnt->device->lun);
++ scst_cmd = scst_rx_cmd(sess->scst_sess, (const uint8_t *)&lun,
++ sizeof(lun), SCpnt->cmnd, SCpnt->cmd_len, true);
++ if (!scst_cmd) {
++ PRINT_ERROR("%s", "scst_rx_cmd() failed");
++ return SCSI_MLQUEUE_HOST_BUSY;
++ }
++
++ scst_cmd_set_tag(scst_cmd, SCpnt->tag);
++ switch (scsi_get_tag_type(SCpnt->device)) {
++ case MSG_SIMPLE_TAG:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_SIMPLE);
++ break;
++ case MSG_HEAD_TAG:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE);
++ break;
++ case MSG_ORDERED_TAG:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED);
++ break;
++ case SCSI_NO_TAG:
++ default:
++ scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_UNTAGGED);
++ break;
++ }
++
++ sgl = scsi_sglist(SCpnt);
++ sgl_count = scsi_sg_count(SCpnt);
++
++ dir = SCST_DATA_NONE;
++ switch (SCpnt->sc_data_direction) {
++ case DMA_TO_DEVICE:
++ dir = SCST_DATA_WRITE;
++ scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(SCpnt));
++ scst_cmd_set_noio_mem_alloc(scst_cmd);
++ scst_cmd_set_tgt_sg(scst_cmd, sgl, sgl_count);
++ break;
++ case DMA_FROM_DEVICE:
++ dir = SCST_DATA_READ;
++ scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(SCpnt));
++ scst_cmd_set_noio_mem_alloc(scst_cmd);
++ scst_cmd_set_tgt_sg(scst_cmd, sgl, sgl_count);
++ break;
++ case DMA_BIDIRECTIONAL:
++ /* Some of these symbols are only defined after 2.6.24 */
++ dir = SCST_DATA_BIDI;
++ scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(SCpnt));
++ scst_cmd_set_expected_out_transfer_len(scst_cmd,
++ scsi_in(SCpnt)->length);
++ scst_cmd_set_noio_mem_alloc(scst_cmd);
++ scst_cmd_set_tgt_sg(scst_cmd, scsi_in(SCpnt)->table.sgl,
++ scsi_in(SCpnt)->table.nents);
++ scst_cmd_set_tgt_out_sg(scst_cmd, sgl, sgl_count);
++ break;
++ case DMA_NONE:
++ default:
++ dir = SCST_DATA_NONE;
++ scst_cmd_set_expected(scst_cmd, dir, 0);
++ break;
++ }
++
++ /* Save the correct thing below depending on version */
++ scst_cmd_set_tgt_priv(scst_cmd, SCpnt);
++
++/*
++ * Although starting from 2.6.37 queuecommand() called with no host_lock
++ * held, in fact without DEF_SCSI_QCMD() it doesn't work and leading
++ * to various problems like hangs under highload. Most likely, it is caused
++ * by some not reenrable block layer function(s). So, until that changed, we
++ * have to go ahead with extra context switch. In this regard doesn't matter
++ * much if we under host_lock or not (although we absolutely don't need this
++ * lock), so let's have simpler code with DEF_SCSI_QCMD().
++ */
++#ifdef CONFIG_SCST_LOCAL_FORCE_DIRECT_PROCESSING
++ scst_cmd_init_done(scst_cmd, SCST_CONTEXT_DIRECT);
++#else
++ /*
++ * We called with IRQs disabled, so have no choice,
++ * except to pass to the thread context.
++ */
++ scst_cmd_init_done(scst_cmd, SCST_CONTEXT_THREAD);
++#endif
++
++ TRACE_EXIT();
++ return 0;
++}
++
++#if !defined(CONFIG_SCST_LOCAL_FORCE_DIRECT_PROCESSING)
++/*
++ * See comment in scst_local_queuecommand_lck() near
++ * CONFIG_SCST_LOCAL_FORCE_DIRECT_PROCESSING
++ */
++static DEF_SCSI_QCMD(scst_local_queuecommand)
++#endif
++
++static int scst_local_targ_pre_exec(struct scst_cmd *scst_cmd)
++{
++ int res = SCST_PREPROCESS_STATUS_SUCCESS;
++
++ TRACE_ENTRY();
++
++ if (scst_cmd_get_dh_data_buff_alloced(scst_cmd) &&
++ (scst_cmd_get_data_direction(scst_cmd) & SCST_DATA_WRITE))
++ scst_copy_sg(scst_cmd, SCST_SG_COPY_FROM_TARGET);
++
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++/* Must be called under sess->aen_lock. Drops then reacquires it inside. */
++static void scst_process_aens(struct scst_local_sess *sess,
++ bool cleanup_only)
++ __releases(&sess->aen_lock)
++ __acquires(&sess->aen_lock)
++{
++ struct scst_aen_work_item *work_item = NULL;
++
++ TRACE_ENTRY();
++
++ TRACE_DBG("Target work sess %p", sess);
++
++ while (!list_empty(&sess->aen_work_list)) {
++ work_item = list_entry(sess->aen_work_list.next,
++ struct scst_aen_work_item, work_list_entry);
++ list_del(&work_item->work_list_entry);
++
++ spin_unlock(&sess->aen_lock);
++
++ if (cleanup_only)
++ goto done;
++
++ BUG_ON(work_item->aen->event_fn != SCST_AEN_SCSI);
++
++ /* Let's always rescan */
++ scsi_scan_target(&sess->shost->shost_gendev, 0, 0,
++ SCAN_WILD_CARD, 1);
++
++done:
++ scst_aen_done(work_item->aen);
++ kfree(work_item);
++
++ spin_lock(&sess->aen_lock);
++ }
++
++ TRACE_EXIT();
++ return;
++}
++
++static void scst_aen_work_fn(struct work_struct *work)
++{
++ struct scst_local_sess *sess =
++ container_of(work, struct scst_local_sess, aen_work);
++
++ TRACE_ENTRY();
++
++ TRACE_MGMT_DBG("Target work %p)", sess);
++
++ spin_lock(&sess->aen_lock);
++ scst_process_aens(sess, false);
++ spin_unlock(&sess->aen_lock);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int scst_local_report_aen(struct scst_aen *aen)
++{
++ int res = 0;
++ int event_fn = scst_aen_get_event_fn(aen);
++ struct scst_local_sess *sess;
++ struct scst_aen_work_item *work_item = NULL;
++
++ TRACE_ENTRY();
++
++ sess = (struct scst_local_sess *)scst_sess_get_tgt_priv(
++ scst_aen_get_sess(aen));
++ switch (event_fn) {
++ case SCST_AEN_SCSI:
++ /*
++ * Allocate a work item and place it on the queue
++ */
++ work_item = kzalloc(sizeof(*work_item), GFP_KERNEL);
++ if (!work_item) {
++ PRINT_ERROR("%s", "Unable to allocate work item "
++ "to handle AEN!");
++ return -ENOMEM;
++ }
++
++ spin_lock(&sess->aen_lock);
++
++ if (unlikely(sess->unregistering)) {
++ spin_unlock(&sess->aen_lock);
++ kfree(work_item);
++ res = SCST_AEN_RES_NOT_SUPPORTED;
++ goto out;
++ }
++
++ list_add_tail(&work_item->work_list_entry, &sess->aen_work_list);
++ work_item->aen = aen;
++
++ spin_unlock(&sess->aen_lock);
++
++ schedule_work(&sess->aen_work);
++ break;
++
++ default:
++ TRACE_MGMT_DBG("Unsupported AEN %d", event_fn);
++ res = SCST_AEN_RES_NOT_SUPPORTED;
++ break;
++ }
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++}
++
++static int scst_local_targ_detect(struct scst_tgt_template *tgt_template)
++{
++ TRACE_ENTRY();
++
++ TRACE_EXIT();
++ return 0;
++};
++
++static int scst_local_targ_release(struct scst_tgt *tgt)
++{
++ TRACE_ENTRY();
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static int scst_local_targ_xmit_response(struct scst_cmd *scst_cmd)
++{
++ struct scsi_cmnd *SCpnt = NULL;
++ void (*done)(struct scsi_cmnd *);
++
++ TRACE_ENTRY();
++
++ if (unlikely(scst_cmd_aborted(scst_cmd))) {
++ scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_ABORTED);
++ scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_SAME);
++ return SCST_TGT_RES_SUCCESS;
++ }
++
++ if (scst_cmd_get_dh_data_buff_alloced(scst_cmd) &&
++ (scst_cmd_get_data_direction(scst_cmd) & SCST_DATA_READ))
++ scst_copy_sg(scst_cmd, SCST_SG_COPY_TO_TARGET);
++
++ SCpnt = scst_cmd_get_tgt_priv(scst_cmd);
++ done = SCpnt->scsi_done;
++
++ /*
++ * This might have to change to use the two status flags
++ */
++ if (scst_cmd_get_is_send_status(scst_cmd)) {
++ int resid = 0, out_resid = 0;
++
++ /* Calculate the residual ... */
++ if (likely(!scst_get_resid(scst_cmd, &resid, &out_resid))) {
++ TRACE_DBG("No residuals for request %p", SCpnt);
++ } else {
++ if (out_resid != 0)
++ PRINT_ERROR("Unable to return OUT residual %d "
++ "(op %02x)", out_resid, SCpnt->cmnd[0]);
++ }
++
++ scsi_set_resid(SCpnt, resid);
++
++ /*
++ * It seems like there is no way to set out_resid ...
++ */
++
++ (void)scst_local_send_resp(SCpnt, scst_cmd, done,
++ scst_cmd_get_status(scst_cmd));
++ }
++
++ /* Now tell SCST that the command is done ... */
++ scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_SAME);
++
++ TRACE_EXIT();
++ return SCST_TGT_RES_SUCCESS;
++}
++
++static void scst_local_targ_task_mgmt_done(struct scst_mgmt_cmd *mgmt_cmd)
++{
++ struct completion *compl;
++
++ TRACE_ENTRY();
++
++ compl = (struct completion *)scst_mgmt_cmd_get_tgt_priv(mgmt_cmd);
++ if (compl)
++ complete(compl);
++
++ TRACE_EXIT();
++ return;
++}
++
++static uint16_t scst_local_get_scsi_transport_version(struct scst_tgt *scst_tgt)
++{
++ struct scst_local_tgt *tgt;
++
++ tgt = (struct scst_local_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++
++ if (tgt->scsi_transport_version == 0)
++ return 0x0BE0; /* SAS */
++ else
++ return tgt->scsi_transport_version;
++}
++
++static uint16_t scst_local_get_phys_transport_version(struct scst_tgt *scst_tgt)
++{
++ struct scst_local_tgt *tgt;
++
++ tgt = (struct scst_local_tgt *)scst_tgt_get_tgt_priv(scst_tgt);
++
++ return tgt->phys_transport_version;
++}
++
++static struct scst_tgt_template scst_local_targ_tmpl = {
++ .name = "scst_local",
++ .sg_tablesize = 0xffff,
++ .xmit_response_atomic = 1,
++ .enabled_attr_not_needed = 1,
++ .tgtt_attrs = scst_local_tgtt_attrs,
++ .tgt_attrs = scst_local_tgt_attrs,
++ .sess_attrs = scst_local_sess_attrs,
++ .add_target = scst_local_sysfs_add_target,
++ .del_target = scst_local_sysfs_del_target,
++ .mgmt_cmd = scst_local_sysfs_mgmt_cmd,
++ .add_target_parameters = "session_name",
++ .mgmt_cmd_help = " echo \"add_session target_name session_name\" >mgmt\n"
++ " echo \"del_session target_name session_name\" >mgmt\n",
++ .detect = scst_local_targ_detect,
++ .release = scst_local_targ_release,
++ .pre_exec = scst_local_targ_pre_exec,
++ .xmit_response = scst_local_targ_xmit_response,
++ .task_mgmt_fn_done = scst_local_targ_task_mgmt_done,
++ .report_aen = scst_local_report_aen,
++ .get_initiator_port_transport_id = scst_local_get_initiator_port_transport_id,
++ .get_scsi_transport_version = scst_local_get_scsi_transport_version,
++ .get_phys_transport_version = scst_local_get_phys_transport_version,
++#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
++ .default_trace_flags = SCST_LOCAL_DEFAULT_LOG_FLAGS,
++ .trace_flags = &trace_flag,
++#endif
++};
++
++static struct scsi_host_template scst_lcl_ini_driver_template = {
++ .name = SCST_LOCAL_NAME,
++ .queuecommand = scst_local_queuecommand,
++ .eh_abort_handler = scst_local_abort,
++ .eh_device_reset_handler = scst_local_device_reset,
++ .eh_target_reset_handler = scst_local_target_reset,
++ .can_queue = 256,
++ .this_id = -1,
++ .sg_tablesize = 0xFFFF,
++ .cmd_per_lun = 32,
++ .max_sectors = 0xffff,
++ /* Possible pass-through backend device may not support clustering */
++ .use_clustering = DISABLE_CLUSTERING,
++ .skip_settle_delay = 1,
++ .module = THIS_MODULE,
++};
++
++/*
++ * LLD Bus and functions
++ */
++
++static int scst_local_driver_probe(struct device *dev)
++{
++ int ret;
++ struct scst_local_sess *sess;
++ struct Scsi_Host *hpnt;
++
++ TRACE_ENTRY();
++
++ sess = to_scst_lcl_sess(dev);
++
++ TRACE_DBG("sess %p", sess);
++
++ hpnt = scsi_host_alloc(&scst_lcl_ini_driver_template, sizeof(*sess));
++ if (NULL == hpnt) {
++ PRINT_ERROR("%s", "scsi_register() failed");
++ ret = -ENODEV;
++ goto out;
++ }
++
++ sess->shost = hpnt;
++
++ hpnt->max_id = 0; /* Don't want more than one id */
++ hpnt->max_lun = 0xFFFF;
++
++ /*
++ * Because of a change in the size of this field at 2.6.26
++ * we use this check ... it allows us to work on earlier
++ * kernels. If we don't, max_cmd_size gets set to 4 (and we get
++ * a compiler warning) so a scan never occurs.
++ */
++ hpnt->max_cmd_len = 260;
++
++ ret = scsi_add_host(hpnt, &sess->dev);
++ if (ret) {
++ PRINT_ERROR("%s", "scsi_add_host() failed");
++ ret = -ENODEV;
++ scsi_host_put(hpnt);
++ goto out;
++ }
++
++out:
++ TRACE_EXIT_RES(ret);
++ return ret;
++}
++
++static int scst_local_driver_remove(struct device *dev)
++{
++ struct scst_local_sess *sess;
++
++ TRACE_ENTRY();
++
++ sess = to_scst_lcl_sess(dev);
++ if (!sess) {
++ PRINT_ERROR("%s", "Unable to locate sess info");
++ return -ENODEV;
++ }
++
++ scsi_remove_host(sess->shost);
++ scsi_host_put(sess->shost);
++
++ TRACE_EXIT();
++ return 0;
++}
++
++static int scst_local_bus_match(struct device *dev,
++ struct device_driver *dev_driver)
++{
++ TRACE_ENTRY();
++
++ TRACE_EXIT();
++ return 1;
++}
++
++static struct bus_type scst_local_lld_bus = {
++ .name = "scst_local_bus",
++ .match = scst_local_bus_match,
++ .probe = scst_local_driver_probe,
++ .remove = scst_local_driver_remove,
++};
++
++static struct device_driver scst_local_driver = {
++ .name = SCST_LOCAL_NAME,
++ .bus = &scst_local_lld_bus,
++};
++
++static struct device *scst_local_root;
++
++static void scst_local_release_adapter(struct device *dev)
++{
++ struct scst_local_sess *sess;
++
++ TRACE_ENTRY();
++
++ sess = to_scst_lcl_sess(dev);
++ if (sess == NULL)
++ goto out;
++
++ spin_lock(&sess->aen_lock);
++ sess->unregistering = 1;
++ scst_process_aens(sess, true);
++ spin_unlock(&sess->aen_lock);
++
++ cancel_work_sync(&sess->aen_work);
++
++ scst_unregister_session(sess->scst_sess, true, NULL);
++
++ kfree(sess);
++
++out:
++ TRACE_EXIT();
++ return;
++}
++
++static int __scst_local_add_adapter(struct scst_local_tgt *tgt,
++ const char *initiator_name, bool locked)
++{
++ int res;
++ struct scst_local_sess *sess;
++
++ TRACE_ENTRY();
++
++ sess = kzalloc(sizeof(*sess), GFP_KERNEL);
++ if (NULL == sess) {
++ PRINT_ERROR("Unable to alloc scst_lcl_host (size %zu)",
++ sizeof(*sess));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ sess->tgt = tgt;
++ sess->number = atomic_inc_return(&scst_local_sess_num);
++ mutex_init(&sess->tr_id_mutex);
++
++ /*
++ * Init this stuff we need for scheduling AEN work
++ */
++ INIT_WORK(&sess->aen_work, scst_aen_work_fn);
++ spin_lock_init(&sess->aen_lock);
++ INIT_LIST_HEAD(&sess->aen_work_list);
++
++ sess->scst_sess = scst_register_session(tgt->scst_tgt, 0,
++ initiator_name, (void *)sess, NULL, NULL);
++ if (sess->scst_sess == NULL) {
++ PRINT_ERROR("%s", "scst_register_session failed");
++ kfree(sess);
++ res = -EFAULT;
++ goto out_free;
++ }
++
++ sess->dev.bus = &scst_local_lld_bus;
++ sess->dev.parent = scst_local_root;
++ sess->dev.release = &scst_local_release_adapter;
++ sess->dev.init_name = kobject_name(&sess->scst_sess->sess_kobj);
++
++ res = device_register(&sess->dev);
++ if (res != 0)
++ goto unregister_session;
++
++ res = sysfs_create_link(scst_sysfs_get_sess_kobj(sess->scst_sess),
++ &sess->shost->shost_dev.kobj, "host");
++ if (res != 0) {
++ PRINT_ERROR("Unable to create \"host\" link for target "
++ "%s", scst_get_tgt_name(tgt->scst_tgt));
++ goto unregister_dev;
++ }
++
++ if (!locked)
++ mutex_lock(&scst_local_mutex);
++ list_add_tail(&sess->sessions_list_entry, &tgt->sessions_list);
++ if (!locked)
++ mutex_unlock(&scst_local_mutex);
++
++ if (scst_initiator_has_luns(tgt->scst_tgt, initiator_name))
++ scsi_scan_target(&sess->shost->shost_gendev, 0, 0,
++ SCAN_WILD_CARD, 1);
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++unregister_dev:
++ device_unregister(&sess->dev);
++
++unregister_session:
++ scst_unregister_session(sess->scst_sess, true, NULL);
++
++out_free:
++ kfree(sess);
++ goto out;
++}
++
++static int scst_local_add_adapter(struct scst_local_tgt *tgt,
++ const char *initiator_name)
++{
++ return __scst_local_add_adapter(tgt, initiator_name, false);
++}
++
++/* Must be called under scst_local_mutex */
++static void scst_local_remove_adapter(struct scst_local_sess *sess)
++{
++ TRACE_ENTRY();
++
++ list_del(&sess->sessions_list_entry);
++
++ device_unregister(&sess->dev);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int scst_local_add_target(const char *target_name,
++ struct scst_local_tgt **out_tgt)
++{
++ int res;
++ struct scst_local_tgt *tgt;
++
++ TRACE_ENTRY();
++
++ tgt = kzalloc(sizeof(*tgt), GFP_KERNEL);
++ if (NULL == tgt) {
++ PRINT_ERROR("Unable to alloc tgt (size %zu)", sizeof(*tgt));
++ res = -ENOMEM;
++ goto out;
++ }
++
++ INIT_LIST_HEAD(&tgt->sessions_list);
++
++ tgt->scst_tgt = scst_register_target(&scst_local_targ_tmpl, target_name);
++ if (tgt->scst_tgt == NULL) {
++ PRINT_ERROR("%s", "scst_register_target() failed:");
++ res = -EFAULT;
++ goto out_free;
++ }
++
++ scst_tgt_set_tgt_priv(tgt->scst_tgt, tgt);
++
++ mutex_lock(&scst_local_mutex);
++ list_add_tail(&tgt->tgts_list_entry, &scst_local_tgts_list);
++ mutex_unlock(&scst_local_mutex);
++
++ if (out_tgt != NULL)
++ *out_tgt = tgt;
++
++ res = 0;
++
++out:
++ TRACE_EXIT_RES(res);
++ return res;
++
++out_free:
++ kfree(tgt);
++ goto out;
++}
++
++/* Must be called under scst_local_mutex */
++static void __scst_local_remove_target(struct scst_local_tgt *tgt)
++{
++ struct scst_local_sess *sess, *ts;
++
++ TRACE_ENTRY();
++
++ list_for_each_entry_safe(sess, ts, &tgt->sessions_list,
++ sessions_list_entry) {
++ scst_local_remove_adapter(sess);
++ }
++
++ list_del(&tgt->tgts_list_entry);
++
++ scst_unregister_target(tgt->scst_tgt);
++
++ kfree(tgt);
++
++ TRACE_EXIT();
++ return;
++}
++
++static void scst_local_remove_target(struct scst_local_tgt *tgt)
++{
++ TRACE_ENTRY();
++
++ mutex_lock(&scst_local_mutex);
++ __scst_local_remove_target(tgt);
++ mutex_unlock(&scst_local_mutex);
++
++ TRACE_EXIT();
++ return;
++}
++
++static int __init scst_local_init(void)
++{
++ int ret;
++ struct scst_local_tgt *tgt;
++
++ TRACE_ENTRY();
++
++ scst_local_root = root_device_register(SCST_LOCAL_NAME);
++ if (IS_ERR(scst_local_root)) {
++ ret = PTR_ERR(scst_local_root);
++ goto out;
++ }
++
++ ret = bus_register(&scst_local_lld_bus);
++ if (ret < 0) {
++ PRINT_ERROR("bus_register() error: %d", ret);
++ goto dev_unreg;
++ }
++
++ ret = driver_register(&scst_local_driver);
++ if (ret < 0) {
++ PRINT_ERROR("driver_register() error: %d", ret);
++ goto bus_unreg;
++ }
++
++ ret = scst_register_target_template(&scst_local_targ_tmpl);
++ if (ret != 0) {
++ PRINT_ERROR("Unable to register target template: %d", ret);
++ goto driver_unreg;
++ }
++
++ /*
++ * If we are using sysfs, then don't add a default target unless
++ * we are told to do so. When using procfs, we always add a default
++ * target because that was what the earliest versions did. Just
++ * remove the preprocessor directives when no longer needed.
++ */
++ if (!scst_local_add_default_tgt)
++ goto out;
++
++ ret = scst_local_add_target("scst_local_tgt", &tgt);
++ if (ret != 0)
++ goto tgt_templ_unreg;
++
++ ret = scst_local_add_adapter(tgt, "scst_local_host");
++ if (ret != 0)
++ goto tgt_unreg;
++
++out:
++ TRACE_EXIT_RES(ret);
++ return ret;
++
++tgt_unreg:
++ scst_local_remove_target(tgt);
++
++tgt_templ_unreg:
++ scst_unregister_target_template(&scst_local_targ_tmpl);
++
++driver_unreg:
++ driver_unregister(&scst_local_driver);
++
++bus_unreg:
++ bus_unregister(&scst_local_lld_bus);
++
++dev_unreg:
++ root_device_unregister(scst_local_root);
++
++ goto out;
++}
++
++static void __exit scst_local_exit(void)
++{
++ struct scst_local_tgt *tgt, *tt;
++
++ TRACE_ENTRY();
++
++ down_write(&scst_local_exit_rwsem);
++
++ mutex_lock(&scst_local_mutex);
++ list_for_each_entry_safe(tgt, tt, &scst_local_tgts_list,
++ tgts_list_entry) {
++ __scst_local_remove_target(tgt);
++ }
++ mutex_unlock(&scst_local_mutex);
++
++ driver_unregister(&scst_local_driver);
++ bus_unregister(&scst_local_lld_bus);
++ root_device_unregister(scst_local_root);
++
++ /* Now unregister the target template */
++ scst_unregister_target_template(&scst_local_targ_tmpl);
++
++ /* To make lockdep happy */
++ up_write(&scst_local_exit_rwsem);
++
++ TRACE_EXIT();
++ return;
++}
++
++device_initcall(scst_local_init);
++module_exit(scst_local_exit);