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 <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_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);
static int iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn,
struct iscsi_chunk *chunk);
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;
}
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_hash_digest(&tcp_conn->tx_hash, &buf->sg, buf->sg.length, crc);
buf->sg.length += ISCSI_DIGEST_SIZE;
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/*
* Scatterlist handling: inside the iscsi_chunk, we
* remember an index into the scatterlist, and set data/size
* to the current scatterlist entry. For highmem pages, we
* kmap as needed.
*
* Note that the page is unmapped when we return from
* TCP's data_ready handler, so we may end up mapping and
* unmapping the same page repeatedly. The whole reason
* for this is that we shouldn't keep the page mapped
* outside the softirq.
*/
/**
* iscsi_tcp_chunk_init_sg - init indicated scatterlist entry
* @chunk: the buffer object
* @idx: index into scatterlist
* @offset: byte offset into that sg entry
*
* This function sets up the chunk so that subsequent
* data is copied to the indicated sg entry, at the given
* offset.
*/
static inline void
iscsi_tcp_chunk_init_sg(struct iscsi_chunk *chunk,
unsigned int idx, unsigned int offset)
{
struct scatterlist *sg;
BUG_ON(chunk->sg == NULL);
sg = &chunk->sg[idx];
chunk->sg_index = idx;
chunk->sg_offset = offset;
chunk->size = min(sg->length - offset, chunk->total_size);
chunk->data = NULL;
}
/**
* iscsi_tcp_chunk_map - map the current S/G page
* @chunk: iscsi chunk
*
* We only need to possibly kmap data if scatter lists are being used,
* because the iscsi passthrough and internal IO paths will never use high
* mem pages.
*/
static inline void
iscsi_tcp_chunk_map(struct iscsi_chunk *chunk)
{
struct scatterlist *sg;
if (chunk->data != NULL || !chunk->sg)
return;
sg = &chunk->sg[chunk->sg_index];
BUG_ON(chunk->sg_mapped);
BUG_ON(sg->length == 0);
chunk->sg_mapped = kmap_atomic(sg->page, KM_SOFTIRQ0);
chunk->data = chunk->sg_mapped + sg->offset + chunk->sg_offset;
}
static inline void
iscsi_tcp_chunk_unmap(struct iscsi_chunk *chunk)
{
if (chunk->sg_mapped) {
kunmap_atomic(chunk->sg_mapped, KM_SOFTIRQ0);
chunk->sg_mapped = NULL;
chunk->data = NULL;
}
}
/*
* Splice the digest buffer into the buffer
*/
static inline void
iscsi_tcp_chunk_splice_digest(struct iscsi_chunk *chunk, void *digest)
{
chunk->data = digest;
chunk->digest_len = ISCSI_DIGEST_SIZE;
chunk->total_size += ISCSI_DIGEST_SIZE;
chunk->size = ISCSI_DIGEST_SIZE;
chunk->copied = 0;
chunk->sg = NULL;
chunk->sg_index = 0;
chunk->hash = NULL;
}
/**
* iscsi_tcp_chunk_done - check whether the chunk is complete
* @chunk: iscsi chunk to check
*
* Check if we're done receiving this chunk. If the receive
* buffer is full but we expect more data, move on to the
* next entry in the scatterlist.
*
* If the amount of data we received isn't a multiple of 4,
* we will transparently receive the pad bytes, too.
*
* This function must be re-entrant.
*/
static inline int
iscsi_tcp_chunk_done(struct iscsi_chunk *chunk)
static unsigned char padbuf[ISCSI_PAD_LEN];
if (chunk->copied < chunk->size) {
iscsi_tcp_chunk_map(chunk);
return 0;
}
chunk->total_copied += chunk->copied;
chunk->copied = 0;
chunk->size = 0;
/* Unmap the current scatterlist page, if there is one. */
iscsi_tcp_chunk_unmap(chunk);
/* Do we have more scatterlist entries? */
if (chunk->total_copied < chunk->total_size) {
/* Proceed to the next entry in the scatterlist. */
iscsi_tcp_chunk_init_sg(chunk, chunk->sg_index + 1, 0);
iscsi_tcp_chunk_map(chunk);
BUG_ON(chunk->size == 0);
return 0;
}
/* Do we need to handle padding? */
pad = iscsi_padding(chunk->total_copied);
if (pad != 0) {
debug_tcp("consume %d pad bytes\n", pad);
chunk->total_size += pad;
chunk->size = pad;
chunk->data = padbuf;
return 0;
}
/*
* Set us up for receiving the data digest. hdr digest
* is completely handled in hdr done function.
*/
if (chunk->hash) {
if (chunk->digest_len == 0) {
crypto_hash_final(chunk->hash, chunk->digest);
iscsi_tcp_chunk_splice_digest(chunk,
chunk->recv_digest);
return 0;
/**
* iscsi_tcp_chunk_recv - copy data to chunk
* @tcp_conn: the iSCSI TCP connection
* @chunk: the buffer to copy to
* @ptr: data pointer
* @len: amount of data available
*
* This function copies up to @len bytes to the
* given buffer, and returns the number of bytes
* consumed, which can actually be less than @len.
*
* If hash digest is enabled, the function will update the
* hash while copying.
* Combining these two operations doesn't buy us a lot (yet),
* but in the future we could implement combined copy+crc,
* just way we do for network layer checksums.
*/
static int
iscsi_tcp_chunk_recv(struct iscsi_tcp_conn *tcp_conn,
struct iscsi_chunk *chunk, const void *ptr,
unsigned int len)
{
struct scatterlist sg;
unsigned int copy, copied = 0;
while (!iscsi_tcp_chunk_done(chunk)) {
if (copied == len)
goto out;
copy = min(len - copied, chunk->size - chunk->copied);
memcpy(chunk->data + chunk->copied, ptr + copied, copy);
if (chunk->hash) {
sg_init_one(&sg, ptr + copied, copy);
crypto_hash_update(chunk->hash, &sg, copy);
}
chunk->copied += copy;
copied += copy;
}
out:
return copied;
}
static inline void
iscsi_tcp_dgst_header(struct hash_desc *hash, const void *hdr, size_t hdrlen,
unsigned char digest[ISCSI_DIGEST_SIZE])
{
struct scatterlist sg;
322
323
324
325
326
327
328
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
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
sg_init_one(&sg, hdr, hdrlen);
crypto_hash_digest(hash, &sg, hdrlen, digest);
}
static inline int
iscsi_tcp_dgst_verify(struct iscsi_tcp_conn *tcp_conn,
struct iscsi_chunk *chunk)
{
if (!chunk->digest_len)
return 1;
if (memcmp(chunk->recv_digest, chunk->digest, chunk->digest_len)) {
debug_scsi("digest mismatch\n");
return 0;
}
return 1;
}
/*
* Helper function to set up chunk buffer
*/
static inline void
__iscsi_chunk_init(struct iscsi_chunk *chunk, size_t size,
iscsi_chunk_done_fn_t *done, struct hash_desc *hash)
{
memset(chunk, 0, sizeof(*chunk));
chunk->total_size = size;
chunk->done = done;
if (hash) {
chunk->hash = hash;
crypto_hash_init(hash);
}
}
static inline void
iscsi_chunk_init_linear(struct iscsi_chunk *chunk, void *data, size_t size,
iscsi_chunk_done_fn_t *done, struct hash_desc *hash)
{
__iscsi_chunk_init(chunk, size, done, hash);
chunk->data = data;
chunk->size = size;
}
static inline int
iscsi_chunk_seek_sg(struct iscsi_chunk *chunk,
struct scatterlist *sg, unsigned int sg_count,
unsigned int offset, size_t size,
iscsi_chunk_done_fn_t *done, struct hash_desc *hash)
{
unsigned int i;
__iscsi_chunk_init(chunk, size, done, hash);
for (i = 0; i < sg_count; ++i) {
if (offset < sg[i].length) {
chunk->sg = sg;
chunk->sg_count = sg_count;
iscsi_tcp_chunk_init_sg(chunk, i, offset);
return 0;
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
return ISCSI_ERR_DATA_OFFSET;
}
/**
* iscsi_tcp_hdr_recv_prep - prep chunk for hdr reception
* @tcp_conn: iscsi connection to prep for
*
* This function always passes NULL for the hash argument, because when this
* function is called we do not yet know the final size of the header and want
* to delay the digest processing until we know that.
*/
static void
iscsi_tcp_hdr_recv_prep(struct iscsi_tcp_conn *tcp_conn)
{
debug_tcp("iscsi_tcp_hdr_recv_prep(%p%s)\n", tcp_conn,
tcp_conn->iscsi_conn->hdrdgst_en ? ", digest enabled" : "");
iscsi_chunk_init_linear(&tcp_conn->in.chunk,
tcp_conn->in.hdr_buf, sizeof(struct iscsi_hdr),
iscsi_tcp_hdr_recv_done, NULL);
}
/*
* Handle incoming reply to any other type of command
*/
static int
iscsi_tcp_data_recv_done(struct iscsi_tcp_conn *tcp_conn,
struct iscsi_chunk *chunk)
{
struct iscsi_conn *conn = tcp_conn->iscsi_conn;
int rc = 0;
if (!iscsi_tcp_dgst_verify(tcp_conn, chunk))
return ISCSI_ERR_DATA_DGST;
rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr,
conn->data, tcp_conn->in.datalen);
if (rc)
return rc;
iscsi_tcp_hdr_recv_prep(tcp_conn);
return 0;
}
static void
iscsi_tcp_data_recv_prep(struct iscsi_tcp_conn *tcp_conn)
{
struct iscsi_conn *conn = tcp_conn->iscsi_conn;
struct hash_desc *rx_hash = NULL;
if (conn->datadgst_en)
rx_hash = &tcp_conn->rx_hash;
iscsi_chunk_init_linear(&tcp_conn->in.chunk,
conn->data, tcp_conn->in.datalen,
iscsi_tcp_data_recv_done, rx_hash);
}
/*
* must be called with session lock
*/
static void
iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
/* 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");
}
tcp_ctask->xmstate = XMSTATE_IDLE;
}
/**
* 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)
{
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;

Mike Christie
committed
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)

Mike Christie
committed
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;

Mike Christie
committed
}

Mike Christie
committed
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)) {

Mike Christie
committed
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;

Mike Christie
committed
}
if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) {
sc->result = (DID_OK << 16) | rhdr->cmd_status;
conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1;
if (rhdr->flags & (ISCSI_FLAG_DATA_UNDERFLOW |
ISCSI_FLAG_DATA_OVERFLOW)) {
int res_count = be32_to_cpu(rhdr->residual_count);
if (res_count > 0 &&
(rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW ||
res_count <= scsi_bufflen(sc)))
scsi_set_resid(sc, res_count);
sc->result = (DID_BAD_TARGET << 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 scsi_cmnd *sc = ctask->sc;
int i, sg_count = 0;
struct scatterlist *sg;
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,
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 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;
if (tcp_conn->in.datalen) {
printk(KERN_ERR "iscsi_tcp: invalid R2t with datalen %d\n",
tcp_conn->in.datalen);
return ISCSI_ERR_DATALEN;

Mike Christie
committed
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;

Mike Christie
committed
}
/* fill-in new R2T associated with the task */
spin_lock(&session->lock);
iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
if (!ctask->sc || 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);

Mike Christie
committed
tcp_ctask->exp_datasn = r2tsn + 1;
__kfifo_put(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*));
tcp_ctask->xmstate |= XMSTATE_SOL_HDR_INIT;
conn->r2t_pdus_cnt++;
iscsi_requeue_ctask(ctask);
spin_unlock(&session->lock);
return 0;
}
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
/*
* Handle incoming reply to DataIn command
*/
static int
iscsi_tcp_process_data_in(struct iscsi_tcp_conn *tcp_conn,
struct iscsi_chunk *chunk)
{
struct iscsi_conn *conn = tcp_conn->iscsi_conn;
struct iscsi_hdr *hdr = tcp_conn->in.hdr;
int rc;
if (!iscsi_tcp_dgst_verify(tcp_conn, chunk))
return ISCSI_ERR_DATA_DGST;
/* check for non-exceptional status */
if (hdr->flags & ISCSI_FLAG_DATA_STATUS) {
rc = iscsi_complete_pdu(conn, tcp_conn->in.hdr, NULL, 0);
if (rc)
return rc;
}
iscsi_tcp_hdr_recv_prep(tcp_conn);
return 0;
}
/**
* iscsi_tcp_hdr_dissect - process PDU header
* @conn: iSCSI connection
* @hdr: PDU header
*
* This function analyzes the header of the PDU received,
* and performs several sanity checks. If the PDU is accompanied
* by data, the receive buffer is set up to copy the incoming data
* to the correct location.
*/
iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
struct iscsi_session *session = conn->session;
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
struct iscsi_cmd_task *ctask;
uint32_t 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;
}
/* Additional header segments. So far, we don't
* process additional headers.
*/
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 ahslen %d datalen %d\n",
opcode, ahslen, tcp_conn->in.datalen);
switch(opcode) {
case ISCSI_OP_SCSI_DATA_IN:
ctask = session->cmds[itt];
rc = iscsi_data_rsp(conn, ctask);
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
if (tcp_conn->in.datalen) {
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
struct hash_desc *rx_hash = NULL;
/*
* Setup copy of Data-In into the Scsi_Cmnd
* Scatterlist case:
* We set up the iscsi_chunk to point to the next
* scatterlist entry to copy to. As we go along,
* we move on to the next scatterlist entry and
* update the digest per-entry.
*/
if (conn->datadgst_en)
rx_hash = &tcp_conn->rx_hash;
debug_tcp("iscsi_tcp_begin_data_in(%p, offset=%d, "
"datalen=%d)\n", tcp_conn,
tcp_ctask->data_offset,
tcp_conn->in.datalen);
return iscsi_chunk_seek_sg(&tcp_conn->in.chunk,
scsi_sglist(ctask->sc),
scsi_sg_count(ctask->sc),
tcp_ctask->data_offset,
tcp_conn->in.datalen,
iscsi_tcp_process_data_in,
rx_hash);
}
/* fall through */
case ISCSI_OP_SCSI_CMD_RSP:
if (tcp_conn->in.datalen) {
iscsi_tcp_data_recv_prep(tcp_conn);
return 0;
}
rc = iscsi_complete_pdu(conn, hdr, NULL, 0);
break;
case ISCSI_OP_R2T:
if (ahslen)
rc = ISCSI_ERR_AHSLEN;
else if (ctask->sc->sc_data_direction == DMA_TO_DEVICE)
rc = iscsi_r2t_rsp(conn, ctask);
else
rc = ISCSI_ERR_PROTO;
break;
case ISCSI_OP_LOGIN_RSP:
case ISCSI_OP_TEXT_RSP:
case ISCSI_OP_REJECT:
case ISCSI_OP_ASYNC_EVENT:
/*
* It is possible that we could get a PDU with a buffer larger
* than 8K, but there are no targets that currently do this.
* For now we fail until we find a vendor that needs it
*/
if (ISCSI_DEF_MAX_RECV_SEG_LEN < tcp_conn->in.datalen) {
printk(KERN_ERR "iscsi_tcp: received buffer of len %u "
"but conn buffer is only %u (opcode %0x)\n",
tcp_conn->in.datalen,
ISCSI_DEF_MAX_RECV_SEG_LEN, opcode);
rc = ISCSI_ERR_PROTO;
break;
}
/* If there's data coming in with the response,
* receive it to the connection's buffer.
*/
if (tcp_conn->in.datalen) {
iscsi_tcp_data_recv_prep(tcp_conn);
return 0;
}
case ISCSI_OP_LOGOUT_RSP:
case ISCSI_OP_NOOP_IN:
case ISCSI_OP_SCSI_TMFUNC_RSP:
rc = iscsi_complete_pdu(conn, hdr, NULL, 0);
break;
default:
rc = ISCSI_ERR_BAD_OPCODE;
break;
}
if (rc == 0) {
/* Anything that comes with data should have
* been handled above. */
if (tcp_conn->in.datalen)
return ISCSI_ERR_PROTO;
iscsi_tcp_hdr_recv_prep(tcp_conn);
}
static inline void
partial_sg_digest_update(struct hash_desc *desc, struct scatterlist *sg,
int offset, int length)
{
struct scatterlist temp;
sg_init_table(&temp, 1);
sg_set_page(&temp, sg_page(sg), length, offset);
crypto_hash_update(desc, &temp, length);
/**
* iscsi_tcp_hdr_recv_done - process PDU header
*
* This is the callback invoked when the PDU header has
* been received. If the header is followed by additional
* header segments, we go back for more data.
*/
static int
iscsi_tcp_hdr_recv_done(struct iscsi_tcp_conn *tcp_conn,
struct iscsi_chunk *chunk)
struct iscsi_conn *conn = tcp_conn->iscsi_conn;
struct iscsi_hdr *hdr;
/* Check if there are additional header segments
* *prior* to computing the digest, because we
* may need to go back to the caller for more.
*/
hdr = (struct iscsi_hdr *) tcp_conn->in.hdr_buf;
if (chunk->copied == sizeof(struct iscsi_hdr) && hdr->hlength) {
/* Bump the header length - the caller will
* just loop around and get the AHS for us, and
* call again. */
unsigned int ahslen = hdr->hlength << 2;
/* Make sure we don't overflow */
if (sizeof(*hdr) + ahslen > sizeof(tcp_conn->in.hdr_buf))
return ISCSI_ERR_AHSLEN;
chunk->total_size += ahslen;
chunk->size += ahslen;
return 0;
/* We're done processing the header. See if we're doing
* header digests; if so, set up the recv_digest buffer
* and go back for more. */
if (conn->hdrdgst_en) {
if (chunk->digest_len == 0) {
iscsi_tcp_chunk_splice_digest(chunk,
chunk->recv_digest);
return 0;
iscsi_tcp_dgst_header(&tcp_conn->rx_hash, hdr,
chunk->total_copied - ISCSI_DIGEST_SIZE,
chunk->digest);
if (!iscsi_tcp_dgst_verify(tcp_conn, chunk))
return ISCSI_ERR_HDR_DGST;
tcp_conn->in.hdr = hdr;
return iscsi_tcp_hdr_dissect(conn, hdr);
* iscsi_tcp_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_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
unsigned int offset, size_t len)
{
struct iscsi_conn *conn = rd_desc->arg.data;
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
struct iscsi_chunk *chunk = &tcp_conn->in.chunk;
struct skb_seq_state seq;
unsigned int consumed = 0;
int rc = 0;
debug_tcp("in %d bytes\n", skb->len - offset);
if (unlikely(conn->suspend_rx)) {
debug_tcp("conn %d Rx suspended!\n", conn->id);
return 0;
}
skb_prepare_seq_read(skb, offset, skb->len, &seq);
while (1) {
unsigned int avail;
const u8 *ptr;
avail = skb_seq_read(consumed, &ptr, &seq);
if (avail == 0)
break;
BUG_ON(chunk->copied >= chunk->size);
debug_tcp("skb %p ptr=%p avail=%u\n", skb, ptr, avail);
rc = iscsi_tcp_chunk_recv(tcp_conn, chunk, ptr, avail);
BUG_ON(rc == 0);
consumed += rc;
if (chunk->total_copied >= chunk->total_size) {
rc = chunk->done(tcp_conn, chunk);
if (rc != 0) {
skb_abort_seq_read(&seq);
goto error;
/* The done() functions sets up the
* next chunk. */
conn->rxdata_octets += consumed;
return consumed;
error:
debug_tcp("Error receiving PDU, errno=%d\n", rc);
iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
return 0;
}
static void
iscsi_tcp_data_ready(struct sock *sk, int flag)
{
struct iscsi_conn *conn = sk->sk_user_data;
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
read_descriptor_t rd_desc;
read_lock(&sk->sk_callback_lock);
* Use rd_desc to pass 'conn' to iscsi_tcp_recv.
* We set count to 1 because we want the network layer to
* hand us all the skbs that are available. iscsi_tcp_recv
* handled pdus that cross buffers or pdus that still need data.
*/
rd_desc.arg.data = conn;
tcp_read_sock(sk, &rd_desc, iscsi_tcp_recv);
read_unlock(&sk->sk_callback_lock);
/* If we had to (atomically) map a highmem page,
* unmap it now. */
iscsi_tcp_chunk_unmap(&tcp_conn->in.chunk);