⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 iscsi_tcp.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 5 页
字号:
/* * 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 + -