diff options
author | Mike Christie <michaelc@cs.wisc.edu> | 2009-01-21 16:51:55 -0600 |
---|---|---|
committer | Mike Christie <michaelc@cs.wisc.edu> | 2009-01-21 17:09:28 -0600 |
commit | 07fdaaa472e6e9d4baed4a9f56aec91b647d9807 (patch) | |
tree | 0d62755d7b24566b5f798076d2923884fbc656ad | |
parent | 5097c21531a981abecdcd997dba3d4feec395efd (diff) | |
download | open-iscsi-07fdaaa472e6e9d4baed4a9f56aec91b647d9807.tar.gz |
iscsi mod: sync up kernel modules with 2.6.29
This takes the iscsi modules that are planned for 2.6.29
and adds them to the kernel dir.
-rw-r--r-- | include/iscsi_if.h | 7 | ||||
-rw-r--r-- | kernel/Makefile | 5 | ||||
-rw-r--r-- | kernel/iscsi_tcp.c | 1628 | ||||
-rw-r--r-- | kernel/iscsi_tcp.h | 88 | ||||
-rw-r--r-- | kernel/libiscsi.c | 401 | ||||
-rw-r--r-- | kernel/libiscsi.h | 55 | ||||
-rw-r--r-- | kernel/libiscsi_tcp.c | 1162 | ||||
-rw-r--r-- | kernel/libiscsi_tcp.h | 133 | ||||
-rw-r--r-- | kernel/scsi_transport_iscsi.c | 25 | ||||
-rw-r--r-- | kernel/scsi_transport_iscsi.h | 15 |
10 files changed, 1946 insertions, 1573 deletions
diff --git a/include/iscsi_if.h b/include/iscsi_if.h index afa86e8..81f92e8 100644 --- a/include/iscsi_if.h +++ b/include/iscsi_if.h @@ -333,8 +333,11 @@ enum iscsi_host_param { #define CAP_TEXT_NEGO 0x80 #define CAP_MARKERS 0x100 #define CAP_FW_DB 0x200 -#define CAP_SENDTARGETS_OFFLOAD 0x400 -#define CAP_DATA_PATH_OFFLOAD 0x800 +#define CAP_SENDTARGETS_OFFLOAD 0x400 /* offload discovery process */ +#define CAP_DATA_PATH_OFFLOAD 0x800 /* offload entire IO path */ +#define CAP_DIGEST_OFFLOAD 0x1000 /* offload hdr and data digests */ +#define CAP_PADDING_OFFLOAD 0x2000 /* offload padding insertion, removal, + and verification */ /* * These flags describes reason of stop_conn() call diff --git a/kernel/Makefile b/kernel/Makefile index a68fd1b..81717b5 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -20,6 +20,7 @@ endif obj-m += scsi_transport_iscsi.o obj-m += libiscsi.o +obj-m += libiscsi_tcp.o obj-m += iscsi_tcp.o # Everything beyond this point is used to call KBuild or handle support @@ -95,6 +96,10 @@ linux_2_6_26: has_26_patch linux_2_6_27: $(unpatch_code) +linux_2_6_28: $(unpatch_code) + +linux_2_6_29: $(unpatch_code) + do_unpatch_code: echo "Un-patching source code for use with linux-2.6.14 and up ..." patch -R -E -p1 < $(cur_patched) diff --git a/kernel/iscsi_tcp.c b/kernel/iscsi_tcp.c index d074146..6214055 100644 --- a/kernel/iscsi_tcp.c +++ b/kernel/iscsi_tcp.c @@ -27,7 +27,6 @@ */ #include <linux/types.h> -#include <linux/list.h> #include <linux/inet.h> #include <linux/file.h> #include <linux/blkdev.h> @@ -44,12 +43,12 @@ #include "iscsi_tcp.h" -MODULE_AUTHOR("Dmitry Yusupov <dmitry_yus@yahoo.com>, " +MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, " + "Dmitry Yusupov <dmitry_yus@yahoo.com>, " "Alex Aizman <itn780@yahoo.com>"); MODULE_DESCRIPTION("iSCSI/TCP data-path"); MODULE_LICENSE("GPL"); #undef DEBUG_TCP -#define DEBUG_ASSERT #ifdef DEBUG_TCP #define debug_tcp(fmt...) printk(KERN_INFO "tcp: " fmt) @@ -57,934 +56,41 @@ MODULE_LICENSE("GPL"); #define debug_tcp(fmt...) #endif -#ifndef DEBUG_ASSERT -#ifdef BUG_ON -#undef BUG_ON -#endif -#define BUG_ON(expr) -#endif - -static struct scsi_transport_template *iscsi_tcp_scsi_transport; -static struct scsi_host_template iscsi_sht; -static struct iscsi_transport iscsi_tcp_transport; +static struct scsi_transport_template *iscsi_sw_tcp_scsi_transport; +static struct scsi_host_template iscsi_sw_tcp_sht; +static struct iscsi_transport iscsi_sw_tcp_transport; static unsigned int iscsi_max_lun = 512; module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO); -static int iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn, - struct iscsi_segment *segment); - -/* - * Scatterlist handling: inside the iscsi_segment, we - * remember an index into the scatterlist, and set data/size - * to the current scatterlist entry. For highmem pages, we - * kmap as needed. - * - * Note that the page is unmapped when we return from - * TCP's data_ready handler, so we may end up mapping and - * unmapping the same page repeatedly. The whole reason - * for this is that we shouldn't keep the page mapped - * outside the softirq. - */ - -/** - * iscsi_tcp_segment_init_sg - init indicated scatterlist entry - * @segment: the buffer object - * @sg: scatterlist - * @offset: byte offset into that sg entry - * - * This function sets up the segment so that subsequent - * data is copied to the indicated sg entry, at the given - * offset. - */ -static inline void -iscsi_tcp_segment_init_sg(struct iscsi_segment *segment, - struct scatterlist *sg, unsigned int offset) -{ - segment->sg = sg; - segment->sg_offset = offset; - segment->size = min(sg->length - offset, - segment->total_size - segment->total_copied); - segment->data = NULL; -} - -/** - * iscsi_tcp_segment_map - map the current S/G page - * @segment: iscsi_segment - * @recv: 1 if called from recv path - * - * We only need to possibly kmap data if scatter lists are being used, - * because the iscsi passthrough and internal IO paths will never use high - * mem pages. - */ -static inline void -iscsi_tcp_segment_map(struct iscsi_segment *segment, int recv) -{ - struct scatterlist *sg; - - if (segment->data != NULL || !segment->sg) - return; - - sg = segment->sg; - BUG_ON(segment->sg_mapped); - BUG_ON(sg->length == 0); - - /* - * If the page count is greater than one it is ok to send - * to the network layer's zero copy send path. If not we - * have to go the slow sendmsg path. We always map for the - * recv path. - */ - if (page_count(sg_page(sg)) >= 1 && !recv) - return; - - debug_tcp("iscsi_tcp_segment_map %s %p\n", recv ? "recv" : "xmit", - segment); - segment->sg_mapped = kmap_atomic(sg_page(sg), KM_SOFTIRQ0); - segment->data = segment->sg_mapped + sg->offset + segment->sg_offset; -} - -static inline void -iscsi_tcp_segment_unmap(struct iscsi_segment *segment) -{ - debug_tcp("iscsi_tcp_segment_unmap %p\n", segment); - - if (segment->sg_mapped) { - debug_tcp("iscsi_tcp_segment_unmap valid\n"); - kunmap_atomic(segment->sg_mapped, KM_SOFTIRQ0); - segment->sg_mapped = NULL; - segment->data = NULL; - } -} - -/* - * Splice the digest buffer into the buffer - */ -static inline void -iscsi_tcp_segment_splice_digest(struct iscsi_segment *segment, void *digest) -{ - segment->data = digest; - segment->digest_len = ISCSI_DIGEST_SIZE; - segment->total_size += ISCSI_DIGEST_SIZE; - segment->size = ISCSI_DIGEST_SIZE; - segment->copied = 0; - segment->sg = NULL; - segment->hash = NULL; -} - -/** - * iscsi_tcp_segment_done - check whether the segment is complete - * @segment: iscsi segment to check - * @recv: set to one of this is called from the recv path - * @copied: number of bytes copied - * - * Check if we're done receiving this segment. If the receive - * buffer is full but we expect more data, move on to the - * next entry in the scatterlist. - * - * If the amount of data we received isn't a multiple of 4, - * we will transparently receive the pad bytes, too. - * - * This function must be re-entrant. - */ -static inline int -iscsi_tcp_segment_done(struct iscsi_segment *segment, int recv, unsigned copied) -{ - static unsigned char padbuf[ISCSI_PAD_LEN]; - struct scatterlist sg; - unsigned int pad; - - debug_tcp("copied %u %u size %u %s\n", segment->copied, copied, - segment->size, recv ? "recv" : "xmit"); - if (segment->hash && copied) { - /* - * If a segment is kmapd we must unmap it before sending - * to the crypto layer since that will try to kmap it again. - */ - iscsi_tcp_segment_unmap(segment); - - if (!segment->data) { - sg_init_table(&sg, 1); - sg_set_page(&sg, sg_page(segment->sg), copied, - segment->copied + segment->sg_offset + - segment->sg->offset); - } else - sg_init_one(&sg, segment->data + segment->copied, - copied); - crypto_hash_update(segment->hash, &sg, copied); - } - - segment->copied += copied; - if (segment->copied < segment->size) { - iscsi_tcp_segment_map(segment, recv); - return 0; - } - - segment->total_copied += segment->copied; - segment->copied = 0; - segment->size = 0; - - /* Unmap the current scatterlist page, if there is one. */ - iscsi_tcp_segment_unmap(segment); - - /* Do we have more scatterlist entries? */ - debug_tcp("total copied %u total size %u\n", segment->total_copied, - segment->total_size); - if (segment->total_copied < segment->total_size) { - /* Proceed to the next entry in the scatterlist. */ - iscsi_tcp_segment_init_sg(segment, sg_next(segment->sg), - 0); - iscsi_tcp_segment_map(segment, recv); - BUG_ON(segment->size == 0); - return 0; - } - - /* Do we need to handle padding? */ - pad = iscsi_padding(segment->total_copied); - if (pad != 0) { - debug_tcp("consume %d pad bytes\n", pad); - segment->total_size += pad; - segment->size = pad; - segment->data = padbuf; - return 0; - } - - /* - * Set us up for transferring the data digest. hdr digest - * is completely handled in hdr done function. - */ - if (segment->hash) { - crypto_hash_final(segment->hash, segment->digest); - iscsi_tcp_segment_splice_digest(segment, - recv ? segment->recv_digest : segment->digest); - return 0; - } - - return 1; -} - -/** - * iscsi_tcp_xmit_segment - transmit segment - * @tcp_conn: the iSCSI TCP connection - * @segment: the buffer to transmnit - * - * This function transmits as much of the buffer as - * the network layer will accept, and returns the number of - * bytes transmitted. - * - * If CRC hashing is enabled, the function will compute the - * hash as it goes. When the entire segment has been transmitted, - * it will retrieve the hash value and send it as well. - */ -static int -iscsi_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn, - struct iscsi_segment *segment) -{ - struct socket *sk = tcp_conn->sock; - unsigned int copied = 0; - int r = 0; - - while (!iscsi_tcp_segment_done(segment, 0, r)) { - struct scatterlist *sg; - unsigned int offset, copy; - int flags = 0; - - r = 0; - offset = segment->copied; - copy = segment->size - offset; - - if (segment->total_copied + segment->size < segment->total_size) - flags |= MSG_MORE; - - /* Use sendpage if we can; else fall back to sendmsg */ - if (!segment->data) { - sg = segment->sg; - offset += segment->sg_offset + sg->offset; - r = tcp_conn->sendpage(sk, sg_page(sg), offset, copy, - flags); - } else { - struct msghdr msg = { .msg_flags = flags }; - struct kvec iov = { - .iov_base = segment->data + offset, - .iov_len = copy - }; - - r = kernel_sendmsg(sk, &msg, &iov, 1, copy); - } - - if (r < 0) { - iscsi_tcp_segment_unmap(segment); - if (copied || r == -EAGAIN) - break; - return r; - } - copied += r; - } - return copied; -} - /** - * iscsi_tcp_segment_recv - copy data to segment - * @tcp_conn: the iSCSI TCP connection - * @segment: the buffer to copy to - * @ptr: data pointer - * @len: amount of data available - * - * This function copies up to @len bytes to the - * given buffer, and returns the number of bytes - * consumed, which can actually be less than @len. - * - * If hash digest is enabled, the function will update the - * hash while copying. - * Combining these two operations doesn't buy us a lot (yet), - * but in the future we could implement combined copy+crc, - * just way we do for network layer checksums. - */ -static int -iscsi_tcp_segment_recv(struct iscsi_tcp_conn *tcp_conn, - struct iscsi_segment *segment, const void *ptr, - unsigned int len) -{ - unsigned int copy = 0, copied = 0; - - while (!iscsi_tcp_segment_done(segment, 1, copy)) { - if (copied == len) { - debug_tcp("iscsi_tcp_segment_recv copied %d bytes\n", - len); - break; - } - - copy = min(len - copied, segment->size - segment->copied); - debug_tcp("iscsi_tcp_segment_recv copying %d\n", copy); - memcpy(segment->data + segment->copied, ptr + copied, copy); - copied += copy; - } - return copied; -} - -static inline void -iscsi_tcp_dgst_header(struct hash_desc *hash, const void *hdr, size_t hdrlen, - unsigned char digest[ISCSI_DIGEST_SIZE]) -{ - struct scatterlist sg; - - sg_init_one(&sg, hdr, hdrlen); - crypto_hash_digest(hash, &sg, hdrlen, digest); -} - -static inline int -iscsi_tcp_dgst_verify(struct iscsi_tcp_conn *tcp_conn, - struct iscsi_segment *segment) -{ - if (!segment->digest_len) - return 1; - - if (memcmp(segment->recv_digest, segment->digest, - segment->digest_len)) { - debug_scsi("digest mismatch\n"); - return 0; - } - - return 1; -} - -/* - * Helper function to set up segment buffer - */ -static inline void -__iscsi_segment_init(struct iscsi_segment *segment, size_t size, - iscsi_segment_done_fn_t *done, struct hash_desc *hash) -{ - memset(segment, 0, sizeof(*segment)); - segment->total_size = size; - segment->done = done; - - if (hash) { - segment->hash = hash; - crypto_hash_init(hash); - } -} - -static inline void -iscsi_segment_init_linear(struct iscsi_segment *segment, void *data, - size_t size, iscsi_segment_done_fn_t *done, - struct hash_desc *hash) -{ - __iscsi_segment_init(segment, size, done, hash); - segment->data = data; - segment->size = size; -} - -static inline int -iscsi_segment_seek_sg(struct iscsi_segment *segment, - struct scatterlist *sg_list, unsigned int sg_count, - unsigned int offset, size_t size, - iscsi_segment_done_fn_t *done, struct hash_desc *hash) -{ - struct scatterlist *sg; - unsigned int i; - - debug_scsi("iscsi_segment_seek_sg offset %u size %llu\n", - offset, size); - __iscsi_segment_init(segment, size, done, hash); - for_each_sg(sg_list, sg, sg_count, i) { - debug_scsi("sg %d, len %u offset %u\n", i, sg->length, - sg->offset); - if (offset < sg->length) { - iscsi_tcp_segment_init_sg(segment, sg, offset); - return 0; - } - offset -= sg->length; - } - - return ISCSI_ERR_DATA_OFFSET; -} - -/** - * iscsi_tcp_hdr_recv_prep - prep segment for hdr reception - * @tcp_conn: iscsi connection to prep for - * - * This function always passes NULL for the hash argument, because when this - * function is called we do not yet know the final size of the header and want - * to delay the digest processing until we know that. - */ -static void -iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn) -{ - debug_tcp("iscsi_tcp_hdr_recv_prep(%p%s)\n", tcp_conn, - tcp_conn->iscsi_conn->hdrdgst_en ? ", digest enabled" : ""); - iscsi_segment_init_linear(&tcp_conn->in.segment, - tcp_conn->in.hdr_buf, sizeof(struct iscsi_hdr), - iscsi_tcp_hdr_recv_done, NULL); -} - -/* - * Handle incoming reply to any other type of command - */ -static int -iscsi_tcp_data_recv_done(struct iscsi_tcp_conn *tcp_conn, - struct iscsi_segment *segment) -{ - struct iscsi_conn *conn = tcp_conn->iscsi_conn; - int rc = 0; - - if (!iscsi_tcp_dgst_verify(tcp_conn, segment)) - return ISCSI_ERR_DATA_DGST; - - rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, - conn->data, tcp_conn->in.datalen); - if (rc) - return rc; - - iscsi_tcp_hdr_recv_prep(tcp_conn); - return 0; -} - -static void -iscsi_tcp_data_recv_prep(struct iscsi_tcp_conn *tcp_conn) -{ - struct iscsi_conn *conn = tcp_conn->iscsi_conn; - struct hash_desc *rx_hash = NULL; - - if (conn->datadgst_en) - rx_hash = &tcp_conn->rx_hash; - - iscsi_segment_init_linear(&tcp_conn->in.segment, - conn->data, tcp_conn->in.datalen, - iscsi_tcp_data_recv_done, rx_hash); -} - -/* - * must be called with session lock - */ -static void -iscsi_tcp_cleanup_task(struct iscsi_conn *conn, struct iscsi_task *task) -{ - struct iscsi_tcp_task *tcp_task = task->dd_data; - struct iscsi_r2t_info *r2t; - - /* nothing to do for mgmt tasks */ - if (!task->sc) - return; - - /* flush task's r2t queues */ - while (__kfifo_get(tcp_task->r2tqueue, (void*)&r2t, sizeof(void*))) { - __kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t, - sizeof(void*)); - debug_scsi("iscsi_tcp_cleanup_task pending r2t dropped\n"); - } - - r2t = tcp_task->r2t; - if (r2t != NULL) { - __kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t, - sizeof(void*)); - tcp_task->r2t = NULL; - } -} - -/** - * iscsi_data_in - SCSI Data-In Response processing - * @conn: iscsi connection - * @task: scsi command task - **/ -static int -iscsi_data_in(struct iscsi_conn *conn, struct iscsi_task *task) -{ - struct iscsi_tcp_conn *tcp_conn = conn->dd_data; - struct iscsi_tcp_task *tcp_task = task->dd_data; - struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)tcp_conn->in.hdr; - int datasn = be32_to_cpu(rhdr->datasn); - unsigned total_in_length = scsi_in(task->sc)->length; - - iscsi_update_cmdsn(conn->session, (struct iscsi_nopin*)rhdr); - if (tcp_conn->in.datalen == 0) - return 0; - - if (tcp_task->exp_datasn != datasn) { - debug_tcp("%s: task->exp_datasn(%d) != rhdr->datasn(%d)\n", - __func__, tcp_task->exp_datasn, datasn); - return ISCSI_ERR_DATASN; - } - - tcp_task->exp_datasn++; - - tcp_task->data_offset = be32_to_cpu(rhdr->offset); - if (tcp_task->data_offset + tcp_conn->in.datalen > total_in_length) { - debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n", - __func__, tcp_task->data_offset, - tcp_conn->in.datalen, total_in_length); - return ISCSI_ERR_DATA_OFFSET; - } - - conn->datain_pdus_cnt++; - return 0; -} - -/** - * iscsi_solicit_data_init - initialize first Data-Out - * @conn: iscsi connection - * @task: scsi command task - * @r2t: R2T info - * - * Notes: - * Initialize first Data-Out within this R2T sequence and finds - * proper data_offset within this SCSI command. - * - * This function is called with connection lock taken. - **/ -static void -iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_task *task, - struct iscsi_r2t_info *r2t) -{ - struct iscsi_data *hdr; - - hdr = &r2t->dtask.hdr; - memset(hdr, 0, sizeof(struct iscsi_data)); - hdr->ttt = r2t->ttt; - hdr->datasn = cpu_to_be32(r2t->solicit_datasn); - r2t->solicit_datasn++; - hdr->opcode = ISCSI_OP_SCSI_DATA_OUT; - memcpy(hdr->lun, task->hdr->lun, sizeof(hdr->lun)); - hdr->itt = task->hdr->itt; - hdr->exp_statsn = r2t->exp_statsn; - hdr->offset = cpu_to_be32(r2t->data_offset); - if (r2t->data_length > conn->max_xmit_dlength) { - hton24(hdr->dlength, conn->max_xmit_dlength); - r2t->data_count = conn->max_xmit_dlength; - hdr->flags = 0; - } else { - hton24(hdr->dlength, r2t->data_length); - r2t->data_count = r2t->data_length; - hdr->flags = ISCSI_FLAG_CMD_FINAL; - } - conn->dataout_pdus_cnt++; - - r2t->sent = 0; -} - -/** - * iscsi_r2t_rsp - iSCSI R2T Response processing - * @conn: iscsi connection - * @task: scsi command task - **/ -static int -iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_task *task) -{ - struct iscsi_r2t_info *r2t; - struct iscsi_session *session = conn->session; - struct iscsi_tcp_task *tcp_task = task->dd_data; - struct iscsi_tcp_conn *tcp_conn = conn->dd_data; - struct iscsi_r2t_rsp *rhdr = (struct iscsi_r2t_rsp *)tcp_conn->in.hdr; - int r2tsn = be32_to_cpu(rhdr->r2tsn); - int rc; - - if (tcp_conn->in.datalen) { - iscsi_conn_printk(KERN_ERR, conn, - "invalid R2t with datalen %d\n", - tcp_conn->in.datalen); - return ISCSI_ERR_DATALEN; - } - - if (tcp_task->exp_datasn != r2tsn){ - debug_tcp("%s: task->exp_datasn(%d) != rhdr->r2tsn(%d)\n", - __func__, tcp_task->exp_datasn, r2tsn); - return ISCSI_ERR_R2TSN; - } - - /* fill-in new R2T associated with the task */ - iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr); - - if (!task->sc || session->state != ISCSI_STATE_LOGGED_IN) { - iscsi_conn_printk(KERN_INFO, conn, - "dropping R2T itt %d in recovery.\n", - task->itt); - return 0; - } - - rc = __kfifo_get(tcp_task->r2tpool.queue, (void*)&r2t, sizeof(void*)); - BUG_ON(!rc); - - r2t->exp_statsn = rhdr->statsn; - r2t->data_length = be32_to_cpu(rhdr->data_length); - if (r2t->data_length == 0) { - iscsi_conn_printk(KERN_ERR, conn, - "invalid R2T with zero data len\n"); - __kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t, - sizeof(void*)); - return ISCSI_ERR_DATALEN; - } - - if (r2t->data_length > session->max_burst) - debug_scsi("invalid R2T with data len %u and max burst %u." - "Attempting to execute request.\n", - r2t->data_length, session->max_burst); - - r2t->data_offset = be32_to_cpu(rhdr->data_offset); - if (r2t->data_offset + r2t->data_length > scsi_out(task->sc)->length) { - iscsi_conn_printk(KERN_ERR, conn, - "invalid R2T with data len %u at offset %u " - "and total length %d\n", r2t->data_length, - r2t->data_offset, scsi_out(task->sc)->length); - __kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t, - sizeof(void*)); - return ISCSI_ERR_DATALEN; - } - - r2t->ttt = rhdr->ttt; /* no flip */ - r2t->solicit_datasn = 0; - - iscsi_solicit_data_init(conn, task, r2t); - - tcp_task->exp_datasn = r2tsn + 1; - __kfifo_put(tcp_task->r2tqueue, (void*)&r2t, sizeof(void*)); - conn->r2t_pdus_cnt++; - - iscsi_requeue_task(task); - return 0; -} - -/* - * Handle incoming reply to DataIn command - */ -static int -iscsi_tcp_process_data_in(struct iscsi_tcp_conn *tcp_conn, - struct iscsi_segment *segment) -{ - struct iscsi_conn *conn = tcp_conn->iscsi_conn; - struct iscsi_hdr *hdr = tcp_conn->in.hdr; - int rc; - - if (!iscsi_tcp_dgst_verify(tcp_conn, segment)) - return ISCSI_ERR_DATA_DGST; - - /* check for non-exceptional status */ - if (hdr->flags & ISCSI_FLAG_DATA_STATUS) { - rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0); - if (rc) - return rc; - } - - iscsi_tcp_hdr_recv_prep(tcp_conn); - return 0; -} - -/** - * iscsi_tcp_hdr_dissect - process PDU header - * @conn: iSCSI connection - * @hdr: PDU header - * - * This function analyzes the header of the PDU received, - * and performs several sanity checks. If the PDU is accompanied - * by data, the receive buffer is set up to copy the incoming data - * to the correct location. - */ -static int -iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr) -{ - int rc = 0, opcode, ahslen; - struct iscsi_tcp_conn *tcp_conn = conn->dd_data; - struct iscsi_task *task; - - /* verify PDU length */ - tcp_conn->in.datalen = ntoh24(hdr->dlength); - if (tcp_conn->in.datalen > conn->max_recv_dlength) { - iscsi_conn_printk(KERN_ERR, conn, - "iscsi_tcp: datalen %d > %d\n", - tcp_conn->in.datalen, conn->max_recv_dlength); - return ISCSI_ERR_DATALEN; - } - - /* Additional header segments. So far, we don't - * process additional headers. - */ - ahslen = hdr->hlength << 2; - - opcode = hdr->opcode & ISCSI_OPCODE_MASK; - /* verify itt (itt encoding: age+cid+itt) */ - rc = iscsi_verify_itt(conn, hdr->itt); - if (rc) - return rc; - - debug_tcp("opcode 0x%x ahslen %d datalen %d\n", - opcode, ahslen, tcp_conn->in.datalen); - - switch(opcode) { - case ISCSI_OP_SCSI_DATA_IN: - spin_lock(&conn->session->lock); - task = iscsi_itt_to_ctask(conn, hdr->itt); - if (!task) - rc = ISCSI_ERR_BAD_ITT; - else - rc = iscsi_data_in(conn, task); - if (rc) { - spin_unlock(&conn->session->lock); - break; - } - - if (tcp_conn->in.datalen) { - struct iscsi_tcp_task *tcp_task = task->dd_data; - struct hash_desc *rx_hash = NULL; - struct scsi_data_buffer *sdb = scsi_in(task->sc); - - /* - * Setup copy of Data-In into the Scsi_Cmnd - * Scatterlist case: - * We set up the iscsi_segment to point to the next - * scatterlist entry to copy to. As we go along, - * we move on to the next scatterlist entry and - * update the digest per-entry. - */ - if (conn->datadgst_en) - rx_hash = &tcp_conn->rx_hash; - - debug_tcp("iscsi_tcp_begin_data_in(%p, offset=%d, " - "datalen=%d)\n", tcp_conn, - tcp_task->data_offset, - tcp_conn->in.datalen); - rc = iscsi_segment_seek_sg(&tcp_conn->in.segment, - sdb->table.sgl, - sdb->table.nents, - tcp_task->data_offset, - tcp_conn->in.datalen, - iscsi_tcp_process_data_in, - rx_hash); - spin_unlock(&conn->session->lock); - return rc; - } - rc = __iscsi_complete_pdu(conn, hdr, NULL, 0); - spin_unlock(&conn->session->lock); - break; - case ISCSI_OP_SCSI_CMD_RSP: - if (tcp_conn->in.datalen) { - iscsi_tcp_data_recv_prep(tcp_conn); - return 0; - } - rc = iscsi_complete_pdu(conn, hdr, NULL, 0); - break; - case ISCSI_OP_R2T: - spin_lock(&conn->session->lock); - task = iscsi_itt_to_ctask(conn, hdr->itt); - if (!task) - rc = ISCSI_ERR_BAD_ITT; - else if (ahslen) - rc = ISCSI_ERR_AHSLEN; - else if (task->sc->sc_data_direction == DMA_TO_DEVICE) - rc = iscsi_r2t_rsp(conn, task); - else - rc = ISCSI_ERR_PROTO; - spin_unlock(&conn->session->lock); - break; - case ISCSI_OP_LOGIN_RSP: - case ISCSI_OP_TEXT_RSP: - case ISCSI_OP_REJECT: - case ISCSI_OP_ASYNC_EVENT: - /* - * It is possible that we could get a PDU with a buffer larger - * than 8K, but there are no targets that currently do this. - * For now we fail until we find a vendor that needs it - */ - if (ISCSI_DEF_MAX_RECV_SEG_LEN < tcp_conn->in.datalen) { - iscsi_conn_printk(KERN_ERR, conn, - "iscsi_tcp: received buffer of " - "len %u but conn buffer is only %u " - "(opcode %0x)\n", - tcp_conn->in.datalen, - ISCSI_DEF_MAX_RECV_SEG_LEN, opcode); - rc = ISCSI_ERR_PROTO; - break; - } - - /* If there's data coming in with the response, - * receive it to the connection's buffer. - */ - if (tcp_conn->in.datalen) { - iscsi_tcp_data_recv_prep(tcp_conn); - return 0; - } - /* fall through */ - case ISCSI_OP_LOGOUT_RSP: - case ISCSI_OP_NOOP_IN: - case ISCSI_OP_SCSI_TMFUNC_RSP: - rc = iscsi_complete_pdu(conn, hdr, NULL, 0); - break; - default: - rc = ISCSI_ERR_BAD_OPCODE; - break; - } - - if (rc == 0) { - /* Anything that comes with data should have - * been handled above. */ - if (tcp_conn->in.datalen) - return ISCSI_ERR_PROTO; - iscsi_tcp_hdr_recv_prep(tcp_conn); - } - - return rc; -} - -/** - * iscsi_tcp_hdr_recv_done - process PDU header - * - * This is the callback invoked when the PDU header has - * been received. If the header is followed by additional - * header segments, we go back for more data. - */ -static int -iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn, - struct iscsi_segment *segment) -{ - struct iscsi_conn *conn = tcp_conn->iscsi_conn; - struct iscsi_hdr *hdr; - - /* Check if there are additional header segments - * *prior* to computing the digest, because we - * may need to go back to the caller for more. - */ - hdr = (struct iscsi_hdr *) tcp_conn->in.hdr_buf; - if (segment->copied == sizeof(struct iscsi_hdr) && hdr->hlength) { - /* Bump the header length - the caller will - * just loop around and get the AHS for us, and - * call again. */ - unsigned int ahslen = hdr->hlength << 2; - - /* Make sure we don't overflow */ - if (sizeof(*hdr) + ahslen > sizeof(tcp_conn->in.hdr_buf)) - return ISCSI_ERR_AHSLEN; - - segment->total_size += ahslen; - segment->size += ahslen; - return 0; - } - - /* We're done processing the header. See if we're doing - * header digests; if so, set up the recv_digest buffer - * and go back for more. */ - if (conn->hdrdgst_en) { - if (segment->digest_len == 0) { - iscsi_tcp_segment_splice_digest(segment, - segment->recv_digest); - return 0; - } - iscsi_tcp_dgst_header(&tcp_conn->rx_hash, hdr, - segment->total_copied - ISCSI_DIGEST_SIZE, - segment->digest); - - if (!iscsi_tcp_dgst_verify(tcp_conn, segment)) - return ISCSI_ERR_HDR_DGST; - } - - tcp_conn->in.hdr = hdr; - return iscsi_tcp_hdr_dissect(conn, hdr); -} - -/** - * iscsi_tcp_recv - TCP receive in sendfile fashion + * iscsi_sw_tcp_recv - TCP receive in sendfile fashion * @rd_desc: read descriptor * @skb: socket buffer * @offset: offset in skb * @len: skb->len - offset - **/ -static int -iscsi_tcp_recv(read_descriptor_t *rd_desc, struct sk_buff *skb, - unsigned int offset, size_t len) + */ +static int iscsi_sw_tcp_recv(read_descriptor_t *rd_desc, struct sk_buff *skb, + unsigned int offset, size_t len) { struct iscsi_conn *conn = rd_desc->arg.data; - struct iscsi_tcp_conn *tcp_conn = conn->dd_data; - struct iscsi_segment *segment = &tcp_conn->in.segment; - struct skb_seq_state seq; - unsigned int consumed = 0; - int rc = 0; + unsigned int consumed, total_consumed = 0; + int status; debug_tcp("in %d bytes\n", skb->len - offset); - if (unlikely(conn->suspend_rx)) { - debug_tcp("conn %d Rx suspended!\n", conn->id); - return 0; - } - - skb_prepare_seq_read(skb, offset, skb->len, &seq); - while (1) { - unsigned int avail; - const u8 *ptr; + do { + status = 0; + consumed = iscsi_tcp_recv_skb(conn, skb, offset, 0, &status); + offset += consumed; + total_consumed += consumed; + } while (consumed != 0 && status != ISCSI_TCP_SKB_DONE); - avail = skb_seq_read(consumed, &ptr, &seq); - if (avail == 0) { - debug_tcp("no more data avail. Consumed %d\n", - consumed); - break; - } - BUG_ON(segment->copied >= segment->size); - - debug_tcp("skb %p ptr=%p avail=%u\n", skb, ptr, avail); - rc = iscsi_tcp_segment_recv(tcp_conn, segment, ptr, avail); - BUG_ON(rc == 0); - consumed += rc; - - if (segment->total_copied >= segment->total_size) { - debug_tcp("segment done\n"); - rc = segment->done(tcp_conn, segment); - if (rc != 0) { - skb_abort_seq_read(&seq); - goto error; - } - - /* The done() functions sets up the - * next segment. */ - } - } - skb_abort_seq_read(&seq); - conn->rxdata_octets += consumed; - return consumed; - -error: - debug_tcp("Error receiving PDU, errno=%d\n", rc); - iscsi_conn_failure(conn, rc); - return 0; + debug_tcp("read %d bytes status %d\n", skb->len - offset, status); + return total_consumed; } -static void -iscsi_tcp_data_ready(struct sock *sk, int flag) +static void iscsi_sw_tcp_data_ready(struct sock *sk, int flag) { struct iscsi_conn *conn = sk->sk_user_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; @@ -1000,7 +106,7 @@ iscsi_tcp_data_ready(struct sock *sk, int flag) */ rd_desc.arg.data = conn; rd_desc.count = 1; - tcp_read_sock(sk, &rd_desc, iscsi_tcp_recv); + tcp_read_sock(sk, &rd_desc, iscsi_sw_tcp_recv); read_unlock(&sk->sk_callback_lock); @@ -1009,10 +115,10 @@ iscsi_tcp_data_ready(struct sock *sk, int flag) iscsi_tcp_segment_unmap(&tcp_conn->in.segment); } -static void -iscsi_tcp_state_change(struct sock *sk) +static void iscsi_sw_tcp_state_change(struct sock *sk) { struct iscsi_tcp_conn *tcp_conn; + struct iscsi_sw_tcp_conn *tcp_sw_conn; struct iscsi_conn *conn; struct iscsi_session *session; void (*old_state_change)(struct sock *); @@ -1030,7 +136,8 @@ iscsi_tcp_state_change(struct sock *sk) } tcp_conn = conn->dd_data; - old_state_change = tcp_conn->old_state_change; + tcp_sw_conn = tcp_conn->dd_data; + old_state_change = tcp_sw_conn->old_state_change; read_unlock(&sk->sk_callback_lock); @@ -1041,63 +148,123 @@ iscsi_tcp_state_change(struct sock *sk) * iscsi_write_space - Called when more output buffer space is available * @sk: socket space is available for **/ -static void -iscsi_write_space(struct sock *sk) +static void iscsi_sw_tcp_write_space(struct sock *sk) { struct iscsi_conn *conn = (struct iscsi_conn*)sk->sk_user_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; - tcp_conn->old_write_space(sk); + tcp_sw_conn->old_write_space(sk); debug_tcp("iscsi_write_space: cid %d\n", conn->id); scsi_queue_work(conn->session->host, &conn->xmitwork); } -static void -iscsi_conn_set_callbacks(struct iscsi_conn *conn) +static void iscsi_sw_tcp_conn_set_callbacks(struct iscsi_conn *conn) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; - struct sock *sk = tcp_conn->sock->sk; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; + struct sock *sk = tcp_sw_conn->sock->sk; /* assign new callbacks */ write_lock_bh(&sk->sk_callback_lock); sk->sk_user_data = conn; - tcp_conn->old_data_ready = sk->sk_data_ready; - tcp_conn->old_state_change = sk->sk_state_change; - tcp_conn->old_write_space = sk->sk_write_space; - sk->sk_data_ready = iscsi_tcp_data_ready; - sk->sk_state_change = iscsi_tcp_state_change; - sk->sk_write_space = iscsi_write_space; + tcp_sw_conn->old_data_ready = sk->sk_data_ready; + tcp_sw_conn->old_state_change = sk->sk_state_change; + tcp_sw_conn->old_write_space = sk->sk_write_space; + sk->sk_data_ready = iscsi_sw_tcp_data_ready; + sk->sk_state_change = iscsi_sw_tcp_state_change; + sk->sk_write_space = iscsi_sw_tcp_write_space; write_unlock_bh(&sk->sk_callback_lock); } static void -iscsi_conn_restore_callbacks(struct iscsi_tcp_conn *tcp_conn) +iscsi_sw_tcp_conn_restore_callbacks(struct iscsi_sw_tcp_conn *tcp_sw_conn) { - struct sock *sk = tcp_conn->sock->sk; + struct sock *sk = tcp_sw_conn->sock->sk; /* restore socket callbacks, see also: iscsi_conn_set_callbacks() */ write_lock_bh(&sk->sk_callback_lock); sk->sk_user_data = NULL; - sk->sk_data_ready = tcp_conn->old_data_ready; - sk->sk_state_change = tcp_conn->old_state_change; - sk->sk_write_space = tcp_conn->old_write_space; + sk->sk_data_ready = tcp_sw_conn->old_data_ready; + sk->sk_state_change = tcp_sw_conn->old_state_change; + sk->sk_write_space = tcp_sw_conn->old_write_space; sk->sk_no_check = 0; write_unlock_bh(&sk->sk_callback_lock); } /** - * iscsi_xmit - TCP transmit + * iscsi_sw_tcp_xmit_segment - transmit segment + * @tcp_conn: the iSCSI TCP connection + * @segment: the buffer to transmnit + * + * This function transmits as much of the buffer as + * the network layer will accept, and returns the number of + * bytes transmitted. + * + * If CRC hashing is enabled, the function will compute the + * hash as it goes. When the entire segment has been transmitted, + * it will retrieve the hash value and send it as well. + */ +static int iscsi_sw_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment) +{ + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; + struct socket *sk = tcp_sw_conn->sock; + unsigned int copied = 0; + int r = 0; + + while (!iscsi_tcp_segment_done(tcp_conn, segment, 0, r)) { + struct scatterlist *sg; + unsigned int offset, copy; + int flags = 0; + + r = 0; + offset = segment->copied; + copy = segment->size - offset; + + if (segment->total_copied + segment->size < segment->total_size) + flags |= MSG_MORE; + + /* Use sendpage if we can; else fall back to sendmsg */ + if (!segment->data) { + sg = segment->sg; + offset += segment->sg_offset + sg->offset; + r = tcp_sw_conn->sendpage(sk, sg_page(sg), offset, + copy, flags); + } else { + struct msghdr msg = { .msg_flags = flags }; + struct kvec iov = { + .iov_base = segment->data + offset, + .iov_len = copy + }; + + r = kernel_sendmsg(sk, &msg, &iov, 1, copy); + } + + if (r < 0) { + iscsi_tcp_segment_unmap(segment); + if (copied || r == -EAGAIN) + break; + return r; + } + copied += r; + } + return copied; +} + +/** + * iscsi_sw_tcp_xmit - TCP transmit **/ -static int -iscsi_xmit(struct iscsi_conn *conn) +static int iscsi_sw_tcp_xmit(struct iscsi_conn *conn) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; - struct iscsi_segment *segment = &tcp_conn->out.segment; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; + struct iscsi_segment *segment = &tcp_sw_conn->out.segment; unsigned int consumed = 0; int rc = 0; while (1) { - rc = iscsi_tcp_xmit_segment(tcp_conn, segment); + rc = iscsi_sw_tcp_xmit_segment(tcp_conn, segment); if (rc < 0) { rc = ISCSI_ERR_XMIT_FAILED; goto error; @@ -1132,22 +299,22 @@ error: /** * iscsi_tcp_xmit_qlen - return the number of bytes queued for xmit */ -static inline int -iscsi_tcp_xmit_qlen(struct iscsi_conn *conn) +static inline int iscsi_sw_tcp_xmit_qlen(struct iscsi_conn *conn) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; - struct iscsi_segment *segment = &tcp_conn->out.segment; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; + struct iscsi_segment *segment = &tcp_sw_conn->out.segment; return segment->total_copied - segment->total_size; } -static inline int -iscsi_tcp_flush(struct iscsi_conn *conn) +static int iscsi_sw_tcp_pdu_xmit(struct iscsi_task *task) { + struct iscsi_conn *conn = task->conn; int rc; - while (iscsi_tcp_xmit_qlen(conn)) { - rc = iscsi_xmit(conn); + while (iscsi_sw_tcp_xmit_qlen(conn)) { + rc = iscsi_sw_tcp_xmit(conn); if (rc == 0) return -EAGAIN; if (rc < 0) @@ -1161,27 +328,31 @@ iscsi_tcp_flush(struct iscsi_conn *conn) * This is called when we're done sending the header. * Simply copy the data_segment to the send segment, and return. */ -static int -iscsi_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn, - struct iscsi_segment *segment) +static int iscsi_sw_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment) { - tcp_conn->out.segment = tcp_conn->out.data_segment; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; + + tcp_sw_conn->out.segment = tcp_sw_conn->out.data_segment; debug_tcp("Header done. Next segment size %u total_size %u\n", - tcp_conn->out.segment.size, tcp_conn->out.segment.total_size); + tcp_sw_conn->out.segment.size, + tcp_sw_conn->out.segment.total_size); return 0; } -static void -iscsi_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen) +static void iscsi_sw_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, + size_t hdrlen) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; debug_tcp("%s(%p%s)\n", __func__, tcp_conn, conn->hdrdgst_en? ", digest enabled" : ""); /* Clear the data segment - needs to be filled in by the * caller using iscsi_tcp_send_data_prep() */ - memset(&tcp_conn->out.data_segment, 0, sizeof(struct iscsi_segment)); + memset(&tcp_sw_conn->out.data_segment, 0, + sizeof(struct iscsi_segment)); /* If header digest is enabled, compute the CRC and * place the digest into the same buffer. We make @@ -1189,7 +360,7 @@ iscsi_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen) * sufficient room. */ if (conn->hdrdgst_en) { - iscsi_tcp_dgst_header(&tcp_conn->tx_hash, hdr, hdrlen, + iscsi_tcp_dgst_header(&tcp_sw_conn->tx_hash, hdr, hdrlen, hdr + hdrlen); hdrlen += ISCSI_DIGEST_SIZE; } @@ -1197,10 +368,10 @@ iscsi_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen) /* Remember header pointer for later, when we need * to decide whether there's a payload to go along * with the header. */ - tcp_conn->out.hdr = hdr; + tcp_sw_conn->out.hdr = hdr; - iscsi_segment_init_linear(&tcp_conn->out.segment, hdr, hdrlen, - iscsi_tcp_send_hdr_done, NULL); + iscsi_segment_init_linear(&tcp_sw_conn->out.segment, hdr, hdrlen, + iscsi_sw_tcp_send_hdr_done, NULL); } /* @@ -1209,11 +380,12 @@ iscsi_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen) * of by the iscsi_segment routines. */ static int -iscsi_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg, - unsigned int count, unsigned int offset, - unsigned int len) +iscsi_sw_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg, + unsigned int count, unsigned int offset, + unsigned int len) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct hash_desc *tx_hash = NULL; unsigned int hdr_spec_len; @@ -1223,22 +395,23 @@ iscsi_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg, /* Make sure the datalen matches what the caller said he would send. */ - hdr_spec_len = ntoh24(tcp_conn->out.hdr->dlength); + hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength); WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len)); if (conn->datadgst_en) - tx_hash = &tcp_conn->tx_hash; + tx_hash = &tcp_sw_conn->tx_hash; - return iscsi_segment_seek_sg(&tcp_conn->out.data_segment, - sg, count, offset, len, - NULL, tx_hash); + return iscsi_segment_seek_sg(&tcp_sw_conn->out.data_segment, + sg, count, offset, len, + NULL, tx_hash); } static void -iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data, +iscsi_sw_tcp_send_linear_data_prep(struct iscsi_conn *conn, void *data, size_t len) { struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct hash_desc *tx_hash = NULL; unsigned int hdr_spec_len; @@ -1247,341 +420,160 @@ iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data, /* Make sure the datalen matches what the caller said he would send. */ - hdr_spec_len = ntoh24(tcp_conn->out.hdr->dlength); + hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength); WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len)); if (conn->datadgst_en) - tx_hash = &tcp_conn->tx_hash; + tx_hash = &tcp_sw_conn->tx_hash; - iscsi_segment_init_linear(&tcp_conn->out.data_segment, + iscsi_segment_init_linear(&tcp_sw_conn->out.data_segment, data, len, NULL, tx_hash); } -/** - * iscsi_solicit_data_cont - initialize next Data-Out - * @conn: iscsi connection - * @task: scsi command task - * @r2t: R2T info - * @left: bytes left to transfer - * - * Notes: - * Initialize next Data-Out within this R2T sequence and continue - * to process next Scatter-Gather element(if any) of this SCSI command. - * - * Called under connection lock. - **/ -static int -iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_task *task, - struct iscsi_r2t_info *r2t) +static int iscsi_sw_tcp_pdu_init(struct iscsi_task *task, + unsigned int offset, unsigned int count) { - struct iscsi_data *hdr; - int new_offset, left; - - BUG_ON(r2t->data_length - r2t->sent < 0); - left = r2t->data_length - r2t->sent; - if (left == 0) - return 0; - - hdr = &r2t->dtask.hdr; - memset(hdr, 0, sizeof(struct iscsi_data)); - hdr->ttt = r2t->ttt; - hdr->datasn = cpu_to_be32(r2t->solicit_datasn); - r2t->solicit_datasn++; - hdr->opcode = ISCSI_OP_SCSI_DATA_OUT; - memcpy(hdr->lun, task->hdr->lun, sizeof(hdr->lun)); - hdr->itt = task->hdr->itt; - hdr->exp_statsn = r2t->exp_statsn; - new_offset = r2t->data_offset + r2t->sent; - hdr->offset = cpu_to_be32(new_offset); - if (left > conn->max_xmit_dlength) { - hton24(hdr->dlength, conn->max_xmit_dlength); - r2t->data_count = conn->max_xmit_dlength; - } else { - hton24(hdr->dlength, left); - r2t->data_count = left; - hdr->flags = ISCSI_FLAG_CMD_FINAL; - } - - conn->dataout_pdus_cnt++; - return 1; -} - -/** - * iscsi_tcp_task - Initialize iSCSI SCSI_READ or SCSI_WRITE commands - * @conn: iscsi connection - * @task: scsi command task - * @sc: scsi command - **/ -static int -iscsi_tcp_task_init(struct iscsi_task *task) -{ - struct iscsi_tcp_task *tcp_task = task->dd_data; struct iscsi_conn *conn = task->conn; - struct scsi_cmnd *sc = task->sc; - int err; + int err = 0; - if (!sc) { - /* - * mgmt tasks do not have a scatterlist since they come - * in from the iscsi interface. - */ - debug_scsi("mtask deq [cid %d itt 0x%x]\n", conn->id, - task->itt); - - /* Prepare PDU, optionally w/ immediate data */ - iscsi_tcp_send_hdr_prep(conn, task->hdr, sizeof(*task->hdr)); - - /* If we have immediate data, attach a payload */ - if (task->data_count) - iscsi_tcp_send_linear_data_prepare(conn, task->data, - task->data_count); - return 0; - } + iscsi_sw_tcp_send_hdr_prep(conn, task->hdr, task->hdr_len); - BUG_ON(__kfifo_len(tcp_task->r2tqueue)); - tcp_task->sent = 0; - tcp_task->exp_datasn = 0; + if (!count) + return 0; - /* Prepare PDU, optionally w/ immediate data */ - debug_scsi("task deq [cid %d itt 0x%x imm %d unsol %d]\n", - conn->id, task->itt, task->imm_count, - task->unsol_count); - iscsi_tcp_send_hdr_prep(conn, task->hdr, task->hdr_len); + if (!task->sc) + iscsi_sw_tcp_send_linear_data_prep(conn, task->data, count); + else { + struct scsi_data_buffer *sdb = scsi_out(task->sc); - if (!task->imm_count) - return 0; + err = iscsi_sw_tcp_send_data_prep(conn, sdb->table.sgl, + sdb->table.nents, offset, + count); + } - /* If we have immediate data, attach a payload */ - err = iscsi_tcp_send_data_prep(conn, scsi_out(sc)->table.sgl, - scsi_out(sc)->table.nents, - 0, task->imm_count); - if (err) - return err; - tcp_task->sent += task->imm_count; - task->imm_count = 0; + if (err) { + iscsi_conn_failure(conn, err); + return -EIO; + } return 0; } -/* - * iscsi_tcp_task_xmit - xmit normal PDU task - * @task: iscsi command task - * - * We're expected to return 0 when everything was transmitted succesfully, - * -EAGAIN if there's still data in the queue, or != 0 for any other kind - * of error. - */ -static int -iscsi_tcp_task_xmit(struct iscsi_task *task) +static int iscsi_sw_tcp_pdu_alloc(struct iscsi_task *task, uint8_t opcode) { - struct iscsi_conn *conn = task->conn; struct iscsi_tcp_task *tcp_task = task->dd_data; - struct scsi_cmnd *sc = task->sc; - struct scsi_data_buffer *sdb; - int rc = 0; - -flush: - /* Flush any pending data first. */ - rc = iscsi_tcp_flush(conn); - if (rc < 0) - return rc; - - /* mgmt command */ - if (!sc) { - if (task->hdr->itt == RESERVED_ITT) - iscsi_put_task(task); - return 0; - } - - /* Are we done already? */ - if (sc->sc_data_direction != DMA_TO_DEVICE) - return 0; - sdb = scsi_out(sc); - if (task->unsol_count != 0) { - struct iscsi_data *hdr = &tcp_task->unsol_dtask.hdr; - - /* Prepare a header for the unsolicited PDU. - * The amount of data we want to send will be - * in task->data_count. - * FIXME: return the data count instead. - */ - iscsi_prep_unsolicit_data_pdu(task, hdr); - - debug_tcp("unsol dout [itt 0x%x doff %d dlen %d]\n", - task->itt, tcp_task->sent, task->data_count); - - iscsi_tcp_send_hdr_prep(conn, hdr, sizeof(*hdr)); - rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl, - sdb->table.nents, tcp_task->sent, - task->data_count); - if (rc) - goto fail; - tcp_task->sent += task->data_count; - task->unsol_count -= task->data_count; - goto flush; - } else { - struct iscsi_session *session = conn->session; - struct iscsi_r2t_info *r2t; - - /* All unsolicited PDUs sent. Check for solicited PDUs. - */ - spin_lock_bh(&session->lock); - r2t = tcp_task->r2t; - if (r2t != NULL) { - /* Continue with this R2T? */ - if (!iscsi_solicit_data_cont(conn, task, r2t)) { - debug_scsi(" done with r2t %p\n", r2t); - - __kfifo_put(tcp_task->r2tpool.queue, - (void*)&r2t, sizeof(void*)); - tcp_task->r2t = r2t = NULL; - } - } - - if (r2t == NULL) { - __kfifo_get(tcp_task->r2tqueue, (void*)&tcp_task->r2t, - sizeof(void*)); - r2t = tcp_task->r2t; - } - spin_unlock_bh(&session->lock); - - /* Waiting for more R2Ts to arrive. */ - if (r2t == NULL) { - debug_tcp("no R2Ts yet\n"); - return 0; - } - - debug_scsi("sol dout %p [dsn %d itt 0x%x doff %d dlen %d]\n", - r2t, r2t->solicit_datasn - 1, task->itt, - r2t->data_offset + r2t->sent, r2t->data_count); - - iscsi_tcp_send_hdr_prep(conn, &r2t->dtask.hdr, - sizeof(struct iscsi_hdr)); - - rc = iscsi_tcp_send_data_prep(conn, sdb->table.sgl, - sdb->table.nents, - r2t->data_offset + r2t->sent, - r2t->data_count); - if (rc) - goto fail; - tcp_task->sent += r2t->data_count; - r2t->sent += r2t->data_count; - goto flush; - } + task->hdr = task->dd_data + sizeof(*tcp_task); + task->hdr_max = sizeof(struct iscsi_sw_tcp_hdrbuf) - ISCSI_DIGEST_SIZE; return 0; -fail: - iscsi_conn_failure(conn, rc); - return -EIO; } static struct iscsi_cls_conn * -iscsi_tcp_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx) +iscsi_sw_tcp_conn_create(struct iscsi_cls_session *cls_session, + uint32_t conn_idx) { struct iscsi_conn *conn; struct iscsi_cls_conn *cls_conn; struct iscsi_tcp_conn *tcp_conn; + struct iscsi_sw_tcp_conn *tcp_sw_conn; - cls_conn = iscsi_conn_setup(cls_session, sizeof(*tcp_conn), conn_idx); + cls_conn = iscsi_tcp_conn_setup(cls_session, sizeof(*tcp_sw_conn), + conn_idx); if (!cls_conn) return NULL; conn = cls_conn->dd_data; - /* - * due to strange issues with iser these are not set - * in iscsi_conn_setup - */ - conn->max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; - tcp_conn = conn->dd_data; - tcp_conn->iscsi_conn = conn; + tcp_sw_conn = tcp_conn->dd_data; - tcp_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0, - CRYPTO_ALG_ASYNC); - tcp_conn->tx_hash.flags = 0; - if (IS_ERR(tcp_conn->tx_hash.tfm)) + tcp_sw_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0, + CRYPTO_ALG_ASYNC); + tcp_sw_conn->tx_hash.flags = 0; + if (IS_ERR(tcp_sw_conn->tx_hash.tfm)) goto free_conn; - tcp_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0, - CRYPTO_ALG_ASYNC); - tcp_conn->rx_hash.flags = 0; - if (IS_ERR(tcp_conn->rx_hash.tfm)) + tcp_sw_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0, + CRYPTO_ALG_ASYNC); + tcp_sw_conn->rx_hash.flags = 0; + if (IS_ERR(tcp_sw_conn->rx_hash.tfm)) goto free_tx_tfm; + tcp_conn->rx_hash = &tcp_sw_conn->rx_hash; return cls_conn; free_tx_tfm: - crypto_free_hash(tcp_conn->tx_hash.tfm); + crypto_free_hash(tcp_sw_conn->tx_hash.tfm); free_conn: iscsi_conn_printk(KERN_ERR, conn, "Could not create connection due to crc32c " "loading error. Make sure the crc32c " "module is built as a module or into the " "kernel\n"); - iscsi_conn_teardown(cls_conn); + iscsi_tcp_conn_teardown(cls_conn); return NULL; } -static void -iscsi_tcp_release_conn(struct iscsi_conn *conn) +static void iscsi_sw_tcp_release_conn(struct iscsi_conn *conn) { struct iscsi_session *session = conn->session; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; - struct socket *sock = tcp_conn->sock; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; + struct socket *sock = tcp_sw_conn->sock; if (!sock) return; sock_hold(sock->sk); - iscsi_conn_restore_callbacks(tcp_conn); + iscsi_sw_tcp_conn_restore_callbacks(tcp_sw_conn); sock_put(sock->sk); spin_lock_bh(&session->lock); - tcp_conn->sock = NULL; + tcp_sw_conn->sock = NULL; spin_unlock_bh(&session->lock); sockfd_put(sock); } -static void -iscsi_tcp_conn_destroy(struct iscsi_cls_conn *cls_conn) +static void iscsi_sw_tcp_conn_destroy(struct iscsi_cls_conn *cls_conn) { struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; - iscsi_tcp_release_conn(conn); + iscsi_sw_tcp_release_conn(conn); - if (tcp_conn->tx_hash.tfm) - crypto_free_hash(tcp_conn->tx_hash.tfm); - if (tcp_conn->rx_hash.tfm) - crypto_free_hash(tcp_conn->rx_hash.tfm); + if (tcp_sw_conn->tx_hash.tfm) + crypto_free_hash(tcp_sw_conn->tx_hash.tfm); + if (tcp_sw_conn->rx_hash.tfm) + crypto_free_hash(tcp_sw_conn->rx_hash.tfm); - iscsi_conn_teardown(cls_conn); + iscsi_tcp_conn_teardown(cls_conn); } -static void -iscsi_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag) +static void iscsi_sw_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag) { struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; /* userspace may have goofed up and not bound us */ - if (!tcp_conn->sock) + if (!tcp_sw_conn->sock) return; /* * Make sure our recv side is stopped. * Older tools called conn stop before ep_disconnect * so IO could still be coming in. */ - write_lock_bh(&tcp_conn->sock->sk->sk_callback_lock); + write_lock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock); set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx); - write_unlock_bh(&tcp_conn->sock->sk->sk_callback_lock); + write_unlock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock); iscsi_conn_stop(cls_conn, flag); - iscsi_tcp_release_conn(conn); + iscsi_sw_tcp_release_conn(conn); } -static int iscsi_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock, - char *buf, int *port, - int (*getname)(struct socket *, struct sockaddr *, - int *addrlen)) +static int iscsi_sw_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock, + char *buf, int *port, + int (*getname)(struct socket *, + struct sockaddr *, + int *addrlen)) { struct sockaddr_storage *addr; struct sockaddr_in6 *sin6; @@ -1601,14 +593,14 @@ static int iscsi_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock, case AF_INET: sin = (struct sockaddr_in *)addr; spin_lock_bh(&conn->session->lock); - sprintf(buf, NIPQUAD_FMT, NIPQUAD(sin->sin_addr.s_addr)); + sprintf(buf, "%pI4", &sin->sin_addr.s_addr); *port = be16_to_cpu(sin->sin_port); spin_unlock_bh(&conn->session->lock); break; case AF_INET6: sin6 = (struct sockaddr_in6 *)addr; spin_lock_bh(&conn->session->lock); - sprintf(buf, NIP6_FMT, NIP6(sin6->sin6_addr)); + sprintf(buf, "%pI6", &sin6->sin6_addr); *port = be16_to_cpu(sin6->sin6_port); spin_unlock_bh(&conn->session->lock); break; @@ -1619,14 +611,15 @@ free_addr: } static int -iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session, - struct iscsi_cls_conn *cls_conn, uint64_t transport_eph, - int is_leading) +iscsi_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session, + struct iscsi_cls_conn *cls_conn, uint64_t transport_eph, + int is_leading) { struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); struct iscsi_host *ihost = shost_priv(shost); struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; struct sock *sk; struct socket *sock; int err; @@ -1643,13 +636,13 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session, * userspace may still want to query the values since we will * be using them for the reconnect */ - err = iscsi_tcp_get_addr(conn, sock, conn->portal_address, - &conn->portal_port, kernel_getpeername); + err = iscsi_sw_tcp_get_addr(conn, sock, conn->portal_address, + &conn->portal_port, kernel_getpeername); if (err) goto free_socket; - err = iscsi_tcp_get_addr(conn, sock, ihost->local_address, - &ihost->local_port, kernel_getsockname); + err = iscsi_sw_tcp_get_addr(conn, sock, ihost->local_address, + &ihost->local_port, kernel_getsockname); if (err) goto free_socket; @@ -1658,7 +651,7 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session, goto free_socket; /* bind iSCSI connection and socket */ - tcp_conn->sock = sock; + tcp_sw_conn->sock = sock; /* setup Socket parameters */ sk = sock->sk; @@ -1666,8 +659,8 @@ iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session, sk->sk_sndtimeo = 15 * HZ; /* FIXME: make it configurable */ sk->sk_allocation = GFP_ATOMIC; - iscsi_conn_set_callbacks(conn); - tcp_conn->sendpage = tcp_conn->sock->ops->sendpage; + iscsi_sw_tcp_conn_set_callbacks(conn); + tcp_sw_conn->sendpage = tcp_sw_conn->sock->ops->sendpage; /* * set receive state machine into initial state */ @@ -1679,74 +672,14 @@ free_socket: return err; } -static int -iscsi_r2tpool_alloc(struct iscsi_session *session) -{ - int i; - int cmd_i; - - /* - * initialize per-task: R2T pool and xmit queue - */ - for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { - struct iscsi_task *task = session->cmds[cmd_i]; - struct iscsi_tcp_task *tcp_task = task->dd_data; - - /* - * pre-allocated x4 as much r2ts to handle race when - * target acks DataOut faster than we data_xmit() queues - * could replenish r2tqueue. - */ - - /* R2T pool */ - if (iscsi_pool_init(&tcp_task->r2tpool, session->max_r2t * 4, NULL, - sizeof(struct iscsi_r2t_info))) { - goto r2t_alloc_fail; - } - - /* R2T xmit queue */ - tcp_task->r2tqueue = kfifo_alloc( - session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL); - if (tcp_task->r2tqueue == ERR_PTR(-ENOMEM)) { - iscsi_pool_free(&tcp_task->r2tpool); - goto r2t_alloc_fail; - } - } - - return 0; - -r2t_alloc_fail: - for (i = 0; i < cmd_i; i++) { - struct iscsi_task *task = session->cmds[i]; - struct iscsi_tcp_task *tcp_task = task->dd_data; - - kfifo_free(tcp_task->r2tqueue); - iscsi_pool_free(&tcp_task->r2tpool); - } - return -ENOMEM; -} - -static void -iscsi_r2tpool_free(struct iscsi_session *session) -{ - int i; - - for (i = 0; i < session->cmds_max; i++) { - struct iscsi_task *task = session->cmds[i]; - struct iscsi_tcp_task *tcp_task = task->dd_data; - - kfifo_free(tcp_task->r2tqueue); - iscsi_pool_free(&tcp_task->r2tpool); - } -} - -static int -iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, - char *buf, int buflen) +static int iscsi_sw_tcp_conn_set_param(struct iscsi_cls_conn *cls_conn, + enum iscsi_param param, char *buf, + int buflen) { struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_session *session = conn->session; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; int value; switch(param) { @@ -1755,8 +688,8 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, break; case ISCSI_PARAM_DATADGST_EN: iscsi_set_param(cls_conn, param, buf, buflen); - tcp_conn->sendpage = conn->datadgst_en ? - sock_no_sendpage : tcp_conn->sock->ops->sendpage; + tcp_sw_conn->sendpage = conn->datadgst_en ? + sock_no_sendpage : tcp_sw_conn->sock->ops->sendpage; break; case ISCSI_PARAM_MAX_R2T: sscanf(buf, "%d", &value); @@ -1764,9 +697,9 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, return -EINVAL; if (session->max_r2t == value) break; - iscsi_r2tpool_free(session); + iscsi_tcp_r2tpool_free(session); iscsi_set_param(cls_conn, param, buf, buflen); - if (iscsi_r2tpool_alloc(session)) + if (iscsi_tcp_r2tpool_alloc(session)) return -ENOMEM; break; default: @@ -1776,9 +709,8 @@ iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, return 0; } -static int -iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn, - enum iscsi_param param, char *buf) +static int iscsi_sw_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn, + enum iscsi_param param, char *buf) { struct iscsi_conn *conn = cls_conn->dd_data; int len; @@ -1802,48 +734,42 @@ iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn, } static void -iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats) +iscsi_sw_tcp_conn_get_stats(struct iscsi_cls_conn *cls_conn, + struct iscsi_stats *stats) { struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; - stats->txdata_octets = conn->txdata_octets; - stats->rxdata_octets = conn->rxdata_octets; - stats->scsicmd_pdus = conn->scsicmd_pdus_cnt; - stats->dataout_pdus = conn->dataout_pdus_cnt; - stats->scsirsp_pdus = conn->scsirsp_pdus_cnt; - stats->datain_pdus = conn->datain_pdus_cnt; - stats->r2t_pdus = conn->r2t_pdus_cnt; - stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt; - stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt; stats->custom_length = 3; strcpy(stats->custom[0].desc, "tx_sendpage_failures"); - stats->custom[0].value = tcp_conn->sendpage_failures_cnt; + stats->custom[0].value = tcp_sw_conn->sendpage_failures_cnt; strcpy(stats->custom[1].desc, "rx_discontiguous_hdr"); - stats->custom[1].value = tcp_conn->discontiguous_hdr_cnt; + stats->custom[1].value = tcp_sw_conn->discontiguous_hdr_cnt; strcpy(stats->custom[2].desc, "eh_abort_cnt"); stats->custom[2].value = conn->eh_abort_cnt; + + iscsi_tcp_conn_get_stats(cls_conn, stats); } static struct iscsi_cls_session * -iscsi_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max, - uint16_t qdepth, uint32_t initial_cmdsn, - uint32_t *hostno) +iscsi_sw_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max, + uint16_t qdepth, uint32_t initial_cmdsn, + uint32_t *hostno) { struct iscsi_cls_session *cls_session; struct iscsi_session *session; struct Scsi_Host *shost; - int cmd_i; if (ep) { printk(KERN_ERR "iscsi_tcp: invalid ep %p.\n", ep); return NULL; } - shost = iscsi_host_alloc(&iscsi_sht, 0, qdepth); + shost = iscsi_host_alloc(&iscsi_sw_tcp_sht, 0, qdepth); if (!shost) return NULL; - shost->transportt = iscsi_tcp_scsi_transport; + shost->transportt = iscsi_sw_tcp_scsi_transport; shost->max_lun = iscsi_max_lun; shost->max_id = 0; shost->max_channel = 0; @@ -1853,23 +779,17 @@ iscsi_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max, goto free_host; *hostno = shost->host_no; - cls_session = iscsi_session_setup(&iscsi_tcp_transport, shost, cmds_max, - sizeof(struct iscsi_tcp_task), + cls_session = iscsi_session_setup(&iscsi_sw_tcp_transport, shost, + cmds_max, + sizeof(struct iscsi_tcp_task) + + sizeof(struct iscsi_sw_tcp_hdrbuf), initial_cmdsn, 0); if (!cls_session) goto remove_host; session = cls_session->dd_data; shost->can_queue = session->scsi_cmds_max; - for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { - struct iscsi_task *task = session->cmds[cmd_i]; - struct iscsi_tcp_task *tcp_task = task->dd_data; - - task->hdr = &tcp_task->hdr.cmd_hdr; - task->hdr_max = sizeof(tcp_task->hdr) - ISCSI_DIGEST_SIZE; - } - - if (iscsi_r2tpool_alloc(session)) + if (iscsi_tcp_r2tpool_alloc(session)) goto remove_session; return cls_session; @@ -1882,24 +802,25 @@ free_host: return NULL; } -static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session) +static void iscsi_sw_tcp_session_destroy(struct iscsi_cls_session *cls_session) { struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); - iscsi_r2tpool_free(cls_session->dd_data); + iscsi_tcp_r2tpool_free(cls_session->dd_data); + iscsi_session_teardown(cls_session); iscsi_host_remove(shost); iscsi_host_free(shost); } -static int iscsi_tcp_slave_configure(struct scsi_device *sdev) +static int iscsi_sw_tcp_slave_configure(struct scsi_device *sdev) { blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_ANY); blk_queue_dma_alignment(sdev->request_queue, 0); return 0; } -static struct scsi_host_template iscsi_sht = { +static struct scsi_host_template iscsi_sw_tcp_sht = { .module = THIS_MODULE, .name = "iSCSI Initiator over TCP/IP", .queuecommand = iscsi_queuecommand, @@ -1910,14 +831,14 @@ static struct scsi_host_template iscsi_sht = { .cmd_per_lun = ISCSI_DEF_CMD_PER_LUN, .eh_abort_handler = iscsi_eh_abort, .eh_device_reset_handler= iscsi_eh_device_reset, - .eh_host_reset_handler = iscsi_eh_host_reset, + .eh_target_reset_handler= iscsi_eh_target_reset, .use_clustering = DISABLE_CLUSTERING, - .slave_configure = iscsi_tcp_slave_configure, + .slave_configure = iscsi_sw_tcp_slave_configure, .proc_name = "iscsi_tcp", .this_id = -1, }; -static struct iscsi_transport iscsi_tcp_transport = { +static struct iscsi_transport iscsi_sw_tcp_transport = { .owner = THIS_MODULE, .name = "tcp", .caps = CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST @@ -1950,32 +871,36 @@ static struct iscsi_transport iscsi_tcp_transport = { ISCSI_HOST_INITIATOR_NAME | ISCSI_HOST_NETDEV_NAME, /* session management */ - .create_session = iscsi_tcp_session_create, - .destroy_session = iscsi_tcp_session_destroy, + .create_session = iscsi_sw_tcp_session_create, + .destroy_session = iscsi_sw_tcp_session_destroy, /* connection management */ - .create_conn = iscsi_tcp_conn_create, - .bind_conn = iscsi_tcp_conn_bind, - .destroy_conn = iscsi_tcp_conn_destroy, - .set_param = iscsi_conn_set_param, - .get_conn_param = iscsi_tcp_conn_get_param, + .create_conn = iscsi_sw_tcp_conn_create, + .bind_conn = iscsi_sw_tcp_conn_bind, + .destroy_conn = iscsi_sw_tcp_conn_destroy, + .set_param = iscsi_sw_tcp_conn_set_param, + .get_conn_param = iscsi_sw_tcp_conn_get_param, .get_session_param = iscsi_session_get_param, .start_conn = iscsi_conn_start, - .stop_conn = iscsi_tcp_conn_stop, + .stop_conn = iscsi_sw_tcp_conn_stop, /* iscsi host params */ .get_host_param = iscsi_host_get_param, .set_host_param = iscsi_host_set_param, /* IO */ .send_pdu = iscsi_conn_send_pdu, - .get_stats = iscsi_conn_get_stats, + .get_stats = iscsi_sw_tcp_conn_get_stats, + /* iscsi task/cmd helpers */ .init_task = iscsi_tcp_task_init, .xmit_task = iscsi_tcp_task_xmit, .cleanup_task = iscsi_tcp_cleanup_task, + /* low level pdu helpers */ + .xmit_pdu = iscsi_sw_tcp_pdu_xmit, + .init_pdu = iscsi_sw_tcp_pdu_init, + .alloc_pdu = iscsi_sw_tcp_pdu_alloc, /* recovery */ .session_recovery_timedout = iscsi_session_recovery_timedout, }; -static int __init -iscsi_tcp_init(void) +static int __init iscsi_sw_tcp_init(void) { if (iscsi_max_lun < 1) { printk(KERN_ERR "iscsi_tcp: Invalid max_lun value of %u\n", @@ -1983,19 +908,18 @@ iscsi_tcp_init(void) return -EINVAL; } - iscsi_tcp_scsi_transport = iscsi_register_transport( - &iscsi_tcp_transport); - if (!iscsi_tcp_scsi_transport) + iscsi_sw_tcp_scsi_transport = iscsi_register_transport( + &iscsi_sw_tcp_transport); + if (!iscsi_sw_tcp_scsi_transport) return -ENODEV; return 0; } -static void __exit -iscsi_tcp_exit(void) +static void __exit iscsi_sw_tcp_exit(void) { - iscsi_unregister_transport(&iscsi_tcp_transport); + iscsi_unregister_transport(&iscsi_sw_tcp_transport); } -module_init(iscsi_tcp_init); -module_exit(iscsi_tcp_exit); +module_init(iscsi_sw_tcp_init); +module_exit(iscsi_sw_tcp_exit); diff --git a/kernel/iscsi_tcp.h b/kernel/iscsi_tcp.h index 68423e8..f9a4044 100644 --- a/kernel/iscsi_tcp.h +++ b/kernel/iscsi_tcp.h @@ -19,67 +19,27 @@ * See the file COPYING included with this distribution for more details. */ -#ifndef ISCSI_TCP_H -#define ISCSI_TCP_H +#ifndef ISCSI_SW_TCP_H +#define ISCSI_SW_TCP_H #include "libiscsi.h" +#include "libiscsi_tcp.h" -struct crypto_hash; struct socket; struct iscsi_tcp_conn; -struct iscsi_segment; - -typedef int iscsi_segment_done_fn_t(struct iscsi_tcp_conn *, - struct iscsi_segment *); - -struct iscsi_segment { - unsigned char *data; - unsigned int size; - unsigned int copied; - unsigned int total_size; - unsigned int total_copied; - - struct hash_desc *hash; - unsigned char recv_digest[ISCSI_DIGEST_SIZE]; - unsigned char digest[ISCSI_DIGEST_SIZE]; - unsigned int digest_len; - - struct scatterlist *sg; - void *sg_mapped; - unsigned int sg_offset; - - iscsi_segment_done_fn_t *done; -}; - -/* Socket connection recieve helper */ -struct iscsi_tcp_recv { - struct iscsi_hdr *hdr; - struct iscsi_segment segment; - - /* Allocate buffer for BHS + AHS */ - uint32_t hdr_buf[64]; - - /* copied and flipped values */ - int datalen; -}; /* Socket connection send helper */ -struct iscsi_tcp_send { +struct iscsi_sw_tcp_send { struct iscsi_hdr *hdr; struct iscsi_segment segment; struct iscsi_segment data_segment; }; -struct iscsi_tcp_conn { +struct iscsi_sw_tcp_conn { struct iscsi_conn *iscsi_conn; struct socket *sock; - int stop_stage; /* conn_stop() flag: * - * stop to recover, * - * stop to terminate */ - /* control data */ - struct iscsi_tcp_recv in; /* TCP receive context */ - struct iscsi_tcp_send out; /* TCP send context */ + struct iscsi_sw_tcp_send out; /* old values for socket callbacks */ void (*old_data_ready)(struct sock *, int); void (*old_state_change)(struct sock *); @@ -93,41 +53,13 @@ struct iscsi_tcp_conn { uint32_t sendpage_failures_cnt; uint32_t discontiguous_hdr_cnt; - int error; - ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int); }; -struct iscsi_data_task { - struct iscsi_data hdr; /* PDU */ - char hdrext[ISCSI_DIGEST_SIZE];/* Header-Digest */ -}; - -struct iscsi_r2t_info { - __be32 ttt; /* copied from R2T */ - __be32 exp_statsn; /* copied from R2T */ - uint32_t data_length; /* copied from R2T */ - uint32_t data_offset; /* copied from R2T */ - int sent; /* R2T sequence progress */ - int data_count; /* DATA-Out payload progress */ - int solicit_datasn; - struct iscsi_data_task dtask; /* Data-Out header buf */ -}; - -struct iscsi_tcp_task { - struct iscsi_hdr_buff { - struct iscsi_cmd cmd_hdr; - char hdrextbuf[ISCSI_MAX_AHS_SIZE + +struct iscsi_sw_tcp_hdrbuf { + struct iscsi_hdr hdrbuf; + char hdrextbuf[ISCSI_MAX_AHS_SIZE + ISCSI_DIGEST_SIZE]; - } hdr; - - int sent; - uint32_t exp_datasn; /* expected target's R2TSN/DataSN */ - int data_offset; - struct iscsi_r2t_info *r2t; /* in progress R2T */ - struct iscsi_pool r2tpool; - struct kfifo *r2tqueue; - struct iscsi_data_task unsol_dtask; /* Data-Out header buf */ }; -#endif /* ISCSI_H */ +#endif /* ISCSI_SW_TCP_H */ diff --git a/kernel/libiscsi.c b/kernel/libiscsi.c index f3b845f..5ac73fb 100644 --- a/kernel/libiscsi.c +++ b/kernel/libiscsi.c @@ -38,7 +38,6 @@ #include "scsi_transport_iscsi.h" #include "libiscsi.h" - /* Serial Number Arithmetic, 32 bits, less than, RFC1982 */ #define SNA32_CHECK 2147483648UL @@ -89,34 +88,47 @@ iscsi_update_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr) } EXPORT_SYMBOL_GPL(iscsi_update_cmdsn); -void iscsi_prep_unsolicit_data_pdu(struct iscsi_task *task, - struct iscsi_data *hdr) +/** + * iscsi_prep_data_out_pdu - initialize Data-Out + * @task: scsi command task + * @r2t: R2T info + * @hdr: iscsi data in pdu + * + * Notes: + * Initialize Data-Out within this R2T sequence and finds + * proper data_offset within this SCSI command. + * + * This function is called with connection lock taken. + **/ +void iscsi_prep_data_out_pdu(struct iscsi_task *task, struct iscsi_r2t_info *r2t, + struct iscsi_data *hdr) { struct iscsi_conn *conn = task->conn; + unsigned int left = r2t->data_length - r2t->sent; + + task->hdr_len = sizeof(struct iscsi_data); memset(hdr, 0, sizeof(struct iscsi_data)); - hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); - hdr->datasn = cpu_to_be32(task->unsol_datasn); - task->unsol_datasn++; + hdr->ttt = r2t->ttt; + hdr->datasn = cpu_to_be32(r2t->datasn); + r2t->datasn++; hdr->opcode = ISCSI_OP_SCSI_DATA_OUT; - memcpy(hdr->lun, task->hdr->lun, sizeof(hdr->lun)); - - hdr->itt = task->hdr->itt; - hdr->exp_statsn = cpu_to_be32(conn->exp_statsn); - hdr->offset = cpu_to_be32(task->unsol_offset); - - if (task->unsol_count > conn->max_xmit_dlength) { + memcpy(hdr->lun, task->lun, sizeof(hdr->lun)); + hdr->itt = task->hdr_itt; + hdr->exp_statsn = r2t->exp_statsn; + hdr->offset = cpu_to_be32(r2t->data_offset + r2t->sent); + if (left > conn->max_xmit_dlength) { hton24(hdr->dlength, conn->max_xmit_dlength); - task->data_count = conn->max_xmit_dlength; - task->unsol_offset += task->data_count; + r2t->data_count = conn->max_xmit_dlength; hdr->flags = 0; } else { - hton24(hdr->dlength, task->unsol_count); - task->data_count = task->unsol_count; + hton24(hdr->dlength, left); + r2t->data_count = left; hdr->flags = ISCSI_FLAG_CMD_FINAL; } + conn->dataout_pdus_cnt++; } -EXPORT_SYMBOL_GPL(iscsi_prep_unsolicit_data_pdu); +EXPORT_SYMBOL_GPL(iscsi_prep_data_out_pdu); static int iscsi_add_hdr(struct iscsi_task *task, unsigned len) { @@ -207,11 +219,24 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task) { struct iscsi_conn *conn = task->conn; struct iscsi_session *session = conn->session; - struct iscsi_cmd *hdr = task->hdr; struct scsi_cmnd *sc = task->sc; + struct iscsi_cmd *hdr; unsigned hdrlength, cmd_len; + itt_t itt; int rc; + rc = conn->session->tt->alloc_pdu(task, ISCSI_OP_SCSI_CMD); + if (rc) + return rc; + hdr = (struct iscsi_cmd *) task->hdr; + itt = hdr->itt; + memset(hdr, 0, sizeof(*hdr)); + + if (session->tt->parse_pdu_itt) + hdr->itt = task->hdr_itt = itt; + else + hdr->itt = task->hdr_itt = build_itt(task->itt, + task->conn->session->age); task->hdr_len = 0; rc = iscsi_add_hdr(task, sizeof(*hdr)); if (rc) @@ -219,8 +244,8 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task) hdr->opcode = ISCSI_OP_SCSI_CMD; hdr->flags = ISCSI_ATTR_SIMPLE; int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun); - hdr->itt = build_itt(task->itt, session->age); - hdr->cmdsn = cpu_to_be32(session->cmdsn); + memcpy(task->lun, hdr->lun, sizeof(task->lun)); + hdr->cmdsn = task->cmdsn = cpu_to_be32(session->cmdsn); session->cmdsn++; hdr->exp_statsn = cpu_to_be32(conn->exp_statsn); cmd_len = sc->cmd_len; @@ -243,6 +268,8 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task) } if (sc->sc_data_direction == DMA_TO_DEVICE) { unsigned out_len = scsi_out(sc)->length; + struct iscsi_r2t_info *r2t = &task->unsol_r2t; + hdr->data_length = cpu_to_be32(out_len); hdr->flags |= ISCSI_FLAG_CMD_WRITE; /* @@ -255,13 +282,11 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task) * without R2T ack right after * immediate data * - * r2t_data_count bytes to be sent via R2T ack's + * r2t data_length bytes to be sent via R2T ack's * * pad_count bytes to be sent as zero-padding */ - task->unsol_count = 0; - task->unsol_offset = 0; - task->unsol_datasn = 0; + memset(r2t, 0, sizeof(*r2t)); if (session->imm_data_en) { if (out_len >= session->first_burst) @@ -275,12 +300,14 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task) zero_data(hdr->dlength); if (!session->initial_r2t_en) { - task->unsol_count = min(session->first_burst, out_len) - - task->imm_count; - task->unsol_offset = task->imm_count; + r2t->data_length = min(session->first_burst, out_len) - + task->imm_count; + r2t->data_offset = task->imm_count; + r2t->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); + r2t->exp_statsn = cpu_to_be32(conn->exp_statsn); } - if (!task->unsol_count) + if (!task->unsol_r2t.data_length) /* No unsolicit Data-Out's */ hdr->flags |= ISCSI_FLAG_CMD_FINAL; } else { @@ -301,8 +328,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task) WARN_ON(hdrlength >= 256); hdr->hlength = hdrlength & 0xFF; - if (conn->session->tt->init_task && - conn->session->tt->init_task(task)) + if (session->tt->init_task && session->tt->init_task(task)) return -EIO; task->state = ISCSI_TASK_RUNNING; @@ -333,6 +359,7 @@ static void iscsi_complete_command(struct iscsi_task *task) struct iscsi_session *session = conn->session; struct scsi_cmnd *sc = task->sc; + session->tt->cleanup_task(task); list_del_init(&task->running); task->state = ISCSI_TASK_COMPLETED; task->sc = NULL; @@ -403,8 +430,6 @@ static void fail_command(struct iscsi_conn *conn, struct iscsi_task *task, * the cmd in the sequencing */ conn->session->queued_cmdsn--; - else - conn->session->tt->cleanup_task(conn, task); sc->result = err; if (!scsi_bidi_cmnd(sc)) @@ -424,7 +449,7 @@ static int iscsi_prep_mgmt_task(struct iscsi_conn *conn, struct iscsi_task *task) { struct iscsi_session *session = conn->session; - struct iscsi_hdr *hdr = (struct iscsi_hdr *)task->hdr; + struct iscsi_hdr *hdr = task->hdr; struct iscsi_nopout *nop = (struct iscsi_nopout *)hdr; if (conn->session->state == ISCSI_STATE_LOGGING_OUT) @@ -438,7 +463,6 @@ static int iscsi_prep_mgmt_task(struct iscsi_conn *conn, */ nop->cmdsn = cpu_to_be32(session->cmdsn); if (hdr->itt != RESERVED_ITT) { - hdr->itt = build_itt(task->itt, session->age); /* * TODO: We always use immediate, so we never hit this. * If we start to send tmfs or nops as non-immediate then @@ -451,12 +475,13 @@ static int iscsi_prep_mgmt_task(struct iscsi_conn *conn, } } - if (session->tt->init_task) - session->tt->init_task(task); + if (session->tt->init_task && session->tt->init_task(task)) + return -EIO; if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT) session->state = ISCSI_STATE_LOGGING_OUT; + task->state = ISCSI_TASK_RUNNING; list_move_tail(&task->running, &conn->mgmt_run_list); debug_scsi("mgmtpdu [op 0x%x hdr->itt 0x%x datalen %d]\n", hdr->opcode & ISCSI_OPCODE_MASK, hdr->itt, @@ -470,6 +495,7 @@ __iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, { struct iscsi_session *session = conn->session; struct iscsi_task *task; + itt_t itt; if (session->state == ISCSI_STATE_TERMINATE) return NULL; @@ -490,12 +516,6 @@ __iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, if (!__kfifo_get(session->cmdpool.queue, (void*)&task, sizeof(void*))) return NULL; - - if ((hdr->opcode == (ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE)) && - hdr->ttt == RESERVED_ITT) { - conn->ping_task = task; - conn->last_ping = jiffies; - } } /* * released in complete pdu for task we expect a response for, and @@ -512,23 +532,47 @@ __iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, } else task->data_count = 0; + if (conn->session->tt->alloc_pdu(task, hdr->opcode)) { + iscsi_conn_printk(KERN_ERR, conn, "Could not allocate " + "pdu for mgmt task.\n"); + goto requeue_task; + } + itt = task->hdr->itt; + task->hdr_len = sizeof(struct iscsi_hdr); memcpy(task->hdr, hdr, sizeof(struct iscsi_hdr)); + + if (hdr->itt != RESERVED_ITT) { + if (session->tt->parse_pdu_itt) + task->hdr->itt = itt; + else + task->hdr->itt = build_itt(task->itt, + task->conn->session->age); + } + INIT_LIST_HEAD(&task->running); list_add_tail(&task->running, &conn->mgmtqueue); if (session->tt->caps & CAP_DATA_PATH_OFFLOAD) { - if (iscsi_prep_mgmt_task(conn, task)) { - __iscsi_put_task(task); - return NULL; - } + if (iscsi_prep_mgmt_task(conn, task)) + goto free_task; if (session->tt->xmit_task(task)) - task = NULL; + goto free_task; } else scsi_queue_work(conn->session->host, &conn->xmitwork); return task; + +free_task: + __iscsi_put_task(task); + return NULL; + +requeue_task: + if (task != conn->login_task) + __kfifo_put(session->cmdpool.queue, (void*)&task, + sizeof(void*)); + return NULL; } int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr, @@ -704,6 +748,11 @@ static void iscsi_send_nopout(struct iscsi_conn *conn, struct iscsi_nopin *rhdr) task = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)&hdr, NULL, 0); if (!task) iscsi_conn_printk(KERN_ERR, conn, "Could not send nopout\n"); + else if (!rhdr) { + /* only track our nops */ + conn->ping_task = task; + conn->last_ping = jiffies; + } } static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr, @@ -711,7 +760,6 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr, { struct iscsi_reject *reject = (struct iscsi_reject *)hdr; struct iscsi_hdr rejected_pdu; - uint32_t itt; conn->exp_statsn = be32_to_cpu(reject->statsn) + 1; @@ -721,10 +769,9 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr, if (ntoh24(reject->dlength) >= sizeof(struct iscsi_hdr)) { memcpy(&rejected_pdu, data, sizeof(struct iscsi_hdr)); - itt = get_itt(rejected_pdu.itt); iscsi_conn_printk(KERN_ERR, conn, - "itt 0x%x had pdu (op 0x%x) rejected " - "due to DataDigest error.\n", itt, + "pdu (op 0x%x) rejected " + "due to DataDigest error.\n", rejected_pdu.opcode); } } @@ -744,12 +791,15 @@ static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr, static struct iscsi_task *iscsi_itt_to_task(struct iscsi_conn *conn, itt_t itt) { struct iscsi_session *session = conn->session; - uint32_t i; + int i; if (itt == RESERVED_ITT) return NULL; - i = get_itt(itt); + if (session->tt->parse_pdu_itt) + session->tt->parse_pdu_itt(conn, itt, &i, NULL); + else + i = get_itt(itt); if (i >= session->cmds_max) return NULL; @@ -924,20 +974,25 @@ EXPORT_SYMBOL_GPL(iscsi_complete_pdu); int iscsi_verify_itt(struct iscsi_conn *conn, itt_t itt) { struct iscsi_session *session = conn->session; - uint32_t i; + int age = 0, i = 0; if (itt == RESERVED_ITT) return 0; - if (((__force u32)itt & ISCSI_AGE_MASK) != - (session->age << ISCSI_AGE_SHIFT)) { + if (session->tt->parse_pdu_itt) + session->tt->parse_pdu_itt(conn, itt, &i, &age); + else { + i = get_itt(itt); + age = ((__force u32)itt >> ISCSI_AGE_SHIFT) & ISCSI_AGE_MASK; + } + + if (age != session->age) { iscsi_conn_printk(KERN_ERR, conn, "received itt %x expected session age (%x)\n", (__force u32)itt, session->age); return ISCSI_ERR_BAD_ITT; } - i = get_itt(itt); if (i >= session->cmds_max) { iscsi_conn_printk(KERN_ERR, conn, "received invalid itt index %u (max cmds " @@ -979,6 +1034,38 @@ struct iscsi_task *iscsi_itt_to_ctask(struct iscsi_conn *conn, itt_t itt) } EXPORT_SYMBOL_GPL(iscsi_itt_to_ctask); +void iscsi_session_failure(struct iscsi_cls_session *cls_session, + enum iscsi_err err) +{ + struct iscsi_session *session = cls_session->dd_data; + struct iscsi_conn *conn; + struct device *dev; + unsigned long flags; + + spin_lock_irqsave(&session->lock, flags); + conn = session->leadconn; + if (session->state == ISCSI_STATE_TERMINATE || !conn) { + spin_unlock_irqrestore(&session->lock, flags); + return; + } + + dev = get_device(&conn->cls_conn->dev); + spin_unlock_irqrestore(&session->lock, flags); + if (!dev) + return; + /* + * if the host is being removed bypass the connection + * recovery initialization because we are going to kill + * the session. + */ + if (err == ISCSI_ERR_INVALID_HOST) + iscsi_conn_error_event(conn->cls_conn, err); + else + iscsi_conn_failure(conn, err); + put_device(dev); +} +EXPORT_SYMBOL_GPL(iscsi_session_failure); + void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err) { struct iscsi_session *session = conn->session; @@ -993,9 +1080,10 @@ void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err) if (conn->stop_stage == 0) session->state = ISCSI_STATE_FAILED; spin_unlock_irqrestore(&session->lock, flags); + set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx); set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx); - iscsi_conn_error(conn->cls_conn, err); + iscsi_conn_error_event(conn->cls_conn, err); } EXPORT_SYMBOL_GPL(iscsi_conn_failure); @@ -1105,8 +1193,13 @@ check_mgmt: fail_command(conn, conn->task, DID_IMM_RETRY << 16); continue; } - if (iscsi_prep_scsi_cmd_pdu(conn->task)) { - fail_command(conn, conn->task, DID_ABORT << 16); + rc = iscsi_prep_scsi_cmd_pdu(conn->task); + if (rc) { + if (rc == -ENOMEM) { + conn->task = NULL; + goto again; + } else + fail_command(conn, conn->task, DID_ABORT << 16); continue; } rc = iscsi_xmit_task(conn); @@ -1164,6 +1257,26 @@ static void iscsi_xmitworker(struct work_struct *work) } while (rc >= 0 || rc == -EAGAIN); } +static inline struct iscsi_task *iscsi_alloc_task(struct iscsi_conn *conn, + struct scsi_cmnd *sc) +{ + struct iscsi_task *task; + + if (!__kfifo_get(conn->session->cmdpool.queue, + (void *) &task, sizeof(void *))) + return NULL; + + sc->SCp.phase = conn->session->age; + sc->SCp.ptr = (char *) task; + + atomic_set(&task->refcount, 1); + task->state = ISCSI_TASK_PENDING; + task->conn = conn; + task->sc = sc; + INIT_LIST_HEAD(&task->running); + return task; +} + enum { FAILURE_BAD_HOST = 1, FAILURE_SESSION_FAILED, @@ -1219,15 +1332,13 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *)) switch (session->state) { case ISCSI_STATE_IN_RECOVERY: reason = FAILURE_SESSION_IN_RECOVERY; - sc->result = DID_IMM_RETRY << 16; - break; + goto reject; case ISCSI_STATE_LOGGING_OUT: reason = FAILURE_SESSION_LOGGING_OUT; - sc->result = DID_IMM_RETRY << 16; - break; + goto reject; case ISCSI_STATE_RECOVERY_FAILED: reason = FAILURE_SESSION_RECOVERY_TIMEOUT; - sc->result = DID_NO_CONNECT << 16; + sc->result = DID_TRANSPORT_FAILFAST << 16; break; case ISCSI_STATE_TERMINATE: reason = FAILURE_SESSION_TERMINATE; @@ -1252,33 +1363,27 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *)) goto reject; } - if (!__kfifo_get(session->cmdpool.queue, (void*)&task, - sizeof(void*))) { + task = iscsi_alloc_task(conn, sc); + if (!task) { reason = FAILURE_OOM; goto reject; } - sc->SCp.phase = session->age; - sc->SCp.ptr = (char *)task; - - atomic_set(&task->refcount, 1); - task->state = ISCSI_TASK_PENDING; - task->conn = conn; - task->sc = sc; - INIT_LIST_HEAD(&task->running); list_add_tail(&task->running, &conn->xmitqueue); if (session->tt->caps & CAP_DATA_PATH_OFFLOAD) { - if (iscsi_prep_scsi_cmd_pdu(task)) { - sc->result = DID_ABORT << 16; - sc->scsi_done = NULL; - iscsi_complete_command(task); - goto fault; + reason = iscsi_prep_scsi_cmd_pdu(task); + if (reason) { + if (reason == -ENOMEM) { + reason = FAILURE_OOM; + goto prepd_reject; + } else { + sc->result = DID_ABORT << 16; + goto prepd_fault; + } } if (session->tt->xmit_task(task)) { - sc->scsi_done = NULL; - iscsi_complete_command(task); reason = FAILURE_SESSION_NOT_READY; - goto reject; + goto prepd_reject; } } else scsi_queue_work(session->host, &conn->xmitwork); @@ -1288,12 +1393,18 @@ int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *)) spin_lock(host->host_lock); return 0; +prepd_reject: + sc->scsi_done = NULL; + iscsi_complete_command(task); reject: spin_unlock(&session->lock); debug_scsi("cmd 0x%x rejected (%d)\n", sc->cmnd[0], reason); spin_lock(host->host_lock); - return SCSI_MLQUEUE_HOST_BUSY; + return SCSI_MLQUEUE_TARGET_BUSY; +prepd_fault: + sc->scsi_done = NULL; + iscsi_complete_command(task); fault: spin_unlock(&session->lock); debug_scsi("iscsi: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason); @@ -1332,7 +1443,7 @@ void iscsi_session_recovery_timedout(struct iscsi_cls_session *cls_session) } EXPORT_SYMBOL_GPL(iscsi_session_recovery_timedout); -int iscsi_eh_host_reset(struct scsi_cmnd *sc) +int iscsi_eh_target_reset(struct scsi_cmnd *sc) { struct iscsi_cls_session *cls_session; struct iscsi_session *session; @@ -1346,7 +1457,7 @@ int iscsi_eh_host_reset(struct scsi_cmnd *sc) spin_lock_bh(&session->lock); if (session->state == ISCSI_STATE_TERMINATE) { failed: - debug_scsi("failing host reset: session terminated " + debug_scsi("failing target reset: session terminated " "[CID %d age %d]\n", conn->id, session->age); spin_unlock_bh(&session->lock); mutex_unlock(&session->eh_mutex); @@ -1361,7 +1472,7 @@ failed: */ iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED); - debug_scsi("iscsi_eh_host_reset wait for relogin\n"); + debug_scsi("iscsi_eh_target_reset wait for relogin\n"); wait_event_interruptible(conn->ehwait, session->state == ISCSI_STATE_TERMINATE || session->state == ISCSI_STATE_LOGGED_IN || @@ -1373,14 +1484,14 @@ failed: spin_lock_bh(&session->lock); if (session->state == ISCSI_STATE_LOGGED_IN) iscsi_session_printk(KERN_INFO, session, - "host reset succeeded\n"); + "target reset succeeded\n"); else goto failed; spin_unlock_bh(&session->lock); mutex_unlock(&session->eh_mutex); return SUCCESS; } -EXPORT_SYMBOL_GPL(iscsi_eh_host_reset); +EXPORT_SYMBOL_GPL(iscsi_eh_target_reset); static void iscsi_tmf_timedout(unsigned long data) { @@ -1481,7 +1592,7 @@ static void fail_all_commands(struct iscsi_conn *conn, unsigned lun, if (lun == task->sc->device->lun || lun == -1) { debug_scsi("failing in progress sc %p itt 0x%x\n", task->sc, task->itt); - fail_command(conn, task, DID_BUS_BUSY << 16); + fail_command(conn, task, error << 16); } } } @@ -1501,12 +1612,12 @@ static void iscsi_start_tx(struct iscsi_conn *conn) scsi_queue_work(conn->session->host, &conn->xmitwork); } -static enum scsi_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd) +static enum blk_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd) { struct iscsi_cls_session *cls_session; struct iscsi_session *session; struct iscsi_conn *conn; - enum scsi_eh_timer_return rc = EH_NOT_HANDLED; + enum blk_eh_timer_return rc = BLK_EH_NOT_HANDLED; cls_session = starget_to_session(scsi_target(scmd->device)); session = cls_session->dd_data; @@ -1519,14 +1630,14 @@ static enum scsi_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd) * We are probably in the middle of iscsi recovery so let * that complete and handle the error. */ - rc = EH_RESET_TIMER; + rc = BLK_EH_RESET_TIMER; goto done; } conn = session->leadconn; if (!conn) { /* In the middle of shuting down */ - rc = EH_RESET_TIMER; + rc = BLK_EH_RESET_TIMER; goto done; } @@ -1538,20 +1649,21 @@ static enum scsi_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd) */ if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ) + (conn->ping_timeout * HZ), jiffies)) - rc = EH_RESET_TIMER; + rc = BLK_EH_RESET_TIMER; /* * if we are about to check the transport then give the command * more time */ if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ), jiffies)) - rc = EH_RESET_TIMER; + rc = BLK_EH_RESET_TIMER; /* if in the middle of checking the transport then give us more time */ if (conn->ping_task) - rc = EH_RESET_TIMER; + rc = BLK_EH_RESET_TIMER; done: spin_unlock(&session->lock); - debug_scsi("return %s\n", rc == EH_RESET_TIMER ? "timer reset" : "nh"); + debug_scsi("return %s\n", rc == BLK_EH_RESET_TIMER ? + "timer reset" : "nh"); return rc; } @@ -1604,9 +1716,9 @@ static void iscsi_prep_abort_task_pdu(struct iscsi_task *task, hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE; hdr->flags = ISCSI_TM_FUNC_ABORT_TASK & ISCSI_FLAG_TM_FUNC_MASK; hdr->flags |= ISCSI_FLAG_CMD_FINAL; - memcpy(hdr->lun, task->hdr->lun, sizeof(hdr->lun)); - hdr->rtt = task->hdr->itt; - hdr->refcmdsn = task->hdr->cmdsn; + memcpy(hdr->lun, task->lun, sizeof(hdr->lun)); + hdr->rtt = task->hdr_itt; + hdr->refcmdsn = task->cmdsn; } int iscsi_eh_abort(struct scsi_cmnd *sc) @@ -1869,6 +1981,7 @@ void iscsi_pool_free(struct iscsi_pool *q) kfree(q->pool[i]); if (q->pool) kfree(q->pool); + kfree(q->queue); } EXPORT_SYMBOL_GPL(iscsi_pool_free); @@ -1902,6 +2015,7 @@ struct Scsi_Host *iscsi_host_alloc(struct scsi_host_template *sht, int dd_data_size, uint16_t qdepth) { struct Scsi_Host *shost; + struct iscsi_host *ihost; shost = scsi_host_alloc(sht, sizeof(struct iscsi_host) + dd_data_size); if (!shost) @@ -1916,22 +2030,43 @@ struct Scsi_Host *iscsi_host_alloc(struct scsi_host_template *sht, qdepth = ISCSI_DEF_CMD_PER_LUN; } shost->cmd_per_lun = qdepth; + + ihost = shost_priv(shost); + spin_lock_init(&ihost->lock); + ihost->state = ISCSI_HOST_SETUP; + ihost->num_sessions = 0; + init_waitqueue_head(&ihost->session_removal_wq); return shost; } EXPORT_SYMBOL_GPL(iscsi_host_alloc); +static void iscsi_notify_host_removed(struct iscsi_cls_session *cls_session) +{ + iscsi_session_failure(cls_session, ISCSI_ERR_INVALID_HOST); +} + /** * iscsi_host_remove - remove host and sessions * @shost: scsi host * - * This will also remove any sessions attached to the host, but if userspace - * is managing the session at the same time this will break. TODO: add - * refcounting to the netlink iscsi interface so a rmmod or host hot unplug - * does not remove the memory from under us. + * If there are any sessions left, this will initiate the removal and wait + * for the completion. */ void iscsi_host_remove(struct Scsi_Host *shost) { - iscsi_host_for_each_session(shost, iscsi_session_teardown); + struct iscsi_host *ihost = shost_priv(shost); + unsigned long flags; + + spin_lock_irqsave(&ihost->lock, flags); + ihost->state = ISCSI_HOST_REMOVED; + spin_unlock_irqrestore(&ihost->lock, flags); + + iscsi_host_for_each_session(shost, iscsi_notify_host_removed); + wait_event_interruptible(ihost->session_removal_wq, + ihost->num_sessions == 0); + if (signal_pending(current)) + flush_signals(current); + scsi_remove_host(shost); } EXPORT_SYMBOL_GPL(iscsi_host_remove); @@ -1947,6 +2082,27 @@ void iscsi_host_free(struct Scsi_Host *shost) } EXPORT_SYMBOL_GPL(iscsi_host_free); +static void iscsi_host_dec_session_cnt(struct Scsi_Host *shost) +{ + struct iscsi_host *ihost = shost_priv(shost); + unsigned long flags; + + shost = scsi_host_get(shost); + if (!shost) { + printk(KERN_ERR "Invalid state. Cannot notify host removal " + "of session teardown event because host already " + "removed.\n"); + return; + } + + spin_lock_irqsave(&ihost->lock, flags); + ihost->num_sessions--; + if (ihost->num_sessions == 0) + wake_up(&ihost->session_removal_wq); + spin_unlock_irqrestore(&ihost->lock, flags); + scsi_host_put(shost); +} + /** * iscsi_session_setup - create iscsi cls session and host and session * @iscsit: iscsi transport template @@ -1967,9 +2123,19 @@ iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost, uint16_t cmds_max, int cmd_task_size, uint32_t initial_cmdsn, unsigned int id) { + struct iscsi_host *ihost = shost_priv(shost); struct iscsi_session *session; struct iscsi_cls_session *cls_session; int cmd_i, scsi_cmds, total_cmds = cmds_max; + unsigned long flags; + + spin_lock_irqsave(&ihost->lock, flags); + if (ihost->state == ISCSI_HOST_REMOVED) { + spin_unlock_irqrestore(&ihost->lock, flags); + return NULL; + } + ihost->num_sessions++; + spin_unlock_irqrestore(&ihost->lock, flags); if (!total_cmds) total_cmds = ISCSI_DEF_XMIT_CMDS_MAX; @@ -1982,7 +2148,7 @@ iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost, printk(KERN_ERR "iscsi: invalid can_queue of %d. can_queue " "must be a power of two that is at least %d.\n", total_cmds, ISCSI_TOTAL_CMDS_MIN); - return NULL; + goto dec_session_count; } if (total_cmds > ISCSI_TOTAL_CMDS_MAX) { @@ -2006,7 +2172,7 @@ iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost, cls_session = iscsi_alloc_session(shost, iscsit, sizeof(struct iscsi_session)); if (!cls_session) - return NULL; + goto dec_session_count; session = cls_session->dd_data; session->cls_session = cls_session; session->host = shost; @@ -2045,6 +2211,7 @@ iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost, if (iscsi_add_session(cls_session, id)) goto cls_session_fail; + return cls_session; cls_session_fail: @@ -2053,6 +2220,8 @@ module_get_fail: iscsi_pool_free(&session->cmdpool); cmdpool_alloc_fail: iscsi_free_session(cls_session); +dec_session_count: + iscsi_host_dec_session_cnt(shost); return NULL; } EXPORT_SYMBOL_GPL(iscsi_session_setup); @@ -2068,6 +2237,7 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session) { struct iscsi_session *session = cls_session->dd_data; struct module *owner = cls_session->transport->owner; + struct Scsi_Host *shost = session->host; iscsi_pool_free(&session->cmdpool); @@ -2080,6 +2250,7 @@ void iscsi_session_teardown(struct iscsi_cls_session *cls_session) kfree(session->ifacename); iscsi_destroy_session(cls_session); + iscsi_host_dec_session_cnt(shost); module_put(owner); } EXPORT_SYMBOL_GPL(iscsi_session_teardown); @@ -2135,7 +2306,8 @@ iscsi_conn_setup(struct iscsi_cls_session *cls_session, int dd_size, } spin_unlock_bh(&session->lock); - data = kmalloc(ISCSI_DEF_MAX_RECV_SEG_LEN, GFP_KERNEL); + data = (char *) __get_free_pages(GFP_KERNEL, + get_order(ISCSI_DEF_MAX_RECV_SEG_LEN)); if (!data) goto login_task_data_alloc_fail; conn->login_task->data = conn->data = data; @@ -2206,7 +2378,8 @@ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn) iscsi_suspend_tx(conn); spin_lock_bh(&session->lock); - kfree(conn->data); + free_pages((unsigned long) conn->data, + get_order(ISCSI_DEF_MAX_RECV_SEG_LEN)); kfree(conn->persistent_address); __kfifo_put(session->cmdpool.queue, (void*)&conn->login_task, sizeof(void*)); @@ -2359,8 +2532,10 @@ static void iscsi_start_session_recovery(struct iscsi_session *session, * flush queues. */ spin_lock_bh(&session->lock); - fail_all_commands(conn, -1, - STOP_CONN_RECOVER ? DID_BUS_BUSY : DID_ERROR); + if (flag == STOP_CONN_RECOVER) + fail_all_commands(conn, -1, DID_TRANSPORT_DISRUPTED); + else + fail_all_commands(conn, -1, DID_ERROR); flush_control_queues(session, conn); spin_unlock_bh(&session->lock); mutex_unlock(&session->eh_mutex); diff --git a/kernel/libiscsi.h b/kernel/libiscsi.h index cfc5fa6..a261e2c 100644 --- a/kernel/libiscsi.h +++ b/kernel/libiscsi.h @@ -28,9 +28,9 @@ #include <linux/mutex.h> #include <linux/timer.h> #include <linux/workqueue.h> - #include "iscsi_proto.h" #include "iscsi_if.h" +#include "scsi_transport_iscsi.h" struct scsi_transport_template; struct scsi_host_template; @@ -71,12 +71,12 @@ enum { /* Connection suspend "bit" */ #define ISCSI_SUSPEND_BIT 1 -#define ISCSI_ITT_MASK (0x1fff) +#define ISCSI_ITT_MASK 0x1fff #define ISCSI_TOTAL_CMDS_MAX 4096 /* this must be a power of two greater than ISCSI_MGMT_CMDS_MAX */ #define ISCSI_TOTAL_CMDS_MIN 16 #define ISCSI_AGE_SHIFT 28 -#define ISCSI_AGE_MASK (0xf << ISCSI_AGE_SHIFT) +#define ISCSI_AGE_MASK 0xf #define ISCSI_ADDRESS_BUF_LEN 64 @@ -94,24 +94,38 @@ enum { ISCSI_TASK_RUNNING, }; +struct iscsi_r2t_info { + __be32 ttt; /* copied from R2T */ + __be32 exp_statsn; /* copied from R2T */ + uint32_t data_length; /* copied from R2T */ + uint32_t data_offset; /* copied from R2T */ + int data_count; /* DATA-Out payload progress */ + int datasn; + /* LLDs should set/update these values */ + int sent; /* R2T sequence progress */ +}; + struct iscsi_task { /* * Because LLDs allocate their hdr differently, this is a pointer * and length to that storage. It must be setup at session * creation time. */ - struct iscsi_cmd *hdr; + struct iscsi_hdr *hdr; unsigned short hdr_max; unsigned short hdr_len; /* accumulated size of hdr used */ + /* copied values in case we need to send tmfs */ + itt_t hdr_itt; + __be32 cmdsn; + uint8_t lun[8]; + int itt; /* this ITT */ - uint32_t unsol_datasn; unsigned imm_count; /* imm-data (bytes) */ - unsigned unsol_count; /* unsolicited (bytes)*/ /* offset in unsolicited stream (bytes); */ - unsigned unsol_offset; - unsigned data_count; /* remaining Data-Out */ + struct iscsi_r2t_info unsol_r2t; char *data; /* mgmt payload */ + unsigned data_count; struct scsi_cmnd *sc; /* associated SCSI cmd*/ struct iscsi_conn *conn; /* used connection */ @@ -122,6 +136,11 @@ struct iscsi_task { void *dd_data; /* driver/transport data */ }; +static inline int iscsi_task_has_unsol_data(struct iscsi_task *task) +{ + return task->unsol_r2t.data_length > task->unsol_r2t.sent; +} + static inline void* iscsi_next_hdr(struct iscsi_task *task) { return (void*)task->hdr + task->hdr_len; @@ -288,6 +307,11 @@ struct iscsi_session { struct iscsi_pool cmdpool; /* PDU's pool */ }; +enum { + ISCSI_HOST_SETUP, + ISCSI_HOST_REMOVED, +}; + struct iscsi_host { char *initiatorname; /* hw address or netdev iscsi connection is bound to */ @@ -296,6 +320,12 @@ struct iscsi_host { /* local address */ int local_port; char local_address[ISCSI_ADDRESS_BUF_LEN]; + + wait_queue_head_t session_removal_wq; + /* protects sessions and state */ + spinlock_t lock; + int num_sessions; + int state; }; /* @@ -303,7 +333,7 @@ struct iscsi_host { */ extern int iscsi_change_queue_depth(struct scsi_device *sdev, int depth); extern int iscsi_eh_abort(struct scsi_cmnd *sc); -extern int iscsi_eh_host_reset(struct scsi_cmnd *sc); +extern int iscsi_eh_target_reset(struct scsi_cmnd *sc); extern int iscsi_eh_device_reset(struct scsi_cmnd *sc); extern int iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *)); @@ -352,6 +382,8 @@ extern void iscsi_conn_stop(struct iscsi_cls_conn *, int); extern int iscsi_conn_bind(struct iscsi_cls_session *, struct iscsi_cls_conn *, int); extern void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err); +extern void iscsi_session_failure(struct iscsi_cls_session *cls_session, + enum iscsi_err err); extern int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, char *buf); extern void iscsi_suspend_tx(struct iscsi_conn *conn); @@ -364,8 +396,9 @@ extern void iscsi_suspend_tx(struct iscsi_conn *conn); * pdu and task processing */ extern void iscsi_update_cmdsn(struct iscsi_session *, struct iscsi_nopin *); -extern void iscsi_prep_unsolicit_data_pdu(struct iscsi_task *, - struct iscsi_data *hdr); +extern void iscsi_prep_data_out_pdu(struct iscsi_task *task, + struct iscsi_r2t_info *r2t, + struct iscsi_data *hdr); extern int iscsi_conn_send_pdu(struct iscsi_cls_conn *, struct iscsi_hdr *, char *, uint32_t); extern int iscsi_complete_pdu(struct iscsi_conn *, struct iscsi_hdr *, diff --git a/kernel/libiscsi_tcp.c b/kernel/libiscsi_tcp.c new file mode 100644 index 0000000..92cb13d --- /dev/null +++ b/kernel/libiscsi_tcp.c @@ -0,0 +1,1162 @@ +/* + * iSCSI over TCP/IP Data-Path lib + * + * Copyright (C) 2004 Dmitry Yusupov + * Copyright (C) 2004 Alex Aizman + * Copyright (C) 2005 - 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + * + * Credits: + * Christoph Hellwig + * FUJITA Tomonori + * Arne Redlich + * Zhenyu Wang + */ + +#include <linux/types.h> +#include <linux/list.h> +#include <linux/inet.h> +#include <linux/file.h> +#include <linux/blkdev.h> +#include <linux/crypto.h> +#include <linux/delay.h> +#include <linux/kfifo.h> +#include <linux/scatterlist.h> +#include <net/tcp.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi.h> +#include "scsi_transport_iscsi.h" + +#include "iscsi_tcp.h" + +MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, " + "Dmitry Yusupov <dmitry_yus@yahoo.com>, " + "Alex Aizman <itn780@yahoo.com>"); +MODULE_DESCRIPTION("iSCSI/TCP data-path"); +MODULE_LICENSE("GPL"); +#undef DEBUG_TCP + +#ifdef DEBUG_TCP +#define debug_tcp(fmt...) printk(KERN_INFO "tcp: " fmt) +#else +#define debug_tcp(fmt...) +#endif + +static int iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment); + +/* + * Scatterlist handling: inside the iscsi_segment, we + * remember an index into the scatterlist, and set data/size + * to the current scatterlist entry. For highmem pages, we + * kmap as needed. + * + * Note that the page is unmapped when we return from + * TCP's data_ready handler, so we may end up mapping and + * unmapping the same page repeatedly. The whole reason + * for this is that we shouldn't keep the page mapped + * outside the softirq. + */ + +/** + * iscsi_tcp_segment_init_sg - init indicated scatterlist entry + * @segment: the buffer object + * @sg: scatterlist + * @offset: byte offset into that sg entry + * + * This function sets up the segment so that subsequent + * data is copied to the indicated sg entry, at the given + * offset. + */ +static inline void +iscsi_tcp_segment_init_sg(struct iscsi_segment *segment, + struct scatterlist *sg, unsigned int offset) +{ + segment->sg = sg; + segment->sg_offset = offset; + segment->size = min(sg->length - offset, + segment->total_size - segment->total_copied); + segment->data = NULL; +} + +/** + * iscsi_tcp_segment_map - map the current S/G page + * @segment: iscsi_segment + * @recv: 1 if called from recv path + * + * We only need to possibly kmap data if scatter lists are being used, + * because the iscsi passthrough and internal IO paths will never use high + * mem pages. + */ +static void iscsi_tcp_segment_map(struct iscsi_segment *segment, int recv) +{ + struct scatterlist *sg; + + if (segment->data != NULL || !segment->sg) + return; + + sg = segment->sg; + BUG_ON(segment->sg_mapped); + BUG_ON(sg->length == 0); + + /* + * If the page count is greater than one it is ok to send + * to the network layer's zero copy send path. If not we + * have to go the slow sendmsg path. We always map for the + * recv path. + */ + if (page_count(sg_page(sg)) >= 1 && !recv) + return; + + debug_tcp("iscsi_tcp_segment_map %s %p\n", recv ? "recv" : "xmit", + segment); + segment->sg_mapped = kmap_atomic(sg_page(sg), KM_SOFTIRQ0); + segment->data = segment->sg_mapped + sg->offset + segment->sg_offset; +} + +void iscsi_tcp_segment_unmap(struct iscsi_segment *segment) +{ + debug_tcp("iscsi_tcp_segment_unmap %p\n", segment); + + if (segment->sg_mapped) { + debug_tcp("iscsi_tcp_segment_unmap valid\n"); + kunmap_atomic(segment->sg_mapped, KM_SOFTIRQ0); + segment->sg_mapped = NULL; + segment->data = NULL; + } +} +EXPORT_SYMBOL_GPL(iscsi_tcp_segment_unmap); + +/* + * Splice the digest buffer into the buffer + */ +static inline void +iscsi_tcp_segment_splice_digest(struct iscsi_segment *segment, void *digest) +{ + segment->data = digest; + segment->digest_len = ISCSI_DIGEST_SIZE; + segment->total_size += ISCSI_DIGEST_SIZE; + segment->size = ISCSI_DIGEST_SIZE; + segment->copied = 0; + segment->sg = NULL; + segment->hash = NULL; +} + +/** + * iscsi_tcp_segment_done - check whether the segment is complete + * @tcp_conn: iscsi tcp connection + * @segment: iscsi segment to check + * @recv: set to one of this is called from the recv path + * @copied: number of bytes copied + * + * Check if we're done receiving this segment. If the receive + * buffer is full but we expect more data, move on to the + * next entry in the scatterlist. + * + * If the amount of data we received isn't a multiple of 4, + * we will transparently receive the pad bytes, too. + * + * This function must be re-entrant. + */ +int iscsi_tcp_segment_done(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment, int recv, + unsigned copied) +{ + struct scatterlist sg; + unsigned int pad; + + debug_tcp("copied %u %u size %u %s\n", segment->copied, copied, + segment->size, recv ? "recv" : "xmit"); + if (segment->hash && copied) { + /* + * If a segment is kmapd we must unmap it before sending + * to the crypto layer since that will try to kmap it again. + */ + iscsi_tcp_segment_unmap(segment); + + if (!segment->data) { + sg_init_table(&sg, 1); + sg_set_page(&sg, sg_page(segment->sg), copied, + segment->copied + segment->sg_offset + + segment->sg->offset); + } else + sg_init_one(&sg, segment->data + segment->copied, + copied); + crypto_hash_update(segment->hash, &sg, copied); + } + + segment->copied += copied; + if (segment->copied < segment->size) { + iscsi_tcp_segment_map(segment, recv); + return 0; + } + + segment->total_copied += segment->copied; + segment->copied = 0; + segment->size = 0; + + /* Unmap the current scatterlist page, if there is one. */ + iscsi_tcp_segment_unmap(segment); + + /* Do we have more scatterlist entries? */ + debug_tcp("total copied %u total size %u\n", segment->total_copied, + segment->total_size); + if (segment->total_copied < segment->total_size) { + /* Proceed to the next entry in the scatterlist. */ + iscsi_tcp_segment_init_sg(segment, sg_next(segment->sg), + 0); + iscsi_tcp_segment_map(segment, recv); + BUG_ON(segment->size == 0); + return 0; + } + + /* Do we need to handle padding? */ + if (!(tcp_conn->iscsi_conn->session->tt->caps & CAP_PADDING_OFFLOAD)) { + pad = iscsi_padding(segment->total_copied); + if (pad != 0) { + debug_tcp("consume %d pad bytes\n", pad); + segment->total_size += pad; + segment->size = pad; + segment->data = segment->padbuf; + return 0; + } + } + + /* + * Set us up for transferring the data digest. hdr digest + * is completely handled in hdr done function. + */ + if (segment->hash) { + crypto_hash_final(segment->hash, segment->digest); + iscsi_tcp_segment_splice_digest(segment, + recv ? segment->recv_digest : segment->digest); + return 0; + } + + return 1; +} +EXPORT_SYMBOL_GPL(iscsi_tcp_segment_done); + +/** + * iscsi_tcp_segment_recv - copy data to segment + * @tcp_conn: the iSCSI TCP connection + * @segment: the buffer to copy to + * @ptr: data pointer + * @len: amount of data available + * + * This function copies up to @len bytes to the + * given buffer, and returns the number of bytes + * consumed, which can actually be less than @len. + * + * If hash digest is enabled, the function will update the + * hash while copying. + * Combining these two operations doesn't buy us a lot (yet), + * but in the future we could implement combined copy+crc, + * just way we do for network layer checksums. + */ +static int +iscsi_tcp_segment_recv(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment, const void *ptr, + unsigned int len) +{ + unsigned int copy = 0, copied = 0; + + while (!iscsi_tcp_segment_done(tcp_conn, segment, 1, copy)) { + if (copied == len) { + debug_tcp("iscsi_tcp_segment_recv copied %d bytes\n", + len); + break; + } + + copy = min(len - copied, segment->size - segment->copied); + debug_tcp("iscsi_tcp_segment_recv copying %d\n", copy); + memcpy(segment->data + segment->copied, ptr + copied, copy); + copied += copy; + } + return copied; +} + +inline void +iscsi_tcp_dgst_header(struct hash_desc *hash, const void *hdr, size_t hdrlen, + unsigned char digest[ISCSI_DIGEST_SIZE]) +{ + struct scatterlist sg; + + sg_init_one(&sg, hdr, hdrlen); + crypto_hash_digest(hash, &sg, hdrlen, digest); +} +EXPORT_SYMBOL_GPL(iscsi_tcp_dgst_header); + +static inline int +iscsi_tcp_dgst_verify(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment) +{ + if (!segment->digest_len) + return 1; + + if (memcmp(segment->recv_digest, segment->digest, + segment->digest_len)) { + debug_scsi("digest mismatch\n"); + return 0; + } + + return 1; +} + +/* + * Helper function to set up segment buffer + */ +static inline void +__iscsi_segment_init(struct iscsi_segment *segment, size_t size, + iscsi_segment_done_fn_t *done, struct hash_desc *hash) +{ + memset(segment, 0, sizeof(*segment)); + segment->total_size = size; + segment->done = done; + + if (hash) { + segment->hash = hash; + crypto_hash_init(hash); + } +} + +inline void +iscsi_segment_init_linear(struct iscsi_segment *segment, void *data, + size_t size, iscsi_segment_done_fn_t *done, + struct hash_desc *hash) +{ + __iscsi_segment_init(segment, size, done, hash); + segment->data = data; + segment->size = size; +} +EXPORT_SYMBOL_GPL(iscsi_segment_init_linear); + +inline int +iscsi_segment_seek_sg(struct iscsi_segment *segment, + struct scatterlist *sg_list, unsigned int sg_count, + unsigned int offset, size_t size, + iscsi_segment_done_fn_t *done, struct hash_desc *hash) +{ + struct scatterlist *sg; + unsigned int i; + + debug_scsi("iscsi_segment_seek_sg offset %u size %llu\n", + offset, size); + __iscsi_segment_init(segment, size, done, hash); + for_each_sg(sg_list, sg, sg_count, i) { + debug_scsi("sg %d, len %u offset %u\n", i, sg->length, + sg->offset); + if (offset < sg->length) { + iscsi_tcp_segment_init_sg(segment, sg, offset); + return 0; + } + offset -= sg->length; + } + + return ISCSI_ERR_DATA_OFFSET; +} +EXPORT_SYMBOL_GPL(iscsi_segment_seek_sg); + +/** + * iscsi_tcp_hdr_recv_prep - prep segment for hdr reception + * @tcp_conn: iscsi connection to prep for + * + * This function always passes NULL for the hash argument, because when this + * function is called we do not yet know the final size of the header and want + * to delay the digest processing until we know that. + */ +void iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn) +{ + debug_tcp("iscsi_tcp_hdr_recv_prep(%p%s)\n", tcp_conn, + tcp_conn->iscsi_conn->hdrdgst_en ? ", digest enabled" : ""); + iscsi_segment_init_linear(&tcp_conn->in.segment, + tcp_conn->in.hdr_buf, sizeof(struct iscsi_hdr), + iscsi_tcp_hdr_recv_done, NULL); +} +EXPORT_SYMBOL_GPL(iscsi_tcp_hdr_recv_prep); + +/* + * Handle incoming reply to any other type of command + */ +static int +iscsi_tcp_data_recv_done(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment) +{ + struct iscsi_conn *conn = tcp_conn->iscsi_conn; + int rc = 0; + + if (!iscsi_tcp_dgst_verify(tcp_conn, segment)) + return ISCSI_ERR_DATA_DGST; + + rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, + conn->data, tcp_conn->in.datalen); + if (rc) + return rc; + + iscsi_tcp_hdr_recv_prep(tcp_conn); + return 0; +} + +static void +iscsi_tcp_data_recv_prep(struct iscsi_tcp_conn *tcp_conn) +{ + struct iscsi_conn *conn = tcp_conn->iscsi_conn; + struct hash_desc *rx_hash = NULL; + + if (conn->datadgst_en & + !(conn->session->tt->caps & CAP_DIGEST_OFFLOAD)) + rx_hash = tcp_conn->rx_hash; + + iscsi_segment_init_linear(&tcp_conn->in.segment, + conn->data, tcp_conn->in.datalen, + iscsi_tcp_data_recv_done, rx_hash); +} + +/** + * iscsi_tcp_cleanup_task - free tcp_task resources + * @task: iscsi task + * + * must be called with session lock + */ +void iscsi_tcp_cleanup_task(struct iscsi_task *task) +{ + struct iscsi_tcp_task *tcp_task = task->dd_data; + struct iscsi_r2t_info *r2t; + + /* nothing to do for mgmt or pending tasks */ + if (!task->sc || task->state == ISCSI_TASK_PENDING) + return; + + /* flush task's r2t queues */ + while (__kfifo_get(tcp_task->r2tqueue, (void*)&r2t, sizeof(void*))) { + __kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t, + sizeof(void*)); + debug_scsi("iscsi_tcp_cleanup_task pending r2t dropped\n"); + } + + r2t = tcp_task->r2t; + if (r2t != NULL) { + __kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t, + sizeof(void*)); + tcp_task->r2t = NULL; + } +} +EXPORT_SYMBOL_GPL(iscsi_tcp_cleanup_task); + +/** + * iscsi_tcp_data_in - SCSI Data-In Response processing + * @conn: iscsi connection + * @task: scsi command task + */ +static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_tcp_task *tcp_task = task->dd_data; + struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)tcp_conn->in.hdr; + int datasn = be32_to_cpu(rhdr->datasn); + unsigned total_in_length = scsi_in(task->sc)->length; + + iscsi_update_cmdsn(conn->session, (struct iscsi_nopin*)rhdr); + if (tcp_conn->in.datalen == 0) + return 0; + + if (tcp_task->exp_datasn != datasn) { + debug_tcp("%s: task->exp_datasn(%d) != rhdr->datasn(%d)\n", + __func__, tcp_task->exp_datasn, datasn); + return ISCSI_ERR_DATASN; + } + + tcp_task->exp_datasn++; + + tcp_task->data_offset = be32_to_cpu(rhdr->offset); + if (tcp_task->data_offset + tcp_conn->in.datalen > total_in_length) { + debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n", + __func__, tcp_task->data_offset, + tcp_conn->in.datalen, total_in_length); + return ISCSI_ERR_DATA_OFFSET; + } + + conn->datain_pdus_cnt++; + return 0; +} + +/** + * iscsi_tcp_r2t_rsp - iSCSI R2T Response processing + * @conn: iscsi connection + * @task: scsi command task + */ +static int iscsi_tcp_r2t_rsp(struct iscsi_conn *conn, struct iscsi_task *task) +{ + struct iscsi_session *session = conn->session; + struct iscsi_tcp_task *tcp_task = task->dd_data; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_r2t_rsp *rhdr = (struct iscsi_r2t_rsp *)tcp_conn->in.hdr; + struct iscsi_r2t_info *r2t; + int r2tsn = be32_to_cpu(rhdr->r2tsn); + int rc; + + if (tcp_conn->in.datalen) { + iscsi_conn_printk(KERN_ERR, conn, + "invalid R2t with datalen %d\n", + tcp_conn->in.datalen); + return ISCSI_ERR_DATALEN; + } + + if (tcp_task->exp_datasn != r2tsn){ + debug_tcp("%s: task->exp_datasn(%d) != rhdr->r2tsn(%d)\n", + __func__, tcp_task->exp_datasn, r2tsn); + return ISCSI_ERR_R2TSN; + } + + /* fill-in new R2T associated with the task */ + iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr); + + if (!task->sc || session->state != ISCSI_STATE_LOGGED_IN) { + iscsi_conn_printk(KERN_INFO, conn, + "dropping R2T itt %d in recovery.\n", + task->itt); + return 0; + } + + rc = __kfifo_get(tcp_task->r2tpool.queue, (void*)&r2t, sizeof(void*)); + if (!rc) { + iscsi_conn_printk(KERN_ERR, conn, "Could not allocate R2T. " + "Target has sent more R2Ts than it " + "negotiated for or driver has has leaked.\n"); + return ISCSI_ERR_PROTO; + } + + r2t->exp_statsn = rhdr->statsn; + r2t->data_length = be32_to_cpu(rhdr->data_length); + if (r2t->data_length == 0) { + iscsi_conn_printk(KERN_ERR, conn, + "invalid R2T with zero data len\n"); + __kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t, + sizeof(void*)); + return ISCSI_ERR_DATALEN; + } + + if (r2t->data_length > session->max_burst) + debug_scsi("invalid R2T with data len %u and max burst %u." + "Attempting to execute request.\n", + r2t->data_length, session->max_burst); + + r2t->data_offset = be32_to_cpu(rhdr->data_offset); + if (r2t->data_offset + r2t->data_length > scsi_out(task->sc)->length) { + iscsi_conn_printk(KERN_ERR, conn, + "invalid R2T with data len %u at offset %u " + "and total length %d\n", r2t->data_length, + r2t->data_offset, scsi_out(task->sc)->length); + __kfifo_put(tcp_task->r2tpool.queue, (void*)&r2t, + sizeof(void*)); + return ISCSI_ERR_DATALEN; + } + + r2t->ttt = rhdr->ttt; /* no flip */ + r2t->datasn = 0; + r2t->sent = 0; + + tcp_task->exp_datasn = r2tsn + 1; + __kfifo_put(tcp_task->r2tqueue, (void*)&r2t, sizeof(void*)); + conn->r2t_pdus_cnt++; + + iscsi_requeue_task(task); + return 0; +} + +/* + * Handle incoming reply to DataIn command + */ +static int +iscsi_tcp_process_data_in(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment) +{ + struct iscsi_conn *conn = tcp_conn->iscsi_conn; + struct iscsi_hdr *hdr = tcp_conn->in.hdr; + int rc; + + if (!iscsi_tcp_dgst_verify(tcp_conn, segment)) + return ISCSI_ERR_DATA_DGST; + + /* check for non-exceptional status */ + if (hdr->flags & ISCSI_FLAG_DATA_STATUS) { + rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0); + if (rc) + return rc; + } + + iscsi_tcp_hdr_recv_prep(tcp_conn); + return 0; +} + +/** + * iscsi_tcp_hdr_dissect - process PDU header + * @conn: iSCSI connection + * @hdr: PDU header + * + * This function analyzes the header of the PDU received, + * and performs several sanity checks. If the PDU is accompanied + * by data, the receive buffer is set up to copy the incoming data + * to the correct location. + */ +static int +iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr) +{ + int rc = 0, opcode, ahslen; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_task *task; + + /* verify PDU length */ + tcp_conn->in.datalen = ntoh24(hdr->dlength); + if (tcp_conn->in.datalen > conn->max_recv_dlength) { + iscsi_conn_printk(KERN_ERR, conn, + "iscsi_tcp: datalen %d > %d\n", + tcp_conn->in.datalen, conn->max_recv_dlength); + return ISCSI_ERR_DATALEN; + } + + /* Additional header segments. So far, we don't + * process additional headers. + */ + ahslen = hdr->hlength << 2; + + opcode = hdr->opcode & ISCSI_OPCODE_MASK; + /* verify itt (itt encoding: age+cid+itt) */ + rc = iscsi_verify_itt(conn, hdr->itt); + if (rc) + return rc; + + debug_tcp("opcode 0x%x ahslen %d datalen %d\n", + opcode, ahslen, tcp_conn->in.datalen); + + switch(opcode) { + case ISCSI_OP_SCSI_DATA_IN: + spin_lock(&conn->session->lock); + task = iscsi_itt_to_ctask(conn, hdr->itt); + if (!task) + rc = ISCSI_ERR_BAD_ITT; + else + rc = iscsi_tcp_data_in(conn, task); + if (rc) { + spin_unlock(&conn->session->lock); + break; + } + + if (tcp_conn->in.datalen) { + struct iscsi_tcp_task *tcp_task = task->dd_data; + struct hash_desc *rx_hash = NULL; + struct scsi_data_buffer *sdb = scsi_in(task->sc); + + /* + * Setup copy of Data-In into the Scsi_Cmnd + * Scatterlist case: + * We set up the iscsi_segment to point to the next + * scatterlist entry to copy to. As we go along, + * we move on to the next scatterlist entry and + * update the digest per-entry. + */ + if (conn->datadgst_en && + !(conn->session->tt->caps & CAP_DIGEST_OFFLOAD)) + rx_hash = tcp_conn->rx_hash; + + debug_tcp("iscsi_tcp_begin_data_in(%p, offset=%d, " + "datalen=%d)\n", tcp_conn, + tcp_task->data_offset, + tcp_conn->in.datalen); + rc = iscsi_segment_seek_sg(&tcp_conn->in.segment, + sdb->table.sgl, + sdb->table.nents, + tcp_task->data_offset, + tcp_conn->in.datalen, + iscsi_tcp_process_data_in, + rx_hash); + spin_unlock(&conn->session->lock); + return rc; + } + rc = __iscsi_complete_pdu(conn, hdr, NULL, 0); + spin_unlock(&conn->session->lock); + break; + case ISCSI_OP_SCSI_CMD_RSP: + if (tcp_conn->in.datalen) { + iscsi_tcp_data_recv_prep(tcp_conn); + return 0; + } + rc = iscsi_complete_pdu(conn, hdr, NULL, 0); + break; + case ISCSI_OP_R2T: + spin_lock(&conn->session->lock); + task = iscsi_itt_to_ctask(conn, hdr->itt); + if (!task) + rc = ISCSI_ERR_BAD_ITT; + else if (ahslen) + rc = ISCSI_ERR_AHSLEN; + else if (task->sc->sc_data_direction == DMA_TO_DEVICE) + rc = iscsi_tcp_r2t_rsp(conn, task); + else + rc = ISCSI_ERR_PROTO; + spin_unlock(&conn->session->lock); + break; + case ISCSI_OP_LOGIN_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_REJECT: + case ISCSI_OP_ASYNC_EVENT: + /* + * It is possible that we could get a PDU with a buffer larger + * than 8K, but there are no targets that currently do this. + * For now we fail until we find a vendor that needs it + */ + if (ISCSI_DEF_MAX_RECV_SEG_LEN < tcp_conn->in.datalen) { + iscsi_conn_printk(KERN_ERR, conn, + "iscsi_tcp: received buffer of " + "len %u but conn buffer is only %u " + "(opcode %0x)\n", + tcp_conn->in.datalen, + ISCSI_DEF_MAX_RECV_SEG_LEN, opcode); + rc = ISCSI_ERR_PROTO; + break; + } + + /* If there's data coming in with the response, + * receive it to the connection's buffer. + */ + if (tcp_conn->in.datalen) { + iscsi_tcp_data_recv_prep(tcp_conn); + return 0; + } + /* fall through */ + case ISCSI_OP_LOGOUT_RSP: + case ISCSI_OP_NOOP_IN: + case ISCSI_OP_SCSI_TMFUNC_RSP: + rc = iscsi_complete_pdu(conn, hdr, NULL, 0); + break; + default: + rc = ISCSI_ERR_BAD_OPCODE; + break; + } + + if (rc == 0) { + /* Anything that comes with data should have + * been handled above. */ + if (tcp_conn->in.datalen) + return ISCSI_ERR_PROTO; + iscsi_tcp_hdr_recv_prep(tcp_conn); + } + + return rc; +} + +/** + * iscsi_tcp_hdr_recv_done - process PDU header + * + * This is the callback invoked when the PDU header has + * been received. If the header is followed by additional + * header segments, we go back for more data. + */ +static int +iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment) +{ + struct iscsi_conn *conn = tcp_conn->iscsi_conn; + struct iscsi_hdr *hdr; + + /* Check if there are additional header segments + * *prior* to computing the digest, because we + * may need to go back to the caller for more. + */ + hdr = (struct iscsi_hdr *) tcp_conn->in.hdr_buf; + if (segment->copied == sizeof(struct iscsi_hdr) && hdr->hlength) { + /* Bump the header length - the caller will + * just loop around and get the AHS for us, and + * call again. */ + unsigned int ahslen = hdr->hlength << 2; + + /* Make sure we don't overflow */ + if (sizeof(*hdr) + ahslen > sizeof(tcp_conn->in.hdr_buf)) + return ISCSI_ERR_AHSLEN; + + segment->total_size += ahslen; + segment->size += ahslen; + return 0; + } + + /* We're done processing the header. See if we're doing + * header digests; if so, set up the recv_digest buffer + * and go back for more. */ + if (conn->hdrdgst_en && + !(conn->session->tt->caps & CAP_DIGEST_OFFLOAD)) { + if (segment->digest_len == 0) { + /* + * Even if we offload the digest processing we + * splice it in so we can increment the skb/segment + * counters in preparation for the data segment. + */ + iscsi_tcp_segment_splice_digest(segment, + segment->recv_digest); + return 0; + } + + iscsi_tcp_dgst_header(tcp_conn->rx_hash, hdr, + segment->total_copied - ISCSI_DIGEST_SIZE, + segment->digest); + + if (!iscsi_tcp_dgst_verify(tcp_conn, segment)) + return ISCSI_ERR_HDR_DGST; + } + + tcp_conn->in.hdr = hdr; + return iscsi_tcp_hdr_dissect(conn, hdr); +} + +/** + * iscsi_tcp_recv_segment_is_hdr - tests if we are reading in a header + * @tcp_conn: iscsi tcp conn + * + * returns non zero if we are currently processing or setup to process + * a header. + */ +inline int iscsi_tcp_recv_segment_is_hdr(struct iscsi_tcp_conn *tcp_conn) +{ + return tcp_conn->in.segment.done == iscsi_tcp_hdr_recv_done; +} +EXPORT_SYMBOL_GPL(iscsi_tcp_recv_segment_is_hdr); + +/** + * iscsi_tcp_recv_skb - Process skb + * @conn: iscsi connection + * @skb: network buffer with header and/or data segment + * @offset: offset in skb + * @offload: bool indicating if transfer was offloaded + * + * Will return status of transfer in status. And will return + * number of bytes copied. + */ +int iscsi_tcp_recv_skb(struct iscsi_conn *conn, struct sk_buff *skb, + unsigned int offset, bool offloaded, int *status) +{ + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_segment *segment = &tcp_conn->in.segment; + struct skb_seq_state seq; + unsigned int consumed = 0; + int rc = 0; + + debug_tcp("in %d bytes\n", skb->len - offset); + + if (unlikely(conn->suspend_rx)) { + debug_tcp("conn %d Rx suspended!\n", conn->id); + *status = ISCSI_TCP_SUSPENDED; + return 0; + } + + if (offloaded) { + segment->total_copied = segment->total_size; + goto segment_done; + } + + skb_prepare_seq_read(skb, offset, skb->len, &seq); + while (1) { + unsigned int avail; + const u8 *ptr; + + avail = skb_seq_read(consumed, &ptr, &seq); + if (avail == 0) { + debug_tcp("no more data avail. Consumed %d\n", + consumed); + *status = ISCSI_TCP_SKB_DONE; + skb_abort_seq_read(&seq); + goto skb_done; + } + BUG_ON(segment->copied >= segment->size); + + debug_tcp("skb %p ptr=%p avail=%u\n", skb, ptr, avail); + rc = iscsi_tcp_segment_recv(tcp_conn, segment, ptr, avail); + BUG_ON(rc == 0); + consumed += rc; + + if (segment->total_copied >= segment->total_size) { + skb_abort_seq_read(&seq); + goto segment_done; + } + } + +segment_done: + *status = ISCSI_TCP_SEGMENT_DONE; + debug_tcp("segment done\n"); + rc = segment->done(tcp_conn, segment); + if (rc != 0) { + *status = ISCSI_TCP_CONN_ERR; + debug_tcp("Error receiving PDU, errno=%d\n", rc); + iscsi_conn_failure(conn, rc); + return 0; + } + /* The done() functions sets up the next segment. */ + +skb_done: + conn->rxdata_octets += consumed; + return consumed; +} +EXPORT_SYMBOL_GPL(iscsi_tcp_recv_skb); + +/** + * iscsi_tcp_task_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands + * @conn: iscsi connection + * @task: scsi command task + * @sc: scsi command + */ +int iscsi_tcp_task_init(struct iscsi_task *task) +{ + struct iscsi_tcp_task *tcp_task = task->dd_data; + struct iscsi_conn *conn = task->conn; + struct scsi_cmnd *sc = task->sc; + int err; + + if (!sc) { + /* + * mgmt tasks do not have a scatterlist since they come + * in from the iscsi interface. + */ + debug_scsi("mtask deq [cid %d itt 0x%x]\n", conn->id, + task->itt); + + return conn->session->tt->init_pdu(task, 0, task->data_count); + } + + BUG_ON(__kfifo_len(tcp_task->r2tqueue)); + tcp_task->exp_datasn = 0; + + /* Prepare PDU, optionally w/ immediate data */ + debug_scsi("task deq [cid %d itt 0x%x imm %d unsol %d]\n", + conn->id, task->itt, task->imm_count, + task->unsol_r2t.data_length); + + err = conn->session->tt->init_pdu(task, 0, task->imm_count); + if (err) + return err; + task->imm_count = 0; + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_tcp_task_init); + +static struct iscsi_r2t_info *iscsi_tcp_get_curr_r2t(struct iscsi_task *task) +{ + struct iscsi_session *session = task->conn->session; + struct iscsi_tcp_task *tcp_task = task->dd_data; + struct iscsi_r2t_info *r2t = NULL; + + if (iscsi_task_has_unsol_data(task)) + r2t = &task->unsol_r2t; + else { + spin_lock_bh(&session->lock); + if (tcp_task->r2t) { + r2t = tcp_task->r2t; + /* Continue with this R2T? */ + if (r2t->data_length <= r2t->sent) { + debug_scsi(" done with r2t %p\n", r2t); + __kfifo_put(tcp_task->r2tpool.queue, + (void *)&tcp_task->r2t, + sizeof(void *)); + tcp_task->r2t = r2t = NULL; + } + } + + if (r2t == NULL) { + __kfifo_get(tcp_task->r2tqueue, + (void *)&tcp_task->r2t, sizeof(void *)); + r2t = tcp_task->r2t; + } + spin_unlock_bh(&session->lock); + } + + return r2t; +} + +/** + * iscsi_tcp_task_xmit - xmit normal PDU task + * @task: iscsi command task + * + * We're expected to return 0 when everything was transmitted succesfully, + * -EAGAIN if there's still data in the queue, or != 0 for any other kind + * of error. + */ +int iscsi_tcp_task_xmit(struct iscsi_task *task) +{ + struct iscsi_conn *conn = task->conn; + struct iscsi_session *session = conn->session; + struct iscsi_r2t_info *r2t; + int rc = 0; + +flush: + /* Flush any pending data first. */ + rc = session->tt->xmit_pdu(task); + if (rc < 0) + return rc; + + /* mgmt command */ + if (!task->sc) { + if (task->hdr->itt == RESERVED_ITT) + iscsi_put_task(task); + return 0; + } + + /* Are we done already? */ + if (task->sc->sc_data_direction != DMA_TO_DEVICE) + return 0; + + r2t = iscsi_tcp_get_curr_r2t(task); + if (r2t == NULL) { + /* Waiting for more R2Ts to arrive. */ + debug_tcp("no R2Ts yet\n"); + return 0; + } + + rc = conn->session->tt->alloc_pdu(task, ISCSI_OP_SCSI_DATA_OUT); + if (rc) + return rc; + iscsi_prep_data_out_pdu(task, r2t, (struct iscsi_data *) task->hdr); + + debug_scsi("sol dout %p [dsn %d itt 0x%x doff %d dlen %d]\n", + r2t, r2t->datasn - 1, task->hdr->itt, + r2t->data_offset + r2t->sent, r2t->data_count); + + rc = conn->session->tt->init_pdu(task, r2t->data_offset + r2t->sent, + r2t->data_count); + if (rc) + return rc; + r2t->sent += r2t->data_count; + goto flush; +} +EXPORT_SYMBOL_GPL(iscsi_tcp_task_xmit); + +struct iscsi_cls_conn * +iscsi_tcp_conn_setup(struct iscsi_cls_session *cls_session, int dd_data_size, + uint32_t conn_idx) + +{ + struct iscsi_conn *conn; + struct iscsi_cls_conn *cls_conn; + struct iscsi_tcp_conn *tcp_conn; + + cls_conn = iscsi_conn_setup(cls_session, sizeof(*tcp_conn), conn_idx); + if (!cls_conn) + return NULL; + conn = cls_conn->dd_data; + /* + * due to strange issues with iser these are not set + * in iscsi_conn_setup + */ + conn->max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; + + tcp_conn = conn->dd_data; + tcp_conn->iscsi_conn = conn; + + tcp_conn->dd_data = kzalloc(dd_data_size, GFP_KERNEL); + if (!tcp_conn->dd_data) { + iscsi_conn_teardown(cls_conn); + return NULL; + } + return cls_conn; +} +EXPORT_SYMBOL_GPL(iscsi_tcp_conn_setup); + +void iscsi_tcp_conn_teardown(struct iscsi_cls_conn *cls_conn) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + + kfree(tcp_conn->dd_data); + iscsi_conn_teardown(cls_conn); +} +EXPORT_SYMBOL_GPL(iscsi_tcp_conn_teardown); + +int iscsi_tcp_r2tpool_alloc(struct iscsi_session *session) +{ + int i; + int cmd_i; + + /* + * initialize per-task: R2T pool and xmit queue + */ + for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { + struct iscsi_task *task = session->cmds[cmd_i]; + struct iscsi_tcp_task *tcp_task = task->dd_data; + + /* + * pre-allocated x2 as much r2ts to handle race when + * target acks DataOut faster than we data_xmit() queues + * could replenish r2tqueue. + */ + + /* R2T pool */ + if (iscsi_pool_init(&tcp_task->r2tpool, + session->max_r2t * 2, NULL, + sizeof(struct iscsi_r2t_info))) { + goto r2t_alloc_fail; + } + + /* R2T xmit queue */ + tcp_task->r2tqueue = kfifo_alloc( + session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL); + if (tcp_task->r2tqueue == ERR_PTR(-ENOMEM)) { + iscsi_pool_free(&tcp_task->r2tpool); + goto r2t_alloc_fail; + } + } + + return 0; + +r2t_alloc_fail: + for (i = 0; i < cmd_i; i++) { + struct iscsi_task *task = session->cmds[i]; + struct iscsi_tcp_task *tcp_task = task->dd_data; + + kfifo_free(tcp_task->r2tqueue); + iscsi_pool_free(&tcp_task->r2tpool); + } + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(iscsi_tcp_r2tpool_alloc); + +void iscsi_tcp_r2tpool_free(struct iscsi_session *session) +{ + int i; + + for (i = 0; i < session->cmds_max; i++) { + struct iscsi_task *task = session->cmds[i]; + struct iscsi_tcp_task *tcp_task = task->dd_data; + + kfifo_free(tcp_task->r2tqueue); + iscsi_pool_free(&tcp_task->r2tpool); + } +} +EXPORT_SYMBOL_GPL(iscsi_tcp_r2tpool_free); + +void iscsi_tcp_conn_get_stats(struct iscsi_cls_conn *cls_conn, + struct iscsi_stats *stats) +{ + struct iscsi_conn *conn = cls_conn->dd_data; + + stats->txdata_octets = conn->txdata_octets; + stats->rxdata_octets = conn->rxdata_octets; + stats->scsicmd_pdus = conn->scsicmd_pdus_cnt; + stats->dataout_pdus = conn->dataout_pdus_cnt; + stats->scsirsp_pdus = conn->scsirsp_pdus_cnt; + stats->datain_pdus = conn->datain_pdus_cnt; + stats->r2t_pdus = conn->r2t_pdus_cnt; + stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt; + stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt; +} +EXPORT_SYMBOL_GPL(iscsi_tcp_conn_get_stats); diff --git a/kernel/libiscsi_tcp.h b/kernel/libiscsi_tcp.h new file mode 100644 index 0000000..3bacef5 --- /dev/null +++ b/kernel/libiscsi_tcp.h @@ -0,0 +1,133 @@ +/* + * iSCSI over TCP/IP Data-Path lib + * + * Copyright (C) 2008 Mike Christie + * Copyright (C) 2008 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef LIBISCSI_TCP_H +#define LIBISCSI_TCP_H + +#include "libiscsi.h" + +struct iscsi_tcp_conn; +struct iscsi_segment; +struct sk_buff; +struct hash_desc; + +typedef int iscsi_segment_done_fn_t(struct iscsi_tcp_conn *, + struct iscsi_segment *); + +struct iscsi_segment { + unsigned char *data; + unsigned int size; + unsigned int copied; + unsigned int total_size; + unsigned int total_copied; + + struct hash_desc *hash; + unsigned char padbuf[ISCSI_PAD_LEN]; + unsigned char recv_digest[ISCSI_DIGEST_SIZE]; + unsigned char digest[ISCSI_DIGEST_SIZE]; + unsigned int digest_len; + + struct scatterlist *sg; + void *sg_mapped; + unsigned int sg_offset; + + iscsi_segment_done_fn_t *done; +}; + +/* Socket connection recieve helper */ +struct iscsi_tcp_recv { + struct iscsi_hdr *hdr; + struct iscsi_segment segment; + + /* Allocate buffer for BHS + AHS */ + uint32_t hdr_buf[64]; + + /* copied and flipped values */ + int datalen; +}; + +struct iscsi_tcp_conn { + struct iscsi_conn *iscsi_conn; + void *dd_data; + int stop_stage; /* conn_stop() flag: * + * stop to recover, * + * stop to terminate */ + /* control data */ + struct iscsi_tcp_recv in; /* TCP receive context */ + /* CRC32C (Rx) LLD should set this is they do not offload */ + struct hash_desc *rx_hash; +}; + +struct iscsi_tcp_task { + uint32_t exp_datasn; /* expected target's R2TSN/DataSN */ + int data_offset; + struct iscsi_r2t_info *r2t; /* in progress solict R2T */ + struct iscsi_pool r2tpool; + struct kfifo *r2tqueue; + void *dd_data; +}; + +enum { + ISCSI_TCP_SEGMENT_DONE, /* curr seg has been processed */ + ISCSI_TCP_SKB_DONE, /* skb is out of data */ + ISCSI_TCP_CONN_ERR, /* iscsi layer has fired a conn err */ + ISCSI_TCP_SUSPENDED, /* conn is suspended */ +}; + +extern void iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn); +extern int iscsi_tcp_recv_skb(struct iscsi_conn *conn, struct sk_buff *skb, + unsigned int offset, bool offloaded, int *status); +extern void iscsi_tcp_cleanup_task(struct iscsi_task *task); +extern int iscsi_tcp_task_init(struct iscsi_task *task); +extern int iscsi_tcp_task_xmit(struct iscsi_task *task); + +/* segment helpers */ +extern int iscsi_tcp_recv_segment_is_hdr(struct iscsi_tcp_conn *tcp_conn); +extern int iscsi_tcp_segment_done(struct iscsi_tcp_conn *tcp_conn, + struct iscsi_segment *segment, int recv, + unsigned copied); +extern void iscsi_tcp_segment_unmap(struct iscsi_segment *segment); + +extern void iscsi_segment_init_linear(struct iscsi_segment *segment, + void *data, size_t size, + iscsi_segment_done_fn_t *done, + struct hash_desc *hash); +extern int +iscsi_segment_seek_sg(struct iscsi_segment *segment, + struct scatterlist *sg_list, unsigned int sg_count, + unsigned int offset, size_t size, + iscsi_segment_done_fn_t *done, struct hash_desc *hash); + +/* digest helpers */ +extern void iscsi_tcp_dgst_header(struct hash_desc *hash, const void *hdr, + size_t hdrlen, + unsigned char digest[ISCSI_DIGEST_SIZE]); +extern struct iscsi_cls_conn * +iscsi_tcp_conn_setup(struct iscsi_cls_session *cls_session, int dd_data_size, + uint32_t conn_idx); +extern void iscsi_tcp_conn_teardown(struct iscsi_cls_conn *cls_conn); + +/* misc helpers */ +extern int iscsi_tcp_r2tpool_alloc(struct iscsi_session *session); +extern void iscsi_tcp_r2tpool_free(struct iscsi_session *session); + +extern void iscsi_tcp_conn_get_stats(struct iscsi_cls_conn *cls_conn, + struct iscsi_stats *stats); +#endif /* LIBISCSI_TCP_H */ diff --git a/kernel/scsi_transport_iscsi.c b/kernel/scsi_transport_iscsi.c index 852b016..4781d81 100644 --- a/kernel/scsi_transport_iscsi.c +++ b/kernel/scsi_transport_iscsi.c @@ -187,8 +187,7 @@ iscsi_create_endpoint(int dd_size) ep->id = id; ep->dev.class = &iscsi_endpoint_class; - snprintf(ep->dev.bus_id, BUS_ID_SIZE, "ep-%llu", - (unsigned long long) id); + dev_set_name(&ep->dev, "ep-%llu", (unsigned long long) id); err = device_register(&ep->dev); if (err) goto free_ep; @@ -375,10 +374,10 @@ int iscsi_session_chkready(struct iscsi_cls_session *session) err = 0; break; case ISCSI_SESSION_FAILED: - err = DID_IMM_RETRY << 16; + err = DID_TRANSPORT_DISRUPTED << 16; break; case ISCSI_SESSION_FREE: - err = DID_NO_CONNECT << 16; + err = DID_TRANSPORT_FAILFAST << 16; break; default: err = DID_NO_CONNECT << 16; @@ -724,8 +723,7 @@ int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id) } session->target_id = id; - snprintf(session->dev.bus_id, BUS_ID_SIZE, "session%u", - session->sid); + dev_set_name(&session->dev, "session%u", session->sid); err = device_add(&session->dev); if (err) { iscsi_cls_session_printk(KERN_ERR, session, @@ -898,8 +896,7 @@ iscsi_create_conn(struct iscsi_cls_session *session, int dd_size, uint32_t cid) if (!get_device(&session->dev)) goto free_conn; - snprintf(conn->dev.bus_id, BUS_ID_SIZE, "connection%d:%u", - session->sid, cid); + dev_set_name(&conn->dev, "connection%d:%u", session->sid, cid); conn->dev.parent = &session->dev; conn->dev.release = iscsi_conn_release; err = device_register(&conn->dev); @@ -1011,7 +1008,7 @@ int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr, skb = alloc_skb(len, GFP_ATOMIC); if (!skb) { - iscsi_conn_error(conn, ISCSI_ERR_CONN_FAILED); + iscsi_conn_error_event(conn, ISCSI_ERR_CONN_FAILED); iscsi_cls_conn_printk(KERN_ERR, conn, "can not deliver " "control PDU: OOM\n"); return -ENOMEM; @@ -1032,7 +1029,7 @@ int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr, } EXPORT_SYMBOL_GPL(iscsi_recv_pdu); -void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error) +void iscsi_conn_error_event(struct iscsi_cls_conn *conn, enum iscsi_err error) { struct nlmsghdr *nlh; struct sk_buff *skb; @@ -1064,7 +1061,7 @@ void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error) iscsi_cls_conn_printk(KERN_INFO, conn, "detected conn error (%d)\n", error); } -EXPORT_SYMBOL_GPL(iscsi_conn_error); +EXPORT_SYMBOL_GPL(iscsi_conn_error_event); static int iscsi_if_send_reply(int pid, int seq, int type, int done, int multi, @@ -1362,7 +1359,7 @@ iscsi_tgt_dscvr(struct iscsi_transport *transport, return -EINVAL; shost = scsi_host_lookup(ev->u.tgt_dscvr.host_no); - if (IS_ERR(shost)) { + if (!shost) { printk(KERN_ERR "target discovery could not find host no %u\n", ev->u.tgt_dscvr.host_no); return -ENODEV; @@ -1388,7 +1385,7 @@ iscsi_set_host_param(struct iscsi_transport *transport, return -ENOSYS; shost = scsi_host_lookup(ev->u.set_host_param.host_no); - if (IS_ERR(shost)) { + if (!shost) { printk(KERN_ERR "set_host_param could not find host no %u\n", ev->u.set_host_param.host_no); return -ENODEV; @@ -1816,7 +1813,7 @@ iscsi_register_transport(struct iscsi_transport *tt) priv->t.create_work_queue = 1; priv->dev.class = &iscsi_transport_class; - snprintf(priv->dev.bus_id, BUS_ID_SIZE, "%s", tt->name); + dev_set_name(&priv->dev, "%s", tt->name); err = device_register(&priv->dev); if (err) goto free_priv; diff --git a/kernel/scsi_transport_iscsi.h b/kernel/scsi_transport_iscsi.h index b65c96a..27d067a 100644 --- a/kernel/scsi_transport_iscsi.h +++ b/kernel/scsi_transport_iscsi.h @@ -113,10 +113,18 @@ struct iscsi_transport { char *data, uint32_t data_size); void (*get_stats) (struct iscsi_cls_conn *conn, struct iscsi_stats *stats); + int (*init_task) (struct iscsi_task *task); int (*xmit_task) (struct iscsi_task *task); - void (*cleanup_task) (struct iscsi_conn *conn, - struct iscsi_task *task); + void (*cleanup_task) (struct iscsi_task *task); + + int (*alloc_pdu) (struct iscsi_task *task, uint8_t opcode); + int (*xmit_pdu) (struct iscsi_task *task); + int (*init_pdu) (struct iscsi_task *task, unsigned int offset, + unsigned int count); + void (*parse_pdu_itt) (struct iscsi_conn *conn, itt_t itt, + int *index, int *age); + void (*session_recovery_timedout) (struct iscsi_cls_session *session); struct iscsi_endpoint *(*ep_connect) (struct sockaddr *dst_addr, int non_blocking); @@ -135,7 +143,8 @@ extern int iscsi_unregister_transport(struct iscsi_transport *tt); /* * control plane upcalls */ -extern void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error); +extern void iscsi_conn_error_event(struct iscsi_cls_conn *conn, + enum iscsi_err error); extern int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr, char *data, uint32_t data_size); |