📄 iscsi_tcp.c
字号:
/* * iSCSI Initiator over TCP/IP Data-Path * * 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/scsi_transport_iscsi.h>#include "iscsi_tcp.h"MODULE_AUTHOR("Dmitry Yusupov <dmitry_yus@yahoo.com>, " "Alex Aizman <itn780@yahoo.com>");MODULE_DESCRIPTION("iSCSI/TCP data-path");MODULE_LICENSE("GPL");/* #define DEBUG_TCP */#define DEBUG_ASSERT#ifdef DEBUG_TCP#define debug_tcp(fmt...) printk(KERN_INFO "tcp: " fmt)#else#define debug_tcp(fmt...)#endif#ifndef DEBUG_ASSERT#ifdef BUG_ON#undef BUG_ON#endif#define BUG_ON(expr)#endifstatic unsigned int iscsi_max_lun = 512;module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);static inline voidiscsi_buf_init_iov(struct iscsi_buf *ibuf, char *vbuf, int size){ sg_init_one(&ibuf->sg, vbuf, size); ibuf->sent = 0; ibuf->use_sendmsg = 1;}static inline voidiscsi_buf_init_sg(struct iscsi_buf *ibuf, struct scatterlist *sg){ sg_init_table(&ibuf->sg, 1); sg_set_page(&ibuf->sg, sg_page(sg), sg->length, sg->offset); /* * Fastpath: sg element fits into single page */ if (sg->length + sg->offset <= PAGE_SIZE && !PageSlab(sg_page(sg))) ibuf->use_sendmsg = 0; else ibuf->use_sendmsg = 1; ibuf->sent = 0;}static inline intiscsi_buf_left(struct iscsi_buf *ibuf){ int rc; rc = ibuf->sg.length - ibuf->sent; BUG_ON(rc < 0); return rc;}static inline voidiscsi_hdr_digest(struct iscsi_conn *conn, struct iscsi_buf *buf, u8* crc){ struct iscsi_tcp_conn *tcp_conn = conn->dd_data; crypto_hash_digest(&tcp_conn->tx_hash, &buf->sg, buf->sg.length, crc); buf->sg.length += sizeof(u32);}static inline intiscsi_hdr_extract(struct iscsi_tcp_conn *tcp_conn){ struct sk_buff *skb = tcp_conn->in.skb; tcp_conn->in.zero_copy_hdr = 0; if (tcp_conn->in.copy >= tcp_conn->hdr_size && tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER) { /* * Zero-copy PDU Header: using connection context * to store header pointer. */ if (skb_shinfo(skb)->frag_list == NULL && !skb_shinfo(skb)->nr_frags) { tcp_conn->in.hdr = (struct iscsi_hdr *) ((char*)skb->data + tcp_conn->in.offset); tcp_conn->in.zero_copy_hdr = 1; } else { /* ignoring return code since we checked * in.copy before */ skb_copy_bits(skb, tcp_conn->in.offset, &tcp_conn->hdr, tcp_conn->hdr_size); tcp_conn->in.hdr = &tcp_conn->hdr; } tcp_conn->in.offset += tcp_conn->hdr_size; tcp_conn->in.copy -= tcp_conn->hdr_size; } else { int hdr_remains; int copylen; /* * PDU header scattered across SKB's, * copying it... This'll happen quite rarely. */ if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER) tcp_conn->in.hdr_offset = 0; hdr_remains = tcp_conn->hdr_size - tcp_conn->in.hdr_offset; BUG_ON(hdr_remains <= 0); copylen = min(tcp_conn->in.copy, hdr_remains); skb_copy_bits(skb, tcp_conn->in.offset, (char*)&tcp_conn->hdr + tcp_conn->in.hdr_offset, copylen); debug_tcp("PDU gather offset %d bytes %d in.offset %d " "in.copy %d\n", tcp_conn->in.hdr_offset, copylen, tcp_conn->in.offset, tcp_conn->in.copy); tcp_conn->in.offset += copylen; tcp_conn->in.copy -= copylen; if (copylen < hdr_remains) { tcp_conn->in_progress = IN_PROGRESS_HEADER_GATHER; tcp_conn->in.hdr_offset += copylen; return -EAGAIN; } tcp_conn->in.hdr = &tcp_conn->hdr; tcp_conn->discontiguous_hdr_cnt++; tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER; } return 0;}/* * must be called with session lock */static voidiscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask){ struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; struct iscsi_r2t_info *r2t; struct scsi_cmnd *sc; /* flush ctask's r2t queues */ while (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*))) { __kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t, sizeof(void*)); debug_scsi("iscsi_tcp_cleanup_ctask pending r2t dropped\n"); } sc = ctask->sc; if (unlikely(!sc)) return; tcp_ctask->xmstate = XMSTATE_VALUE_IDLE; tcp_ctask->r2t = NULL;}/** * iscsi_data_rsp - SCSI Data-In Response processing * @conn: iscsi connection * @ctask: scsi command task **/static intiscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask){ struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; struct iscsi_data_rsp *rhdr = (struct iscsi_data_rsp *)tcp_conn->in.hdr; struct iscsi_session *session = conn->session; struct scsi_cmnd *sc = ctask->sc; int datasn = be32_to_cpu(rhdr->datasn); iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr); /* * setup Data-In byte counter (gets decremented..) */ ctask->data_count = tcp_conn->in.datalen; if (tcp_conn->in.datalen == 0) return 0; if (tcp_ctask->exp_datasn != datasn) { debug_tcp("%s: ctask->exp_datasn(%d) != rhdr->datasn(%d)\n", __FUNCTION__, tcp_ctask->exp_datasn, datasn); return ISCSI_ERR_DATASN; } tcp_ctask->exp_datasn++; tcp_ctask->data_offset = be32_to_cpu(rhdr->offset); if (tcp_ctask->data_offset + tcp_conn->in.datalen > scsi_bufflen(sc)) { debug_tcp("%s: data_offset(%d) + data_len(%d) > total_length_in(%d)\n", __FUNCTION__, tcp_ctask->data_offset, tcp_conn->in.datalen, scsi_bufflen(sc)); return ISCSI_ERR_DATA_OFFSET; } if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) { conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1; if (rhdr->flags & ISCSI_FLAG_DATA_UNDERFLOW) { int res_count = be32_to_cpu(rhdr->residual_count); if (res_count > 0 && res_count <= scsi_bufflen(sc)) { scsi_set_resid(sc, res_count); sc->result = (DID_OK << 16) | rhdr->cmd_status; } else sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status; } else if (rhdr->flags & ISCSI_FLAG_DATA_OVERFLOW) { scsi_set_resid(sc, be32_to_cpu(rhdr->residual_count)); sc->result = (DID_OK << 16) | rhdr->cmd_status; } else sc->result = (DID_OK << 16) | rhdr->cmd_status; } conn->datain_pdus_cnt++; return 0;}/** * iscsi_solicit_data_init - initialize first Data-Out * @conn: iscsi connection * @ctask: 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 voidiscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask, struct iscsi_r2t_info *r2t){ struct iscsi_data *hdr; struct scsi_cmnd *sc = ctask->sc; int i, sg_count = 0; struct scatterlist *sg; 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, ctask->hdr->lun, sizeof(hdr->lun)); hdr->itt = ctask->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_buf_init_iov(&r2t->headbuf, (char*)hdr, sizeof(struct iscsi_hdr)); sg = scsi_sglist(sc); r2t->sg = NULL; for (i = 0; i < scsi_sg_count(sc); i++, sg += 1) { /* FIXME: prefetch ? */ if (sg_count + sg->length > r2t->data_offset) { int page_offset; /* sg page found! */ /* offset within this page */ page_offset = r2t->data_offset - sg_count; /* fill in this buffer */ iscsi_buf_init_sg(&r2t->sendbuf, sg); r2t->sendbuf.sg.offset += page_offset; r2t->sendbuf.sg.length -= page_offset; /* xmit logic will continue with next one */ r2t->sg = sg + 1; break; } sg_count += sg->length; } BUG_ON(r2t->sg == NULL);}/** * iscsi_r2t_rsp - iSCSI R2T Response processing * @conn: iscsi connection * @ctask: scsi command task **/static intiscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask){ struct iscsi_r2t_info *r2t; struct iscsi_session *session = conn->session; struct iscsi_tcp_cmd_task *tcp_ctask = ctask->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) { printk(KERN_ERR "iscsi_tcp: invalid R2t with datalen %d\n", tcp_conn->in.datalen); return ISCSI_ERR_DATALEN; } if (tcp_ctask->exp_datasn != r2tsn){ debug_tcp("%s: ctask->exp_datasn(%d) != rhdr->r2tsn(%d)\n", __FUNCTION__, tcp_ctask->exp_datasn, r2tsn); return ISCSI_ERR_R2TSN; } /* fill-in new R2T associated with the task */ spin_lock(&session->lock); iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr); if (!ctask->sc || ctask->mtask || session->state != ISCSI_STATE_LOGGED_IN) { printk(KERN_INFO "iscsi_tcp: dropping R2T itt %d in " "recovery...\n", ctask->itt); spin_unlock(&session->lock); return 0; } rc = __kfifo_get(tcp_ctask->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) { printk(KERN_ERR "iscsi_tcp: invalid R2T with zero data len\n"); spin_unlock(&session->lock); 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_bufflen(ctask->sc)) { spin_unlock(&session->lock); printk(KERN_ERR "iscsi_tcp: invalid R2T with data len %u at " "offset %u and total length %d\n", r2t->data_length, r2t->data_offset, scsi_bufflen(ctask->sc)); return ISCSI_ERR_DATALEN; } r2t->ttt = rhdr->ttt; /* no flip */ r2t->solicit_datasn = 0; iscsi_solicit_data_init(conn, ctask, r2t); tcp_ctask->exp_datasn = r2tsn + 1; __kfifo_put(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*)); set_bit(XMSTATE_BIT_SOL_HDR_INIT, &tcp_ctask->xmstate); list_move_tail(&ctask->running, &conn->xmitqueue); scsi_queue_work(session->host, &conn->xmitwork); conn->r2t_pdus_cnt++; spin_unlock(&session->lock); return 0;}static intiscsi_tcp_hdr_recv(struct iscsi_conn *conn){ int rc = 0, opcode, ahslen; struct iscsi_hdr *hdr; struct iscsi_session *session = conn->session; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; uint32_t cdgst, rdgst = 0, itt; hdr = tcp_conn->in.hdr; /* verify PDU length */ tcp_conn->in.datalen = ntoh24(hdr->dlength); if (tcp_conn->in.datalen > conn->max_recv_dlength) { printk(KERN_ERR "iscsi_tcp: datalen %d > %d\n", tcp_conn->in.datalen, conn->max_recv_dlength); return ISCSI_ERR_DATALEN; } tcp_conn->data_copied = 0; /* read AHS */ ahslen = hdr->hlength << 2; tcp_conn->in.offset += ahslen; tcp_conn->in.copy -= ahslen; if (tcp_conn->in.copy < 0) { printk(KERN_ERR "iscsi_tcp: can't handle AHS with length " "%d bytes\n", ahslen); return ISCSI_ERR_AHSLEN; } /* calculate read padding */ tcp_conn->in.padding = tcp_conn->in.datalen & (ISCSI_PAD_LEN-1); if (tcp_conn->in.padding) { tcp_conn->in.padding = ISCSI_PAD_LEN - tcp_conn->in.padding; debug_scsi("read padding %d bytes\n", tcp_conn->in.padding); } if (conn->hdrdgst_en) { struct scatterlist sg; sg_init_one(&sg, (u8 *)hdr, sizeof(struct iscsi_hdr) + ahslen); crypto_hash_digest(&tcp_conn->rx_hash, &sg, sg.length,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -