iscsi-recv-pdu.c

来自「iSCSI协议在LINUX下的源码.源代码是IBM公布的.主要是结合其OSD设备」· C语言 代码 · 共 1,019 行 · 第 1/2 页

C
1,019
字号
/* * iSCSI driver for Linux * Copyright (C) 2001 Cisco Systems, Inc. * Copyright (C) 2004 Mike Christie * Copyright (C) 2004 IBM Corporation * maintained by linux-iscsi-devel@lists.sourceforge.net * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; 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. * * $Id: iscsi-recv-pdu.c,v 1.81 2005/01/19 23:16:40 mikenc Exp $ * * All the incoming iSCSI PDUs are processed by functions * defined here. */#include <linux/blkdev.h>#include <linux/scatterlist.h>#include <linux/tcp.h>#include <linux/net.h>#include <scsi/scsi_device.h>#include <scsi/scsi_dbg.h>#include "iscsi-session.h"#include "iscsi-task.h"#include "iscsi-protocol.h"#include "iscsi-login.h"#include "iscsi-sfnet.h"/* possibly update the ExpCmdSN and MaxCmdSN */static voidupdate_sn(struct iscsi_session *session, u32 expcmdsn, u32 maxcmdsn){	/*	 * standard specifies this check for when to update expected and	 * max sequence numbers	 */	if (iscsi_sna_lt(maxcmdsn, expcmdsn - 1))		return;	if (expcmdsn != session->exp_cmd_sn &&	    !iscsi_sna_lt(expcmdsn, session->exp_cmd_sn))		session->exp_cmd_sn = expcmdsn;	if (maxcmdsn != session->max_cmd_sn &&	    !iscsi_sna_lt(maxcmdsn, session->max_cmd_sn)) {		session->max_cmd_sn = maxcmdsn;		/* wake the tx thread to try sending more commands */		iscsi_wake_tx_thread(TX_SCSI_COMMAND, session);	}	/*	 * record whether or not the command window for this session	 * has closed, so that we can ping the target periodically to	 * ensure we eventually find out that the window has re-opened.	 */	if (maxcmdsn == expcmdsn - 1) {		/*		 * record how many times this happens, to see		 * how often we're getting throttled		 */		session->window_closed++;		/*		 * prepare to poll the target to see if		 * the window has reopened		 */		session->last_window_check = jiffies;		set_bit(SESSION_WINDOW_CLOSED, &session->control_bits);	} else if (test_bit(SESSION_WINDOW_CLOSED, &session->control_bits))		clear_bit(SESSION_WINDOW_CLOSED, &session->control_bits);}static intiscsi_recv_header(struct iscsi_session *session, struct iscsi_hdr *sth,		 int digest){	struct scatterlist sg;	struct kvec iov[2];	int length, rc;	u32 recvd_crc32c, hdr_crc32c;	u8 iovn = 0;	iov[iovn].iov_base = sth;	iov[iovn].iov_len = length = sizeof(*sth);	iovn++;	if (digest == ISCSI_DIGEST_CRC32C) {		iov[iovn].iov_base = &recvd_crc32c;		iov[iovn].iov_len = sizeof(recvd_crc32c);		iovn++;		length += sizeof(recvd_crc32c);	}	rc = iscsi_recvmsg(session, iov, iovn, length);	if (rc != ISCSI_IO_SUCCESS)		return rc;	if (digest == ISCSI_DIGEST_CRC32C) {		crypto_digest_init(session->rx_tfm);		sg_init_one(&sg, (u8 *)sth, sizeof(*sth));		crypto_digest_digest(session->rx_tfm, &sg, 1,				     (u8*)&hdr_crc32c);		if (recvd_crc32c != hdr_crc32c) {			iscsi_host_err(session, "HeaderDigest mismatch, "				       "received 0x%08x, calculated 0x%08x, "				       "dropping session\n", recvd_crc32c,				       hdr_crc32c);			return ISCSI_IO_CRC32C_ERR;		}	}	/* connection is ok */	session->last_rx = jiffies;	if (sth->hlength) {		/*		 * FIXME: read any additional header segments.		 * For now, drop the session if one is		 * received, since we can't handle them.		 */		iscsi_host_err(session, "Received opcode %x, ahs length %d, itt"			       " %u. Dropping, additional header segments not "			       "supported by this driver version.\n",			       sth->opcode, sth->hlength, ntohl(sth->itt));		return ISCSI_IO_ERR;	}	return ISCSI_IO_SUCCESS;}static voidhandle_logout(struct iscsi_session *session, struct iscsi_hdr *sth){	struct iscsi_logout_rsp_hdr *stlh = (struct iscsi_logout_rsp_hdr *)sth;	update_sn(session, ntohl(stlh->expcmdsn), ntohl(stlh->maxcmdsn));	session->logout_response_deadline = 0;	if (test_bit(SESSION_LOGOUT_REQUESTED, &session->control_bits))		switch (stlh->response) {		case ISCSI_LOGOUT_SUCCESS:			/*			 * set session's time2wait to zero?			 * use DefaultTime2Wait?			 */			session->time2wait = 0;			iscsi_host_notice(session, "Session logged out\n");			break;		case ISCSI_LOGOUT_CID_NOT_FOUND:			iscsi_host_err(session, "Session logout failed, cid not"				       " found\n");			break;		case ISCSI_LOGOUT_RECOVERY_UNSUPPORTED:			iscsi_host_err(session, "Session logout failed, "				       "connection recovery not supported\n");			break;		case ISCSI_LOGOUT_CLEANUP_FAILED:			iscsi_host_err(session, "Session logout failed, cleanup"				       " failed\n");			break;		default:			iscsi_host_err(session, "Session logout failed, "				       "response 0x%x\n", stlh->response);			break;		}	else		iscsi_host_err(session, "Session received logout response, but "			       "never sent a login request\n");	iscsi_drop_session(session);}static voidsetup_nop_out(struct iscsi_session *session, struct iscsi_nop_in_hdr *stnih){	struct iscsi_nop_info *nop_info;	/*	 * we preallocate space for one data-less nop reply in	 * session structure, to avoid having to invoke kernel	 * memory allocator in the common case where the target	 * has at most one outstanding data-less nop reply	 * requested at any given time.	 */	spin_lock_bh(&session->task_lock);	if (session->nop_reply.ttt == ISCSI_RSVD_TASK_TAG &&	    list_empty(&session->nop_reply_list))		nop_info = &session->nop_reply;	else {		nop_info = kmalloc(sizeof(*nop_info), GFP_ATOMIC);		if (!nop_info) {			spin_unlock_bh(&session->task_lock);			iscsi_host_warn(session, "Couldn't queue nop reply "					"for ttt %u ", ntohl(stnih->ttt));			return;		}		list_add_tail(&nop_info->reply_list, &session->nop_reply_list);	}	session->nop_reply.ttt = stnih->ttt;	memcpy(session->nop_reply.lun, stnih->lun,	       sizeof(session->nop_reply.lun));	spin_unlock_bh(&session->task_lock);	iscsi_wake_tx_thread(TX_NOP_REPLY, session);}static voidhandle_nop_in(struct iscsi_session *session, struct iscsi_hdr *sth){	struct iscsi_nop_in_hdr *stnih = (struct iscsi_nop_in_hdr *)sth;	update_sn(session, ntohl(stnih->expcmdsn), ntohl(stnih->maxcmdsn));	if (stnih->itt != ISCSI_RSVD_TASK_TAG)		/*		 * we do not send data in our nop-outs, so there		 * is not much to do right now		 */		/*		 * FIXME: check StatSN		 */		session->exp_stat_sn = ntohl(stnih->statsn) + 1;	/*	 * check the ttt to decide whether to reply with a Nop-out	 */	if (stnih->ttt != ISCSI_RSVD_TASK_TAG)		setup_nop_out(session, stnih);}/** * handle_scsi_rsp - Process the SCSI response PDU. * @session: Session on which the cmd response is received. * @stsrh: SCSI cmd Response header * @sense_data: Sense data received for the cmd * * Description: *     Get the task for the SCSI cmd, process the response received and *     complete the task. **/static voidhandle_scsi_rsp(struct iscsi_session *session, struct iscsi_hdr *sth,		unsigned char *sense_data){	struct iscsi_scsi_rsp_hdr *stsrh = (struct iscsi_scsi_rsp_hdr *)sth;	struct iscsi_task *task;	unsigned int senselen = 0;	u32 itt = ntohl(stsrh->itt);	/* FIXME: check StatSN */	session->exp_stat_sn = ntohl(stsrh->statsn) + 1;	update_sn(session, ntohl(stsrh->expcmdsn), ntohl(stsrh->maxcmdsn));	spin_lock_bh(&session->task_lock);	task = iscsi_find_session_task(session, itt);	if (!task) {		iscsi_host_info(session, "recv_cmd - response for itt %u, but "				"no such task\n", itt);		spin_unlock_bh(&session->task_lock);		return;	}	/* check for sense data */	if (ntoh24(stsrh->dlength) > 1) {		/*		 * Sense data format per draft-08, 3.4.6.  2-byte sense length,		 * then sense data, then iSCSI response data		 */		senselen = (sense_data[0] << 8) | sense_data[1];		if (senselen > (ntoh24(stsrh->dlength) - 2))			senselen = (ntoh24(stsrh->dlength) - 2);		sense_data += 2;	}	iscsi_process_task_response(task, stsrh, sense_data, senselen);	iscsi_complete_task(task);	__iscsi_put_task(task);	spin_unlock_bh(&session->task_lock);}static voidhandle_r2t(struct iscsi_session *session, struct iscsi_hdr *sth){	struct iscsi_r2t_hdr *strh = (struct iscsi_r2t_hdr *)sth;	struct iscsi_task *task;	u32 itt = ntohl(strh->itt);	update_sn(session, ntohl(strh->expcmdsn), ntohl(strh->maxcmdsn));	spin_lock_bh(&session->task_lock);	task = iscsi_find_session_task(session, itt);	if (!task) {		/* the task no longer exists */		iscsi_host_info(session, "ignoring R2T for itt %u, %u bytes @ "				"offset %u\n", ntohl(strh->itt),				ntohl(strh->data_length),				ntohl(strh->data_offset));		goto done;	}	if (!test_bit(ISCSI_TASK_WRITE, &task->flags)) {		/*		 * bug in the target.  the command isn't a write,		 * so we have no data to send		 */		iscsi_host_err(session, "Ignoring unexpected R2T for task itt "			       "%u, %u bytes @ offset %u, ttt %u, not a write "			       "command\n", ntohl(strh->itt),			       ntohl(strh->data_length),			       ntohl(strh->data_offset), ntohl(strh->ttt));		iscsi_drop_session(session);	} else if (task->ttt != ISCSI_RSVD_TASK_TAG)		/*		 * bug in the target.  MaxOutstandingR2T == 1 should		 * have prevented this from occuring		 */		iscsi_host_warn(session, "Ignoring R2T for task itt %u, %u "				"bytes @ offset %u, ttt %u, already have R2T "				"for %u @ %u, ttt %u\n", ntohl(strh->itt),				ntohl(strh->data_length),				ntohl(strh->data_offset), ntohl(strh->ttt),				task->data_length, task->data_offset,				ntohl(task->ttt));	else {		/* record the R2T */		task->ttt = strh->ttt;		task->data_length = ntohl(strh->data_length);		task->data_offset = ntohl(strh->data_offset);		/*		 * even if we've issued an abort task set, we need		 * to respond to R2Ts for this task, though we can		 * apparently set the F-bit and terminate the data burst		 * early.  Rather than hope targets handle that		 * correctly, we just send the data requested as usual.		 */		iscsi_queue_r2t(session, task);		iscsi_wake_tx_thread(TX_DATA, session);	}	__iscsi_put_task(task); done:	spin_unlock_bh(&session->task_lock);}static intrecv_extra_data(struct iscsi_session *session,  u32 data_len, u32 *recvd_crc32c){	struct scatterlist tmpsg;	struct kvec iov[2];	char padding[PAD_WORD_LEN - 1];	int pad = 0, iovn = 0, len = 0, rc;	if (data_len % PAD_WORD_LEN) {		pad = PAD_WORD_LEN - (data_len % PAD_WORD_LEN);		iov[iovn].iov_base = padding;		iov[iovn].iov_len = pad;		iovn++;		len += pad;	}	if (recvd_crc32c) {		iov[iovn].iov_base = recvd_crc32c;		iov[iovn].iov_len = sizeof(*recvd_crc32c);		len += iov[iovn].iov_len;		iovn++;	}	if (iovn) {		rc = iscsi_recvmsg(session, iov, iovn, len);		if (rc != ISCSI_IO_SUCCESS)			return rc;		if (pad && recvd_crc32c) {			sg_init_one(&tmpsg, padding, pad);			crypto_digest_update(session->rx_tfm, &tmpsg, 1);		}	}	return ISCSI_IO_SUCCESS;}/** * iscsi_recv_sg_data - read the PDU's payload * @session: iscsi session * @data_len: data length * @sglist: data scatterlist * @sglist_len: number of sg elements * @sg_offset: offset in sglist * @digest_opt: CRC32C or NONE **/static intiscsi_recv_sg_data(struct iscsi_session *session, u32 data_len,		   struct scatterlist *sglist, int sglist_len,		   unsigned int sg_offset, int digest_opt){	int i, len, rc = ISCSI_IO_ERR;	struct scatterlist *sg, tmpsg;	unsigned int page_offset, remaining, sg_bytes;	struct page *p;	void *page_addr;	struct kvec iov;	u32 recvd_crc32c, data_crc32c;	remaining = data_len;	if (digest_opt == ISCSI_DIGEST_CRC32C)		crypto_digest_init(session->rx_tfm);	/*	 * Read in the data for each sg in PDU	 */	for (i = 0; remaining > 0 && i < sglist_len; i++) {		/*		 * Find the right sg entry first		 */		if (sg_offset >= sglist[i].length) {			sg_offset -= sglist[i].length;			continue;		}		sg = &sglist[i];		/*		 * Find page corresponding to segment offset first		 */		page_offset = sg->offset + sg_offset;		p = sg->page + (page_offset >> PAGE_SHIFT);		page_offset -= (page_offset & PAGE_MASK);		/*		 * yuck, for each page in sg (can't pass a sg with its		 * pages mapped to kernel_recvmsg in one iov entry and must		 * use one iov entry for each PAGE when using highmem???????)		 */		sg_bytes = min(remaining, sg->length - sg_offset);		remaining -= sg_bytes;		for (; sg_bytes > 0; sg_bytes -= len) {			page_addr = kmap(p);			if (!page_addr) {				iscsi_host_err(session, "recv_sg_data kmap "					       "failed to map page in sg %p\n",					       sg);				goto error_exit;			}					iov.iov_base = page_addr + page_offset;			iov.iov_len = min_t(unsigned int, sg_bytes,					    PAGE_SIZE - page_offset);			len = iov.iov_len;			/*			 * is it better to do one call with all the pages			 * setup or multiple calls?			 */			rc = iscsi_recvmsg(session, &iov, 1, len);			kunmap(p);			if (rc != ISCSI_IO_SUCCESS)				goto error_exit;			/* crypto_digest_update will kmap itself */			if (digest_opt == ISCSI_DIGEST_CRC32C) {				tmpsg.page = p;				tmpsg.offset = page_offset;				tmpsg.length = len;				crypto_digest_update(session->rx_tfm, &tmpsg,						     1);			}			p++;			page_offset = 0;		}		sg_offset = 0;	}	if (remaining != 0) {		/* Maybe this should be a BUG? */		iscsi_host_err(session, "recv_sg_data - invalid sglist for "			       "offset %u len %u, remaining data %u, sglist "			       "size %d, dropping session\n", sg_offset,			       data_len, remaining, sglist_len);		goto error_exit;	}	rc = recv_extra_data(session, data_len, digest_opt ==			     ISCSI_DIGEST_CRC32C ? &recvd_crc32c : NULL);	if (rc != ISCSI_IO_SUCCESS)  		goto error_exit;	if (digest_opt == ISCSI_DIGEST_CRC32C) {		crypto_digest_final(session->rx_tfm, (u8*)&data_crc32c);		if (data_crc32c != recvd_crc32c) {			iscsi_host_err(session, "DataDigest mismatch, received "				       "0x%08x, calculated 0x%08x\n",				       recvd_crc32c, data_crc32c);			return ISCSI_IO_CRC32C_ERR;		}	}	/* connection is ok */	session->last_rx = jiffies;	return rc; error_exit:

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?