summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile18
-rw-r--r--TODO30
-rw-r--r--iscsi.h543
-rw-r--r--iscsi_control.c912
-rw-r--r--iscsi_control.h181
-rw-r--r--iscsi_if.h147
-rw-r--r--iscsi_tcp.c2238
-rw-r--r--iscsi_tcp.h284
-rwxr-xr-xiscsiadm800
9 files changed, 5153 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ca4fd14
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for the Linux Kernel iSCSI Initiator
+#
+
+EXTRA_CFLAGS += -I$(obj)
+
+obj-m = iscsi.o
+iscsi-y = iscsi_control.o iscsi_tcp.o
+
+#KSRC ?= /lib/modules/`uname -r`/build
+KSRC ?= /usr/src/linux-2.6.10-um
+KARCH ?= ARCH=um
+
+all:
+ make -C $(KSRC) SUBDIRS=`pwd` $(KARCH)
+
+clean:
+ rm -f *.mod.c .*cmd *.o *.ko
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..b874361
--- /dev/null
+++ b/TODO
@@ -0,0 +1,30 @@
+* optimize queue->lock, use atomic operations if possible
+* do we need ctask->sg_count? can we use ctask->sg instead?
+* optimize HEAD + SENDBUF logic, no need to have two "if" statements:
+
+
+ }
+ ctask->r2t = r2t;
+ ctask->in_progress = IN_PROGRESS_SOLICIT_WRITE;
+ /* can goto to fixme */
+ }
+
+ if (ctask->in_progress == IN_PROGRESS_SOLICIT_WRITE) {
+ r2t = ctask->r2t;
+_solicit_again:
+ /*
+ * send Data-Out's payload whitnin this R2T sequence.
+ */
+ if (r2t->data_count) {
+fixme:
+ len = r2t->sendbuf.size - r2t->sendbuf.sent;
+ if (len > r2t->data_count) {
+ len = r2t->data_count;
+
+* data_xmit(): recover sendpage() error.
+* make sense to split data_recv() and exctract data-in processing into
+ separated function. Call it from header processing context directly if
+ there is an iSCSI data and from data_recv() later on conn state machine
+ processing.
+* restructure header processing code into set of small functions.
+* prefetch in tcp_copy_bits().
diff --git a/iscsi.h b/iscsi.h
new file mode 100644
index 0000000..f71a7a7
--- /dev/null
+++ b/iscsi.h
@@ -0,0 +1,543 @@
+/*
+ * RFC 3720 (iSCSI) protocol data types
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by simple-iscsi-devel@lists.sourceforge.net
+ *
+ * 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.
+ */
+
+#ifndef ISCSI_H
+#define ISCSI_H
+
+/*
+ * useful common(control and data pathes) macro
+ */
+#define ntoh24(p) (((p)[0] << 16) | ((p)[1] << 8) | ((p)[2]))
+#define hton24(p, v) { \
+ p[0] = (((v) >> 16) & 0xFF); \
+ p[1] = (((v) >> 8) & 0xFF); \
+ p[2] = ((v) & 0xFF); \
+}
+#define zero_data(p) {p[0]=0;p[1]=0;p[2]=0;}
+
+/*
+ * iSCSI Template Message Header
+ */
+typedef struct iscsi_hdr {
+ uint8_t opcode;
+ uint8_t flags; /* Final bit */
+ uint8_t rsvd2[2];
+ uint8_t hlength; /* AHSs total length */
+ uint8_t dlength[3]; /* Data length */
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t ttt; /* Target Task Tag */
+ uint32_t statsn;
+ uint32_t exp_statsn;
+ uint8_t other[16];
+} iscsi_hdr_t;
+
+/************************* RFC 3720 Begin *****************************/
+
+#define ISCSI_RESERVED_TAG 0xffffffff
+
+/* Opcode encoding bits */
+#define ISCSI_OP_RETRY 0x80
+#define ISCSI_OP_IMMEDIATE 0x40
+#define ISCSI_OPCODE_MASK 0x3F
+
+/* Initiator Opcode values */
+#define ISCSI_OP_NOOP_OUT 0x00
+#define ISCSI_OP_SCSI_CMD 0x01
+#define ISCSI_OP_SCSI_TMFUNC 0x02
+#define ISCSI_OP_LOGIN 0x03
+#define ISCSI_OP_TEXT 0x04
+#define ISCSI_OP_SCSI_DATA_OUT 0x05
+#define ISCSI_OP_LOGOUT 0x06
+#define ISCSI_OP_SNACK 0x10
+
+/* Target Opcode values */
+#define ISCSI_OP_NOOP_IN 0x20
+#define ISCSI_OP_SCSI_CMD_RSP 0x21
+#define ISCSI_OP_SCSI_TMFUNC_RSP 0x22
+#define ISCSI_OP_LOGIN_RSP 0x23
+#define ISCSI_OP_TEXT_RSP 0x24
+#define ISCSI_OP_SCSI_DATA_IN 0x25
+#define ISCSI_OP_LOGOUT_RSP 0x26
+#define ISCSI_OP_R2T 0x31
+#define ISCSI_OP_ASYNC_EVENT 0x32
+#define ISCSI_OP_REJECT_MSG 0x3f
+
+/* SCSI Command Header */
+typedef struct iscsi_cmd {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2;
+ uint8_t cmdrn;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t data_length;
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t cdb[16]; /* SCSI Command Block */
+ /* Additional Data (Command Dependent) */
+} iscsi_cmd_t;
+
+/* Command PDU flags */
+#define ISCSI_FLAG_CMD_FINAL 0x80
+#define ISCSI_FLAG_CMD_READ 0x40
+#define ISCSI_FLAG_CMD_WRITE 0x20
+#define ISCSI_FLAG_CMD_ATTR_MASK 0x07 /* 3 bits */
+
+/* SCSI Command Attribute values */
+#define ISCSI_ATTR_UNTAGGED 0
+#define ISCSI_ATTR_SIMPLE 1
+#define ISCSI_ATTR_ORDERED 2
+#define ISCSI_ATTR_HEAD_OF_QUEUE 3
+#define ISCSI_ATTR_ACA 4
+
+/* SCSI Response Header */
+typedef struct iscsi_cmd_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t response;
+ uint8_t cmd_status;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rsvd1;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t expdatasn;
+ uint32_t residual_count;
+ uint32_t bi_residual_count;
+ /* Response or Sense Data (optional) */
+} iscsi_cmd_rsp_t;
+
+/* Command Response PDU flags */
+#define ISCSI_FLAG_CMD_BIDI_OVERFLOW 0x10
+#define ISCSI_FLAG_CMD_BIDI_UNDERFLOW 0x08
+#define ISCSI_FLAG_CMD_OVERFLOW 0x04
+#define ISCSI_FLAG_CMD_UNDERFLOW 0x02
+
+/* iSCSI Status values. Valid if Rsp Selector bit is not set */
+#define ISCSI_STATUS_CMD_COMPLETED 0
+#define ISCSI_STATUS_TARGET_FAILURE 1
+#define ISCSI_STATUS_SUBSYS_FAILURE 2
+
+/* Asynchronous Event Header */
+typedef struct iscsi_async {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint8_t rsvd4[8];
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t async_event;
+ uint8_t async_vcode;
+ uint16_t param1;
+ uint16_t param2;
+ uint16_t param3;
+ uint8_t rsvd5[4];
+} iscsi_async_t;
+
+/* iSCSI Event Indicator values */
+#define ASYNC_EVENT_SCSI_EVENT 0
+#define ASYNC_EVENT_REQUEST_LOGOUT 1
+#define ASYNC_EVENT_DROPPING_CONNECTION 2
+#define ASYNC_EVENT_DROPPING_ALL_CONNECTIONS 3
+#define ASYNC_EVENT_PARAM_NEGOTIATION 4
+#define ASYNC_EVENT_VENDOR_SPECIFIC 255
+
+/* NOP-Out Message */
+typedef struct iscsi_nopout {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t rsvd2;
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t ttt; /* Target Transfer Tag */
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t rsvd4[16];
+} iscsi_nopout_t;
+
+/* NOP-In Message */
+typedef struct iscsi_nopin {
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t rsvd2;
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t ttt; /* Target Transfer Tag */
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t rsvd4[12];
+} iscsi_nopin_t;
+
+/* SCSI Task Management Message Header */
+typedef struct iscsi_tm {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd1[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rtt; /* Reference Task Tag */
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint32_t refcmdsn;
+ uint32_t expdatasn;
+ uint8_t rsvd2[8];
+} iscsi_tm_t;
+
+#define ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK 0x7F
+
+/* Function values */
+#define ISCSI_TM_FUNC_ABORT_TASK 1
+#define ISCSI_TM_FUNC_ABORT_TASK_SET 2
+#define ISCSI_TM_FUNC_CLEAR_ACA 3
+#define ISCSI_TM_FUNC_CLEAR_TASK_SET 4
+#define ISCSI_TM_FUNC_LOGICAL_UNIT_RESET 5
+#define ISCSI_TM_FUNC_TARGET_WARM_RESET 6
+#define ISCSI_TM_FUNC_TARGET_COLD_RESET 7
+#define ISCSI_TM_FUNC_TASK_REASSIGN 8
+
+/* SCSI Task Management Response Header */
+typedef struct iscsi_tm_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t response; /* see Response values below */
+ uint8_t qualifier;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd2[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rtt; /* Reference Task Tag */
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t rsvd3[12];
+} iscsi_tm_rsp_t;
+
+/* Response values */
+#define SCSI_TCP_TM_RESP_COMPLETE 0x00
+#define SCSI_TCP_TM_RESP_NO_TASK 0x01
+#define SCSI_TCP_TM_RESP_NO_LUN 0x02
+#define SCSI_TCP_TM_RESP_TASK_ALLEGIANT 0x03
+#define SCSI_TCP_TM_RESP_NO_FAILOVER 0x04
+#define SCSI_TCP_TM_RESP_IN_PRGRESS 0x05
+#define SCSI_TCP_TM_RESP_REJECTED 0xff
+
+/* Ready To Transfer Header */
+typedef struct iscsi_r2t_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t ttt; /* Target Transfer Tag */
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t rttsn;
+ uint32_t data_offset;
+ uint32_t data_length;
+} iscsi_r2t_rsp_t;
+
+/* SCSI Data Hdr */
+typedef struct iscsi_data {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt;
+ uint32_t ttt;
+ uint32_t rsvd4;
+ uint32_t exp_statsn;
+ uint32_t rsvd5;
+ uint32_t datasn;
+ uint32_t offset;
+ uint32_t rsvd6;
+ /* Payload */
+} iscsi_data_t;
+
+/* SCSI Data Response Hdr */
+typedef struct iscsi_data_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2;
+ uint8_t cmd_status;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t lun[8];
+ uint32_t itt;
+ uint32_t ttt;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t datasn;
+ uint32_t offset;
+ uint32_t residual_count;
+} iscsi_data_rsp_t;
+
+/* Data Response PDU flags */
+#define ISCSI_FLAG_DATA_ACK 0x40
+#define ISCSI_FLAG_DATA_OVERFLOW 0x04
+#define ISCSI_FLAG_DATA_UNDERFLOW 0x02
+#define ISCSI_FLAG_DATA_STATUS 0x01
+
+/* Text Header */
+typedef struct iscsi_text {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd4[8];
+ uint32_t itt;
+ uint32_t ttt;
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t rsvd5[16];
+ /* Text - key=value pairs */
+} iscsi_text_t;
+
+#define ISCSI_FLAG_TEXT_CONTINUE 0x40
+
+/* Text Response Header */
+typedef struct iscsi_text_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd4[8];
+ uint32_t itt;
+ uint32_t ttt;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t rsvd5[12];
+ /* Text Response - key:value pairs */
+} iscsi_text_rsp_t;
+
+/* Login Header */
+typedef struct iscsi_login {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t max_version; /* Max. version supported */
+ uint8_t min_version; /* Min. version supported */
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t isid[6]; /* Initiator Session ID */
+ uint16_t tsih; /* Target Session Handle */
+ uint32_t itt; /* Initiator Task Tag */
+ uint16_t cid;
+ uint16_t rsvd3;
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t rsvd5[16];
+} iscsi_login_t;
+
+/* Login PDU flags */
+#define ISCSI_FLAG_LOGIN_TRANSIT 0x80
+#define ISCSI_FLAG_LOGIN_CONTINUE 0x40
+#define ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK 0x0C /* 2 bits */
+#define ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK 0x03 /* 2 bits */
+
+#define ISCSI_LOGIN_CURRENT_STAGE(flags) \
+((flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2)
+#define ISCSI_LOGIN_NEXT_STAGE(flags) \
+(flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK)
+
+/* Login Response Header */
+typedef struct iscsi_login_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t max_version; /* Max. version supported */
+ uint8_t active_version; /* Active version */
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t isid[6]; /* Initiator Session ID */
+ uint16_t tsih; /* Target Session Handle */
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rsvd3;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint8_t status_class; /* see Login RSP ststus classes below */
+ uint8_t status_detail; /* see Login RSP Status details below */
+ uint8_t rsvd4[10];
+} iscsi_login_rsp_t;
+
+/* Login stage (phase) codes for CSG, NSG */
+#define ISCSI_INITIAL_LOGIN_STAGE -1
+#define ISCSI_SECURITY_NEGOTIATION_STAGE 0
+#define ISCSI_OP_PARMS_NEGOTIATION_STAGE 1
+#define ISCSI_FULL_FEATURE_PHASE 3
+
+/* Login Status response classes */
+#define STATUS_CLASS_SUCCESS 0x00
+#define STATUS_CLASS_REDIRECT 0x01
+#define STATUS_CLASS_INITIATOR_ERR 0x02
+#define STATUS_CLASS_TARGET_ERR 0x03
+
+/* Login Status response detail codes */
+/* Class-0 (Success) */
+#define ISCSI_LOGIN_STATUS_ACCEPT 0x00
+
+/* Class-1 (Redirection) */
+#define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP 0x01
+#define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM 0x02
+
+/* Class-2 (Initiator Error) */
+#define ISCSI_LOGIN_STATUS_INIT_ERR 0x00
+#define ISCSI_LOGIN_STATUS_AUTH_FAILED 0x01
+#define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN 0x02
+#define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND 0x03
+#define ISCSI_LOGIN_STATUS_TGT_REMOVED 0x04
+#define ISCSI_LOGIN_STATUS_NO_VERSION 0x05
+#define ISCSI_LOGIN_STATUS_ISID_ERROR 0x06
+#define ISCSI_LOGIN_STATUS_MISSING_FIELDS 0x07
+#define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED 0x08
+#define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE 0x09
+#define ISCSI_LOGIN_STATUS_NO_SESSION 0x0a
+#define ISCSI_LOGIN_STATUS_INVALID_REQUEST 0x0b
+
+/* Class-3 (Target Error) */
+#define ISCSI_LOGIN_STATUS_TARGET_ERROR 0x00
+#define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE 0x01
+#define ISCSI_LOGIN_STATUS_NO_RESOURCES 0x02
+
+/* Logout Header */
+typedef struct iscsi_logout {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd1[2];
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd2[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint16_t cid;
+ uint8_t rsvd3[2];
+ uint32_t cmdsn;
+ uint32_t exp_statsn;
+ uint8_t rsvd4[16];
+} iscsi_logout_t;
+
+/* Logout PDU flags */
+#define ISCSI_FLAG_LOGOUT_REASON_MASK 0x7F
+
+/* logout reason_code values */
+
+#define ISCSI_LOGOUT_REASON_CLOSE_SESSION 0
+#define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION 1
+#define ISCSI_LOGOUT_REASON_RECOVERY 2
+#define ISCSI_LOGOUT_REASON_AEN_REQUEST 3
+
+/* Logout Response Header */
+typedef struct iscsi_logout_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t response; /* see Logout response values below */
+ uint8_t rsvd2;
+ uint8_t hlength;
+ uint8_t dlength[3];
+ uint8_t rsvd3[8];
+ uint32_t itt; /* Initiator Task Tag */
+ uint32_t rsvd4;
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t rsvd5;
+ uint16_t t2wait;
+ uint16_t t2retain;
+ uint32_t rsvd6;
+} iscsi_logout_rsp_t;
+
+/* logout response status values */
+
+#define ISCSI_LOGOUT_SUCCESS 0
+#define ISCSI_LOGOUT_CID_NOT_FOUND 1
+#define ISCSI_LOGOUT_RECOVERY_UNSUPPORTED 2
+#define ISCSI_LOGOUT_CLEANUP_FAILED 3
+
+/* SNACK Header */
+typedef struct iscsi_snack {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t rsvd2[14];
+ uint32_t itt;
+ uint32_t begrun;
+ uint32_t runlength;
+ uint32_t exp_statsn;
+ uint32_t rsvd3;
+ uint32_t expdatasn;
+ uint8_t rsvd6[8];
+} iscsi_snack_t;
+
+/* SNACK PDU flags */
+#define ISCSI_FLAG_SNACK_TYPE_MASK 0x0F /* 4 bits */
+
+/* Reject Message Header */
+typedef struct iscsi_reject_rsp {
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t reason;
+ uint8_t rsvd2;
+ uint8_t rsvd3;
+ uint8_t dlength[3];
+ uint8_t rsvd4[16];
+ uint32_t statsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+ uint32_t datasn;
+ uint8_t rsvd5[8];
+ /* Text - Rejected hdr */
+} iscsi_reject_rsp_t;
+
+/* Reason for Reject */
+#define CMD_BEFORE_LOGIN 1
+#define DATA_DIGEST_ERROR 2
+#define DATA_SNACK_REJECT 3
+#define ISCSI_PROTOCOL_ERROR 4
+#define CMD_NOT_SUPPORTED 5
+#define IMM_CMD_REJECT 6
+#define TASK_IN_PROGRESS 7
+#define INVALID_SNACK 8
+#define BOOKMARK_REJECTED 9
+#define BOOKMARK_NO_RESOURCES 10
+#define NEGOTIATION_RESET 11
+
+/************************* RFC 3720 End *****************************/
+
+#endif /* ISCSI_H */
diff --git a/iscsi_control.c b/iscsi_control.c
new file mode 100644
index 0000000..587bfe3
--- /dev/null
+++ b/iscsi_control.c
@@ -0,0 +1,912 @@
+/*
+ * iSCSI Initiator Control-Path
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by linux-storage-devel@lists.sourceforge.net
+ *
+ * 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.
+ */
+
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_transport.h>
+#include <iscsi_if.h>
+#include <iscsi_control.h>
+
+static iscsi_provider_t provider_table[ISCSI_PROVIDER_MAX];
+
+static iscsi_initiator_t initiator = {
+ .sp.initiator_name = "<not specified>",
+ .sp.initiator_alias = "<not specified>",
+ .sp.isid = {0,0,0,0,0,0},
+ .sp.target_name = "<not specified>",
+ .sp.target_alias = "<not specified>",
+ .sp.target_portal = "<not specified>",
+ .sp.target_address = "<not specified>",
+ .sp.tpgt = 1,
+ .sp.tsih = 0,
+ .sp.first_burst = 131072,
+ .cp.max_recv_dlength = 131072,
+ .cp.max_xmit_dlength = 131072,
+ .sp.max_burst = 262144,
+ .sp.max_r2t = 8,
+ .sp.max_cnx = 1,
+ .sp.erl = 0,
+ .sp.initial_r2t_en = 0,
+ .sp.imm_data_en = 1,
+ .cp.hdrdgst_en = 0,
+ .cp.datadgst_en = 0,
+ .sp.ifmarker_en = 0,
+ .sp.ofmarker_en = 0,
+ .sp.pdu_inorder_en = 1,
+ .sp.dataseq_inorder_en = 1,
+ .sp.time2wait = 2,
+ .sp.time2retain = 20,
+ .sp.auth_en = 0,
+ .sp.cmdsn = 1,
+ .sp.exp_cmdsn = 2,
+ .sp.max_cmdsn = 2,
+};
+
+static iscsi_param_t param_table[] = {
+ {1, "initiator_name", &initiator.sp.initiator_name, 0, 0, 1},
+ {1, "initiator_alias", &initiator.sp.initiator_alias, 0, 0, 1},
+ {1, "isid", &initiator.sp.isid, 0, 0, 0},
+ {1, "target_name", &initiator.sp.target_name, 0, 0, 0},
+ {1, "target_alias", &initiator.sp.target_alias, 0, 0, 0},
+ {1, "target_portal", &initiator.sp.target_portal, 0, 0, 0},
+ {1, "target_address", &initiator.sp.target_address, 0, 0, 0},
+ {0, "tpgt", &initiator.sp.tpgt, 0, 65535, 0},
+ {0, "tsih", &initiator.sp.tsih, 0, 65535, 0},
+ {0, "first_burst", &initiator.sp.first_burst, 512, 16777215, 0},
+ {0, "max_recv_dlength", &initiator.cp.max_recv_dlength,512,16777215, 0},
+ {0, "max_xmit_dlength", &initiator.cp.max_xmit_dlength,512,16777215, 0},
+ {0, "max_burst", &initiator.sp.max_burst, 512, 16777215, 0},
+ {0, "max_r2t", &initiator.sp.max_r2t, 1, 65535, 0},
+ {0, "max_cnx", &initiator.sp.max_cnx, 1, 65535, 0},
+ {0, "erl", &initiator.sp.erl, 0, 2, 0},
+ {0, "initial_r2t_en", &initiator.sp.initial_r2t_en, 0, 1, 0},
+ {0, "imm_data_en", &initiator.sp.imm_data_en, 0, 1, 0},
+ {0, "hdrdgst_en", &initiator.cp.hdrdgst_en, 0, 1, 0},
+ {0, "datadgst_en", &initiator.cp.datadgst_en, 0, 1, 0},
+ {0, "ifmarker_en", &initiator.sp.ifmarker_en, 0, 1, 0},
+ {0, "ofmarker_en", &initiator.sp.ofmarker_en, 0, 1, 0},
+ {0, "pdu_inorder_en", &initiator.sp.pdu_inorder_en, 0, 1, 0},
+ {0, "dataseq_inorder_en", &initiator.sp.dataseq_inorder_en, 0, 1, 0},
+ {0, "time2wait", &initiator.sp.time2wait, 0, 3600, 0},
+ {0, "time2retain", &initiator.sp.time2retain, 0, 3600, 0},
+ {0, "auth_en", &initiator.sp.auth_en, 0, 1, 0},
+ {0, "cmdsn", &initiator.sp.auth_en, 0, 0xffffffff, 0},
+ {0, "exp_cmdsn", &initiator.sp.auth_en, 0, 0xffffffff, 0},
+ {0, "max_cmdsn", &initiator.sp.auth_en, 0, 0xffffffff, 0},
+};
+
+static void
+iscsi_host_class_release(struct class_device *class_dev)
+{
+ struct Scsi_Host *shost = transport_class_to_shost(class_dev);
+ put_device(&shost->shost_gendev);
+}
+
+struct class iscsi_host_class = {
+ .name = "iscsi",
+ .release = iscsi_host_class_release,
+};
+
+static void
+iscsi_send_nopin_rsp(iscsi_event_t *ev)
+{
+ iscsi_nopout_t *hdr = (iscsi_nopout_t*)&ev->rhdr;
+ iscsi_provider_t *provider = ev->session->provider;
+
+ memset(hdr, 0, sizeof(iscsi_nopout_t));
+ hdr->opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE;
+ hdr->flags = ISCSI_FLAG_CMD_FINAL;
+ hdr->dlength[0] = ev->hdr.dlength[0];
+ hdr->dlength[1] = ev->hdr.dlength[1];
+ hdr->dlength[2] = ev->hdr.dlength[2];
+ memcpy(hdr->lun, ev->hdr.lun, 8);
+ hdr->ttt = ev->hdr.ttt;
+ hdr->itt = ISCSI_RESERVED_TAG;
+
+ /* DP fill-ins cmdsn and exp_statsn */
+ provider->ops.send_immpdu(ev->cnx->handle, &ev->rhdr, ev->data);
+}
+
+static iscsi_provider_t*
+iscsi_find_provider(char *name)
+{
+ int i;
+
+ for (i=0; i<ISCSI_PROVIDER_MAX; i++) {
+ if (!strcmp(provider_table[i].name, name))
+ return &provider_table[i];
+ }
+ return NULL;
+}
+
+static iscsi_cnx_ctrl_t*
+iscsi_find_cnx(iscsi_session_ctrl_t *session, int cid)
+{
+ struct list_head *lh;
+
+ spin_lock(&session->connections_lock);
+ list_for_each(lh, &session->connections) {
+ iscsi_cnx_ctrl_t *cnx;
+ cnx = list_entry(lh, iscsi_cnx_ctrl_t, item);
+ if (cnx && cnx->cid == cid) {
+ spin_unlock(&session->connections_lock);
+ return cnx;
+ }
+ }
+ spin_unlock(&session->connections_lock);
+ return NULL;
+}
+
+static iscsi_session_ctrl_t*
+iscsi_find_session(int host_no)
+{
+ struct list_head *lh;
+
+ spin_lock(&initiator.sessions_lock);
+ list_for_each(lh, &initiator.sessions) {
+ iscsi_session_ctrl_t *session;
+ session = list_entry(lh, iscsi_session_ctrl_t, item);
+ if (session && session->host_no == host_no) {
+ spin_unlock(&initiator.sessions_lock);
+ return session;
+ }
+ }
+ spin_unlock(&initiator.sessions_lock);
+ return NULL;
+}
+
+static iscsi_event_t*
+iscsi_event_alloc(iscsi_cnx_ctrl_t *cnx, iscsi_event_e type)
+{
+ iscsi_event_t *ev;
+
+ if ((ev = kmalloc(sizeof(iscsi_event_t), GFP_KERNEL)) == NULL) {
+ return NULL;
+ }
+ memset(ev, 0, sizeof(iscsi_event_t));
+
+ ev->session = cnx->session;
+ ev->cnx = cnx;
+ ev->type = type;
+
+ return ev;
+}
+
+static iscsi_event_t*
+iscsi_event_pdu_alloc(iscsi_cnx_ctrl_t *cnx,
+ iscsi_hdr_t *hdr, char *data)
+{
+ int datalen = ntoh24(hdr->dlength);
+ iscsi_event_t *ev;
+
+ if ((ev = kmalloc(sizeof(iscsi_event_t), GFP_KERNEL)) == NULL) {
+ return NULL;
+ }
+
+ if ((ev->data = kmalloc(datalen, GFP_KERNEL)) == NULL) {
+ kfree(ev);
+ return NULL;
+ }
+
+ memcpy(&ev->hdr, hdr, sizeof(iscsi_hdr_t));
+ memcpy(ev->data, data, datalen);
+ ev->session = cnx->session;
+ ev->cnx = cnx;
+ ev->type = ISCSI_EVENT_PDU;
+
+ return ev;
+}
+
+static void
+iscsi_event_free(iscsi_event_t *ev)
+{
+ if (ev->type == ISCSI_EVENT_PDU) {
+ kfree(ev->data);
+ }
+ kfree(ev);
+}
+
+static void
+iscsi_event_enqueue(iscsi_event_t *ev)
+{
+ iscsi_session_ctrl_t *session = ev->session;
+
+ spin_lock(&session->eventlock);
+ list_add_tail(&ev->item, &session->eventqueue);
+ spin_unlock(&session->eventlock);
+ schedule_work(&session->eventwork);
+}
+
+static void
+iscsi_event_process(iscsi_event_t *ev)
+{
+ int free_ev = 1;
+
+ switch (ev->type) {
+ case ISCSI_EVENT_PDU: {
+ switch (ev->hdr.opcode) {
+ case ISCSI_OP_NOOP_IN:
+ iscsi_send_nopin_rsp(ev);
+ free_ev = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case ISCSI_EVENT_REOPEN: {
+ iscsi_session_ctrl_t *session = ev->session;
+ iscsi_cnx_ctrl_t *cnx = ev->cnx;
+ iscsi_provider_t *provider = session->provider;
+ struct Scsi_Host *host = scsi_host_lookup(session->host_no);
+
+ provider->ops.stop_cnx(cnx->handle);
+ provider->ops.destroy_cnx(cnx->handle);
+
+ spin_lock(&session->connections_lock);
+ list_del(&cnx->item);
+ if (list_empty(&session->connections))
+ session->leadcnx = NULL;
+ spin_unlock(&session->connections_lock);
+
+ printk("<1>iSCSI/CP: attempt to recover session #%d\n",
+ session->host_no);
+ kobject_hotplug(&host->transport_classdev.kobj, KOBJ_CHANGE);
+ }
+ break;
+ default:
+ BUG_ON(1);
+ }
+
+ if (free_ev) {
+ iscsi_event_free(ev);
+ } else {
+ spin_lock_bh(&ev->session->freelock);
+ list_add(&ev->item, &ev->session->freequeue);
+ spin_unlock_bh(&ev->session->freelock);
+ }
+}
+
+static void
+iscsi_event_worker(void *data)
+{
+ iscsi_session_ctrl_t *session = (iscsi_session_ctrl_t *)data;
+ struct list_head *lh, *n;
+
+ spin_lock_bh(&session->eventlock);
+ list_for_each_safe(lh, n, &session->eventqueue) {
+ iscsi_event_t *ev;
+ ev = list_entry(lh, iscsi_event_t, item);
+ if (ev) {
+ list_del(&ev->item);
+ spin_unlock_bh(&session->eventlock);
+ iscsi_event_process(ev);
+ spin_lock_bh(&session->eventlock);
+ }
+ }
+ spin_unlock_bh(&session->eventlock);
+}
+
+static ssize_t
+iscsi_host_class_parameters_show(struct class_device *cdev, char *buf)
+{
+ struct Scsi_Host *shost = transport_class_to_shost(cdev);
+ iscsi_session_ctrl_t *session =
+ container_of(shost->transportt, struct iscsi_session_ctrl,
+ transportt);
+ int count = 0;
+
+ count += sprintf(buf+count, "target_name = %s\n",
+ session->p.target_name);
+ count += sprintf(buf+count, "target_alias = %s\n",
+ session->p.target_alias);
+ count += sprintf(buf+count, "target_portal = %s\n",
+ session->p.target_portal);
+ count += sprintf(buf+count, "target_address = %s\n",
+ session->p.target_address);
+ count += sprintf(buf+count, "tsih = %d\n", session->p.tsih);
+ count += sprintf(buf+count, "tpgt = %d\n", session->p.tpgt);
+ count += sprintf(buf+count, "isid = %02x.%02x.%02x.%02x.%02x.%02x\n",
+ session->p.isid[0], session->p.isid[1],
+ session->p.isid[2], session->p.isid[3],
+ session->p.isid[4], session->p.isid[5]);
+ count += sprintf(buf+count, "time2wait = %d\n",
+ session->p.time2wait);
+ count += sprintf(buf+count, "time2retain = %d\n",
+ session->p.time2retain);
+ count += sprintf(buf+count, "max_cnx = %d\n", session->p.max_cnx);
+ count += sprintf(buf+count, "initial_r2t_en = %d\n",
+ session->p.initial_r2t_en);
+ count += sprintf(buf+count, "max_r2t = %d\n", session->p.max_r2t);
+ count += sprintf(buf+count, "imm_data_en = %d\n",
+ session->p.imm_data_en);
+ count += sprintf(buf+count, "first_burst = %d\n",
+ session->p.first_burst);
+ count += sprintf(buf+count, "max_burst = %d\n", session->p.max_burst);
+ count += sprintf(buf+count, "pdu_inorder_en = %d\n",
+ session->p.pdu_inorder_en);
+ count += sprintf(buf+count, "dataseq_inorder_en = %d\n",
+ session->p.dataseq_inorder_en);
+ count += sprintf(buf+count, "erl = %d\n",
+ session->p.erl);
+ count += sprintf(buf+count, "ifmarker_en = %d\n",
+ session->p.ifmarker_en);
+ count += sprintf(buf+count, "ofmarker_en = %d\n",
+ session->p.ofmarker_en);
+
+ return count;
+}
+static CLASS_DEVICE_ATTR(parameters, S_IRUGO,
+ iscsi_host_class_parameters_show, NULL);
+
+static ssize_t
+iscsi_host_class_cnx_parameters_show(struct class_device *cdev, char *buf)
+{
+ struct Scsi_Host *shost = transport_class_to_shost(cdev);
+ iscsi_session_ctrl_t *session =
+ container_of(shost->transportt, struct iscsi_session_ctrl,
+ transportt);
+ int count = 0;
+ struct list_head *lh;
+
+ spin_lock(&session->connections_lock);
+ list_for_each_prev(lh, &session->connections) {
+ iscsi_cnx_ctrl_t *cnx;
+ cnx = list_entry(lh, iscsi_cnx_ctrl_t, item);
+ if (cnx) {
+ count += sprintf(buf+count,"%d\t%d\t%d\t%d\t%d\n",
+ cnx->cid,
+ cnx->p.max_recv_dlength,
+ cnx->p.max_xmit_dlength,
+ cnx->p.hdrdgst_en,
+ cnx->p.datadgst_en);
+ }
+ }
+ spin_unlock(&session->connections_lock);
+
+ return count;
+}
+static CLASS_DEVICE_ATTR(cnx_parameters, S_IRUGO,
+ iscsi_host_class_cnx_parameters_show, NULL);
+
+static ssize_t
+iscsi_host_class_cnx_parameters_hdr_show(struct class_device *cdev, char *buf)
+{
+ return sprintf(buf, "CID\tMaxRecv\tMaxXmit\tHdrDgst\tDataDgst\n");
+}
+static CLASS_DEVICE_ATTR(cnx_parameters_hdr, S_IRUGO,
+ iscsi_host_class_cnx_parameters_hdr_show, NULL);
+
+static ssize_t
+iscsi_host_class_cnx_stats_show(struct class_device *cdev, char *buf)
+{
+ return sprintf(buf, "to be implemented...\n");
+}
+static CLASS_DEVICE_ATTR(cnx_stats, S_IRUGO,
+ iscsi_host_class_cnx_stats_show, NULL);
+
+static ssize_t
+iscsi_host_class_cnx_stats_hdr_show(struct class_device *cdev, char *buf)
+{
+ return sprintf(buf, "to be implemented...\n");
+}
+static CLASS_DEVICE_ATTR(cnx_stats_hdr, S_IRUGO,
+ iscsi_host_class_cnx_stats_hdr_show, NULL);
+
+static ssize_t
+iscsi_host_class_state(struct class_device *cdev, char *buf)
+{
+ struct Scsi_Host *shost = transport_class_to_shost(cdev);
+ iscsi_session_ctrl_t *session =
+ container_of(shost->transportt, struct iscsi_session_ctrl,
+ transportt);
+ int count = 0;
+
+ if (session->state == ISCSI_STATE_FREE) {
+ count = sprintf(buf, "free\n");
+ } else if (session->state == ISCSI_STATE_FAILED) {
+ count = sprintf(buf, "failed\n");
+ } else {
+ count = sprintf(buf, "logged_in\n");
+ }
+
+ return count;
+}
+static CLASS_DEVICE_ATTR(state, S_IRUGO,
+ iscsi_host_class_state, NULL);
+
+
+static int
+iscsi_add_session(iscsi_provider_t *provider, int host_no)
+{
+ iscsi_session_ctrl_t *session;
+
+ session = kmalloc(sizeof(iscsi_session_ctrl_t), GFP_KERNEL);
+ if (session == NULL) {
+ return -ENOMEM;
+ }
+ memset(session, 0, sizeof(iscsi_session_ctrl_t));
+
+ session->class_attrs[0] = &class_device_attr_parameters;
+ session->class_attrs[1] = &class_device_attr_cnx_parameters;
+ session->class_attrs[2] = &class_device_attr_cnx_parameters_hdr;
+ session->class_attrs[3] = &class_device_attr_cnx_stats;
+ session->class_attrs[4] = &class_device_attr_cnx_stats_hdr;
+ session->class_attrs[5] = &class_device_attr_state;
+ session->class_attrs[6] = NULL;
+ session->transportt.host_attrs = &session->class_attrs[0];
+ session->transportt.host_class = &iscsi_host_class;
+ session->transportt.host_setup = NULL;
+ session->transportt.host_size = 0;
+
+ session->handle = provider->ops.create_session(session, host_no,
+ &session->transportt, initiator.sp.cmdsn);
+ if (session->handle == NULL) {
+ kfree(session);
+ return -EIO;
+ }
+
+ memcpy(&session->p, &initiator.sp, sizeof(iscsi_session_params_t));
+ session->provider = provider;
+ session->host_no = host_no;
+
+ spin_lock(&initiator.sessions_lock);
+ list_add(&session->item, &initiator.sessions);
+ spin_unlock(&initiator.sessions_lock);
+
+ INIT_LIST_HEAD(&session->connections);
+ spin_lock_init(&session->connections_lock);
+
+ INIT_LIST_HEAD(&session->eventqueue);
+ INIT_WORK(&session->eventwork, iscsi_event_worker, session);
+ spin_lock_init(&session->eventlock);
+
+ INIT_LIST_HEAD(&session->freequeue);
+ spin_lock_init(&session->freelock);
+
+ session->state = ISCSI_STATE_FREE;
+
+ return 0;
+}
+
+static int
+iscsi_remove_session(iscsi_provider_t *provider, int host_no)
+{
+ iscsi_session_ctrl_t *session;
+
+ if ((session = iscsi_find_session(host_no)) == NULL) {
+ return -ENXIO;
+ }
+
+ provider->ops.destroy_session(session->handle);
+
+ spin_lock(&initiator.sessions_lock);
+ list_del(&session->item);
+ spin_unlock(&initiator.sessions_lock);
+
+ kfree(session);
+
+ return 0;
+}
+
+static int
+iscsi_add_connection(iscsi_provider_t *provider, int host_no,
+ int cid, struct socket *sock)
+{
+ iscsi_session_ctrl_t *session;
+ iscsi_cnx_ctrl_t *cnx;
+
+ if ((session = iscsi_find_session(host_no)) == NULL) {
+ return -ENXIO;
+ }
+
+ if (iscsi_find_cnx(session, cid)) {
+ return -EPERM;
+ }
+
+ cnx = kmalloc(sizeof(iscsi_cnx_ctrl_t), GFP_KERNEL);
+ if (cnx == NULL) {
+ return -ENOMEM;
+ }
+ memset(cnx, 0, sizeof(iscsi_cnx_ctrl_t));
+ memcpy(&cnx->p, &initiator.cp, sizeof(iscsi_cnx_params_t));
+
+ if ((cnx->handle = provider->ops.create_cnx(
+ session->handle, cnx, sock, cid)) == NULL) {
+ kfree(cnx);
+ return -EIO;
+ }
+
+ if (provider->ops.bind_cnx(session->handle, cnx->handle, cid == 0)) {
+ provider->ops.destroy_cnx(cnx->handle);
+ kfree(cnx);
+ return -ESRCH;
+ }
+
+ /* leading connection */
+ if (cid == 0) {
+ session->leadcnx = cnx;
+ }
+
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_MAX_RECV_DLENGH, initiator.cp.max_recv_dlength);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_MAX_XMIT_DLENGH, initiator.cp.max_xmit_dlength);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_HDRDGST_EN, initiator.cp.hdrdgst_en);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_DATADGST_EN, initiator.cp.datadgst_en);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_INITIAL_R2T_EN, initiator.sp.initial_r2t_en);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_MAX_R2T, initiator.sp.max_r2t);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_IMM_DATA_EN, initiator.sp.imm_data_en);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_FIRST_BURST, initiator.sp.first_burst);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_MAX_BURST, initiator.sp.max_burst);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_PDU_INORDER_EN, initiator.sp.pdu_inorder_en);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_DATASEQ_INORDER_EN,initiator.sp.dataseq_inorder_en);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_ERL, initiator.sp.erl);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_IFMARKER_EN, initiator.sp.ifmarker_en);
+ provider->ops.set_param(cnx->handle,
+ ISCSI_PARAM_OFMARKER_EN, initiator.sp.ofmarker_en);
+
+ if (provider->ops.start_cnx(cnx->handle)) {
+ provider->ops.destroy_cnx(cnx->handle);
+ kfree(cnx);
+ return -EIO;
+ }
+
+ cnx->cid = cid;
+ cnx->session = session;
+
+ spin_lock(&session->connections_lock);
+ list_add(&cnx->item, &session->connections);
+ spin_unlock(&session->connections_lock);
+
+ session->state = ISCSI_STATE_LOGGED_IN;
+
+ return 0;
+}
+
+static int
+iscsi_remove_connection(iscsi_provider_t *provider, int host_no, int cid)
+{
+ iscsi_session_ctrl_t *session;
+ iscsi_cnx_ctrl_t *cnx;
+
+ if ((session = iscsi_find_session(host_no)) == NULL) {
+ return -ENXIO;
+ }
+
+ if ((cnx = iscsi_find_cnx(session, cid)) == NULL) {
+ return -EPERM;
+ }
+
+ provider->ops.destroy_cnx(cnx->handle);
+
+ spin_lock(&session->connections_lock);
+ list_del(&cnx->item);
+ if (list_empty(&session->connections))
+ session->leadcnx = NULL;
+ spin_unlock(&session->connections_lock);
+
+ if (cid == 0)
+ session->state = ISCSI_STATE_FREE;
+
+ return 0;
+}
+
+/*
+ * Stop, Logout and Cleanup all active sessions for a given provider.
+ */
+static void
+iscsi_cleanup(iscsi_provider_t *provider)
+{
+ struct list_head *lhs, *lhc, *ns, *nc;
+
+ spin_lock(&initiator.sessions_lock);
+ list_for_each_safe(lhs, ns, &initiator.sessions) {
+ iscsi_session_ctrl_t *session;
+ session = list_entry(lhs, iscsi_session_ctrl_t, item);
+ if (session) {
+ spin_lock(&session->connections_lock);
+ list_for_each_safe(lhc, nc, &session->connections) {
+ iscsi_cnx_ctrl_t *cnx;
+ cnx = list_entry(lhc, iscsi_cnx_ctrl_t, item);
+ if (cnx) {
+ provider->ops.stop_cnx(cnx->handle);
+ provider->ops.destroy_cnx(cnx->handle);
+ list_del(&cnx->item);
+ }
+ }
+ spin_unlock(&session->connections_lock);
+ provider->ops.destroy_session(session->handle);
+ list_del(&session->item);
+ kfree(session);
+ }
+ }
+ spin_unlock(&initiator.sessions_lock);
+}
+
+int
+iscsi_control_recv_pdu(iscsi_cnx_h handle, iscsi_hdr_t *hdr, char *data)
+{
+ iscsi_event_t *ev;
+ iscsi_cnx_ctrl_t *cnx = (iscsi_cnx_ctrl_t *)handle;
+ iscsi_session_ctrl_t *session = cnx->session;
+ struct list_head *lh, *n;
+
+ /* free pending resources based on received statsn */
+ spin_lock(&session->freelock);
+ list_for_each_safe(lh, n, &session->freequeue) {
+ iscsi_event_t *ev;
+ ev = list_entry(lh, iscsi_event_t, item);
+ if (ev && ev->type == ISCSI_EVENT_PDU &&
+ ntohl(ev->hdr.statsn) < ntohl(hdr->statsn)) {
+ list_del(&ev->item);
+ iscsi_event_free(ev);
+ }
+ }
+ spin_unlock(&session->freelock);
+
+ switch (hdr->opcode) {
+ case ISCSI_OP_NOOP_IN: {
+ if (hdr->ttt != ISCSI_RESERVED_TAG) {
+ /* its a "ping" from the target */
+ if ((ev = iscsi_event_pdu_alloc(
+ cnx, hdr, data))) {
+ iscsi_event_enqueue(ev);
+ } else {
+ /* FIXME: send reject (reason 0x6)
+ * we should be able allocate
+ * reject in most cases */
+ }
+ }
+ }
+ break;
+ case ISCSI_OP_TEXT_RSP:
+ case ISCSI_OP_LOGOUT_RSP:
+ case ISCSI_OP_ASYNC_EVENT:
+ case ISCSI_OP_REJECT_MSG:
+ break;
+ default: break;
+ }
+
+ return 0;
+}
+
+void
+iscsi_control_cnx_error(iscsi_cnx_h handle, int error)
+{
+ iscsi_cnx_ctrl_t *cnx = (iscsi_cnx_ctrl_t *)handle;
+ iscsi_session_ctrl_t *session = cnx->session;
+
+ switch (error) {
+ case ISCSI_ERR_CNX_FAILED: {
+ iscsi_event_t *ev;
+ session->state = ISCSI_STATE_FAILED;
+ if ((ev = iscsi_event_alloc(cnx, ISCSI_EVENT_REOPEN))) {
+ iscsi_event_enqueue(ev);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * iscsi_sysfs_parameters_show - sysfs callback.
+ *
+ * Usage: cat .../iscsi/parameters
+ */
+static int
+iscsi_host_class_initiator_parameters_show(struct class *class, char * buf)
+{
+ int count=0, i;
+
+ for (i=0; i<sizeof(param_table)/sizeof(iscsi_param_t); i++) {
+ iscsi_param_t *p = &param_table[i];
+ if (!p->show)
+ continue;
+ if (p->type == 0) { /* int type */
+ count += sprintf(buf+count, "%s = %d\n", p->key,
+ *(int*)p->value);
+ } else {
+ count += sprintf(buf+count, "%s = %s\n", p->key,
+ (char*)p->value);
+ }
+ }
+ return count;
+}
+
+/*
+ * iscsi_sysfs_parameters_store - sysfs callback.
+ *
+ * Usage: echo "#1 #2" > /sys/class/iscsi/initiator_parameters
+ *
+ * #1 - an iSCSI text key
+ * #2 - value
+ */
+static int
+iscsi_host_class_initiator_parameters_store(struct class *class,
+ const char * buf, size_t count)
+{
+ int i, res;
+ char key[64];
+ char sval[ISCSI_STRING_MAX];
+
+ res = sscanf(buf, "%64s %255s", key, sval);
+ if (res != 2) {
+ printk("wrong format '%s' (%d)\n", buf, res);
+ return count;
+ }
+
+ for (i=0; i<sizeof(param_table)/sizeof(iscsi_param_t); i++) {
+ iscsi_param_t *p = &param_table[i];
+ if (!strnicmp(p->key, key, strlen(key))) {
+ if (p->type == 0) { /* int type */
+ *(int*)p->value = simple_strtoul(sval, NULL, 0);
+ if (*(int*)p->value < p->min ||
+ *(int*)p->value > p->max) {
+ printk("bad range '%s'\n", key);
+ return count;
+ }
+ } else { /* string type */
+ strncpy((char*)p->value, sval, strlen(sval)+1);
+ }
+ break;
+ }
+ }
+ if (i == sizeof(param_table)/sizeof(iscsi_param_t)) {
+ printk("wrong parameter '%s'\n", key);
+ return count;
+ }
+ return count;
+}
+static CLASS_ATTR(initiator_parameters, S_IWUSR | S_IRUGO,
+ iscsi_host_class_initiator_parameters_show,
+ iscsi_host_class_initiator_parameters_store);
+
+/*
+ * iscsi_sysfs_operation_store - sysfs callback.
+ *
+ * Usage: echo "#provider #mode #op #1 #2 #3" > ../iscsi/operation
+ *
+ * #provider - "tcp", "iser" or specific vendor
+ * #mode - "session" or "connection"
+ * #op - "add" or "remove"
+ * #1 - is a session/host number to use
+ * #2 - iSCSI CID ("connection" mode only)
+ * #3 - socket's file descriptor ("connection" mode only)
+ *
+ * Examples:
+ *
+ * echo "tcp session add 0" > /sys/class/iscsi/session_operation
+ * echo "tcp connection remove 0 0 5" > /sys/class/iscsi/session_operation
+ */
+static int
+iscsi_host_class_session_operation_store(struct class *class,
+ const char * buf, size_t count)
+{
+ char pname[16], mode[10], op[6];
+ iscsi_provider_t *provider;
+ struct socket *sock;
+ int res, host_no, cid, fd;
+
+ res = sscanf(buf, "%16s %10s %6s %d %d %d", pname, mode, op,
+ &host_no, &cid, &fd);
+ if (res < 4) {
+ printk("wrong format '%s' (%d)\n", buf, res);
+ return -1;
+ } else if ((provider = iscsi_find_provider(pname)) == NULL) {
+ printk("wrong provider '%s'\n", pname);
+ return -1;
+ } else {
+ if (!strcmp("session", mode)) {
+ if (!strcmp("add", op) &&
+ (res = iscsi_add_session(provider, host_no))) {
+ printk("can't create session (%d)\n", res);
+ return -1;
+ } else if (!strcmp("remove", op) &&
+ (res = iscsi_remove_session(provider, host_no))) {
+ printk("can't remove session (%d)\n", res);
+ return -1;
+ } else if (res) {
+ printk("wrong session operation '%s'\n", op);
+ return -1;
+ }
+ } else if (!strcmp("connection", mode)) {
+ if (!strcmp("add", op) &&
+ res == 6 &&
+ (sock = sockfd_lookup(fd, &res)) != NULL &&
+ (res = iscsi_add_connection(provider, host_no,
+ cid, sock))) {
+ printk("can't add connection (%d)\n", res);
+ return -1;
+ } else if (!strcmp("remove", op) &&
+ res == 5 &&
+ (res = iscsi_remove_connection(provider,
+ host_no, cid))) {
+ printk("can't remove connection (%d)\n", res);
+ return -1;
+ } else if (res) {
+ printk("wrong connection operation '%s'\n", op);
+ return -1;
+ }
+ } else {
+ printk("wrong mode '%s'\n", mode);
+ return -1;
+ }
+ }
+ return count;
+}
+static CLASS_ATTR(session_operation, S_IWUSR, NULL,
+ iscsi_host_class_session_operation_store);
+
+static int __init
+iscsi_init(void)
+{
+ int ret;
+
+ ret = class_register(&iscsi_host_class);
+ if (ret) {
+ printk("iSCSI: failed to register iSCSI class (%d).\n", ret);
+ return ret;
+ }
+
+ strcpy(provider_table[ISCSI_PROVIDER_TCP].name,"tcp");
+ ret = iscsi_tcp_register(&provider_table[ISCSI_PROVIDER_TCP].ops,
+ &provider_table[ISCSI_PROVIDER_TCP].caps);
+ if (ret) {
+ class_unregister(&iscsi_host_class);
+ return ret;
+ }
+
+ class_create_file(&iscsi_host_class,
+ &class_attr_initiator_parameters);
+ class_create_file(&iscsi_host_class,
+ &class_attr_session_operation);
+
+ INIT_LIST_HEAD(&initiator.sessions);
+ spin_lock_init(&initiator.sessions_lock);
+
+ return 0;
+}
+
+static void __exit
+iscsi_exit(void)
+{
+ iscsi_cleanup(&provider_table[ISCSI_PROVIDER_TCP]);
+ class_remove_file(&iscsi_host_class,
+ &class_attr_session_operation);
+ class_remove_file(&iscsi_host_class,
+ &class_attr_initiator_parameters);
+ iscsi_tcp_unregister();
+ class_unregister(&iscsi_host_class);
+}
+
+module_init(iscsi_init);
+module_exit(iscsi_exit);
diff --git a/iscsi_control.h b/iscsi_control.h
new file mode 100644
index 0000000..9fd73f6
--- /dev/null
+++ b/iscsi_control.h
@@ -0,0 +1,181 @@
+/*
+ * iSCSI Initiator Control Path
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by linux-storage-devel@lists.sourceforge.net
+ *
+ * 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.
+ */
+
+#ifndef ISCSI_CONTROL_H
+#define ISCSI_CONTROL_H
+
+#include <asm/io.h>
+#include <net/tcp.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/inet.h>
+#include <linux/blkdev.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_request.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi.h>
+
+#include <iscsi_if.h>
+
+#define ISCSI_PORTAL_MAX 32
+#define ISCSI_STRING_MAX 255
+#define ISCSI_NODE_NAME_MAX 255
+#define ISCSI_ALIAS_NAME_MAX 255
+#define ISCSI_PROVIDER_MAX 1
+#define ISCSI_SESSION_ATTRS_MAX 20
+
+#define ISCSI_PROVIDER_TCP 0
+#define ISCSI_PROVIDER_ISER 1
+
+typedef struct iscsi_provider {
+ char name[16];
+ iscsi_ops_t ops;
+ iscsi_caps_t caps;
+} iscsi_provider_t;
+
+typedef enum {
+ ISCSI_EVENT_CMD = 1,
+ ISCSI_EVENT_PDU = 2,
+ ISCSI_EVENT_REOPEN = 3,
+} iscsi_event_e;
+
+struct iscsi_session_ctrl;
+struct iscsi_cnx_ctrl;
+
+typedef struct iscsi_event {
+ /* Event's control info */
+ struct list_head item;
+ iscsi_event_e type;
+ struct iscsi_session_ctrl *session;
+ struct iscsi_cnx_ctrl *cnx;
+
+ /* PDU event type */
+ iscsi_hdr_t rhdr; /* outgoing PDU Header(if any)*/
+ iscsi_hdr_t hdr; /* original PDU Header */
+ char *data; /* original PDU Data */
+} iscsi_event_t;
+
+/* Initial iSCSI Connection-wide Parameters (See RFC 3720) */
+typedef struct iscsi_cnx_params {
+ uint32_t max_recv_dlength;
+ uint32_t max_xmit_dlength;
+ int hdrdgst_en;
+ int datadgst_en;
+} iscsi_cnx_params_t;
+
+typedef struct iscsi_cnx_ctrl {
+ iscsi_cnx_h handle; /* Data-Path conn. handle */
+ struct list_head item; /* item in connection list */
+ struct iscsi_session_ctrl *session; /* associated session */
+ iscsi_cnx_params_t p;
+
+ /* RFC 3720 Network Portal's parameters */
+ unsigned char ipaddr[16];
+ int port;
+ int tag;
+ int cid;
+} iscsi_cnx_ctrl_t;
+
+/* Initial iSCSI Session-wide Parameters (See RFC 3720) */
+typedef struct iscsi_session_params {
+ char initiator_name[ISCSI_NODE_NAME_MAX];
+ char initiator_alias[ISCSI_ALIAS_NAME_MAX];
+ uint8_t isid[7];
+ char target_name[ISCSI_NODE_NAME_MAX];
+ char target_alias[ISCSI_ALIAS_NAME_MAX];
+ char target_portal[ISCSI_PORTAL_MAX];
+ char target_address[ISCSI_PORTAL_MAX];
+ uint16_t tpgt;
+ uint16_t tsih;
+ uint32_t first_burst;
+ uint32_t max_burst;
+ uint16_t max_r2t;
+ uint16_t max_cnx;
+ int erl;
+ int initial_r2t_en;
+ int imm_data_en;
+ int ifmarker_en;
+ int ofmarker_en;
+ int pdu_inorder_en;
+ int dataseq_inorder_en;
+ uint16_t time2wait;
+ uint16_t time2retain;
+ int auth_en;
+ uint32_t cmdsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+} iscsi_session_params_t;
+
+typedef struct iscsi_session_ctrl {
+ iscsi_snx_h handle; /* Data-Path session handle */
+ int host_no; /* SCSI Host number */
+ struct list_head item; /* item in session list */
+ struct class transport_class;
+ iscsi_provider_t *provider;
+ iscsi_cnx_ctrl_t *leadcnx;
+ iscsi_session_params_t p;
+ iscsi_session_state_e state;
+
+ /* RFC 3720 FF and iSCSI Node parameters */
+ int time2wait;
+ int time2retain;
+ char target_name[ISCSI_NODE_NAME_MAX];
+ char target_alias[ISCSI_ALIAS_NAME_MAX];
+ char target_portal[ISCSI_PORTAL_MAX];
+ char target_address[ISCSI_PORTAL_MAX];
+ uint16_t tpgt;
+ uint16_t tsih;
+ uint8_t isid[6];
+ uint16_t max_cnx;
+
+ struct list_head connections; /* connections list */
+ spinlock_t connections_lock;/* Protects connections */
+ struct work_struct eventwork;
+ spinlock_t eventlock;
+ struct list_head eventqueue; /* the events queue */
+ spinlock_t freelock;
+ struct list_head freequeue; /* pending to free queue */
+
+ /* presetns iSCSI session as a transport /sys/class/iscsi_session
+ * associated with SCSI Host */
+ struct scsi_transport_template transportt;
+ struct class_device_attribute *class_attrs[ISCSI_SESSION_ATTRS_MAX + 1];
+} iscsi_session_ctrl_t;
+
+typedef struct iscsi_initiator {
+ iscsi_session_params_t sp; /* initial session params */
+ iscsi_cnx_params_t cp; /* initial connection params */
+ struct list_head sessions; /* sessions list */
+ spinlock_t sessions_lock; /* Protects sessions */
+} iscsi_initiator_t;
+
+/*
+ * Helper for parsing Initiator's initial parameters
+ */
+typedef struct iscsi_param {
+ int type; /* 0 - int, 1 - string */
+ char key[32];
+ void *value;
+ uint32_t min, max; /* range for int */
+ int show;
+} iscsi_param_t;
+
+#endif /* ISCSI_CONTROL_H */
diff --git a/iscsi_if.h b/iscsi_if.h
new file mode 100644
index 0000000..a4bcd4b
--- /dev/null
+++ b/iscsi_if.h
@@ -0,0 +1,147 @@
+/*
+ * iSCSI Initiator for Linux Kernel (iSCSI Control Path)
+ * Copyright (C) 2004 Dmitry Yusupov
+ * maintained by simple-iscsi-devel@lists.sourceforge.net
+ *
+ * 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.
+ */
+
+#ifndef ISCSI_IF_H
+#define ISCSI_IF_H
+
+#include <net/tcp.h>
+#include <iscsi.h>
+
+typedef void* iscsi_snx_h; /* iSCSI Data-Path session handle */
+typedef void* iscsi_cnx_h; /* iSCSI Data-Path connection handle */
+typedef void* iscsi_pdu_h; /* iSCSI Control-Path PDU handle */
+
+typedef enum {
+ ISCSI_STATE_FREE = 1,
+ ISCSI_STATE_LOGGED_IN = 2,
+ ISCSI_STATE_FAILED = 3,
+} iscsi_session_state_e;
+
+typedef enum {
+ ISCSI_PARAM_MAX_RECV_DLENGH = 0,
+ ISCSI_PARAM_MAX_XMIT_DLENGH = 1,
+ ISCSI_PARAM_HDRDGST_EN = 2,
+ ISCSI_PARAM_DATADGST_EN = 3,
+ ISCSI_PARAM_INITIAL_R2T_EN = 4,
+ ISCSI_PARAM_MAX_R2T = 5,
+ ISCSI_PARAM_IMM_DATA_EN = 6,
+ ISCSI_PARAM_FIRST_BURST = 7,
+ ISCSI_PARAM_MAX_BURST = 8,
+ ISCSI_PARAM_PDU_INORDER_EN = 9,
+ ISCSI_PARAM_DATASEQ_INORDER_EN = 10,
+ ISCSI_PARAM_ERL = 11,
+ ISCSI_PARAM_IFMARKER_EN = 12,
+ ISCSI_PARAM_OFMARKER_EN = 13,
+} iscsi_param_e;
+
+#define ISCSI_CTRL_ERR_BASE 100
+#define ISCSI_DP_ERR_BASE 1000
+
+typedef enum {
+ ISCSI_OK = 0,
+
+ ISCSI_ERR_BAD_CNX = ISCSI_CTRL_ERR_BASE + 1,
+
+ ISCSI_ERR_BAD_TARGET = ISCSI_DP_ERR_BASE + 1,
+ ISCSI_ERR_DATASN = ISCSI_DP_ERR_BASE + 2,
+ ISCSI_ERR_DATA_OFFSET = ISCSI_DP_ERR_BASE + 3,
+ ISCSI_ERR_MAX_CMDSN = ISCSI_DP_ERR_BASE + 4,
+ ISCSI_ERR_EXP_CMDSN = ISCSI_DP_ERR_BASE + 5,
+ ISCSI_ERR_BAD_OPCODE = ISCSI_DP_ERR_BASE + 6,
+ ISCSI_ERR_DATALEN = ISCSI_DP_ERR_BASE + 7,
+ ISCSI_ERR_AHSLEN = ISCSI_DP_ERR_BASE + 8,
+ ISCSI_ERR_PROTO = ISCSI_DP_ERR_BASE + 9,
+ ISCSI_ERR_LUN = ISCSI_DP_ERR_BASE + 10,
+ ISCSI_ERR_BAD_ITT = ISCSI_DP_ERR_BASE + 11,
+ ISCSI_ERR_CNX_FAILED = ISCSI_DP_ERR_BASE + 12,
+} iscsi_err_e;
+
+/*
+ * These flags presents iSCSI Data-Path capabilities.
+ */
+#define CAP_RECOVERY_L0 0x1
+#define CAP_RECOVERY_L1 0x2
+#define CAP_RECOVERY_L2 0x4
+#define CAP_MULTI_R2T 0x8
+#define CAP_HDRDGST 0x10
+#define CAP_DATADGST 0x20
+#define CAP_MULTI_CNX 0x40
+#define CAP_TEXT_NEGO 0x80
+
+typedef struct iscsi_caps {
+ int flags;
+ int max_cnx;
+} iscsi_caps_t;
+
+/**
+ * struct iscsi_ops
+ *
+ * @caps: iSCSI Data-Path capabilities
+ * @create_snx: create new iSCSI session object
+ * @destroy_snx: destroy existing iSCSI session object
+ * @create_cnx: create new iSCSI connection using specified transport
+ * @bind_cnx: associate this connection with existing iSCSI session
+ * @destroy_cnx: destroy inactive iSCSI connection
+ * @set_param: set iSCSI Data-Path operational parameter
+ * @start_cnx: set connection to be operational
+ * @stop_cnx: suspend connection
+ * @send_pdu: send iSCSI PDU, Login, Logout, NOP-Out, Reject, Text.
+ *
+ * API provided by generic iSCSI Data Path module
+ */
+typedef struct iscsi_ops {
+
+ iscsi_snx_h (*create_session) (iscsi_snx_h cp_snx,
+ int host_on,
+ struct scsi_transport_template *tt,
+ int initial_cmdsn);
+
+ void (*destroy_session)(iscsi_snx_h dp_snx);
+
+ iscsi_cnx_h (*create_cnx) (iscsi_snx_h dp_snx,
+ iscsi_cnx_h cp_cnx,
+ struct socket *sock,
+ int cid);
+
+ int (*bind_cnx) (iscsi_snx_h dp_snx,
+ iscsi_cnx_h dp_cnx,
+ int is_leading);
+
+ void (*destroy_cnx) (iscsi_cnx_h dp_cnx);
+
+ void (*set_param) (iscsi_cnx_h dp_cnx,
+ iscsi_param_e param,
+ int value);
+
+ int (*start_cnx) (iscsi_cnx_h dp_cnx);
+
+ void (*stop_cnx) (iscsi_cnx_h dp_cnx);
+
+ int (*send_immpdu) (iscsi_cnx_h dp_cnx,
+ iscsi_hdr_t *hdr,
+ char *data);
+} iscsi_ops_t;
+
+int iscsi_control_recv_pdu(iscsi_cnx_h cp_cnx, iscsi_hdr_t *hdr, char *data);
+void iscsi_control_cnx_error(iscsi_cnx_h cp_cnx, int error);
+
+/* FIXME: generic register/unregister interface needed */
+extern int iscsi_tcp_register(iscsi_ops_t *ops, iscsi_caps_t *caps);
+extern void iscsi_tcp_unregister(void);
+
+#endif
diff --git a/iscsi_tcp.c b/iscsi_tcp.c
new file mode 100644
index 0000000..d8cc94d
--- /dev/null
+++ b/iscsi_tcp.c
@@ -0,0 +1,2238 @@
+/*
+ * iSCSI Initiator TCP Data-Path (IDP/TCP)
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by linux-storage-devel@lists.sourceforge.net
+ *
+ * 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.
+ */
+
+#include <iscsi_tcp.h>
+
+MODULE_AUTHOR("Dmitry Yusupov <dmitry_yus@yahoo.com>, "
+ "Alex Aizman <itn780@yahoo.com>");
+MODULE_DESCRIPTION("iSCSI/TCP data-path");
+MODULE_LICENSE("GPL");
+
+/* #define DEBUG_PREV_PDU */
+/* #define DEBUG_TCP */
+/* #define DEBUG_SCSI */
+/* #define DEBUG_ASSERT */
+
+#ifdef DEBUG_TCP
+#define debug_tcp(fmt...) printk("tcp: " fmt)
+#else
+#define debug_tcp(fmt...)
+#endif
+
+#ifdef DEBUG_SCSI
+#define debug_scsi(fmt...) printk("scsi: " fmt)
+#else
+#define debug_scsi(fmt...)
+#endif
+
+#ifdef DEBUG_ASSERT
+#define __BUG_ON BUG_ON
+#else
+#define __BUG_ON(expr)
+#endif
+
+/* global data */
+static kmem_cache_t *taskcache;
+
+/* socket management */
+static int iscsi_tcp_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
+ unsigned int offset, size_t len);
+static void iscsi_tcp_data_ready(struct sock *sk, int flag);
+static void iscsi_tcp_state_change(struct sock *sk);
+static void iscsi_write_space(struct sock *sk);
+static inline int iscsi_sendpage(iscsi_conn_t *conn, iscsi_buf_t *buf,
+ int size);
+static void iscsi_conn_set_callbacks(iscsi_conn_t *conn);
+
+/* data-path */
+static int iscsi_data_recv(iscsi_conn_t *conn);
+static void iscsi_cmd_init(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask,
+ struct scsi_cmnd *sc);
+static void iscsi_unsolicit_data_init(iscsi_conn_t *conn,
+ iscsi_cmd_task_t *ctask);
+static int iscsi_solicit_data_init(iscsi_conn_t *conn,
+ iscsi_cmd_task_t *ctask, iscsi_r2t_info_t *r2t);
+static int iscsi_solicit_data_cont(iscsi_conn_t *conn,
+ iscsi_cmd_task_t *ctask, iscsi_r2t_info_t *r2t, int left);
+static int iscsi_cmd_rsp(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask);
+static int iscsi_data_rsp(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask);
+static int iscsi_r2t_rsp(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask);
+static void iscsi_xmitworker(void *data);
+
+/* page management */
+static inline void iscsi_buf_init_virt(iscsi_buf_t *ibuf, char *vbuf, int size);
+static inline void iscsi_buf_init_sg(iscsi_buf_t *ibuf, struct scatterlist *sg);
+
+/* SCSI Midlayer glue */
+static const char *iscsi_info(struct Scsi_Host *host);
+static int iscsi_proc_info(struct Scsi_Host *host, char *buffer, char **start,
+ off_t offset, int length, int inout);
+
+
+/******************************************************************************
+ * *
+ * G E N E R I C *
+ * Q U E U E M A N A G E M E N T *
+ * *
+ ******************************************************************************/
+
+/*
+ * Insert before consumer pointer
+ */
+static inline void
+iscsi_insert(iscsi_queue_t *queue, void *data)
+{
+ spin_lock_bh(queue->lock);
+ if (queue->cons - 1 < 0) {
+ queue->cons = queue->max - 1;
+ }
+ __BUG_ON(queue->cons - 1 == queue->prod);
+ queue->pool[--queue->cons] = data;
+ spin_unlock_bh(queue->lock);
+}
+
+static inline void
+__enqueue(iscsi_queue_t *queue, void *data)
+{
+ if (queue->prod == queue->max) {
+ queue->prod = 0;
+ }
+ __BUG_ON(queue->prod + 1 == queue->cons);
+ queue->pool[queue->prod++] = data;
+}
+
+/*
+ * Enqueue back to the queue using producer pointer
+ */
+static inline void
+iscsi_enqueue(iscsi_queue_t *queue, void *data)
+{
+ spin_lock_bh(queue->lock);
+ __enqueue(queue, data);
+ spin_unlock_bh(queue->lock);
+}
+
+static inline void*
+__dequeue(iscsi_queue_t *queue)
+{
+ void *data;
+
+ if (queue->cons == queue->prod) {
+ return NULL;
+ }
+ if (queue->cons == queue->max) {
+ queue->cons = 0;
+ }
+ data = queue->pool[queue->cons];
+ if (queue->max > 1)
+ queue->cons++;
+ else
+ queue->prod = 0;
+ return data;
+}
+
+/*
+ * Dequeue from the queue using consumer pointer
+ *
+ * Returns void* or NULL on empty queue
+ */
+static inline void*
+iscsi_dequeue(iscsi_queue_t *queue)
+{
+ void *data;
+
+ spin_lock_bh(queue->lock);
+ data = __dequeue(queue);
+ spin_unlock_bh(queue->lock);
+
+ return data;
+}
+
+
+/******************************************************************************
+ * *
+ * T C P / I P *
+ * S O C K E T M A N A G E M E N T *
+ * *
+ ******************************************************************************/
+
+#define iscsi_conn_get(rdd) (iscsi_conn_t*)(rdd)->arg.data
+#define iscsi_conn_set(rdd, conn) (rdd)->arg.data = conn
+
+/*
+ * TCP record receive routine
+ */
+static int
+iscsi_tcp_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
+ unsigned int offset, size_t len)
+{
+ int rc;
+ iscsi_conn_t *conn = iscsi_conn_get(rd_desc);
+ iscsi_session_t *session = conn->session;
+ int start = skb_headlen(skb);
+
+ if ((conn->in.copy = start - offset) <= 0) {
+ printk("iSCSI: in %d < 0!!!\n", conn->in.copy);
+ __BUG_ON(1);
+ return len;
+ }
+ debug_tcp("in %d bytes\n", conn->in.copy);
+
+ /*
+ * Save current SKB and its offset in the corresponding
+ * connection context.
+ */
+ conn->in.offset = offset;
+ conn->in.skb = skb;
+ conn->in.len = conn->in.copy;
+
+more:
+ conn->in.copied = 0;
+ rc = 0;
+
+ if (conn->in_progress == IN_PROGRESS_WAIT_HEADER ||
+ conn->in_progress == IN_PROGRESS_HEADER_GATHER) {
+ iscsi_hdr_t *hdr;
+ iscsi_cmd_task_t *ctask;
+
+ /*
+ * Extract PDU Header
+ */
+ if (conn->in.copy >= conn->hdr_size &&
+ conn->in_progress != IN_PROGRESS_HEADER_GATHER) {
+ /*
+ * Zero-copy PDU Header. Using connection's context
+ * to store pointer for incomming PDU Header.
+ */
+ if (skb_shinfo(skb)->frag_list == NULL &&
+ !skb_shinfo(skb)->nr_frags) {
+ conn->in.hdr = (iscsi_hdr_t *)
+ ((char*)skb->data + conn->in.offset);
+ } else {
+ (void)skb_copy_bits(skb, conn->in.offset,
+ &conn->hdr, conn->hdr_size);
+ conn->in.hdr = &conn->hdr;
+ }
+ conn->in.offset += conn->hdr_size;
+ conn->in.copy -= conn->hdr_size;
+ conn->in.hdr_offset = 0;
+ } else {
+ int copylen;
+
+ /*
+ * Oops... got PDU Header scattered accross SKB's.
+ * Not much we can do but only copy Header into
+ * the connection PDU Header placeholder.
+ */
+ if (conn->in_progress == IN_PROGRESS_WAIT_HEADER) {
+ (void)skb_copy_bits(skb, conn->in.offset,
+ &conn->hdr, conn->in.copy);
+ conn->in_progress = IN_PROGRESS_HEADER_GATHER;
+ conn->in.hdr_offset = conn->in.copy;
+ conn->in.offset += conn->in.copy;
+ conn->in.copy = 0;
+ debug_tcp("PDU gather #1 %d bytes!\n",
+ conn->in.hdr_offset);
+ goto nomore;
+ }
+
+ copylen = conn->hdr_size - conn->in.hdr_offset;
+ if (copylen > conn->in.copy) {
+ /* FIXME: recover error */
+ printk("iSCSI: PDU gather failed! "
+ "copylen %d conn->in.copy %d\n",
+ copylen, conn->in.copy);
+ __BUG_ON(1);
+ return 0;
+ }
+ debug_tcp("PDU gather #2 %d bytes!\n", copylen);
+
+ (void)skb_copy_bits(skb, conn->in.offset,
+ (char*)&conn->hdr + conn->in.hdr_offset, copylen);
+ conn->in.offset += copylen;
+ conn->in.copy -= copylen;
+ conn->in.hdr_offset = 0;
+ conn->in.hdr = &conn->hdr;
+ conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ }
+
+ /*
+ * Incomming PDU Header processing.
+ * At this stage we process only common parts, like ITT,
+ * DataSegmentLength, etc.
+ */
+
+ hdr = conn->in.hdr;
+
+ if (conn->hdrdgst_en) {
+ /* FIXME: check HeaderDigest */
+ __BUG_ON(1);
+ return 0;
+ }
+
+ /* check for malformed pdu */
+ conn->in.datalen = ntoh24(hdr->dlength);
+ if (conn->in.datalen > conn->max_recv_dlength) {
+ /* FIXME: recover error */
+ printk("iSCSI: datalen %d > %d\n", conn->in.datalen,
+ conn->max_recv_dlength);
+ __BUG_ON(1);
+ return 0;
+ }
+ conn->data_copied = 0;
+
+ /* calculate padding */
+ conn->in.padding = conn->in.datalen & (ISCSI_PAD_LEN-1);
+ if (conn->in.padding) {
+ conn->in.padding = ISCSI_PAD_LEN - conn->in.padding;
+ debug_scsi("padding %d bytes\n", conn->in.padding);
+ }
+
+ /* respect AHS */
+ conn->in.ahslen = hdr->hlength*(4*sizeof(__u16));
+ conn->in.offset += conn->in.ahslen;
+ conn->in.copy -= conn->in.ahslen;
+ /* FIXME: if ahslen too big... need special case to handle */
+ __BUG_ON(conn->in.copy < 0);
+
+ /* save opcode & itt for later processing */
+ conn->in.opcode = hdr->opcode;
+ conn->prev_itt = conn->in.itt;
+ conn->in.itt = ntohl(hdr->itt);
+
+ debug_tcp("opcode 0x%x offset %d copy %d ahslen %d "
+ "datalen %d\n",
+ hdr->opcode, conn->in.offset, conn->in.copy,
+ conn->in.ahslen, conn->in.datalen);
+
+ if (conn->in.itt < session->cmds_max) {
+ int cstate;
+
+ ctask = (iscsi_cmd_task_t *)session->cmds[conn->in.itt];
+ conn->in.ctask = ctask;
+ cstate = ctask->in_progress & IN_PROGRESS_OP_MASK;
+
+ debug_scsi(
+ "rsp [op 0x%x cid %d sc %lx itt 0x%x len %d]\n",
+ hdr->opcode, conn->id, (long)ctask->sc, ctask->itt,
+ ctask->total_length);
+
+ switch(conn->in.opcode) {
+ case ISCSI_OP_SCSI_CMD_RSP:
+ if (conn->prev_itt == conn->in.itt) {
+ /* FIXME: recover error */
+ printk("iSCSI: dup rsp [itt 0x%x]\n",
+ conn->in.itt);
+ __BUG_ON(1);
+ return 0;
+ }
+ if (cstate == IN_PROGRESS_READ) {
+ if (!conn->in.datalen) {
+ rc = iscsi_cmd_rsp(conn, ctask);
+ } else {
+ /* have sense or response data
+ * copying PDU Header to the
+ * connection's header
+ * placeholder */
+ memcpy(&conn->hdr, conn->in.hdr,
+ sizeof(iscsi_hdr_t));
+ conn->data_copied = 0;
+ }
+ } else if (cstate == IN_PROGRESS_WRITE) {
+ rc = iscsi_cmd_rsp(conn, ctask);
+ }
+ break;
+ case ISCSI_OP_SCSI_DATA_IN:
+ /* save flags for nonexception status */
+ conn->in.flags = hdr->flags;
+ /* save cmd_status for sense data */
+ conn->in.cmd_status =
+ ((iscsi_data_rsp_t*)hdr)->cmd_status;
+ rc = iscsi_data_rsp(conn, ctask);
+ break;
+ case ISCSI_OP_R2T:
+ rc = iscsi_r2t_rsp(conn, ctask);
+ break;
+ case ISCSI_OP_NOOP_IN:
+ case ISCSI_OP_TEXT_RSP:
+ case ISCSI_OP_LOGOUT_RSP:
+ case ISCSI_OP_ASYNC_EVENT:
+ case ISCSI_OP_REJECT_MSG:
+ if (!conn->in.datalen) {
+ rc = iscsi_control_recv_pdu(
+ conn->handle, hdr, NULL);
+ } else {
+ conn->data_copied = 0;
+ }
+ break;
+ default:
+ rc = ISCSI_ERR_BAD_OPCODE;
+ break;
+ }
+ } else if (conn->in.itt >= ISCSI_IMM_ITT_OFFSET &&
+ conn->in.itt < ISCSI_IMM_ITT_OFFSET +
+ session->imm_max) {
+ /* FIXME: implement */
+ } else if (conn->in.itt == ISCSI_RESERVED_TAG) {
+ conn->data_copied = 0;
+ if (conn->in.opcode == ISCSI_OP_NOOP_IN &&
+ !conn->in.datalen) {
+ rc = iscsi_control_recv_pdu(
+ conn->handle, hdr, NULL);
+ } else {
+ rc = ISCSI_ERR_BAD_OPCODE;
+ }
+ } else {
+ rc = ISCSI_ERR_BAD_ITT;
+ }
+
+#ifdef DEBUG_PREV_PDU
+ memcpy(&conn->prev_hdr, hdr, conn->hdr_size);
+#endif
+
+ if (!rc && conn->in.datalen) {
+ conn->in_progress = IN_PROGRESS_DATA_RECV;
+ } else if (rc) {
+ /* FIXME: recover error */
+ printk("iSCSI: bad hdr rc (%d)\n", rc);
+ __BUG_ON(1);
+ return 0;
+ }
+ }
+
+ if (conn->in_progress == IN_PROGRESS_DATA_RECV &&
+ conn->in.copy) {
+
+ debug_tcp("data_recv offset %d copy %d\n",
+ conn->in.offset, conn->in.copy);
+
+ if ((rc = iscsi_data_recv(conn))) {
+ if (rc == -EAGAIN) {
+ rd_desc->count = conn->in.datalen -
+ conn->in.ctask->sent;
+ goto again;
+ }
+ /* FIXME: recover error */
+ printk("iSCSI: bad data rc (%d)\n", rc);
+ __BUG_ON(1);
+ return 0;
+ }
+ conn->in.copy -= conn->in.padding;
+ conn->in.offset += conn->in.padding;
+ conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ }
+
+ debug_tcp("f, processed %d from out of %d padding %d\n",
+ conn->in.offset - offset, len, conn->in.padding);
+ __BUG_ON(conn->in.offset - offset > len);
+
+ if (conn->in.offset - offset != len) {
+ debug_tcp("continue to process %d bytes\n",
+ len - (conn->in.offset - offset));
+ goto more;
+ }
+
+nomore:
+ __BUG_ON(conn->in.offset - offset == 0);
+ return conn->in.offset - offset;
+
+again:
+ debug_tcp("c, processed %d from out of %d rd_desc_cnt %d\n",
+ conn->in.offset - offset, len, rd_desc->count);
+ __BUG_ON(conn->in.offset - offset == 0);
+ __BUG_ON(conn->in.offset - offset > len);
+
+ return conn->in.offset - offset;
+}
+
+static void
+iscsi_tcp_data_ready(struct sock *sk, int flag)
+{
+ iscsi_conn_t *conn = (iscsi_conn_t*)sk->sk_user_data;
+ read_descriptor_t rd_desc;
+
+ read_lock(&sk->sk_callback_lock);
+
+ /* We use rd_desc to pass 'conn' to iscsi_tcp_data_recv */
+ iscsi_conn_set(&rd_desc, conn);
+ rd_desc.count = 0;
+ tcp_read_sock(sk, &rd_desc, iscsi_tcp_data_recv);
+
+ read_unlock(&sk->sk_callback_lock);
+}
+
+static void
+iscsi_tcp_state_change(struct sock *sk)
+{
+ iscsi_conn_t *conn = (iscsi_conn_t*)sk->sk_user_data;
+
+ if (sk->sk_state == TCP_CLOSE_WAIT) {
+ conn->session->state = ISCSI_STATE_FAILED;
+ wmb();
+ iscsi_control_cnx_error(conn->handle, ISCSI_ERR_CNX_FAILED);
+ }
+ conn->old_state_change(sk);
+}
+
+/*
+ * Called when more output buffer space is available for this socket.
+ */
+static void
+iscsi_write_space(struct sock *sk)
+{
+ iscsi_conn_t *conn = (iscsi_conn_t*)sk->sk_user_data;
+ conn->old_write_space(sk);
+ debug_tcp("iscsi_write_space\n");
+}
+
+static void
+iscsi_conn_set_callbacks(iscsi_conn_t *conn)
+{
+ struct sock *sk = conn->sock->sk;
+
+ /* assign new callbacks */
+ write_lock_bh(&sk->sk_callback_lock);
+ sk->sk_user_data = conn;
+ conn->old_data_ready = sk->sk_data_ready;
+ conn->old_state_change = sk->sk_state_change;
+ 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(iscsi_conn_t *conn)
+{
+ struct sock *sk = conn->sock->sk;
+
+ /* restore old callbacks */
+ write_lock_bh(&sk->sk_callback_lock);
+ sk->sk_user_data = NULL;
+ sk->sk_data_ready = conn->old_data_ready;
+ sk->sk_state_change = conn->old_state_change;
+ sk->sk_write_space = conn->old_write_space;
+ sk->sk_no_check = 0;
+ write_unlock_bh(&sk->sk_callback_lock);
+}
+
+/*
+ * iscsi_sendpage - send one page of iSCSI Data-Out buffer
+ *
+ * This function used for fast path send. Outgoing data presents
+ * as iscsi_buf_t helper structure.
+ */
+static inline int
+iscsi_sendpage(iscsi_conn_t *conn, iscsi_buf_t *buf, int size)
+{
+ struct socket *sk = conn->sock;
+ int flags = MSG_DONTWAIT;
+ int res;
+ int offset;
+
+ __BUG_ON(buf->sent + size > buf->size);
+
+ if (buf->sent + size != buf->size) {
+ flags |= MSG_MORE;
+ }
+
+ offset = buf->offset + buf->sent;
+
+ debug_tcp("sendpage %lx %d bytes at offset %d sent %d\n",
+ (long)page_address(buf->page), size, offset, buf->sent);
+
+ // tcp_sendpage, do_tcp_sendpages, tcp_sendmsg
+ res = sk->ops->sendpage(sk, buf->page, offset, size, flags);
+
+ if (res > 0) {
+ buf->sent += res;
+ }
+
+ return res;
+}
+
+/*
+ * iscsi_tcp_copy - copy skb bits to the destanation buffer
+ *
+ * Function using iSCSI connection to keep copy counters and states.
+ */
+static inline int
+iscsi_tcp_copy(iscsi_conn_t *conn, void *buf, int buf_size)
+{
+ int sk_left = conn->in.len - conn->in.offset;
+ int buf_left = buf_size - conn->data_copied;
+ int size = min(sk_left, buf_left);
+ int rc;
+
+ debug_tcp("tcp_copy %d bytes at offset %d copied %d\n",
+ size, conn->in.offset, conn->data_copied);
+
+ rc = skb_copy_bits(conn->in.skb, conn->in.offset,
+ (char*)buf + conn->data_copied, size);
+ __BUG_ON(rc);
+
+ conn->in.offset += size;
+ conn->in.copy -= size;
+ conn->in.copied += size;
+ conn->data_copied += size;
+
+ if (buf_size != conn->data_copied) {
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+/*
+ * iscsi_ctask_copy - copy skb bits to the destanation cmd task
+ *
+ * Function using iSCSI connection to keep copy counters and states.
+ * In addition, cmd task keeps total "sent" counter across multiple Data-In's.
+ */
+static inline int
+iscsi_ctask_copy(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask,
+ void *buf, int buf_size)
+{
+ int sk_left = conn->in.len - conn->in.offset;
+ int buf_left = buf_size - conn->data_copied;
+ int size = min(sk_left, buf_left);
+ int rc;
+
+ debug_tcp("ctask_copy %d bytes at offset %d copied %d\n",
+ size, conn->in.offset, conn->in.copied);
+
+ rc = skb_copy_bits(conn->in.skb, conn->in.offset,
+ (char*)buf + conn->data_copied, size);
+ __BUG_ON(rc);
+
+ if (conn->in.opcode == ISCSI_OP_SCSI_DATA_IN) {
+ __BUG_ON(ctask->sent + size > ctask->total_length);
+#ifdef DEBUG_PREV_PDU
+ memcpy(conn->data + ctask->sent, buf, size);
+#endif
+ }
+
+ conn->in.offset += size;
+ conn->in.copy -= size;
+ conn->in.copied += size;
+ conn->data_copied += size;
+ ctask->sent += size;
+
+ if (buf_size != conn->data_copied) {
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+
+/******************************************************************************
+ * *
+ * i S C S I D A T A - P A T H *
+ * *
+ ******************************************************************************/
+
+static inline void
+iscsi_buf_init_virt(iscsi_buf_t *ibuf, char *vbuf, int size)
+{
+ ibuf->page = virt_to_page(vbuf);
+ ibuf->offset = offset_in_page(vbuf);
+ ibuf->size = size;
+ ibuf->sent = 0;
+}
+
+static inline void
+iscsi_buf_init_sg(iscsi_buf_t *ibuf, struct scatterlist *sg)
+{
+ ibuf->page = sg->page;
+ ibuf->offset = sg->offset;
+ ibuf->size = sg->length;
+ ibuf->sent = 0;
+}
+
+/*
+ * iscsi_solicit_data_cont - initialize next Data-Out
+ *
+ * Initialize first Data-Out within this R2T sequence and continue
+ * to process next Scatter-Gather element(if any) of this SCSI command.
+ */
+static int
+iscsi_solicit_data_cont(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask,
+ iscsi_r2t_info_t *r2t, int left)
+{
+ iscsi_data_t *hdr;
+ iscsi_data_task_t *dtask;
+ iscsi_buf_t *headbuf = &r2t->headbuf;
+ struct scsi_cmnd *sc = ctask->sc;
+ int new_offset;
+
+ dtask = mempool_alloc(ctask->datapool, GFP_ATOMIC);
+ if (dtask == NULL) {
+ printk("iSCSI: datapool: out of memory itt 0x%x\n",
+ ctask->itt);
+ return -ENOMEM;
+ }
+ hdr = &dtask->hdr;
+ hdr->flags = 0;
+ hdr->rsvd2[0] = hdr->rsvd2[1] = hdr->rsvd3 =
+ hdr->rsvd4 = hdr->rsvd5 = hdr->rsvd6 = 0;
+ hdr->ttt = r2t->ttt;
+ hdr->datasn = htonl(ctask->solicit_count);
+ ctask->solicit_count++;
+ hdr->opcode = ISCSI_OP_SCSI_DATA_OUT;
+ memset(hdr->lun, 0, 8);
+ hdr->lun[1] = ctask->hdr.lun[1];
+ hdr->itt = ctask->hdr.itt;
+ hdr->exp_statsn = htonl(conn->exp_statsn);
+ new_offset = r2t->data_offset + r2t->sent;
+ hdr->offset = htonl(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;
+ }
+ iscsi_buf_init_virt(headbuf, (char*)hdr, sizeof(iscsi_hdr_t));
+
+ if (sc->use_sg) {
+ __BUG_ON(ctask->bad_sg == r2t->sg);
+ iscsi_buf_init_sg(&r2t->sendbuf, r2t->sg);
+ r2t->sg += 1;
+ } else {
+ iscsi_buf_init_virt(&ctask->sendbuf,
+ (char*)sc->request_buffer + new_offset,
+ r2t->data_count);
+ }
+
+ /* no lock needed since we have dedicated datapool per iSCSI task */
+ list_add(&dtask->item, &ctask->dataqueue);
+ return 0;
+}
+
+/*
+ * iscsi_solicit_data_init - initialize first Data-Out
+ *
+ * Initialize first Data-Out within this R2T sequence and finds
+ * proper data_offset within this SCSI command.
+ */
+static int
+iscsi_solicit_data_init(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask,
+ iscsi_r2t_info_t *r2t)
+{
+ iscsi_data_t *hdr;
+ iscsi_data_task_t *dtask;
+ iscsi_buf_t *headbuf = &r2t->headbuf;
+ struct scsi_cmnd *sc = ctask->sc;
+
+ dtask = mempool_alloc(ctask->datapool, GFP_ATOMIC);
+ if (dtask == NULL) {
+ printk("iSCSI: datapool: out of memory itt 0x%x\n",
+ ctask->itt);
+ return -ENOMEM;
+ }
+ hdr = &dtask->hdr;
+ hdr->flags = 0;
+ hdr->rsvd2[0] = hdr->rsvd2[1] = hdr->rsvd3 =
+ hdr->rsvd4 = hdr->rsvd5 = hdr->rsvd6 = 0;
+ hdr->ttt = r2t->ttt;
+ hdr->datasn = htonl(ctask->solicit_count);
+ ctask->solicit_count++;
+ hdr->opcode = ISCSI_OP_SCSI_DATA_OUT;
+ memset(hdr->lun, 0, 8);
+ hdr->lun[1] = ctask->hdr.lun[1];
+ hdr->itt = ctask->hdr.itt;
+ hdr->exp_statsn = htonl(conn->exp_statsn);
+ hdr->offset = htonl(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;
+ } else {
+ hton24(hdr->dlength, r2t->data_length);
+ r2t->data_count = r2t->data_length;
+ hdr->flags = ISCSI_FLAG_CMD_FINAL;
+ }
+
+ r2t->sent = 0;
+
+ iscsi_buf_init_virt(headbuf, (char*)hdr, sizeof(iscsi_hdr_t));
+
+ if (sc->use_sg) {
+ int i, sg_count = 0;
+ struct scatterlist *sg = (struct scatterlist *)
+ sc->request_buffer;
+ r2t->sg = NULL;
+ for (i=0; i<sc->use_sg; i++, sg += 1) { /* FIXME: prefetch ? */
+ if (sg_count + sg->length > r2t->data_offset) {
+ int page_offset = r2t->data_offset - sg_count;
+ /* sg page found! */
+ iscsi_buf_init_sg(&r2t->sendbuf, sg);
+ r2t->sendbuf.offset += page_offset;
+ r2t->sendbuf.size -= page_offset;
+ r2t->sg = sg + 1;
+ break;
+ }
+ sg_count += sg->length;
+ }
+ __BUG_ON(r2t->sg == NULL);
+ } else {
+ iscsi_buf_init_virt(&ctask->sendbuf,
+ (char*)sc->request_buffer + r2t->data_offset,
+ r2t->data_count);
+ }
+
+ /* no lock needed since we have dedicated datapool per iSCSI task */
+ list_add(&dtask->item, &ctask->dataqueue);
+ return 0;
+}
+
+static void
+iscsi_unsolicit_data_init(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask)
+{
+ iscsi_data_t *hdr;
+ iscsi_buf_t *headbuf;
+
+ hdr = (iscsi_data_t *)
+ &ctask->unsolicit_data[ctask->unsolicit_count]->hdr;
+ headbuf = &ctask->headbuf;
+ hdr->flags = ISCSI_FLAG_CMD_FINAL;
+ hdr->rsvd2[0] = hdr->rsvd2[1] = hdr->rsvd3 =
+ hdr->rsvd4 = hdr->rsvd5 = hdr->rsvd6 = 0;
+ hdr->ttt = ISCSI_RESERVED_TAG;
+ hdr->datasn = htonl(ctask->unsolicit_count);
+ ctask->unsolicit_count++;
+ hdr->opcode = ISCSI_OP_SCSI_DATA_OUT;
+ hdr->lun[1] = ctask->hdr.lun[1];
+ hdr->itt = ctask->hdr.itt;
+ hdr->exp_statsn = htonl(conn->exp_statsn);
+ hdr->offset = htonl(ctask->total_length - ctask->data_count);
+ if (ctask->data_count > conn->max_xmit_dlength) {
+ hton24(hdr->dlength, conn->max_xmit_dlength);
+ } else {
+ hton24(hdr->dlength, ctask->data_count);
+ }
+
+ ctask->sent = 0;
+
+ iscsi_buf_init_virt(headbuf, (char*)hdr, sizeof(iscsi_hdr_t));
+}
+
+/*
+ * Initialize iSCSI SCSI_READ or SCSI_WRITE commands
+ */
+static void
+iscsi_cmd_init(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask,
+ struct scsi_cmnd *sc)
+{
+ iscsi_session_t *session = conn->session;
+
+ /*
+ * R2T's protected by the owner - connection.
+ */
+ ctask->r2tpool.lock = &conn->lock;
+ ctask->r2tqueue.lock = &conn->lock;
+
+ ctask->sc = sc;
+ ctask->conn = conn;
+ ctask->hdr.opcode = ISCSI_OP_SCSI_CMD;
+ ctask->hdr.flags = ISCSI_ATTR_SIMPLE;
+ if (sc->sc_data_direction == DMA_TO_DEVICE) {
+ ctask->hdr.flags |= ISCSI_FLAG_CMD_WRITE;
+ } else if (sc->sc_data_direction == DMA_FROM_DEVICE) {
+ ctask->hdr.flags |= ISCSI_FLAG_CMD_READ | ISCSI_FLAG_CMD_FINAL;
+ } else {
+ /* assume read op */
+ ctask->hdr.flags |= ISCSI_FLAG_CMD_FINAL;
+ }
+ ctask->hdr.lun[1] = sc->device->lun;
+ ctask->hdr.itt = htonl(ctask->itt);
+ ctask->hdr.data_length = htonl(sc->request_bufflen);
+ ctask->hdr.cmdsn = htonl(session->cmdsn); session->cmdsn++;
+ ctask->hdr.exp_statsn = htonl(conn->exp_statsn);
+ memcpy(ctask->hdr.cdb, sc->cmnd, sc->cmd_len);
+ memset(&ctask->hdr.cdb[sc->cmd_len], 0, MAX_COMMAND_SIZE - sc->cmd_len);
+
+ ctask->in_progress = IN_PROGRESS_IDLE;
+ ctask->sent = 0;
+ ctask->datasn = 0;
+ ctask->sg_count = 0;
+
+ iscsi_buf_init_virt(&ctask->headbuf, (char*)&ctask->hdr,
+ sizeof(iscsi_hdr_t));
+
+ if (sc->sc_data_direction == DMA_TO_DEVICE) {
+ if (sc->use_sg) {
+ struct scatterlist *sg = (struct scatterlist *)
+ sc->request_buffer;
+ iscsi_buf_init_sg(&ctask->sendbuf, &sg[0]);
+ ctask->sg = sg + 1;
+ ctask->bad_sg = sg + sc->use_sg;
+ } else {
+ iscsi_buf_init_virt(&ctask->sendbuf, sc->request_buffer,
+ sc->request_bufflen);
+ __BUG_ON(sc->request_bufflen > PAGE_SIZE);
+ }
+ ctask->in_progress = IN_PROGRESS_WRITE |
+ IN_PROGRESS_BEGIN_WRITE;
+ } else {
+ ctask->in_progress = IN_PROGRESS_READ;
+ }
+
+ ctask->total_length = sc->request_bufflen;
+
+ if (session->imm_data_en &&
+ ctask->hdr.flags & ISCSI_FLAG_CMD_WRITE) {
+ if (ctask->total_length > session->first_burst) {
+ ctask->imm_count =min(session->first_burst,
+ conn->max_xmit_dlength);
+ ctask->data_count =
+ ctask->total_length - ctask->imm_count;
+ if (session->first_burst > conn->max_xmit_dlength) {
+ ctask->data_pdu_count = session->first_burst /
+ conn->max_xmit_dlength;
+ } else {
+ ctask->data_pdu_count = 0;
+ }
+ } else {
+ ctask->imm_count = ctask->total_length;
+ ctask->data_count = 0;
+ ctask->data_pdu_count = 0;
+ ctask->hdr.flags |= ISCSI_FLAG_CMD_FINAL;
+ }
+ hton24(ctask->hdr.dlength, ctask->imm_count);
+ } else {
+ ctask->data_count = 0;
+ ctask->imm_count = 0;
+ zero_data(ctask->hdr.dlength);
+ }
+ if (conn->hdrdgst_en) {
+ printk("iSCSI: not implemented\n");
+ __BUG_ON(1);
+ }
+
+ ctask->in_progress |= IN_PROGRESS_HEAD;
+}
+
+static int
+iscsi_data_recv(iscsi_conn_t *conn)
+{
+ iscsi_session_t *session = conn->session;
+ int rc = 0;
+
+ switch(conn->in.opcode) {
+ case ISCSI_OP_SCSI_DATA_IN: {
+ iscsi_cmd_task_t *ctask = conn->in.ctask;
+ struct scsi_cmnd *sc = ctask->sc;
+ __BUG_ON(!(ctask->in_progress & IN_PROGRESS_READ &&
+ conn->in_progress == IN_PROGRESS_DATA_RECV));
+ /*
+ * Copying Data-In to the Scsi_Cmnd
+ */
+ if (sc->use_sg) {
+ int i;
+ struct scatterlist *sg = (struct scatterlist *)
+ sc->request_buffer;
+ for (i=ctask->sg_count; i<sc->use_sg; i++) {
+ char *dest =(char*)page_address(sg[i].page) +
+ sg[i].offset;
+ if (iscsi_ctask_copy(conn, ctask, dest, sg->length)) {
+ rc = -EAGAIN;
+ goto exit;
+ }
+ conn->data_copied = 0;
+ ctask->sg_count++;
+ }
+ } else {
+ if (iscsi_ctask_copy(conn, ctask, sc->request_buffer,
+ sc->request_bufflen)) {
+ rc = -EAGAIN;
+ goto exit;
+ }
+ }
+
+ /* check for nonexceptional status */
+ if (conn->in.flags & ISCSI_FLAG_DATA_STATUS) {
+ unsigned long flags;
+ ctask->in_progress = IN_PROGRESS_IDLE;
+ sc->result = conn->in.cmd_status;
+ debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ (long)sc, sc->result, ctask->itt);
+ spin_lock_irqsave(session->host->host_lock, flags);
+ __enqueue(&session->cmdpool, ctask);
+ sc->scsi_done(sc);
+ spin_unlock_irqrestore(session->host->host_lock, flags);
+ }
+ }
+ break;
+ case ISCSI_OP_SCSI_CMD_RSP: {
+ /*
+ * SCSI Sense Data.
+ * copying the whole Data Segment to the connection's data
+ * placeholder.
+ */
+ if (iscsi_tcp_copy(conn, conn->data, conn->in.datalen)) {
+ rc = -EAGAIN;
+ goto exit;
+ }
+
+ /*
+ * Check for sense data
+ */
+ conn->in.hdr = &conn->hdr;
+ conn->senselen = ntohs(*(__u16*)conn->data);
+ rc = iscsi_cmd_rsp(conn, conn->in.ctask);
+ }
+ break;
+ case ISCSI_OP_NOOP_IN: {
+ /*
+ * Copying "Ping" Data to the connection's data
+ * placeholder
+ */
+ if (iscsi_tcp_copy(conn, conn->data, conn->in.datalen)) {
+ rc = -EAGAIN;
+ goto exit;
+ }
+
+ rc = iscsi_control_recv_pdu(conn->handle,
+ conn->in.hdr, conn->data);
+ }
+ default:
+ __BUG_ON(1);
+ }
+exit:
+ return rc;
+}
+
+/*
+ * iSCSI R2T Response processing
+ */
+static int
+iscsi_r2t_rsp(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask)
+{
+ int rc;
+ iscsi_r2t_info_t *r2t;
+ iscsi_session_t *session = conn->session;
+ iscsi_r2t_rsp_t *rhdr = (iscsi_r2t_rsp_t *)conn->in.hdr;
+ int max_cmdsn = ntohl(rhdr->max_cmdsn);
+ int exp_cmdsn = ntohl(rhdr->exp_cmdsn);
+
+ __BUG_ON(ctask->data_pdu_count);
+
+ if (conn->in.ahslen) {
+ return ISCSI_ERR_AHSLEN;
+ }
+ if (conn->in.datalen) {
+ return ISCSI_ERR_DATALEN;
+ }
+
+ if (max_cmdsn < exp_cmdsn - 1) {
+ return ISCSI_ERR_MAX_CMDSN;
+ }
+ session->max_cmdsn = max_cmdsn;
+ session->exp_cmdsn = exp_cmdsn;
+ conn->exp_statsn = ntohl(rhdr->statsn) + 1;
+
+ /* FIXME: detect missing R2T by using R2TSN */
+
+ /* fill-in new R2T associated with the task */
+ r2t = iscsi_dequeue(&ctask->r2tpool);
+ if (r2t == NULL) {
+ return ISCSI_ERR_PROTO;
+ }
+ r2t->data_length = ntohl(rhdr->data_length);
+ if (r2t->data_length == 0 ||
+ r2t->data_length > session->max_burst) {
+ iscsi_enqueue(&ctask->r2tpool, r2t);
+ return ISCSI_ERR_DATALEN;
+ }
+ if (ctask->hdr.lun[1] != rhdr->lun[1]) {
+ iscsi_enqueue(&ctask->r2tpool, r2t);
+ return ISCSI_ERR_LUN;
+ }
+ r2t->data_offset = ntohl(rhdr->data_offset);
+ if (r2t->data_offset + r2t->data_length > ctask->total_length) {
+ iscsi_enqueue(&ctask->r2tpool, r2t);
+ return ISCSI_ERR_DATALEN;
+ }
+ r2t->ttt = rhdr->ttt; /* no flip */
+
+ if ((rc = iscsi_solicit_data_init(conn, ctask, r2t))) {
+ iscsi_enqueue(&ctask->r2tpool, r2t);
+ return rc;
+ }
+
+ iscsi_enqueue(&ctask->r2tqueue, r2t);
+ ctask->in_progress |= IN_PROGRESS_SOLICIT_HEAD;
+
+ iscsi_enqueue(&conn->xmitqueue, ctask);
+ schedule_work(&conn->xmitwork);
+
+ return 0;
+}
+
+/*
+ * SCSI Command Response processing
+ */
+static int
+iscsi_cmd_rsp(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask)
+{
+ int rc = 0;
+ iscsi_cmd_rsp_t *rhdr = (iscsi_cmd_rsp_t *)conn->in.hdr;
+ iscsi_session_t *session = conn->session;
+ struct scsi_cmnd *sc = ctask->sc;
+ int max_cmdsn = ntohl(rhdr->max_cmdsn);
+ int exp_cmdsn = ntohl(rhdr->exp_cmdsn);
+ unsigned long flags;
+
+ if (max_cmdsn < exp_cmdsn - 1) {
+ rc = ISCSI_ERR_MAX_CMDSN;
+ sc->result = host_byte(DID_ERROR);
+ goto fault;
+ }
+ session->max_cmdsn = max_cmdsn;
+ session->exp_cmdsn = exp_cmdsn;
+ conn->exp_statsn = ntohl(rhdr->statsn) + 1;
+
+ sc->result = host_byte(DID_OK) | status_byte(rhdr->cmd_status);
+
+ if (rhdr->response == ISCSI_STATUS_CMD_COMPLETED) {
+ if (status_byte(rhdr->cmd_status) == CHECK_CONDITION &&
+ conn->senselen) {
+ int sensecopy = min(conn->senselen,
+ SCSI_SENSE_BUFFERSIZE);
+ memcpy(sc->sense_buffer, conn->data, sensecopy);
+ debug_scsi("copied %d bytes of sense\n", sensecopy);
+ }
+
+ if (sc->sc_data_direction != DMA_TO_DEVICE ) {
+ if (rhdr->flags & ISCSI_FLAG_CMD_UNDERFLOW) {
+ int res_count = ntohl(rhdr->residual_count);
+ if( res_count > 0 &&
+ res_count <= sc->request_bufflen ) {
+ sc->resid = res_count;
+ } else {
+ sc->result =
+ host_byte(DID_BAD_TARGET) |
+ status_byte(rhdr->cmd_status);
+ rc = ISCSI_ERR_BAD_TARGET;
+ goto fault;
+ }
+ } else if (rhdr->flags& ISCSI_FLAG_CMD_BIDI_UNDERFLOW) {
+ sc->result = host_byte(DID_BAD_TARGET) |
+ status_byte(rhdr->cmd_status);
+ rc = ISCSI_ERR_BAD_TARGET;
+ goto fault;
+ } else if (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW) {
+ sc->result = host_byte(DID_ERROR) |
+ status_byte(rhdr->cmd_status);
+ rc = ISCSI_ERR_BAD_TARGET;
+ goto fault;
+ }
+ }
+ } else {
+ sc->result = host_byte(DID_ERROR);
+ rc = ISCSI_ERR_BAD_TARGET;
+ goto fault;
+ }
+
+fault:
+ ctask->in_progress = IN_PROGRESS_IDLE;
+ debug_scsi("done [sc %lx res %d itt 0x%x]\n",
+ (long)sc, sc->result, ctask->itt);
+ spin_lock_irqsave(session->host->host_lock, flags);
+ if (sc->sc_data_direction == DMA_TO_DEVICE) {
+ struct list_head *lh, *n;
+ /* for WRITE, clean up Data-Out's if any */
+ list_for_each_safe(lh, n, &ctask->dataqueue) {
+ iscsi_data_task_t *dtask;
+ dtask = list_entry(lh, iscsi_data_task_t, item);
+ if (dtask) {
+ list_del(&dtask->item);
+ mempool_free(dtask, ctask->datapool);
+ }
+ }
+ }
+ __enqueue(&session->cmdpool, ctask);
+ sc->scsi_done(sc);
+ spin_unlock_irqrestore(session->host->host_lock, flags);
+ return rc;
+}
+
+/*
+ * SCSI Data-In Response processing
+ */
+static int
+iscsi_data_rsp(iscsi_conn_t *conn, iscsi_cmd_task_t *ctask)
+{
+ iscsi_data_rsp_t *rhdr = (iscsi_data_rsp_t *)conn->in.hdr;
+ iscsi_session_t *session = conn->session;
+ int datasn = ntohl(rhdr->datasn);
+ int max_cmdsn = ntohl(rhdr->max_cmdsn);
+ int exp_cmdsn = ntohl(rhdr->exp_cmdsn);
+
+ if (conn->in.datalen == 0) {
+ return 0;
+ }
+
+ if (max_cmdsn < exp_cmdsn -1) {
+ return ISCSI_ERR_MAX_CMDSN;
+ }
+ session->max_cmdsn = max_cmdsn;
+ session->exp_cmdsn = exp_cmdsn;
+
+ if (ctask->datasn != datasn) {
+ return ISCSI_ERR_DATASN;
+ }
+ ctask->datasn++;
+
+ ctask->data_offset = ntohl(rhdr->offset);
+ if (ctask->data_offset + conn->in.datalen > ctask->total_length) {
+ printk("iSCSI: bad data_offset %d datalen %d total_length %d\n",
+ ctask->data_offset, conn->in.datalen, ctask->total_length);
+ return ISCSI_ERR_DATA_OFFSET;
+ }
+
+ if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) {
+ struct scsi_cmnd *sc = ctask->sc;
+ conn->exp_statsn = ntohl(rhdr->statsn) + 1;
+ if (rhdr->flags & ISCSI_FLAG_CMD_UNDERFLOW) {
+ int res_count = ntohl(rhdr->residual_count);
+ if( res_count > 0 &&
+ res_count <= sc->request_bufflen ) {
+ sc->resid = res_count;
+ } else {
+ sc->result = (DID_BAD_TARGET << 16) |
+ rhdr->cmd_status;
+ return ISCSI_ERR_BAD_TARGET;
+ }
+ } else if (rhdr->flags& ISCSI_FLAG_CMD_BIDI_UNDERFLOW) {
+ sc->result = (DID_BAD_TARGET << 16) |
+ rhdr->cmd_status;
+ return ISCSI_ERR_BAD_TARGET;
+ } else if (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW) {
+ sc->result = (DID_ERROR << 16) |
+ rhdr->cmd_status;
+ return ISCSI_ERR_BAD_TARGET;
+ }
+ }
+
+ return 0;
+}
+
+static int
+iscsi_data_xmit(iscsi_conn_t *conn)
+{
+ int len, res;
+ iscsi_cmd_task_t *ctask;
+ iscsi_mgmt_task_t *mtask;
+ iscsi_session_t *session = conn->session;
+
+ /* process immediate queue first */
+ while (conn->in_progress_xmit == IN_PROGRESS_XMIT_IMM &&
+ (mtask = iscsi_dequeue(&conn->immqueue)) != NULL) {
+ if (mtask->in_progress & IN_PROGRESS_IMM_HEAD) {
+ len = mtask->headbuf.size - mtask->headbuf.sent;
+ res = iscsi_sendpage(conn, &mtask->headbuf, len);
+ if (len != res) {
+ iscsi_insert(&conn->immqueue, mtask);
+ return -EAGAIN;
+ }
+ if (mtask->data_count == 0 &&
+ mtask->hdr.itt == ISCSI_RESERVED_TAG) {
+ iscsi_enqueue(&session->immpool, mtask);
+ continue;
+ } else if (mtask->data_count) {
+ mtask->in_progress = IN_PROGRESS_IMM_DATA;
+ }
+ }
+ if (mtask->in_progress == IN_PROGRESS_IMM_DATA) {
+ printk("iSCSI: immediate PDU data is not supported\n");
+ __BUG_ON(1);
+_mtask_again:
+ len = mtask->sendbuf.size - mtask->sendbuf.sent;
+ if (len > mtask->data_count) {
+ len = mtask->data_count;
+ }
+ res = iscsi_sendpage(conn, &mtask->sendbuf, len);
+ if (len != res) {
+ iscsi_insert(&conn->immqueue, mtask);
+ if (res > 0) {
+ mtask->data_count -= res;
+ mtask->sent += res;
+ }
+ return -EAGAIN;
+ }
+ mtask->data_count -= res;
+ mtask->sent += res;
+ if (mtask->data_count) {
+ /* FIXME: implement.
+ * Keep in mind that virtual buffer
+ * spreaded accross multiple pages... */
+ goto _mtask_again;
+ }
+ }
+ }
+
+ /* when we done with immediate queue, process non-immediate queue */
+ while ((ctask = iscsi_dequeue(&conn->xmitqueue)) != NULL) {
+ iscsi_r2t_info_t *r2t;
+
+ conn->in_progress_xmit = IN_PROGRESS_XMIT_DATA;
+
+ if (ctask->in_progress & IN_PROGRESS_HEAD) {
+ len = ctask->headbuf.size - ctask->headbuf.sent;
+ res = iscsi_sendpage(conn, &ctask->headbuf, len);
+ if (len != res) {
+ iscsi_insert(&conn->xmitqueue, ctask);
+ return -EAGAIN;
+ }
+ if (ctask->hdr.flags & ISCSI_FLAG_CMD_READ) {
+ /* wait for DATA_RSP */
+ continue;
+ }
+ }
+
+ if (ctask->in_progress & IN_PROGRESS_BEGIN_WRITE) {
+ if (session->imm_data_en) {
+_imm_again:
+ __BUG_ON(!ctask->imm_count);
+ len = ctask->sendbuf.size - ctask->sendbuf.sent;
+ if (len > ctask->imm_count) {
+ len = ctask->imm_count;
+ }
+ res = iscsi_sendpage(conn, &ctask->sendbuf,len);
+ if (len != res) {
+ iscsi_insert(&conn->xmitqueue,
+ ctask);
+ if (res > 0) {
+ ctask->imm_count -= res;
+ ctask->sent += res;
+ }
+ return -EAGAIN;
+ }
+ ctask->imm_count -= res;
+ ctask->sent += res;
+ if (ctask->imm_count) {
+ iscsi_buf_init_sg(&ctask->sendbuf,
+ &ctask->sg[ctask->sg_count++]);
+ goto _imm_again;
+ }
+ if (!session->initial_r2t_en) {
+ if (ctask->data_pdu_count) {
+ ctask->in_progress =
+ IN_PROGRESS_WRITE |
+ IN_PROGRESS_UNSOLICIT_HEAD;
+ iscsi_unsolicit_data_init(
+ conn, ctask);
+ } else {
+ ctask->in_progress =
+ IN_PROGRESS_WRITE |
+ IN_PROGRESS_R2T_WAIT;
+ continue;
+ }
+ } else if (session->initial_r2t_en &&
+ ctask->data_count) {
+ ctask->in_progress =
+ IN_PROGRESS_WRITE |
+ IN_PROGRESS_R2T_WAIT;
+ continue;
+ }
+ } else {
+ printk("iSCSI: not implemented\n");
+ __BUG_ON(1);
+ }
+ }
+
+ if (ctask->in_progress & IN_PROGRESS_UNSOLICIT_HEAD) {
+ len = ctask->headbuf.size - ctask->headbuf.sent;
+ res = iscsi_sendpage(conn, &ctask->headbuf, len);
+ if (len != res) {
+ iscsi_insert(&conn->xmitqueue, ctask);
+ return -EAGAIN;
+ }
+ ctask->in_progress = IN_PROGRESS_WRITE |
+ IN_PROGRESS_UNSOLICIT_WRITE;
+ }
+
+_unsolicit_again:
+ if (ctask->in_progress & IN_PROGRESS_UNSOLICIT_WRITE) {
+ if (ctask->data_count) {
+ len = ctask->sendbuf.size - ctask->sendbuf.sent;
+ if (len > ctask->data_count) {
+ len = ctask->data_count;
+ }
+ res = iscsi_sendpage(conn, &ctask->sendbuf,len);
+ if (len != res) {
+ iscsi_insert(&conn->xmitqueue,
+ ctask);
+ if (res > 0) {
+ ctask->data_count -= res;
+ ctask->sent += res;
+ }
+ return -EAGAIN;
+ }
+ ctask->data_count -= res;
+ ctask->sent += res;
+ if (ctask->data_count) {
+ iscsi_buf_init_sg(&ctask->sendbuf,
+ &ctask->sg[ctask->sg_count++]);
+ goto _unsolicit_again;
+ }
+ }
+ ctask->in_progress = IN_PROGRESS_IDLE;
+ continue;
+ }
+
+ if (ctask->in_progress & IN_PROGRESS_SOLICIT_HEAD) {
+ r2t = iscsi_dequeue(&ctask->r2tqueue);
+_solicit_head_again:
+ __BUG_ON(r2t == NULL);
+ if (r2t->cont_bit) {
+ /*
+ * we failed to fill-in Data-Out last time
+ * due to memory allocation failure most likely
+ * try it again until we succeed. Once we
+ * succeed, reset cont_bit.
+ */
+ if (iscsi_solicit_data_cont(conn, ctask, r2t,
+ r2t->data_length - r2t->sent)) {
+ iscsi_insert(&ctask->r2tqueue, r2t);
+ return -EAGAIN;
+ }
+ r2t->cont_bit = 0;
+ }
+ len = r2t->headbuf.size - r2t->headbuf.sent;
+ res = iscsi_sendpage(conn, &r2t->headbuf, len);
+ if (len != res) {
+ iscsi_insert(&ctask->r2tqueue, r2t);
+ iscsi_insert(&conn->xmitqueue, ctask);
+ return -EAGAIN;
+ }
+ ctask->r2t = r2t;
+ ctask->in_progress = IN_PROGRESS_WRITE |
+ IN_PROGRESS_SOLICIT_WRITE;
+ }
+
+ if (ctask->in_progress & IN_PROGRESS_SOLICIT_WRITE) {
+ int left;
+ r2t = ctask->r2t;
+_solicit_again:
+ /*
+ * send Data-Out's payload whitnin this R2T sequence.
+ */
+ if (r2t->data_count) {
+ len = r2t->sendbuf.size - r2t->sendbuf.sent;
+ if (len > r2t->data_count) {
+ len = r2t->data_count;
+ }
+ res = iscsi_sendpage(conn, &r2t->sendbuf,len);
+ if (len != res) {
+ iscsi_insert(&conn->xmitqueue, ctask);
+ if (res > 0) {
+ r2t->data_count -= res;
+ r2t->sent += res;
+ }
+ return -EAGAIN;
+ }
+ r2t->data_count -= res;
+ r2t->sent += res;
+ if (r2t->data_count) {
+ __BUG_ON(ctask->bad_sg == r2t->sg);
+ __BUG_ON(ctask->sc->use_sg == 0);
+ iscsi_buf_init_sg(&r2t->sendbuf,
+ r2t->sg);
+ r2t->sg += 1;
+ goto _solicit_again;
+ }
+ }
+
+ /*
+ * done with Data-Out's payload. Check if we have
+ * to send another Data-Out whithin this R2T sequence.
+ */
+ left = r2t->data_length - r2t->sent;
+ if (left) {
+ if (iscsi_solicit_data_cont(conn,
+ ctask, r2t, left)) {
+ r2t->cont_bit = 1;
+ iscsi_insert(&ctask->r2tqueue, r2t);
+ ctask->in_progress =
+ IN_PROGRESS_WRITE |
+ IN_PROGRESS_SOLICIT_HEAD;
+ return -EAGAIN;
+ }
+ ctask->in_progress = IN_PROGRESS_WRITE |
+ IN_PROGRESS_SOLICIT_HEAD;
+ goto _solicit_head_again;
+ }
+
+ /*
+ * done with this R2T sequence. Check if we have
+ * more outstanding R2T's ready to send.
+ */
+ ctask->data_count -= r2t->data_length;
+ iscsi_enqueue(&ctask->r2tpool, r2t);
+ if ((r2t = iscsi_dequeue(&ctask->r2tqueue))) {
+ ctask->in_progress = IN_PROGRESS_WRITE |
+ IN_PROGRESS_SOLICIT_HEAD;
+ goto _solicit_head_again;
+ } else {
+ if (ctask->data_count) {
+ ctask->in_progress =
+ IN_PROGRESS_WRITE |
+ IN_PROGRESS_R2T_WAIT;
+ }
+ }
+ continue;
+ }
+ }
+
+ conn->in_progress_xmit = IN_PROGRESS_XMIT_IMM;
+
+ return 0;
+}
+
+static void
+iscsi_xmitworker(void *data)
+{
+ iscsi_conn_t *conn = (iscsi_conn_t*)data;
+
+ if (conn->id != smp_processor_id()) {
+ schedule_delayed_work_on(conn->id, &conn->xmitwork, 0);
+ return;
+ }
+
+ /*
+ * We serialize Xmit worker on per-connection basis.
+ */
+ down(&conn->xmitsema);
+ if (iscsi_data_xmit(conn)) {
+ schedule_work(&conn->xmitwork);
+ }
+ up(&conn->xmitsema);
+}
+
+#define FAILURE_BAD_HOST 1
+#define FAILURE_BAD_SESSION 2
+#define FAILURE_BAD_SESSID 3
+#define FAILURE_SESSION_FAILED 4
+#define FAILURE_SESSION_FREED 5
+#define FAILURE_WINDOW_CLOSED 6
+
+int
+iscsi_queuecommand(struct scsi_cmnd *sc, void (*done)(struct scsi_cmnd *))
+{
+ struct Scsi_Host *host;
+ int reason = 0;
+ iscsi_session_t *session;
+ iscsi_conn_t *conn = NULL;
+ iscsi_cmd_task_t *ctask;
+ struct list_head *lh;
+
+ sc->scsi_done = done;
+ sc->result = 0;
+
+ if (!(host = sc->device->host)) {
+ reason = FAILURE_BAD_HOST;
+ goto fault;
+ }
+
+ if (!(session = (iscsi_session_t*)*host->hostdata)) {
+ reason = FAILURE_BAD_SESSION;
+ goto fault;
+ }
+
+ if (host->host_no != session->id) {
+ reason = FAILURE_BAD_SESSID;
+ goto fault;
+ }
+
+ if (session->state != ISCSI_STATE_LOGGED_IN) {
+ if (session->state == ISCSI_STATE_FAILED) {
+ reason = FAILURE_SESSION_FAILED;
+ goto fault;
+ }
+ reason = FAILURE_SESSION_FREED;
+ goto fault;
+ }
+
+ /*
+ * Check for iSCSI window and taking care of CmdSN wrap-around
+ * issue. Assuming that running platform will guarantee atomic
+ * double-word memory write operation.
+ */
+ if ((int)(session->max_cmdsn - session->cmdsn) < 0) {
+ reason = FAILURE_WINDOW_CLOSED;
+ goto reject;
+ }
+
+ if (session->conn_cnt > 1) {
+ int cpu = smp_processor_id();
+
+ spin_lock(&session->conn_lock);
+ list_for_each(lh, &session->connections) {
+ iscsi_conn_t *cnx;
+
+ cnx = list_entry(lh, iscsi_conn_t, item);
+ if (cnx->id == cpu) {
+ conn = cnx;
+ break;
+ }
+ }
+ spin_unlock(&session->conn_lock);
+ if (conn == NULL)
+ conn = session->leadconn;
+ } else {
+ conn = session->leadconn;
+ }
+
+ ctask = __dequeue(&session->cmdpool);
+ sc->SCp.ptr = (char*)ctask;
+ iscsi_cmd_init(conn, ctask, sc);
+ spin_unlock_irq(host->host_lock);
+
+ iscsi_enqueue(&conn->xmitqueue, ctask);
+ debug_scsi(
+ "queued [%s cid %d sc %lx itt 0x%x len %d cmdsn %d win %d]\n",
+ sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read",
+ conn->id, (long)sc, ctask->itt, sc->request_bufflen,
+ session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);
+
+ if (in_interrupt())
+ schedule_work(&conn->xmitwork);
+ else
+ iscsi_xmitworker(conn);
+
+ spin_lock_irq(host->host_lock);
+ return 0;
+
+reject:
+ debug_scsi("cmd 0x%x rejected (%d)\n", sc->cmnd[0], reason);
+ return SCSI_MLQUEUE_HOST_BUSY;
+
+fault:
+ printk("iSCSI: cmd 0x%x is not queued (%d)\n", sc->cmnd[0], reason);
+ sc->sense_buffer[0] = 0x70;
+ sc->sense_buffer[2] = NOT_READY;
+ sc->sense_buffer[7] = 0x6;
+ sc->sense_buffer[12] = 0x08;
+ sc->sense_buffer[13] = 0x00;
+ sc->result = DID_NO_CONNECT << 16;
+ switch (sc->cmnd[0]) {
+ case INQUIRY:
+ case REQUEST_SENSE:
+ sc->resid = sc->cmnd[4];
+ case REPORT_LUNS:
+ sc->resid = sc->cmnd[6] << 24;
+ sc->resid |= sc->cmnd[7] << 16;
+ sc->resid |= sc->cmnd[8] << 8;
+ sc->resid |= sc->cmnd[9];
+ default:
+ sc->resid = sc->request_bufflen;
+ }
+ sc->scsi_done(sc);
+ return 0;
+}
+
+static int
+iscsi_eh_abort(struct scsi_cmnd *sc)
+{
+#ifdef DEBUG_SCSI
+ iscsi_cmd_task_t *ctask = (iscsi_cmd_task_t *)sc->SCp.ptr;
+#endif
+ debug_scsi("abort [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
+#if 0
+ iscsi_conn_t *conn = ctask->conn;
+ iscsi_session_t *session = conn->session;
+ iscsi_mgmt_task_t *mtask;
+
+ debug_scsi("abort [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
+
+ mtask = iscsi_dequeue(&session->immpool);
+
+ mtask->hdr.itt = htonl(mtask->itt);
+ mtask->hdr.exp_statsn = htonl(conn->exp_statsn);
+ mtask->data_count = 0;
+ mtask->in_progress = IN_PROGRESS_IMM_HEAD;
+
+ iscsi_buf_init_virt(&mtask->headbuf, (char*)&mtask->hdr,
+ sizeof(iscsi_hdr_t));
+
+ iscsi_enqueue(&conn->immqueue, mtask);
+ schedule_work(&conn->xmitwork);
+
+ down(&session->tmsema);
+#endif
+ return FAILED;
+}
+
+static const char *
+iscsi_info(struct Scsi_Host *host)
+{
+ return "iSCSI/TCP bridge, v." ISCSI_DRV_VERSION;
+}
+
+static int
+iscsi_proc_info(struct Scsi_Host *host, char *buffer, char **start,
+ off_t offset, int length, int inout)
+{
+ return length;
+}
+
+static int
+iscsi_queue_init(iscsi_queue_t *q, int max, spinlock_t *lock)
+{
+ q->lock = lock;
+ q->max = max;
+ q->pool = kmalloc(max * sizeof(void*), GFP_KERNEL);
+ if (q->pool == NULL) {
+ return -ENOMEM;
+ }
+ /* queue's pool is empty initially */
+ q->cons = 0;
+ q->prod = 0;
+ return 0;
+}
+
+static void
+iscsi_queue_free(iscsi_queue_t *q)
+{
+ kfree(q->pool);
+}
+
+static int
+iscsi_pool_init(iscsi_queue_t *q, int max, void ***items, int item_size,
+ spinlock_t *lock)
+{
+ int i;
+
+ *items = kmalloc(max * sizeof(void*), GFP_KERNEL);
+ if (*items == NULL) {
+ return -ENOMEM;
+ }
+
+ q->lock = lock;
+ q->max = max;
+ q->pool = kmalloc(max * sizeof(void*), GFP_KERNEL);
+ if (q->pool == NULL) {
+ kfree(*items);
+ return -ENOMEM;
+ }
+ for (i=0; i<max; i++) {
+ q->pool[i] = kmalloc(item_size, GFP_KERNEL);
+ if (q->pool[i] == NULL) {
+ int j;
+ for (j=0; j<i; j++) {
+ kfree(q->pool[j]);
+ }
+ kfree(q->pool);
+ return -ENOMEM;
+ }
+ memset(q->pool[i], 0, item_size);
+ (*items)[i] = q->pool[i];
+ }
+ /* queue's pool is full initially */
+ q->cons = 1;
+ q->prod = 0;
+ return 0;
+}
+
+static void
+iscsi_pool_free(iscsi_queue_t *q, void **items)
+{
+ int i;
+
+ for (i=0; i<q->max; i++) {
+ kfree(items[i]);
+ }
+ kfree(q->pool);
+ kfree(items);
+}
+
+/*
+ * Allocate new connection within the session and bind it to
+ * the provided socket
+ */
+static iscsi_cnx_h
+iscsi_conn_create(iscsi_snx_h snxh, iscsi_cnx_h handle,
+ struct socket *sock, int conn_idx)
+{
+ iscsi_session_t *session = snxh;
+ struct tcp_opt *tp;
+ struct sock *sk;
+ iscsi_conn_t *conn;
+
+ conn = kmalloc(sizeof(iscsi_conn_t), GFP_KERNEL);
+ if (conn == NULL) {
+ return NULL;
+ }
+ memset(conn, 0, sizeof(iscsi_conn_t));
+
+ /* bind iSCSI connection and socket */
+ 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;
+
+ /* disable Nagle's algorithm */
+ tp = tcp_sk(sk);
+ tp->nonagle = 1;
+
+ if (!(sk->sk_route_caps & NETIF_F_SG)) {
+ printk("iSCSI/TCP: PHY do not support "
+ "HW scatter-gather!\n");
+ }
+ if (!(sk->sk_route_caps & (NETIF_F_IP_CSUM |
+ NETIF_F_NO_CSUM |
+ NETIF_F_HW_CSUM))) {
+ printk("iSCSI/TCP: PHY do not support "
+ "HW checksumming!\n");
+ }
+
+
+ /* Intercepts TCP callbacks for sendfile like receive processing. */
+ iscsi_conn_set_callbacks(conn);
+
+ conn->c_stage = ISCSI_CNX_INITIAL_STAGE;
+ conn->in_progress = IN_PROGRESS_WAIT_HEADER;
+ conn->in_progress_xmit = IN_PROGRESS_XMIT_IMM;
+ conn->id = conn_idx;
+ conn->exp_statsn = 0;
+ conn->handle = handle;
+
+ /* pre calculate PDU Header size */
+ conn->hdr_size = sizeof(iscsi_hdr_t);
+ if (conn->hdrdgst_en) {
+ conn->hdr_size += sizeof(__u32);
+ }
+
+ spin_lock_init(&conn->lock);
+
+ /* initialize xmit PDU commands queue */
+ if (iscsi_queue_init(&conn->xmitqueue, session->cmds_max,
+ &conn->lock)) {
+ kfree(conn);
+ return NULL;
+ }
+ /* initialize immediate xmit queue */
+ if (iscsi_queue_init(&conn->immqueue, session->imm_max,
+ &conn->lock)) {
+ iscsi_queue_free(&conn->xmitqueue);
+ kfree(conn);
+ return NULL;
+ }
+ INIT_WORK(&conn->xmitwork, iscsi_xmitworker, conn);
+ sema_init(&conn->xmitsema, 1);
+
+ return conn;
+}
+
+/*
+ * Do Logout request, terminate connection queus, free
+ * all associated resources
+ */
+static void
+iscsi_conn_destroy(iscsi_cnx_h cnxh)
+{
+ iscsi_conn_t *conn = cnxh;
+ iscsi_session_t *session = conn->session;
+
+ __BUG_ON(conn->sock == NULL);
+
+ sock_hold(conn->sock->sk);
+
+ iscsi_conn_restore_callbacks(conn);
+
+ sock_put(conn->sock->sk);
+
+ sock_release(conn->sock);
+
+ iscsi_queue_free(&conn->xmitqueue);
+ iscsi_queue_free(&conn->immqueue);
+
+ spin_lock_bh(&session->conn_lock);
+ list_del(&conn->item);
+ if (list_empty(&session->connections))
+ session->leadconn = NULL;
+ if (session->leadconn && session->leadconn == conn)
+ session->leadconn = container_of(session->connections.next,
+ struct iscsi_conn, item);
+ spin_unlock_bh(&session->conn_lock);
+
+ if (session->leadconn == NULL) {
+ /* non connections exits.. reset sequencing */
+ session->cmdsn = session->max_cmdsn = session->exp_cmdsn = 1;
+ }
+
+ kfree(conn);
+}
+
+static int
+iscsi_conn_bind(iscsi_snx_h snxh, iscsi_cnx_h cnxh, int is_leading)
+{
+ iscsi_session_t *session = snxh;
+ iscsi_conn_t *conn = cnxh;
+
+ /*
+ * Bind iSCSI connection and session
+ */
+ conn->session = session;
+
+ spin_lock_bh(&session->conn_lock);
+ list_add(&conn->item, &session->connections);
+ spin_unlock_bh(&session->conn_lock);
+
+ if (is_leading) {
+ session->leadconn = conn;
+ }
+
+ return 0;
+}
+
+static int
+iscsi_conn_start(iscsi_cnx_h cnxh)
+{
+ iscsi_conn_t *conn = cnxh;
+ iscsi_session_t *session = conn->session;
+ unsigned long flags;
+
+ if (session == NULL) {
+ printk("iSCSI: can start not-binded connection\n");
+ return -EPERM;
+ }
+
+ if (session->state == ISCSI_STATE_LOGGED_IN &&
+ session->leadconn == conn) {
+ scsi_scan_host(session->host);
+ }
+
+ spin_lock_irqsave(session->host->host_lock, flags);
+ conn->c_stage = ISCSI_CNX_STARTED;
+ session->state = ISCSI_STATE_LOGGED_IN;
+ session->conn_cnt++;
+ spin_unlock_irqrestore(session->host->host_lock, flags);
+
+ return 0;
+}
+
+static void
+iscsi_conn_stop(iscsi_cnx_h cnxh)
+{
+ iscsi_conn_t *conn = cnxh;
+ iscsi_session_t *session = conn->session;
+ unsigned long flags;
+
+ spin_lock_irqsave(session->host->host_lock, flags);
+ conn->c_stage = ISCSI_CNX_STOPPED;
+ session->conn_cnt--;
+
+ if (session->conn_cnt == 0 ||
+ session->leadconn == conn) {
+ session->state = ISCSI_STATE_FAILED;
+ }
+ spin_unlock_irqrestore(session->host->host_lock, flags);
+}
+
+static int
+iscsi_send_immpdu(iscsi_cnx_h cnxh, iscsi_hdr_t *hdr, char *data)
+{
+ iscsi_conn_t *conn = cnxh;
+ iscsi_session_t *session = conn->session;
+ iscsi_mgmt_task_t *mtask;
+
+ mtask = iscsi_dequeue(&session->immpool);
+
+ if (hdr->itt != ISCSI_RESERVED_TAG) {
+ hdr->itt = htonl(mtask->itt);
+ } else {
+ hdr->exp_statsn = htonl(conn->exp_statsn);
+ }
+
+ memcpy(&mtask->hdr, hdr, sizeof(iscsi_hdr_t));
+ mtask->data = data;
+ mtask->data_count = ntoh24(hdr->dlength);
+ mtask->in_progress = IN_PROGRESS_IMM_HEAD;
+
+ iscsi_buf_init_virt(&mtask->headbuf, (char*)&mtask->hdr,
+ sizeof(iscsi_hdr_t));
+
+ /* FIXME: implement convertion of mtask->data into 1st mtask->sendbuf.
+ * Keep in mind that virtual buffer
+ * spreaded accross multiple pages... */
+
+ iscsi_enqueue(&conn->immqueue, mtask);
+ schedule_work(&conn->xmitwork);
+
+ return 0;
+}
+
+static iscsi_snx_h
+iscsi_session_create(iscsi_snx_h handle, int host_no,
+ struct scsi_transport_template *tt, int initial_cmdsn)
+{
+ int cmd_i, i;
+ iscsi_session_t *session;
+ struct Scsi_Host *host;
+ int res;
+
+ host = scsi_host_lookup(host_no);
+ if (host != ERR_PTR(-ENXIO)) {
+ printk("host_no %d already exists\n", host_no);
+ return NULL;
+ }
+
+ session = kmalloc(sizeof(iscsi_session_t), GFP_KERNEL);
+ if (session == NULL) {
+ printk("not enough memory to allocate host_no %d\n",
+ host_no);
+ return NULL;
+ }
+ memset(session, 0, sizeof(iscsi_session_t));
+
+ /* fill-in SCSI Host Template */
+ session->sht.name = "iSCSI Initiator";
+ session->sht.proc_info = iscsi_proc_info;
+ session->sht.info = iscsi_info;
+ session->sht.queuecommand = iscsi_queuecommand;
+ session->sht.can_queue = 128;
+ session->sht.sg_tablesize = 128;
+ session->sht.max_sectors = 128;
+ session->sht.cmd_per_lun = 128;
+ session->sht.this_id = (uint32_t)(unsigned long)session;
+ session->sht.use_clustering = DISABLE_CLUSTERING;
+ session->sht.eh_abort_handler = iscsi_eh_abort;
+ session->sht.proc_name = kmalloc(10, GFP_KERNEL);
+ snprintf(session->sht.proc_name, 10, "iscsi%d", host_no);
+
+ if ((host = scsi_host_alloc(&session->sht, sizeof(void*))) == NULL) {
+ printk("can not allocate host_no %d\n", host_no);
+ goto host_alloc_fault;
+ }
+ host->host_no = session->id = host_no;
+ host->max_id = 1;
+ host->max_channel = 0;
+ host->transportt = tt;
+ *(unsigned long*)host->hostdata = (unsigned long)session;
+ session->host = host;
+ session->state = ISCSI_STATE_LOGGED_IN;
+ session->imm_max = ISCSI_IMM_CMDS_MAX;
+ session->cmds_max = session->sht.can_queue + 1;
+ session->cmdsn = initial_cmdsn;
+ session->exp_cmdsn = initial_cmdsn + 1;
+ session->max_cmdsn = initial_cmdsn + 1;
+ session->handle = handle;
+
+ if ((res=scsi_add_host(host, NULL))) {
+ printk("can not add host_no %d (%d)\n", host_no, res);
+ goto add_host_fault;
+ }
+
+ /* initialize SCSI PDU commands pool */
+ if (iscsi_pool_init(&session->cmdpool, session->cmds_max,
+ (void***)&session->cmds, sizeof(iscsi_cmd_task_t),
+ session->host->host_lock)) {
+ goto cmdpool_alloc_fault;
+ }
+ /* pre-format cmds pool with ITT */
+ for (cmd_i=0; cmd_i<session->cmds_max; cmd_i++) {
+ session->cmds[cmd_i]->itt = cmd_i;
+ }
+
+ spin_lock_init(&session->conn_lock);
+ INIT_LIST_HEAD(&session->connections);
+
+ /* initialize per-task R2T queue with size based on
+ * session's login result MaxOutstandingR2T */
+ for (cmd_i=0; cmd_i<session->cmds_max; cmd_i++) {
+ iscsi_cmd_task_t *ctask = session->cmds[cmd_i];
+
+ /* now initialize per-task R2T pool */
+ if (iscsi_pool_init(&ctask->r2tpool, session->max_r2t,
+ (void***)&ctask->r2ts, sizeof(iscsi_r2t_info_t),
+ NULL)) {
+ goto r2t_alloc_fault;
+ }
+
+ /* now initialize per-task R2T queue */
+ if (iscsi_queue_init(&ctask->r2tqueue, session->max_r2t,
+ NULL)) {
+ iscsi_pool_free(&ctask->r2tpool, (void**)ctask->r2ts);
+ goto r2t_alloc_fault;
+ }
+
+ /*
+ * since under some configurations number of
+ * Data-Out PDU's within R2T-sequence can be quite big
+ * we are using mempool
+ */
+ ctask->datapool = mempool_create(ISCSI_CMD_DATAPOOL_SIZE,
+ mempool_alloc_slab,
+ mempool_free_slab,
+ taskcache);
+ if (ctask->datapool == NULL) {
+ kfree(ctask->r2tqueue.pool);
+ iscsi_pool_free(&ctask->r2tpool, (void**)ctask->r2ts);
+ goto r2t_alloc_fault;
+ }
+ INIT_LIST_HEAD(&ctask->dataqueue);
+ }
+
+ /* initialize immediate commands pool */
+ if (iscsi_pool_init(&session->immpool, session->imm_max,
+ (void***)&session->imm_cmds, sizeof(iscsi_mgmt_task_t),
+ session->host->host_lock)) {
+ goto immpool_alloc_fault;
+ }
+ /* pre-format cmds pool with ITT */
+ for (cmd_i=0; cmd_i<session->imm_max; cmd_i++) {
+ session->imm_cmds[cmd_i]->itt = ISCSI_IMM_ITT_OFFSET + cmd_i;
+ }
+
+ return session;
+
+immpool_alloc_fault:
+r2t_alloc_fault:
+ for (i=0; i<cmd_i; i++) {
+ mempool_destroy(session->cmds[i]->datapool);
+ kfree(session->cmds[i]->r2tqueue.pool);
+ iscsi_pool_free(&session->cmds[i]->r2tpool,
+ (void**)session->cmds[i]->r2ts);
+ }
+ iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
+cmdpool_alloc_fault:
+ scsi_remove_host(host);
+add_host_fault:
+ scsi_host_put(host);
+host_alloc_fault:
+ kfree(session->sht.proc_name);
+ kfree(session);
+ return NULL;
+}
+
+static void
+iscsi_session_destroy(iscsi_snx_h snxh)
+{
+ iscsi_session_t *session = snxh;
+ int i;
+
+ for (i=0; i<session->cmds_max; i++) {
+ mempool_destroy(session->cmds[i]->datapool);
+ kfree(session->cmds[i]->r2tqueue.pool);
+ iscsi_pool_free(&session->cmds[i]->r2tpool,
+ (void**)session->cmds[i]->r2ts);
+ }
+ iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
+ scsi_remove_host(session->host);
+ scsi_host_put(session->host);
+ kfree(session->sht.proc_name);
+ kfree(session);
+}
+
+static void
+iscsi_set_param(iscsi_cnx_h cnxh, iscsi_param_e param, int value)
+{
+ iscsi_conn_t *conn = cnxh;
+ iscsi_session_t *session = conn->session;
+
+ if (conn->c_stage != ISCSI_FULL_FEATURE_PHASE) {
+ switch(param) {
+ case ISCSI_PARAM_MAX_RECV_DLENGH:
+ conn->max_recv_dlength = value;
+ break;
+ case ISCSI_PARAM_MAX_XMIT_DLENGH:
+ conn->max_xmit_dlength = value;
+ break;
+ case ISCSI_PARAM_HDRDGST_EN:
+ conn->hdrdgst_en = value;
+ break;
+ case ISCSI_PARAM_DATADGST_EN:
+ conn->datadgst_en = value;
+ break;
+ case ISCSI_PARAM_INITIAL_R2T_EN:
+ session->initial_r2t_en = value;
+ break;
+ case ISCSI_PARAM_MAX_R2T:
+ session->max_r2t = value;
+ break;
+ case ISCSI_PARAM_IMM_DATA_EN:
+ session->imm_data_en = value;
+ break;
+ case ISCSI_PARAM_FIRST_BURST:
+ session->first_burst = value;
+ break;
+ case ISCSI_PARAM_MAX_BURST:
+ session->max_burst = value;
+ break;
+ case ISCSI_PARAM_PDU_INORDER_EN:
+ session->pdu_inorder_en = value;
+ break;
+ case ISCSI_PARAM_DATASEQ_INORDER_EN:
+ session->dataseq_inorder_en = value;
+ break;
+ case ISCSI_PARAM_ERL:
+ session->erl = value;
+ break;
+ case ISCSI_PARAM_IFMARKER_EN:
+ session->ifmarker_en = value;
+ break;
+ case ISCSI_PARAM_OFMARKER_EN:
+ session->ifmarker_en = value;
+ break;
+ default:
+ break;
+ }
+ } else {
+ printk("iSCSI: can not change parameter [%d]\n", param);
+ }
+}
+
+int
+iscsi_tcp_register(iscsi_ops_t *ops, iscsi_caps_t *caps)
+{
+ taskcache = kmem_cache_create("iscsi_taskcache",
+ sizeof(iscsi_union_task_t), 0, 0, NULL, NULL);
+ if (taskcache == NULL) {
+ printk("not enough memory to allocate iscsi_taskcache\n");
+ return -ENOMEM;
+ }
+
+ ops->create_session = iscsi_session_create;
+ ops->destroy_session = iscsi_session_destroy;
+ ops->create_cnx = iscsi_conn_create;
+ ops->bind_cnx = iscsi_conn_bind;
+ ops->destroy_cnx = iscsi_conn_destroy;
+ ops->set_param = iscsi_set_param;
+ ops->start_cnx = iscsi_conn_start;
+ ops->stop_cnx = iscsi_conn_stop;
+ ops->send_immpdu = iscsi_send_immpdu;
+
+ caps->flags = CAP_RECOVERY_L0 | CAP_MULTI_R2T;
+
+ return 0;
+}
+
+void
+iscsi_tcp_unregister(void)
+{
+ kmem_cache_destroy(taskcache);
+}
diff --git a/iscsi_tcp.h b/iscsi_tcp.h
new file mode 100644
index 0000000..0d5b2eb
--- /dev/null
+++ b/iscsi_tcp.h
@@ -0,0 +1,284 @@
+/*
+ * iSCSI Initiator TCP Data-Path
+ * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+ * maintained by linux-storage-devel@lists.sourceforge.net
+ *
+ * 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.
+ */
+
+#ifndef ISCSI_IDP_TCP_H
+#define ISCSI_IDP_TCP_H
+
+#include <asm/io.h>
+#include <net/tcp.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/inet.h>
+#include <linux/blkdev.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_request.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi.h>
+
+#include <iscsi_if.h>
+
+/* Connection states */
+#define ISCSI_CNX_INITIAL_STAGE 0
+#define ISCSI_CNX_STARTED 1
+#define ISCSI_CNX_STOPPED 2
+
+/* Socket's Receive state machine */
+#define IN_PROGRESS_WAIT_HEADER 0x0
+#define IN_PROGRESS_HEADER_GATHER 0x1
+#define IN_PROGRESS_DATA_RECV 0x2
+
+/* Socket's Xmit state machine */
+#define IN_PROGRESS_XMIT_IMM 0x0
+#define IN_PROGRESS_XMIT_DATA 0x1
+
+/* iSCSI Task Command's state machine */
+#define IN_PROGRESS_OP_MASK 0x3 /* READ | WRITE */
+#define IN_PROGRESS_IDLE 0x0
+#define IN_PROGRESS_READ 0x1
+#define IN_PROGRESS_WRITE 0x2
+#define IN_PROGRESS_HEAD 0x4
+#define IN_PROGRESS_UNSOLICIT_HEAD 0x10
+#define IN_PROGRESS_SOLICIT_HEAD 0x20
+#define IN_PROGRESS_UNSOLICIT_WRITE 0x40
+#define IN_PROGRESS_SOLICIT_WRITE 0x80
+#define IN_PROGRESS_R2T_WAIT 0x100
+#define IN_PROGRESS_BEGIN_WRITE 0x200
+#define IN_PROGRESS_IMM_HEAD 0x400
+#define IN_PROGRESS_IMM_DATA 0x800
+
+#define ISCSI_DRV_VERSION "0.1"
+#define ISCSI_DEFAULT_PORT 3260
+#define ISCSI_STRING_MAX 255
+#define ISCSI_NODE_NAME_MAX 255
+#define ISCSI_NODE_PORTAL_MAX 32
+#define ISCSI_ALIAS_NAME_MAX 255
+#define ISCSI_CONN_MAX 1
+#define ISCSI_PORTAL_MAX 1
+#define ISCSI_CONN_RCVBUF_MIN 262144
+#define ISCSI_CONN_SNDBUF_MIN 262144
+#define ISCSI_TEXT_SEPARATOR '='
+#define ISCSI_PAD_LEN 4
+#define ISCSI_DATA_MAX 65536
+#define ISCSI_DRAFT20_VERSION 0x00
+#define ISCSI_R2T_MAX 16
+#define ISCSI_IMM_CMDS_MAX 32
+#define ISCSI_IMM_ITT_OFFSET 0x1000
+#define ISCSI_CMD_DATAPOOL_SIZE 32
+
+typedef struct iscsi_portal {
+ unsigned char ipaddr[16];
+ int port;
+ int tag;
+} iscsi_portal_t;
+
+typedef struct iscsi_queue {
+ void **pool; /* Queue pool */
+ int cons; /* Queue consumer pointer */
+ int prod; /* Queue producer pointer */
+ int max; /* Max number of elements */
+ spinlock_t *lock; /* Queue protection lock */
+} iscsi_queue_t;
+
+struct iscsi_session;
+struct iscsi_cmd_task;
+struct iscsi_mgmt_task;
+
+/* Socket connection recieve helper */
+typedef struct iscsi_tcp_recv {
+ iscsi_hdr_t *hdr;
+ struct sk_buff *skb;
+ int offset;
+ int len;
+ int hdr_offset;
+ int copy;
+ int copied;
+ int padding;
+ struct iscsi_cmd_task *ctask; /* current cmd in progress */
+
+ /* copied and flipped values */
+ int opcode;
+ int flags;
+ int cmd_status;
+ int ahslen;
+ int datalen;
+ uint32_t itt;
+} iscsi_tcp_recv_t;
+
+typedef struct iscsi_conn {
+ iscsi_hdr_t hdr; /* Header placeholder */
+ iscsi_hdr_t prev_hdr; /* Header placeholder */
+ uint32_t prev_itt;
+
+ /* FIXME: do dynamic allocation by size max_recv_dlength */
+ char data[ISCSI_DATA_MAX]; /* Data placeholder */
+ int data_copied;
+
+ /* iSCSI connection-wide sequencing */
+ uint32_t exp_statsn;
+ int hdr_size; /* PDU Header size pre-calc. */
+
+ /* control data */
+ int senselen; /* is data has sense? */
+ int id; /* iSCSI CID */
+ iscsi_tcp_recv_t in; /* TCP receive context */
+ int in_progress; /* Connection state machine */
+ struct socket *sock; /* BSD socket layer */
+ struct iscsi_session *session; /* Parent session */
+ struct list_head item; /* item's list of connections */
+ iscsi_queue_t immqueue; /* Immediate xmit queue */
+ iscsi_queue_t xmitqueue; /* Data-path queue */
+ struct work_struct xmitwork; /* per-conn. xmit workqueue */
+ struct semaphore xmitsema;
+ int c_stage; /* Connection state */
+ iscsi_cnx_h handle; /* CP connection handle */
+ int in_progress_xmit; /* xmit state machine */
+ spinlock_t lock;
+
+ /* configuration */
+ int max_recv_dlength;
+ int max_xmit_dlength;
+ int hdrdgst_en;
+ int datadgst_en;
+
+ /* old values for socket callbacks */
+ void (*old_data_ready)(struct sock *, int);
+ void (*old_state_change)(struct sock *);
+ void (*old_write_space)(struct sock *);
+} iscsi_conn_t;
+
+typedef struct iscsi_session {
+ /* iSCSI session-wide sequencing */
+ uint32_t cmdsn;
+ uint32_t exp_cmdsn;
+ uint32_t max_cmdsn;
+
+ /* configuration */
+ int initial_r2t_en;
+ int max_r2t;
+ int imm_data_en;
+ int first_burst;
+ int max_burst;
+ int time2wait;
+ int time2retain;
+ int pdu_inorder_en;
+ int dataseq_inorder_en;
+ int erl;
+ int ifmarker_en;
+ int ofmarker_en;
+
+ /* control data */
+ struct scsi_host_template sht;
+ struct Scsi_Host *host;
+ uint8_t isid[6];
+ int id;
+ iscsi_conn_t *leadconn; /* Leading Conn. */
+ spinlock_t conn_lock;
+ volatile iscsi_session_state_e state;
+ struct list_head item;
+ void *auth_client;
+ iscsi_snx_h handle; /* CP session handle */
+ int conn_cnt;
+
+ struct list_head connections; /* list of connects. */
+ int cmds_max; /* size of cmds array */
+ struct iscsi_cmd_task **cmds; /* Original Cmds arr */
+ iscsi_queue_t cmdpool; /* PDU's pool */
+ int imm_max; /* size of Imm array */
+ struct iscsi_mgmt_task **imm_cmds; /* Original Imm arr */
+ iscsi_queue_t immpool; /* Imm PDU's pool */
+} iscsi_session_t;
+
+typedef struct iscsi_buf {
+ struct page *page;
+ int offset;
+ int size;
+ int sent;
+} iscsi_buf_t;
+
+typedef struct iscsi_data_task {
+ iscsi_data_t hdr; /* PDU */
+ char opt[sizeof(__u32)]; /* Header-Digest */
+ struct list_head item; /* data queue item */
+} iscsi_data_task_t;
+
+typedef struct iscsi_mgmt_task {
+ iscsi_hdr_t hdr; /* mgmt. PDU */
+ char opt[sizeof(__u32)]; /* Header-Digest */
+ char *data; /* mgmt payload */
+ int in_progress; /* mgmt xmit progress */
+ int data_count; /* counts data to be sent */
+ iscsi_buf_t headbuf; /* Header Buffer */
+ iscsi_buf_t sendbuf; /* in progress buffer */
+ int sent;
+ uint32_t itt; /* this ITT */
+} iscsi_mgmt_task_t;
+
+typedef union iscsi_union_task {
+ iscsi_data_task_t dtask;
+ iscsi_mgmt_task_t mtask;
+} iscsi_union_task_t;
+
+typedef struct iscsi_r2t_info {
+ int ttt; /* copied from R2T */
+ int data_length; /* copied from R2T */
+ int data_offset; /* copied from R2T */
+ iscsi_buf_t headbuf; /* Data-Out Header Buffer */
+ iscsi_buf_t sendbuf; /* Data-Out in progress buffer*/
+ int sent; /* R2T sequence progress */
+ int cont_bit; /* Data-Out cont. faulure */
+ int data_count; /* DATA-Out payload progress */
+ struct scatterlist *sg; /* per-R2T SG list */
+} iscsi_r2t_info_t;
+
+typedef struct iscsi_cmd_task {
+ iscsi_cmd_t hdr; /* orig. SCSI PDU */
+ char opt[4*sizeof(__u16) + /* one AHS */
+ sizeof(__u32)]; /* Header-Digest */
+ int itt; /* this ITT */
+ int datasn; /* DataSN numbering */
+ iscsi_buf_t headbuf; /* Header Buffer */
+ iscsi_buf_t sendbuf; /* in progress buffer */
+ int sent;
+ struct scatterlist *sg; /* per-cmd SG list */
+ struct scatterlist *bad_sg; /* assert statement */
+ int sg_count; /* SG's to process */
+ iscsi_data_task_t **solicit_data; /* Solicited PDU's */
+ int solicit_count;
+ iscsi_data_task_t **unsolicit_data; /* Unsolicited PDU's */
+ int unsolicit_count;
+ int in_progress; /* State machine */
+ int imm_count; /* Total Imm-Data cnt */
+ int data_count; /* Total DATA-Out cnt */
+ int data_pdu_count; /* DATA-Out PDU cnt */
+ struct scsi_cmnd *sc; /* Assoc. SCSI cmnd */
+ int total_length;
+ int data_offset;
+ iscsi_conn_t *conn; /* used connection */
+
+ iscsi_r2t_info_t *r2t; /* in progress R2T */
+ iscsi_queue_t r2tpool;
+ iscsi_queue_t r2tqueue;
+ iscsi_r2t_info_t *r2ts;
+ struct list_head dataqueue; /* Data-Out dataqueue */
+ mempool_t *datapool;
+} iscsi_cmd_task_t;
+
+#endif /* ISCSI_H */
diff --git a/iscsiadm b/iscsiadm
new file mode 100755
index 0000000..5deee7b
--- /dev/null
+++ b/iscsiadm
@@ -0,0 +1,800 @@
+#!/usr/bin/perl
+#
+# iSCSI Configuration Utility
+# Copyright (C) 2004 Dmitry Yusupov, Alex Aizman
+# maintained by simple-iscsi-devel@lists.sourceforge.net
+#
+# 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.
+#
+
+use Cwd;
+use Getopt::Std;
+use Socket;
+use strict;
+
+use vars qw/ %opt /;
+my $reopen_attempts = 0;
+my $reopen_timeout = 3;
+my $recv_timeout = 5;
+my $sysfs_path = "/sys/class/iscsi";
+
+#
+# Default Configuration
+#
+my $initiator_name = "iqn.com.dima";
+my $initiator_alias = "dima-um";
+my @isid = ('0', '1', '2', '3', '4', '5');
+my $first_burst = 262144;
+my $max_recv_dlength = 65536;
+my $max_burst = 262144;
+my $max_r2t = 1;
+my $max_cnx = 1;
+my $erl = 0;
+my $initial_r2t_en = 0;
+my $imm_data_en = 1;
+my $hdrdgst_en = 0;
+my $datadgst_en = 0;
+my $ifmarker_en = 0;
+my $ofmarker_en = 0;
+my $pdu_inorder_en = 1;
+my $dataseq_inorder_en = 1;
+my $time2wait = 5;
+my $time2retain = 20;
+my $auth_en = 0;
+my $cmdsn = 1;
+
+#
+# Response Configuration
+#
+my $target_name;
+my $target_portal;
+my $target_alias;
+my $target_address;
+my $tpgt;
+my $max_xmit_dlength = 8192;
+my $tsih = 0;
+my $exp_cmdsn;
+my $max_cmdsn;
+
+my $iscsiadm_path;
+
+sub fatal {
+ print "iscsiadm: @_", "\n";
+ exit;
+}
+
+$SIG{ALRM} = sub {
+ fatal("recv timeout");
+};
+
+sub iscsi_open {
+ my ($attr, $op) = @_;
+ open(my $fd, "$attr$sysfs_path/$op") ||
+ fatal("iSCSI driver is not loaded");
+ return $fd;
+}
+
+#
+# Retrieve string with the length of padding filled in
+# by zeroes.
+#
+sub padding {
+ my ($dlength) = @_;
+ my $pad = "";
+ if ($dlength%4 != 0) { $pad = "\0" x (4 - $dlength%4); }
+ return $pad;
+}
+
+#
+# Send Login PDU
+#
+sub send_login_req {
+ my ($cmdsn, $exp_statsn, $flags, $cid, $data) = @_;
+ my $dlength = length($data);
+ my $loginpdu = pack('CCCCCCCCccccccnNnccNNNNNN',
+ 0x43, # C: opcode: Login + Immediate
+ 0x87, # C: flags T, OP, FF
+ 0, # C: max ver
+ 0, # C: min ver
+ 0, # C: hlength
+ ($dlength >> 16) & 0xFF, # C: byte 0 of dlength
+ ($dlength >> 8) & 0xFF, # C: byte 1 of dlength
+ $dlength & 0xFF, # C: byte 2 of dlength
+ @isid[0],@isid[1],@isid[2],@isid[3],@isid[4],@isid[5],
+ # cccccc: isid
+ $tsih, # n: tsih
+ 0, # N: itt
+ $cid, # n: cid
+ 0,0, # cc: rsvd2
+ $cmdsn, # N: cmdsn
+ $exp_statsn, # N: exp_statsn
+ 0,0,0,0 # NNNN: rsvd16
+ );
+ print SOCK $loginpdu.$data.padding($dlength);
+}
+
+#
+# Send Text PDU
+#
+sub send_text_req {
+ my ($cmdsn, $exp_statsn, $ttt, $data) = @_;
+ my $dlength = length($data);
+ my $textpdu = pack('CCsCCCCNNNNNNNNNN',
+ 0x4, # C: opcode: Text
+ 0x80, # C: flags F
+ 0, # s: rsvd2
+ 0, # C: hlength
+ ($dlength >> 16) & 0xFF, # C: byte 0 of dlength
+ ($dlength >> 8) & 0xFF, # C: byte 1 of dlength
+ $dlength & 0xFF, # C: byte 2 of dlength
+ 0, # N: rsvd4
+ 0, # N: rsvd4
+ 0, # N: itt
+ $ttt, # N: ttt
+ $cmdsn, # N: cmdsn
+ $exp_statsn, # N: exp_statsn
+ 0,0,0,0 # NNNN: rsvd16
+ );
+ print SOCK $textpdu.$data.padding($dlength);
+}
+
+sub recv_pdu {
+ my ($len) = @_;
+ alarm($recv_timeout);
+ read(SOCK, my $rsp, $len)==$len || fatal("bad length response ($len)");
+ alarm(0);
+ return $rsp;
+}
+
+sub login_text_rsp_parse {
+ my $data = "";
+ my ($rsp) = @_;
+
+ my @pairs = split(/\0/, $rsp);
+ my $i = 0;
+ while (@pairs[$i]) {
+ $data = $data."\0" if $i != 0;
+ if (@pairs[$i] =~ /^TargetAlias=(.*)/) {
+ $target_alias = $1;
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^TargetAddress=(.*)/) {
+ $target_address = $1;
+ fatal("redirection is not supported");
+ } elsif (@pairs[$i] =~ /^TargetPortalGroupTag=(.*)/) {
+ $tpgt = $1;
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^InitialR2T=(.*)/) {
+ $initial_r2t_en = ($1 eq "Yes"?1:0);
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^ImmediateData=(.*)/) {
+ $imm_data_en = ($1 eq "Yes"?1:0);
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^MaxRecvDataSegmentLength=(.*)/) {
+ $max_xmit_dlength = $1;
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^FirstBurstLength=(.*)/) {
+ $first_burst = $1;
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^MaxBurstLength=(.*)/) {
+ $max_burst = $1;
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^HeaderDigest/) {
+ $data = $data."HeaderDigest=None";
+ } elsif (@pairs[$i] =~ /^DataDigest/) {
+ $data = $data."DataDigest=None";
+ } elsif (@pairs[$i] =~ /^DefaultTime2Wait=(.*)/) {
+ $time2wait = $1;
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^DefaultTime2Retain=(.*)/) {
+ $time2retain = $1;
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^MaxOutstandingR2T=(.*)/) {
+ $max_r2t = $1;
+ $data = $data.@pairs[$i];
+ } elsif (@pairs[$i] =~ /^MaxConnections=(.*)/) {
+ fatal("max connections rsp error") if $1 > $max_cnx;
+ } elsif (@pairs[$i] =~ /^ErrorRecoveryLevel=(.*)/) {
+ fatal("recovery level rsp error")
+ if $1 != $erl;
+ } elsif (@pairs[$i] =~ /^OFMarker=(.*)/) {
+ # ignoring...
+ } elsif (@pairs[$i] =~ /^OFMarkInt=(.*)/) {
+ # ignoring...
+ } elsif (@pairs[$i] =~ /^IFMarker=(.*)/) {
+ # ignoring...
+ } elsif (@pairs[$i] =~ /^IFMarkInt=(.*)/) {
+ # ignoring...
+ } elsif (@pairs[$i] =~ /^DataPDUInOrder=(.*)/) {
+ # ignoring...
+ } elsif (@pairs[$i] =~ /^DataSequenceInOrder=(.*)/) {
+ # ignoring...
+ }
+ $i++;
+ }
+ return $data;
+}
+
+#
+# Do SendTargets discovery, on good result either automaticaly
+# login or just show the result
+#
+sub sendtargets_discovery {
+ my ($callback, $ipaddr_port) = @_;
+ my $rsp;
+ my $dlength;
+ my $ipaddr;
+ my $port;
+ if ($ipaddr_port =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):([0-9]+)$/) {
+ $ipaddr = $1;
+ $port = $2;
+ } else {
+ fatal("bad discovery format: $ipaddr_port, ".
+ "expecting <ipaddr>:<port>");
+ }
+
+ socket(SOCK, PF_INET, SOCK_STREAM,
+ getprotobyname('tcp')) || fatal("socket: $!");
+ connect(SOCK, sockaddr_in($port,
+ inet_aton($ipaddr))) || fatal("connect: $!");
+ select(SOCK); $| = 1; select(STDOUT); # use unbuffemiles i/o.
+
+ my $cmdsn = 1;
+ my $exp_statsn = 1;
+ my $data = "InitiatorName=$initiator_name\0SessionType=Discovery";
+ while(1) {
+ send_login_req($cmdsn, $exp_statsn, 0x87, 0, $data);
+ $rsp = recv_pdu(48);
+ my ($opcode, $flags, $junk, $len0, $len1, $len2, $junk,
+ $statsn, $junk, $class, $detail, $junk) =
+ unpack('CCa3CCCa16Na8CCa*', $rsp);
+ $opcode == 0x23 || fatal("bad login_rsp opcode ($opcode)");
+ $dlength = (($len0 << 16) | ($len1 << 8) | $len2);
+ if ($dlength%4 != 0) { $dlength += 4 - $dlength%4 };
+ $rsp = recv_pdu($dlength);
+
+ $class == 1 && fatal("redirection is not supported ($detail)");
+ $class == 2 && fatal("initiator error ($detail)");
+ $class == 3 && fatal("target error ($detail)");
+
+ #
+ # stop if T-bit is set
+ #
+ last if ($flags & 0x80);
+
+ $data = login_text_rsp_parse($rsp);
+ $exp_statsn = $statsn + 1;
+ }
+
+ my $ttt = 0xffffffff;
+ my $i=0;
+ my $data = "SendTargets=All\0";
+ while(1) {
+ send_text_req($cmdsn++, $exp_statsn, $ttt, $data);
+ my $rsp = recv_pdu(48);
+ my ($opcode, $flags, $junk, $len0, $len1, $len2, $junk,
+ $ttt, $statsn, $junk) = unpack('CCa3CCCa12NNa*', $rsp);
+ $opcode == 0x24 || fatal("bad text_rsp opcode ($opcode)");
+ $dlength = (($len0 << 16) | ($len1 << 8) | $len2);
+ if ($dlength%4 != 0) { $dlength += 4 - $dlength%4; }
+
+ #
+ # read & parse Text's DataSegment
+ #
+ $rsp = recv_pdu($dlength);
+ if ($rsp =~ /.*TargetName=(.*)\0TargetAddress=(.*)\0/) {
+ $target_name = $1;
+ $target_portal = $2;
+ } elsif ($rsp =~ /.*TargetAddress=(.*)\0TargetName=(.*)\0/) {
+ $target_portal = $1;
+ $target_name = $2;
+ } else { last; }
+
+ &$callback($i++, $target_name, $target_portal);
+
+ #
+ # stop if T-bit is set
+ #
+ last if ($flags & 0x80);
+
+ $exp_statsn = $statsn + 1;
+ };
+
+ close(SOCK);
+}
+
+#
+# Do Login request, auth, parameters negotiation. On success,
+# transfer connection to the kernel.
+#
+sub connection_login {
+ my ($target_name, $target_portal, $cid) = @_;
+ my $rsp;
+ my $dlength;
+ my $ipaddr;
+ my $port;
+ if ($target_portal =~
+ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):([0-9]+),([0-9]+).*$/) {
+ $ipaddr = $1;
+ $port = $2;
+ $tpgt = $3;
+ } else {
+ print "bad target portal format: $target_portal, ".
+ "expecting <ipaddr>:<port>,<tpgt>";
+ return 0;
+ }
+
+ socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp')) ||
+ printf("socket: $!\n") && return 0;
+ setsockopt(SOCK, SOL_SOCKET, SO_SNDBUF, 500*1024);
+ setsockopt(SOCK, SOL_SOCKET, SO_RCVBUF, 500*1024);
+ connect(SOCK, sockaddr_in($port, inet_aton($ipaddr))) ||
+ printf("connect: $!") && return 0;
+ select(SOCK); $| = 1; select(STDOUT); # use unbuffemiles i/o.
+
+ my $exp_statsn = 1;
+ my $data;
+ my $data_initial;
+ my $flags;
+ my $c_stage = -1;
+ my $n_stage = -1;
+ my $transit = 0;
+ my $partial_rsp = 0;
+ do {
+ if ($c_stage == -1) {
+ $data_initial = "InitiatorName=$initiator_name\0".
+ "InitiatorAlias=$initiator_alias\0".
+ "TargetName=$target_name\0".
+ "SessionType=Normal" if $cid == 0;
+ if ($auth_en) {
+ # we're prepared to do authentication
+ $c_stage = $n_stage = 0;
+ } else {
+ # can't do any authentication, skip that
+ # stage
+ $c_stage = $n_stage = 1;
+ }
+ }
+
+ if ($c_stage == 0) {
+ # negotiate security...
+ # to be done...
+ } elsif ($c_stage == 1) {
+ $n_stage = 3;
+ $transit = 1;
+ if (!$partial_rsp) {
+ $data = $data_initial."\0".
+ "HeaderDigest=".($hdrdgst_en?"CRC32C,None":"None")."\0".
+ "DataDigest=".($datadgst_en?"CRC32C,None":"None")."\0".
+ "MaxRecvDataSegmentLength=$max_recv_dlength\0".
+ "InitialR2T=".($initial_r2t_en?"Yes":"No")."\0".
+ "ImmediateData=".($imm_data_en?"Yes":"No")."\0".
+ "MaxBurstLength=$max_burst\0".
+ "FirstBurstLength=$first_burst\0".
+ "DefaultTime2Wait=$time2wait\0".
+ "DefaultTime2Retain=$time2retain\0".
+ "MaxOutstandingR2T=$max_r2t\0".
+ "MaxConnections=$max_cnx\0".
+ "ErrorRecoveryLevel=$erl\0".
+ "IFMarker=".($ifmarker_en?"Yes":"No")."\0".
+ "OFMarker=".($ofmarker_en?"Yes":"No")."\0".
+ "DataPDUInOrder=".($pdu_inorder_en?"Yes":"No")."\0".
+ "DataSequenceInOrder=".
+ ($dataseq_inorder_en?"Yes":"No") if $cid == 0;
+ $data =
+ "HeaderDigest=".($hdrdgst_en?"CRC32C,None":"None")."\0".
+ "DataDigest=".($datadgst_en?"CRC32C,None":"None")."\0".
+ "MaxRecvDataSegmentLength=".
+ "$max_recv_dlength\0" if $cid != 0;
+ }
+ }
+
+ # fill in the flags
+ $flags = 0;
+ $flags |= $c_stage << 2;
+ if ($transit) {
+ # transit to the next stage
+ $flags |= $n_stage;
+ $flags |= 0x80;
+ } else {
+ # next == current
+ $flags |= $c_stage;
+ }
+ send_login_req($cmdsn, $exp_statsn, $flags, $cid, $data);
+ $rsp = recv_pdu(48);
+ my ($opcode, $flags, $vmax, $vactive,
+ $junk, $len0, $len1, $len2, $junk, $_tsih, $junk,
+ $statsn, $_exp_cmdsn, $_max_cmdsn, $class, $detail, $junk) =
+ unpack('CCCCa1CCCa6na8NNNCCa*', $rsp);
+ $opcode == 0x23 ||
+ printf("bad login_rsp opcode ($opcode)\n") && return 0;
+ $dlength = (($len0 << 16) | ($len1 << 8) | $len2);
+ if ($dlength%4 != 0) { $dlength += 4 - $dlength%4 };
+ $rsp = recv_pdu($dlength);
+
+ $class == 1 &&
+ printf("redirection is not supported ($detail)\n") &&
+ return 0;
+ $class == 2 && printf("initiator error ($detail)\n") &&
+ return 0;
+ $class == 3 && printf("target error ($detail)\n") && return 0;
+
+ $vactive != 0 && fatal("version mismatch");
+
+ # make sure the current stages matches
+ $c_stage != (($flags & 0x0C)>>2) &&
+ printf("protocol error: current stage not matches\n") &&
+ return 0;
+
+ # make sure that we're actually advancing if the
+ # T-bit is set
+ $transit && (($flags & 0x03) <= $c_stage) &&
+ fatal("protocol error: next stage is not advancing\n") &&
+ return 0;
+
+ if ($c_stage == 0) {
+ # to be implemented...
+ } elsif ($c_stage == 1) {
+ $data = login_text_rsp_parse($rsp);
+ }
+
+ if ($transit) {
+ # advance to the next stage
+ $partial_rsp = 0;
+ $c_stage = $flags & 0x3;
+ } else {
+ # we got a partial response, don't advance,
+ # more negotiation to do
+ $partial_rsp = 1;
+ }
+
+ # record some fields for later use
+ $tsih = $_tsih;
+ $max_cmdsn = $_max_cmdsn;
+ $exp_cmdsn = $_exp_cmdsn;
+ $exp_statsn = $statsn + 1;
+ } while ($c_stage != 3);
+
+ return fileno(SOCK);
+}
+
+sub get_session_ids {
+ opendir(DIR, $sysfs_path);
+ my @ids = grep(s/^host([0-9]+)$/\1/,readdir(DIR));
+ closedir(DIR);
+ return @ids;
+}
+
+sub getstate {
+ my ($id) = @_;
+ my $fd;
+
+ $fd = iscsi_open("<", "host$id/state");
+ chop(my @state = <$fd>);
+ close($fd);
+ return $state[0];
+}
+
+sub getparam {
+ my ($id, $param) = @_;
+ my $fd;
+
+ if ($id == -1) {
+ $fd = iscsi_open("<", "initiator_parameters");
+ } else {
+ $fd = iscsi_open("<", "host$id/parameters");
+ }
+ my @lines = <$fd>;
+ close($fd);
+ foreach my $line (@lines) {
+ if ($line =~ /^$param.*=(.*)$/) {
+ $line = $1;
+ $line =~ s/^\s+|\s+$//g;
+ return $line;
+ }
+ }
+ fatal("not existing parameter '$param'!");
+}
+
+sub show_sessions {
+ my @ids = get_session_ids();
+
+ if (length(@ids) == 0) {
+ printf "Active iSCSI Sessions: none\n";
+ } else {
+ printf "Active iSCSI Sessions:\n";
+ foreach my $id (sort @ids) {
+ my $state = getstate($id);
+ printf("#%d %s ($state)\n",
+ $id, getparam($id, "target_name"));
+ }
+ }
+}
+
+sub setparam {
+ my ($id, $param, $value) = @_;
+ my $fd;
+
+ if ($id == -1) {
+ $fd = iscsi_open(">", "initiator_parameters");
+ } else {
+ $fd = iscsi_open(">", "host$id/parameters");
+ }
+ print $fd "$param $value";
+ close($fd);
+}
+
+sub operation {
+ my ($str) = @_;
+ my $fd = iscsi_open(">", "session_operation");
+ if (syswrite($fd, "$str") == 0) {
+ close($fd);
+ printf("iscsiadm: unsuccessful operation: '$str'\n");
+ return 0;
+ }
+ close($fd);
+ return 1;
+}
+
+sub logout {
+ my ($sid) = @_;
+ operation("tcp connection remove $sid 0") || exit 1;
+ operation("tcp session remove $sid") || exit 1;
+}
+
+sub sysfs_save {
+ setparam(-1, "initiator_name", $initiator_name);
+ setparam(-1, "initiator_alias", $initiator_alias);
+ setparam(-1, "isid", @isid);
+ setparam(-1, "target_name", $target_name);
+ setparam(-1, "target_alias", $target_name);
+ setparam(-1, "target_portal", $target_portal);
+ setparam(-1, "target_address", $target_portal);
+ setparam(-1, "tpgt", $tpgt);
+ setparam(-1, "first_burst", $first_burst);
+ setparam(-1, "max_recv_dlength", $max_recv_dlength);
+ setparam(-1, "max_xmit_dlength", $max_xmit_dlength);
+ setparam(-1, "max_burst", $max_burst);
+ setparam(-1, "max_r2t", $max_r2t);
+ setparam(-1, "max_cnx", $max_cnx);
+ setparam(-1, "erl", $erl);
+ setparam(-1, "initial_r2t_en", $initial_r2t_en);
+ setparam(-1, "imm_data_en", $imm_data_en);
+ setparam(-1, "hdrdgst_en", $hdrdgst_en);
+ setparam(-1, "datadgst_en", $datadgst_en);
+ setparam(-1, "ifmarker_en", $ifmarker_en);
+ setparam(-1, "ofmarker_en", $ofmarker_en);
+ setparam(-1, "pdu_inorder_en", $pdu_inorder_en);
+ setparam(-1, "dataseq_inorder_en", $dataseq_inorder_en);
+ setparam(-1, "time2wait", $time2wait);
+ setparam(-1, "time2retain", $time2retain);
+ setparam(-1, "auth_en", $auth_en);
+ setparam(-1, "cmdsn", $cmdsn);
+ setparam(-1, "exp_cmdsn", $exp_cmdsn);
+ setparam(-1, "max_cmdsn", $max_cmdsn);
+ setparam(-1, "tsih", $tsih);
+}
+
+sub sysfs_restore {
+ my ($id) = @_;
+
+ $initiator_name = getparam(-1, "initiator_name");
+ $initiator_alias = getparam(-1, "initiator_alias");
+ @isid = getparam($id, "isid");
+ $tsih = getparam($id, "tsih");
+ $target_name = getparam($id, "target_name");
+ $target_alias = getparam($id, "target_alias");
+ $target_portal = getparam($id, "target_portal");
+ $target_address = getparam($id, "target_address");
+ $tpgt = getparam($id, "tpgt");
+ $first_burst = getparam($id, "first_burst");
+ $max_burst = getparam($id, "max_burst");
+ $max_r2t = getparam($id, "max_r2t");
+ $max_cnx = getparam($id, "max_cnx");
+ $erl = getparam($id, "erl");
+ $initial_r2t_en = getparam($id, "initial_r2t_en");
+ $imm_data_en = getparam($id, "imm_data_en");
+ $ifmarker_en = getparam($id, "ifmarker_en");
+ $ofmarker_en = getparam($id, "ofmarker_en");
+ $pdu_inorder_en = getparam($id, "pdu_inorder_en");
+ $dataseq_inorder_en = getparam($id, "dataseq_inorder_en");
+ $time2wait = getparam($id, "time2wait");
+ $time2retain = getparam($id, "time2retain");
+
+ #$max_recv_dlength = getparam($id, "max_recv_dlength");
+ #$max_xmit_dlength = getparam($id, "max_xmit_dlength");
+ #$hdrdgst_en = getparam($id, "hdrdgst_en");
+ #$datadgst_en = getparam($id, "datadgst_en");
+}
+
+sub add_conn {
+ my ($sid) = @_;
+}
+
+############################## end of library #################################
+
+sub discovery_list_cb {
+ my ($id, $target_name, $target_portal) = @_;
+ printf "#%d %s [%s]\n", $id, $target_name, $target_portal;
+}
+
+sub discovery_login_cb {
+ my ($id, $target_name, $target_portal) = @_;
+ my $cnx_cnt = 0;
+ my $sid;
+
+ do {
+ my $sock_fd = connection_login($target_name,
+ $target_portal, $cnx_cnt);
+ if ($sock_fd) {
+ if ($cnx_cnt == 0) { # leading connection
+ sysfs_save();
+ my @sids = get_session_ids();
+ my ($min,$max)=(sort @sids)[0,-1];
+ $sid = ++$max;
+ operation("tcp session add $sid") || exit 1;
+ }
+ operation("tcp connection add $sid ".
+ "$cnx_cnt $sock_fd") || exit 1;
+ require 'sys/syscall.ph';
+ syscall(&SYS_close, $sock_fd + 0);
+ }
+ } while (++$cnx_cnt < $max_cnx);
+}
+
+sub try_reopen {
+ my ($id) = @_;
+ my $sock_fd;
+
+ sysfs_restore($id);
+ $sock_fd = connection_login($target_name, $target_portal, 0);
+ if ($sock_fd) {
+ sysfs_save();
+ my $res = operation("tcp connection add $id 0 $sock_fd");
+ require 'sys/syscall.ph';
+ syscall(&SYS_close, $sock_fd + 0);
+ return $res;
+ }
+ return 0;
+}
+
+sub usage {
+print STDERR << "EOF";
+
+iSCSI Configuration Utility
+
+ usage: iscsiadm [-hv] [-f file] [-d addr:port] [-r id]
+
+ -h : this (help) message
+ -v : verbose output
+ -f file : file containing configuration, using /etc/iscsi.conf
+ if omited
+ -d addr:port : SendTargets method IP-address and port
+ -c sid:cid : Add one more connection 'cid' to the session 'sid'
+ -r sid[:cid] : Logout and remove iSCSI session specified by 'sid'
+ and all connections. Or remove only connection
+ specified by 'cid'
+ -a : process hotplug events
+
+ example: iscsiadm -f /mypath/my.conf 127.0.0.1:3260
+
+EOF
+exit;
+}
+
+sub init() {
+ my $opt_string = 'hvr:d:f:ac:';
+ getopts( "$opt_string", \%opt ) or usage();
+ usage() if $opt{h};
+ $iscsiadm_path = $0;
+ if ($iscsiadm_path =~ /^\.(.*)$/) {
+ $iscsiadm_path = cwd . $1;
+ }
+}
+
+sub hotplug_agent_check() {
+ if (! -x "/etc/hotplug/iscsi.agent") {
+ open(FH,">/etc/hotplug/iscsi.agent");
+ print FH "#!/bin/sh
+#
+# Generated by iscsiadm
+#
+# iSCSI hotplug agent for 2.6 kernels
+#
+# Example of event:
+#
+# PHYSDEVPATH=/devices/platform/host23
+# SUBSYSTEM=iscsi
+# DEVPATH=/class/iscsi/host2
+# PATH=/sbin:/bin:/usr/sbin:/usr/bin
+# ACTION=change
+#
+
+cd /etc/hotplug
+. ./hotplug.functions
+
+ISCSIADM=$iscsiadm_path
+
+case \$ACTION in
+
+change)
+ \$ISCSIADM -a
+ ;;
+
+*)
+ debug_mesg \"iSCSI \$ACTION event not supported\"
+ exit 1
+ ;;
+
+esac
+";
+ close(FH);
+ chmod(0755, "/etc/hotplug/iscsi.agent");
+ }
+}
+
+sub hotplug_event() {
+ if ($ENV{"SUBSYSTEM"} eq "iscsi" &&
+ $ENV{"ACTION"} eq "change" &&
+ $ENV{"DEVPATH"} =~ /^\/class\/iscsi\/host([0-9]+)$/) {
+ my $cnt = $reopen_attempts;
+ while($cnt) {
+ last if try_reopen($1);
+ sleep($reopen_timeout);
+ --$cnt if $cnt != -1;
+ }
+ exit 0;
+ } else {
+ fatal("not recognized hotplug event!");
+ }
+}
+
+################################ main program ################################
+
+init();
+hotplug_agent_check();
+if ($opt{f}) {
+ #
+ # Read Configuration
+ #
+ printf "not implemented\n";
+}
+if ($opt{a}) {
+ hotplug_event();
+}
+if (defined($opt{r})) {
+ logout($opt{r});
+ exit;
+}
+if (defined($opt{c})) {
+ add_conn($opt{c});
+ exit;
+}
+if ($opt{v}) {
+ if ($opt{d}) {
+ #
+ # Do SendTargets method discovery (verbose)
+ #
+ sendtargets_discovery(\&discovery_list_cb, $opt{d});
+ } else {
+ usage();
+ }
+} else {
+ if ($opt{d}) {
+ #
+ # Do SendTargets method discovery
+ #
+ sendtargets_discovery(\&discovery_login_cb, $opt{d});
+ } else {
+ show_sessions();
+ }
+}