f6d5180c78
During root boot and shutdown the target could send us nops. At this time iscsid cannot be running, so the target will drop the session and the boot or shutdown will hang. To handle this and allow us to better control when to check the network this patch moves the nop handling to the kernel. Signed-off-by: Mike Christie <michaelc@cs.wisc.edu> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
2310 lines
60 KiB
C
2310 lines
60 KiB
C
/*
|
|
* 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");
|
|
#undef 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);
|
|
|
|
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;
|
|
ibuf->sent = 0;
|
|
ibuf->use_sendmsg = 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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];
|
|
unsigned int pad;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
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;
|
|
}
|
|
offset -= sg[i].length;
|
|
}
|
|
|
|
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;
|
|
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_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)
|
|
{
|
|
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) {
|
|
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);
|
|
else
|
|
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;
|
|
|
|
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 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;
|
|
}
|
|
|
|
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 || 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*));
|
|
tcp_ctask->xmstate |= XMSTATE_SOL_HDR_INIT;
|
|
conn->r2t_pdus_cnt++;
|
|
|
|
iscsi_requeue_ctask(ctask);
|
|
spin_unlock(&session->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static int
|
|
iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
|
|
{
|
|
int rc = 0, opcode, ahslen;
|
|
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.
|
|
*/
|
|
ahslen = hdr->hlength << 2;
|
|
|
|
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) {
|
|
/* XXX: what does this do? */
|
|
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);
|
|
if (rc)
|
|
return rc;
|
|
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:
|
|
ctask = session->cmds[itt];
|
|
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;
|
|
}
|
|
/* fall through */
|
|
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);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
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;
|
|
rd_desc.count = 1;
|
|
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);
|
|
}
|
|
|
|
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 *);
|
|
|
|
read_lock(&sk->sk_callback_lock);
|
|
|
|
conn = (struct iscsi_conn*)sk->sk_user_data;
|
|
session = conn->session;
|
|
|
|
if ((sk->sk_state == TCP_CLOSE_WAIT ||
|
|
sk->sk_state == TCP_CLOSE) &&
|
|
!atomic_read(&sk->sk_rmem_alloc)) {
|
|
debug_tcp("iscsi_tcp_state_change: TCP_CLOSE|TCP_CLOSE_WAIT\n");
|
|
iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
|
|
}
|
|
|
|
tcp_conn = conn->dd_data;
|
|
old_state_change = tcp_conn->old_state_change;
|
|
|
|
read_unlock(&sk->sk_callback_lock);
|
|
|
|
old_state_change(sk);
|
|
}
|
|
|
|
/**
|
|
* iscsi_write_space - Called when more output buffer space is available
|
|
* @sk: socket space is available for
|
|
**/
|
|
static void
|
|
iscsi_write_space(struct sock *sk)
|
|
{
|
|
struct iscsi_conn *conn = (struct iscsi_conn*)sk->sk_user_data;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
|
|
tcp_conn->old_write_space(sk);
|
|
debug_tcp("iscsi_write_space: cid %d\n", conn->id);
|
|
scsi_queue_work(conn->session->host, &conn->xmitwork);
|
|
}
|
|
|
|
static void
|
|
iscsi_conn_set_callbacks(struct iscsi_conn *conn)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct sock *sk = tcp_conn->sock->sk;
|
|
|
|
/* assign new callbacks */
|
|
write_lock_bh(&sk->sk_callback_lock);
|
|
sk->sk_user_data = conn;
|
|
tcp_conn->old_data_ready = sk->sk_data_ready;
|
|
tcp_conn->old_state_change = sk->sk_state_change;
|
|
tcp_conn->old_write_space = sk->sk_write_space;
|
|
sk->sk_data_ready = iscsi_tcp_data_ready;
|
|
sk->sk_state_change = iscsi_tcp_state_change;
|
|
sk->sk_write_space = iscsi_write_space;
|
|
write_unlock_bh(&sk->sk_callback_lock);
|
|
}
|
|
|
|
static void
|
|
iscsi_conn_restore_callbacks(struct iscsi_tcp_conn *tcp_conn)
|
|
{
|
|
struct sock *sk = tcp_conn->sock->sk;
|
|
|
|
/* restore socket callbacks, see also: iscsi_conn_set_callbacks() */
|
|
write_lock_bh(&sk->sk_callback_lock);
|
|
sk->sk_user_data = NULL;
|
|
sk->sk_data_ready = tcp_conn->old_data_ready;
|
|
sk->sk_state_change = tcp_conn->old_state_change;
|
|
sk->sk_write_space = tcp_conn->old_write_space;
|
|
sk->sk_no_check = 0;
|
|
write_unlock_bh(&sk->sk_callback_lock);
|
|
}
|
|
|
|
/**
|
|
* iscsi_send - generic send routine
|
|
* @sk: kernel's socket
|
|
* @buf: buffer to write from
|
|
* @size: actual size to write
|
|
* @flags: socket's flags
|
|
*/
|
|
static inline int
|
|
iscsi_send(struct iscsi_conn *conn, struct iscsi_buf *buf, int size, int flags)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct socket *sk = tcp_conn->sock;
|
|
int offset = buf->sg.offset + buf->sent, res;
|
|
|
|
/*
|
|
* if we got use_sg=0 or are sending something we kmallocd
|
|
* then we did not have to do kmap (kmap returns page_address)
|
|
*
|
|
* if we got use_sg > 0, but had to drop down, we do not
|
|
* set clustering so this should only happen for that
|
|
* slab case.
|
|
*/
|
|
if (buf->use_sendmsg)
|
|
res = sock_no_sendpage(sk, buf->sg.page, offset, size, flags);
|
|
else
|
|
res = tcp_conn->sendpage(sk, buf->sg.page, offset, size, flags);
|
|
|
|
if (res >= 0) {
|
|
conn->txdata_octets += res;
|
|
buf->sent += res;
|
|
return res;
|
|
}
|
|
|
|
tcp_conn->sendpage_failures_cnt++;
|
|
if (res == -EAGAIN)
|
|
res = -ENOBUFS;
|
|
else
|
|
iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* iscsi_sendhdr - send PDU Header via tcp_sendpage()
|
|
* @conn: iscsi connection
|
|
* @buf: buffer to write from
|
|
* @datalen: lenght of data to be sent after the header
|
|
*
|
|
* Notes:
|
|
* (Tx, Fast Path)
|
|
**/
|
|
static inline int
|
|
iscsi_sendhdr(struct iscsi_conn *conn, struct iscsi_buf *buf, int datalen)
|
|
{
|
|
int flags = 0; /* MSG_DONTWAIT; */
|
|
int res, size;
|
|
|
|
size = buf->sg.length - buf->sent;
|
|
BUG_ON(buf->sent + size > buf->sg.length);
|
|
if (buf->sent + size != buf->sg.length || datalen)
|
|
flags |= MSG_MORE;
|
|
|
|
res = iscsi_send(conn, buf, size, flags);
|
|
debug_tcp("sendhdr %d bytes, sent %d res %d\n", size, buf->sent, res);
|
|
if (res >= 0) {
|
|
if (size != res)
|
|
return -EAGAIN;
|
|
return 0;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* iscsi_sendpage - send one page of iSCSI Data-Out.
|
|
* @conn: iscsi connection
|
|
* @buf: buffer to write from
|
|
* @count: remaining data
|
|
* @sent: number of bytes sent
|
|
*
|
|
* Notes:
|
|
* (Tx, Fast Path)
|
|
**/
|
|
static inline int
|
|
iscsi_sendpage(struct iscsi_conn *conn, struct iscsi_buf *buf,
|
|
int *count, int *sent)
|
|
{
|
|
int flags = 0; /* MSG_DONTWAIT; */
|
|
int res, size;
|
|
|
|
size = buf->sg.length - buf->sent;
|
|
BUG_ON(buf->sent + size > buf->sg.length);
|
|
if (size > *count)
|
|
size = *count;
|
|
if (buf->sent + size != buf->sg.length || *count != size)
|
|
flags |= MSG_MORE;
|
|
|
|
res = iscsi_send(conn, buf, size, flags);
|
|
debug_tcp("sendpage: %d bytes, sent %d left %d sent %d res %d\n",
|
|
size, buf->sent, *count, *sent, res);
|
|
if (res >= 0) {
|
|
*count -= res;
|
|
*sent += res;
|
|
if (size != res)
|
|
return -EAGAIN;
|
|
return 0;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline void
|
|
iscsi_data_digest_init(struct iscsi_tcp_conn *tcp_conn,
|
|
struct iscsi_tcp_cmd_task *tcp_ctask)
|
|
{
|
|
crypto_hash_init(&tcp_conn->tx_hash);
|
|
tcp_ctask->digest_count = 4;
|
|
}
|
|
|
|
/**
|
|
* iscsi_solicit_data_cont - initialize next Data-Out
|
|
* @conn: iscsi connection
|
|
* @ctask: scsi command task
|
|
* @r2t: R2T info
|
|
* @left: bytes left to transfer
|
|
*
|
|
* Notes:
|
|
* Initialize next Data-Out within this R2T sequence and continue
|
|
* to process next Scatter-Gather element(if any) of this SCSI command.
|
|
*
|
|
* Called under connection lock.
|
|
**/
|
|
static void
|
|
iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
|
|
struct iscsi_r2t_info *r2t, int left)
|
|
{
|
|
struct iscsi_data *hdr;
|
|
int new_offset;
|
|
|
|
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;
|
|
new_offset = r2t->data_offset + r2t->sent;
|
|
hdr->offset = cpu_to_be32(new_offset);
|
|
if (left > conn->max_xmit_dlength) {
|
|
hton24(hdr->dlength, conn->max_xmit_dlength);
|
|
r2t->data_count = conn->max_xmit_dlength;
|
|
} else {
|
|
hton24(hdr->dlength, left);
|
|
r2t->data_count = left;
|
|
hdr->flags = ISCSI_FLAG_CMD_FINAL;
|
|
}
|
|
conn->dataout_pdus_cnt++;
|
|
|
|
iscsi_buf_init_iov(&r2t->headbuf, (char*)hdr,
|
|
sizeof(struct iscsi_hdr));
|
|
|
|
if (iscsi_buf_left(&r2t->sendbuf))
|
|
return;
|
|
|
|
iscsi_buf_init_sg(&r2t->sendbuf, r2t->sg);
|
|
r2t->sg += 1;
|
|
}
|
|
|
|
static void iscsi_set_padding(struct iscsi_tcp_cmd_task *tcp_ctask,
|
|
unsigned long len)
|
|
{
|
|
tcp_ctask->pad_count = len & (ISCSI_PAD_LEN - 1);
|
|
if (!tcp_ctask->pad_count)
|
|
return;
|
|
|
|
tcp_ctask->pad_count = ISCSI_PAD_LEN - tcp_ctask->pad_count;
|
|
debug_scsi("write padding %d bytes\n", tcp_ctask->pad_count);
|
|
tcp_ctask->xmstate |= XMSTATE_W_PAD;
|
|
}
|
|
|
|
/**
|
|
* iscsi_tcp_cmd_init - Initialize iSCSI SCSI_READ or SCSI_WRITE commands
|
|
* @conn: iscsi connection
|
|
* @ctask: scsi command task
|
|
* @sc: scsi command
|
|
**/
|
|
static void
|
|
iscsi_tcp_cmd_init(struct iscsi_cmd_task *ctask)
|
|
{
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
|
|
BUG_ON(__kfifo_len(tcp_ctask->r2tqueue));
|
|
tcp_ctask->xmstate = XMSTATE_CMD_HDR_INIT;
|
|
}
|
|
|
|
/**
|
|
* iscsi_tcp_mtask_xmit - xmit management(immediate) task
|
|
* @conn: iscsi connection
|
|
* @mtask: task management task
|
|
*
|
|
* Notes:
|
|
* The function can return -EAGAIN in which case caller must
|
|
* call it again later, or recover. '0' return code means successful
|
|
* xmit.
|
|
*
|
|
* Management xmit state machine consists of these states:
|
|
* XMSTATE_IMM_HDR_INIT - calculate digest of PDU Header
|
|
* XMSTATE_IMM_HDR - PDU Header xmit in progress
|
|
* XMSTATE_IMM_DATA - PDU Data xmit in progress
|
|
* XMSTATE_IDLE - management PDU is done
|
|
**/
|
|
static int
|
|
iscsi_tcp_mtask_xmit(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
|
|
{
|
|
struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
|
|
int rc;
|
|
|
|
debug_scsi("mtask deq [cid %d state %x itt 0x%x]\n",
|
|
conn->id, tcp_mtask->xmstate, mtask->itt);
|
|
|
|
if (tcp_mtask->xmstate & XMSTATE_IMM_HDR_INIT) {
|
|
iscsi_buf_init_iov(&tcp_mtask->headbuf, (char*)mtask->hdr,
|
|
sizeof(struct iscsi_hdr));
|
|
|
|
if (mtask->data_count) {
|
|
tcp_mtask->xmstate |= XMSTATE_IMM_DATA;
|
|
iscsi_buf_init_iov(&tcp_mtask->sendbuf,
|
|
(char*)mtask->data,
|
|
mtask->data_count);
|
|
}
|
|
|
|
if (conn->c_stage != ISCSI_CONN_INITIAL_STAGE &&
|
|
conn->stop_stage != STOP_CONN_RECOVER &&
|
|
conn->hdrdgst_en)
|
|
iscsi_hdr_digest(conn, &tcp_mtask->headbuf,
|
|
(u8*)tcp_mtask->hdrext);
|
|
|
|
tcp_mtask->sent = 0;
|
|
tcp_mtask->xmstate &= ~XMSTATE_IMM_HDR_INIT;
|
|
tcp_mtask->xmstate |= XMSTATE_IMM_HDR;
|
|
}
|
|
|
|
if (tcp_mtask->xmstate & XMSTATE_IMM_HDR) {
|
|
rc = iscsi_sendhdr(conn, &tcp_mtask->headbuf,
|
|
mtask->data_count);
|
|
if (rc)
|
|
return rc;
|
|
tcp_mtask->xmstate &= ~XMSTATE_IMM_HDR;
|
|
}
|
|
|
|
if (tcp_mtask->xmstate & XMSTATE_IMM_DATA) {
|
|
BUG_ON(!mtask->data_count);
|
|
tcp_mtask->xmstate &= ~XMSTATE_IMM_DATA;
|
|
/* FIXME: implement.
|
|
* Virtual buffer could be spreaded across multiple pages...
|
|
*/
|
|
do {
|
|
int rc;
|
|
|
|
rc = iscsi_sendpage(conn, &tcp_mtask->sendbuf,
|
|
&mtask->data_count, &tcp_mtask->sent);
|
|
if (rc) {
|
|
tcp_mtask->xmstate |= XMSTATE_IMM_DATA;
|
|
return rc;
|
|
}
|
|
} while (mtask->data_count);
|
|
}
|
|
|
|
BUG_ON(tcp_mtask->xmstate != XMSTATE_IDLE);
|
|
if (mtask->hdr->itt == RESERVED_ITT) {
|
|
struct iscsi_session *session = conn->session;
|
|
|
|
spin_lock_bh(&session->lock);
|
|
iscsi_free_mgmt_task(conn, mtask);
|
|
spin_unlock_bh(&session->lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
iscsi_send_cmd_hdr(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
|
|
{
|
|
struct scsi_cmnd *sc = ctask->sc;
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
int rc = 0;
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_CMD_HDR_INIT) {
|
|
tcp_ctask->sent = 0;
|
|
tcp_ctask->sg_count = 0;
|
|
tcp_ctask->exp_datasn = 0;
|
|
|
|
if (sc->sc_data_direction == DMA_TO_DEVICE) {
|
|
struct scatterlist *sg = scsi_sglist(sc);
|
|
|
|
iscsi_buf_init_sg(&tcp_ctask->sendbuf, sg);
|
|
tcp_ctask->sg = sg + 1;
|
|
tcp_ctask->bad_sg = sg + scsi_sg_count(sc);
|
|
|
|
debug_scsi("cmd [itt 0x%x total %d imm_data %d "
|
|
"unsol count %d, unsol offset %d]\n",
|
|
ctask->itt, scsi_bufflen(sc),
|
|
ctask->imm_count, ctask->unsol_count,
|
|
ctask->unsol_offset);
|
|
}
|
|
|
|
iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)ctask->hdr,
|
|
ctask->hdr_len);
|
|
|
|
if (conn->hdrdgst_en)
|
|
iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
|
|
iscsi_next_hdr(ctask));
|
|
tcp_ctask->xmstate &= ~XMSTATE_CMD_HDR_INIT;
|
|
tcp_ctask->xmstate |= XMSTATE_CMD_HDR_XMIT;
|
|
}
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_CMD_HDR_XMIT) {
|
|
rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->imm_count);
|
|
if (rc)
|
|
return rc;
|
|
tcp_ctask->xmstate &= ~XMSTATE_CMD_HDR_XMIT;
|
|
|
|
if (sc->sc_data_direction != DMA_TO_DEVICE)
|
|
return 0;
|
|
|
|
if (ctask->imm_count) {
|
|
tcp_ctask->xmstate |= XMSTATE_IMM_DATA;
|
|
iscsi_set_padding(tcp_ctask, ctask->imm_count);
|
|
|
|
if (ctask->conn->datadgst_en) {
|
|
iscsi_data_digest_init(ctask->conn->dd_data,
|
|
tcp_ctask);
|
|
tcp_ctask->immdigest = 0;
|
|
}
|
|
}
|
|
|
|
if (ctask->unsol_count)
|
|
tcp_ctask->xmstate |=
|
|
XMSTATE_UNS_HDR | XMSTATE_UNS_INIT;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
iscsi_send_padding(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
|
|
{
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
int sent = 0, rc;
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_W_PAD) {
|
|
iscsi_buf_init_iov(&tcp_ctask->sendbuf, (char*)&tcp_ctask->pad,
|
|
tcp_ctask->pad_count);
|
|
if (conn->datadgst_en)
|
|
crypto_hash_update(&tcp_conn->tx_hash,
|
|
&tcp_ctask->sendbuf.sg,
|
|
tcp_ctask->sendbuf.sg.length);
|
|
} else if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_PAD))
|
|
return 0;
|
|
|
|
tcp_ctask->xmstate &= ~XMSTATE_W_PAD;
|
|
tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_PAD;
|
|
debug_scsi("sending %d pad bytes for itt 0x%x\n",
|
|
tcp_ctask->pad_count, ctask->itt);
|
|
rc = iscsi_sendpage(conn, &tcp_ctask->sendbuf, &tcp_ctask->pad_count,
|
|
&sent);
|
|
if (rc) {
|
|
debug_scsi("padding send failed %d\n", rc);
|
|
tcp_ctask->xmstate |= XMSTATE_W_RESEND_PAD;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
iscsi_send_digest(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask,
|
|
struct iscsi_buf *buf, uint32_t *digest)
|
|
{
|
|
struct iscsi_tcp_cmd_task *tcp_ctask;
|
|
struct iscsi_tcp_conn *tcp_conn;
|
|
int rc, sent = 0;
|
|
|
|
if (!conn->datadgst_en)
|
|
return 0;
|
|
|
|
tcp_ctask = ctask->dd_data;
|
|
tcp_conn = conn->dd_data;
|
|
|
|
if (!(tcp_ctask->xmstate & XMSTATE_W_RESEND_DATA_DIGEST)) {
|
|
crypto_hash_final(&tcp_conn->tx_hash, (u8*)digest);
|
|
iscsi_buf_init_iov(buf, (char*)digest, 4);
|
|
}
|
|
tcp_ctask->xmstate &= ~XMSTATE_W_RESEND_DATA_DIGEST;
|
|
|
|
rc = iscsi_sendpage(conn, buf, &tcp_ctask->digest_count, &sent);
|
|
if (!rc)
|
|
debug_scsi("sent digest 0x%x for itt 0x%x\n", *digest,
|
|
ctask->itt);
|
|
else {
|
|
debug_scsi("sending digest 0x%x failed for itt 0x%x!\n",
|
|
*digest, ctask->itt);
|
|
tcp_ctask->xmstate |= XMSTATE_W_RESEND_DATA_DIGEST;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
iscsi_send_data(struct iscsi_cmd_task *ctask, struct iscsi_buf *sendbuf,
|
|
struct scatterlist **sg, int *sent, int *count,
|
|
struct iscsi_buf *digestbuf, uint32_t *digest)
|
|
{
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
struct iscsi_conn *conn = ctask->conn;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
int rc, buf_sent, offset;
|
|
|
|
while (*count) {
|
|
buf_sent = 0;
|
|
offset = sendbuf->sent;
|
|
|
|
rc = iscsi_sendpage(conn, sendbuf, count, &buf_sent);
|
|
*sent = *sent + buf_sent;
|
|
if (buf_sent && conn->datadgst_en)
|
|
partial_sg_digest_update(&tcp_conn->tx_hash,
|
|
&sendbuf->sg, sendbuf->sg.offset + offset,
|
|
buf_sent);
|
|
if (!iscsi_buf_left(sendbuf) && *sg != tcp_ctask->bad_sg) {
|
|
iscsi_buf_init_sg(sendbuf, *sg);
|
|
*sg = *sg + 1;
|
|
}
|
|
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
rc = iscsi_send_padding(conn, ctask);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return iscsi_send_digest(conn, ctask, digestbuf, digest);
|
|
}
|
|
|
|
static int
|
|
iscsi_send_unsol_hdr(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
|
|
{
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
struct iscsi_data_task *dtask;
|
|
int rc;
|
|
|
|
tcp_ctask->xmstate |= XMSTATE_UNS_DATA;
|
|
if (tcp_ctask->xmstate & XMSTATE_UNS_INIT) {
|
|
dtask = &tcp_ctask->unsol_dtask;
|
|
|
|
iscsi_prep_unsolicit_data_pdu(ctask, &dtask->hdr);
|
|
iscsi_buf_init_iov(&tcp_ctask->headbuf, (char*)&dtask->hdr,
|
|
sizeof(struct iscsi_hdr));
|
|
if (conn->hdrdgst_en)
|
|
iscsi_hdr_digest(conn, &tcp_ctask->headbuf,
|
|
(u8*)dtask->hdrext);
|
|
|
|
tcp_ctask->xmstate &= ~XMSTATE_UNS_INIT;
|
|
iscsi_set_padding(tcp_ctask, ctask->data_count);
|
|
}
|
|
|
|
rc = iscsi_sendhdr(conn, &tcp_ctask->headbuf, ctask->data_count);
|
|
if (rc) {
|
|
tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA;
|
|
tcp_ctask->xmstate |= XMSTATE_UNS_HDR;
|
|
return rc;
|
|
}
|
|
|
|
if (conn->datadgst_en) {
|
|
dtask = &tcp_ctask->unsol_dtask;
|
|
iscsi_data_digest_init(ctask->conn->dd_data, tcp_ctask);
|
|
dtask->digest = 0;
|
|
}
|
|
|
|
debug_scsi("uns dout [itt 0x%x dlen %d sent %d]\n",
|
|
ctask->itt, ctask->unsol_count, tcp_ctask->sent);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
iscsi_send_unsol_pdu(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
|
|
{
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
int rc;
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_UNS_HDR) {
|
|
BUG_ON(!ctask->unsol_count);
|
|
tcp_ctask->xmstate &= ~XMSTATE_UNS_HDR;
|
|
send_hdr:
|
|
rc = iscsi_send_unsol_hdr(conn, ctask);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_UNS_DATA) {
|
|
struct iscsi_data_task *dtask = &tcp_ctask->unsol_dtask;
|
|
int start = tcp_ctask->sent;
|
|
|
|
rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg,
|
|
&tcp_ctask->sent, &ctask->data_count,
|
|
&dtask->digestbuf, &dtask->digest);
|
|
ctask->unsol_count -= tcp_ctask->sent - start;
|
|
if (rc)
|
|
return rc;
|
|
tcp_ctask->xmstate &= ~XMSTATE_UNS_DATA;
|
|
/*
|
|
* Done with the Data-Out. Next, check if we need
|
|
* to send another unsolicited Data-Out.
|
|
*/
|
|
if (ctask->unsol_count) {
|
|
debug_scsi("sending more uns\n");
|
|
tcp_ctask->xmstate |= XMSTATE_UNS_INIT;
|
|
goto send_hdr;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int iscsi_send_sol_pdu(struct iscsi_conn *conn,
|
|
struct iscsi_cmd_task *ctask)
|
|
{
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
struct iscsi_session *session = conn->session;
|
|
struct iscsi_r2t_info *r2t;
|
|
struct iscsi_data_task *dtask;
|
|
int left, rc;
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_SOL_HDR_INIT) {
|
|
if (!tcp_ctask->r2t) {
|
|
spin_lock_bh(&session->lock);
|
|
__kfifo_get(tcp_ctask->r2tqueue, (void*)&tcp_ctask->r2t,
|
|
sizeof(void*));
|
|
spin_unlock_bh(&session->lock);
|
|
}
|
|
send_hdr:
|
|
r2t = tcp_ctask->r2t;
|
|
dtask = &r2t->dtask;
|
|
|
|
if (conn->hdrdgst_en)
|
|
iscsi_hdr_digest(conn, &r2t->headbuf,
|
|
(u8*)dtask->hdrext);
|
|
tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR_INIT;
|
|
tcp_ctask->xmstate |= XMSTATE_SOL_HDR;
|
|
}
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_SOL_HDR) {
|
|
r2t = tcp_ctask->r2t;
|
|
dtask = &r2t->dtask;
|
|
|
|
rc = iscsi_sendhdr(conn, &r2t->headbuf, r2t->data_count);
|
|
if (rc)
|
|
return rc;
|
|
tcp_ctask->xmstate &= ~XMSTATE_SOL_HDR;
|
|
tcp_ctask->xmstate |= XMSTATE_SOL_DATA;
|
|
|
|
if (conn->datadgst_en) {
|
|
iscsi_data_digest_init(conn->dd_data, tcp_ctask);
|
|
dtask->digest = 0;
|
|
}
|
|
|
|
iscsi_set_padding(tcp_ctask, r2t->data_count);
|
|
debug_scsi("sol dout [dsn %d itt 0x%x dlen %d sent %d]\n",
|
|
r2t->solicit_datasn - 1, ctask->itt, r2t->data_count,
|
|
r2t->sent);
|
|
}
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_SOL_DATA) {
|
|
r2t = tcp_ctask->r2t;
|
|
dtask = &r2t->dtask;
|
|
|
|
rc = iscsi_send_data(ctask, &r2t->sendbuf, &r2t->sg,
|
|
&r2t->sent, &r2t->data_count,
|
|
&dtask->digestbuf, &dtask->digest);
|
|
if (rc)
|
|
return rc;
|
|
tcp_ctask->xmstate &= ~XMSTATE_SOL_DATA;
|
|
|
|
/*
|
|
* Done with this Data-Out. Next, check if we have
|
|
* to send another Data-Out for this R2T.
|
|
*/
|
|
BUG_ON(r2t->data_length - r2t->sent < 0);
|
|
left = r2t->data_length - r2t->sent;
|
|
if (left) {
|
|
iscsi_solicit_data_cont(conn, ctask, r2t, left);
|
|
goto send_hdr;
|
|
}
|
|
|
|
/*
|
|
* Done with this R2T. Check if there are more
|
|
* outstanding R2Ts ready to be processed.
|
|
*/
|
|
spin_lock_bh(&session->lock);
|
|
tcp_ctask->r2t = NULL;
|
|
__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
|
|
sizeof(void*));
|
|
if (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t,
|
|
sizeof(void*))) {
|
|
tcp_ctask->r2t = r2t;
|
|
spin_unlock_bh(&session->lock);
|
|
goto send_hdr;
|
|
}
|
|
spin_unlock_bh(&session->lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* iscsi_tcp_ctask_xmit - xmit normal PDU task
|
|
* @conn: iscsi connection
|
|
* @ctask: iscsi command task
|
|
*
|
|
* Notes:
|
|
* The function can return -EAGAIN in which case caller must
|
|
* call it again later, or recover. '0' return code means successful
|
|
* xmit.
|
|
* The function is devided to logical helpers (above) for the different
|
|
* xmit stages.
|
|
*
|
|
*iscsi_send_cmd_hdr()
|
|
* XMSTATE_CMD_HDR_INIT - prepare Header and Data buffers Calculate
|
|
* Header Digest
|
|
* XMSTATE_CMD_HDR_XMIT - Transmit header in progress
|
|
*
|
|
*iscsi_send_padding
|
|
* XMSTATE_W_PAD - Prepare and send pading
|
|
* XMSTATE_W_RESEND_PAD - retry send pading
|
|
*
|
|
*iscsi_send_digest
|
|
* XMSTATE_W_RESEND_DATA_DIGEST - Finalize and send Data Digest
|
|
* XMSTATE_W_RESEND_DATA_DIGEST - retry sending digest
|
|
*
|
|
*iscsi_send_unsol_hdr
|
|
* XMSTATE_UNS_INIT - prepare un-solicit data header and digest
|
|
* XMSTATE_UNS_HDR - send un-solicit header
|
|
*
|
|
*iscsi_send_unsol_pdu
|
|
* XMSTATE_UNS_DATA - send un-solicit data in progress
|
|
*
|
|
*iscsi_send_sol_pdu
|
|
* XMSTATE_SOL_HDR_INIT - solicit data header and digest initialize
|
|
* XMSTATE_SOL_HDR - send solicit header
|
|
* XMSTATE_SOL_DATA - send solicit data
|
|
*
|
|
*iscsi_tcp_ctask_xmit
|
|
* XMSTATE_IMM_DATA - xmit managment data (??)
|
|
**/
|
|
static int
|
|
iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
|
|
{
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
int rc = 0;
|
|
|
|
debug_scsi("ctask deq [cid %d xmstate %x itt 0x%x]\n",
|
|
conn->id, tcp_ctask->xmstate, ctask->itt);
|
|
|
|
rc = iscsi_send_cmd_hdr(conn, ctask);
|
|
if (rc)
|
|
return rc;
|
|
if (ctask->sc->sc_data_direction != DMA_TO_DEVICE)
|
|
return 0;
|
|
|
|
if (tcp_ctask->xmstate & XMSTATE_IMM_DATA) {
|
|
rc = iscsi_send_data(ctask, &tcp_ctask->sendbuf, &tcp_ctask->sg,
|
|
&tcp_ctask->sent, &ctask->imm_count,
|
|
&tcp_ctask->immbuf, &tcp_ctask->immdigest);
|
|
if (rc)
|
|
return rc;
|
|
tcp_ctask->xmstate &= ~XMSTATE_IMM_DATA;
|
|
}
|
|
|
|
rc = iscsi_send_unsol_pdu(conn, ctask);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = iscsi_send_sol_pdu(conn, ctask);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct iscsi_cls_conn *
|
|
iscsi_tcp_conn_create(struct iscsi_cls_session *cls_session, uint32_t conn_idx)
|
|
{
|
|
struct iscsi_conn *conn;
|
|
struct iscsi_cls_conn *cls_conn;
|
|
struct iscsi_tcp_conn *tcp_conn;
|
|
|
|
cls_conn = iscsi_conn_setup(cls_session, conn_idx);
|
|
if (!cls_conn)
|
|
return NULL;
|
|
conn = cls_conn->dd_data;
|
|
/*
|
|
* due to strange issues with iser these are not set
|
|
* in iscsi_conn_setup
|
|
*/
|
|
conn->max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN;
|
|
|
|
tcp_conn = kzalloc(sizeof(*tcp_conn), GFP_KERNEL);
|
|
if (!tcp_conn)
|
|
goto tcp_conn_alloc_fail;
|
|
|
|
conn->dd_data = tcp_conn;
|
|
tcp_conn->iscsi_conn = conn;
|
|
|
|
tcp_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0,
|
|
CRYPTO_ALG_ASYNC);
|
|
tcp_conn->tx_hash.flags = 0;
|
|
if (IS_ERR(tcp_conn->tx_hash.tfm)) {
|
|
printk(KERN_ERR "Could not create connection due to crc32c "
|
|
"loading error %ld. Make sure the crc32c module is "
|
|
"built as a module or into the kernel\n",
|
|
PTR_ERR(tcp_conn->tx_hash.tfm));
|
|
goto free_tcp_conn;
|
|
}
|
|
|
|
tcp_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0,
|
|
CRYPTO_ALG_ASYNC);
|
|
tcp_conn->rx_hash.flags = 0;
|
|
if (IS_ERR(tcp_conn->rx_hash.tfm)) {
|
|
printk(KERN_ERR "Could not create connection due to crc32c "
|
|
"loading error %ld. Make sure the crc32c module is "
|
|
"built as a module or into the kernel\n",
|
|
PTR_ERR(tcp_conn->rx_hash.tfm));
|
|
goto free_tx_tfm;
|
|
}
|
|
|
|
return cls_conn;
|
|
|
|
free_tx_tfm:
|
|
crypto_free_hash(tcp_conn->tx_hash.tfm);
|
|
free_tcp_conn:
|
|
kfree(tcp_conn);
|
|
tcp_conn_alloc_fail:
|
|
iscsi_conn_teardown(cls_conn);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
iscsi_tcp_release_conn(struct iscsi_conn *conn)
|
|
{
|
|
struct iscsi_session *session = conn->session;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct socket *sock = tcp_conn->sock;
|
|
|
|
if (!sock)
|
|
return;
|
|
|
|
sock_hold(sock->sk);
|
|
iscsi_conn_restore_callbacks(tcp_conn);
|
|
sock_put(sock->sk);
|
|
|
|
spin_lock_bh(&session->lock);
|
|
tcp_conn->sock = NULL;
|
|
conn->recv_lock = NULL;
|
|
spin_unlock_bh(&session->lock);
|
|
sockfd_put(sock);
|
|
}
|
|
|
|
static void
|
|
iscsi_tcp_conn_destroy(struct iscsi_cls_conn *cls_conn)
|
|
{
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
|
|
iscsi_tcp_release_conn(conn);
|
|
iscsi_conn_teardown(cls_conn);
|
|
|
|
if (tcp_conn->tx_hash.tfm)
|
|
crypto_free_hash(tcp_conn->tx_hash.tfm);
|
|
if (tcp_conn->rx_hash.tfm)
|
|
crypto_free_hash(tcp_conn->rx_hash.tfm);
|
|
|
|
kfree(tcp_conn);
|
|
}
|
|
|
|
static void
|
|
iscsi_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
|
|
{
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
|
|
iscsi_conn_stop(cls_conn, flag);
|
|
iscsi_tcp_release_conn(conn);
|
|
}
|
|
|
|
static int iscsi_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock,
|
|
char *buf, int *port,
|
|
int (*getname)(struct socket *, struct sockaddr *,
|
|
int *addrlen))
|
|
{
|
|
struct sockaddr_storage *addr;
|
|
struct sockaddr_in6 *sin6;
|
|
struct sockaddr_in *sin;
|
|
int rc = 0, len;
|
|
|
|
addr = kmalloc(sizeof(*addr), GFP_KERNEL);
|
|
if (!addr)
|
|
return -ENOMEM;
|
|
|
|
if (getname(sock, (struct sockaddr *) addr, &len)) {
|
|
rc = -ENODEV;
|
|
goto free_addr;
|
|
}
|
|
|
|
switch (addr->ss_family) {
|
|
case AF_INET:
|
|
sin = (struct sockaddr_in *)addr;
|
|
spin_lock_bh(&conn->session->lock);
|
|
sprintf(buf, NIPQUAD_FMT, NIPQUAD(sin->sin_addr.s_addr));
|
|
*port = be16_to_cpu(sin->sin_port);
|
|
spin_unlock_bh(&conn->session->lock);
|
|
break;
|
|
case AF_INET6:
|
|
sin6 = (struct sockaddr_in6 *)addr;
|
|
spin_lock_bh(&conn->session->lock);
|
|
sprintf(buf, NIP6_FMT, NIP6(sin6->sin6_addr));
|
|
*port = be16_to_cpu(sin6->sin6_port);
|
|
spin_unlock_bh(&conn->session->lock);
|
|
break;
|
|
}
|
|
free_addr:
|
|
kfree(addr);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
iscsi_tcp_conn_bind(struct iscsi_cls_session *cls_session,
|
|
struct iscsi_cls_conn *cls_conn, uint64_t transport_eph,
|
|
int is_leading)
|
|
{
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct sock *sk;
|
|
struct socket *sock;
|
|
int err;
|
|
|
|
/* lookup for existing socket */
|
|
sock = sockfd_lookup((int)transport_eph, &err);
|
|
if (!sock) {
|
|
printk(KERN_ERR "iscsi_tcp: sockfd_lookup failed %d\n", err);
|
|
return -EEXIST;
|
|
}
|
|
/*
|
|
* copy these values now because if we drop the session
|
|
* userspace may still want to query the values since we will
|
|
* be using them for the reconnect
|
|
*/
|
|
err = iscsi_tcp_get_addr(conn, sock, conn->portal_address,
|
|
&conn->portal_port, kernel_getpeername);
|
|
if (err)
|
|
goto free_socket;
|
|
|
|
err = iscsi_tcp_get_addr(conn, sock, conn->local_address,
|
|
&conn->local_port, kernel_getsockname);
|
|
if (err)
|
|
goto free_socket;
|
|
|
|
err = iscsi_conn_bind(cls_session, cls_conn, is_leading);
|
|
if (err)
|
|
goto free_socket;
|
|
|
|
/* bind iSCSI connection and socket */
|
|
tcp_conn->sock = sock;
|
|
|
|
/* setup Socket parameters */
|
|
sk = sock->sk;
|
|
sk->sk_reuse = 1;
|
|
sk->sk_sndtimeo = 15 * HZ; /* FIXME: make it configurable */
|
|
sk->sk_allocation = GFP_ATOMIC;
|
|
|
|
/* FIXME: disable Nagle's algorithm */
|
|
|
|
/*
|
|
* Intercept TCP callbacks for sendfile like receive
|
|
* processing.
|
|
*/
|
|
conn->recv_lock = &sk->sk_callback_lock;
|
|
iscsi_conn_set_callbacks(conn);
|
|
tcp_conn->sendpage = tcp_conn->sock->ops->sendpage;
|
|
/*
|
|
* set receive state machine into initial state
|
|
*/
|
|
iscsi_tcp_hdr_recv_prep(tcp_conn);
|
|
return 0;
|
|
|
|
free_socket:
|
|
sockfd_put(sock);
|
|
return err;
|
|
}
|
|
|
|
/* called with host lock */
|
|
static void
|
|
iscsi_tcp_mgmt_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask)
|
|
{
|
|
struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
|
|
tcp_mtask->xmstate = XMSTATE_IMM_HDR_INIT;
|
|
}
|
|
|
|
static int
|
|
iscsi_r2tpool_alloc(struct iscsi_session *session)
|
|
{
|
|
int i;
|
|
int cmd_i;
|
|
|
|
/*
|
|
* initialize per-task: R2T pool and xmit queue
|
|
*/
|
|
for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
|
|
struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
|
|
/*
|
|
* pre-allocated x4 as much r2ts to handle race when
|
|
* target acks DataOut faster than we data_xmit() queues
|
|
* could replenish r2tqueue.
|
|
*/
|
|
|
|
/* R2T pool */
|
|
if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4, NULL,
|
|
sizeof(struct iscsi_r2t_info))) {
|
|
goto r2t_alloc_fail;
|
|
}
|
|
|
|
/* R2T xmit queue */
|
|
tcp_ctask->r2tqueue = kfifo_alloc(
|
|
session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL);
|
|
if (tcp_ctask->r2tqueue == ERR_PTR(-ENOMEM)) {
|
|
iscsi_pool_free(&tcp_ctask->r2tpool);
|
|
goto r2t_alloc_fail;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
r2t_alloc_fail:
|
|
for (i = 0; i < cmd_i; i++) {
|
|
struct iscsi_cmd_task *ctask = session->cmds[i];
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
|
|
kfifo_free(tcp_ctask->r2tqueue);
|
|
iscsi_pool_free(&tcp_ctask->r2tpool);
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void
|
|
iscsi_r2tpool_free(struct iscsi_session *session)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < session->cmds_max; i++) {
|
|
struct iscsi_cmd_task *ctask = session->cmds[i];
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
|
|
kfifo_free(tcp_ctask->r2tqueue);
|
|
iscsi_pool_free(&tcp_ctask->r2tpool);
|
|
}
|
|
}
|
|
|
|
static int
|
|
iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
|
|
char *buf, int buflen)
|
|
{
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
struct iscsi_session *session = conn->session;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
int value;
|
|
|
|
switch(param) {
|
|
case ISCSI_PARAM_HDRDGST_EN:
|
|
iscsi_set_param(cls_conn, param, buf, buflen);
|
|
break;
|
|
case ISCSI_PARAM_DATADGST_EN:
|
|
iscsi_set_param(cls_conn, param, buf, buflen);
|
|
tcp_conn->sendpage = conn->datadgst_en ?
|
|
sock_no_sendpage : tcp_conn->sock->ops->sendpage;
|
|
break;
|
|
case ISCSI_PARAM_MAX_R2T:
|
|
sscanf(buf, "%d", &value);
|
|
if (session->max_r2t == roundup_pow_of_two(value))
|
|
break;
|
|
iscsi_r2tpool_free(session);
|
|
iscsi_set_param(cls_conn, param, buf, buflen);
|
|
if (session->max_r2t & (session->max_r2t - 1))
|
|
session->max_r2t = roundup_pow_of_two(session->max_r2t);
|
|
if (iscsi_r2tpool_alloc(session))
|
|
return -ENOMEM;
|
|
break;
|
|
default:
|
|
return iscsi_set_param(cls_conn, param, buf, buflen);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
iscsi_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
|
|
enum iscsi_param param, char *buf)
|
|
{
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
int len;
|
|
|
|
switch(param) {
|
|
case ISCSI_PARAM_CONN_PORT:
|
|
spin_lock_bh(&conn->session->lock);
|
|
len = sprintf(buf, "%hu\n", conn->portal_port);
|
|
spin_unlock_bh(&conn->session->lock);
|
|
break;
|
|
case ISCSI_PARAM_CONN_ADDRESS:
|
|
spin_lock_bh(&conn->session->lock);
|
|
len = sprintf(buf, "%s\n", conn->portal_address);
|
|
spin_unlock_bh(&conn->session->lock);
|
|
break;
|
|
default:
|
|
return iscsi_conn_get_param(cls_conn, param, buf);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
iscsi_tcp_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param,
|
|
char *buf)
|
|
{
|
|
struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
|
|
int len;
|
|
|
|
switch (param) {
|
|
case ISCSI_HOST_PARAM_IPADDRESS:
|
|
spin_lock_bh(&session->lock);
|
|
if (!session->leadconn)
|
|
len = -ENODEV;
|
|
else
|
|
len = sprintf(buf, "%s\n",
|
|
session->leadconn->local_address);
|
|
spin_unlock_bh(&session->lock);
|
|
break;
|
|
default:
|
|
return iscsi_host_get_param(shost, param, buf);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static void
|
|
iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats)
|
|
{
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
|
|
stats->txdata_octets = conn->txdata_octets;
|
|
stats->rxdata_octets = conn->rxdata_octets;
|
|
stats->scsicmd_pdus = conn->scsicmd_pdus_cnt;
|
|
stats->dataout_pdus = conn->dataout_pdus_cnt;
|
|
stats->scsirsp_pdus = conn->scsirsp_pdus_cnt;
|
|
stats->datain_pdus = conn->datain_pdus_cnt;
|
|
stats->r2t_pdus = conn->r2t_pdus_cnt;
|
|
stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt;
|
|
stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt;
|
|
stats->custom_length = 3;
|
|
strcpy(stats->custom[0].desc, "tx_sendpage_failures");
|
|
stats->custom[0].value = tcp_conn->sendpage_failures_cnt;
|
|
strcpy(stats->custom[1].desc, "rx_discontiguous_hdr");
|
|
stats->custom[1].value = tcp_conn->discontiguous_hdr_cnt;
|
|
strcpy(stats->custom[2].desc, "eh_abort_cnt");
|
|
stats->custom[2].value = conn->eh_abort_cnt;
|
|
}
|
|
|
|
static struct iscsi_cls_session *
|
|
iscsi_tcp_session_create(struct iscsi_transport *iscsit,
|
|
struct scsi_transport_template *scsit,
|
|
uint16_t cmds_max, uint16_t qdepth,
|
|
uint32_t initial_cmdsn, uint32_t *hostno)
|
|
{
|
|
struct iscsi_cls_session *cls_session;
|
|
struct iscsi_session *session;
|
|
uint32_t hn;
|
|
int cmd_i;
|
|
|
|
cls_session = iscsi_session_setup(iscsit, scsit, cmds_max, qdepth,
|
|
sizeof(struct iscsi_tcp_cmd_task),
|
|
sizeof(struct iscsi_tcp_mgmt_task),
|
|
initial_cmdsn, &hn);
|
|
if (!cls_session)
|
|
return NULL;
|
|
*hostno = hn;
|
|
|
|
session = class_to_transport_session(cls_session);
|
|
for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
|
|
struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
|
|
struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
|
|
|
|
ctask->hdr = &tcp_ctask->hdr.cmd_hdr;
|
|
ctask->hdr_max = sizeof(tcp_ctask->hdr) - ISCSI_DIGEST_SIZE;
|
|
}
|
|
|
|
for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) {
|
|
struct iscsi_mgmt_task *mtask = session->mgmt_cmds[cmd_i];
|
|
struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
|
|
|
|
mtask->hdr = &tcp_mtask->hdr;
|
|
}
|
|
|
|
if (iscsi_r2tpool_alloc(class_to_transport_session(cls_session)))
|
|
goto r2tpool_alloc_fail;
|
|
|
|
return cls_session;
|
|
|
|
r2tpool_alloc_fail:
|
|
iscsi_session_teardown(cls_session);
|
|
return NULL;
|
|
}
|
|
|
|
static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
|
|
{
|
|
iscsi_r2tpool_free(class_to_transport_session(cls_session));
|
|
iscsi_session_teardown(cls_session);
|
|
}
|
|
|
|
static int iscsi_tcp_slave_configure(struct scsi_device *sdev)
|
|
{
|
|
blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_ANY);
|
|
blk_queue_dma_alignment(sdev->request_queue, 0);
|
|
return 0;
|
|
}
|
|
|
|
static struct scsi_host_template iscsi_sht = {
|
|
.module = THIS_MODULE,
|
|
.name = "iSCSI Initiator over TCP/IP",
|
|
.queuecommand = iscsi_queuecommand,
|
|
.change_queue_depth = iscsi_change_queue_depth,
|
|
.can_queue = ISCSI_DEF_XMIT_CMDS_MAX - 1,
|
|
.sg_tablesize = ISCSI_SG_TABLESIZE,
|
|
.max_sectors = 0xFFFF,
|
|
.cmd_per_lun = ISCSI_DEF_CMD_PER_LUN,
|
|
.eh_abort_handler = iscsi_eh_abort,
|
|
.eh_device_reset_handler= iscsi_eh_device_reset,
|
|
.eh_host_reset_handler = iscsi_eh_host_reset,
|
|
.use_clustering = DISABLE_CLUSTERING,
|
|
.slave_configure = iscsi_tcp_slave_configure,
|
|
.proc_name = "iscsi_tcp",
|
|
.this_id = -1,
|
|
};
|
|
|
|
static struct iscsi_transport iscsi_tcp_transport = {
|
|
.owner = THIS_MODULE,
|
|
.name = "tcp",
|
|
.caps = CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST
|
|
| CAP_DATADGST,
|
|
.param_mask = ISCSI_MAX_RECV_DLENGTH |
|
|
ISCSI_MAX_XMIT_DLENGTH |
|
|
ISCSI_HDRDGST_EN |
|
|
ISCSI_DATADGST_EN |
|
|
ISCSI_INITIAL_R2T_EN |
|
|
ISCSI_MAX_R2T |
|
|
ISCSI_IMM_DATA_EN |
|
|
ISCSI_FIRST_BURST |
|
|
ISCSI_MAX_BURST |
|
|
ISCSI_PDU_INORDER_EN |
|
|
ISCSI_DATASEQ_INORDER_EN |
|
|
ISCSI_ERL |
|
|
ISCSI_CONN_PORT |
|
|
ISCSI_CONN_ADDRESS |
|
|
ISCSI_EXP_STATSN |
|
|
ISCSI_PERSISTENT_PORT |
|
|
ISCSI_PERSISTENT_ADDRESS |
|
|
ISCSI_TARGET_NAME | ISCSI_TPGT |
|
|
ISCSI_USERNAME | ISCSI_PASSWORD |
|
|
ISCSI_USERNAME_IN | ISCSI_PASSWORD_IN |
|
|
ISCSI_FAST_ABORT | ISCSI_ABORT_TMO |
|
|
ISCSI_LU_RESET_TMO |
|
|
ISCSI_PING_TMO | ISCSI_RECV_TMO,
|
|
.host_param_mask = ISCSI_HOST_HWADDRESS | ISCSI_HOST_IPADDRESS |
|
|
ISCSI_HOST_INITIATOR_NAME |
|
|
ISCSI_HOST_NETDEV_NAME,
|
|
.host_template = &iscsi_sht,
|
|
.conndata_size = sizeof(struct iscsi_conn),
|
|
.max_conn = 1,
|
|
.max_cmd_len = ISCSI_TCP_MAX_CMD_LEN,
|
|
/* session management */
|
|
.create_session = iscsi_tcp_session_create,
|
|
.destroy_session = iscsi_tcp_session_destroy,
|
|
/* connection management */
|
|
.create_conn = iscsi_tcp_conn_create,
|
|
.bind_conn = iscsi_tcp_conn_bind,
|
|
.destroy_conn = iscsi_tcp_conn_destroy,
|
|
.set_param = iscsi_conn_set_param,
|
|
.get_conn_param = iscsi_tcp_conn_get_param,
|
|
.get_session_param = iscsi_session_get_param,
|
|
.start_conn = iscsi_conn_start,
|
|
.stop_conn = iscsi_tcp_conn_stop,
|
|
/* iscsi host params */
|
|
.get_host_param = iscsi_tcp_host_get_param,
|
|
.set_host_param = iscsi_host_set_param,
|
|
/* IO */
|
|
.send_pdu = iscsi_conn_send_pdu,
|
|
.get_stats = iscsi_conn_get_stats,
|
|
.init_cmd_task = iscsi_tcp_cmd_init,
|
|
.init_mgmt_task = iscsi_tcp_mgmt_init,
|
|
.xmit_cmd_task = iscsi_tcp_ctask_xmit,
|
|
.xmit_mgmt_task = iscsi_tcp_mtask_xmit,
|
|
.cleanup_cmd_task = iscsi_tcp_cleanup_ctask,
|
|
/* recovery */
|
|
.session_recovery_timedout = iscsi_session_recovery_timedout,
|
|
};
|
|
|
|
static int __init
|
|
iscsi_tcp_init(void)
|
|
{
|
|
if (iscsi_max_lun < 1) {
|
|
printk(KERN_ERR "iscsi_tcp: Invalid max_lun value of %u\n",
|
|
iscsi_max_lun);
|
|
return -EINVAL;
|
|
}
|
|
iscsi_tcp_transport.max_lun = iscsi_max_lun;
|
|
|
|
if (!iscsi_register_transport(&iscsi_tcp_transport))
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit
|
|
iscsi_tcp_exit(void)
|
|
{
|
|
iscsi_unregister_transport(&iscsi_tcp_transport);
|
|
}
|
|
|
|
module_init(iscsi_tcp_init);
|
|
module_exit(iscsi_tcp_exit);
|