2005-08-05 10:30:08 +08:00
|
|
|
/*
|
|
|
|
* iSCSI Initiator over TCP/IP Data-Path
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004 Dmitry Yusupov
|
|
|
|
* Copyright (C) 2004 Alex Aizman
|
2006-04-07 10:26:46 +08:00
|
|
|
* Copyright (C) 2005 - 2006 Mike Christie
|
|
|
|
* Copyright (C) 2006 Red Hat, Inc. All rights reserved.
|
2005-08-05 10:30:08 +08:00
|
|
|
* 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/inet.h>
|
2007-05-31 01:57:23 +08:00
|
|
|
#include <linux/file.h>
|
2005-08-05 10:30:08 +08:00
|
|
|
#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>
|
2007-05-31 01:57:21 +08:00
|
|
|
#include <scsi/scsi_device.h>
|
2005-08-05 10:30:08 +08:00
|
|
|
#include <scsi/scsi_host.h>
|
|
|
|
#include <scsi/scsi.h>
|
|
|
|
#include <scsi/scsi_transport_iscsi.h>
|
|
|
|
|
|
|
|
#include "iscsi_tcp.h"
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, "
|
|
|
|
"Dmitry Yusupov <dmitry_yus@yahoo.com>, "
|
2005-08-05 10:30:08 +08:00
|
|
|
"Alex Aizman <itn780@yahoo.com>");
|
|
|
|
MODULE_DESCRIPTION("iSCSI/TCP data-path");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static struct scsi_transport_template *iscsi_sw_tcp_scsi_transport;
|
|
|
|
static struct scsi_host_template iscsi_sw_tcp_sht;
|
|
|
|
static struct iscsi_transport iscsi_sw_tcp_transport;
|
2008-05-22 04:53:59 +08:00
|
|
|
|
2005-08-05 10:30:08 +08:00
|
|
|
static unsigned int iscsi_max_lun = 512;
|
|
|
|
module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);
|
|
|
|
|
2009-03-06 04:46:00 +08:00
|
|
|
static int iscsi_sw_tcp_dbg;
|
|
|
|
module_param_named(debug_iscsi_tcp, iscsi_sw_tcp_dbg, int,
|
|
|
|
S_IRUGO | S_IWUSR);
|
|
|
|
MODULE_PARM_DESC(debug_iscsi_tcp, "Turn on debugging for iscsi_tcp module "
|
|
|
|
"Set to 1 to turn on, and zero to turn off. Default is off.");
|
|
|
|
|
|
|
|
#define ISCSI_SW_TCP_DBG(_conn, dbg_fmt, arg...) \
|
|
|
|
do { \
|
|
|
|
if (iscsi_sw_tcp_dbg) \
|
|
|
|
iscsi_conn_printk(KERN_INFO, _conn, \
|
|
|
|
"%s " dbg_fmt, \
|
|
|
|
__func__, ##arg); \
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
|
2007-12-14 02:43:21 +08:00
|
|
|
/**
|
2008-12-02 14:32:12 +08:00
|
|
|
* iscsi_sw_tcp_recv - TCP receive in sendfile fashion
|
2008-12-02 14:32:04 +08:00
|
|
|
* @rd_desc: read descriptor
|
|
|
|
* @skb: socket buffer
|
|
|
|
* @offset: offset in skb
|
|
|
|
* @len: skb->len - offset
|
2008-12-02 14:32:12 +08:00
|
|
|
*/
|
|
|
|
static int iscsi_sw_tcp_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
|
|
|
|
unsigned int offset, size_t len)
|
2008-12-02 14:32:04 +08:00
|
|
|
{
|
|
|
|
struct iscsi_conn *conn = rd_desc->arg.data;
|
|
|
|
unsigned int consumed, total_consumed = 0;
|
|
|
|
int status;
|
|
|
|
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(conn, "in %d bytes\n", skb->len - offset);
|
2008-12-02 14:32:04 +08:00
|
|
|
|
|
|
|
do {
|
|
|
|
status = 0;
|
|
|
|
consumed = iscsi_tcp_recv_skb(conn, skb, offset, 0, &status);
|
|
|
|
offset += consumed;
|
|
|
|
total_consumed += consumed;
|
|
|
|
} while (consumed != 0 && status != ISCSI_TCP_SKB_DONE);
|
|
|
|
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(conn, "read %d bytes status %d\n",
|
|
|
|
skb->len - offset, status);
|
2008-12-02 14:32:04 +08:00
|
|
|
return total_consumed;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2009-08-21 04:10:57 +08:00
|
|
|
/**
|
|
|
|
* iscsi_sw_sk_state_check - check socket state
|
|
|
|
* @sk: socket
|
|
|
|
*
|
|
|
|
* If the socket is in CLOSE or CLOSE_WAIT we should
|
|
|
|
* not close the connection if there is still some
|
|
|
|
* data pending.
|
|
|
|
*/
|
|
|
|
static inline int iscsi_sw_sk_state_check(struct sock *sk)
|
|
|
|
{
|
2009-08-21 04:11:02 +08:00
|
|
|
struct iscsi_conn *conn = (struct iscsi_conn*)sk->sk_user_data;
|
2009-08-21 04:10:57 +08:00
|
|
|
|
2009-08-21 04:11:02 +08:00
|
|
|
if ((sk->sk_state == TCP_CLOSE_WAIT || sk->sk_state == TCP_CLOSE) &&
|
|
|
|
!atomic_read(&sk->sk_rmem_alloc)) {
|
|
|
|
ISCSI_SW_TCP_DBG(conn, "TCP_CLOSE|TCP_CLOSE_WAIT\n");
|
|
|
|
iscsi_conn_failure(conn, ISCSI_ERR_TCP_CONN_CLOSE);
|
|
|
|
return -ECONNRESET;
|
|
|
|
}
|
2009-08-21 04:10:57 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_data_ready(struct sock *sk, int flag)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
|
|
|
struct iscsi_conn *conn = sk->sk_user_data;
|
2007-12-14 02:43:21 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2005-08-05 10:30:08 +08:00
|
|
|
read_descriptor_t rd_desc;
|
|
|
|
|
|
|
|
read_lock(&sk->sk_callback_lock);
|
|
|
|
|
2006-05-03 08:46:49 +08:00
|
|
|
/*
|
2007-12-14 02:43:21 +08:00
|
|
|
* Use rd_desc to pass 'conn' to iscsi_tcp_recv.
|
2006-05-03 08:46:49 +08:00
|
|
|
* We set count to 1 because we want the network layer to
|
2007-12-14 02:43:21 +08:00
|
|
|
* hand us all the skbs that are available. iscsi_tcp_recv
|
2006-05-03 08:46:49 +08:00
|
|
|
* handled pdus that cross buffers or pdus that still need data.
|
|
|
|
*/
|
2005-08-05 10:30:08 +08:00
|
|
|
rd_desc.arg.data = conn;
|
2006-05-03 08:46:49 +08:00
|
|
|
rd_desc.count = 1;
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_read_sock(sk, &rd_desc, iscsi_sw_tcp_recv);
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2009-08-21 04:11:02 +08:00
|
|
|
iscsi_sw_sk_state_check(sk);
|
2009-08-21 04:10:57 +08:00
|
|
|
|
2005-08-05 10:30:08 +08:00
|
|
|
read_unlock(&sk->sk_callback_lock);
|
2007-12-14 02:43:21 +08:00
|
|
|
|
|
|
|
/* If we had to (atomically) map a highmem page,
|
|
|
|
* unmap it now. */
|
2007-12-14 02:43:35 +08:00
|
|
|
iscsi_tcp_segment_unmap(&tcp_conn->in.segment);
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_state_change(struct sock *sk)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn;
|
2005-08-05 10:30:08 +08:00
|
|
|
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;
|
|
|
|
|
2009-08-21 04:11:02 +08:00
|
|
|
iscsi_sw_sk_state_check(sk);
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2006-04-07 10:26:46 +08:00
|
|
|
tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
old_state_change = tcp_sw_conn->old_state_change;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
|
|
|
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
|
|
|
|
**/
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_write_space(struct sock *sk)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
|
|
|
struct iscsi_conn *conn = (struct iscsi_conn*)sk->sk_user_data;
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2006-04-07 10:26:46 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn->old_write_space(sk);
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(conn, "iscsi_write_space\n");
|
2009-03-06 04:46:03 +08:00
|
|
|
iscsi_conn_queue_work(conn);
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_conn_set_callbacks(struct iscsi_conn *conn)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
struct sock *sk = tcp_sw_conn->sock->sk;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
|
|
|
/* assign new callbacks */
|
|
|
|
write_lock_bh(&sk->sk_callback_lock);
|
|
|
|
sk->sk_user_data = conn;
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn->old_data_ready = sk->sk_data_ready;
|
|
|
|
tcp_sw_conn->old_state_change = sk->sk_state_change;
|
|
|
|
tcp_sw_conn->old_write_space = sk->sk_write_space;
|
|
|
|
sk->sk_data_ready = iscsi_sw_tcp_data_ready;
|
|
|
|
sk->sk_state_change = iscsi_sw_tcp_state_change;
|
|
|
|
sk->sk_write_space = iscsi_sw_tcp_write_space;
|
2005-08-05 10:30:08 +08:00
|
|
|
write_unlock_bh(&sk->sk_callback_lock);
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void
|
|
|
|
iscsi_sw_tcp_conn_restore_callbacks(struct iscsi_sw_tcp_conn *tcp_sw_conn)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2008-12-02 14:32:12 +08:00
|
|
|
struct sock *sk = tcp_sw_conn->sock->sk;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
|
|
|
/* restore socket callbacks, see also: iscsi_conn_set_callbacks() */
|
|
|
|
write_lock_bh(&sk->sk_callback_lock);
|
|
|
|
sk->sk_user_data = NULL;
|
2008-12-02 14:32:12 +08:00
|
|
|
sk->sk_data_ready = tcp_sw_conn->old_data_ready;
|
|
|
|
sk->sk_state_change = tcp_sw_conn->old_state_change;
|
|
|
|
sk->sk_write_space = tcp_sw_conn->old_write_space;
|
2005-08-05 10:30:08 +08:00
|
|
|
sk->sk_no_check = 0;
|
|
|
|
write_unlock_bh(&sk->sk_callback_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-12-02 14:32:12 +08:00
|
|
|
* iscsi_sw_tcp_xmit_segment - transmit segment
|
2008-12-02 14:32:16 +08:00
|
|
|
* @tcp_conn: the iSCSI TCP connection
|
2008-12-02 14:32:12 +08:00
|
|
|
* @segment: the buffer to transmnit
|
|
|
|
*
|
|
|
|
* This function transmits as much of the buffer as
|
|
|
|
* the network layer will accept, and returns the number of
|
|
|
|
* bytes transmitted.
|
|
|
|
*
|
|
|
|
* If CRC hashing is enabled, the function will compute the
|
|
|
|
* hash as it goes. When the entire segment has been transmitted,
|
|
|
|
* it will retrieve the hash value and send it as well.
|
|
|
|
*/
|
2008-12-02 14:32:16 +08:00
|
|
|
static int iscsi_sw_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn,
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_segment *segment)
|
|
|
|
{
|
2008-12-02 14:32:16 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct socket *sk = tcp_sw_conn->sock;
|
|
|
|
unsigned int copied = 0;
|
|
|
|
int r = 0;
|
|
|
|
|
2008-12-02 14:32:16 +08:00
|
|
|
while (!iscsi_tcp_segment_done(tcp_conn, segment, 0, r)) {
|
2008-12-02 14:32:12 +08:00
|
|
|
struct scatterlist *sg;
|
|
|
|
unsigned int offset, copy;
|
|
|
|
int flags = 0;
|
|
|
|
|
|
|
|
r = 0;
|
|
|
|
offset = segment->copied;
|
|
|
|
copy = segment->size - offset;
|
|
|
|
|
|
|
|
if (segment->total_copied + segment->size < segment->total_size)
|
|
|
|
flags |= MSG_MORE;
|
|
|
|
|
|
|
|
/* Use sendpage if we can; else fall back to sendmsg */
|
|
|
|
if (!segment->data) {
|
|
|
|
sg = segment->sg;
|
|
|
|
offset += segment->sg_offset + sg->offset;
|
|
|
|
r = tcp_sw_conn->sendpage(sk, sg_page(sg), offset,
|
|
|
|
copy, flags);
|
|
|
|
} else {
|
|
|
|
struct msghdr msg = { .msg_flags = flags };
|
|
|
|
struct kvec iov = {
|
|
|
|
.iov_base = segment->data + offset,
|
|
|
|
.iov_len = copy
|
|
|
|
};
|
|
|
|
|
|
|
|
r = kernel_sendmsg(sk, &msg, &iov, 1, copy);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r < 0) {
|
|
|
|
iscsi_tcp_segment_unmap(segment);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
copied += r;
|
|
|
|
}
|
|
|
|
return copied;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* iscsi_sw_tcp_xmit - TCP transmit
|
2007-12-14 02:43:35 +08:00
|
|
|
**/
|
2008-12-02 14:32:12 +08:00
|
|
|
static int iscsi_sw_tcp_xmit(struct iscsi_conn *conn)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
struct iscsi_segment *segment = &tcp_sw_conn->out.segment;
|
2007-12-14 02:43:35 +08:00
|
|
|
unsigned int consumed = 0;
|
|
|
|
int rc = 0;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2007-12-14 02:43:35 +08:00
|
|
|
while (1) {
|
2008-12-02 14:32:16 +08:00
|
|
|
rc = iscsi_sw_tcp_xmit_segment(tcp_conn, segment);
|
2009-06-16 11:11:09 +08:00
|
|
|
/*
|
|
|
|
* We may not have been able to send data because the conn
|
|
|
|
* is getting stopped. libiscsi will know so propogate err
|
|
|
|
* for it to do the right thing.
|
|
|
|
*/
|
|
|
|
if (rc == -EAGAIN)
|
|
|
|
return rc;
|
|
|
|
else if (rc < 0) {
|
2008-09-25 00:46:13 +08:00
|
|
|
rc = ISCSI_ERR_XMIT_FAILED;
|
2007-12-14 02:43:35 +08:00
|
|
|
goto error;
|
2009-06-16 11:11:09 +08:00
|
|
|
} else if (rc == 0)
|
2007-12-14 02:43:35 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
consumed += rc;
|
|
|
|
|
|
|
|
if (segment->total_copied >= segment->total_size) {
|
|
|
|
if (segment->done != NULL) {
|
|
|
|
rc = segment->done(tcp_conn, segment);
|
2008-09-25 00:46:13 +08:00
|
|
|
if (rc != 0)
|
2007-12-14 02:43:35 +08:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
2006-05-30 13:37:28 +08:00
|
|
|
}
|
|
|
|
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(conn, "xmit %d bytes\n", consumed);
|
2007-12-14 02:43:35 +08:00
|
|
|
|
|
|
|
conn->txdata_octets += consumed;
|
|
|
|
return consumed;
|
|
|
|
|
|
|
|
error:
|
|
|
|
/* Transmit error. We could initiate error recovery
|
|
|
|
* here. */
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(conn, "Error sending PDU, errno=%d\n", rc);
|
2008-09-25 00:46:13 +08:00
|
|
|
iscsi_conn_failure(conn, rc);
|
|
|
|
return -EIO;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2007-12-14 02:43:35 +08:00
|
|
|
* iscsi_tcp_xmit_qlen - return the number of bytes queued for xmit
|
|
|
|
*/
|
2008-12-02 14:32:12 +08:00
|
|
|
static inline int iscsi_sw_tcp_xmit_qlen(struct iscsi_conn *conn)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2007-12-14 02:43:35 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
struct iscsi_segment *segment = &tcp_sw_conn->out.segment;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2007-12-14 02:43:35 +08:00
|
|
|
return segment->total_copied - segment->total_size;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static int iscsi_sw_tcp_pdu_xmit(struct iscsi_task *task)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2008-12-02 14:32:07 +08:00
|
|
|
struct iscsi_conn *conn = task->conn;
|
2007-12-14 02:43:35 +08:00
|
|
|
int rc;
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
while (iscsi_sw_tcp_xmit_qlen(conn)) {
|
|
|
|
rc = iscsi_sw_tcp_xmit(conn);
|
2007-12-14 02:43:35 +08:00
|
|
|
if (rc == 0)
|
2005-08-05 10:30:08 +08:00
|
|
|
return -EAGAIN;
|
2007-12-14 02:43:35 +08:00
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
2006-05-30 13:37:28 +08:00
|
|
|
}
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2007-12-14 02:43:35 +08:00
|
|
|
return 0;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2007-12-14 02:43:35 +08:00
|
|
|
/*
|
|
|
|
* This is called when we're done sending the header.
|
|
|
|
* Simply copy the data_segment to the send segment, and return.
|
|
|
|
*/
|
2008-12-02 14:32:12 +08:00
|
|
|
static int iscsi_sw_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn,
|
|
|
|
struct iscsi_segment *segment)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
|
|
|
|
tcp_sw_conn->out.segment = tcp_sw_conn->out.data_segment;
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(tcp_conn->iscsi_conn,
|
|
|
|
"Header done. Next segment size %u total_size %u\n",
|
|
|
|
tcp_sw_conn->out.segment.size,
|
|
|
|
tcp_sw_conn->out.segment.total_size);
|
2007-12-14 02:43:35 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr,
|
|
|
|
size_t hdrlen)
|
2007-12-14 02:43:35 +08:00
|
|
|
{
|
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2007-12-14 02:43:35 +08:00
|
|
|
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(conn, "%s\n", conn->hdrdgst_en ?
|
|
|
|
"digest enabled" : "digest disabled");
|
2007-12-14 02:43:35 +08:00
|
|
|
|
|
|
|
/* Clear the data segment - needs to be filled in by the
|
|
|
|
* caller using iscsi_tcp_send_data_prep() */
|
2008-12-02 14:32:12 +08:00
|
|
|
memset(&tcp_sw_conn->out.data_segment, 0,
|
|
|
|
sizeof(struct iscsi_segment));
|
2007-12-14 02:43:35 +08:00
|
|
|
|
|
|
|
/* If header digest is enabled, compute the CRC and
|
|
|
|
* place the digest into the same buffer. We make
|
2008-05-22 04:54:10 +08:00
|
|
|
* sure that both iscsi_tcp_task and mtask have
|
2007-12-14 02:43:35 +08:00
|
|
|
* sufficient room.
|
|
|
|
*/
|
|
|
|
if (conn->hdrdgst_en) {
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_tcp_dgst_header(&tcp_sw_conn->tx_hash, hdr, hdrlen,
|
2007-12-14 02:43:35 +08:00
|
|
|
hdr + hdrlen);
|
|
|
|
hdrlen += ISCSI_DIGEST_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remember header pointer for later, when we need
|
|
|
|
* to decide whether there's a payload to go along
|
|
|
|
* with the header. */
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn->out.hdr = hdr;
|
2007-12-14 02:43:35 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_segment_init_linear(&tcp_sw_conn->out.segment, hdr, hdrlen,
|
|
|
|
iscsi_sw_tcp_send_hdr_done, NULL);
|
2007-12-14 02:43:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prepare the send buffer for the payload data.
|
|
|
|
* Padding and checksumming will all be taken care
|
|
|
|
* of by the iscsi_segment routines.
|
|
|
|
*/
|
|
|
|
static int
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg,
|
|
|
|
unsigned int count, unsigned int offset,
|
|
|
|
unsigned int len)
|
2007-12-14 02:43:35 +08:00
|
|
|
{
|
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2007-12-14 02:43:35 +08:00
|
|
|
struct hash_desc *tx_hash = NULL;
|
|
|
|
unsigned int hdr_spec_len;
|
|
|
|
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(conn, "offset=%d, datalen=%d %s\n", offset, len,
|
|
|
|
conn->datadgst_en ?
|
|
|
|
"digest enabled" : "digest disabled");
|
2007-12-14 02:43:35 +08:00
|
|
|
|
|
|
|
/* Make sure the datalen matches what the caller
|
|
|
|
said he would send. */
|
2008-12-02 14:32:12 +08:00
|
|
|
hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength);
|
2007-12-14 02:43:35 +08:00
|
|
|
WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
|
|
|
|
|
|
|
|
if (conn->datadgst_en)
|
2008-12-02 14:32:12 +08:00
|
|
|
tx_hash = &tcp_sw_conn->tx_hash;
|
2007-12-14 02:43:35 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
return iscsi_segment_seek_sg(&tcp_sw_conn->out.data_segment,
|
|
|
|
sg, count, offset, len,
|
|
|
|
NULL, tx_hash);
|
2007-12-14 02:43:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_send_linear_data_prep(struct iscsi_conn *conn, void *data,
|
2007-12-14 02:43:35 +08:00
|
|
|
size_t len)
|
|
|
|
{
|
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2007-12-14 02:43:35 +08:00
|
|
|
struct hash_desc *tx_hash = NULL;
|
|
|
|
unsigned int hdr_spec_len;
|
|
|
|
|
2009-03-06 04:46:00 +08:00
|
|
|
ISCSI_SW_TCP_DBG(conn, "datalen=%zd %s\n", len, conn->datadgst_en ?
|
|
|
|
"digest enabled" : "digest disabled");
|
2007-12-14 02:43:35 +08:00
|
|
|
|
|
|
|
/* Make sure the datalen matches what the caller
|
|
|
|
said he would send. */
|
2008-12-02 14:32:12 +08:00
|
|
|
hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength);
|
2007-12-14 02:43:35 +08:00
|
|
|
WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
|
|
|
|
|
|
|
|
if (conn->datadgst_en)
|
2008-12-02 14:32:12 +08:00
|
|
|
tx_hash = &tcp_sw_conn->tx_hash;
|
2007-12-14 02:43:35 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_segment_init_linear(&tcp_sw_conn->out.data_segment,
|
2007-12-14 02:43:35 +08:00
|
|
|
data, len, NULL, tx_hash);
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static int iscsi_sw_tcp_pdu_init(struct iscsi_task *task,
|
|
|
|
unsigned int offset, unsigned int count)
|
2008-12-02 14:32:07 +08:00
|
|
|
{
|
|
|
|
struct iscsi_conn *conn = task->conn;
|
|
|
|
int err = 0;
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_send_hdr_prep(conn, task->hdr, task->hdr_len);
|
2008-12-02 14:32:07 +08:00
|
|
|
|
|
|
|
if (!count)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!task->sc)
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_send_linear_data_prep(conn, task->data, count);
|
2008-12-02 14:32:07 +08:00
|
|
|
else {
|
|
|
|
struct scsi_data_buffer *sdb = scsi_out(task->sc);
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
err = iscsi_sw_tcp_send_data_prep(conn, sdb->table.sgl,
|
|
|
|
sdb->table.nents, offset,
|
|
|
|
count);
|
2008-12-02 14:32:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
2009-04-22 04:32:31 +08:00
|
|
|
/* got invalid offset/len */
|
2008-12-02 14:32:07 +08:00
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:14 +08:00
|
|
|
static int iscsi_sw_tcp_pdu_alloc(struct iscsi_task *task, uint8_t opcode)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2008-05-22 04:54:10 +08:00
|
|
|
struct iscsi_tcp_task *tcp_task = task->dd_data;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
task->hdr = task->dd_data + sizeof(*tcp_task);
|
|
|
|
task->hdr_max = sizeof(struct iscsi_sw_tcp_hdrbuf) - ISCSI_DIGEST_SIZE;
|
2007-12-14 02:43:35 +08:00
|
|
|
return 0;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2006-04-07 10:26:46 +08:00
|
|
|
static struct iscsi_cls_conn *
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_conn_create(struct iscsi_cls_session *cls_session,
|
|
|
|
uint32_t conn_idx)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_conn *conn;
|
|
|
|
struct iscsi_cls_conn *cls_conn;
|
|
|
|
struct iscsi_tcp_conn *tcp_conn;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
cls_conn = iscsi_tcp_conn_setup(cls_session, sizeof(*tcp_sw_conn),
|
|
|
|
conn_idx);
|
2006-04-07 10:26:46 +08:00
|
|
|
if (!cls_conn)
|
|
|
|
return NULL;
|
|
|
|
conn = cls_conn->dd_data;
|
2008-05-22 04:54:01 +08:00
|
|
|
tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn = tcp_conn->dd_data;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn->tx_hash.tfm = crypto_alloc_hash("crc32c", 0,
|
|
|
|
CRYPTO_ALG_ASYNC);
|
|
|
|
tcp_sw_conn->tx_hash.flags = 0;
|
|
|
|
if (IS_ERR(tcp_sw_conn->tx_hash.tfm))
|
2008-05-22 04:54:01 +08:00
|
|
|
goto free_conn;
|
2006-09-01 06:09:28 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn->rx_hash.tfm = crypto_alloc_hash("crc32c", 0,
|
|
|
|
CRYPTO_ALG_ASYNC);
|
|
|
|
tcp_sw_conn->rx_hash.flags = 0;
|
|
|
|
if (IS_ERR(tcp_sw_conn->rx_hash.tfm))
|
2006-09-01 06:09:28 +08:00
|
|
|
goto free_tx_tfm;
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_conn->rx_hash = &tcp_sw_conn->rx_hash;
|
2006-09-01 06:09:28 +08:00
|
|
|
|
2006-04-07 10:26:46 +08:00
|
|
|
return cls_conn;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2006-09-01 06:09:28 +08:00
|
|
|
free_tx_tfm:
|
2008-12-02 14:32:12 +08:00
|
|
|
crypto_free_hash(tcp_sw_conn->tx_hash.tfm);
|
2008-05-22 04:54:01 +08:00
|
|
|
free_conn:
|
2008-02-01 03:36:52 +08:00
|
|
|
iscsi_conn_printk(KERN_ERR, conn,
|
|
|
|
"Could not create connection due to crc32c "
|
|
|
|
"loading error. Make sure the crc32c "
|
|
|
|
"module is built as a module or into the "
|
|
|
|
"kernel\n");
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_tcp_conn_teardown(cls_conn);
|
2006-04-07 10:26:46 +08:00
|
|
|
return NULL;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_release_conn(struct iscsi_conn *conn)
|
2006-07-25 04:47:26 +08:00
|
|
|
{
|
2007-05-31 01:57:24 +08:00
|
|
|
struct iscsi_session *session = conn->session;
|
2006-07-25 04:47:26 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
struct socket *sock = tcp_sw_conn->sock;
|
2006-07-25 04:47:26 +08:00
|
|
|
|
2007-05-31 01:57:24 +08:00
|
|
|
if (!sock)
|
2006-07-25 04:47:26 +08:00
|
|
|
return;
|
|
|
|
|
2007-05-31 01:57:24 +08:00
|
|
|
sock_hold(sock->sk);
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_conn_restore_callbacks(tcp_sw_conn);
|
2007-05-31 01:57:24 +08:00
|
|
|
sock_put(sock->sk);
|
2006-07-25 04:47:26 +08:00
|
|
|
|
2007-05-31 01:57:24 +08:00
|
|
|
spin_lock_bh(&session->lock);
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn->sock = NULL;
|
2007-05-31 01:57:24 +08:00
|
|
|
spin_unlock_bh(&session->lock);
|
|
|
|
sockfd_put(sock);
|
2006-07-25 04:47:26 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_conn_destroy(struct iscsi_cls_conn *cls_conn)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_release_conn(conn);
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
if (tcp_sw_conn->tx_hash.tfm)
|
|
|
|
crypto_free_hash(tcp_sw_conn->tx_hash.tfm);
|
|
|
|
if (tcp_sw_conn->rx_hash.tfm)
|
|
|
|
crypto_free_hash(tcp_sw_conn->rx_hash.tfm);
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_tcp_conn_teardown(cls_conn);
|
2006-04-07 10:26:46 +08:00
|
|
|
}
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
|
2006-07-25 04:47:26 +08:00
|
|
|
{
|
|
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
2008-05-22 04:54:18 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2008-05-22 04:54:18 +08:00
|
|
|
|
|
|
|
/* userspace may have goofed up and not bound us */
|
2008-12-02 14:32:12 +08:00
|
|
|
if (!tcp_sw_conn->sock)
|
2008-05-22 04:54:18 +08:00
|
|
|
return;
|
|
|
|
/*
|
|
|
|
* Make sure our recv side is stopped.
|
|
|
|
* Older tools called conn stop before ep_disconnect
|
|
|
|
* so IO could still be coming in.
|
|
|
|
*/
|
2008-12-02 14:32:12 +08:00
|
|
|
write_lock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock);
|
2008-05-22 04:54:18 +08:00
|
|
|
set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
|
2008-12-02 14:32:12 +08:00
|
|
|
write_unlock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock);
|
2006-07-25 04:47:26 +08:00
|
|
|
|
|
|
|
iscsi_conn_stop(cls_conn, flag);
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_release_conn(conn);
|
2006-07-25 04:47:26 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static int iscsi_sw_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock,
|
|
|
|
char *buf, int *port,
|
|
|
|
int (*getname)(struct socket *,
|
|
|
|
struct sockaddr *,
|
|
|
|
int *addrlen))
|
2007-05-31 01:57:24 +08:00
|
|
|
{
|
|
|
|
struct sockaddr_storage *addr;
|
|
|
|
struct sockaddr_in6 *sin6;
|
|
|
|
struct sockaddr_in *sin;
|
|
|
|
int rc = 0, len;
|
|
|
|
|
2007-07-20 23:03:40 +08:00
|
|
|
addr = kmalloc(sizeof(*addr), GFP_KERNEL);
|
2007-05-31 01:57:24 +08:00
|
|
|
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);
|
2008-10-31 15:56:00 +08:00
|
|
|
sprintf(buf, "%pI4", &sin->sin_addr.s_addr);
|
2007-05-31 01:57:24 +08:00
|
|
|
*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);
|
2008-10-30 03:52:50 +08:00
|
|
|
sprintf(buf, "%pI6", &sin6->sin6_addr);
|
2007-05-31 01:57:24 +08:00
|
|
|
*port = be16_to_cpu(sin6->sin6_port);
|
|
|
|
spin_unlock_bh(&conn->session->lock);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
free_addr:
|
|
|
|
kfree(addr);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2006-04-07 10:26:46 +08:00
|
|
|
static int
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session,
|
|
|
|
struct iscsi_cls_conn *cls_conn, uint64_t transport_eph,
|
|
|
|
int is_leading)
|
2006-04-07 10:26:46 +08:00
|
|
|
{
|
2008-05-22 04:53:59 +08:00
|
|
|
struct Scsi_Host *shost = iscsi_session_to_shost(cls_session);
|
|
|
|
struct iscsi_host *ihost = shost_priv(shost);
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2006-04-07 10:26:46 +08:00
|
|
|
struct sock *sk;
|
|
|
|
struct socket *sock;
|
|
|
|
int err;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2006-04-07 10:26:46 +08:00
|
|
|
/* lookup for existing socket */
|
2006-05-03 08:46:36 +08:00
|
|
|
sock = sockfd_lookup((int)transport_eph, &err);
|
2006-04-07 10:26:46 +08:00
|
|
|
if (!sock) {
|
2008-02-01 03:36:52 +08:00
|
|
|
iscsi_conn_printk(KERN_ERR, conn,
|
|
|
|
"sockfd_lookup failed %d\n", err);
|
2006-04-07 10:26:46 +08:00
|
|
|
return -EEXIST;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
2007-05-31 01:57:24 +08:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
2008-12-02 14:32:12 +08:00
|
|
|
err = iscsi_sw_tcp_get_addr(conn, sock, conn->portal_address,
|
|
|
|
&conn->portal_port, kernel_getpeername);
|
2007-05-31 01:57:24 +08:00
|
|
|
if (err)
|
|
|
|
goto free_socket;
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
err = iscsi_sw_tcp_get_addr(conn, sock, ihost->local_address,
|
|
|
|
&ihost->local_port, kernel_getsockname);
|
2007-05-31 01:57:24 +08:00
|
|
|
if (err)
|
|
|
|
goto free_socket;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2006-04-07 10:26:46 +08:00
|
|
|
err = iscsi_conn_bind(cls_session, cls_conn, is_leading);
|
|
|
|
if (err)
|
2007-05-31 01:57:24 +08:00
|
|
|
goto free_socket;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2006-05-30 13:37:20 +08:00
|
|
|
/* bind iSCSI connection and socket */
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn->sock = sock;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2006-05-30 13:37:20 +08:00
|
|
|
/* setup Socket parameters */
|
|
|
|
sk = sock->sk;
|
|
|
|
sk->sk_reuse = 1;
|
|
|
|
sk->sk_sndtimeo = 15 * HZ; /* FIXME: make it configurable */
|
|
|
|
sk->sk_allocation = GFP_ATOMIC;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_conn_set_callbacks(conn);
|
|
|
|
tcp_sw_conn->sendpage = tcp_sw_conn->sock->ops->sendpage;
|
2006-05-30 13:37:20 +08:00
|
|
|
/*
|
|
|
|
* set receive state machine into initial state
|
|
|
|
*/
|
2007-12-14 02:43:21 +08:00
|
|
|
iscsi_tcp_hdr_recv_prep(tcp_conn);
|
2005-08-05 10:30:08 +08:00
|
|
|
return 0;
|
2007-05-31 01:57:24 +08:00
|
|
|
|
|
|
|
free_socket:
|
|
|
|
sockfd_put(sock);
|
|
|
|
return err;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static int iscsi_sw_tcp_conn_set_param(struct iscsi_cls_conn *cls_conn,
|
|
|
|
enum iscsi_param param, char *buf,
|
|
|
|
int buflen)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2006-02-02 11:06:49 +08:00
|
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
2005-08-05 10:30:08 +08:00
|
|
|
struct iscsi_session *session = conn->session;
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2006-06-29 01:00:26 +08:00
|
|
|
int value;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
|
|
|
switch(param) {
|
|
|
|
case ISCSI_PARAM_HDRDGST_EN:
|
2006-06-29 01:00:26 +08:00
|
|
|
iscsi_set_param(cls_conn, param, buf, buflen);
|
2005-08-05 10:30:08 +08:00
|
|
|
break;
|
|
|
|
case ISCSI_PARAM_DATADGST_EN:
|
2006-06-29 01:00:26 +08:00
|
|
|
iscsi_set_param(cls_conn, param, buf, buflen);
|
2008-12-02 14:32:12 +08:00
|
|
|
tcp_sw_conn->sendpage = conn->datadgst_en ?
|
|
|
|
sock_no_sendpage : tcp_sw_conn->sock->ops->sendpage;
|
2005-08-05 10:30:08 +08:00
|
|
|
break;
|
|
|
|
case ISCSI_PARAM_MAX_R2T:
|
2006-06-29 01:00:26 +08:00
|
|
|
sscanf(buf, "%d", &value);
|
2007-12-14 02:43:42 +08:00
|
|
|
if (value <= 0 || !is_power_of_2(value))
|
|
|
|
return -EINVAL;
|
|
|
|
if (session->max_r2t == value)
|
2005-08-05 10:30:08 +08:00
|
|
|
break;
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_tcp_r2tpool_free(session);
|
2006-06-29 01:00:26 +08:00
|
|
|
iscsi_set_param(cls_conn, param, buf, buflen);
|
2008-12-02 14:32:12 +08:00
|
|
|
if (iscsi_tcp_r2tpool_alloc(session))
|
2005-08-05 10:30:08 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
break;
|
|
|
|
default:
|
2006-06-29 01:00:26 +08:00
|
|
|
return iscsi_set_param(cls_conn, param, buf, buflen);
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static int iscsi_sw_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
|
|
|
|
enum iscsi_param param, char *buf)
|
2006-01-14 08:05:50 +08:00
|
|
|
{
|
2006-02-02 11:06:49 +08:00
|
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
2006-06-29 01:00:26 +08:00
|
|
|
int len;
|
2006-01-14 08:05:50 +08:00
|
|
|
|
|
|
|
switch(param) {
|
2006-04-07 10:13:36 +08:00
|
|
|
case ISCSI_PARAM_CONN_PORT:
|
2007-05-31 01:57:24 +08:00
|
|
|
spin_lock_bh(&conn->session->lock);
|
|
|
|
len = sprintf(buf, "%hu\n", conn->portal_port);
|
|
|
|
spin_unlock_bh(&conn->session->lock);
|
2006-05-03 08:46:47 +08:00
|
|
|
break;
|
2006-04-07 10:13:36 +08:00
|
|
|
case ISCSI_PARAM_CONN_ADDRESS:
|
2007-05-31 01:57:24 +08:00
|
|
|
spin_lock_bh(&conn->session->lock);
|
|
|
|
len = sprintf(buf, "%s\n", conn->portal_address);
|
|
|
|
spin_unlock_bh(&conn->session->lock);
|
2006-04-07 10:13:36 +08:00
|
|
|
break;
|
|
|
|
default:
|
2006-06-29 01:00:26 +08:00
|
|
|
return iscsi_conn_get_param(cls_conn, param, buf);
|
2006-04-07 10:13:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-05 10:30:08 +08:00
|
|
|
static void
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_conn_get_stats(struct iscsi_cls_conn *cls_conn,
|
|
|
|
struct iscsi_stats *stats)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2006-02-02 11:06:49 +08:00
|
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
2008-12-02 14:32:12 +08:00
|
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
|
|
|
stats->custom_length = 3;
|
|
|
|
strcpy(stats->custom[0].desc, "tx_sendpage_failures");
|
2008-12-02 14:32:12 +08:00
|
|
|
stats->custom[0].value = tcp_sw_conn->sendpage_failures_cnt;
|
2005-08-05 10:30:08 +08:00
|
|
|
strcpy(stats->custom[1].desc, "rx_discontiguous_hdr");
|
2008-12-02 14:32:12 +08:00
|
|
|
stats->custom[1].value = tcp_sw_conn->discontiguous_hdr_cnt;
|
2005-08-05 10:30:08 +08:00
|
|
|
strcpy(stats->custom[2].desc, "eh_abort_cnt");
|
|
|
|
stats->custom[2].value = conn->eh_abort_cnt;
|
2008-12-02 14:32:12 +08:00
|
|
|
|
|
|
|
iscsi_tcp_conn_get_stats(cls_conn, stats);
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2006-04-07 10:26:46 +08:00
|
|
|
static struct iscsi_cls_session *
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max,
|
2009-03-06 04:46:06 +08:00
|
|
|
uint16_t qdepth, uint32_t initial_cmdsn)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2006-04-07 10:26:46 +08:00
|
|
|
struct iscsi_cls_session *cls_session;
|
|
|
|
struct iscsi_session *session;
|
2008-05-22 04:54:15 +08:00
|
|
|
struct Scsi_Host *shost;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-05-22 04:54:15 +08:00
|
|
|
if (ep) {
|
|
|
|
printk(KERN_ERR "iscsi_tcp: invalid ep %p.\n", ep);
|
2008-05-22 04:53:59 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2009-03-06 04:46:04 +08:00
|
|
|
shost = iscsi_host_alloc(&iscsi_sw_tcp_sht, 0, 1);
|
2008-05-22 04:53:59 +08:00
|
|
|
if (!shost)
|
2006-04-07 10:26:46 +08:00
|
|
|
return NULL;
|
2008-12-02 14:32:12 +08:00
|
|
|
shost->transportt = iscsi_sw_tcp_scsi_transport;
|
2009-03-06 04:46:04 +08:00
|
|
|
shost->cmd_per_lun = qdepth;
|
2008-05-22 04:53:59 +08:00
|
|
|
shost->max_lun = iscsi_max_lun;
|
|
|
|
shost->max_id = 0;
|
|
|
|
shost->max_channel = 0;
|
2008-05-26 16:31:19 +08:00
|
|
|
shost->max_cmd_len = SCSI_MAX_VARLEN_CDB_SIZE;
|
2008-05-22 04:53:59 +08:00
|
|
|
|
2008-05-22 04:54:00 +08:00
|
|
|
if (iscsi_host_add(shost, NULL))
|
2008-05-22 04:53:59 +08:00
|
|
|
goto free_host;
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
cls_session = iscsi_session_setup(&iscsi_sw_tcp_transport, shost,
|
2009-09-22 10:51:22 +08:00
|
|
|
cmds_max, 0,
|
2008-12-02 14:32:12 +08:00
|
|
|
sizeof(struct iscsi_tcp_task) +
|
|
|
|
sizeof(struct iscsi_sw_tcp_hdrbuf),
|
2008-05-22 04:54:12 +08:00
|
|
|
initial_cmdsn, 0);
|
2008-05-22 04:53:59 +08:00
|
|
|
if (!cls_session)
|
|
|
|
goto remove_host;
|
|
|
|
session = cls_session->dd_data;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2008-05-22 04:54:07 +08:00
|
|
|
shost->can_queue = session->scsi_cmds_max;
|
2008-12-02 14:32:12 +08:00
|
|
|
if (iscsi_tcp_r2tpool_alloc(session))
|
2008-05-22 04:53:59 +08:00
|
|
|
goto remove_session;
|
2006-04-07 10:26:46 +08:00
|
|
|
return cls_session;
|
|
|
|
|
2008-05-22 04:53:59 +08:00
|
|
|
remove_session:
|
2006-04-07 10:26:46 +08:00
|
|
|
iscsi_session_teardown(cls_session);
|
2008-05-22 04:53:59 +08:00
|
|
|
remove_host:
|
2008-05-22 04:54:00 +08:00
|
|
|
iscsi_host_remove(shost);
|
2008-05-22 04:53:59 +08:00
|
|
|
free_host:
|
2008-05-22 04:54:00 +08:00
|
|
|
iscsi_host_free(shost);
|
2006-04-07 10:26:46 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void iscsi_sw_tcp_session_destroy(struct iscsi_cls_session *cls_session)
|
2006-04-07 10:26:46 +08:00
|
|
|
{
|
2008-05-22 04:53:59 +08:00
|
|
|
struct Scsi_Host *shost = iscsi_session_to_shost(cls_session);
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_tcp_r2tpool_free(cls_session->dd_data);
|
2008-09-25 00:46:10 +08:00
|
|
|
iscsi_session_teardown(cls_session);
|
2008-05-22 04:53:59 +08:00
|
|
|
|
2008-05-22 04:54:00 +08:00
|
|
|
iscsi_host_remove(shost);
|
|
|
|
iscsi_host_free(shost);
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2009-03-06 04:45:56 +08:00
|
|
|
static int iscsi_sw_tcp_slave_alloc(struct scsi_device *sdev)
|
|
|
|
{
|
|
|
|
set_bit(QUEUE_FLAG_BIDI, &sdev->request_queue->queue_flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static int iscsi_sw_tcp_slave_configure(struct scsi_device *sdev)
|
2007-05-31 01:57:21 +08:00
|
|
|
{
|
2007-07-27 01:46:47 +08:00
|
|
|
blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_ANY);
|
2007-05-31 01:57:21 +08:00
|
|
|
blk_queue_dma_alignment(sdev->request_queue, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static struct scsi_host_template iscsi_sw_tcp_sht = {
|
2007-07-27 01:46:46 +08:00
|
|
|
.module = THIS_MODULE,
|
2006-07-25 04:47:54 +08:00
|
|
|
.name = "iSCSI Initiator over TCP/IP",
|
2006-04-07 10:26:46 +08:00
|
|
|
.queuecommand = iscsi_queuecommand,
|
|
|
|
.change_queue_depth = iscsi_change_queue_depth,
|
2007-05-31 01:57:19 +08:00
|
|
|
.can_queue = ISCSI_DEF_XMIT_CMDS_MAX - 1,
|
2007-12-14 02:43:39 +08:00
|
|
|
.sg_tablesize = 4096,
|
2007-03-01 07:32:20 +08:00
|
|
|
.max_sectors = 0xFFFF,
|
2006-04-07 10:26:46 +08:00
|
|
|
.cmd_per_lun = ISCSI_DEF_CMD_PER_LUN,
|
|
|
|
.eh_abort_handler = iscsi_eh_abort,
|
2007-12-14 02:43:20 +08:00
|
|
|
.eh_device_reset_handler= iscsi_eh_device_reset,
|
2008-09-25 00:46:12 +08:00
|
|
|
.eh_target_reset_handler= iscsi_eh_target_reset,
|
2006-04-07 10:26:46 +08:00
|
|
|
.use_clustering = DISABLE_CLUSTERING,
|
2009-03-06 04:45:56 +08:00
|
|
|
.slave_alloc = iscsi_sw_tcp_slave_alloc,
|
2008-12-02 14:32:12 +08:00
|
|
|
.slave_configure = iscsi_sw_tcp_slave_configure,
|
2009-04-22 04:32:32 +08:00
|
|
|
.target_alloc = iscsi_target_alloc,
|
2006-04-07 10:26:46 +08:00
|
|
|
.proc_name = "iscsi_tcp",
|
|
|
|
.this_id = -1,
|
|
|
|
};
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static struct iscsi_transport iscsi_sw_tcp_transport = {
|
2005-08-05 10:30:08 +08:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.name = "tcp",
|
|
|
|
.caps = CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST
|
|
|
|
| CAP_DATADGST,
|
2006-04-07 10:13:36 +08:00
|
|
|
.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 |
|
2006-05-03 08:46:47 +08:00
|
|
|
ISCSI_CONN_ADDRESS |
|
2006-06-29 01:00:26 +08:00
|
|
|
ISCSI_EXP_STATSN |
|
|
|
|
ISCSI_PERSISTENT_PORT |
|
|
|
|
ISCSI_PERSISTENT_ADDRESS |
|
2007-05-31 01:57:16 +08:00
|
|
|
ISCSI_TARGET_NAME | ISCSI_TPGT |
|
|
|
|
ISCSI_USERNAME | ISCSI_PASSWORD |
|
2007-12-14 02:43:20 +08:00
|
|
|
ISCSI_USERNAME_IN | ISCSI_PASSWORD_IN |
|
2007-12-14 02:43:30 +08:00
|
|
|
ISCSI_FAST_ABORT | ISCSI_ABORT_TMO |
|
|
|
|
ISCSI_LU_RESET_TMO |
|
2008-05-22 04:54:16 +08:00
|
|
|
ISCSI_PING_TMO | ISCSI_RECV_TMO |
|
|
|
|
ISCSI_IFACE_NAME | ISCSI_INITIATOR_NAME,
|
2007-05-31 01:57:24 +08:00
|
|
|
.host_param_mask = ISCSI_HOST_HWADDRESS | ISCSI_HOST_IPADDRESS |
|
[SCSI] iscsi class, iscsi_tcp, iser, qla4xxx: add netdevname sysfs attr
iSCSI must support software iscsi (iscsi_tcp, iser), hardware iscsi (qla4xxx),
and partial offload (broadcom). To be able to allow each stack or driver
or port (virtual or physical) to be able to log into the same target portal
we use the initiator tuple [[HWADDRESS | NETDEVNAME], INITIATOR_NAME] and
the target tuple [TARGETNAME, CONN_ADDRESS, CONN_PORT] to id a session.
This patch adds the netdev name, which is used by software iscsi when
it binds a session to a netdevice using the SO_BINDTODEVICE sock opt.
It cannot use HWADDRESS because if someone did vlans then the same netdevice
will have the same mac and the initiator,target id will not be unique.
Signed-off-by: Mike Christie <michaelc@cs.wisc.edu>
Cc: Roland Dreier <rdreier@cisco.com>
Cc: David C Somayajulu <david.somayajulu@qlogic.com>
Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
2007-05-31 01:57:25 +08:00
|
|
|
ISCSI_HOST_INITIATOR_NAME |
|
|
|
|
ISCSI_HOST_NETDEV_NAME,
|
2006-04-07 10:26:46 +08:00
|
|
|
/* session management */
|
2008-12-02 14:32:12 +08:00
|
|
|
.create_session = iscsi_sw_tcp_session_create,
|
|
|
|
.destroy_session = iscsi_sw_tcp_session_destroy,
|
2006-04-07 10:26:46 +08:00
|
|
|
/* connection management */
|
2008-12-02 14:32:12 +08:00
|
|
|
.create_conn = iscsi_sw_tcp_conn_create,
|
|
|
|
.bind_conn = iscsi_sw_tcp_conn_bind,
|
|
|
|
.destroy_conn = iscsi_sw_tcp_conn_destroy,
|
|
|
|
.set_param = iscsi_sw_tcp_conn_set_param,
|
|
|
|
.get_conn_param = iscsi_sw_tcp_conn_get_param,
|
2006-01-14 08:05:50 +08:00
|
|
|
.get_session_param = iscsi_session_get_param,
|
2005-08-05 10:30:08 +08:00
|
|
|
.start_conn = iscsi_conn_start,
|
2008-12-02 14:32:12 +08:00
|
|
|
.stop_conn = iscsi_sw_tcp_conn_stop,
|
2007-05-31 01:57:12 +08:00
|
|
|
/* iscsi host params */
|
2008-05-22 04:53:59 +08:00
|
|
|
.get_host_param = iscsi_host_get_param,
|
2007-05-31 01:57:12 +08:00
|
|
|
.set_host_param = iscsi_host_set_param,
|
2006-04-07 10:26:46 +08:00
|
|
|
/* IO */
|
2005-08-05 10:30:08 +08:00
|
|
|
.send_pdu = iscsi_conn_send_pdu,
|
2008-12-02 14:32:12 +08:00
|
|
|
.get_stats = iscsi_sw_tcp_conn_get_stats,
|
2008-12-02 14:32:07 +08:00
|
|
|
/* iscsi task/cmd helpers */
|
2008-05-22 04:54:07 +08:00
|
|
|
.init_task = iscsi_tcp_task_init,
|
|
|
|
.xmit_task = iscsi_tcp_task_xmit,
|
|
|
|
.cleanup_task = iscsi_tcp_cleanup_task,
|
2008-12-02 14:32:07 +08:00
|
|
|
/* low level pdu helpers */
|
2008-12-02 14:32:12 +08:00
|
|
|
.xmit_pdu = iscsi_sw_tcp_pdu_xmit,
|
|
|
|
.init_pdu = iscsi_sw_tcp_pdu_init,
|
|
|
|
.alloc_pdu = iscsi_sw_tcp_pdu_alloc,
|
2006-04-07 10:26:46 +08:00
|
|
|
/* recovery */
|
2006-04-07 10:13:39 +08:00
|
|
|
.session_recovery_timedout = iscsi_session_recovery_timedout,
|
2005-08-05 10:30:08 +08:00
|
|
|
};
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static int __init iscsi_sw_tcp_init(void)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
|
|
|
if (iscsi_max_lun < 1) {
|
2006-05-03 08:46:43 +08:00
|
|
|
printk(KERN_ERR "iscsi_tcp: Invalid max_lun value of %u\n",
|
|
|
|
iscsi_max_lun);
|
2005-08-05 10:30:08 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_sw_tcp_scsi_transport = iscsi_register_transport(
|
|
|
|
&iscsi_sw_tcp_transport);
|
|
|
|
if (!iscsi_sw_tcp_scsi_transport)
|
2006-05-19 09:31:36 +08:00
|
|
|
return -ENODEV;
|
2005-08-05 10:30:08 +08:00
|
|
|
|
2006-01-14 08:05:50 +08:00
|
|
|
return 0;
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
static void __exit iscsi_sw_tcp_exit(void)
|
2005-08-05 10:30:08 +08:00
|
|
|
{
|
2008-12-02 14:32:12 +08:00
|
|
|
iscsi_unregister_transport(&iscsi_sw_tcp_transport);
|
2005-08-05 10:30:08 +08:00
|
|
|
}
|
|
|
|
|
2008-12-02 14:32:12 +08:00
|
|
|
module_init(iscsi_sw_tcp_init);
|
|
|
|
module_exit(iscsi_sw_tcp_exit);
|