diff options
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.patch | 80901 |
1 files changed, 0 insertions, 80901 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 deleted file mode 100644 index 68e90a791e..0000000000 --- a/main/linux-scst/scst-2.2.0-3.2.2.patch +++ /dev/null @@ -1,80901 +0,0 @@ -=== 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(¶ms, 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, ¶ms); -+} -+ -+/* -+ * 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(¶ms, 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, ¶ms); -+} -+ -+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" -+#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 *)␣ -+ -+ 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(®->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(®->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(®->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(®->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(®->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 *)®->key, -+ sizeof(reg->key), &pos); -+ if (res != sizeof(reg->key)) -+ goto write_error; -+ -+ res = vfs_write(file, (void __force __user *)®->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(®->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(®->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(®->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(®->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(¶m); -+ 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(¶m); -+ 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(¶m)[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[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(¶ms); -+ if (param == NULL) -+ break; -+ -+ p = scst_get_next_lexem(¶m); -+ 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(¶m); -+ 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(¶m)[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(¶ms, 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, ¶ms); -+ 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" 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(®, 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(®, 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, ®, 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(¶ms->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(¶ms->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(¶ms, 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, -+ ¶ms); -+ 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, -+ ¶ms); -+ 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, -+ ¶ms); -+ 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, -+ ¶ms); -+ 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, -+ ¶ms); -+ 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, -+ ¶ms); -+ 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( -+ ®->isp24.req_q_out); -+ else -+ cnt = qla2x00_debounce_register( -+ ISP_REQ_Q_OUT(ha, ®->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(¶ms); -+ if (param == NULL) -+ break; -+ -+ p = scst_get_next_lexem(¶m); -+ if (*p == '\0') { -+ PRINT_ERROR("qla2x00t: Syntax error at %s (target %s)", -+ param, target_name); -+ res = -EINVAL; -+ goto out; -+ } -+ -+ pp = scst_get_next_lexem(¶m); -+ 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(¶m)[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" -+#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(®_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, -+ ®_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" -+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(¶ms); -+ if (param == NULL) -+ break; -+ -+ p = scst_get_next_lexem(¶m); -+ 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(¶m); -+ 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); |