mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 12:42:02 +00:00
5a0e3ad6af
percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
972 lines
27 KiB
C
972 lines
27 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/inet.h>
|
|
#include <linux/slab.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("Mike Christie <michaelc@cs.wisc.edu>, "
|
|
"Dmitry Yusupov <dmitry_yus@yahoo.com>, "
|
|
"Alex Aizman <itn780@yahoo.com>");
|
|
MODULE_DESCRIPTION("iSCSI/TCP data-path");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
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;
|
|
|
|
static unsigned int iscsi_max_lun = 512;
|
|
module_param_named(max_lun, iscsi_max_lun, uint, S_IRUGO);
|
|
|
|
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);
|
|
|
|
|
|
/**
|
|
* iscsi_sw_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_sw_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;
|
|
unsigned int consumed, total_consumed = 0;
|
|
int status;
|
|
|
|
ISCSI_SW_TCP_DBG(conn, "in %d bytes\n", skb->len - offset);
|
|
|
|
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);
|
|
|
|
ISCSI_SW_TCP_DBG(conn, "read %d bytes status %d\n",
|
|
skb->len - offset, status);
|
|
return total_consumed;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct iscsi_conn *conn = (struct iscsi_conn*)sk->sk_user_data;
|
|
|
|
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;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void iscsi_sw_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_sw_tcp_recv);
|
|
|
|
iscsi_sw_sk_state_check(sk);
|
|
|
|
read_unlock(&sk->sk_callback_lock);
|
|
|
|
/* If we had to (atomically) map a highmem page,
|
|
* unmap it now. */
|
|
iscsi_tcp_segment_unmap(&tcp_conn->in.segment);
|
|
}
|
|
|
|
static void iscsi_sw_tcp_state_change(struct sock *sk)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_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;
|
|
|
|
iscsi_sw_sk_state_check(sk);
|
|
|
|
tcp_conn = conn->dd_data;
|
|
tcp_sw_conn = tcp_conn->dd_data;
|
|
old_state_change = tcp_sw_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_sw_tcp_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;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
tcp_sw_conn->old_write_space(sk);
|
|
ISCSI_SW_TCP_DBG(conn, "iscsi_write_space\n");
|
|
iscsi_conn_queue_work(conn);
|
|
}
|
|
|
|
static void iscsi_sw_tcp_conn_set_callbacks(struct iscsi_conn *conn)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct sock *sk = tcp_sw_conn->sock->sk;
|
|
|
|
/* assign new callbacks */
|
|
write_lock_bh(&sk->sk_callback_lock);
|
|
sk->sk_user_data = conn;
|
|
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;
|
|
write_unlock_bh(&sk->sk_callback_lock);
|
|
}
|
|
|
|
static void
|
|
iscsi_sw_tcp_conn_restore_callbacks(struct iscsi_sw_tcp_conn *tcp_sw_conn)
|
|
{
|
|
struct sock *sk = tcp_sw_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_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;
|
|
sk->sk_no_check = 0;
|
|
write_unlock_bh(&sk->sk_callback_lock);
|
|
}
|
|
|
|
/**
|
|
* iscsi_sw_tcp_xmit_segment - transmit segment
|
|
* @tcp_conn: the iSCSI TCP connection
|
|
* @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.
|
|
*/
|
|
static int iscsi_sw_tcp_xmit_segment(struct iscsi_tcp_conn *tcp_conn,
|
|
struct iscsi_segment *segment)
|
|
{
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct socket *sk = tcp_sw_conn->sock;
|
|
unsigned int copied = 0;
|
|
int r = 0;
|
|
|
|
while (!iscsi_tcp_segment_done(tcp_conn, segment, 0, r)) {
|
|
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
|
|
**/
|
|
static int iscsi_sw_tcp_xmit(struct iscsi_conn *conn)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct iscsi_segment *segment = &tcp_sw_conn->out.segment;
|
|
unsigned int consumed = 0;
|
|
int rc = 0;
|
|
|
|
while (1) {
|
|
rc = iscsi_sw_tcp_xmit_segment(tcp_conn, segment);
|
|
/*
|
|
* 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) {
|
|
rc = ISCSI_ERR_XMIT_FAILED;
|
|
goto error;
|
|
} else if (rc == 0)
|
|
break;
|
|
|
|
consumed += rc;
|
|
|
|
if (segment->total_copied >= segment->total_size) {
|
|
if (segment->done != NULL) {
|
|
rc = segment->done(tcp_conn, segment);
|
|
if (rc != 0)
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
ISCSI_SW_TCP_DBG(conn, "xmit %d bytes\n", consumed);
|
|
|
|
conn->txdata_octets += consumed;
|
|
return consumed;
|
|
|
|
error:
|
|
/* Transmit error. We could initiate error recovery
|
|
* here. */
|
|
ISCSI_SW_TCP_DBG(conn, "Error sending PDU, errno=%d\n", rc);
|
|
iscsi_conn_failure(conn, rc);
|
|
return -EIO;
|
|
}
|
|
|
|
/**
|
|
* iscsi_tcp_xmit_qlen - return the number of bytes queued for xmit
|
|
*/
|
|
static inline int iscsi_sw_tcp_xmit_qlen(struct iscsi_conn *conn)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct iscsi_segment *segment = &tcp_sw_conn->out.segment;
|
|
|
|
return segment->total_copied - segment->total_size;
|
|
}
|
|
|
|
static int iscsi_sw_tcp_pdu_xmit(struct iscsi_task *task)
|
|
{
|
|
struct iscsi_conn *conn = task->conn;
|
|
int rc;
|
|
|
|
while (iscsi_sw_tcp_xmit_qlen(conn)) {
|
|
rc = iscsi_sw_tcp_xmit(conn);
|
|
if (rc == 0)
|
|
return -EAGAIN;
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is called when we're done sending the header.
|
|
* Simply copy the data_segment to the send segment, and return.
|
|
*/
|
|
static int iscsi_sw_tcp_send_hdr_done(struct iscsi_tcp_conn *tcp_conn,
|
|
struct iscsi_segment *segment)
|
|
{
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
tcp_sw_conn->out.segment = tcp_sw_conn->out.data_segment;
|
|
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);
|
|
return 0;
|
|
}
|
|
|
|
static void iscsi_sw_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr,
|
|
size_t hdrlen)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
ISCSI_SW_TCP_DBG(conn, "%s\n", conn->hdrdgst_en ?
|
|
"digest enabled" : "digest disabled");
|
|
|
|
/* Clear the data segment - needs to be filled in by the
|
|
* caller using iscsi_tcp_send_data_prep() */
|
|
memset(&tcp_sw_conn->out.data_segment, 0,
|
|
sizeof(struct iscsi_segment));
|
|
|
|
/* If header digest is enabled, compute the CRC and
|
|
* place the digest into the same buffer. We make
|
|
* sure that both iscsi_tcp_task and mtask have
|
|
* sufficient room.
|
|
*/
|
|
if (conn->hdrdgst_en) {
|
|
iscsi_tcp_dgst_header(&tcp_sw_conn->tx_hash, hdr, hdrlen,
|
|
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. */
|
|
tcp_sw_conn->out.hdr = hdr;
|
|
|
|
iscsi_segment_init_linear(&tcp_sw_conn->out.segment, hdr, hdrlen,
|
|
iscsi_sw_tcp_send_hdr_done, NULL);
|
|
}
|
|
|
|
/*
|
|
* Prepare the send buffer for the payload data.
|
|
* Padding and checksumming will all be taken care
|
|
* of by the iscsi_segment routines.
|
|
*/
|
|
static int
|
|
iscsi_sw_tcp_send_data_prep(struct iscsi_conn *conn, struct scatterlist *sg,
|
|
unsigned int count, unsigned int offset,
|
|
unsigned int len)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct hash_desc *tx_hash = NULL;
|
|
unsigned int hdr_spec_len;
|
|
|
|
ISCSI_SW_TCP_DBG(conn, "offset=%d, datalen=%d %s\n", offset, len,
|
|
conn->datadgst_en ?
|
|
"digest enabled" : "digest disabled");
|
|
|
|
/* Make sure the datalen matches what the caller
|
|
said he would send. */
|
|
hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength);
|
|
WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
|
|
|
|
if (conn->datadgst_en)
|
|
tx_hash = &tcp_sw_conn->tx_hash;
|
|
|
|
return iscsi_segment_seek_sg(&tcp_sw_conn->out.data_segment,
|
|
sg, count, offset, len,
|
|
NULL, tx_hash);
|
|
}
|
|
|
|
static void
|
|
iscsi_sw_tcp_send_linear_data_prep(struct iscsi_conn *conn, void *data,
|
|
size_t len)
|
|
{
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct hash_desc *tx_hash = NULL;
|
|
unsigned int hdr_spec_len;
|
|
|
|
ISCSI_SW_TCP_DBG(conn, "datalen=%zd %s\n", len, conn->datadgst_en ?
|
|
"digest enabled" : "digest disabled");
|
|
|
|
/* Make sure the datalen matches what the caller
|
|
said he would send. */
|
|
hdr_spec_len = ntoh24(tcp_sw_conn->out.hdr->dlength);
|
|
WARN_ON(iscsi_padded(len) != iscsi_padded(hdr_spec_len));
|
|
|
|
if (conn->datadgst_en)
|
|
tx_hash = &tcp_sw_conn->tx_hash;
|
|
|
|
iscsi_segment_init_linear(&tcp_sw_conn->out.data_segment,
|
|
data, len, NULL, tx_hash);
|
|
}
|
|
|
|
static int iscsi_sw_tcp_pdu_init(struct iscsi_task *task,
|
|
unsigned int offset, unsigned int count)
|
|
{
|
|
struct iscsi_conn *conn = task->conn;
|
|
int err = 0;
|
|
|
|
iscsi_sw_tcp_send_hdr_prep(conn, task->hdr, task->hdr_len);
|
|
|
|
if (!count)
|
|
return 0;
|
|
|
|
if (!task->sc)
|
|
iscsi_sw_tcp_send_linear_data_prep(conn, task->data, count);
|
|
else {
|
|
struct scsi_data_buffer *sdb = scsi_out(task->sc);
|
|
|
|
err = iscsi_sw_tcp_send_data_prep(conn, sdb->table.sgl,
|
|
sdb->table.nents, offset,
|
|
count);
|
|
}
|
|
|
|
if (err) {
|
|
/* got invalid offset/len */
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int iscsi_sw_tcp_pdu_alloc(struct iscsi_task *task, uint8_t opcode)
|
|
{
|
|
struct iscsi_tcp_task *tcp_task = task->dd_data;
|
|
|
|
task->hdr = task->dd_data + sizeof(*tcp_task);
|
|
task->hdr_max = sizeof(struct iscsi_sw_tcp_hdrbuf) - ISCSI_DIGEST_SIZE;
|
|
return 0;
|
|
}
|
|
|
|
static struct iscsi_cls_conn *
|
|
iscsi_sw_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;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn;
|
|
|
|
cls_conn = iscsi_tcp_conn_setup(cls_session, sizeof(*tcp_sw_conn),
|
|
conn_idx);
|
|
if (!cls_conn)
|
|
return NULL;
|
|
conn = cls_conn->dd_data;
|
|
tcp_conn = conn->dd_data;
|
|
tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
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))
|
|
goto free_conn;
|
|
|
|
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))
|
|
goto free_tx_tfm;
|
|
tcp_conn->rx_hash = &tcp_sw_conn->rx_hash;
|
|
|
|
return cls_conn;
|
|
|
|
free_tx_tfm:
|
|
crypto_free_hash(tcp_sw_conn->tx_hash.tfm);
|
|
free_conn:
|
|
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");
|
|
iscsi_tcp_conn_teardown(cls_conn);
|
|
return NULL;
|
|
}
|
|
|
|
static void iscsi_sw_tcp_release_conn(struct iscsi_conn *conn)
|
|
{
|
|
struct iscsi_session *session = conn->session;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct socket *sock = tcp_sw_conn->sock;
|
|
|
|
if (!sock)
|
|
return;
|
|
|
|
sock_hold(sock->sk);
|
|
iscsi_sw_tcp_conn_restore_callbacks(tcp_sw_conn);
|
|
sock_put(sock->sk);
|
|
|
|
spin_lock_bh(&session->lock);
|
|
tcp_sw_conn->sock = NULL;
|
|
spin_unlock_bh(&session->lock);
|
|
sockfd_put(sock);
|
|
}
|
|
|
|
static void iscsi_sw_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;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
iscsi_sw_tcp_release_conn(conn);
|
|
|
|
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);
|
|
|
|
iscsi_tcp_conn_teardown(cls_conn);
|
|
}
|
|
|
|
static void iscsi_sw_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
|
|
{
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct socket *sock = tcp_sw_conn->sock;
|
|
|
|
/* userspace may have goofed up and not bound us */
|
|
if (!sock)
|
|
return;
|
|
/*
|
|
* Make sure our recv side is stopped.
|
|
* Older tools called conn stop before ep_disconnect
|
|
* so IO could still be coming in.
|
|
*/
|
|
write_lock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock);
|
|
set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
|
|
write_unlock_bh(&tcp_sw_conn->sock->sk->sk_callback_lock);
|
|
|
|
if (sock->sk->sk_sleep && waitqueue_active(sock->sk->sk_sleep)) {
|
|
sock->sk->sk_err = EIO;
|
|
wake_up_interruptible(sock->sk->sk_sleep);
|
|
}
|
|
|
|
iscsi_conn_stop(cls_conn, flag);
|
|
iscsi_sw_tcp_release_conn(conn);
|
|
}
|
|
|
|
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))
|
|
{
|
|
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, "%pI4", &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, "%pI6", &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_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session,
|
|
struct iscsi_cls_conn *cls_conn, uint64_t transport_eph,
|
|
int is_leading)
|
|
{
|
|
struct Scsi_Host *shost = iscsi_session_to_shost(cls_session);
|
|
struct iscsi_host *ihost = shost_priv(shost);
|
|
struct iscsi_conn *conn = cls_conn->dd_data;
|
|
struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
struct sock *sk;
|
|
struct socket *sock;
|
|
int err;
|
|
|
|
/* lookup for existing socket */
|
|
sock = sockfd_lookup((int)transport_eph, &err);
|
|
if (!sock) {
|
|
iscsi_conn_printk(KERN_ERR, conn,
|
|
"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_sw_tcp_get_addr(conn, sock, conn->portal_address,
|
|
&conn->portal_port, kernel_getpeername);
|
|
if (err)
|
|
goto free_socket;
|
|
|
|
err = iscsi_sw_tcp_get_addr(conn, sock, ihost->local_address,
|
|
&ihost->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_sw_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;
|
|
|
|
iscsi_sw_tcp_conn_set_callbacks(conn);
|
|
tcp_sw_conn->sendpage = tcp_sw_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;
|
|
}
|
|
|
|
static int iscsi_sw_tcp_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;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_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_sw_conn->sendpage = conn->datadgst_en ?
|
|
sock_no_sendpage : tcp_sw_conn->sock->ops->sendpage;
|
|
break;
|
|
case ISCSI_PARAM_MAX_R2T:
|
|
sscanf(buf, "%d", &value);
|
|
if (value <= 0 || !is_power_of_2(value))
|
|
return -EINVAL;
|
|
if (session->max_r2t == value)
|
|
break;
|
|
iscsi_tcp_r2tpool_free(session);
|
|
iscsi_set_param(cls_conn, param, buf, buflen);
|
|
if (iscsi_tcp_r2tpool_alloc(session))
|
|
return -ENOMEM;
|
|
break;
|
|
default:
|
|
return iscsi_set_param(cls_conn, param, buf, buflen);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int iscsi_sw_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 void
|
|
iscsi_sw_tcp_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;
|
|
struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data;
|
|
|
|
stats->custom_length = 3;
|
|
strcpy(stats->custom[0].desc, "tx_sendpage_failures");
|
|
stats->custom[0].value = tcp_sw_conn->sendpage_failures_cnt;
|
|
strcpy(stats->custom[1].desc, "rx_discontiguous_hdr");
|
|
stats->custom[1].value = tcp_sw_conn->discontiguous_hdr_cnt;
|
|
strcpy(stats->custom[2].desc, "eh_abort_cnt");
|
|
stats->custom[2].value = conn->eh_abort_cnt;
|
|
|
|
iscsi_tcp_conn_get_stats(cls_conn, stats);
|
|
}
|
|
|
|
static struct iscsi_cls_session *
|
|
iscsi_sw_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max,
|
|
uint16_t qdepth, uint32_t initial_cmdsn)
|
|
{
|
|
struct iscsi_cls_session *cls_session;
|
|
struct iscsi_session *session;
|
|
struct Scsi_Host *shost;
|
|
|
|
if (ep) {
|
|
printk(KERN_ERR "iscsi_tcp: invalid ep %p.\n", ep);
|
|
return NULL;
|
|
}
|
|
|
|
shost = iscsi_host_alloc(&iscsi_sw_tcp_sht, 0, 1);
|
|
if (!shost)
|
|
return NULL;
|
|
shost->transportt = iscsi_sw_tcp_scsi_transport;
|
|
shost->cmd_per_lun = qdepth;
|
|
shost->max_lun = iscsi_max_lun;
|
|
shost->max_id = 0;
|
|
shost->max_channel = 0;
|
|
shost->max_cmd_len = SCSI_MAX_VARLEN_CDB_SIZE;
|
|
|
|
if (iscsi_host_add(shost, NULL))
|
|
goto free_host;
|
|
|
|
cls_session = iscsi_session_setup(&iscsi_sw_tcp_transport, shost,
|
|
cmds_max, 0,
|
|
sizeof(struct iscsi_tcp_task) +
|
|
sizeof(struct iscsi_sw_tcp_hdrbuf),
|
|
initial_cmdsn, 0);
|
|
if (!cls_session)
|
|
goto remove_host;
|
|
session = cls_session->dd_data;
|
|
|
|
shost->can_queue = session->scsi_cmds_max;
|
|
if (iscsi_tcp_r2tpool_alloc(session))
|
|
goto remove_session;
|
|
return cls_session;
|
|
|
|
remove_session:
|
|
iscsi_session_teardown(cls_session);
|
|
remove_host:
|
|
iscsi_host_remove(shost);
|
|
free_host:
|
|
iscsi_host_free(shost);
|
|
return NULL;
|
|
}
|
|
|
|
static void iscsi_sw_tcp_session_destroy(struct iscsi_cls_session *cls_session)
|
|
{
|
|
struct Scsi_Host *shost = iscsi_session_to_shost(cls_session);
|
|
|
|
iscsi_tcp_r2tpool_free(cls_session->dd_data);
|
|
iscsi_session_teardown(cls_session);
|
|
|
|
iscsi_host_remove(shost);
|
|
iscsi_host_free(shost);
|
|
}
|
|
|
|
static int iscsi_sw_tcp_slave_alloc(struct scsi_device *sdev)
|
|
{
|
|
set_bit(QUEUE_FLAG_BIDI, &sdev->request_queue->queue_flags);
|
|
return 0;
|
|
}
|
|
|
|
static int iscsi_sw_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_sw_tcp_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 = 4096,
|
|
.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_target_reset_handler = iscsi_eh_recover_target,
|
|
.use_clustering = DISABLE_CLUSTERING,
|
|
.slave_alloc = iscsi_sw_tcp_slave_alloc,
|
|
.slave_configure = iscsi_sw_tcp_slave_configure,
|
|
.target_alloc = iscsi_target_alloc,
|
|
.proc_name = "iscsi_tcp",
|
|
.this_id = -1,
|
|
};
|
|
|
|
static struct iscsi_transport iscsi_sw_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_TGT_RESET_TMO |
|
|
ISCSI_PING_TMO | ISCSI_RECV_TMO |
|
|
ISCSI_IFACE_NAME | ISCSI_INITIATOR_NAME,
|
|
.host_param_mask = ISCSI_HOST_HWADDRESS | ISCSI_HOST_IPADDRESS |
|
|
ISCSI_HOST_INITIATOR_NAME |
|
|
ISCSI_HOST_NETDEV_NAME,
|
|
/* session management */
|
|
.create_session = iscsi_sw_tcp_session_create,
|
|
.destroy_session = iscsi_sw_tcp_session_destroy,
|
|
/* connection management */
|
|
.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,
|
|
.get_session_param = iscsi_session_get_param,
|
|
.start_conn = iscsi_conn_start,
|
|
.stop_conn = iscsi_sw_tcp_conn_stop,
|
|
/* iscsi host params */
|
|
.get_host_param = iscsi_host_get_param,
|
|
.set_host_param = iscsi_host_set_param,
|
|
/* IO */
|
|
.send_pdu = iscsi_conn_send_pdu,
|
|
.get_stats = iscsi_sw_tcp_conn_get_stats,
|
|
/* iscsi task/cmd helpers */
|
|
.init_task = iscsi_tcp_task_init,
|
|
.xmit_task = iscsi_tcp_task_xmit,
|
|
.cleanup_task = iscsi_tcp_cleanup_task,
|
|
/* low level pdu helpers */
|
|
.xmit_pdu = iscsi_sw_tcp_pdu_xmit,
|
|
.init_pdu = iscsi_sw_tcp_pdu_init,
|
|
.alloc_pdu = iscsi_sw_tcp_pdu_alloc,
|
|
/* recovery */
|
|
.session_recovery_timedout = iscsi_session_recovery_timedout,
|
|
};
|
|
|
|
static int __init iscsi_sw_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_sw_tcp_scsi_transport = iscsi_register_transport(
|
|
&iscsi_sw_tcp_transport);
|
|
if (!iscsi_sw_tcp_scsi_transport)
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit iscsi_sw_tcp_exit(void)
|
|
{
|
|
iscsi_unregister_transport(&iscsi_sw_tcp_transport);
|
|
}
|
|
|
|
module_init(iscsi_sw_tcp_init);
|
|
module_exit(iscsi_sw_tcp_exit);
|