Newer
Older
/*
* 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/blkdev.h>
#include <linux/crypto.h>
#include <linux/delay.h>
#include <linux/kfifo.h>
#include <linux/scatterlist.h>
#include <linux/mutex.h>
#include <net/tcp.h>
#include <scsi/scsi_cmnd.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)
#endif
static unsigned int iscsi_max_lun = 512;
module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);
/* global data */
static kmem_cache_t *taskcache;
static inline void
iscsi_buf_init_virt(struct iscsi_buf *ibuf, char *vbuf, int size)
{
sg_init_one(&ibuf->sg, (u8 *)vbuf, size);
ibuf->sent = 0;
}
static inline void
iscsi_buf_init_iov(struct iscsi_buf *ibuf, char *vbuf, int size)
{
ibuf->sg.page = virt_to_page(vbuf);
ibuf->sg.offset = offset_in_page(vbuf);
ibuf->sg.length = size;
ibuf->sent = 0;
}
static inline void
iscsi_buf_init_sg(struct iscsi_buf *ibuf, struct scatterlist *sg)
{
ibuf->sg.page = sg->page;
ibuf->sg.offset = sg->offset;
ibuf->sg.length = sg->length;
/*
* Fastpath: sg element fits into single page
*/
if (sg->length + sg->offset <= PAGE_SIZE && !PageSlab(sg->page))
ibuf->use_sendmsg = 0;
else
ibuf->use_sendmsg = 1;
ibuf->sent = 0;
}
static inline int
iscsi_buf_left(struct iscsi_buf *ibuf)
{
int rc;
rc = ibuf->sg.length - ibuf->sent;
BUG_ON(rc < 0);
return rc;
}
static inline void
iscsi_hdr_digest(struct iscsi_conn *conn, struct iscsi_buf *buf,
u8* crc)
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
crypto_digest_digest(tcp_conn->tx_tfm, &buf->sg, 1, crc);
buf->sg.length += sizeof(uint32_t);
}
static inline int
iscsi_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 void
__iscsi_ctask_cleanup(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
if (sc->sc_data_direction == DMA_TO_DEVICE) {
struct iscsi_data_task *dtask, *n;
/* WRITE: cleanup Data-Out's if any */
list_for_each_entry_safe(dtask, n, &tcp_ctask->dataqueue,
item) {
list_del(&dtask->item);
mempool_free(dtask, tcp_ctask->datapool);
tcp_ctask->xmstate = XMSTATE_IDLE;
tcp_ctask->r2t = NULL;
}
/**
* iscsi_data_rsp - SCSI Data-In Response processing
* @conn: iscsi connection
* @ctask: scsi command task
**/
static int
iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
{
int rc;
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;
int datasn = be32_to_cpu(rhdr->datasn);
rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
if (rc)
return rc;
/*
* setup Data-In byte counter (gets decremented..)
*/
ctask->data_count = tcp_conn->in.datalen;
if (tcp_conn->in.datalen == 0)
return 0;
if (ctask->datasn != datasn)
return ISCSI_ERR_DATASN;
ctask->datasn++;
tcp_ctask->data_offset = be32_to_cpu(rhdr->offset);
if (tcp_ctask->data_offset + tcp_conn->in.datalen > ctask->total_length)
return ISCSI_ERR_DATA_OFFSET;
if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) {
struct scsi_cmnd *sc = ctask->sc;
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 <= sc->request_bufflen) {
sc->resid = 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) {
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
sc->resid = 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 void
iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
struct iscsi_r2t_info *r2t)
{
struct iscsi_data *hdr;
struct iscsi_data_task *dtask;
struct scsi_cmnd *sc = ctask->sc;
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
dtask = mempool_alloc(tcp_ctask->datapool, GFP_ATOMIC);
hdr = &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_virt(&r2t->headbuf, (char*)hdr,
sizeof(struct iscsi_hdr));
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
r2t->dtask = dtask;
if (sc->use_sg) {
int i, sg_count = 0;
struct scatterlist *sg = sc->request_buffer;
r2t->sg = NULL;
for (i = 0; i < sc->use_sg; 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);
} else
iscsi_buf_init_iov(&tcp_ctask->sendbuf,
(char*)sc->request_buffer + r2t->data_offset,
r2t->data_count);
list_add(&dtask->item, &tcp_ctask->dataqueue);
}
/**
* iscsi_r2t_rsp - iSCSI R2T Response processing
* @conn: iscsi connection
* @ctask: scsi command task
**/
static int
iscsi_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;
return ISCSI_ERR_DATALEN;
if (tcp_ctask->exp_r2tsn && tcp_ctask->exp_r2tsn != r2tsn)
return ISCSI_ERR_R2TSN;
rc = iscsi_check_assign_cmdsn(session, (struct iscsi_nopin*)rhdr);
if (rc)
return rc;
/* FIXME: use R2TSN to detect missing R2T */
/* fill-in new R2T associated with the task */
spin_lock(&session->lock);
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 ||
r2t->data_length > session->max_burst) {
spin_unlock(&session->lock);
return ISCSI_ERR_DATALEN;
}
r2t->data_offset = be32_to_cpu(rhdr->data_offset);
if (r2t->data_offset + r2t->data_length > ctask->total_length) {
spin_unlock(&session->lock);
return ISCSI_ERR_DATALEN;
}
r2t->ttt = rhdr->ttt; /* no flip */
r2t->solicit_datasn = 0;
iscsi_solicit_data_init(conn, ctask, r2t);
tcp_ctask->exp_r2tsn = r2tsn + 1;
tcp_ctask->xmstate |= XMSTATE_SOL_HDR;
__kfifo_put(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*));
__kfifo_put(conn->xmitqueue, (void*)&ctask, sizeof(void*));
scsi_queue_work(session->host, &conn->xmitwork);
conn->r2t_pdus_cnt++;
spin_unlock(&session->lock);
return 0;
}
static int
iscsi_tcp_hdr_recv(struct iscsi_conn *conn)
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;
/* 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;
}
/* 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 "
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_digest_digest(tcp_conn->rx_tfm, &sg, 1, (u8 *)&cdgst);
rdgst = *(uint32_t*)((char*)hdr + sizeof(struct iscsi_hdr) +
printk(KERN_ERR "iscsi_tcp: hdrdgst error "
"recv 0x%x calc 0x%x\n", rdgst, cdgst);
return ISCSI_ERR_HDR_DGST;
}
opcode = hdr->opcode & ISCSI_OPCODE_MASK;
/* verify itt (itt encoding: age+cid+itt) */
rc = iscsi_verify_itt(conn, hdr, &itt);
if (rc == ISCSI_ERR_NO_SCSI_CMD) {
tcp_conn->in.datalen = 0; /* force drop */
return 0;
} else if (rc)
return rc;
debug_tcp("opcode 0x%x offset %d copy %d ahslen %d datalen %d\n",
opcode, tcp_conn->in.offset, tcp_conn->in.copy,
ahslen, tcp_conn->in.datalen);
switch(opcode) {
case ISCSI_OP_SCSI_DATA_IN:
tcp_conn->in.ctask = session->cmds[itt];
rc = iscsi_data_rsp(conn, tcp_conn->in.ctask);
/* fall through */
case ISCSI_OP_SCSI_CMD_RSP:
tcp_conn->in.ctask = session->cmds[itt];
if (tcp_conn->in.datalen)
goto copy_hdr;
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
spin_lock(&session->lock);
__iscsi_ctask_cleanup(conn, tcp_conn->in.ctask);
rc = __iscsi_complete_pdu(conn, hdr, NULL, 0);
spin_unlock(&session->lock);
break;
case ISCSI_OP_R2T:
tcp_conn->in.ctask = session->cmds[itt];
if (ahslen)
rc = ISCSI_ERR_AHSLEN;
else if (tcp_conn->in.ctask->sc->sc_data_direction ==
DMA_TO_DEVICE)
rc = iscsi_r2t_rsp(conn, tcp_conn->in.ctask);
else
rc = ISCSI_ERR_PROTO;
break;
case ISCSI_OP_LOGIN_RSP:
case ISCSI_OP_TEXT_RSP:
case ISCSI_OP_LOGOUT_RSP:
case ISCSI_OP_NOOP_IN:
case ISCSI_OP_REJECT:
case ISCSI_OP_ASYNC_EVENT:
if (tcp_conn->in.datalen)
goto copy_hdr;
/* fall through */
case ISCSI_OP_SCSI_TMFUNC_RSP:
rc = iscsi_complete_pdu(conn, hdr, NULL, 0);
break;
default:
rc = ISCSI_ERR_BAD_OPCODE;
break;
}
copy_hdr:
/*
* if we did zero copy for the header but we will need multiple
* skbs to complete the command then we have to copy the header
* for later use
*/
if (tcp_conn->in.zero_copy_hdr && tcp_conn->in.copy <
(tcp_conn->in.datalen + tcp_conn->in.padding +
(conn->datadgst_en ? 4 : 0))) {
debug_tcp("Copying header for later use. in.copy %d in.datalen"
" %d\n", tcp_conn->in.copy, tcp_conn->in.datalen);
memcpy(&tcp_conn->hdr, tcp_conn->in.hdr,
sizeof(struct iscsi_hdr));
tcp_conn->in.hdr = &tcp_conn->hdr;
tcp_conn->in.zero_copy_hdr = 0;
}
return 0;
}
/**
* iscsi_ctask_copy - copy skb bits to the destanation cmd task
* @conn: iscsi tcp connection
* @ctask: scsi command task
* @buf: buffer to copy to
* @buf_size: size of buffer
* @offset: offset within the buffer
*
* Notes:
* The function calls skb_copy_bits() and updates per-connection and
* per-cmd byte counters.
*
* Read counters (in bytes):
*
* conn->in.offset offset within in progress SKB
* conn->in.copy left to copy from in progress SKB
* including padding
* conn->in.copied copied already from in progress SKB
* conn->data_copied copied already from in progress buffer
* ctask->sent total bytes sent up to the MidLayer
* ctask->data_count left to copy from in progress Data-In
* buf_left left to copy from in progress buffer
**/
static inline int
iscsi_ctask_copy(struct iscsi_tcp_conn *tcp_conn, struct iscsi_cmd_task *ctask,
void *buf, int buf_size, int offset)
{
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
int buf_left = buf_size - (tcp_conn->data_copied + offset);
int size = min(tcp_conn->in.copy, buf_left);
int rc;
size = min(size, ctask->data_count);
debug_tcp("ctask_copy %d bytes at offset %d copied %d\n",
size, tcp_conn->in.offset, tcp_conn->in.copied);
BUG_ON(size <= 0);
BUG_ON(tcp_ctask->sent + size > ctask->total_length);
rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
(char*)buf + (offset + tcp_conn->data_copied), size);
/* must fit into skb->len */
BUG_ON(rc);
tcp_conn->in.offset += size;
tcp_conn->in.copy -= size;
tcp_conn->in.copied += size;
tcp_conn->data_copied += size;
tcp_ctask->sent += size;
ctask->data_count -= size;
BUG_ON(tcp_conn->in.copy < 0);
BUG_ON(ctask->data_count < 0);
if (buf_size != (tcp_conn->data_copied + offset)) {
if (!ctask->data_count) {
BUG_ON(buf_size - tcp_conn->data_copied < 0);
/* done with this PDU */
return buf_size - tcp_conn->data_copied;
}
return -EAGAIN;
}
/* done with this buffer or with both - PDU and buffer */
return 0;
}
/**
* iscsi_tcp_copy - copy skb bits to the destanation buffer
* @conn: iscsi tcp connection
*
* Notes:
* The function calls skb_copy_bits() and updates per-connection
* byte counters.
**/
static inline int
iscsi_tcp_copy(struct iscsi_tcp_conn *tcp_conn)
void *buf = tcp_conn->data;
int buf_size = tcp_conn->in.datalen;
int buf_left = buf_size - tcp_conn->data_copied;
int size = min(tcp_conn->in.copy, buf_left);
int rc;
debug_tcp("tcp_copy %d bytes at offset %d copied %d\n",
size, tcp_conn->in.offset, tcp_conn->data_copied);
BUG_ON(size <= 0);
rc = skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
(char*)buf + tcp_conn->data_copied, size);
tcp_conn->in.offset += size;
tcp_conn->in.copy -= size;
tcp_conn->in.copied += size;
tcp_conn->data_copied += size;
if (buf_size != tcp_conn->data_copied)
return -EAGAIN;
return 0;
}
static inline void
partial_sg_digest_update(struct iscsi_tcp_conn *tcp_conn,
struct scatterlist *sg, int offset, int length)
{
struct scatterlist temp;
memcpy(&temp, sg, sizeof(struct scatterlist));
temp.offset = offset;
temp.length = length;
crypto_digest_update(tcp_conn->data_rx_tfm, &temp, 1);
iscsi_recv_digest_update(struct iscsi_tcp_conn *tcp_conn, char* buf, int len)
{
struct scatterlist tmp;
sg_init_one(&tmp, buf, len);
crypto_digest_update(tcp_conn->data_rx_tfm, &tmp, 1);
static int iscsi_scsi_data_in(struct iscsi_conn *conn)
{
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
struct iscsi_cmd_task *ctask = tcp_conn->in.ctask;
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
struct scsi_cmnd *sc = ctask->sc;
int i, offset, rc = 0;
BUG_ON((void*)ctask != sc->SCp.ptr);
/*
* copying Data-In into the Scsi_Cmnd
*/
if (!sc->use_sg) {
i = ctask->data_count;
rc = iscsi_ctask_copy(tcp_conn, ctask, sc->request_buffer,
sc->request_bufflen,
tcp_ctask->data_offset);
if (rc == -EAGAIN)
return rc;
iscsi_recv_digest_update(tcp_conn, sc->request_buffer,
i);
rc = 0;
goto done;
}
offset = tcp_ctask->data_offset;
sg = sc->request_buffer;
if (tcp_ctask->data_offset)
for (i = 0; i < tcp_ctask->sg_count; i++)
offset -= sg[i].length;
/* we've passed through partial sg*/
if (offset < 0)
offset = 0;
for (i = tcp_ctask->sg_count; i < sc->use_sg; i++) {
char *dest;
dest = kmap_atomic(sg[i].page, KM_SOFTIRQ0);
rc = iscsi_ctask_copy(tcp_conn, ctask, dest + sg[i].offset,
sg[i].length, offset);
kunmap_atomic(dest, KM_SOFTIRQ0);
if (rc == -EAGAIN)
/* continue with the next SKB/PDU */
return rc;
if (!rc) {
if (conn->datadgst_en) {
if (!offset)
crypto_digest_update(
tcp_conn->data_rx_tfm,
&sg[i], 1);
partial_sg_digest_update(tcp_conn,
&sg[i],
sg[i].offset + offset,
sg[i].length - offset);
}
offset = 0;
}
if (!ctask->data_count) {
if (rc && conn->datadgst_en)
/*
* data-in is complete, but buffer not...
*/
partial_sg_digest_update(tcp_conn, &sg[i],
sg[i].offset, sg[i].length-rc);
rc = 0;
break;
}
return -EAGAIN;
}
BUG_ON(ctask->data_count);
done:
/* check for non-exceptional status */
if (tcp_conn->in.hdr->flags & ISCSI_FLAG_DATA_STATUS) {
debug_scsi("done [sc %lx res %d itt 0x%x]\n",
(long)sc, sc->result, ctask->itt);
spin_lock(&conn->session->lock);
__iscsi_ctask_cleanup(conn, ctask);
__iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
spin_unlock(&conn->session->lock);
}
return rc;
}
static int
iscsi_data_recv(struct iscsi_conn *conn)
{
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
int rc = 0, opcode;
opcode = tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK;
switch (opcode) {
case ISCSI_OP_SCSI_DATA_IN:
rc = iscsi_scsi_data_in(conn);
break;
case ISCSI_OP_SCSI_CMD_RSP:
spin_lock(&conn->session->lock);
__iscsi_ctask_cleanup(conn, tcp_conn->in.ctask);
spin_unlock(&conn->session->lock);
case ISCSI_OP_TEXT_RSP:
case ISCSI_OP_LOGIN_RSP:
case ISCSI_OP_NOOP_IN:
case ISCSI_OP_ASYNC_EVENT:
case ISCSI_OP_REJECT:
/*
* Collect data segment to the connection's data
* placeholder
*/
if (iscsi_tcp_copy(tcp_conn)) {
rc = -EAGAIN;
goto exit;
}
rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, tcp_conn->data,
tcp_conn->in.datalen);
if (!rc && conn->datadgst_en && opcode != ISCSI_OP_LOGIN_RSP)
iscsi_recv_digest_update(tcp_conn, tcp_conn->data,
tcp_conn->in.datalen);
break;
default:
BUG_ON(1);
}
exit:
return rc;
}
/**
* iscsi_tcp_data_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_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
unsigned int offset, size_t len)
{
int rc;
struct iscsi_conn *conn = rd_desc->arg.data;
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
int processed;
char pad[ISCSI_PAD_LEN];
struct scatterlist sg;
/*
* Save current SKB and its offset in the corresponding
* connection context.
*/
tcp_conn->in.copy = skb->len - offset;
tcp_conn->in.offset = offset;
tcp_conn->in.skb = skb;
tcp_conn->in.len = tcp_conn->in.copy;
BUG_ON(tcp_conn->in.copy <= 0);
debug_tcp("in %d bytes\n", tcp_conn->in.copy);
rc = 0;
if (unlikely(conn->suspend_rx)) {
debug_tcp("conn %d Rx suspended!\n", conn->id);
return 0;
}
if (tcp_conn->in_progress == IN_PROGRESS_WAIT_HEADER ||
tcp_conn->in_progress == IN_PROGRESS_HEADER_GATHER) {
rc = iscsi_hdr_extract(tcp_conn);
if (rc) {
if (rc == -EAGAIN)
goto nomore;
else {
iscsi_conn_failure(conn, rc);
return 0;
}
}
/*
* Verify and process incoming PDU header.
*/
rc = iscsi_tcp_hdr_recv(conn);
if (!rc && tcp_conn->in.datalen) {
if (conn->datadgst_en) {
BUG_ON(!tcp_conn->data_rx_tfm);
crypto_digest_init(tcp_conn->data_rx_tfm);
tcp_conn->in_progress = IN_PROGRESS_DATA_RECV;
} else if (rc) {
iscsi_conn_failure(conn, rc);
return 0;
}
}
if (tcp_conn->in_progress == IN_PROGRESS_DDIGEST_RECV) {
debug_tcp("extra data_recv offset %d copy %d\n",
tcp_conn->in.offset, tcp_conn->in.copy);
skb_copy_bits(tcp_conn->in.skb, tcp_conn->in.offset,
tcp_conn->in.offset += 4;
tcp_conn->in.copy -= 4;
if (recv_digest != tcp_conn->in.datadgst) {
debug_tcp("iscsi_tcp: data digest error!"
"0x%x != 0x%x\n", recv_digest,
iscsi_conn_failure(conn, ISCSI_ERR_DATA_DGST);
return 0;
} else {
debug_tcp("iscsi_tcp: data digest match!"
"0x%x == 0x%x\n", recv_digest,
tcp_conn->in.datadgst);
tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
if (tcp_conn->in_progress == IN_PROGRESS_DATA_RECV &&
tcp_conn->in.copy) {
debug_tcp("data_recv offset %d copy %d\n",
tcp_conn->in.offset, tcp_conn->in.copy);
rc = iscsi_data_recv(conn);
if (rc) {
if (rc == -EAGAIN) {
rd_desc->count = tcp_conn->in.datalen -
tcp_conn->in.ctask->data_count;
goto again;
}
iscsi_conn_failure(conn, rc);
return 0;
}
tcp_conn->in.copy -= tcp_conn->in.padding;
tcp_conn->in.offset += tcp_conn->in.padding;
if (conn->datadgst_en) {
if (tcp_conn->in.padding) {
debug_tcp("padding -> %d\n",
tcp_conn->in.padding);
memset(pad, 0, tcp_conn->in.padding);
sg_init_one(&sg, pad, tcp_conn->in.padding);
crypto_digest_update(tcp_conn->data_rx_tfm,
&sg, 1);
crypto_digest_final(tcp_conn->data_rx_tfm,
(u8 *) & tcp_conn->in.datadgst);
debug_tcp("rx digest 0x%x\n", tcp_conn->in.datadgst);
tcp_conn->in_progress = IN_PROGRESS_DDIGEST_RECV;
tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
}
debug_tcp("f, processed %d from out of %d padding %d\n",
tcp_conn->in.offset - offset, (int)len, tcp_conn->in.padding);
BUG_ON(tcp_conn->in.offset - offset > len);
if (tcp_conn->in.offset - offset != len) {
debug_tcp("continue to process %d bytes\n",
(int)len - (tcp_conn->in.offset - offset));
goto more;
}
nomore:
processed = tcp_conn->in.offset - offset;
BUG_ON(processed == 0);
return processed;
again:
processed = tcp_conn->in.offset - offset;
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
debug_tcp("c, processed %d from out of %d rd_desc_cnt %d\n",
processed, (int)len, (int)rd_desc->count);
BUG_ON(processed == 0);
BUG_ON(processed > len);
conn->rxdata_octets += processed;
return processed;
}
static void
iscsi_tcp_data_ready(struct sock *sk, int flag)
{
struct iscsi_conn *conn = sk->sk_user_data;
read_descriptor_t rd_desc;
read_lock(&sk->sk_callback_lock);
/* use rd_desc to pass 'conn' to iscsi_tcp_data_recv */
rd_desc.arg.data = conn;
rd_desc.count = 0;
tcp_read_sock(sk, &rd_desc, iscsi_tcp_data_recv);
read_unlock(&sk->sk_callback_lock);
}
static void
iscsi_tcp_state_change(struct sock *sk)
{
struct iscsi_tcp_conn *tcp_conn;
struct iscsi_conn *conn;
struct iscsi_session *session;
void (*old_state_change)(struct sock *);