diff options
-rw-r--r-- | Makefile | 18 | ||||
-rw-r--r-- | TODO | 30 | ||||
-rw-r--r-- | iscsi.h | 543 | ||||
-rw-r--r-- | iscsi_control.c | 912 | ||||
-rw-r--r-- | iscsi_control.h | 181 | ||||
-rw-r--r-- | iscsi_if.h | 147 | ||||
-rw-r--r-- | iscsi_tcp.c | 2238 | ||||
-rw-r--r-- | iscsi_tcp.h | 284 | ||||
-rwxr-xr-x | iscsiadm | 800 |
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 @@ -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(). @@ -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 = ¶m_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 = ¶m_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(); + } +} |