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 + -
显示快捷键?