Commit e48354ce authored by Nicholas Bellinger's avatar Nicholas Bellinger

iscsi-target: Add iSCSI fabric support for target v4.1

The Linux-iSCSI.org target module is a full featured in-kernel
software implementation of iSCSI target mode (RFC-3720) for the
current WIP mainline target v4.1 infrastructure code for the v3.1
kernel.  More information can be found here:

http://linux-iscsi.org/wiki/ISCSI

This includes support for:

   * RFC-3720 defined request / response state machines and support for
     all defined iSCSI operation codes from Section 10.2.1.2 using libiscsi
     include/scsi/iscsi_proto.h PDU definitions
   * Target v4.1 compatible control plane using the generic layout in
     target_core_fabric_configfs.c and fabric dependent attributes
     within /sys/kernel/config/target/iscsi/ subdirectories.
   * Target v4.1 compatible iSCSI statistics based on RFC-4544 (iSCSI MIBS)
   * Support for IPv6 and IPv4 network portals in M:N mapping to TPGs
   * iSCSI Error Recovery Hierarchy support
   * Per iSCSI connection RX/TX thread pair scheduling affinity
   * crc32c + crc32c_intel SSEv4 instruction offload support using libcrypto
   * CHAP Authentication support using libcrypto
   * Conversion to use internal SGl allocation with iscsit_alloc_buffs() ->
     transport_generic_map_mem_to_cmd()

(nab: Fix iscsi_proto.h struct scsi_lun usage from linux-next in commit:
      iscsi: Use struct scsi_lun in iscsi structs instead of u8[8])
(nab: Fix 32-bit compile warnings)
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
Reviewed-by: default avatarAndy Grover <agrover@redhat.com>
Acked-by: default avatarRoland Dreier <roland@kernel.org>
Signed-off-by: default avatarNicholas A. Bellinger <nab@linux-iscsi.org>
parent 8304bbce
......@@ -31,5 +31,6 @@ config TCM_PSCSI
source "drivers/target/loopback/Kconfig"
source "drivers/target/tcm_fc/Kconfig"
source "drivers/target/iscsi/Kconfig"
endif
......@@ -24,5 +24,5 @@ obj-$(CONFIG_TCM_PSCSI) += target_core_pscsi.o
# Fabric modules
obj-$(CONFIG_LOOPBACK_TARGET) += loopback/
obj-$(CONFIG_TCM_FC) += tcm_fc/
obj-$(CONFIG_ISCSI_TARGET) += iscsi/
config ISCSI_TARGET
tristate "Linux-iSCSI.org iSCSI Target Mode Stack"
select CRYPTO
select CRYPTO_CRC32C
select CRYPTO_CRC32C_INTEL if X86
help
Say M here to enable the ConfigFS enabled Linux-iSCSI.org iSCSI
Target Mode Stack.
iscsi_target_mod-y += iscsi_target_parameters.o \
iscsi_target_seq_pdu_list.o \
iscsi_target_tq.o \
iscsi_target_auth.o \
iscsi_target_datain_values.o \
iscsi_target_device.o \
iscsi_target_erl0.o \
iscsi_target_erl1.o \
iscsi_target_erl2.o \
iscsi_target_login.o \
iscsi_target_nego.o \
iscsi_target_nodeattrib.o \
iscsi_target_tmr.o \
iscsi_target_tpg.o \
iscsi_target_util.o \
iscsi_target.o \
iscsi_target_configfs.o \
iscsi_target_stat.o
obj-$(CONFIG_ISCSI_TARGET) += iscsi_target_mod.o
This source diff could not be displayed because it is too large. You can view the blob instead.
#ifndef ISCSI_TARGET_H
#define ISCSI_TARGET_H
extern struct iscsi_tiqn *iscsit_get_tiqn_for_login(unsigned char *);
extern struct iscsi_tiqn *iscsit_get_tiqn(unsigned char *, int);
extern void iscsit_put_tiqn_for_login(struct iscsi_tiqn *);
extern struct iscsi_tiqn *iscsit_add_tiqn(unsigned char *);
extern void iscsit_del_tiqn(struct iscsi_tiqn *);
extern int iscsit_access_np(struct iscsi_np *, struct iscsi_portal_group *);
extern int iscsit_deaccess_np(struct iscsi_np *, struct iscsi_portal_group *);
extern struct iscsi_np *iscsit_add_np(struct __kernel_sockaddr_storage *,
char *, int);
extern int iscsit_reset_np_thread(struct iscsi_np *, struct iscsi_tpg_np *,
struct iscsi_portal_group *);
extern int iscsit_del_np(struct iscsi_np *);
extern int iscsit_add_reject_from_cmd(u8, int, int, unsigned char *, struct iscsi_cmd *);
extern int iscsit_logout_closesession(struct iscsi_cmd *, struct iscsi_conn *);
extern int iscsit_logout_closeconnection(struct iscsi_cmd *, struct iscsi_conn *);
extern int iscsit_logout_removeconnforrecovery(struct iscsi_cmd *, struct iscsi_conn *);
extern int iscsit_send_async_msg(struct iscsi_conn *, u16, u8, u8);
extern int iscsit_send_r2t(struct iscsi_cmd *, struct iscsi_conn *);
extern int iscsit_build_r2ts_for_cmd(struct iscsi_cmd *, struct iscsi_conn *, int);
extern void iscsit_thread_get_cpumask(struct iscsi_conn *);
extern int iscsi_target_tx_thread(void *);
extern int iscsi_target_rx_thread(void *);
extern int iscsit_close_connection(struct iscsi_conn *);
extern int iscsit_close_session(struct iscsi_session *);
extern void iscsit_fail_session(struct iscsi_session *);
extern int iscsit_free_session(struct iscsi_session *);
extern void iscsit_stop_session(struct iscsi_session *, int, int);
extern int iscsit_release_sessions_for_tpg(struct iscsi_portal_group *, int);
extern struct iscsit_global *iscsit_global;
extern struct target_fabric_configfs *lio_target_fabric_configfs;
extern struct kmem_cache *lio_dr_cache;
extern struct kmem_cache *lio_ooo_cache;
extern struct kmem_cache *lio_cmd_cache;
extern struct kmem_cache *lio_qr_cache;
extern struct kmem_cache *lio_r2t_cache;
#endif /*** ISCSI_TARGET_H ***/
/*******************************************************************************
* This file houses the main functions for the iSCSI CHAP support
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/string.h>
#include <linux/crypto.h>
#include <linux/err.h>
#include <linux/scatterlist.h>
#include "iscsi_target_core.h"
#include "iscsi_target_nego.h"
#include "iscsi_target_auth.h"
static unsigned char chap_asciihex_to_binaryhex(unsigned char val[2])
{
unsigned char result = 0;
/*
* MSB
*/
if ((val[0] >= 'a') && (val[0] <= 'f'))
result = ((val[0] - 'a' + 10) & 0xf) << 4;
else
if ((val[0] >= 'A') && (val[0] <= 'F'))
result = ((val[0] - 'A' + 10) & 0xf) << 4;
else /* digit */
result = ((val[0] - '0') & 0xf) << 4;
/*
* LSB
*/
if ((val[1] >= 'a') && (val[1] <= 'f'))
result |= ((val[1] - 'a' + 10) & 0xf);
else
if ((val[1] >= 'A') && (val[1] <= 'F'))
result |= ((val[1] - 'A' + 10) & 0xf);
else /* digit */
result |= ((val[1] - '0') & 0xf);
return result;
}
static int chap_string_to_hex(unsigned char *dst, unsigned char *src, int len)
{
int i, j = 0;
for (i = 0; i < len; i += 2) {
dst[j++] = (unsigned char) chap_asciihex_to_binaryhex(&src[i]);
}
dst[j] = '\0';
return j;
}
static void chap_binaryhex_to_asciihex(char *dst, char *src, int src_len)
{
int i;
for (i = 0; i < src_len; i++) {
sprintf(&dst[i*2], "%02x", (int) src[i] & 0xff);
}
}
static void chap_set_random(char *data, int length)
{
long r;
unsigned n;
while (length > 0) {
get_random_bytes(&r, sizeof(long));
r = r ^ (r >> 8);
r = r ^ (r >> 4);
n = r & 0x7;
get_random_bytes(&r, sizeof(long));
r = r ^ (r >> 8);
r = r ^ (r >> 5);
n = (n << 3) | (r & 0x7);
get_random_bytes(&r, sizeof(long));
r = r ^ (r >> 8);
r = r ^ (r >> 5);
n = (n << 2) | (r & 0x3);
*data++ = n;
length--;
}
}
static void chap_gen_challenge(
struct iscsi_conn *conn,
int caller,
char *c_str,
unsigned int *c_len)
{
unsigned char challenge_asciihex[CHAP_CHALLENGE_LENGTH * 2 + 1];
struct iscsi_chap *chap = (struct iscsi_chap *) conn->auth_protocol;
memset(challenge_asciihex, 0, CHAP_CHALLENGE_LENGTH * 2 + 1);
chap_set_random(chap->challenge, CHAP_CHALLENGE_LENGTH);
chap_binaryhex_to_asciihex(challenge_asciihex, chap->challenge,
CHAP_CHALLENGE_LENGTH);
/*
* Set CHAP_C, and copy the generated challenge into c_str.
*/
*c_len += sprintf(c_str + *c_len, "CHAP_C=0x%s", challenge_asciihex);
*c_len += 1;
pr_debug("[%s] Sending CHAP_C=0x%s\n\n", (caller) ? "server" : "client",
challenge_asciihex);
}
static struct iscsi_chap *chap_server_open(
struct iscsi_conn *conn,
struct iscsi_node_auth *auth,
const char *a_str,
char *aic_str,
unsigned int *aic_len)
{
struct iscsi_chap *chap;
if (!(auth->naf_flags & NAF_USERID_SET) ||
!(auth->naf_flags & NAF_PASSWORD_SET)) {
pr_err("CHAP user or password not set for"
" Initiator ACL\n");
return NULL;
}
conn->auth_protocol = kzalloc(sizeof(struct iscsi_chap), GFP_KERNEL);
if (!conn->auth_protocol)
return NULL;
chap = (struct iscsi_chap *) conn->auth_protocol;
/*
* We only support MD5 MDA presently.
*/
if (strncmp(a_str, "CHAP_A=5", 8)) {
pr_err("CHAP_A is not MD5.\n");
return NULL;
}
pr_debug("[server] Got CHAP_A=5\n");
/*
* Send back CHAP_A set to MD5.
*/
*aic_len = sprintf(aic_str, "CHAP_A=5");
*aic_len += 1;
chap->digest_type = CHAP_DIGEST_MD5;
pr_debug("[server] Sending CHAP_A=%d\n", chap->digest_type);
/*
* Set Identifier.
*/
chap->id = ISCSI_TPG_C(conn)->tpg_chap_id++;
*aic_len += sprintf(aic_str + *aic_len, "CHAP_I=%d", chap->id);
*aic_len += 1;
pr_debug("[server] Sending CHAP_I=%d\n", chap->id);
/*
* Generate Challenge.
*/
chap_gen_challenge(conn, 1, aic_str, aic_len);
return chap;
}
static void chap_close(struct iscsi_conn *conn)
{
kfree(conn->auth_protocol);
conn->auth_protocol = NULL;
}
static int chap_server_compute_md5(
struct iscsi_conn *conn,
struct iscsi_node_auth *auth,
char *nr_in_ptr,
char *nr_out_ptr,
unsigned int *nr_out_len)
{
char *endptr;
unsigned char id, digest[MD5_SIGNATURE_SIZE];
unsigned char type, response[MD5_SIGNATURE_SIZE * 2 + 2];
unsigned char identifier[10], *challenge = NULL;
unsigned char *challenge_binhex = NULL;
unsigned char client_digest[MD5_SIGNATURE_SIZE];
unsigned char server_digest[MD5_SIGNATURE_SIZE];
unsigned char chap_n[MAX_CHAP_N_SIZE], chap_r[MAX_RESPONSE_LENGTH];
struct iscsi_chap *chap = (struct iscsi_chap *) conn->auth_protocol;
struct crypto_hash *tfm;
struct hash_desc desc;
struct scatterlist sg;
int auth_ret = -1, ret, challenge_len;
memset(identifier, 0, 10);
memset(chap_n, 0, MAX_CHAP_N_SIZE);
memset(chap_r, 0, MAX_RESPONSE_LENGTH);
memset(digest, 0, MD5_SIGNATURE_SIZE);
memset(response, 0, MD5_SIGNATURE_SIZE * 2 + 2);
memset(client_digest, 0, MD5_SIGNATURE_SIZE);
memset(server_digest, 0, MD5_SIGNATURE_SIZE);
challenge = kzalloc(CHAP_CHALLENGE_STR_LEN, GFP_KERNEL);
if (!challenge) {
pr_err("Unable to allocate challenge buffer\n");
goto out;
}
challenge_binhex = kzalloc(CHAP_CHALLENGE_STR_LEN, GFP_KERNEL);
if (!challenge_binhex) {
pr_err("Unable to allocate challenge_binhex buffer\n");
goto out;
}
/*
* Extract CHAP_N.
*/
if (extract_param(nr_in_ptr, "CHAP_N", MAX_CHAP_N_SIZE, chap_n,
&type) < 0) {
pr_err("Could not find CHAP_N.\n");
goto out;
}
if (type == HEX) {
pr_err("Could not find CHAP_N.\n");
goto out;
}
if (memcmp(chap_n, auth->userid, strlen(auth->userid)) != 0) {
pr_err("CHAP_N values do not match!\n");
goto out;
}
pr_debug("[server] Got CHAP_N=%s\n", chap_n);
/*
* Extract CHAP_R.
*/
if (extract_param(nr_in_ptr, "CHAP_R", MAX_RESPONSE_LENGTH, chap_r,
&type) < 0) {
pr_err("Could not find CHAP_R.\n");
goto out;
}
if (type != HEX) {
pr_err("Could not find CHAP_R.\n");
goto out;
}
pr_debug("[server] Got CHAP_R=%s\n", chap_r);
chap_string_to_hex(client_digest, chap_r, strlen(chap_r));
tfm = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(tfm)) {
pr_err("Unable to allocate struct crypto_hash\n");
goto out;
}
desc.tfm = tfm;
desc.flags = 0;
ret = crypto_hash_init(&desc);
if (ret < 0) {
pr_err("crypto_hash_init() failed\n");
crypto_free_hash(tfm);
goto out;
}
sg_init_one(&sg, (void *)&chap->id, 1);
ret = crypto_hash_update(&desc, &sg, 1);
if (ret < 0) {
pr_err("crypto_hash_update() failed for id\n");
crypto_free_hash(tfm);
goto out;
}
sg_init_one(&sg, (void *)&auth->password, strlen(auth->password));
ret = crypto_hash_update(&desc, &sg, strlen(auth->password));
if (ret < 0) {
pr_err("crypto_hash_update() failed for password\n");
crypto_free_hash(tfm);
goto out;
}
sg_init_one(&sg, (void *)chap->challenge, CHAP_CHALLENGE_LENGTH);
ret = crypto_hash_update(&desc, &sg, CHAP_CHALLENGE_LENGTH);
if (ret < 0) {
pr_err("crypto_hash_update() failed for challenge\n");
crypto_free_hash(tfm);
goto out;
}
ret = crypto_hash_final(&desc, server_digest);
if (ret < 0) {
pr_err("crypto_hash_final() failed for server digest\n");
crypto_free_hash(tfm);
goto out;
}
crypto_free_hash(tfm);
chap_binaryhex_to_asciihex(response, server_digest, MD5_SIGNATURE_SIZE);
pr_debug("[server] MD5 Server Digest: %s\n", response);
if (memcmp(server_digest, client_digest, MD5_SIGNATURE_SIZE) != 0) {
pr_debug("[server] MD5 Digests do not match!\n\n");
goto out;
} else
pr_debug("[server] MD5 Digests match, CHAP connetication"
" successful.\n\n");
/*
* One way authentication has succeeded, return now if mutual
* authentication is not enabled.
*/
if (!auth->authenticate_target) {
kfree(challenge);
kfree(challenge_binhex);
return 0;
}
/*
* Get CHAP_I.
*/
if (extract_param(nr_in_ptr, "CHAP_I", 10, identifier, &type) < 0) {
pr_err("Could not find CHAP_I.\n");
goto out;
}
if (type == HEX)
id = (unsigned char)simple_strtoul((char *)&identifier[2],
&endptr, 0);
else
id = (unsigned char)simple_strtoul(identifier, &endptr, 0);
/*
* RFC 1994 says Identifier is no more than octet (8 bits).
*/
pr_debug("[server] Got CHAP_I=%d\n", id);
/*
* Get CHAP_C.
*/
if (extract_param(nr_in_ptr, "CHAP_C", CHAP_CHALLENGE_STR_LEN,
challenge, &type) < 0) {
pr_err("Could not find CHAP_C.\n");
goto out;
}
if (type != HEX) {
pr_err("Could not find CHAP_C.\n");
goto out;
}
pr_debug("[server] Got CHAP_C=%s\n", challenge);
challenge_len = chap_string_to_hex(challenge_binhex, challenge,
strlen(challenge));
if (!challenge_len) {
pr_err("Unable to convert incoming challenge\n");
goto out;
}
/*
* Generate CHAP_N and CHAP_R for mutual authentication.
*/
tfm = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(tfm)) {
pr_err("Unable to allocate struct crypto_hash\n");
goto out;
}
desc.tfm = tfm;
desc.flags = 0;
ret = crypto_hash_init(&desc);
if (ret < 0) {
pr_err("crypto_hash_init() failed\n");
crypto_free_hash(tfm);
goto out;
}
sg_init_one(&sg, (void *)&id, 1);
ret = crypto_hash_update(&desc, &sg, 1);
if (ret < 0) {
pr_err("crypto_hash_update() failed for id\n");
crypto_free_hash(tfm);
goto out;
}
sg_init_one(&sg, (void *)auth->password_mutual,
strlen(auth->password_mutual));
ret = crypto_hash_update(&desc, &sg, strlen(auth->password_mutual));
if (ret < 0) {
pr_err("crypto_hash_update() failed for"
" password_mutual\n");
crypto_free_hash(tfm);
goto out;
}
/*
* Convert received challenge to binary hex.
*/
sg_init_one(&sg, (void *)challenge_binhex, challenge_len);
ret = crypto_hash_update(&desc, &sg, challenge_len);
if (ret < 0) {
pr_err("crypto_hash_update() failed for ma challenge\n");
crypto_free_hash(tfm);
goto out;
}
ret = crypto_hash_final(&desc, digest);
if (ret < 0) {
pr_err("crypto_hash_final() failed for ma digest\n");
crypto_free_hash(tfm);
goto out;
}
crypto_free_hash(tfm);
/*
* Generate CHAP_N and CHAP_R.
*/
*nr_out_len = sprintf(nr_out_ptr, "CHAP_N=%s", auth->userid_mutual);
*nr_out_len += 1;
pr_debug("[server] Sending CHAP_N=%s\n", auth->userid_mutual);
/*
* Convert response from binary hex to ascii hext.
*/
chap_binaryhex_to_asciihex(response, digest, MD5_SIGNATURE_SIZE);
*nr_out_len += sprintf(nr_out_ptr + *nr_out_len, "CHAP_R=0x%s",
response);
*nr_out_len += 1;
pr_debug("[server] Sending CHAP_R=0x%s\n", response);
auth_ret = 0;
out:
kfree(challenge);
kfree(challenge_binhex);
return auth_ret;
}
static int chap_got_response(
struct iscsi_conn *conn,
struct iscsi_node_auth *auth,
char *nr_in_ptr,
char *nr_out_ptr,
unsigned int *nr_out_len)
{
struct iscsi_chap *chap = (struct iscsi_chap *) conn->auth_protocol;
switch (chap->digest_type) {
case CHAP_DIGEST_MD5:
if (chap_server_compute_md5(conn, auth, nr_in_ptr,
nr_out_ptr, nr_out_len) < 0)
return -1;
return 0;
default:
pr_err("Unknown CHAP digest type %d!\n",
chap->digest_type);
return -1;
}
}
u32 chap_main_loop(
struct iscsi_conn *conn,
struct iscsi_node_auth *auth,
char *in_text,
char *out_text,
int *in_len,
int *out_len)
{
struct iscsi_chap *chap = (struct iscsi_chap *) conn->auth_protocol;
if (!chap) {
chap = chap_server_open(conn, auth, in_text, out_text, out_len);
if (!chap)
return 2;
chap->chap_state = CHAP_STAGE_SERVER_AIC;
return 0;
} else if (chap->chap_state == CHAP_STAGE_SERVER_AIC) {
convert_null_to_semi(in_text, *in_len);
if (chap_got_response(conn, auth, in_text, out_text,
out_len) < 0) {
chap_close(conn);
return 2;
}
if (auth->authenticate_target)
chap->chap_state = CHAP_STAGE_SERVER_NR;
else
*out_len = 0;
chap_close(conn);
return 1;
}
return 2;
}
#ifndef _ISCSI_CHAP_H_
#define _ISCSI_CHAP_H_
#define CHAP_DIGEST_MD5 5
#define CHAP_DIGEST_SHA 6
#define CHAP_CHALLENGE_LENGTH 16
#define CHAP_CHALLENGE_STR_LEN 4096
#define MAX_RESPONSE_LENGTH 64 /* sufficient for MD5 */
#define MAX_CHAP_N_SIZE 512
#define MD5_SIGNATURE_SIZE 16 /* 16 bytes in a MD5 message digest */
#define CHAP_STAGE_CLIENT_A 1
#define CHAP_STAGE_SERVER_AIC 2
#define CHAP_STAGE_CLIENT_NR 3
#define CHAP_STAGE_CLIENT_NRIC 4
#define CHAP_STAGE_SERVER_NR 5
extern u32 chap_main_loop(struct iscsi_conn *, struct iscsi_node_auth *, char *, char *,
int *, int *);
struct iscsi_chap {
unsigned char digest_type;
unsigned char id;
unsigned char challenge[CHAP_CHALLENGE_LENGTH];
unsigned int authenticate_target;
unsigned int chap_state;
} ____cacheline_aligned;
#endif /*** _ISCSI_CHAP_H_ ***/
/*******************************************************************************
* This file contains the configfs implementation for iSCSI Target mode
* from the LIO-Target Project.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
****************************************************************************/
#include <linux/configfs.h>
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include <target/target_core_fabric_ops.h>
#include <target/target_core_fabric_configfs.h>
#include <target/target_core_fabric_lib.h>
#include <target/target_core_device.h>
#include <target/target_core_tpg.h>
#include <target/target_core_configfs.h>
#include <target/configfs_macros.h>
#include "iscsi_target_core.h"
#include "iscsi_target_parameters.h"
#include "iscsi_target_device.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_nodeattrib.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
#include "iscsi_target_stat.h"
#include "iscsi_target_configfs.h"
struct target_fabric_configfs *lio_target_fabric_configfs;
struct lio_target_configfs_attribute {
struct configfs_attribute attr;
ssize_t (*show)(void *, char *);
ssize_t (*store)(void *, const char *, size_t);
};
struct iscsi_portal_group *lio_get_tpg_from_tpg_item(
struct config_item *item,
struct iscsi_tiqn **tiqn_out)
{
struct se_portal_group *se_tpg = container_of(to_config_group(item),
struct se_portal_group, tpg_group);
struct iscsi_portal_group *tpg =
(struct iscsi_portal_group *)se_tpg->se_tpg_fabric_ptr;
int ret;
if (!tpg) {
pr_err("Unable to locate struct iscsi_portal_group "
"pointer\n");
return NULL;
}
ret = iscsit_get_tpg(tpg);
if (ret < 0)
return NULL;
*tiqn_out = tpg->tpg_tiqn;
return tpg;
}
/* Start items for lio_target_portal_cit */
static ssize_t lio_target_np_show_sctp(
struct se_tpg_np *se_tpg_np,
char *page)
{
struct iscsi_tpg_np *tpg_np = container_of(se_tpg_np,
struct iscsi_tpg_np, se_tpg_np);
struct iscsi_tpg_np *tpg_np_sctp;
ssize_t rb;
tpg_np_sctp = iscsit_tpg_locate_child_np(tpg_np, ISCSI_SCTP_TCP);
if (tpg_np_sctp)
rb = sprintf(page, "1\n");
else
rb = sprintf(page, "0\n");
return rb;
}
static ssize_t lio_target_np_store_sctp(
struct se_tpg_np *se_tpg_np,
const char *page,
size_t count)
{
struct iscsi_np *np;
struct iscsi_portal_group *tpg;
struct iscsi_tpg_np *tpg_np = container_of(se_tpg_np,
struct iscsi_tpg_np, se_tpg_np);
struct iscsi_tpg_np *tpg_np_sctp = NULL;
char *endptr;
u32 op;
int ret;
op = simple_strtoul(page, &endptr, 0);
if ((op != 1) && (op != 0)) {
pr_err("Illegal value for tpg_enable: %u\n", op);
return -EINVAL;
}
np = tpg_np->tpg_np;
if (!np) {
pr_err("Unable to locate struct iscsi_np from"
" struct iscsi_tpg_np\n");
return -EINVAL;
}
tpg = tpg_np->tpg;
if (iscsit_get_tpg(tpg) < 0)
return -EINVAL;
if (op) {
/*
* Use existing np->np_sockaddr for SCTP network portal reference
*/
tpg_np_sctp = iscsit_tpg_add_network_portal(tpg, &np->np_sockaddr,
np->np_ip, tpg_np, ISCSI_SCTP_TCP);
if (!tpg_np_sctp || IS_ERR(tpg_np_sctp))
goto out;
} else {
tpg_np_sctp = iscsit_tpg_locate_child_np(tpg_np, ISCSI_SCTP_TCP);
if (!tpg_np_sctp)
goto out;
ret = iscsit_tpg_del_network_portal(tpg, tpg_np_sctp);
if (ret < 0)
goto out;
}
iscsit_put_tpg(tpg);
return count;
out:
iscsit_put_tpg(tpg);
return -EINVAL;
}
TF_NP_BASE_ATTR(lio_target, sctp, S_IRUGO | S_IWUSR);
static struct configfs_attribute *lio_target_portal_attrs[] = {
&lio_target_np_sctp.attr,
NULL,
};
/* Stop items for lio_target_portal_cit */
/* Start items for lio_target_np_cit */
#define MAX_PORTAL_LEN 256
struct se_tpg_np *lio_target_call_addnptotpg(
struct se_portal_group *se_tpg,
struct config_group *group,
const char *name)
{
struct iscsi_portal_group *tpg;
struct iscsi_tpg_np *tpg_np;
char *str, *str2, *ip_str, *port_str;
struct __kernel_sockaddr_storage sockaddr;
struct sockaddr_in *sock_in;
struct sockaddr_in6 *sock_in6;
unsigned long port;
int ret;
char buf[MAX_PORTAL_LEN + 1];
if (strlen(name) > MAX_PORTAL_LEN) {
pr_err("strlen(name): %d exceeds MAX_PORTAL_LEN: %d\n",
(int)strlen(name), MAX_PORTAL_LEN);
return ERR_PTR(-EOVERFLOW);
}
memset(buf, 0, MAX_PORTAL_LEN + 1);
snprintf(buf, MAX_PORTAL_LEN, "%s", name);
memset(&sockaddr, 0, sizeof(struct __kernel_sockaddr_storage));
str = strstr(buf, "[");
if (str) {
const char *end;
str2 = strstr(str, "]");
if (!str2) {
pr_err("Unable to locate trailing \"]\""
" in IPv6 iSCSI network portal address\n");
return ERR_PTR(-EINVAL);
}
str++; /* Skip over leading "[" */
*str2 = '\0'; /* Terminate the IPv6 address */
str2++; /* Skip over the "]" */
port_str = strstr(str2, ":");
if (!port_str) {
pr_err("Unable to locate \":port\""
" in IPv6 iSCSI network portal address\n");
return ERR_PTR(-EINVAL);
}
*port_str = '\0'; /* Terminate string for IP */
port_str++; /* Skip over ":" */
ret = strict_strtoul(port_str, 0, &port);
if (ret < 0) {
pr_err("strict_strtoul() failed for port_str: %d\n", ret);
return ERR_PTR(ret);
}
sock_in6 = (struct sockaddr_in6 *)&sockaddr;
sock_in6->sin6_family = AF_INET6;
sock_in6->sin6_port = htons((unsigned short)port);
ret = in6_pton(str, IPV6_ADDRESS_SPACE,
(void *)&sock_in6->sin6_addr.in6_u, -1, &end);
if (ret <= 0) {
pr_err("in6_pton returned: %d\n", ret);
return ERR_PTR(-EINVAL);
}
} else {
str = ip_str = &buf[0];
port_str = strstr(ip_str, ":");
if (!port_str) {
pr_err("Unable to locate \":port\""
" in IPv4 iSCSI network portal address\n");
return ERR_PTR(-EINVAL);
}
*port_str = '\0'; /* Terminate string for IP */
port_str++; /* Skip over ":" */
ret = strict_strtoul(port_str, 0, &port);
if (ret < 0) {
pr_err("strict_strtoul() failed for port_str: %d\n", ret);
return ERR_PTR(ret);
}
sock_in = (struct sockaddr_in *)&sockaddr;
sock_in->sin_family = AF_INET;
sock_in->sin_port = htons((unsigned short)port);
sock_in->sin_addr.s_addr = in_aton(ip_str);
}
tpg = container_of(se_tpg, struct iscsi_portal_group, tpg_se_tpg);
ret = iscsit_get_tpg(tpg);
if (ret < 0)
return ERR_PTR(-EINVAL);
pr_debug("LIO_Target_ConfigFS: REGISTER -> %s TPGT: %hu"
" PORTAL: %s\n",
config_item_name(&se_tpg->se_tpg_wwn->wwn_group.cg_item),
tpg->tpgt, name);
/*
* Assume ISCSI_TCP by default. Other network portals for other
* iSCSI fabrics:
*
* Traditional iSCSI over SCTP (initial support)
* iSER/TCP (TODO, hardware available)
* iSER/SCTP (TODO, software emulation with osc-iwarp)
* iSER/IB (TODO, hardware available)
*
* can be enabled with atributes under
* sys/kernel/config/iscsi/$IQN/$TPG/np/$IP:$PORT/
*
*/
tpg_np = iscsit_tpg_add_network_portal(tpg, &sockaddr, str, NULL,
ISCSI_TCP);
if (IS_ERR(tpg_np)) {
iscsit_put_tpg(tpg);
return ERR_PTR(PTR_ERR(tpg_np));
}
pr_debug("LIO_Target_ConfigFS: addnptotpg done!\n");
iscsit_put_tpg(tpg);
return &tpg_np->se_tpg_np;
}
static void lio_target_call_delnpfromtpg(
struct se_tpg_np *se_tpg_np)
{
struct iscsi_portal_group *tpg;
struct iscsi_tpg_np *tpg_np;
struct se_portal_group *se_tpg;
int ret;
tpg_np = container_of(se_tpg_np, struct iscsi_tpg_np, se_tpg_np);
tpg = tpg_np->tpg;
ret = iscsit_get_tpg(tpg);
if (ret < 0)
return;
se_tpg = &tpg->tpg_se_tpg;
pr_debug("LIO_Target_ConfigFS: DEREGISTER -> %s TPGT: %hu"
" PORTAL: %s:%hu\n", config_item_name(&se_tpg->se_tpg_wwn->wwn_group.cg_item),
tpg->tpgt, tpg_np->tpg_np->np_ip, tpg_np->tpg_np->np_port);
ret = iscsit_tpg_del_network_portal(tpg, tpg_np);
if (ret < 0)
goto out;
pr_debug("LIO_Target_ConfigFS: delnpfromtpg done!\n");
out:
iscsit_put_tpg(tpg);
}
/* End items for lio_target_np_cit */
/* Start items for lio_target_nacl_attrib_cit */
#define DEF_NACL_ATTRIB(name) \
static ssize_t iscsi_nacl_attrib_show_##name( \
struct se_node_acl *se_nacl, \
char *page) \
{ \
struct iscsi_node_acl *nacl = container_of(se_nacl, struct iscsi_node_acl, \
se_node_acl); \
\
return sprintf(page, "%u\n", ISCSI_NODE_ATTRIB(nacl)->name); \
} \
\
static ssize_t iscsi_nacl_attrib_store_##name( \
struct se_node_acl *se_nacl, \
const char *page, \
size_t count) \
{ \
struct iscsi_node_acl *nacl = container_of(se_nacl, struct iscsi_node_acl, \
se_node_acl); \
char *endptr; \
u32 val; \
int ret; \
\
val = simple_strtoul(page, &endptr, 0); \
ret = iscsit_na_##name(nacl, val); \
if (ret < 0) \
return ret; \
\
return count; \
}
#define NACL_ATTR(_name, _mode) TF_NACL_ATTRIB_ATTR(iscsi, _name, _mode);
/*
* Define iscsi_node_attrib_s_dataout_timeout
*/
DEF_NACL_ATTRIB(dataout_timeout);
NACL_ATTR(dataout_timeout, S_IRUGO | S_IWUSR);
/*
* Define iscsi_node_attrib_s_dataout_timeout_retries
*/
DEF_NACL_ATTRIB(dataout_timeout_retries);
NACL_ATTR(dataout_timeout_retries, S_IRUGO | S_IWUSR);
/*
* Define iscsi_node_attrib_s_default_erl
*/
DEF_NACL_ATTRIB(default_erl);
NACL_ATTR(default_erl, S_IRUGO | S_IWUSR);
/*
* Define iscsi_node_attrib_s_nopin_timeout
*/
DEF_NACL_ATTRIB(nopin_timeout);
NACL_ATTR(nopin_timeout, S_IRUGO | S_IWUSR);
/*
* Define iscsi_node_attrib_s_nopin_response_timeout
*/
DEF_NACL_ATTRIB(nopin_response_timeout);
NACL_ATTR(nopin_response_timeout, S_IRUGO | S_IWUSR);
/*
* Define iscsi_node_attrib_s_random_datain_pdu_offsets
*/
DEF_NACL_ATTRIB(random_datain_pdu_offsets);
NACL_ATTR(random_datain_pdu_offsets, S_IRUGO | S_IWUSR);
/*
* Define iscsi_node_attrib_s_random_datain_seq_offsets
*/
DEF_NACL_ATTRIB(random_datain_seq_offsets);
NACL_ATTR(random_datain_seq_offsets, S_IRUGO | S_IWUSR);
/*
* Define iscsi_node_attrib_s_random_r2t_offsets
*/
DEF_NACL_ATTRIB(random_r2t_offsets);
NACL_ATTR(random_r2t_offsets, S_IRUGO | S_IWUSR);
static struct configfs_attribute *lio_target_nacl_attrib_attrs[] = {
&iscsi_nacl_attrib_dataout_timeout.attr,
&iscsi_nacl_attrib_dataout_timeout_retries.attr,
&iscsi_nacl_attrib_default_erl.attr,
&iscsi_nacl_attrib_nopin_timeout.attr,
&iscsi_nacl_attrib_nopin_response_timeout.attr,
&iscsi_nacl_attrib_random_datain_pdu_offsets.attr,
&iscsi_nacl_attrib_random_datain_seq_offsets.attr,
&iscsi_nacl_attrib_random_r2t_offsets.attr,
NULL,
};
/* End items for lio_target_nacl_attrib_cit */
/* Start items for lio_target_nacl_auth_cit */
#define __DEF_NACL_AUTH_STR(prefix, name, flags) \
static ssize_t __iscsi_##prefix##_show_##name( \
struct iscsi_node_acl *nacl, \
char *page) \
{ \
struct iscsi_node_auth *auth = &nacl->node_auth; \
\
if (!capable(CAP_SYS_ADMIN)) \
return -EPERM; \
return snprintf(page, PAGE_SIZE, "%s\n", auth->name); \
} \
\
static ssize_t __iscsi_##prefix##_store_##name( \
struct iscsi_node_acl *nacl, \
const char *page, \
size_t count) \
{ \
struct iscsi_node_auth *auth = &nacl->node_auth; \
\
if (!capable(CAP_SYS_ADMIN)) \
return -EPERM; \
\
snprintf(auth->name, PAGE_SIZE, "%s", page); \
if (!strncmp("NULL", auth->name, 4)) \
auth->naf_flags &= ~flags; \
else \
auth->naf_flags |= flags; \
\
if ((auth->naf_flags & NAF_USERID_IN_SET) && \
(auth->naf_flags & NAF_PASSWORD_IN_SET)) \
auth->authenticate_target = 1; \
else \
auth->authenticate_target = 0; \
\
return count; \
}
#define __DEF_NACL_AUTH_INT(prefix, name) \
static ssize_t __iscsi_##prefix##_show_##name( \
struct iscsi_node_acl *nacl, \
char *page) \
{ \
struct iscsi_node_auth *auth = &nacl->node_auth; \
\
if (!capable(CAP_SYS_ADMIN)) \
return -EPERM; \
\
return snprintf(page, PAGE_SIZE, "%d\n", auth->name); \
}
#define DEF_NACL_AUTH_STR(name, flags) \
__DEF_NACL_AUTH_STR(nacl_auth, name, flags) \
static ssize_t iscsi_nacl_auth_show_##name( \
struct se_node_acl *nacl, \
char *page) \
{ \
return __iscsi_nacl_auth_show_##name(container_of(nacl, \
struct iscsi_node_acl, se_node_acl), page); \
} \
static ssize_t iscsi_nacl_auth_store_##name( \
struct se_node_acl *nacl, \
const char *page, \
size_t count) \
{ \
return __iscsi_nacl_auth_store_##name(container_of(nacl, \
struct iscsi_node_acl, se_node_acl), page, count); \
}
#define DEF_NACL_AUTH_INT(name) \
__DEF_NACL_AUTH_INT(nacl_auth, name) \
static ssize_t iscsi_nacl_auth_show_##name( \
struct se_node_acl *nacl, \
char *page) \
{ \
return __iscsi_nacl_auth_show_##name(container_of(nacl, \
struct iscsi_node_acl, se_node_acl), page); \
}
#define AUTH_ATTR(_name, _mode) TF_NACL_AUTH_ATTR(iscsi, _name, _mode);
#define AUTH_ATTR_RO(_name) TF_NACL_AUTH_ATTR_RO(iscsi, _name);
/*
* One-way authentication userid
*/
DEF_NACL_AUTH_STR(userid, NAF_USERID_SET);
AUTH_ATTR(userid, S_IRUGO | S_IWUSR);
/*
* One-way authentication password
*/
DEF_NACL_AUTH_STR(password, NAF_PASSWORD_SET);
AUTH_ATTR(password, S_IRUGO | S_IWUSR);
/*
* Enforce mutual authentication
*/
DEF_NACL_AUTH_INT(authenticate_target);
AUTH_ATTR_RO(authenticate_target);
/*
* Mutual authentication userid
*/
DEF_NACL_AUTH_STR(userid_mutual, NAF_USERID_IN_SET);
AUTH_ATTR(userid_mutual, S_IRUGO | S_IWUSR);
/*
* Mutual authentication password
*/
DEF_NACL_AUTH_STR(password_mutual, NAF_PASSWORD_IN_SET);
AUTH_ATTR(password_mutual, S_IRUGO | S_IWUSR);
static struct configfs_attribute *lio_target_nacl_auth_attrs[] = {
&iscsi_nacl_auth_userid.attr,
&iscsi_nacl_auth_password.attr,
&iscsi_nacl_auth_authenticate_target.attr,
&iscsi_nacl_auth_userid_mutual.attr,
&iscsi_nacl_auth_password_mutual.attr,
NULL,
};
/* End items for lio_target_nacl_auth_cit */
/* Start items for lio_target_nacl_param_cit */
#define DEF_NACL_PARAM(name) \
static ssize_t iscsi_nacl_param_show_##name( \
struct se_node_acl *se_nacl, \
char *page) \
{ \
struct iscsi_session *sess; \
struct se_session *se_sess; \
ssize_t rb; \
\
spin_lock_bh(&se_nacl->nacl_sess_lock); \
se_sess = se_nacl->nacl_sess; \
if (!se_sess) { \
rb = snprintf(page, PAGE_SIZE, \
"No Active iSCSI Session\n"); \
} else { \
sess = se_sess->fabric_sess_ptr; \
rb = snprintf(page, PAGE_SIZE, "%u\n", \
(u32)sess->sess_ops->name); \
} \
spin_unlock_bh(&se_nacl->nacl_sess_lock); \
\
return rb; \
}
#define NACL_PARAM_ATTR(_name) TF_NACL_PARAM_ATTR_RO(iscsi, _name);
DEF_NACL_PARAM(MaxConnections);
NACL_PARAM_ATTR(MaxConnections);
DEF_NACL_PARAM(InitialR2T);
NACL_PARAM_ATTR(InitialR2T);
DEF_NACL_PARAM(ImmediateData);
NACL_PARAM_ATTR(ImmediateData);
DEF_NACL_PARAM(MaxBurstLength);
NACL_PARAM_ATTR(MaxBurstLength);
DEF_NACL_PARAM(FirstBurstLength);
NACL_PARAM_ATTR(FirstBurstLength);
DEF_NACL_PARAM(DefaultTime2Wait);
NACL_PARAM_ATTR(DefaultTime2Wait);
DEF_NACL_PARAM(DefaultTime2Retain);
NACL_PARAM_ATTR(DefaultTime2Retain);
DEF_NACL_PARAM(MaxOutstandingR2T);
NACL_PARAM_ATTR(MaxOutstandingR2T);
DEF_NACL_PARAM(DataPDUInOrder);
NACL_PARAM_ATTR(DataPDUInOrder);
DEF_NACL_PARAM(DataSequenceInOrder);
NACL_PARAM_ATTR(DataSequenceInOrder);
DEF_NACL_PARAM(ErrorRecoveryLevel);
NACL_PARAM_ATTR(ErrorRecoveryLevel);
static struct configfs_attribute *lio_target_nacl_param_attrs[] = {
&iscsi_nacl_param_MaxConnections.attr,
&iscsi_nacl_param_InitialR2T.attr,
&iscsi_nacl_param_ImmediateData.attr,
&iscsi_nacl_param_MaxBurstLength.attr,
&iscsi_nacl_param_FirstBurstLength.attr,
&iscsi_nacl_param_DefaultTime2Wait.attr,
&iscsi_nacl_param_DefaultTime2Retain.attr,
&iscsi_nacl_param_MaxOutstandingR2T.attr,
&iscsi_nacl_param_DataPDUInOrder.attr,
&iscsi_nacl_param_DataSequenceInOrder.attr,
&iscsi_nacl_param_ErrorRecoveryLevel.attr,
NULL,
};
/* End items for lio_target_nacl_param_cit */
/* Start items for lio_target_acl_cit */
static ssize_t lio_target_nacl_show_info(
struct se_node_acl *se_nacl,
char *page)
{
struct iscsi_session *sess;
struct iscsi_conn *conn;
struct se_session *se_sess;
ssize_t rb = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (!se_sess) {
rb += sprintf(page+rb, "No active iSCSI Session for Initiator"
" Endpoint: %s\n", se_nacl->initiatorname);
} else {
sess = se_sess->fabric_sess_ptr;
if (sess->sess_ops->InitiatorName)
rb += sprintf(page+rb, "InitiatorName: %s\n",
sess->sess_ops->InitiatorName);
if (sess->sess_ops->InitiatorAlias)
rb += sprintf(page+rb, "InitiatorAlias: %s\n",
sess->sess_ops->InitiatorAlias);
rb += sprintf(page+rb, "LIO Session ID: %u "
"ISID: 0x%02x %02x %02x %02x %02x %02x "
"TSIH: %hu ", sess->sid,
sess->isid[0], sess->isid[1], sess->isid[2],
sess->isid[3], sess->isid[4], sess->isid[5],
sess->tsih);
rb += sprintf(page+rb, "SessionType: %s\n",
(sess->sess_ops->SessionType) ?
"Discovery" : "Normal");
rb += sprintf(page+rb, "Session State: ");
switch (sess->session_state) {
case TARG_SESS_STATE_FREE:
rb += sprintf(page+rb, "TARG_SESS_FREE\n");
break;
case TARG_SESS_STATE_ACTIVE:
rb += sprintf(page+rb, "TARG_SESS_STATE_ACTIVE\n");
break;
case TARG_SESS_STATE_LOGGED_IN:
rb += sprintf(page+rb, "TARG_SESS_STATE_LOGGED_IN\n");
break;
case TARG_SESS_STATE_FAILED:
rb += sprintf(page+rb, "TARG_SESS_STATE_FAILED\n");
break;
case TARG_SESS_STATE_IN_CONTINUE:
rb += sprintf(page+rb, "TARG_SESS_STATE_IN_CONTINUE\n");
break;
default:
rb += sprintf(page+rb, "ERROR: Unknown Session"
" State!\n");
break;
}
rb += sprintf(page+rb, "---------------------[iSCSI Session"
" Values]-----------------------\n");
rb += sprintf(page+rb, " CmdSN/WR : CmdSN/WC : ExpCmdSN"
" : MaxCmdSN : ITT : TTT\n");
rb += sprintf(page+rb, " 0x%08x 0x%08x 0x%08x 0x%08x"
" 0x%08x 0x%08x\n",
sess->cmdsn_window,
(sess->max_cmd_sn - sess->exp_cmd_sn) + 1,
sess->exp_cmd_sn, sess->max_cmd_sn,
sess->init_task_tag, sess->targ_xfer_tag);
rb += sprintf(page+rb, "----------------------[iSCSI"
" Connections]-------------------------\n");
spin_lock(&sess->conn_lock);
list_for_each_entry(conn, &sess->sess_conn_list, conn_list) {
rb += sprintf(page+rb, "CID: %hu Connection"
" State: ", conn->cid);
switch (conn->conn_state) {
case TARG_CONN_STATE_FREE:
rb += sprintf(page+rb,
"TARG_CONN_STATE_FREE\n");
break;
case TARG_CONN_STATE_XPT_UP:
rb += sprintf(page+rb,
"TARG_CONN_STATE_XPT_UP\n");
break;
case TARG_CONN_STATE_IN_LOGIN:
rb += sprintf(page+rb,
"TARG_CONN_STATE_IN_LOGIN\n");
break;
case TARG_CONN_STATE_LOGGED_IN:
rb += sprintf(page+rb,
"TARG_CONN_STATE_LOGGED_IN\n");
break;
case TARG_CONN_STATE_IN_LOGOUT:
rb += sprintf(page+rb,
"TARG_CONN_STATE_IN_LOGOUT\n");
break;
case TARG_CONN_STATE_LOGOUT_REQUESTED:
rb += sprintf(page+rb,
"TARG_CONN_STATE_LOGOUT_REQUESTED\n");
break;
case TARG_CONN_STATE_CLEANUP_WAIT:
rb += sprintf(page+rb,
"TARG_CONN_STATE_CLEANUP_WAIT\n");
break;
default:
rb += sprintf(page+rb,
"ERROR: Unknown Connection State!\n");
break;
}
rb += sprintf(page+rb, " Address %s %s", conn->login_ip,
(conn->network_transport == ISCSI_TCP) ?
"TCP" : "SCTP");
rb += sprintf(page+rb, " StatSN: 0x%08x\n",
conn->stat_sn);
}
spin_unlock(&sess->conn_lock);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return rb;
}
TF_NACL_BASE_ATTR_RO(lio_target, info);
static ssize_t lio_target_nacl_show_cmdsn_depth(
struct se_node_acl *se_nacl,
char *page)
{
return sprintf(page, "%u\n", se_nacl->queue_depth);
}
static ssize_t lio_target_nacl_store_cmdsn_depth(
struct se_node_acl *se_nacl,
const char *page,
size_t count)
{
struct se_portal_group *se_tpg = se_nacl->se_tpg;
struct iscsi_portal_group *tpg = container_of(se_tpg,
struct iscsi_portal_group, tpg_se_tpg);
struct config_item *acl_ci, *tpg_ci, *wwn_ci;
char *endptr;
u32 cmdsn_depth = 0;
int ret;
cmdsn_depth = simple_strtoul(page, &endptr, 0);
if (cmdsn_depth > TA_DEFAULT_CMDSN_DEPTH_MAX) {
pr_err("Passed cmdsn_depth: %u exceeds"
" TA_DEFAULT_CMDSN_DEPTH_MAX: %u\n", cmdsn_depth,
TA_DEFAULT_CMDSN_DEPTH_MAX);
return -EINVAL;
}
acl_ci = &se_nacl->acl_group.cg_item;
if (!acl_ci) {
pr_err("Unable to locatel acl_ci\n");
return -EINVAL;
}
tpg_ci = &acl_ci->ci_parent->ci_group->cg_item;
if (!tpg_ci) {
pr_err("Unable to locate tpg_ci\n");
return -EINVAL;
}
wwn_ci = &tpg_ci->ci_group->cg_item;
if (!wwn_ci) {
pr_err("Unable to locate config_item wwn_ci\n");
return -EINVAL;
}
if (iscsit_get_tpg(tpg) < 0)
return -EINVAL;
/*
* iscsit_tpg_set_initiator_node_queue_depth() assumes force=1
*/
ret = iscsit_tpg_set_initiator_node_queue_depth(tpg,
config_item_name(acl_ci), cmdsn_depth, 1);
pr_debug("LIO_Target_ConfigFS: %s/%s Set CmdSN Window: %u for"
"InitiatorName: %s\n", config_item_name(wwn_ci),
config_item_name(tpg_ci), cmdsn_depth,
config_item_name(acl_ci));
iscsit_put_tpg(tpg);
return (!ret) ? count : (ssize_t)ret;
}
TF_NACL_BASE_ATTR(lio_target, cmdsn_depth, S_IRUGO | S_IWUSR);
static struct configfs_attribute *lio_target_initiator_attrs[] = {
&lio_target_nacl_info.attr,
&lio_target_nacl_cmdsn_depth.attr,
NULL,
};
static struct se_node_acl *lio_tpg_alloc_fabric_acl(
struct se_portal_group *se_tpg)
{
struct iscsi_node_acl *acl;
acl = kzalloc(sizeof(struct iscsi_node_acl), GFP_KERNEL);
if (!acl) {
pr_err("Unable to allocate memory for struct iscsi_node_acl\n");
return NULL;
}
return &acl->se_node_acl;
}
static struct se_node_acl *lio_target_make_nodeacl(
struct se_portal_group *se_tpg,
struct config_group *group,
const char *name)
{
struct config_group *stats_cg;
struct iscsi_node_acl *acl;
struct se_node_acl *se_nacl_new, *se_nacl;
struct iscsi_portal_group *tpg = container_of(se_tpg,
struct iscsi_portal_group, tpg_se_tpg);
u32 cmdsn_depth;
se_nacl_new = lio_tpg_alloc_fabric_acl(se_tpg);
if (!se_nacl_new)
return ERR_PTR(-ENOMEM);
acl = container_of(se_nacl_new, struct iscsi_node_acl,
se_node_acl);
cmdsn_depth = ISCSI_TPG_ATTRIB(tpg)->default_cmdsn_depth;
/*
* se_nacl_new may be released by core_tpg_add_initiator_node_acl()
* when converting a NdoeACL from demo mode -> explict
*/
se_nacl = core_tpg_add_initiator_node_acl(se_tpg, se_nacl_new,
name, cmdsn_depth);
if (IS_ERR(se_nacl))
return se_nacl;
stats_cg = &acl->se_node_acl.acl_fabric_stat_group;
stats_cg->default_groups = kzalloc(sizeof(struct config_group) * 2,
GFP_KERNEL);
if (!stats_cg->default_groups) {
pr_err("Unable to allocate memory for"
" stats_cg->default_groups\n");
core_tpg_del_initiator_node_acl(se_tpg, se_nacl, 1);
kfree(acl);
return ERR_PTR(-ENOMEM);
}
stats_cg->default_groups[0] = &NODE_STAT_GRPS(acl)->iscsi_sess_stats_group;
stats_cg->default_groups[1] = NULL;
config_group_init_type_name(&NODE_STAT_GRPS(acl)->iscsi_sess_stats_group,
"iscsi_sess_stats", &iscsi_stat_sess_cit);
return se_nacl;
}
static void lio_target_drop_nodeacl(
struct se_node_acl *se_nacl)
{
struct se_portal_group *se_tpg = se_nacl->se_tpg;
struct iscsi_node_acl *acl = container_of(se_nacl,
struct iscsi_node_acl, se_node_acl);
struct config_item *df_item;
struct config_group *stats_cg;
int i;
stats_cg = &acl->se_node_acl.acl_fabric_stat_group;
for (i = 0; stats_cg->default_groups[i]; i++) {
df_item = &stats_cg->default_groups[i]->cg_item;
stats_cg->default_groups[i] = NULL;
config_item_put(df_item);
}
kfree(stats_cg->default_groups);
core_tpg_del_initiator_node_acl(se_tpg, se_nacl, 1);
kfree(acl);
}
/* End items for lio_target_acl_cit */
/* Start items for lio_target_tpg_attrib_cit */
#define DEF_TPG_ATTRIB(name) \
\
static ssize_t iscsi_tpg_attrib_show_##name( \
struct se_portal_group *se_tpg, \
char *page) \
{ \
struct iscsi_portal_group *tpg = container_of(se_tpg, \
struct iscsi_portal_group, tpg_se_tpg); \
ssize_t rb; \
\
if (iscsit_get_tpg(tpg) < 0) \
return -EINVAL; \
\
rb = sprintf(page, "%u\n", ISCSI_TPG_ATTRIB(tpg)->name); \
iscsit_put_tpg(tpg); \
return rb; \
} \
\
static ssize_t iscsi_tpg_attrib_store_##name( \
struct se_portal_group *se_tpg, \
const char *page, \
size_t count) \
{ \
struct iscsi_portal_group *tpg = container_of(se_tpg, \
struct iscsi_portal_group, tpg_se_tpg); \
char *endptr; \
u32 val; \
int ret; \
\
if (iscsit_get_tpg(tpg) < 0) \
return -EINVAL; \
\
val = simple_strtoul(page, &endptr, 0); \
ret = iscsit_ta_##name(tpg, val); \
if (ret < 0) \
goto out; \
\
iscsit_put_tpg(tpg); \
return count; \
out: \
iscsit_put_tpg(tpg); \
return ret; \
}
#define TPG_ATTR(_name, _mode) TF_TPG_ATTRIB_ATTR(iscsi, _name, _mode);
/*
* Define iscsi_tpg_attrib_s_authentication
*/
DEF_TPG_ATTRIB(authentication);
TPG_ATTR(authentication, S_IRUGO | S_IWUSR);
/*
* Define iscsi_tpg_attrib_s_login_timeout
*/
DEF_TPG_ATTRIB(login_timeout);
TPG_ATTR(login_timeout, S_IRUGO | S_IWUSR);
/*
* Define iscsi_tpg_attrib_s_netif_timeout
*/
DEF_TPG_ATTRIB(netif_timeout);
TPG_ATTR(netif_timeout, S_IRUGO | S_IWUSR);
/*
* Define iscsi_tpg_attrib_s_generate_node_acls
*/
DEF_TPG_ATTRIB(generate_node_acls);
TPG_ATTR(generate_node_acls, S_IRUGO | S_IWUSR);
/*
* Define iscsi_tpg_attrib_s_default_cmdsn_depth
*/
DEF_TPG_ATTRIB(default_cmdsn_depth);
TPG_ATTR(default_cmdsn_depth, S_IRUGO | S_IWUSR);
/*
Define iscsi_tpg_attrib_s_cache_dynamic_acls
*/
DEF_TPG_ATTRIB(cache_dynamic_acls);
TPG_ATTR(cache_dynamic_acls, S_IRUGO | S_IWUSR);
/*
* Define iscsi_tpg_attrib_s_demo_mode_write_protect
*/
DEF_TPG_ATTRIB(demo_mode_write_protect);
TPG_ATTR(demo_mode_write_protect, S_IRUGO | S_IWUSR);
/*
* Define iscsi_tpg_attrib_s_prod_mode_write_protect
*/
DEF_TPG_ATTRIB(prod_mode_write_protect);
TPG_ATTR(prod_mode_write_protect, S_IRUGO | S_IWUSR);
static struct configfs_attribute *lio_target_tpg_attrib_attrs[] = {
&iscsi_tpg_attrib_authentication.attr,
&iscsi_tpg_attrib_login_timeout.attr,
&iscsi_tpg_attrib_netif_timeout.attr,
&iscsi_tpg_attrib_generate_node_acls.attr,
&iscsi_tpg_attrib_default_cmdsn_depth.attr,
&iscsi_tpg_attrib_cache_dynamic_acls.attr,
&iscsi_tpg_attrib_demo_mode_write_protect.attr,
&iscsi_tpg_attrib_prod_mode_write_protect.attr,
NULL,
};
/* End items for lio_target_tpg_attrib_cit */
/* Start items for lio_target_tpg_param_cit */
#define DEF_TPG_PARAM(name) \
static ssize_t iscsi_tpg_param_show_##name( \
struct se_portal_group *se_tpg, \
char *page) \
{ \
struct iscsi_portal_group *tpg = container_of(se_tpg, \
struct iscsi_portal_group, tpg_se_tpg); \
struct iscsi_param *param; \
ssize_t rb; \
\
if (iscsit_get_tpg(tpg) < 0) \
return -EINVAL; \
\
param = iscsi_find_param_from_key(__stringify(name), \
tpg->param_list); \
if (!param) { \
iscsit_put_tpg(tpg); \
return -EINVAL; \
} \
rb = snprintf(page, PAGE_SIZE, "%s\n", param->value); \
\
iscsit_put_tpg(tpg); \
return rb; \
} \
static ssize_t iscsi_tpg_param_store_##name( \
struct se_portal_group *se_tpg, \
const char *page, \
size_t count) \
{ \
struct iscsi_portal_group *tpg = container_of(se_tpg, \
struct iscsi_portal_group, tpg_se_tpg); \
char *buf; \
int ret; \
\
buf = kzalloc(PAGE_SIZE, GFP_KERNEL); \
if (!buf) \
return -ENOMEM; \
snprintf(buf, PAGE_SIZE, "%s=%s", __stringify(name), page); \
buf[strlen(buf)-1] = '\0'; /* Kill newline */ \
\
if (iscsit_get_tpg(tpg) < 0) { \
kfree(buf); \
return -EINVAL; \
} \
\
ret = iscsi_change_param_value(buf, tpg->param_list, 1); \
if (ret < 0) \
goto out; \
\
kfree(buf); \
iscsit_put_tpg(tpg); \
return count; \
out: \
kfree(buf); \
iscsit_put_tpg(tpg); \
return -EINVAL; \
}
#define TPG_PARAM_ATTR(_name, _mode) TF_TPG_PARAM_ATTR(iscsi, _name, _mode);
DEF_TPG_PARAM(AuthMethod);
TPG_PARAM_ATTR(AuthMethod, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(HeaderDigest);
TPG_PARAM_ATTR(HeaderDigest, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(DataDigest);
TPG_PARAM_ATTR(DataDigest, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(MaxConnections);
TPG_PARAM_ATTR(MaxConnections, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(TargetAlias);
TPG_PARAM_ATTR(TargetAlias, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(InitialR2T);
TPG_PARAM_ATTR(InitialR2T, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(ImmediateData);
TPG_PARAM_ATTR(ImmediateData, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(MaxRecvDataSegmentLength);
TPG_PARAM_ATTR(MaxRecvDataSegmentLength, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(MaxBurstLength);
TPG_PARAM_ATTR(MaxBurstLength, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(FirstBurstLength);
TPG_PARAM_ATTR(FirstBurstLength, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(DefaultTime2Wait);
TPG_PARAM_ATTR(DefaultTime2Wait, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(DefaultTime2Retain);
TPG_PARAM_ATTR(DefaultTime2Retain, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(MaxOutstandingR2T);
TPG_PARAM_ATTR(MaxOutstandingR2T, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(DataPDUInOrder);
TPG_PARAM_ATTR(DataPDUInOrder, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(DataSequenceInOrder);
TPG_PARAM_ATTR(DataSequenceInOrder, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(ErrorRecoveryLevel);
TPG_PARAM_ATTR(ErrorRecoveryLevel, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(IFMarker);
TPG_PARAM_ATTR(IFMarker, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(OFMarker);
TPG_PARAM_ATTR(OFMarker, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(IFMarkInt);
TPG_PARAM_ATTR(IFMarkInt, S_IRUGO | S_IWUSR);
DEF_TPG_PARAM(OFMarkInt);
TPG_PARAM_ATTR(OFMarkInt, S_IRUGO | S_IWUSR);
static struct configfs_attribute *lio_target_tpg_param_attrs[] = {
&iscsi_tpg_param_AuthMethod.attr,
&iscsi_tpg_param_HeaderDigest.attr,
&iscsi_tpg_param_DataDigest.attr,
&iscsi_tpg_param_MaxConnections.attr,
&iscsi_tpg_param_TargetAlias.attr,
&iscsi_tpg_param_InitialR2T.attr,
&iscsi_tpg_param_ImmediateData.attr,
&iscsi_tpg_param_MaxRecvDataSegmentLength.attr,
&iscsi_tpg_param_MaxBurstLength.attr,
&iscsi_tpg_param_FirstBurstLength.attr,
&iscsi_tpg_param_DefaultTime2Wait.attr,
&iscsi_tpg_param_DefaultTime2Retain.attr,
&iscsi_tpg_param_MaxOutstandingR2T.attr,
&iscsi_tpg_param_DataPDUInOrder.attr,
&iscsi_tpg_param_DataSequenceInOrder.attr,
&iscsi_tpg_param_ErrorRecoveryLevel.attr,
&iscsi_tpg_param_IFMarker.attr,
&iscsi_tpg_param_OFMarker.attr,
&iscsi_tpg_param_IFMarkInt.attr,
&iscsi_tpg_param_OFMarkInt.attr,
NULL,
};
/* End items for lio_target_tpg_param_cit */
/* Start items for lio_target_tpg_cit */
static ssize_t lio_target_tpg_show_enable(
struct se_portal_group *se_tpg,
char *page)
{
struct iscsi_portal_group *tpg = container_of(se_tpg,
struct iscsi_portal_group, tpg_se_tpg);
ssize_t len;
spin_lock(&tpg->tpg_state_lock);
len = sprintf(page, "%d\n",
(tpg->tpg_state == TPG_STATE_ACTIVE) ? 1 : 0);
spin_unlock(&tpg->tpg_state_lock);
return len;
}
static ssize_t lio_target_tpg_store_enable(
struct se_portal_group *se_tpg,
const char *page,
size_t count)
{
struct iscsi_portal_group *tpg = container_of(se_tpg,
struct iscsi_portal_group, tpg_se_tpg);
char *endptr;
u32 op;
int ret = 0;
op = simple_strtoul(page, &endptr, 0);
if ((op != 1) && (op != 0)) {
pr_err("Illegal value for tpg_enable: %u\n", op);
return -EINVAL;
}
ret = iscsit_get_tpg(tpg);
if (ret < 0)
return -EINVAL;
if (op) {
ret = iscsit_tpg_enable_portal_group(tpg);
if (ret < 0)
goto out;
} else {
/*
* iscsit_tpg_disable_portal_group() assumes force=1
*/
ret = iscsit_tpg_disable_portal_group(tpg, 1);
if (ret < 0)
goto out;
}
iscsit_put_tpg(tpg);
return count;
out:
iscsit_put_tpg(tpg);
return -EINVAL;
}
TF_TPG_BASE_ATTR(lio_target, enable, S_IRUGO | S_IWUSR);
static struct configfs_attribute *lio_target_tpg_attrs[] = {
&lio_target_tpg_enable.attr,
NULL,
};
/* End items for lio_target_tpg_cit */
/* Start items for lio_target_tiqn_cit */
struct se_portal_group *lio_target_tiqn_addtpg(
struct se_wwn *wwn,
struct config_group *group,
const char *name)
{
struct iscsi_portal_group *tpg;
struct iscsi_tiqn *tiqn;
char *tpgt_str, *end_ptr;
int ret = 0;
unsigned short int tpgt;
tiqn = container_of(wwn, struct iscsi_tiqn, tiqn_wwn);
/*
* Only tpgt_# directory groups can be created below
* target/iscsi/iqn.superturodiskarry/
*/
tpgt_str = strstr(name, "tpgt_");
if (!tpgt_str) {
pr_err("Unable to locate \"tpgt_#\" directory"
" group\n");
return NULL;
}
tpgt_str += 5; /* Skip ahead of "tpgt_" */
tpgt = (unsigned short int) simple_strtoul(tpgt_str, &end_ptr, 0);
tpg = iscsit_alloc_portal_group(tiqn, tpgt);
if (!tpg)
return NULL;
ret = core_tpg_register(
&lio_target_fabric_configfs->tf_ops,
wwn, &tpg->tpg_se_tpg, (void *)tpg,
TRANSPORT_TPG_TYPE_NORMAL);
if (ret < 0)
return NULL;
ret = iscsit_tpg_add_portal_group(tiqn, tpg);
if (ret != 0)
goto out;
pr_debug("LIO_Target_ConfigFS: REGISTER -> %s\n", tiqn->tiqn);
pr_debug("LIO_Target_ConfigFS: REGISTER -> Allocated TPG: %s\n",
name);
return &tpg->tpg_se_tpg;
out:
core_tpg_deregister(&tpg->tpg_se_tpg);
kfree(tpg);
return NULL;
}
void lio_target_tiqn_deltpg(struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg;
struct iscsi_tiqn *tiqn;
tpg = container_of(se_tpg, struct iscsi_portal_group, tpg_se_tpg);
tiqn = tpg->tpg_tiqn;
/*
* iscsit_tpg_del_portal_group() assumes force=1
*/
pr_debug("LIO_Target_ConfigFS: DEREGISTER -> Releasing TPG\n");
iscsit_tpg_del_portal_group(tiqn, tpg, 1);
}
/* End items for lio_target_tiqn_cit */
/* Start LIO-Target TIQN struct contig_item lio_target_cit */
static ssize_t lio_target_wwn_show_attr_lio_version(
struct target_fabric_configfs *tf,
char *page)
{
return sprintf(page, "RisingTide Systems Linux-iSCSI Target "ISCSIT_VERSION"\n");
}
TF_WWN_ATTR_RO(lio_target, lio_version);
static struct configfs_attribute *lio_target_wwn_attrs[] = {
&lio_target_wwn_lio_version.attr,
NULL,
};
struct se_wwn *lio_target_call_coreaddtiqn(
struct target_fabric_configfs *tf,
struct config_group *group,
const char *name)
{
struct config_group *stats_cg;
struct iscsi_tiqn *tiqn;
tiqn = iscsit_add_tiqn((unsigned char *)name);
if (IS_ERR(tiqn))
return ERR_PTR(PTR_ERR(tiqn));
/*
* Setup struct iscsi_wwn_stat_grps for se_wwn->fabric_stat_group.
*/
stats_cg = &tiqn->tiqn_wwn.fabric_stat_group;
stats_cg->default_groups = kzalloc(sizeof(struct config_group) * 6,
GFP_KERNEL);
if (!stats_cg->default_groups) {
pr_err("Unable to allocate memory for"
" stats_cg->default_groups\n");
iscsit_del_tiqn(tiqn);
return ERR_PTR(-ENOMEM);
}
stats_cg->default_groups[0] = &WWN_STAT_GRPS(tiqn)->iscsi_instance_group;
stats_cg->default_groups[1] = &WWN_STAT_GRPS(tiqn)->iscsi_sess_err_group;
stats_cg->default_groups[2] = &WWN_STAT_GRPS(tiqn)->iscsi_tgt_attr_group;
stats_cg->default_groups[3] = &WWN_STAT_GRPS(tiqn)->iscsi_login_stats_group;
stats_cg->default_groups[4] = &WWN_STAT_GRPS(tiqn)->iscsi_logout_stats_group;
stats_cg->default_groups[5] = NULL;
config_group_init_type_name(&WWN_STAT_GRPS(tiqn)->iscsi_instance_group,
"iscsi_instance", &iscsi_stat_instance_cit);
config_group_init_type_name(&WWN_STAT_GRPS(tiqn)->iscsi_sess_err_group,
"iscsi_sess_err", &iscsi_stat_sess_err_cit);
config_group_init_type_name(&WWN_STAT_GRPS(tiqn)->iscsi_tgt_attr_group,
"iscsi_tgt_attr", &iscsi_stat_tgt_attr_cit);
config_group_init_type_name(&WWN_STAT_GRPS(tiqn)->iscsi_login_stats_group,
"iscsi_login_stats", &iscsi_stat_login_cit);
config_group_init_type_name(&WWN_STAT_GRPS(tiqn)->iscsi_logout_stats_group,
"iscsi_logout_stats", &iscsi_stat_logout_cit);
pr_debug("LIO_Target_ConfigFS: REGISTER -> %s\n", tiqn->tiqn);
pr_debug("LIO_Target_ConfigFS: REGISTER -> Allocated Node:"
" %s\n", name);
return &tiqn->tiqn_wwn;
}
void lio_target_call_coredeltiqn(
struct se_wwn *wwn)
{
struct iscsi_tiqn *tiqn = container_of(wwn, struct iscsi_tiqn, tiqn_wwn);
struct config_item *df_item;
struct config_group *stats_cg;
int i;
stats_cg = &tiqn->tiqn_wwn.fabric_stat_group;
for (i = 0; stats_cg->default_groups[i]; i++) {
df_item = &stats_cg->default_groups[i]->cg_item;
stats_cg->default_groups[i] = NULL;
config_item_put(df_item);
}
kfree(stats_cg->default_groups);
pr_debug("LIO_Target_ConfigFS: DEREGISTER -> %s\n",
tiqn->tiqn);
iscsit_del_tiqn(tiqn);
}
/* End LIO-Target TIQN struct contig_lio_target_cit */
/* Start lio_target_discovery_auth_cit */
#define DEF_DISC_AUTH_STR(name, flags) \
__DEF_NACL_AUTH_STR(disc, name, flags) \
static ssize_t iscsi_disc_show_##name( \
struct target_fabric_configfs *tf, \
char *page) \
{ \
return __iscsi_disc_show_##name(&iscsit_global->discovery_acl, \
page); \
} \
static ssize_t iscsi_disc_store_##name( \
struct target_fabric_configfs *tf, \
const char *page, \
size_t count) \
{ \
return __iscsi_disc_store_##name(&iscsit_global->discovery_acl, \
page, count); \
}
#define DEF_DISC_AUTH_INT(name) \
__DEF_NACL_AUTH_INT(disc, name) \
static ssize_t iscsi_disc_show_##name( \
struct target_fabric_configfs *tf, \
char *page) \
{ \
return __iscsi_disc_show_##name(&iscsit_global->discovery_acl, \
page); \
}
#define DISC_AUTH_ATTR(_name, _mode) TF_DISC_ATTR(iscsi, _name, _mode)
#define DISC_AUTH_ATTR_RO(_name) TF_DISC_ATTR_RO(iscsi, _name)
/*
* One-way authentication userid
*/
DEF_DISC_AUTH_STR(userid, NAF_USERID_SET);
DISC_AUTH_ATTR(userid, S_IRUGO | S_IWUSR);
/*
* One-way authentication password
*/
DEF_DISC_AUTH_STR(password, NAF_PASSWORD_SET);
DISC_AUTH_ATTR(password, S_IRUGO | S_IWUSR);
/*
* Enforce mutual authentication
*/
DEF_DISC_AUTH_INT(authenticate_target);
DISC_AUTH_ATTR_RO(authenticate_target);
/*
* Mutual authentication userid
*/
DEF_DISC_AUTH_STR(userid_mutual, NAF_USERID_IN_SET);
DISC_AUTH_ATTR(userid_mutual, S_IRUGO | S_IWUSR);
/*
* Mutual authentication password
*/
DEF_DISC_AUTH_STR(password_mutual, NAF_PASSWORD_IN_SET);
DISC_AUTH_ATTR(password_mutual, S_IRUGO | S_IWUSR);
/*
* enforce_discovery_auth
*/
static ssize_t iscsi_disc_show_enforce_discovery_auth(
struct target_fabric_configfs *tf,
char *page)
{
struct iscsi_node_auth *discovery_auth = &iscsit_global->discovery_acl.node_auth;
return sprintf(page, "%d\n", discovery_auth->enforce_discovery_auth);
}
static ssize_t iscsi_disc_store_enforce_discovery_auth(
struct target_fabric_configfs *tf,
const char *page,
size_t count)
{
struct iscsi_param *param;
struct iscsi_portal_group *discovery_tpg = iscsit_global->discovery_tpg;
char *endptr;
u32 op;
op = simple_strtoul(page, &endptr, 0);
if ((op != 1) && (op != 0)) {
pr_err("Illegal value for enforce_discovery_auth:"
" %u\n", op);
return -EINVAL;
}
if (!discovery_tpg) {
pr_err("iscsit_global->discovery_tpg is NULL\n");
return -EINVAL;
}
param = iscsi_find_param_from_key(AUTHMETHOD,
discovery_tpg->param_list);
if (!param)
return -EINVAL;
if (op) {
/*
* Reset the AuthMethod key to CHAP.
*/
if (iscsi_update_param_value(param, CHAP) < 0)
return -EINVAL;
discovery_tpg->tpg_attrib.authentication = 1;
iscsit_global->discovery_acl.node_auth.enforce_discovery_auth = 1;
pr_debug("LIO-CORE[0] Successfully enabled"
" authentication enforcement for iSCSI"
" Discovery TPG\n");
} else {
/*
* Reset the AuthMethod key to CHAP,None
*/
if (iscsi_update_param_value(param, "CHAP,None") < 0)
return -EINVAL;
discovery_tpg->tpg_attrib.authentication = 0;
iscsit_global->discovery_acl.node_auth.enforce_discovery_auth = 0;
pr_debug("LIO-CORE[0] Successfully disabled"
" authentication enforcement for iSCSI"
" Discovery TPG\n");
}
return count;
}
DISC_AUTH_ATTR(enforce_discovery_auth, S_IRUGO | S_IWUSR);
static struct configfs_attribute *lio_target_discovery_auth_attrs[] = {
&iscsi_disc_userid.attr,
&iscsi_disc_password.attr,
&iscsi_disc_authenticate_target.attr,
&iscsi_disc_userid_mutual.attr,
&iscsi_disc_password_mutual.attr,
&iscsi_disc_enforce_discovery_auth.attr,
NULL,
};
/* End lio_target_discovery_auth_cit */
/* Start functions for target_core_fabric_ops */
static char *iscsi_get_fabric_name(void)
{
return "iSCSI";
}
static u32 iscsi_get_task_tag(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
return cmd->init_task_tag;
}
static int iscsi_get_cmd_state(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
return cmd->i_state;
}
static int iscsi_is_state_remove(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
return (cmd->i_state == ISTATE_REMOVE);
}
static int lio_sess_logged_in(struct se_session *se_sess)
{
struct iscsi_session *sess = se_sess->fabric_sess_ptr;
int ret;
/*
* Called with spin_lock_bh(&tpg_lock); and
* spin_lock(&se_tpg->session_lock); held.
*/
spin_lock(&sess->conn_lock);
ret = (sess->session_state != TARG_SESS_STATE_LOGGED_IN);
spin_unlock(&sess->conn_lock);
return ret;
}
static u32 lio_sess_get_index(struct se_session *se_sess)
{
struct iscsi_session *sess = se_sess->fabric_sess_ptr;
return sess->session_index;
}
static u32 lio_sess_get_initiator_sid(
struct se_session *se_sess,
unsigned char *buf,
u32 size)
{
struct iscsi_session *sess = se_sess->fabric_sess_ptr;
/*
* iSCSI Initiator Session Identifier from RFC-3720.
*/
return snprintf(buf, size, "%02x%02x%02x%02x%02x%02x",
sess->isid[0], sess->isid[1], sess->isid[2],
sess->isid[3], sess->isid[4], sess->isid[5]);
}
static int lio_queue_data_in(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
cmd->i_state = ISTATE_SEND_DATAIN;
iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state);
return 0;
}
static int lio_write_pending(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
if (!cmd->immediate_data && !cmd->unsolicited_data)
return iscsit_build_r2ts_for_cmd(cmd, cmd->conn, 1);
return 0;
}
static int lio_write_pending_status(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
int ret;
spin_lock_bh(&cmd->istate_lock);
ret = !(cmd->cmd_flags & ICF_GOT_LAST_DATAOUT);
spin_unlock_bh(&cmd->istate_lock);
return ret;
}
static int lio_queue_status(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
cmd->i_state = ISTATE_SEND_STATUS;
iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state);
return 0;
}
static u16 lio_set_fabric_sense_len(struct se_cmd *se_cmd, u32 sense_length)
{
unsigned char *buffer = se_cmd->sense_buffer;
/*
* From RFC-3720 10.4.7. Data Segment - Sense and Response Data Segment
* 16-bit SenseLength.
*/
buffer[0] = ((sense_length >> 8) & 0xff);
buffer[1] = (sense_length & 0xff);
/*
* Return two byte offset into allocated sense_buffer.
*/
return 2;
}
static u16 lio_get_fabric_sense_len(void)
{
/*
* Return two byte offset into allocated sense_buffer.
*/
return 2;
}
static int lio_queue_tm_rsp(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
cmd->i_state = ISTATE_SEND_TASKMGTRSP;
iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state);
return 0;
}
static char *lio_tpg_get_endpoint_wwn(struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg = se_tpg->se_tpg_fabric_ptr;
return &tpg->tpg_tiqn->tiqn[0];
}
static u16 lio_tpg_get_tag(struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg = se_tpg->se_tpg_fabric_ptr;
return tpg->tpgt;
}
static u32 lio_tpg_get_default_depth(struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg = se_tpg->se_tpg_fabric_ptr;
return ISCSI_TPG_ATTRIB(tpg)->default_cmdsn_depth;
}
static int lio_tpg_check_demo_mode(struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg = se_tpg->se_tpg_fabric_ptr;
return ISCSI_TPG_ATTRIB(tpg)->generate_node_acls;
}
static int lio_tpg_check_demo_mode_cache(struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg = se_tpg->se_tpg_fabric_ptr;
return ISCSI_TPG_ATTRIB(tpg)->cache_dynamic_acls;
}
static int lio_tpg_check_demo_mode_write_protect(
struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg = se_tpg->se_tpg_fabric_ptr;
return ISCSI_TPG_ATTRIB(tpg)->demo_mode_write_protect;
}
static int lio_tpg_check_prod_mode_write_protect(
struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg = se_tpg->se_tpg_fabric_ptr;
return ISCSI_TPG_ATTRIB(tpg)->prod_mode_write_protect;
}
static void lio_tpg_release_fabric_acl(
struct se_portal_group *se_tpg,
struct se_node_acl *se_acl)
{
struct iscsi_node_acl *acl = container_of(se_acl,
struct iscsi_node_acl, se_node_acl);
kfree(acl);
}
/*
* Called with spin_lock_bh(struct se_portal_group->session_lock) held..
*
* Also, this function calls iscsit_inc_session_usage_count() on the
* struct iscsi_session in question.
*/
static int lio_tpg_shutdown_session(struct se_session *se_sess)
{
struct iscsi_session *sess = se_sess->fabric_sess_ptr;
spin_lock(&sess->conn_lock);
if (atomic_read(&sess->session_fall_back_to_erl0) ||
atomic_read(&sess->session_logout) ||
(sess->time2retain_timer_flags & ISCSI_TF_EXPIRED)) {
spin_unlock(&sess->conn_lock);
return 0;
}
atomic_set(&sess->session_reinstatement, 1);
spin_unlock(&sess->conn_lock);
iscsit_inc_session_usage_count(sess);
iscsit_stop_time2retain_timer(sess);
return 1;
}
/*
* Calls iscsit_dec_session_usage_count() as inverse of
* lio_tpg_shutdown_session()
*/
static void lio_tpg_close_session(struct se_session *se_sess)
{
struct iscsi_session *sess = se_sess->fabric_sess_ptr;
/*
* If the iSCSI Session for the iSCSI Initiator Node exists,
* forcefully shutdown the iSCSI NEXUS.
*/
iscsit_stop_session(sess, 1, 1);
iscsit_dec_session_usage_count(sess);
iscsit_close_session(sess);
}
static void lio_tpg_stop_session(
struct se_session *se_sess,
int sess_sleep,
int conn_sleep)
{
struct iscsi_session *sess = se_sess->fabric_sess_ptr;
iscsit_stop_session(sess, sess_sleep, conn_sleep);
}
static void lio_tpg_fall_back_to_erl0(struct se_session *se_sess)
{
struct iscsi_session *sess = se_sess->fabric_sess_ptr;
iscsit_fall_back_to_erl0(sess);
}
static u32 lio_tpg_get_inst_index(struct se_portal_group *se_tpg)
{
struct iscsi_portal_group *tpg = se_tpg->se_tpg_fabric_ptr;
return tpg->tpg_tiqn->tiqn_index;
}
static void lio_set_default_node_attributes(struct se_node_acl *se_acl)
{
struct iscsi_node_acl *acl = container_of(se_acl, struct iscsi_node_acl,
se_node_acl);
ISCSI_NODE_ATTRIB(acl)->nacl = acl;
iscsit_set_default_node_attribues(acl);
}
static void lio_release_cmd(struct se_cmd *se_cmd)
{
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
iscsit_release_cmd(cmd);
}
/* End functions for target_core_fabric_ops */
int iscsi_target_register_configfs(void)
{
struct target_fabric_configfs *fabric;
int ret;
lio_target_fabric_configfs = NULL;
fabric = target_fabric_configfs_init(THIS_MODULE, "iscsi");
if (IS_ERR(fabric)) {
pr_err("target_fabric_configfs_init() for"
" LIO-Target failed!\n");
return PTR_ERR(fabric);
}
/*
* Setup the fabric API of function pointers used by target_core_mod..
*/
fabric->tf_ops.get_fabric_name = &iscsi_get_fabric_name;
fabric->tf_ops.get_fabric_proto_ident = &iscsi_get_fabric_proto_ident;
fabric->tf_ops.tpg_get_wwn = &lio_tpg_get_endpoint_wwn;
fabric->tf_ops.tpg_get_tag = &lio_tpg_get_tag;
fabric->tf_ops.tpg_get_default_depth = &lio_tpg_get_default_depth;
fabric->tf_ops.tpg_get_pr_transport_id = &iscsi_get_pr_transport_id;
fabric->tf_ops.tpg_get_pr_transport_id_len =
&iscsi_get_pr_transport_id_len;
fabric->tf_ops.tpg_parse_pr_out_transport_id =
&iscsi_parse_pr_out_transport_id;
fabric->tf_ops.tpg_check_demo_mode = &lio_tpg_check_demo_mode;
fabric->tf_ops.tpg_check_demo_mode_cache =
&lio_tpg_check_demo_mode_cache;
fabric->tf_ops.tpg_check_demo_mode_write_protect =
&lio_tpg_check_demo_mode_write_protect;
fabric->tf_ops.tpg_check_prod_mode_write_protect =
&lio_tpg_check_prod_mode_write_protect;
fabric->tf_ops.tpg_alloc_fabric_acl = &lio_tpg_alloc_fabric_acl;
fabric->tf_ops.tpg_release_fabric_acl = &lio_tpg_release_fabric_acl;
fabric->tf_ops.tpg_get_inst_index = &lio_tpg_get_inst_index;
fabric->tf_ops.release_cmd = &lio_release_cmd;
fabric->tf_ops.shutdown_session = &lio_tpg_shutdown_session;
fabric->tf_ops.close_session = &lio_tpg_close_session;
fabric->tf_ops.stop_session = &lio_tpg_stop_session;
fabric->tf_ops.fall_back_to_erl0 = &lio_tpg_fall_back_to_erl0;
fabric->tf_ops.sess_logged_in = &lio_sess_logged_in;
fabric->tf_ops.sess_get_index = &lio_sess_get_index;
fabric->tf_ops.sess_get_initiator_sid = &lio_sess_get_initiator_sid;
fabric->tf_ops.write_pending = &lio_write_pending;
fabric->tf_ops.write_pending_status = &lio_write_pending_status;
fabric->tf_ops.set_default_node_attributes =
&lio_set_default_node_attributes;
fabric->tf_ops.get_task_tag = &iscsi_get_task_tag;
fabric->tf_ops.get_cmd_state = &iscsi_get_cmd_state;
fabric->tf_ops.queue_data_in = &lio_queue_data_in;
fabric->tf_ops.queue_status = &lio_queue_status;
fabric->tf_ops.queue_tm_rsp = &lio_queue_tm_rsp;
fabric->tf_ops.set_fabric_sense_len = &lio_set_fabric_sense_len;
fabric->tf_ops.get_fabric_sense_len = &lio_get_fabric_sense_len;
fabric->tf_ops.is_state_remove = &iscsi_is_state_remove;
/*
* Setup function pointers for generic logic in target_core_fabric_configfs.c
*/
fabric->tf_ops.fabric_make_wwn = &lio_target_call_coreaddtiqn;
fabric->tf_ops.fabric_drop_wwn = &lio_target_call_coredeltiqn;
fabric->tf_ops.fabric_make_tpg = &lio_target_tiqn_addtpg;
fabric->tf_ops.fabric_drop_tpg = &lio_target_tiqn_deltpg;
fabric->tf_ops.fabric_post_link = NULL;
fabric->tf_ops.fabric_pre_unlink = NULL;
fabric->tf_ops.fabric_make_np = &lio_target_call_addnptotpg;
fabric->tf_ops.fabric_drop_np = &lio_target_call_delnpfromtpg;
fabric->tf_ops.fabric_make_nodeacl = &lio_target_make_nodeacl;
fabric->tf_ops.fabric_drop_nodeacl = &lio_target_drop_nodeacl;
/*
* Setup default attribute lists for various fabric->tf_cit_tmpl
* sturct config_item_type's
*/
TF_CIT_TMPL(fabric)->tfc_discovery_cit.ct_attrs = lio_target_discovery_auth_attrs;
TF_CIT_TMPL(fabric)->tfc_wwn_cit.ct_attrs = lio_target_wwn_attrs;
TF_CIT_TMPL(fabric)->tfc_tpg_base_cit.ct_attrs = lio_target_tpg_attrs;
TF_CIT_TMPL(fabric)->tfc_tpg_attrib_cit.ct_attrs = lio_target_tpg_attrib_attrs;
TF_CIT_TMPL(fabric)->tfc_tpg_param_cit.ct_attrs = lio_target_tpg_param_attrs;
TF_CIT_TMPL(fabric)->tfc_tpg_np_base_cit.ct_attrs = lio_target_portal_attrs;
TF_CIT_TMPL(fabric)->tfc_tpg_nacl_base_cit.ct_attrs = lio_target_initiator_attrs;
TF_CIT_TMPL(fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = lio_target_nacl_attrib_attrs;
TF_CIT_TMPL(fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = lio_target_nacl_auth_attrs;
TF_CIT_TMPL(fabric)->tfc_tpg_nacl_param_cit.ct_attrs = lio_target_nacl_param_attrs;
ret = target_fabric_configfs_register(fabric);
if (ret < 0) {
pr_err("target_fabric_configfs_register() for"
" LIO-Target failed!\n");
target_fabric_configfs_free(fabric);
return ret;
}
lio_target_fabric_configfs = fabric;
pr_debug("LIO_TARGET[0] - Set fabric ->"
" lio_target_fabric_configfs\n");
return 0;
}
void iscsi_target_deregister_configfs(void)
{
if (!lio_target_fabric_configfs)
return;
/*
* Shutdown discovery sessions and disable discovery TPG
*/
if (iscsit_global->discovery_tpg)
iscsit_tpg_disable_portal_group(iscsit_global->discovery_tpg, 1);
target_fabric_configfs_deregister(lio_target_fabric_configfs);
lio_target_fabric_configfs = NULL;
pr_debug("LIO_TARGET[0] - Cleared"
" lio_target_fabric_configfs\n");
}
#ifndef ISCSI_TARGET_CONFIGFS_H
#define ISCSI_TARGET_CONFIGFS_H
extern int iscsi_target_register_configfs(void);
extern void iscsi_target_deregister_configfs(void);
#endif /* ISCSI_TARGET_CONFIGFS_H */
#ifndef ISCSI_TARGET_CORE_H
#define ISCSI_TARGET_CORE_H
#include <linux/in.h>
#include <linux/configfs.h>
#include <net/sock.h>
#include <net/tcp.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#define ISCSIT_VERSION "v4.1.0-rc1"
#define ISCSI_MAX_DATASN_MISSING_COUNT 16
#define ISCSI_TX_THREAD_TCP_TIMEOUT 2
#define ISCSI_RX_THREAD_TCP_TIMEOUT 2
#define SECONDS_FOR_ASYNC_LOGOUT 10
#define SECONDS_FOR_ASYNC_TEXT 10
#define SECONDS_FOR_LOGOUT_COMP 15
#define WHITE_SPACE " \t\v\f\n\r"
/* struct iscsi_node_attrib sanity values */
#define NA_DATAOUT_TIMEOUT 3
#define NA_DATAOUT_TIMEOUT_MAX 60
#define NA_DATAOUT_TIMEOUT_MIX 2
#define NA_DATAOUT_TIMEOUT_RETRIES 5
#define NA_DATAOUT_TIMEOUT_RETRIES_MAX 15
#define NA_DATAOUT_TIMEOUT_RETRIES_MIN 1
#define NA_NOPIN_TIMEOUT 5
#define NA_NOPIN_TIMEOUT_MAX 60
#define NA_NOPIN_TIMEOUT_MIN 3
#define NA_NOPIN_RESPONSE_TIMEOUT 5
#define NA_NOPIN_RESPONSE_TIMEOUT_MAX 60
#define NA_NOPIN_RESPONSE_TIMEOUT_MIN 3
#define NA_RANDOM_DATAIN_PDU_OFFSETS 0
#define NA_RANDOM_DATAIN_SEQ_OFFSETS 0
#define NA_RANDOM_R2T_OFFSETS 0
#define NA_DEFAULT_ERL 0
#define NA_DEFAULT_ERL_MAX 2
#define NA_DEFAULT_ERL_MIN 0
/* struct iscsi_tpg_attrib sanity values */
#define TA_AUTHENTICATION 1
#define TA_LOGIN_TIMEOUT 15
#define TA_LOGIN_TIMEOUT_MAX 30
#define TA_LOGIN_TIMEOUT_MIN 5
#define TA_NETIF_TIMEOUT 2
#define TA_NETIF_TIMEOUT_MAX 15
#define TA_NETIF_TIMEOUT_MIN 2
#define TA_GENERATE_NODE_ACLS 0
#define TA_DEFAULT_CMDSN_DEPTH 16
#define TA_DEFAULT_CMDSN_DEPTH_MAX 512
#define TA_DEFAULT_CMDSN_DEPTH_MIN 1
#define TA_CACHE_DYNAMIC_ACLS 0
/* Enabled by default in demo mode (generic_node_acls=1) */
#define TA_DEMO_MODE_WRITE_PROTECT 1
/* Disabled by default in production mode w/ explict ACLs */
#define TA_PROD_MODE_WRITE_PROTECT 0
#define TA_CACHE_CORE_NPS 0
enum tpg_np_network_transport_table {
ISCSI_TCP = 0,
ISCSI_SCTP_TCP = 1,
ISCSI_SCTP_UDP = 2,
ISCSI_IWARP_TCP = 3,
ISCSI_IWARP_SCTP = 4,
ISCSI_INFINIBAND = 5,
};
/* RFC-3720 7.1.4 Standard Connection State Diagram for a Target */
enum target_conn_state_table {
TARG_CONN_STATE_FREE = 0x1,
TARG_CONN_STATE_XPT_UP = 0x3,
TARG_CONN_STATE_IN_LOGIN = 0x4,
TARG_CONN_STATE_LOGGED_IN = 0x5,
TARG_CONN_STATE_IN_LOGOUT = 0x6,
TARG_CONN_STATE_LOGOUT_REQUESTED = 0x7,
TARG_CONN_STATE_CLEANUP_WAIT = 0x8,
};
/* RFC-3720 7.3.2 Session State Diagram for a Target */
enum target_sess_state_table {
TARG_SESS_STATE_FREE = 0x1,
TARG_SESS_STATE_ACTIVE = 0x2,
TARG_SESS_STATE_LOGGED_IN = 0x3,
TARG_SESS_STATE_FAILED = 0x4,
TARG_SESS_STATE_IN_CONTINUE = 0x5,
};
/* struct iscsi_data_count->type */
enum data_count_type {
ISCSI_RX_DATA = 1,
ISCSI_TX_DATA = 2,
};
/* struct iscsi_datain_req->dr_complete */
enum datain_req_comp_table {
DATAIN_COMPLETE_NORMAL = 1,
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY = 2,
DATAIN_COMPLETE_CONNECTION_RECOVERY = 3,
};
/* struct iscsi_datain_req->recovery */
enum datain_req_rec_table {
DATAIN_WITHIN_COMMAND_RECOVERY = 1,
DATAIN_CONNECTION_RECOVERY = 2,
};
/* struct iscsi_portal_group->state */
enum tpg_state_table {
TPG_STATE_FREE = 0,
TPG_STATE_ACTIVE = 1,
TPG_STATE_INACTIVE = 2,
TPG_STATE_COLD_RESET = 3,
};
/* struct iscsi_tiqn->tiqn_state */
enum tiqn_state_table {
TIQN_STATE_ACTIVE = 1,
TIQN_STATE_SHUTDOWN = 2,
};
/* struct iscsi_cmd->cmd_flags */
enum cmd_flags_table {
ICF_GOT_LAST_DATAOUT = 0x00000001,
ICF_GOT_DATACK_SNACK = 0x00000002,
ICF_NON_IMMEDIATE_UNSOLICITED_DATA = 0x00000004,
ICF_SENT_LAST_R2T = 0x00000008,
ICF_WITHIN_COMMAND_RECOVERY = 0x00000010,
ICF_CONTIG_MEMORY = 0x00000020,
ICF_ATTACHED_TO_RQUEUE = 0x00000040,
ICF_OOO_CMDSN = 0x00000080,
ICF_REJECT_FAIL_CONN = 0x00000100,
};
/* struct iscsi_cmd->i_state */
enum cmd_i_state_table {
ISTATE_NO_STATE = 0,
ISTATE_NEW_CMD = 1,
ISTATE_DEFERRED_CMD = 2,
ISTATE_UNSOLICITED_DATA = 3,
ISTATE_RECEIVE_DATAOUT = 4,
ISTATE_RECEIVE_DATAOUT_RECOVERY = 5,
ISTATE_RECEIVED_LAST_DATAOUT = 6,
ISTATE_WITHIN_DATAOUT_RECOVERY = 7,
ISTATE_IN_CONNECTION_RECOVERY = 8,
ISTATE_RECEIVED_TASKMGT = 9,
ISTATE_SEND_ASYNCMSG = 10,
ISTATE_SENT_ASYNCMSG = 11,
ISTATE_SEND_DATAIN = 12,
ISTATE_SEND_LAST_DATAIN = 13,
ISTATE_SENT_LAST_DATAIN = 14,
ISTATE_SEND_LOGOUTRSP = 15,
ISTATE_SENT_LOGOUTRSP = 16,
ISTATE_SEND_NOPIN = 17,
ISTATE_SENT_NOPIN = 18,
ISTATE_SEND_REJECT = 19,
ISTATE_SENT_REJECT = 20,
ISTATE_SEND_R2T = 21,
ISTATE_SENT_R2T = 22,
ISTATE_SEND_R2T_RECOVERY = 23,
ISTATE_SENT_R2T_RECOVERY = 24,
ISTATE_SEND_LAST_R2T = 25,
ISTATE_SENT_LAST_R2T = 26,
ISTATE_SEND_LAST_R2T_RECOVERY = 27,
ISTATE_SENT_LAST_R2T_RECOVERY = 28,
ISTATE_SEND_STATUS = 29,
ISTATE_SEND_STATUS_BROKEN_PC = 30,
ISTATE_SENT_STATUS = 31,
ISTATE_SEND_STATUS_RECOVERY = 32,
ISTATE_SENT_STATUS_RECOVERY = 33,
ISTATE_SEND_TASKMGTRSP = 34,
ISTATE_SENT_TASKMGTRSP = 35,
ISTATE_SEND_TEXTRSP = 36,
ISTATE_SENT_TEXTRSP = 37,
ISTATE_SEND_NOPIN_WANT_RESPONSE = 38,
ISTATE_SENT_NOPIN_WANT_RESPONSE = 39,
ISTATE_SEND_NOPIN_NO_RESPONSE = 40,
ISTATE_REMOVE = 41,
ISTATE_FREE = 42,
};
/* Used for iscsi_recover_cmdsn() return values */
enum recover_cmdsn_ret_table {
CMDSN_ERROR_CANNOT_RECOVER = -1,
CMDSN_NORMAL_OPERATION = 0,
CMDSN_LOWER_THAN_EXP = 1,
CMDSN_HIGHER_THAN_EXP = 2,
};
/* Used for iscsi_handle_immediate_data() return values */
enum immedate_data_ret_table {
IMMEDIATE_DATA_CANNOT_RECOVER = -1,
IMMEDIATE_DATA_NORMAL_OPERATION = 0,
IMMEDIATE_DATA_ERL1_CRC_FAILURE = 1,
};
/* Used for iscsi_decide_dataout_action() return values */
enum dataout_action_ret_table {
DATAOUT_CANNOT_RECOVER = -1,
DATAOUT_NORMAL = 0,
DATAOUT_SEND_R2T = 1,
DATAOUT_SEND_TO_TRANSPORT = 2,
DATAOUT_WITHIN_COMMAND_RECOVERY = 3,
};
/* Used for struct iscsi_node_auth->naf_flags */
enum naf_flags_table {
NAF_USERID_SET = 0x01,
NAF_PASSWORD_SET = 0x02,
NAF_USERID_IN_SET = 0x04,
NAF_PASSWORD_IN_SET = 0x08,
};
/* Used by various struct timer_list to manage iSCSI specific state */
enum iscsi_timer_flags_table {
ISCSI_TF_RUNNING = 0x01,
ISCSI_TF_STOP = 0x02,
ISCSI_TF_EXPIRED = 0x04,
};
/* Used for struct iscsi_np->np_flags */
enum np_flags_table {
NPF_IP_NETWORK = 0x00,
NPF_SCTP_STRUCT_FILE = 0x01 /* Bugfix */
};
/* Used for struct iscsi_np->np_thread_state */
enum np_thread_state_table {
ISCSI_NP_THREAD_ACTIVE = 1,
ISCSI_NP_THREAD_INACTIVE = 2,
ISCSI_NP_THREAD_RESET = 3,
ISCSI_NP_THREAD_SHUTDOWN = 4,
ISCSI_NP_THREAD_EXIT = 5,
};
struct iscsi_conn_ops {
u8 HeaderDigest; /* [0,1] == [None,CRC32C] */
u8 DataDigest; /* [0,1] == [None,CRC32C] */
u32 MaxRecvDataSegmentLength; /* [512..2**24-1] */
u8 OFMarker; /* [0,1] == [No,Yes] */
u8 IFMarker; /* [0,1] == [No,Yes] */
u32 OFMarkInt; /* [1..65535] */
u32 IFMarkInt; /* [1..65535] */
};
struct iscsi_sess_ops {
char InitiatorName[224];
char InitiatorAlias[256];
char TargetName[224];
char TargetAlias[256];
char TargetAddress[256];
u16 TargetPortalGroupTag; /* [0..65535] */
u16 MaxConnections; /* [1..65535] */
u8 InitialR2T; /* [0,1] == [No,Yes] */
u8 ImmediateData; /* [0,1] == [No,Yes] */
u32 MaxBurstLength; /* [512..2**24-1] */
u32 FirstBurstLength; /* [512..2**24-1] */
u16 DefaultTime2Wait; /* [0..3600] */
u16 DefaultTime2Retain; /* [0..3600] */
u16 MaxOutstandingR2T; /* [1..65535] */
u8 DataPDUInOrder; /* [0,1] == [No,Yes] */
u8 DataSequenceInOrder; /* [0,1] == [No,Yes] */
u8 ErrorRecoveryLevel; /* [0..2] */
u8 SessionType; /* [0,1] == [Normal,Discovery]*/
};
struct iscsi_queue_req {
int state;
struct iscsi_cmd *cmd;
struct list_head qr_list;
};
struct iscsi_data_count {
int data_length;
int sync_and_steering;
enum data_count_type type;
u32 iov_count;
u32 ss_iov_count;
u32 ss_marker_count;
struct kvec *iov;
};
struct iscsi_param_list {
struct list_head param_list;
struct list_head extra_response_list;
};
struct iscsi_datain_req {
enum datain_req_comp_table dr_complete;
int generate_recovery_values;
enum datain_req_rec_table recovery;
u32 begrun;
u32 runlength;
u32 data_length;
u32 data_offset;
u32 data_offset_end;
u32 data_sn;
u32 next_burst_len;
u32 read_data_done;
u32 seq_send_order;
struct list_head dr_list;
} ____cacheline_aligned;
struct iscsi_ooo_cmdsn {
u16 cid;
u32 batch_count;
u32 cmdsn;
u32 exp_cmdsn;
struct iscsi_cmd *cmd;
struct list_head ooo_list;
} ____cacheline_aligned;
struct iscsi_datain {
u8 flags;
u32 data_sn;
u32 length;
u32 offset;
} ____cacheline_aligned;
struct iscsi_r2t {
int seq_complete;
int recovery_r2t;
int sent_r2t;
u32 r2t_sn;
u32 offset;
u32 targ_xfer_tag;
u32 xfer_len;
struct list_head r2t_list;
} ____cacheline_aligned;
struct iscsi_cmd {
enum iscsi_timer_flags_table dataout_timer_flags;
/* DataOUT timeout retries */
u8 dataout_timeout_retries;
/* Within command recovery count */
u8 error_recovery_count;
/* iSCSI dependent state for out or order CmdSNs */
enum cmd_i_state_table deferred_i_state;
/* iSCSI dependent state */
enum cmd_i_state_table i_state;
/* Command is an immediate command (ISCSI_OP_IMMEDIATE set) */
u8 immediate_cmd;
/* Immediate data present */
u8 immediate_data;
/* iSCSI Opcode */
u8 iscsi_opcode;
/* iSCSI Response Code */
u8 iscsi_response;
/* Logout reason when iscsi_opcode == ISCSI_INIT_LOGOUT_CMND */
u8 logout_reason;
/* Logout response code when iscsi_opcode == ISCSI_INIT_LOGOUT_CMND */
u8 logout_response;
/* MaxCmdSN has been incremented */
u8 maxcmdsn_inc;
/* Immediate Unsolicited Dataout */
u8 unsolicited_data;
/* CID contained in logout PDU when opcode == ISCSI_INIT_LOGOUT_CMND */
u16 logout_cid;
/* Command flags */
enum cmd_flags_table cmd_flags;
/* Initiator Task Tag assigned from Initiator */
u32 init_task_tag;
/* Target Transfer Tag assigned from Target */
u32 targ_xfer_tag;
/* CmdSN assigned from Initiator */
u32 cmd_sn;
/* ExpStatSN assigned from Initiator */
u32 exp_stat_sn;
/* StatSN assigned to this ITT */
u32 stat_sn;
/* DataSN Counter */
u32 data_sn;
/* R2TSN Counter */
u32 r2t_sn;
/* Last DataSN acknowledged via DataAck SNACK */
u32 acked_data_sn;
/* Used for echoing NOPOUT ping data */
u32 buf_ptr_size;
/* Used to store DataDigest */
u32 data_crc;
/* Total size in bytes associated with command */
u32 data_length;
/* Counter for MaxOutstandingR2T */
u32 outstanding_r2ts;
/* Next R2T Offset when DataSequenceInOrder=Yes */
u32 r2t_offset;
/* Iovec current and orig count for iscsi_cmd->iov_data */
u32 iov_data_count;
u32 orig_iov_data_count;
/* Number of miscellaneous iovecs used for IP stack calls */
u32 iov_misc_count;
/* Number of struct iscsi_pdu in struct iscsi_cmd->pdu_list */
u32 pdu_count;
/* Next struct iscsi_pdu to send in struct iscsi_cmd->pdu_list */
u32 pdu_send_order;
/* Current struct iscsi_pdu in struct iscsi_cmd->pdu_list */
u32 pdu_start;
u32 residual_count;
/* Next struct iscsi_seq to send in struct iscsi_cmd->seq_list */
u32 seq_send_order;
/* Number of struct iscsi_seq in struct iscsi_cmd->seq_list */
u32 seq_count;
/* Current struct iscsi_seq in struct iscsi_cmd->seq_list */
u32 seq_no;
/* Lowest offset in current DataOUT sequence */
u32 seq_start_offset;
/* Highest offset in current DataOUT sequence */
u32 seq_end_offset;
/* Total size in bytes received so far of READ data */
u32 read_data_done;
/* Total size in bytes received so far of WRITE data */
u32 write_data_done;
/* Counter for FirstBurstLength key */
u32 first_burst_len;
/* Counter for MaxBurstLength key */
u32 next_burst_len;
/* Transfer size used for IP stack calls */
u32 tx_size;
/* Buffer used for various purposes */
void *buf_ptr;
/* See include/linux/dma-mapping.h */
enum dma_data_direction data_direction;
/* iSCSI PDU Header + CRC */
unsigned char pdu[ISCSI_HDR_LEN + ISCSI_CRC_LEN];
/* Number of times struct iscsi_cmd is present in immediate queue */
atomic_t immed_queue_count;
atomic_t response_queue_count;
atomic_t transport_sent;
spinlock_t datain_lock;
spinlock_t dataout_timeout_lock;
/* spinlock for protecting struct iscsi_cmd->i_state */
spinlock_t istate_lock;
/* spinlock for adding within command recovery entries */
spinlock_t error_lock;
/* spinlock for adding R2Ts */
spinlock_t r2t_lock;
/* DataIN List */
struct list_head datain_list;
/* R2T List */
struct list_head cmd_r2t_list;
struct completion reject_comp;
/* Timer for DataOUT */
struct timer_list dataout_timer;
/* Iovecs for SCSI data payload RX/TX w/ kernel level sockets */
struct kvec *iov_data;
/* Iovecs for miscellaneous purposes */
#define ISCSI_MISC_IOVECS 5
struct kvec iov_misc[ISCSI_MISC_IOVECS];
/* Array of struct iscsi_pdu used for DataPDUInOrder=No */
struct iscsi_pdu *pdu_list;
/* Current struct iscsi_pdu used for DataPDUInOrder=No */
struct iscsi_pdu *pdu_ptr;
/* Array of struct iscsi_seq used for DataSequenceInOrder=No */
struct iscsi_seq *seq_list;
/* Current struct iscsi_seq used for DataSequenceInOrder=No */
struct iscsi_seq *seq_ptr;
/* TMR Request when iscsi_opcode == ISCSI_OP_SCSI_TMFUNC */
struct iscsi_tmr_req *tmr_req;
/* Connection this command is alligient to */
struct iscsi_conn *conn;
/* Pointer to connection recovery entry */
struct iscsi_conn_recovery *cr;
/* Session the command is part of, used for connection recovery */
struct iscsi_session *sess;
/* list_head for connection list */
struct list_head i_list;
/* The TCM I/O descriptor that is accessed via container_of() */
struct se_cmd se_cmd;
/* Sense buffer that will be mapped into outgoing status */
#define ISCSI_SENSE_BUFFER_LEN (TRANSPORT_SENSE_BUFFER + 2)
unsigned char sense_buffer[ISCSI_SENSE_BUFFER_LEN];
struct scatterlist *t_mem_sg;
u32 t_mem_sg_nents;
u32 padding;
u8 pad_bytes[4];
struct scatterlist *first_data_sg;
u32 first_data_sg_off;
u32 kmapped_nents;
} ____cacheline_aligned;
struct iscsi_tmr_req {
bool task_reassign:1;
u32 ref_cmd_sn;
u32 exp_data_sn;
struct iscsi_conn_recovery *conn_recovery;
struct se_tmr_req *se_tmr_req;
};
struct iscsi_conn {
/* Authentication Successful for this connection */
u8 auth_complete;
/* State connection is currently in */
u8 conn_state;
u8 conn_logout_reason;
u8 network_transport;
enum iscsi_timer_flags_table nopin_timer_flags;
enum iscsi_timer_flags_table nopin_response_timer_flags;
u8 tx_immediate_queue;
u8 tx_response_queue;
/* Used to know what thread encountered a transport failure */
u8 which_thread;
/* connection id assigned by the Initiator */
u16 cid;
/* Remote TCP Port */
u16 login_port;
int net_size;
u32 auth_id;
#define CONNFLAG_SCTP_STRUCT_FILE 0x01
u32 conn_flags;
/* Used for iscsi_tx_login_rsp() */
u32 login_itt;
u32 exp_statsn;
/* Per connection status sequence number */
u32 stat_sn;
/* IFMarkInt's Current Value */
u32 if_marker;
/* OFMarkInt's Current Value */
u32 of_marker;
/* Used for calculating OFMarker offset to next PDU */
u32 of_marker_offset;
/* Complete Bad PDU for sending reject */
unsigned char bad_hdr[ISCSI_HDR_LEN];
#define IPV6_ADDRESS_SPACE 48
unsigned char login_ip[IPV6_ADDRESS_SPACE];
int conn_usage_count;
int conn_waiting_on_uc;
atomic_t check_immediate_queue;
atomic_t conn_logout_remove;
atomic_t connection_exit;
atomic_t connection_recovery;
atomic_t connection_reinstatement;
atomic_t connection_wait;
atomic_t connection_wait_rcfr;
atomic_t sleep_on_conn_wait_comp;
atomic_t transport_failed;
struct completion conn_post_wait_comp;
struct completion conn_wait_comp;
struct completion conn_wait_rcfr_comp;
struct completion conn_waiting_on_uc_comp;
struct completion conn_logout_comp;
struct completion tx_half_close_comp;
struct completion rx_half_close_comp;
/* socket used by this connection */
struct socket *sock;
struct timer_list nopin_timer;
struct timer_list nopin_response_timer;
struct timer_list transport_timer;
/* Spinlock used for add/deleting cmd's from conn_cmd_list */
spinlock_t cmd_lock;
spinlock_t conn_usage_lock;
spinlock_t immed_queue_lock;
spinlock_t nopin_timer_lock;
spinlock_t response_queue_lock;
spinlock_t state_lock;
/* libcrypto RX and TX contexts for crc32c */
struct hash_desc conn_rx_hash;
struct hash_desc conn_tx_hash;
/* Used for scheduling TX and RX connection kthreads */
cpumask_var_t conn_cpumask;
int conn_rx_reset_cpumask:1;
int conn_tx_reset_cpumask:1;
/* list_head of struct iscsi_cmd for this connection */
struct list_head conn_cmd_list;
struct list_head immed_queue_list;
struct list_head response_queue_list;
struct iscsi_conn_ops *conn_ops;
struct iscsi_param_list *param_list;
/* Used for per connection auth state machine */
void *auth_protocol;
struct iscsi_login_thread_s *login_thread;
struct iscsi_portal_group *tpg;
/* Pointer to parent session */
struct iscsi_session *sess;
/* Pointer to thread_set in use for this conn's threads */
struct iscsi_thread_set *thread_set;
/* list_head for session connection list */
struct list_head conn_list;
} ____cacheline_aligned;
struct iscsi_conn_recovery {
u16 cid;
u32 cmd_count;
u32 maxrecvdatasegmentlength;
int ready_for_reallegiance;
struct list_head conn_recovery_cmd_list;
spinlock_t conn_recovery_cmd_lock;
struct timer_list time2retain_timer;
struct iscsi_session *sess;
struct list_head cr_list;
} ____cacheline_aligned;
struct iscsi_session {
u8 initiator_vendor;
u8 isid[6];
enum iscsi_timer_flags_table time2retain_timer_flags;
u8 version_active;
u16 cid_called;
u16 conn_recovery_count;
u16 tsih;
/* state session is currently in */
u32 session_state;
/* session wide counter: initiator assigned task tag */
u32 init_task_tag;
/* session wide counter: target assigned task tag */
u32 targ_xfer_tag;
u32 cmdsn_window;
/* protects cmdsn values */
struct mutex cmdsn_mutex;
/* session wide counter: expected command sequence number */
u32 exp_cmd_sn;
/* session wide counter: maximum allowed command sequence number */
u32 max_cmd_sn;
struct list_head sess_ooo_cmdsn_list;
/* LIO specific session ID */
u32 sid;
char auth_type[8];
/* unique within the target */
int session_index;
/* Used for session reference counting */
int session_usage_count;
int session_waiting_on_uc;
u32 cmd_pdus;
u32 rsp_pdus;
u64 tx_data_octets;
u64 rx_data_octets;
u32 conn_digest_errors;
u32 conn_timeout_errors;
u64 creation_time;
spinlock_t session_stats_lock;
/* Number of active connections */
atomic_t nconn;
atomic_t session_continuation;
atomic_t session_fall_back_to_erl0;
atomic_t session_logout;
atomic_t session_reinstatement;
atomic_t session_stop_active;
atomic_t sleep_on_sess_wait_comp;
atomic_t transport_wait_cmds;
/* connection list */
struct list_head sess_conn_list;
struct list_head cr_active_list;
struct list_head cr_inactive_list;
spinlock_t conn_lock;
spinlock_t cr_a_lock;
spinlock_t cr_i_lock;
spinlock_t session_usage_lock;
spinlock_t ttt_lock;
struct completion async_msg_comp;
struct completion reinstatement_comp;
struct completion session_wait_comp;
struct completion session_waiting_on_uc_comp;
struct timer_list time2retain_timer;
struct iscsi_sess_ops *sess_ops;
struct se_session *se_sess;
struct iscsi_portal_group *tpg;
} ____cacheline_aligned;
struct iscsi_login {
u8 auth_complete;
u8 checked_for_existing;
u8 current_stage;
u8 leading_connection;
u8 first_request;
u8 version_min;
u8 version_max;
char isid[6];
u32 cmd_sn;
u32 init_task_tag;
u32 initial_exp_statsn;
u32 rsp_length;
u16 cid;
u16 tsih;
char *req;
char *rsp;
char *req_buf;
char *rsp_buf;
} ____cacheline_aligned;
struct iscsi_node_attrib {
u32 dataout_timeout;
u32 dataout_timeout_retries;
u32 default_erl;
u32 nopin_timeout;
u32 nopin_response_timeout;
u32 random_datain_pdu_offsets;
u32 random_datain_seq_offsets;
u32 random_r2t_offsets;
u32 tmr_cold_reset;
u32 tmr_warm_reset;
struct iscsi_node_acl *nacl;
};
struct se_dev_entry_s;
struct iscsi_node_auth {
enum naf_flags_table naf_flags;
int authenticate_target;
/* Used for iscsit_global->discovery_auth,
* set to zero (auth disabled) by default */
int enforce_discovery_auth;
#define MAX_USER_LEN 256
#define MAX_PASS_LEN 256
char userid[MAX_USER_LEN];
char password[MAX_PASS_LEN];
char userid_mutual[MAX_USER_LEN];
char password_mutual[MAX_PASS_LEN];
};
#include "iscsi_target_stat.h"
struct iscsi_node_stat_grps {
struct config_group iscsi_sess_stats_group;
struct config_group iscsi_conn_stats_group;
};
struct iscsi_node_acl {
struct iscsi_node_attrib node_attrib;
struct iscsi_node_auth node_auth;
struct iscsi_node_stat_grps node_stat_grps;
struct se_node_acl se_node_acl;
};
#define NODE_STAT_GRPS(nacl) (&(nacl)->node_stat_grps)
#define ISCSI_NODE_ATTRIB(t) (&(t)->node_attrib)
#define ISCSI_NODE_AUTH(t) (&(t)->node_auth)
struct iscsi_tpg_attrib {
u32 authentication;
u32 login_timeout;
u32 netif_timeout;
u32 generate_node_acls;
u32 cache_dynamic_acls;
u32 default_cmdsn_depth;
u32 demo_mode_write_protect;
u32 prod_mode_write_protect;
struct iscsi_portal_group *tpg;
};
struct iscsi_np {
int np_network_transport;
int np_ip_proto;
int np_sock_type;
enum np_thread_state_table np_thread_state;
enum iscsi_timer_flags_table np_login_timer_flags;
u32 np_exports;
enum np_flags_table np_flags;
unsigned char np_ip[IPV6_ADDRESS_SPACE];
u16 np_port;
spinlock_t np_thread_lock;
struct completion np_restart_comp;
struct socket *np_socket;
struct __kernel_sockaddr_storage np_sockaddr;
struct task_struct *np_thread;
struct timer_list np_login_timer;
struct iscsi_portal_group *np_login_tpg;
struct list_head np_list;
} ____cacheline_aligned;
struct iscsi_tpg_np {
struct iscsi_np *tpg_np;
struct iscsi_portal_group *tpg;
struct iscsi_tpg_np *tpg_np_parent;
struct list_head tpg_np_list;
struct list_head tpg_np_child_list;
struct list_head tpg_np_parent_list;
struct se_tpg_np se_tpg_np;
spinlock_t tpg_np_parent_lock;
};
struct iscsi_portal_group {
unsigned char tpg_chap_id;
/* TPG State */
enum tpg_state_table tpg_state;
/* Target Portal Group Tag */
u16 tpgt;
/* Id assigned to target sessions */
u16 ntsih;
/* Number of active sessions */
u32 nsessions;
/* Number of Network Portals available for this TPG */
u32 num_tpg_nps;
/* Per TPG LIO specific session ID. */
u32 sid;
/* Spinlock for adding/removing Network Portals */
spinlock_t tpg_np_lock;
spinlock_t tpg_state_lock;
struct se_portal_group tpg_se_tpg;
struct mutex tpg_access_lock;
struct mutex np_login_lock;
struct iscsi_tpg_attrib tpg_attrib;
/* Pointer to default list of iSCSI parameters for TPG */
struct iscsi_param_list *param_list;
struct iscsi_tiqn *tpg_tiqn;
struct list_head tpg_gnp_list;
struct list_head tpg_list;
} ____cacheline_aligned;
#define ISCSI_TPG_C(c) ((struct iscsi_portal_group *)(c)->tpg)
#define ISCSI_TPG_LUN(c, l) ((iscsi_tpg_list_t *)(c)->tpg->tpg_lun_list_t[l])
#define ISCSI_TPG_S(s) ((struct iscsi_portal_group *)(s)->tpg)
#define ISCSI_TPG_ATTRIB(t) (&(t)->tpg_attrib)
#define SE_TPG(tpg) (&(tpg)->tpg_se_tpg)
struct iscsi_wwn_stat_grps {
struct config_group iscsi_stat_group;
struct config_group iscsi_instance_group;
struct config_group iscsi_sess_err_group;
struct config_group iscsi_tgt_attr_group;
struct config_group iscsi_login_stats_group;
struct config_group iscsi_logout_stats_group;
};
struct iscsi_tiqn {
#define ISCSI_IQN_LEN 224
unsigned char tiqn[ISCSI_IQN_LEN];
enum tiqn_state_table tiqn_state;
int tiqn_access_count;
u32 tiqn_active_tpgs;
u32 tiqn_ntpgs;
u32 tiqn_num_tpg_nps;
u32 tiqn_nsessions;
struct list_head tiqn_list;
struct list_head tiqn_tpg_list;
spinlock_t tiqn_state_lock;
spinlock_t tiqn_tpg_lock;
struct se_wwn tiqn_wwn;
struct iscsi_wwn_stat_grps tiqn_stat_grps;
int tiqn_index;
struct iscsi_sess_err_stats sess_err_stats;
struct iscsi_login_stats login_stats;
struct iscsi_logout_stats logout_stats;
} ____cacheline_aligned;
#define WWN_STAT_GRPS(tiqn) (&(tiqn)->tiqn_stat_grps)
struct iscsit_global {
/* In core shutdown */
u32 in_shutdown;
u32 active_ts;
/* Unique identifier used for the authentication daemon */
u32 auth_id;
u32 inactive_ts;
/* Thread Set bitmap count */
int ts_bitmap_count;
/* Thread Set bitmap pointer */
unsigned long *ts_bitmap;
/* Used for iSCSI discovery session authentication */
struct iscsi_node_acl discovery_acl;
struct iscsi_portal_group *discovery_tpg;
};
#endif /* ISCSI_TARGET_CORE_H */
/*******************************************************************************
* This file contains the iSCSI Target DataIN value generation functions.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <scsi/iscsi_proto.h>
#include "iscsi_target_core.h"
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
#include "iscsi_target_datain_values.h"
struct iscsi_datain_req *iscsit_allocate_datain_req(void)
{
struct iscsi_datain_req *dr;
dr = kmem_cache_zalloc(lio_dr_cache, GFP_ATOMIC);
if (!dr) {
pr_err("Unable to allocate memory for"
" struct iscsi_datain_req\n");
return NULL;
}
INIT_LIST_HEAD(&dr->dr_list);
return dr;
}
void iscsit_attach_datain_req(struct iscsi_cmd *cmd, struct iscsi_datain_req *dr)
{
spin_lock(&cmd->datain_lock);
list_add_tail(&dr->dr_list, &cmd->datain_list);
spin_unlock(&cmd->datain_lock);
}
void iscsit_free_datain_req(struct iscsi_cmd *cmd, struct iscsi_datain_req *dr)
{
spin_lock(&cmd->datain_lock);
list_del(&dr->dr_list);
spin_unlock(&cmd->datain_lock);
kmem_cache_free(lio_dr_cache, dr);
}
void iscsit_free_all_datain_reqs(struct iscsi_cmd *cmd)
{
struct iscsi_datain_req *dr, *dr_tmp;
spin_lock(&cmd->datain_lock);
list_for_each_entry_safe(dr, dr_tmp, &cmd->datain_list, dr_list) {
list_del(&dr->dr_list);
kmem_cache_free(lio_dr_cache, dr);
}
spin_unlock(&cmd->datain_lock);
}
struct iscsi_datain_req *iscsit_get_datain_req(struct iscsi_cmd *cmd)
{
struct iscsi_datain_req *dr;
if (list_empty(&cmd->datain_list)) {
pr_err("cmd->datain_list is empty for ITT:"
" 0x%08x\n", cmd->init_task_tag);
return NULL;
}
list_for_each_entry(dr, &cmd->datain_list, dr_list)
break;
return dr;
}
/*
* For Normal and Recovery DataSequenceInOrder=Yes and DataPDUInOrder=Yes.
*/
static struct iscsi_datain_req *iscsit_set_datain_values_yes_and_yes(
struct iscsi_cmd *cmd,
struct iscsi_datain *datain)
{
u32 next_burst_len, read_data_done, read_data_left;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
dr = iscsit_get_datain_req(cmd);
if (!dr)
return NULL;
if (dr->recovery && dr->generate_recovery_values) {
if (iscsit_create_recovery_datain_values_datasequenceinorder_yes(
cmd, dr) < 0)
return NULL;
dr->generate_recovery_values = 0;
}
next_burst_len = (!dr->recovery) ?
cmd->next_burst_len : dr->next_burst_len;
read_data_done = (!dr->recovery) ?
cmd->read_data_done : dr->read_data_done;
read_data_left = (cmd->data_length - read_data_done);
if (!read_data_left) {
pr_err("ITT: 0x%08x read_data_left is zero!\n",
cmd->init_task_tag);
return NULL;
}
if ((read_data_left <= conn->conn_ops->MaxRecvDataSegmentLength) &&
(read_data_left <= (conn->sess->sess_ops->MaxBurstLength -
next_burst_len))) {
datain->length = read_data_left;
datain->flags |= (ISCSI_FLAG_CMD_FINAL | ISCSI_FLAG_DATA_STATUS);
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
datain->flags |= ISCSI_FLAG_DATA_ACK;
} else {
if ((next_burst_len +
conn->conn_ops->MaxRecvDataSegmentLength) <
conn->sess->sess_ops->MaxBurstLength) {
datain->length =
conn->conn_ops->MaxRecvDataSegmentLength;
next_burst_len += datain->length;
} else {
datain->length = (conn->sess->sess_ops->MaxBurstLength -
next_burst_len);
next_burst_len = 0;
datain->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
datain->flags |= ISCSI_FLAG_DATA_ACK;
}
}
datain->data_sn = (!dr->recovery) ? cmd->data_sn++ : dr->data_sn++;
datain->offset = read_data_done;
if (!dr->recovery) {
cmd->next_burst_len = next_burst_len;
cmd->read_data_done += datain->length;
} else {
dr->next_burst_len = next_burst_len;
dr->read_data_done += datain->length;
}
if (!dr->recovery) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS)
dr->dr_complete = DATAIN_COMPLETE_NORMAL;
return dr;
}
if (!dr->runlength) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
} else {
if ((dr->begrun + dr->runlength) == dr->data_sn) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
}
return dr;
}
/*
* For Normal and Recovery DataSequenceInOrder=No and DataPDUInOrder=Yes.
*/
static struct iscsi_datain_req *iscsit_set_datain_values_no_and_yes(
struct iscsi_cmd *cmd,
struct iscsi_datain *datain)
{
u32 offset, read_data_done, read_data_left, seq_send_order;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct iscsi_seq *seq;
dr = iscsit_get_datain_req(cmd);
if (!dr)
return NULL;
if (dr->recovery && dr->generate_recovery_values) {
if (iscsit_create_recovery_datain_values_datasequenceinorder_no(
cmd, dr) < 0)
return NULL;
dr->generate_recovery_values = 0;
}
read_data_done = (!dr->recovery) ?
cmd->read_data_done : dr->read_data_done;
seq_send_order = (!dr->recovery) ?
cmd->seq_send_order : dr->seq_send_order;
read_data_left = (cmd->data_length - read_data_done);
if (!read_data_left) {
pr_err("ITT: 0x%08x read_data_left is zero!\n",
cmd->init_task_tag);
return NULL;
}
seq = iscsit_get_seq_holder_for_datain(cmd, seq_send_order);
if (!seq)
return NULL;
seq->sent = 1;
if (!dr->recovery && !seq->next_burst_len)
seq->first_datasn = cmd->data_sn;
offset = (seq->offset + seq->next_burst_len);
if ((offset + conn->conn_ops->MaxRecvDataSegmentLength) >=
cmd->data_length) {
datain->length = (cmd->data_length - offset);
datain->offset = offset;
datain->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
datain->flags |= ISCSI_FLAG_DATA_ACK;
seq->next_burst_len = 0;
seq_send_order++;
} else {
if ((seq->next_burst_len +
conn->conn_ops->MaxRecvDataSegmentLength) <
conn->sess->sess_ops->MaxBurstLength) {
datain->length =
conn->conn_ops->MaxRecvDataSegmentLength;
datain->offset = (seq->offset + seq->next_burst_len);
seq->next_burst_len += datain->length;
} else {
datain->length = (conn->sess->sess_ops->MaxBurstLength -
seq->next_burst_len);
datain->offset = (seq->offset + seq->next_burst_len);
datain->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
datain->flags |= ISCSI_FLAG_DATA_ACK;
seq->next_burst_len = 0;
seq_send_order++;
}
}
if ((read_data_done + datain->length) == cmd->data_length)
datain->flags |= ISCSI_FLAG_DATA_STATUS;
datain->data_sn = (!dr->recovery) ? cmd->data_sn++ : dr->data_sn++;
if (!dr->recovery) {
cmd->seq_send_order = seq_send_order;
cmd->read_data_done += datain->length;
} else {
dr->seq_send_order = seq_send_order;
dr->read_data_done += datain->length;
}
if (!dr->recovery) {
if (datain->flags & ISCSI_FLAG_CMD_FINAL)
seq->last_datasn = datain->data_sn;
if (datain->flags & ISCSI_FLAG_DATA_STATUS)
dr->dr_complete = DATAIN_COMPLETE_NORMAL;
return dr;
}
if (!dr->runlength) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
} else {
if ((dr->begrun + dr->runlength) == dr->data_sn) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
}
return dr;
}
/*
* For Normal and Recovery DataSequenceInOrder=Yes and DataPDUInOrder=No.
*/
static struct iscsi_datain_req *iscsit_set_datain_values_yes_and_no(
struct iscsi_cmd *cmd,
struct iscsi_datain *datain)
{
u32 next_burst_len, read_data_done, read_data_left;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct iscsi_pdu *pdu;
dr = iscsit_get_datain_req(cmd);
if (!dr)
return NULL;
if (dr->recovery && dr->generate_recovery_values) {
if (iscsit_create_recovery_datain_values_datasequenceinorder_yes(
cmd, dr) < 0)
return NULL;
dr->generate_recovery_values = 0;
}
next_burst_len = (!dr->recovery) ?
cmd->next_burst_len : dr->next_burst_len;
read_data_done = (!dr->recovery) ?
cmd->read_data_done : dr->read_data_done;
read_data_left = (cmd->data_length - read_data_done);
if (!read_data_left) {
pr_err("ITT: 0x%08x read_data_left is zero!\n",
cmd->init_task_tag);
return dr;
}
pdu = iscsit_get_pdu_holder_for_seq(cmd, NULL);
if (!pdu)
return dr;
if ((read_data_done + pdu->length) == cmd->data_length) {
pdu->flags |= (ISCSI_FLAG_CMD_FINAL | ISCSI_FLAG_DATA_STATUS);
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
pdu->flags |= ISCSI_FLAG_DATA_ACK;
next_burst_len = 0;
} else {
if ((next_burst_len + conn->conn_ops->MaxRecvDataSegmentLength) <
conn->sess->sess_ops->MaxBurstLength)
next_burst_len += pdu->length;
else {
pdu->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
pdu->flags |= ISCSI_FLAG_DATA_ACK;
next_burst_len = 0;
}
}
pdu->data_sn = (!dr->recovery) ? cmd->data_sn++ : dr->data_sn++;
if (!dr->recovery) {
cmd->next_burst_len = next_burst_len;
cmd->read_data_done += pdu->length;
} else {
dr->next_burst_len = next_burst_len;
dr->read_data_done += pdu->length;
}
datain->flags = pdu->flags;
datain->length = pdu->length;
datain->offset = pdu->offset;
datain->data_sn = pdu->data_sn;
if (!dr->recovery) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS)
dr->dr_complete = DATAIN_COMPLETE_NORMAL;
return dr;
}
if (!dr->runlength) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
} else {
if ((dr->begrun + dr->runlength) == dr->data_sn) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
}
return dr;
}
/*
* For Normal and Recovery DataSequenceInOrder=No and DataPDUInOrder=No.
*/
static struct iscsi_datain_req *iscsit_set_datain_values_no_and_no(
struct iscsi_cmd *cmd,
struct iscsi_datain *datain)
{
u32 read_data_done, read_data_left, seq_send_order;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct iscsi_pdu *pdu;
struct iscsi_seq *seq = NULL;
dr = iscsit_get_datain_req(cmd);
if (!dr)
return NULL;
if (dr->recovery && dr->generate_recovery_values) {
if (iscsit_create_recovery_datain_values_datasequenceinorder_no(
cmd, dr) < 0)
return NULL;
dr->generate_recovery_values = 0;
}
read_data_done = (!dr->recovery) ?
cmd->read_data_done : dr->read_data_done;
seq_send_order = (!dr->recovery) ?
cmd->seq_send_order : dr->seq_send_order;
read_data_left = (cmd->data_length - read_data_done);
if (!read_data_left) {
pr_err("ITT: 0x%08x read_data_left is zero!\n",
cmd->init_task_tag);
return NULL;
}
seq = iscsit_get_seq_holder_for_datain(cmd, seq_send_order);
if (!seq)
return NULL;
seq->sent = 1;
if (!dr->recovery && !seq->next_burst_len)
seq->first_datasn = cmd->data_sn;
pdu = iscsit_get_pdu_holder_for_seq(cmd, seq);
if (!pdu)
return NULL;
if (seq->pdu_send_order == seq->pdu_count) {
pdu->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
pdu->flags |= ISCSI_FLAG_DATA_ACK;
seq->next_burst_len = 0;
seq_send_order++;
} else
seq->next_burst_len += pdu->length;
if ((read_data_done + pdu->length) == cmd->data_length)
pdu->flags |= ISCSI_FLAG_DATA_STATUS;
pdu->data_sn = (!dr->recovery) ? cmd->data_sn++ : dr->data_sn++;
if (!dr->recovery) {
cmd->seq_send_order = seq_send_order;
cmd->read_data_done += pdu->length;
} else {
dr->seq_send_order = seq_send_order;
dr->read_data_done += pdu->length;
}
datain->flags = pdu->flags;
datain->length = pdu->length;
datain->offset = pdu->offset;
datain->data_sn = pdu->data_sn;
if (!dr->recovery) {
if (datain->flags & ISCSI_FLAG_CMD_FINAL)
seq->last_datasn = datain->data_sn;
if (datain->flags & ISCSI_FLAG_DATA_STATUS)
dr->dr_complete = DATAIN_COMPLETE_NORMAL;
return dr;
}
if (!dr->runlength) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
} else {
if ((dr->begrun + dr->runlength) == dr->data_sn) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
}
return dr;
}
struct iscsi_datain_req *iscsit_get_datain_values(
struct iscsi_cmd *cmd,
struct iscsi_datain *datain)
{
struct iscsi_conn *conn = cmd->conn;
if (conn->sess->sess_ops->DataSequenceInOrder &&
conn->sess->sess_ops->DataPDUInOrder)
return iscsit_set_datain_values_yes_and_yes(cmd, datain);
else if (!conn->sess->sess_ops->DataSequenceInOrder &&
conn->sess->sess_ops->DataPDUInOrder)
return iscsit_set_datain_values_no_and_yes(cmd, datain);
else if (conn->sess->sess_ops->DataSequenceInOrder &&
!conn->sess->sess_ops->DataPDUInOrder)
return iscsit_set_datain_values_yes_and_no(cmd, datain);
else if (!conn->sess->sess_ops->DataSequenceInOrder &&
!conn->sess->sess_ops->DataPDUInOrder)
return iscsit_set_datain_values_no_and_no(cmd, datain);
return NULL;
}
#ifndef ISCSI_TARGET_DATAIN_VALUES_H
#define ISCSI_TARGET_DATAIN_VALUES_H
extern struct iscsi_datain_req *iscsit_allocate_datain_req(void);
extern void iscsit_attach_datain_req(struct iscsi_cmd *, struct iscsi_datain_req *);
extern void iscsit_free_datain_req(struct iscsi_cmd *, struct iscsi_datain_req *);
extern void iscsit_free_all_datain_reqs(struct iscsi_cmd *);
extern struct iscsi_datain_req *iscsit_get_datain_req(struct iscsi_cmd *);
extern struct iscsi_datain_req *iscsit_get_datain_values(struct iscsi_cmd *,
struct iscsi_datain *);
#endif /*** ISCSI_TARGET_DATAIN_VALUES_H ***/
/*******************************************************************************
* This file contains the iSCSI Virtual Device and Disk Transport
* agnostic related functions.
*
\u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <scsi/scsi_device.h>
#include <target/target_core_base.h>
#include <target/target_core_device.h>
#include <target/target_core_transport.h>
#include "iscsi_target_core.h"
#include "iscsi_target_device.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
int iscsit_get_lun_for_tmr(
struct iscsi_cmd *cmd,
u64 lun)
{
u32 unpacked_lun = scsilun_to_int((struct scsi_lun *)&lun);
return transport_lookup_tmr_lun(&cmd->se_cmd, unpacked_lun);
}
int iscsit_get_lun_for_cmd(
struct iscsi_cmd *cmd,
unsigned char *cdb,
u64 lun)
{
u32 unpacked_lun = scsilun_to_int((struct scsi_lun *)&lun);
return transport_lookup_cmd_lun(&cmd->se_cmd, unpacked_lun);
}
void iscsit_determine_maxcmdsn(struct iscsi_session *sess)
{
struct se_node_acl *se_nacl;
/*
* This is a discovery session, the single queue slot was already
* assigned in iscsi_login_zero_tsih(). Since only Logout and
* Text Opcodes are allowed during discovery we do not have to worry
* about the HBA's queue depth here.
*/
if (sess->sess_ops->SessionType)
return;
se_nacl = sess->se_sess->se_node_acl;
/*
* This is a normal session, set the Session's CmdSN window to the
* struct se_node_acl->queue_depth. The value in struct se_node_acl->queue_depth
* has already been validated as a legal value in
* core_set_queue_depth_for_node().
*/
sess->cmdsn_window = se_nacl->queue_depth;
sess->max_cmd_sn = (sess->max_cmd_sn + se_nacl->queue_depth) - 1;
}
void iscsit_increment_maxcmdsn(struct iscsi_cmd *cmd, struct iscsi_session *sess)
{
if (cmd->immediate_cmd || cmd->maxcmdsn_inc)
return;
cmd->maxcmdsn_inc = 1;
mutex_lock(&sess->cmdsn_mutex);
sess->max_cmd_sn += 1;
pr_debug("Updated MaxCmdSN to 0x%08x\n", sess->max_cmd_sn);
mutex_unlock(&sess->cmdsn_mutex);
}
#ifndef ISCSI_TARGET_DEVICE_H
#define ISCSI_TARGET_DEVICE_H
extern int iscsit_get_lun_for_tmr(struct iscsi_cmd *, u64);
extern int iscsit_get_lun_for_cmd(struct iscsi_cmd *, unsigned char *, u64);
extern void iscsit_determine_maxcmdsn(struct iscsi_session *);
extern void iscsit_increment_maxcmdsn(struct iscsi_cmd *, struct iscsi_session *);
#endif /* ISCSI_TARGET_DEVICE_H */
/******************************************************************************
* This file contains error recovery level zero functions used by
* the iSCSI Target driver.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include "iscsi_target_core.h"
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_tq.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
/*
* Used to set values in struct iscsi_cmd that iscsit_dataout_check_sequence()
* checks against to determine a PDU's Offset+Length is within the current
* DataOUT Sequence. Used for DataSequenceInOrder=Yes only.
*/
void iscsit_set_dataout_sequence_values(
struct iscsi_cmd *cmd)
{
struct iscsi_conn *conn = cmd->conn;
/*
* Still set seq_start_offset and seq_end_offset for Unsolicited
* DataOUT, even if DataSequenceInOrder=No.
*/
if (cmd->unsolicited_data) {
cmd->seq_start_offset = cmd->write_data_done;
cmd->seq_end_offset = (cmd->write_data_done +
(cmd->data_length >
conn->sess->sess_ops->FirstBurstLength) ?
conn->sess->sess_ops->FirstBurstLength : cmd->data_length);
return;
}
if (!conn->sess->sess_ops->DataSequenceInOrder)
return;
if (!cmd->seq_start_offset && !cmd->seq_end_offset) {
cmd->seq_start_offset = cmd->write_data_done;
cmd->seq_end_offset = (cmd->data_length >
conn->sess->sess_ops->MaxBurstLength) ?
(cmd->write_data_done +
conn->sess->sess_ops->MaxBurstLength) : cmd->data_length;
} else {
cmd->seq_start_offset = cmd->seq_end_offset;
cmd->seq_end_offset = ((cmd->seq_end_offset +
conn->sess->sess_ops->MaxBurstLength) >=
cmd->data_length) ? cmd->data_length :
(cmd->seq_end_offset +
conn->sess->sess_ops->MaxBurstLength);
}
}
static int iscsit_dataout_within_command_recovery_check(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
struct iscsi_conn *conn = cmd->conn;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
/*
* We do the within-command recovery checks here as it is
* the first function called in iscsi_check_pre_dataout().
* Basically, if we are in within-command recovery and
* the PDU does not contain the offset the sequence needs,
* dump the payload.
*
* This only applies to DataPDUInOrder=Yes, for
* DataPDUInOrder=No we only re-request the failed PDU
* and check that all PDUs in a sequence are received
* upon end of sequence.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
if ((cmd->cmd_flags & ICF_WITHIN_COMMAND_RECOVERY) &&
(cmd->write_data_done != hdr->offset))
goto dump;
cmd->cmd_flags &= ~ICF_WITHIN_COMMAND_RECOVERY;
} else {
struct iscsi_seq *seq;
seq = iscsit_get_seq_holder(cmd, hdr->offset, payload_length);
if (!seq)
return DATAOUT_CANNOT_RECOVER;
/*
* Set the struct iscsi_seq pointer to reuse later.
*/
cmd->seq_ptr = seq;
if (conn->sess->sess_ops->DataPDUInOrder) {
if ((seq->status ==
DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY) &&
((seq->offset != hdr->offset) ||
(seq->data_sn != hdr->datasn)))
goto dump;
} else {
if ((seq->status ==
DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY) &&
(seq->data_sn != hdr->datasn))
goto dump;
}
if (seq->status == DATAOUT_SEQUENCE_COMPLETE)
goto dump;
if (seq->status != DATAOUT_SEQUENCE_COMPLETE)
seq->status = 0;
}
return DATAOUT_NORMAL;
dump:
pr_err("Dumping DataOUT PDU Offset: %u Length: %d DataSN:"
" 0x%08x\n", hdr->offset, payload_length, hdr->datasn);
return iscsit_dump_data_payload(conn, payload_length, 1);
}
static int iscsit_dataout_check_unsolicited_sequence(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
u32 first_burst_len;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
if ((hdr->offset < cmd->seq_start_offset) ||
((hdr->offset + payload_length) > cmd->seq_end_offset)) {
pr_err("Command ITT: 0x%08x with Offset: %u,"
" Length: %u outside of Unsolicited Sequence %u:%u while"
" DataSequenceInOrder=Yes.\n", cmd->init_task_tag,
hdr->offset, payload_length, cmd->seq_start_offset,
cmd->seq_end_offset);
return DATAOUT_CANNOT_RECOVER;
}
first_burst_len = (cmd->first_burst_len + payload_length);
if (first_burst_len > conn->sess->sess_ops->FirstBurstLength) {
pr_err("Total %u bytes exceeds FirstBurstLength: %u"
" for this Unsolicited DataOut Burst.\n",
first_burst_len, conn->sess->sess_ops->FirstBurstLength);
transport_send_check_condition_and_sense(&cmd->se_cmd,
TCM_INCORRECT_AMOUNT_OF_DATA, 0);
return DATAOUT_CANNOT_RECOVER;
}
/*
* Perform various MaxBurstLength and ISCSI_FLAG_CMD_FINAL sanity
* checks for the current Unsolicited DataOUT Sequence.
*/
if (hdr->flags & ISCSI_FLAG_CMD_FINAL) {
/*
* Ignore ISCSI_FLAG_CMD_FINAL checks while DataPDUInOrder=No, end of
* sequence checks are handled in
* iscsit_dataout_datapduinorder_no_fbit().
*/
if (!conn->sess->sess_ops->DataPDUInOrder)
goto out;
if ((first_burst_len != cmd->data_length) &&
(first_burst_len != conn->sess->sess_ops->FirstBurstLength)) {
pr_err("Unsolicited non-immediate data"
" received %u does not equal FirstBurstLength: %u, and"
" does not equal ExpXferLen %u.\n", first_burst_len,
conn->sess->sess_ops->FirstBurstLength,
cmd->data_length);
transport_send_check_condition_and_sense(&cmd->se_cmd,
TCM_INCORRECT_AMOUNT_OF_DATA, 0);
return DATAOUT_CANNOT_RECOVER;
}
} else {
if (first_burst_len == conn->sess->sess_ops->FirstBurstLength) {
pr_err("Command ITT: 0x%08x reached"
" FirstBurstLength: %u, but ISCSI_FLAG_CMD_FINAL is not set. protocol"
" error.\n", cmd->init_task_tag,
conn->sess->sess_ops->FirstBurstLength);
return DATAOUT_CANNOT_RECOVER;
}
if (first_burst_len == cmd->data_length) {
pr_err("Command ITT: 0x%08x reached"
" ExpXferLen: %u, but ISCSI_FLAG_CMD_FINAL is not set. protocol"
" error.\n", cmd->init_task_tag, cmd->data_length);
return DATAOUT_CANNOT_RECOVER;
}
}
out:
return DATAOUT_NORMAL;
}
static int iscsit_dataout_check_sequence(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
u32 next_burst_len;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_seq *seq = NULL;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
/*
* For DataSequenceInOrder=Yes: Check that the offset and offset+length
* is within range as defined by iscsi_set_dataout_sequence_values().
*
* For DataSequenceInOrder=No: Check that an struct iscsi_seq exists for
* offset+length tuple.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
/*
* Due to possibility of recovery DataOUT sent by the initiator
* fullfilling an Recovery R2T, it's best to just dump the
* payload here, instead of erroring out.
*/
if ((hdr->offset < cmd->seq_start_offset) ||
((hdr->offset + payload_length) > cmd->seq_end_offset)) {
pr_err("Command ITT: 0x%08x with Offset: %u,"
" Length: %u outside of Sequence %u:%u while"
" DataSequenceInOrder=Yes.\n", cmd->init_task_tag,
hdr->offset, payload_length, cmd->seq_start_offset,
cmd->seq_end_offset);
if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
return DATAOUT_CANNOT_RECOVER;
return DATAOUT_WITHIN_COMMAND_RECOVERY;
}
next_burst_len = (cmd->next_burst_len + payload_length);
} else {
seq = iscsit_get_seq_holder(cmd, hdr->offset, payload_length);
if (!seq)
return DATAOUT_CANNOT_RECOVER;
/*
* Set the struct iscsi_seq pointer to reuse later.
*/
cmd->seq_ptr = seq;
if (seq->status == DATAOUT_SEQUENCE_COMPLETE) {
if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
return DATAOUT_CANNOT_RECOVER;
return DATAOUT_WITHIN_COMMAND_RECOVERY;
}
next_burst_len = (seq->next_burst_len + payload_length);
}
if (next_burst_len > conn->sess->sess_ops->MaxBurstLength) {
pr_err("Command ITT: 0x%08x, NextBurstLength: %u and"
" Length: %u exceeds MaxBurstLength: %u. protocol"
" error.\n", cmd->init_task_tag,
(next_burst_len - payload_length),
payload_length, conn->sess->sess_ops->MaxBurstLength);
return DATAOUT_CANNOT_RECOVER;
}
/*
* Perform various MaxBurstLength and ISCSI_FLAG_CMD_FINAL sanity
* checks for the current DataOUT Sequence.
*/
if (hdr->flags & ISCSI_FLAG_CMD_FINAL) {
/*
* Ignore ISCSI_FLAG_CMD_FINAL checks while DataPDUInOrder=No, end of
* sequence checks are handled in
* iscsit_dataout_datapduinorder_no_fbit().
*/
if (!conn->sess->sess_ops->DataPDUInOrder)
goto out;
if (conn->sess->sess_ops->DataSequenceInOrder) {
if ((next_burst_len <
conn->sess->sess_ops->MaxBurstLength) &&
((cmd->write_data_done + payload_length) <
cmd->data_length)) {
pr_err("Command ITT: 0x%08x set ISCSI_FLAG_CMD_FINAL"
" before end of DataOUT sequence, protocol"
" error.\n", cmd->init_task_tag);
return DATAOUT_CANNOT_RECOVER;
}
} else {
if (next_burst_len < seq->xfer_len) {
pr_err("Command ITT: 0x%08x set ISCSI_FLAG_CMD_FINAL"
" before end of DataOUT sequence, protocol"
" error.\n", cmd->init_task_tag);
return DATAOUT_CANNOT_RECOVER;
}
}
} else {
if (conn->sess->sess_ops->DataSequenceInOrder) {
if (next_burst_len ==
conn->sess->sess_ops->MaxBurstLength) {
pr_err("Command ITT: 0x%08x reached"
" MaxBurstLength: %u, but ISCSI_FLAG_CMD_FINAL is"
" not set, protocol error.", cmd->init_task_tag,
conn->sess->sess_ops->MaxBurstLength);
return DATAOUT_CANNOT_RECOVER;
}
if ((cmd->write_data_done + payload_length) ==
cmd->data_length) {
pr_err("Command ITT: 0x%08x reached"
" last DataOUT PDU in sequence but ISCSI_FLAG_"
"CMD_FINAL is not set, protocol error.\n",
cmd->init_task_tag);
return DATAOUT_CANNOT_RECOVER;
}
} else {
if (next_burst_len == seq->xfer_len) {
pr_err("Command ITT: 0x%08x reached"
" last DataOUT PDU in sequence but ISCSI_FLAG_"
"CMD_FINAL is not set, protocol error.\n",
cmd->init_task_tag);
return DATAOUT_CANNOT_RECOVER;
}
}
}
out:
return DATAOUT_NORMAL;
}
static int iscsit_dataout_check_datasn(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
int dump = 0, recovery = 0;
u32 data_sn = 0;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
/*
* Considering the target has no method of re-requesting DataOUT
* by DataSN, if we receieve a greater DataSN than expected we
* assume the functions for DataPDUInOrder=[Yes,No] below will
* handle it.
*
* If the DataSN is less than expected, dump the payload.
*/
if (conn->sess->sess_ops->DataSequenceInOrder)
data_sn = cmd->data_sn;
else {
struct iscsi_seq *seq = cmd->seq_ptr;
data_sn = seq->data_sn;
}
if (hdr->datasn > data_sn) {
pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x"
" higher than expected 0x%08x.\n", cmd->init_task_tag,
hdr->datasn, data_sn);
recovery = 1;
goto recover;
} else if (hdr->datasn < data_sn) {
pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x"
" lower than expected 0x%08x, discarding payload.\n",
cmd->init_task_tag, hdr->datasn, data_sn);
dump = 1;
goto dump;
}
return DATAOUT_NORMAL;
recover:
if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
pr_err("Unable to perform within-command recovery"
" while ERL=0.\n");
return DATAOUT_CANNOT_RECOVER;
}
dump:
if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
return DATAOUT_CANNOT_RECOVER;
return (recovery || dump) ? DATAOUT_WITHIN_COMMAND_RECOVERY :
DATAOUT_NORMAL;
}
static int iscsit_dataout_pre_datapduinorder_yes(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
int dump = 0, recovery = 0;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
/*
* For DataSequenceInOrder=Yes: If the offset is greater than the global
* DataPDUInOrder=Yes offset counter in struct iscsi_cmd a protcol error has
* occured and fail the connection.
*
* For DataSequenceInOrder=No: If the offset is greater than the per
* sequence DataPDUInOrder=Yes offset counter in struct iscsi_seq a protocol
* error has occured and fail the connection.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
if (hdr->offset != cmd->write_data_done) {
pr_err("Command ITT: 0x%08x, received offset"
" %u different than expected %u.\n", cmd->init_task_tag,
hdr->offset, cmd->write_data_done);
recovery = 1;
goto recover;
}
} else {
struct iscsi_seq *seq = cmd->seq_ptr;
if (hdr->offset > seq->offset) {
pr_err("Command ITT: 0x%08x, received offset"
" %u greater than expected %u.\n", cmd->init_task_tag,
hdr->offset, seq->offset);
recovery = 1;
goto recover;
} else if (hdr->offset < seq->offset) {
pr_err("Command ITT: 0x%08x, received offset"
" %u less than expected %u, discarding payload.\n",
cmd->init_task_tag, hdr->offset, seq->offset);
dump = 1;
goto dump;
}
}
return DATAOUT_NORMAL;
recover:
if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
pr_err("Unable to perform within-command recovery"
" while ERL=0.\n");
return DATAOUT_CANNOT_RECOVER;
}
dump:
if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
return DATAOUT_CANNOT_RECOVER;
return (recovery) ? iscsit_recover_dataout_sequence(cmd,
hdr->offset, payload_length) :
(dump) ? DATAOUT_WITHIN_COMMAND_RECOVERY : DATAOUT_NORMAL;
}
static int iscsit_dataout_pre_datapduinorder_no(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
struct iscsi_pdu *pdu;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
pdu = iscsit_get_pdu_holder(cmd, hdr->offset, payload_length);
if (!pdu)
return DATAOUT_CANNOT_RECOVER;
cmd->pdu_ptr = pdu;
switch (pdu->status) {
case ISCSI_PDU_NOT_RECEIVED:
case ISCSI_PDU_CRC_FAILED:
case ISCSI_PDU_TIMED_OUT:
break;
case ISCSI_PDU_RECEIVED_OK:
pr_err("Command ITT: 0x%08x received already gotten"
" Offset: %u, Length: %u\n", cmd->init_task_tag,
hdr->offset, payload_length);
return iscsit_dump_data_payload(cmd->conn, payload_length, 1);
default:
return DATAOUT_CANNOT_RECOVER;
}
return DATAOUT_NORMAL;
}
static int iscsit_dataout_update_r2t(struct iscsi_cmd *cmd, u32 offset, u32 length)
{
struct iscsi_r2t *r2t;
if (cmd->unsolicited_data)
return 0;
r2t = iscsit_get_r2t_for_eos(cmd, offset, length);
if (!r2t)
return -1;
spin_lock_bh(&cmd->r2t_lock);
r2t->seq_complete = 1;
cmd->outstanding_r2ts--;
spin_unlock_bh(&cmd->r2t_lock);
return 0;
}
static int iscsit_dataout_update_datapduinorder_no(
struct iscsi_cmd *cmd,
u32 data_sn,
int f_bit)
{
int ret = 0;
struct iscsi_pdu *pdu = cmd->pdu_ptr;
pdu->data_sn = data_sn;
switch (pdu->status) {
case ISCSI_PDU_NOT_RECEIVED:
pdu->status = ISCSI_PDU_RECEIVED_OK;
break;
case ISCSI_PDU_CRC_FAILED:
pdu->status = ISCSI_PDU_RECEIVED_OK;
break;
case ISCSI_PDU_TIMED_OUT:
pdu->status = ISCSI_PDU_RECEIVED_OK;
break;
default:
return DATAOUT_CANNOT_RECOVER;
}
if (f_bit) {
ret = iscsit_dataout_datapduinorder_no_fbit(cmd, pdu);
if (ret == DATAOUT_CANNOT_RECOVER)
return ret;
}
return DATAOUT_NORMAL;
}
static int iscsit_dataout_post_crc_passed(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
int ret, send_r2t = 0;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_seq *seq = NULL;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
if (cmd->unsolicited_data) {
if ((cmd->first_burst_len + payload_length) ==
conn->sess->sess_ops->FirstBurstLength) {
if (iscsit_dataout_update_r2t(cmd, hdr->offset,
payload_length) < 0)
return DATAOUT_CANNOT_RECOVER;
send_r2t = 1;
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
ret = iscsit_dataout_update_datapduinorder_no(cmd,
hdr->datasn, (hdr->flags & ISCSI_FLAG_CMD_FINAL));
if (ret == DATAOUT_CANNOT_RECOVER)
return ret;
}
cmd->first_burst_len += payload_length;
if (conn->sess->sess_ops->DataSequenceInOrder)
cmd->data_sn++;
else {
seq = cmd->seq_ptr;
seq->data_sn++;
seq->offset += payload_length;
}
if (send_r2t) {
if (seq)
seq->status = DATAOUT_SEQUENCE_COMPLETE;
cmd->first_burst_len = 0;
cmd->unsolicited_data = 0;
}
} else {
if (conn->sess->sess_ops->DataSequenceInOrder) {
if ((cmd->next_burst_len + payload_length) ==
conn->sess->sess_ops->MaxBurstLength) {
if (iscsit_dataout_update_r2t(cmd, hdr->offset,
payload_length) < 0)
return DATAOUT_CANNOT_RECOVER;
send_r2t = 1;
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
ret = iscsit_dataout_update_datapduinorder_no(
cmd, hdr->datasn,
(hdr->flags & ISCSI_FLAG_CMD_FINAL));
if (ret == DATAOUT_CANNOT_RECOVER)
return ret;
}
cmd->next_burst_len += payload_length;
cmd->data_sn++;
if (send_r2t)
cmd->next_burst_len = 0;
} else {
seq = cmd->seq_ptr;
if ((seq->next_burst_len + payload_length) ==
seq->xfer_len) {
if (iscsit_dataout_update_r2t(cmd, hdr->offset,
payload_length) < 0)
return DATAOUT_CANNOT_RECOVER;
send_r2t = 1;
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
ret = iscsit_dataout_update_datapduinorder_no(
cmd, hdr->datasn,
(hdr->flags & ISCSI_FLAG_CMD_FINAL));
if (ret == DATAOUT_CANNOT_RECOVER)
return ret;
}
seq->data_sn++;
seq->offset += payload_length;
seq->next_burst_len += payload_length;
if (send_r2t) {
seq->next_burst_len = 0;
seq->status = DATAOUT_SEQUENCE_COMPLETE;
}
}
}
if (send_r2t && conn->sess->sess_ops->DataSequenceInOrder)
cmd->data_sn = 0;
cmd->write_data_done += payload_length;
return (cmd->write_data_done == cmd->data_length) ?
DATAOUT_SEND_TO_TRANSPORT : (send_r2t) ?
DATAOUT_SEND_R2T : DATAOUT_NORMAL;
}
static int iscsit_dataout_post_crc_failed(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
struct iscsi_conn *conn = cmd->conn;
struct iscsi_pdu *pdu;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
if (conn->sess->sess_ops->DataPDUInOrder)
goto recover;
/*
* The rest of this function is only called when DataPDUInOrder=No.
*/
pdu = cmd->pdu_ptr;
switch (pdu->status) {
case ISCSI_PDU_NOT_RECEIVED:
pdu->status = ISCSI_PDU_CRC_FAILED;
break;
case ISCSI_PDU_CRC_FAILED:
break;
case ISCSI_PDU_TIMED_OUT:
pdu->status = ISCSI_PDU_CRC_FAILED;
break;
default:
return DATAOUT_CANNOT_RECOVER;
}
recover:
return iscsit_recover_dataout_sequence(cmd, hdr->offset, payload_length);
}
/*
* Called from iscsit_handle_data_out() before DataOUT Payload is received
* and CRC computed.
*/
extern int iscsit_check_pre_dataout(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
int ret;
struct iscsi_conn *conn = cmd->conn;
ret = iscsit_dataout_within_command_recovery_check(cmd, buf);
if ((ret == DATAOUT_WITHIN_COMMAND_RECOVERY) ||
(ret == DATAOUT_CANNOT_RECOVER))
return ret;
ret = iscsit_dataout_check_datasn(cmd, buf);
if ((ret == DATAOUT_WITHIN_COMMAND_RECOVERY) ||
(ret == DATAOUT_CANNOT_RECOVER))
return ret;
if (cmd->unsolicited_data) {
ret = iscsit_dataout_check_unsolicited_sequence(cmd, buf);
if ((ret == DATAOUT_WITHIN_COMMAND_RECOVERY) ||
(ret == DATAOUT_CANNOT_RECOVER))
return ret;
} else {
ret = iscsit_dataout_check_sequence(cmd, buf);
if ((ret == DATAOUT_WITHIN_COMMAND_RECOVERY) ||
(ret == DATAOUT_CANNOT_RECOVER))
return ret;
}
return (conn->sess->sess_ops->DataPDUInOrder) ?
iscsit_dataout_pre_datapduinorder_yes(cmd, buf) :
iscsit_dataout_pre_datapduinorder_no(cmd, buf);
}
/*
* Called from iscsit_handle_data_out() after DataOUT Payload is received
* and CRC computed.
*/
int iscsit_check_post_dataout(
struct iscsi_cmd *cmd,
unsigned char *buf,
u8 data_crc_failed)
{
struct iscsi_conn *conn = cmd->conn;
cmd->dataout_timeout_retries = 0;
if (!data_crc_failed)
return iscsit_dataout_post_crc_passed(cmd, buf);
else {
if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
pr_err("Unable to recover from DataOUT CRC"
" failure while ERL=0, closing session.\n");
iscsit_add_reject_from_cmd(ISCSI_REASON_DATA_DIGEST_ERROR,
1, 0, buf, cmd);
return DATAOUT_CANNOT_RECOVER;
}
iscsit_add_reject_from_cmd(ISCSI_REASON_DATA_DIGEST_ERROR,
0, 0, buf, cmd);
return iscsit_dataout_post_crc_failed(cmd, buf);
}
}
static void iscsit_handle_time2retain_timeout(unsigned long data)
{
struct iscsi_session *sess = (struct iscsi_session *) data;
struct iscsi_portal_group *tpg = ISCSI_TPG_S(sess);
struct se_portal_group *se_tpg = &tpg->tpg_se_tpg;
spin_lock_bh(&se_tpg->session_lock);
if (sess->time2retain_timer_flags & ISCSI_TF_STOP) {
spin_unlock_bh(&se_tpg->session_lock);
return;
}
if (atomic_read(&sess->session_reinstatement)) {
pr_err("Exiting Time2Retain handler because"
" session_reinstatement=1\n");
spin_unlock_bh(&se_tpg->session_lock);
return;
}
sess->time2retain_timer_flags |= ISCSI_TF_EXPIRED;
pr_err("Time2Retain timer expired for SID: %u, cleaning up"
" iSCSI session.\n", sess->sid);
{
struct iscsi_tiqn *tiqn = tpg->tpg_tiqn;
if (tiqn) {
spin_lock(&tiqn->sess_err_stats.lock);
strcpy(tiqn->sess_err_stats.last_sess_fail_rem_name,
(void *)sess->sess_ops->InitiatorName);
tiqn->sess_err_stats.last_sess_failure_type =
ISCSI_SESS_ERR_CXN_TIMEOUT;
tiqn->sess_err_stats.cxn_timeout_errors++;
sess->conn_timeout_errors++;
spin_unlock(&tiqn->sess_err_stats.lock);
}
}
spin_unlock_bh(&se_tpg->session_lock);
iscsit_close_session(sess);
}
extern void iscsit_start_time2retain_handler(struct iscsi_session *sess)
{
int tpg_active;
/*
* Only start Time2Retain timer when the assoicated TPG is still in
* an ACTIVE (eg: not disabled or shutdown) state.
*/
spin_lock(&ISCSI_TPG_S(sess)->tpg_state_lock);
tpg_active = (ISCSI_TPG_S(sess)->tpg_state == TPG_STATE_ACTIVE);
spin_unlock(&ISCSI_TPG_S(sess)->tpg_state_lock);
if (!tpg_active)
return;
if (sess->time2retain_timer_flags & ISCSI_TF_RUNNING)
return;
pr_debug("Starting Time2Retain timer for %u seconds on"
" SID: %u\n", sess->sess_ops->DefaultTime2Retain, sess->sid);
init_timer(&sess->time2retain_timer);
sess->time2retain_timer.expires =
(get_jiffies_64() + sess->sess_ops->DefaultTime2Retain * HZ);
sess->time2retain_timer.data = (unsigned long)sess;
sess->time2retain_timer.function = iscsit_handle_time2retain_timeout;
sess->time2retain_timer_flags &= ~ISCSI_TF_STOP;
sess->time2retain_timer_flags |= ISCSI_TF_RUNNING;
add_timer(&sess->time2retain_timer);
}
/*
* Called with spin_lock_bh(&struct se_portal_group->session_lock) held
*/
extern int iscsit_stop_time2retain_timer(struct iscsi_session *sess)
{
struct iscsi_portal_group *tpg = ISCSI_TPG_S(sess);
struct se_portal_group *se_tpg = &tpg->tpg_se_tpg;
if (sess->time2retain_timer_flags & ISCSI_TF_EXPIRED)
return -1;
if (!(sess->time2retain_timer_flags & ISCSI_TF_RUNNING))
return 0;
sess->time2retain_timer_flags |= ISCSI_TF_STOP;
spin_unlock_bh(&se_tpg->session_lock);
del_timer_sync(&sess->time2retain_timer);
spin_lock_bh(&se_tpg->session_lock);
sess->time2retain_timer_flags &= ~ISCSI_TF_RUNNING;
pr_debug("Stopped Time2Retain Timer for SID: %u\n",
sess->sid);
return 0;
}
void iscsit_connection_reinstatement_rcfr(struct iscsi_conn *conn)
{
spin_lock_bh(&conn->state_lock);
if (atomic_read(&conn->connection_exit)) {
spin_unlock_bh(&conn->state_lock);
goto sleep;
}
if (atomic_read(&conn->transport_failed)) {
spin_unlock_bh(&conn->state_lock);
goto sleep;
}
spin_unlock_bh(&conn->state_lock);
iscsi_thread_set_force_reinstatement(conn);
sleep:
wait_for_completion(&conn->conn_wait_rcfr_comp);
complete(&conn->conn_post_wait_comp);
}
void iscsit_cause_connection_reinstatement(struct iscsi_conn *conn, int sleep)
{
spin_lock_bh(&conn->state_lock);
if (atomic_read(&conn->connection_exit)) {
spin_unlock_bh(&conn->state_lock);
return;
}
if (atomic_read(&conn->transport_failed)) {
spin_unlock_bh(&conn->state_lock);
return;
}
if (atomic_read(&conn->connection_reinstatement)) {
spin_unlock_bh(&conn->state_lock);
return;
}
if (iscsi_thread_set_force_reinstatement(conn) < 0) {
spin_unlock_bh(&conn->state_lock);
return;
}
atomic_set(&conn->connection_reinstatement, 1);
if (!sleep) {
spin_unlock_bh(&conn->state_lock);
return;
}
atomic_set(&conn->sleep_on_conn_wait_comp, 1);
spin_unlock_bh(&conn->state_lock);
wait_for_completion(&conn->conn_wait_comp);
complete(&conn->conn_post_wait_comp);
}
void iscsit_fall_back_to_erl0(struct iscsi_session *sess)
{
pr_debug("Falling back to ErrorRecoveryLevel=0 for SID:"
" %u\n", sess->sid);
atomic_set(&sess->session_fall_back_to_erl0, 1);
}
static void iscsit_handle_connection_cleanup(struct iscsi_conn *conn)
{
struct iscsi_session *sess = conn->sess;
if ((sess->sess_ops->ErrorRecoveryLevel == 2) &&
!atomic_read(&sess->session_reinstatement) &&
!atomic_read(&sess->session_fall_back_to_erl0))
iscsit_connection_recovery_transport_reset(conn);
else {
pr_debug("Performing cleanup for failed iSCSI"
" Connection ID: %hu from %s\n", conn->cid,
sess->sess_ops->InitiatorName);
iscsit_close_connection(conn);
}
}
extern void iscsit_take_action_for_connection_exit(struct iscsi_conn *conn)
{
spin_lock_bh(&conn->state_lock);
if (atomic_read(&conn->connection_exit)) {
spin_unlock_bh(&conn->state_lock);
return;
}
atomic_set(&conn->connection_exit, 1);
if (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT) {
spin_unlock_bh(&conn->state_lock);
iscsit_close_connection(conn);
return;
}
if (conn->conn_state == TARG_CONN_STATE_CLEANUP_WAIT) {
spin_unlock_bh(&conn->state_lock);
return;
}
pr_debug("Moving to TARG_CONN_STATE_CLEANUP_WAIT.\n");
conn->conn_state = TARG_CONN_STATE_CLEANUP_WAIT;
spin_unlock_bh(&conn->state_lock);
iscsit_handle_connection_cleanup(conn);
}
/*
* This is the simple function that makes the magic of
* sync and steering happen in the follow paradoxical order:
*
* 0) Receive conn->of_marker (bytes left until next OFMarker)
* bytes into an offload buffer. When we pass the exact number
* of bytes in conn->of_marker, iscsit_dump_data_payload() and hence
* rx_data() will automatically receive the identical u32 marker
* values and store it in conn->of_marker_offset;
* 1) Now conn->of_marker_offset will contain the offset to the start
* of the next iSCSI PDU. Dump these remaining bytes into another
* offload buffer.
* 2) We are done!
* Next byte in the TCP stream will contain the next iSCSI PDU!
* Cool Huh?!
*/
int iscsit_recover_from_unknown_opcode(struct iscsi_conn *conn)
{
/*
* Make sure the remaining bytes to next maker is a sane value.
*/
if (conn->of_marker > (conn->conn_ops->OFMarkInt * 4)) {
pr_err("Remaining bytes to OFMarker: %u exceeds"
" OFMarkInt bytes: %u.\n", conn->of_marker,
conn->conn_ops->OFMarkInt * 4);
return -1;
}
pr_debug("Advancing %u bytes in TCP stream to get to the"
" next OFMarker.\n", conn->of_marker);
if (iscsit_dump_data_payload(conn, conn->of_marker, 0) < 0)
return -1;
/*
* Make sure the offset marker we retrived is a valid value.
*/
if (conn->of_marker_offset > (ISCSI_HDR_LEN + (ISCSI_CRC_LEN * 2) +
conn->conn_ops->MaxRecvDataSegmentLength)) {
pr_err("OfMarker offset value: %u exceeds limit.\n",
conn->of_marker_offset);
return -1;
}
pr_debug("Discarding %u bytes of TCP stream to get to the"
" next iSCSI Opcode.\n", conn->of_marker_offset);
if (iscsit_dump_data_payload(conn, conn->of_marker_offset, 0) < 0)
return -1;
return 0;
}
#ifndef ISCSI_TARGET_ERL0_H
#define ISCSI_TARGET_ERL0_H
extern void iscsit_set_dataout_sequence_values(struct iscsi_cmd *);
extern int iscsit_check_pre_dataout(struct iscsi_cmd *, unsigned char *);
extern int iscsit_check_post_dataout(struct iscsi_cmd *, unsigned char *, u8);
extern void iscsit_start_time2retain_handler(struct iscsi_session *);
extern int iscsit_stop_time2retain_timer(struct iscsi_session *);
extern void iscsit_connection_reinstatement_rcfr(struct iscsi_conn *);
extern void iscsit_cause_connection_reinstatement(struct iscsi_conn *, int);
extern void iscsit_fall_back_to_erl0(struct iscsi_session *);
extern void iscsit_take_action_for_connection_exit(struct iscsi_conn *);
extern int iscsit_recover_from_unknown_opcode(struct iscsi_conn *);
#endif /*** ISCSI_TARGET_ERL0_H ***/
/*******************************************************************************
* This file contains error recovery level one used by the iSCSI Target driver.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/list.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include "iscsi_target_core.h"
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_datain_values.h"
#include "iscsi_target_device.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target.h"
#define OFFLOAD_BUF_SIZE 32768
/*
* Used to dump excess datain payload for certain error recovery
* situations. Receive in OFFLOAD_BUF_SIZE max of datain per rx_data().
*
* dump_padding_digest denotes if padding and data digests need
* to be dumped.
*/
int iscsit_dump_data_payload(
struct iscsi_conn *conn,
u32 buf_len,
int dump_padding_digest)
{
char *buf, pad_bytes[4];
int ret = DATAOUT_WITHIN_COMMAND_RECOVERY, rx_got;
u32 length, padding, offset = 0, size;
struct kvec iov;
length = (buf_len > OFFLOAD_BUF_SIZE) ? OFFLOAD_BUF_SIZE : buf_len;
buf = kzalloc(length, GFP_ATOMIC);
if (!buf) {
pr_err("Unable to allocate %u bytes for offload"
" buffer.\n", length);
return -1;
}
memset(&iov, 0, sizeof(struct kvec));
while (offset < buf_len) {
size = ((offset + length) > buf_len) ?
(buf_len - offset) : length;
iov.iov_len = size;
iov.iov_base = buf;
rx_got = rx_data(conn, &iov, 1, size);
if (rx_got != size) {
ret = DATAOUT_CANNOT_RECOVER;
goto out;
}
offset += size;
}
if (!dump_padding_digest)
goto out;
padding = ((-buf_len) & 3);
if (padding != 0) {
iov.iov_len = padding;
iov.iov_base = pad_bytes;
rx_got = rx_data(conn, &iov, 1, padding);
if (rx_got != padding) {
ret = DATAOUT_CANNOT_RECOVER;
goto out;
}
}
if (conn->conn_ops->DataDigest) {
u32 data_crc;
iov.iov_len = ISCSI_CRC_LEN;
iov.iov_base = &data_crc;
rx_got = rx_data(conn, &iov, 1, ISCSI_CRC_LEN);
if (rx_got != ISCSI_CRC_LEN) {
ret = DATAOUT_CANNOT_RECOVER;
goto out;
}
}
out:
kfree(buf);
return ret;
}
/*
* Used for retransmitting R2Ts from a R2T SNACK request.
*/
static int iscsit_send_recovery_r2t_for_snack(
struct iscsi_cmd *cmd,
struct iscsi_r2t *r2t)
{
/*
* If the struct iscsi_r2t has not been sent yet, we can safely
* ignore retransmission
* of the R2TSN in question.
*/
spin_lock_bh(&cmd->r2t_lock);
if (!r2t->sent_r2t) {
spin_unlock_bh(&cmd->r2t_lock);
return 0;
}
r2t->sent_r2t = 0;
spin_unlock_bh(&cmd->r2t_lock);
iscsit_add_cmd_to_immediate_queue(cmd, cmd->conn, ISTATE_SEND_R2T);
return 0;
}
static int iscsit_handle_r2t_snack(
struct iscsi_cmd *cmd,
unsigned char *buf,
u32 begrun,
u32 runlength)
{
u32 last_r2tsn;
struct iscsi_r2t *r2t;
/*
* Make sure the initiator is not requesting retransmission
* of R2TSNs already acknowledged by a TMR TASK_REASSIGN.
*/
if ((cmd->cmd_flags & ICF_GOT_DATACK_SNACK) &&
(begrun <= cmd->acked_data_sn)) {
pr_err("ITT: 0x%08x, R2T SNACK requesting"
" retransmission of R2TSN: 0x%08x to 0x%08x but already"
" acked to R2TSN: 0x%08x by TMR TASK_REASSIGN,"
" protocol error.\n", cmd->init_task_tag, begrun,
(begrun + runlength), cmd->acked_data_sn);
return iscsit_add_reject_from_cmd(
ISCSI_REASON_PROTOCOL_ERROR,
1, 0, buf, cmd);
}
if (runlength) {
if ((begrun + runlength) > cmd->r2t_sn) {
pr_err("Command ITT: 0x%08x received R2T SNACK"
" with BegRun: 0x%08x, RunLength: 0x%08x, exceeds"
" current R2TSN: 0x%08x, protocol error.\n",
cmd->init_task_tag, begrun, runlength, cmd->r2t_sn);
return iscsit_add_reject_from_cmd(
ISCSI_REASON_BOOKMARK_INVALID, 1, 0, buf, cmd);
}
last_r2tsn = (begrun + runlength);
} else
last_r2tsn = cmd->r2t_sn;
while (begrun < last_r2tsn) {
r2t = iscsit_get_holder_for_r2tsn(cmd, begrun);
if (!r2t)
return -1;
if (iscsit_send_recovery_r2t_for_snack(cmd, r2t) < 0)
return -1;
begrun++;
}
return 0;
}
/*
* Generates Offsets and NextBurstLength based on Begrun and Runlength
* carried in a Data SNACK or ExpDataSN in TMR TASK_REASSIGN.
*
* For DataSequenceInOrder=Yes and DataPDUInOrder=[Yes,No] only.
*
* FIXME: How is this handled for a RData SNACK?
*/
int iscsit_create_recovery_datain_values_datasequenceinorder_yes(
struct iscsi_cmd *cmd,
struct iscsi_datain_req *dr)
{
u32 data_sn = 0, data_sn_count = 0;
u32 pdu_start = 0, seq_no = 0;
u32 begrun = dr->begrun;
struct iscsi_conn *conn = cmd->conn;
while (begrun > data_sn++) {
data_sn_count++;
if ((dr->next_burst_len +
conn->conn_ops->MaxRecvDataSegmentLength) <
conn->sess->sess_ops->MaxBurstLength) {
dr->read_data_done +=
conn->conn_ops->MaxRecvDataSegmentLength;
dr->next_burst_len +=
conn->conn_ops->MaxRecvDataSegmentLength;
} else {
dr->read_data_done +=
(conn->sess->sess_ops->MaxBurstLength -
dr->next_burst_len);
dr->next_burst_len = 0;
pdu_start += data_sn_count;
data_sn_count = 0;
seq_no++;
}
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
cmd->seq_no = seq_no;
cmd->pdu_start = pdu_start;
cmd->pdu_send_order = data_sn_count;
}
return 0;
}
/*
* Generates Offsets and NextBurstLength based on Begrun and Runlength
* carried in a Data SNACK or ExpDataSN in TMR TASK_REASSIGN.
*
* For DataSequenceInOrder=No and DataPDUInOrder=[Yes,No] only.
*
* FIXME: How is this handled for a RData SNACK?
*/
int iscsit_create_recovery_datain_values_datasequenceinorder_no(
struct iscsi_cmd *cmd,
struct iscsi_datain_req *dr)
{
int found_seq = 0, i;
u32 data_sn, read_data_done = 0, seq_send_order = 0;
u32 begrun = dr->begrun;
u32 runlength = dr->runlength;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_seq *first_seq = NULL, *seq = NULL;
if (!cmd->seq_list) {
pr_err("struct iscsi_cmd->seq_list is NULL!\n");
return -1;
}
/*
* Calculate read_data_done for all sequences containing a
* first_datasn and last_datasn less than the BegRun.
*
* Locate the struct iscsi_seq the BegRun lies within and calculate
* NextBurstLenghth up to the DataSN based on MaxRecvDataSegmentLength.
*
* Also use struct iscsi_seq->seq_send_order to determine where to start.
*/
for (i = 0; i < cmd->seq_count; i++) {
seq = &cmd->seq_list[i];
if (!seq->seq_send_order)
first_seq = seq;
/*
* No data has been transferred for this DataIN sequence, so the
* seq->first_datasn and seq->last_datasn have not been set.
*/
if (!seq->sent) {
#if 0
pr_err("Ignoring non-sent sequence 0x%08x ->"
" 0x%08x\n\n", seq->first_datasn,
seq->last_datasn);
#endif
continue;
}
/*
* This DataIN sequence is precedes the received BegRun, add the
* total xfer_len of the sequence to read_data_done and reset
* seq->pdu_send_order.
*/
if ((seq->first_datasn < begrun) &&
(seq->last_datasn < begrun)) {
#if 0
pr_err("Pre BegRun sequence 0x%08x ->"
" 0x%08x\n", seq->first_datasn,
seq->last_datasn);
#endif
read_data_done += cmd->seq_list[i].xfer_len;
seq->next_burst_len = seq->pdu_send_order = 0;
continue;
}
/*
* The BegRun lies within this DataIN sequence.
*/
if ((seq->first_datasn <= begrun) &&
(seq->last_datasn >= begrun)) {
#if 0
pr_err("Found sequence begrun: 0x%08x in"
" 0x%08x -> 0x%08x\n", begrun,
seq->first_datasn, seq->last_datasn);
#endif
seq_send_order = seq->seq_send_order;
data_sn = seq->first_datasn;
seq->next_burst_len = seq->pdu_send_order = 0;
found_seq = 1;
/*
* For DataPDUInOrder=Yes, while the first DataSN of
* the sequence is less than the received BegRun, add
* the MaxRecvDataSegmentLength to read_data_done and
* to the sequence's next_burst_len;
*
* For DataPDUInOrder=No, while the first DataSN of the
* sequence is less than the received BegRun, find the
* struct iscsi_pdu of the DataSN in question and add the
* MaxRecvDataSegmentLength to read_data_done and to the
* sequence's next_burst_len;
*/
if (conn->sess->sess_ops->DataPDUInOrder) {
while (data_sn < begrun) {
seq->pdu_send_order++;
read_data_done +=
conn->conn_ops->MaxRecvDataSegmentLength;
seq->next_burst_len +=
conn->conn_ops->MaxRecvDataSegmentLength;
data_sn++;
}
} else {
int j;
struct iscsi_pdu *pdu;
while (data_sn < begrun) {
seq->pdu_send_order++;
for (j = 0; j < seq->pdu_count; j++) {
pdu = &cmd->pdu_list[
seq->pdu_start + j];
if (pdu->data_sn == data_sn) {
read_data_done +=
pdu->length;
seq->next_burst_len +=
pdu->length;
}
}
data_sn++;
}
}
continue;
}
/*
* This DataIN sequence is larger than the received BegRun,
* reset seq->pdu_send_order and continue.
*/
if ((seq->first_datasn > begrun) ||
(seq->last_datasn > begrun)) {
#if 0
pr_err("Post BegRun sequence 0x%08x -> 0x%08x\n",
seq->first_datasn, seq->last_datasn);
#endif
seq->next_burst_len = seq->pdu_send_order = 0;
continue;
}
}
if (!found_seq) {
if (!begrun) {
if (!first_seq) {
pr_err("ITT: 0x%08x, Begrun: 0x%08x"
" but first_seq is NULL\n",
cmd->init_task_tag, begrun);
return -1;
}
seq_send_order = first_seq->seq_send_order;
seq->next_burst_len = seq->pdu_send_order = 0;
goto done;
}
pr_err("Unable to locate struct iscsi_seq for ITT: 0x%08x,"
" BegRun: 0x%08x, RunLength: 0x%08x while"
" DataSequenceInOrder=No and DataPDUInOrder=%s.\n",
cmd->init_task_tag, begrun, runlength,
(conn->sess->sess_ops->DataPDUInOrder) ? "Yes" : "No");
return -1;
}
done:
dr->read_data_done = read_data_done;
dr->seq_send_order = seq_send_order;
return 0;
}
static int iscsit_handle_recovery_datain(
struct iscsi_cmd *cmd,
unsigned char *buf,
u32 begrun,
u32 runlength)
{
struct iscsi_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct se_cmd *se_cmd = &cmd->se_cmd;
if (!atomic_read(&se_cmd->t_transport_complete)) {
pr_err("Ignoring ITT: 0x%08x Data SNACK\n",
cmd->init_task_tag);
return 0;
}
/*
* Make sure the initiator is not requesting retransmission
* of DataSNs already acknowledged by a Data ACK SNACK.
*/
if ((cmd->cmd_flags & ICF_GOT_DATACK_SNACK) &&
(begrun <= cmd->acked_data_sn)) {
pr_err("ITT: 0x%08x, Data SNACK requesting"
" retransmission of DataSN: 0x%08x to 0x%08x but"
" already acked to DataSN: 0x%08x by Data ACK SNACK,"
" protocol error.\n", cmd->init_task_tag, begrun,
(begrun + runlength), cmd->acked_data_sn);
return iscsit_add_reject_from_cmd(ISCSI_REASON_PROTOCOL_ERROR,
1, 0, buf, cmd);
}
/*
* Make sure BegRun and RunLength in the Data SNACK are sane.
* Note: (cmd->data_sn - 1) will carry the maximum DataSN sent.
*/
if ((begrun + runlength) > (cmd->data_sn - 1)) {
pr_err("Initiator requesting BegRun: 0x%08x, RunLength"
": 0x%08x greater than maximum DataSN: 0x%08x.\n",
begrun, runlength, (cmd->data_sn - 1));
return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_INVALID,
1, 0, buf, cmd);
}
dr = iscsit_allocate_datain_req();
if (!dr)
return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_NO_RESOURCES,
1, 0, buf, cmd);
dr->data_sn = dr->begrun = begrun;
dr->runlength = runlength;
dr->generate_recovery_values = 1;
dr->recovery = DATAIN_WITHIN_COMMAND_RECOVERY;
iscsit_attach_datain_req(cmd, dr);
cmd->i_state = ISTATE_SEND_DATAIN;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
int iscsit_handle_recovery_datain_or_r2t(
struct iscsi_conn *conn,
unsigned char *buf,
u32 init_task_tag,
u32 targ_xfer_tag,
u32 begrun,
u32 runlength)
{
struct iscsi_cmd *cmd;
cmd = iscsit_find_cmd_from_itt(conn, init_task_tag);
if (!cmd)
return 0;
/*
* FIXME: This will not work for bidi commands.
*/
switch (cmd->data_direction) {
case DMA_TO_DEVICE:
return iscsit_handle_r2t_snack(cmd, buf, begrun, runlength);
case DMA_FROM_DEVICE:
return iscsit_handle_recovery_datain(cmd, buf, begrun,
runlength);
default:
pr_err("Unknown cmd->data_direction: 0x%02x\n",
cmd->data_direction);
return -1;
}
return 0;
}
/* #warning FIXME: Status SNACK needs to be dependent on OPCODE!!! */
int iscsit_handle_status_snack(
struct iscsi_conn *conn,
u32 init_task_tag,
u32 targ_xfer_tag,
u32 begrun,
u32 runlength)
{
struct iscsi_cmd *cmd = NULL;
u32 last_statsn;
int found_cmd;
if (conn->exp_statsn > begrun) {
pr_err("Got Status SNACK Begrun: 0x%08x, RunLength:"
" 0x%08x but already got ExpStatSN: 0x%08x on CID:"
" %hu.\n", begrun, runlength, conn->exp_statsn,
conn->cid);
return 0;
}
last_statsn = (!runlength) ? conn->stat_sn : (begrun + runlength);
while (begrun < last_statsn) {
found_cmd = 0;
spin_lock_bh(&conn->cmd_lock);
list_for_each_entry(cmd, &conn->conn_cmd_list, i_list) {
if (cmd->stat_sn == begrun) {
found_cmd = 1;
break;
}
}
spin_unlock_bh(&conn->cmd_lock);
if (!found_cmd) {
pr_err("Unable to find StatSN: 0x%08x for"
" a Status SNACK, assuming this was a"
" protactic SNACK for an untransmitted"
" StatSN, ignoring.\n", begrun);
begrun++;
continue;
}
spin_lock_bh(&cmd->istate_lock);
if (cmd->i_state == ISTATE_SEND_DATAIN) {
spin_unlock_bh(&cmd->istate_lock);
pr_err("Ignoring Status SNACK for BegRun:"
" 0x%08x, RunLength: 0x%08x, assuming this was"
" a protactic SNACK for an untransmitted"
" StatSN\n", begrun, runlength);
begrun++;
continue;
}
spin_unlock_bh(&cmd->istate_lock);
cmd->i_state = ISTATE_SEND_STATUS_RECOVERY;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
begrun++;
}
return 0;
}
int iscsit_handle_data_ack(
struct iscsi_conn *conn,
u32 targ_xfer_tag,
u32 begrun,
u32 runlength)
{
struct iscsi_cmd *cmd = NULL;
cmd = iscsit_find_cmd_from_ttt(conn, targ_xfer_tag);
if (!cmd) {
pr_err("Data ACK SNACK for TTT: 0x%08x is"
" invalid.\n", targ_xfer_tag);
return -1;
}
if (begrun <= cmd->acked_data_sn) {
pr_err("ITT: 0x%08x Data ACK SNACK BegRUN: 0x%08x is"
" less than the already acked DataSN: 0x%08x.\n",
cmd->init_task_tag, begrun, cmd->acked_data_sn);
return -1;
}
/*
* For Data ACK SNACK, BegRun is the next expected DataSN.
* (see iSCSI v19: 10.16.6)
*/
cmd->cmd_flags |= ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = (begrun - 1);
pr_debug("Received Data ACK SNACK for ITT: 0x%08x,"
" updated acked DataSN to 0x%08x.\n",
cmd->init_task_tag, cmd->acked_data_sn);
return 0;
}
static int iscsit_send_recovery_r2t(
struct iscsi_cmd *cmd,
u32 offset,
u32 xfer_len)
{
int ret;
spin_lock_bh(&cmd->r2t_lock);
ret = iscsit_add_r2t_to_list(cmd, offset, xfer_len, 1, 0);
spin_unlock_bh(&cmd->r2t_lock);
return ret;
}
int iscsit_dataout_datapduinorder_no_fbit(
struct iscsi_cmd *cmd,
struct iscsi_pdu *pdu)
{
int i, send_recovery_r2t = 0, recovery = 0;
u32 length = 0, offset = 0, pdu_count = 0, xfer_len = 0;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_pdu *first_pdu = NULL;
/*
* Get an struct iscsi_pdu pointer to the first PDU, and total PDU count
* of the DataOUT sequence.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
for (i = 0; i < cmd->pdu_count; i++) {
if (cmd->pdu_list[i].seq_no == pdu->seq_no) {
if (!first_pdu)
first_pdu = &cmd->pdu_list[i];
xfer_len += cmd->pdu_list[i].length;
pdu_count++;
} else if (pdu_count)
break;
}
} else {
struct iscsi_seq *seq = cmd->seq_ptr;
first_pdu = &cmd->pdu_list[seq->pdu_start];
pdu_count = seq->pdu_count;
}
if (!first_pdu || !pdu_count)
return DATAOUT_CANNOT_RECOVER;
/*
* Loop through the ending DataOUT Sequence checking each struct iscsi_pdu.
* The following ugly logic does batching of not received PDUs.
*/
for (i = 0; i < pdu_count; i++) {
if (first_pdu[i].status == ISCSI_PDU_RECEIVED_OK) {
if (!send_recovery_r2t)
continue;
if (iscsit_send_recovery_r2t(cmd, offset, length) < 0)
return DATAOUT_CANNOT_RECOVER;
send_recovery_r2t = length = offset = 0;
continue;
}
/*
* Set recovery = 1 for any missing, CRC failed, or timed
* out PDUs to let the DataOUT logic know that this sequence
* has not been completed yet.
*
* Also, only send a Recovery R2T for ISCSI_PDU_NOT_RECEIVED.
* We assume if the PDU either failed CRC or timed out
* that a Recovery R2T has already been sent.
*/
recovery = 1;
if (first_pdu[i].status != ISCSI_PDU_NOT_RECEIVED)
continue;
if (!offset)
offset = first_pdu[i].offset;
length += first_pdu[i].length;
send_recovery_r2t = 1;
}
if (send_recovery_r2t)
if (iscsit_send_recovery_r2t(cmd, offset, length) < 0)
return DATAOUT_CANNOT_RECOVER;
return (!recovery) ? DATAOUT_NORMAL : DATAOUT_WITHIN_COMMAND_RECOVERY;
}
static int iscsit_recalculate_dataout_values(
struct iscsi_cmd *cmd,
u32 pdu_offset,
u32 pdu_length,
u32 *r2t_offset,
u32 *r2t_length)
{
int i;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_pdu *pdu = NULL;
if (conn->sess->sess_ops->DataSequenceInOrder) {
cmd->data_sn = 0;
if (conn->sess->sess_ops->DataPDUInOrder) {
*r2t_offset = cmd->write_data_done;
*r2t_length = (cmd->seq_end_offset -
cmd->write_data_done);
return 0;
}
*r2t_offset = cmd->seq_start_offset;
*r2t_length = (cmd->seq_end_offset - cmd->seq_start_offset);
for (i = 0; i < cmd->pdu_count; i++) {
pdu = &cmd->pdu_list[i];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
if ((pdu->offset >= cmd->seq_start_offset) &&
((pdu->offset + pdu->length) <=
cmd->seq_end_offset)) {
if (!cmd->unsolicited_data)
cmd->next_burst_len -= pdu->length;
else
cmd->first_burst_len -= pdu->length;
cmd->write_data_done -= pdu->length;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
} else {
struct iscsi_seq *seq = NULL;
seq = iscsit_get_seq_holder(cmd, pdu_offset, pdu_length);
if (!seq)
return -1;
*r2t_offset = seq->orig_offset;
*r2t_length = seq->xfer_len;
cmd->write_data_done -= (seq->offset - seq->orig_offset);
if (cmd->immediate_data)
cmd->first_burst_len = cmd->write_data_done;
seq->data_sn = 0;
seq->offset = seq->orig_offset;
seq->next_burst_len = 0;
seq->status = DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY;
if (conn->sess->sess_ops->DataPDUInOrder)
return 0;
for (i = 0; i < seq->pdu_count; i++) {
pdu = &cmd->pdu_list[i+seq->pdu_start];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
return 0;
}
int iscsit_recover_dataout_sequence(
struct iscsi_cmd *cmd,
u32 pdu_offset,
u32 pdu_length)
{
u32 r2t_length = 0, r2t_offset = 0;
spin_lock_bh(&cmd->istate_lock);
cmd->cmd_flags |= ICF_WITHIN_COMMAND_RECOVERY;
spin_unlock_bh(&cmd->istate_lock);
if (iscsit_recalculate_dataout_values(cmd, pdu_offset, pdu_length,
&r2t_offset, &r2t_length) < 0)
return DATAOUT_CANNOT_RECOVER;
iscsit_send_recovery_r2t(cmd, r2t_offset, r2t_length);
return DATAOUT_WITHIN_COMMAND_RECOVERY;
}
static struct iscsi_ooo_cmdsn *iscsit_allocate_ooo_cmdsn(void)
{
struct iscsi_ooo_cmdsn *ooo_cmdsn = NULL;
ooo_cmdsn = kmem_cache_zalloc(lio_ooo_cache, GFP_ATOMIC);
if (!ooo_cmdsn) {
pr_err("Unable to allocate memory for"
" struct iscsi_ooo_cmdsn.\n");
return NULL;
}
INIT_LIST_HEAD(&ooo_cmdsn->ooo_list);
return ooo_cmdsn;
}
/*
* Called with sess->cmdsn_mutex held.
*/
static int iscsit_attach_ooo_cmdsn(
struct iscsi_session *sess,
struct iscsi_ooo_cmdsn *ooo_cmdsn)
{
struct iscsi_ooo_cmdsn *ooo_tail, *ooo_tmp;
/*
* We attach the struct iscsi_ooo_cmdsn entry to the out of order
* list in increasing CmdSN order.
* This allows iscsi_execute_ooo_cmdsns() to detect any
* additional CmdSN holes while performing delayed execution.
*/
if (list_empty(&sess->sess_ooo_cmdsn_list))
list_add_tail(&ooo_cmdsn->ooo_list,
&sess->sess_ooo_cmdsn_list);
else {
ooo_tail = list_entry(sess->sess_ooo_cmdsn_list.prev,
typeof(*ooo_tail), ooo_list);
/*
* CmdSN is greater than the tail of the list.
*/
if (ooo_tail->cmdsn < ooo_cmdsn->cmdsn)
list_add_tail(&ooo_cmdsn->ooo_list,
&sess->sess_ooo_cmdsn_list);
else {
/*
* CmdSN is either lower than the head, or somewhere
* in the middle.
*/
list_for_each_entry(ooo_tmp, &sess->sess_ooo_cmdsn_list,
ooo_list) {
while (ooo_tmp->cmdsn < ooo_cmdsn->cmdsn)
continue;
list_add(&ooo_cmdsn->ooo_list,
&ooo_tmp->ooo_list);
break;
}
}
}
return 0;
}
/*
* Removes an struct iscsi_ooo_cmdsn from a session's list,
* called with struct iscsi_session->cmdsn_mutex held.
*/
void iscsit_remove_ooo_cmdsn(
struct iscsi_session *sess,
struct iscsi_ooo_cmdsn *ooo_cmdsn)
{
list_del(&ooo_cmdsn->ooo_list);
kmem_cache_free(lio_ooo_cache, ooo_cmdsn);
}
void iscsit_clear_ooo_cmdsns_for_conn(struct iscsi_conn *conn)
{
struct iscsi_ooo_cmdsn *ooo_cmdsn;
struct iscsi_session *sess = conn->sess;
mutex_lock(&sess->cmdsn_mutex);
list_for_each_entry(ooo_cmdsn, &sess->sess_ooo_cmdsn_list, ooo_list) {
if (ooo_cmdsn->cid != conn->cid)
continue;
ooo_cmdsn->cmd = NULL;
}
mutex_unlock(&sess->cmdsn_mutex);
}
/*
* Called with sess->cmdsn_mutex held.
*/
int iscsit_execute_ooo_cmdsns(struct iscsi_session *sess)
{
int ooo_count = 0;
struct iscsi_cmd *cmd = NULL;
struct iscsi_ooo_cmdsn *ooo_cmdsn, *ooo_cmdsn_tmp;
list_for_each_entry_safe(ooo_cmdsn, ooo_cmdsn_tmp,
&sess->sess_ooo_cmdsn_list, ooo_list) {
if (ooo_cmdsn->cmdsn != sess->exp_cmd_sn)
continue;
if (!ooo_cmdsn->cmd) {
sess->exp_cmd_sn++;
iscsit_remove_ooo_cmdsn(sess, ooo_cmdsn);
continue;
}
cmd = ooo_cmdsn->cmd;
cmd->i_state = cmd->deferred_i_state;
ooo_count++;
sess->exp_cmd_sn++;
pr_debug("Executing out of order CmdSN: 0x%08x,"
" incremented ExpCmdSN to 0x%08x.\n",
cmd->cmd_sn, sess->exp_cmd_sn);
iscsit_remove_ooo_cmdsn(sess, ooo_cmdsn);
if (iscsit_execute_cmd(cmd, 1) < 0)
return -1;
continue;
}
return ooo_count;
}
/*
* Called either:
*
* 1. With sess->cmdsn_mutex held from iscsi_execute_ooo_cmdsns()
* or iscsi_check_received_cmdsn().
* 2. With no locks held directly from iscsi_handle_XXX_pdu() functions
* for immediate commands.
*/
int iscsit_execute_cmd(struct iscsi_cmd *cmd, int ooo)
{
struct se_cmd *se_cmd = &cmd->se_cmd;
int lr = 0;
spin_lock_bh(&cmd->istate_lock);
if (ooo)
cmd->cmd_flags &= ~ICF_OOO_CMDSN;
switch (cmd->iscsi_opcode) {
case ISCSI_OP_SCSI_CMD:
/*
* Go ahead and send the CHECK_CONDITION status for
* any SCSI CDB exceptions that may have occurred, also
* handle the SCF_SCSI_RESERVATION_CONFLICT case here as well.
*/
if (se_cmd->se_cmd_flags & SCF_SCSI_CDB_EXCEPTION) {
if (se_cmd->se_cmd_flags &
SCF_SCSI_RESERVATION_CONFLICT) {
cmd->i_state = ISTATE_SEND_STATUS;
spin_unlock_bh(&cmd->istate_lock);
iscsit_add_cmd_to_response_queue(cmd, cmd->conn,
cmd->i_state);
return 0;
}
spin_unlock_bh(&cmd->istate_lock);
/*
* Determine if delayed TASK_ABORTED status for WRITEs
* should be sent now if no unsolicited data out
* payloads are expected, or if the delayed status
* should be sent after unsolicited data out with
* ISCSI_FLAG_CMD_FINAL set in iscsi_handle_data_out()
*/
if (transport_check_aborted_status(se_cmd,
(cmd->unsolicited_data == 0)) != 0)
return 0;
/*
* Otherwise send CHECK_CONDITION and sense for
* exception
*/
return transport_send_check_condition_and_sense(se_cmd,
se_cmd->scsi_sense_reason, 0);
}
/*
* Special case for delayed CmdSN with Immediate
* Data and/or Unsolicited Data Out attached.
*/
if (cmd->immediate_data) {
if (cmd->cmd_flags & ICF_GOT_LAST_DATAOUT) {
spin_unlock_bh(&cmd->istate_lock);
return transport_generic_handle_data(
&cmd->se_cmd);
}
spin_unlock_bh(&cmd->istate_lock);
if (!(cmd->cmd_flags &
ICF_NON_IMMEDIATE_UNSOLICITED_DATA)) {
/*
* Send the delayed TASK_ABORTED status for
* WRITEs if no more unsolicitied data is
* expected.
*/
if (transport_check_aborted_status(se_cmd, 1)
!= 0)
return 0;
iscsit_set_dataout_sequence_values(cmd);
iscsit_build_r2ts_for_cmd(cmd, cmd->conn, 0);
}
return 0;
}
/*
* The default handler.
*/
spin_unlock_bh(&cmd->istate_lock);
if ((cmd->data_direction == DMA_TO_DEVICE) &&
!(cmd->cmd_flags & ICF_NON_IMMEDIATE_UNSOLICITED_DATA)) {
/*
* Send the delayed TASK_ABORTED status for WRITEs if
* no more nsolicitied data is expected.
*/
if (transport_check_aborted_status(se_cmd, 1) != 0)
return 0;
iscsit_set_dataout_sequence_values(cmd);
spin_lock_bh(&cmd->dataout_timeout_lock);
iscsit_start_dataout_timer(cmd, cmd->conn);
spin_unlock_bh(&cmd->dataout_timeout_lock);
}
return transport_handle_cdb_direct(&cmd->se_cmd);
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_TEXT:
spin_unlock_bh(&cmd->istate_lock);
iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state);
break;
case ISCSI_OP_SCSI_TMFUNC:
if (se_cmd->se_cmd_flags & SCF_SCSI_CDB_EXCEPTION) {
spin_unlock_bh(&cmd->istate_lock);
iscsit_add_cmd_to_response_queue(cmd, cmd->conn,
cmd->i_state);
return 0;
}
spin_unlock_bh(&cmd->istate_lock);
return transport_generic_handle_tmr(&cmd->se_cmd);
case ISCSI_OP_LOGOUT:
spin_unlock_bh(&cmd->istate_lock);
switch (cmd->logout_reason) {
case ISCSI_LOGOUT_REASON_CLOSE_SESSION:
lr = iscsit_logout_closesession(cmd, cmd->conn);
break;
case ISCSI_LOGOUT_REASON_CLOSE_CONNECTION:
lr = iscsit_logout_closeconnection(cmd, cmd->conn);
break;
case ISCSI_LOGOUT_REASON_RECOVERY:
lr = iscsit_logout_removeconnforrecovery(cmd, cmd->conn);
break;
default:
pr_err("Unknown iSCSI Logout Request Code:"
" 0x%02x\n", cmd->logout_reason);
return -1;
}
return lr;
default:
spin_unlock_bh(&cmd->istate_lock);
pr_err("Cannot perform out of order execution for"
" unknown iSCSI Opcode: 0x%02x\n", cmd->iscsi_opcode);
return -1;
}
return 0;
}
void iscsit_free_all_ooo_cmdsns(struct iscsi_session *sess)
{
struct iscsi_ooo_cmdsn *ooo_cmdsn, *ooo_cmdsn_tmp;
mutex_lock(&sess->cmdsn_mutex);
list_for_each_entry_safe(ooo_cmdsn, ooo_cmdsn_tmp,
&sess->sess_ooo_cmdsn_list, ooo_list) {
list_del(&ooo_cmdsn->ooo_list);
kmem_cache_free(lio_ooo_cache, ooo_cmdsn);
}
mutex_unlock(&sess->cmdsn_mutex);
}
int iscsit_handle_ooo_cmdsn(
struct iscsi_session *sess,
struct iscsi_cmd *cmd,
u32 cmdsn)
{
int batch = 0;
struct iscsi_ooo_cmdsn *ooo_cmdsn = NULL, *ooo_tail = NULL;
cmd->deferred_i_state = cmd->i_state;
cmd->i_state = ISTATE_DEFERRED_CMD;
cmd->cmd_flags |= ICF_OOO_CMDSN;
if (list_empty(&sess->sess_ooo_cmdsn_list))
batch = 1;
else {
ooo_tail = list_entry(sess->sess_ooo_cmdsn_list.prev,
typeof(*ooo_tail), ooo_list);
if (ooo_tail->cmdsn != (cmdsn - 1))
batch = 1;
}
ooo_cmdsn = iscsit_allocate_ooo_cmdsn();
if (!ooo_cmdsn)
return CMDSN_ERROR_CANNOT_RECOVER;
ooo_cmdsn->cmd = cmd;
ooo_cmdsn->batch_count = (batch) ?
(cmdsn - sess->exp_cmd_sn) : 1;
ooo_cmdsn->cid = cmd->conn->cid;
ooo_cmdsn->exp_cmdsn = sess->exp_cmd_sn;
ooo_cmdsn->cmdsn = cmdsn;
if (iscsit_attach_ooo_cmdsn(sess, ooo_cmdsn) < 0) {
kmem_cache_free(lio_ooo_cache, ooo_cmdsn);
return CMDSN_ERROR_CANNOT_RECOVER;
}
return CMDSN_HIGHER_THAN_EXP;
}
static int iscsit_set_dataout_timeout_values(
struct iscsi_cmd *cmd,
u32 *offset,
u32 *length)
{
struct iscsi_conn *conn = cmd->conn;
struct iscsi_r2t *r2t;
if (cmd->unsolicited_data) {
*offset = 0;
*length = (conn->sess->sess_ops->FirstBurstLength >
cmd->data_length) ?
cmd->data_length :
conn->sess->sess_ops->FirstBurstLength;
return 0;
}
spin_lock_bh(&cmd->r2t_lock);
if (list_empty(&cmd->cmd_r2t_list)) {
pr_err("cmd->cmd_r2t_list is empty!\n");
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
list_for_each_entry(r2t, &cmd->cmd_r2t_list, r2t_list) {
if (r2t->sent_r2t && !r2t->recovery_r2t && !r2t->seq_complete) {
*offset = r2t->offset;
*length = r2t->xfer_len;
spin_unlock_bh(&cmd->r2t_lock);
return 0;
}
}
spin_unlock_bh(&cmd->r2t_lock);
pr_err("Unable to locate any incomplete DataOUT"
" sequences for ITT: 0x%08x.\n", cmd->init_task_tag);
return -1;
}
/*
* NOTE: Called from interrupt (timer) context.
*/
static void iscsit_handle_dataout_timeout(unsigned long data)
{
u32 pdu_length = 0, pdu_offset = 0;
u32 r2t_length = 0, r2t_offset = 0;
struct iscsi_cmd *cmd = (struct iscsi_cmd *) data;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_session *sess = NULL;
struct iscsi_node_attrib *na;
iscsit_inc_conn_usage_count(conn);
spin_lock_bh(&cmd->dataout_timeout_lock);
if (cmd->dataout_timer_flags & ISCSI_TF_STOP) {
spin_unlock_bh(&cmd->dataout_timeout_lock);
iscsit_dec_conn_usage_count(conn);
return;
}
cmd->dataout_timer_flags &= ~ISCSI_TF_RUNNING;
sess = conn->sess;
na = iscsit_tpg_get_node_attrib(sess);
if (!sess->sess_ops->ErrorRecoveryLevel) {
pr_debug("Unable to recover from DataOut timeout while"
" in ERL=0.\n");
goto failure;
}
if (++cmd->dataout_timeout_retries == na->dataout_timeout_retries) {
pr_debug("Command ITT: 0x%08x exceeded max retries"
" for DataOUT timeout %u, closing iSCSI connection.\n",
cmd->init_task_tag, na->dataout_timeout_retries);
goto failure;
}
cmd->cmd_flags |= ICF_WITHIN_COMMAND_RECOVERY;
if (conn->sess->sess_ops->DataSequenceInOrder) {
if (conn->sess->sess_ops->DataPDUInOrder) {
pdu_offset = cmd->write_data_done;
if ((pdu_offset + (conn->sess->sess_ops->MaxBurstLength -
cmd->next_burst_len)) > cmd->data_length)
pdu_length = (cmd->data_length -
cmd->write_data_done);
else
pdu_length = (conn->sess->sess_ops->MaxBurstLength -
cmd->next_burst_len);
} else {
pdu_offset = cmd->seq_start_offset;
pdu_length = (cmd->seq_end_offset -
cmd->seq_start_offset);
}
} else {
if (iscsit_set_dataout_timeout_values(cmd, &pdu_offset,
&pdu_length) < 0)
goto failure;
}
if (iscsit_recalculate_dataout_values(cmd, pdu_offset, pdu_length,
&r2t_offset, &r2t_length) < 0)
goto failure;
pr_debug("Command ITT: 0x%08x timed out waiting for"
" completion of %sDataOUT Sequence Offset: %u, Length: %u\n",
cmd->init_task_tag, (cmd->unsolicited_data) ? "Unsolicited " :
"", r2t_offset, r2t_length);
if (iscsit_send_recovery_r2t(cmd, r2t_offset, r2t_length) < 0)
goto failure;
iscsit_start_dataout_timer(cmd, conn);
spin_unlock_bh(&cmd->dataout_timeout_lock);
iscsit_dec_conn_usage_count(conn);
return;
failure:
spin_unlock_bh(&cmd->dataout_timeout_lock);
iscsit_cause_connection_reinstatement(conn, 0);
iscsit_dec_conn_usage_count(conn);
}
void iscsit_mod_dataout_timer(struct iscsi_cmd *cmd)
{
struct iscsi_conn *conn = cmd->conn;
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na = na = iscsit_tpg_get_node_attrib(sess);
spin_lock_bh(&cmd->dataout_timeout_lock);
if (!(cmd->dataout_timer_flags & ISCSI_TF_RUNNING)) {
spin_unlock_bh(&cmd->dataout_timeout_lock);
return;
}
mod_timer(&cmd->dataout_timer,
(get_jiffies_64() + na->dataout_timeout * HZ));
pr_debug("Updated DataOUT timer for ITT: 0x%08x",
cmd->init_task_tag);
spin_unlock_bh(&cmd->dataout_timeout_lock);
}
/*
* Called with cmd->dataout_timeout_lock held.
*/
void iscsit_start_dataout_timer(
struct iscsi_cmd *cmd,
struct iscsi_conn *conn)
{
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na = na = iscsit_tpg_get_node_attrib(sess);
if (cmd->dataout_timer_flags & ISCSI_TF_RUNNING)
return;
pr_debug("Starting DataOUT timer for ITT: 0x%08x on"
" CID: %hu.\n", cmd->init_task_tag, conn->cid);
init_timer(&cmd->dataout_timer);
cmd->dataout_timer.expires = (get_jiffies_64() + na->dataout_timeout * HZ);
cmd->dataout_timer.data = (unsigned long)cmd;
cmd->dataout_timer.function = iscsit_handle_dataout_timeout;
cmd->dataout_timer_flags &= ~ISCSI_TF_STOP;
cmd->dataout_timer_flags |= ISCSI_TF_RUNNING;
add_timer(&cmd->dataout_timer);
}
void iscsit_stop_dataout_timer(struct iscsi_cmd *cmd)
{
spin_lock_bh(&cmd->dataout_timeout_lock);
if (!(cmd->dataout_timer_flags & ISCSI_TF_RUNNING)) {
spin_unlock_bh(&cmd->dataout_timeout_lock);
return;
}
cmd->dataout_timer_flags |= ISCSI_TF_STOP;
spin_unlock_bh(&cmd->dataout_timeout_lock);
del_timer_sync(&cmd->dataout_timer);
spin_lock_bh(&cmd->dataout_timeout_lock);
cmd->dataout_timer_flags &= ~ISCSI_TF_RUNNING;
pr_debug("Stopped DataOUT Timer for ITT: 0x%08x\n",
cmd->init_task_tag);
spin_unlock_bh(&cmd->dataout_timeout_lock);
}
#ifndef ISCSI_TARGET_ERL1_H
#define ISCSI_TARGET_ERL1_H
extern int iscsit_dump_data_payload(struct iscsi_conn *, u32, int);
extern int iscsit_create_recovery_datain_values_datasequenceinorder_yes(
struct iscsi_cmd *, struct iscsi_datain_req *);
extern int iscsit_create_recovery_datain_values_datasequenceinorder_no(
struct iscsi_cmd *, struct iscsi_datain_req *);
extern int iscsit_handle_recovery_datain_or_r2t(struct iscsi_conn *, unsigned char *,
u32, u32, u32, u32);
extern int iscsit_handle_status_snack(struct iscsi_conn *, u32, u32,
u32, u32);
extern int iscsit_handle_data_ack(struct iscsi_conn *, u32, u32, u32);
extern int iscsit_dataout_datapduinorder_no_fbit(struct iscsi_cmd *, struct iscsi_pdu *);
extern int iscsit_recover_dataout_sequence(struct iscsi_cmd *, u32, u32);
extern void iscsit_clear_ooo_cmdsns_for_conn(struct iscsi_conn *);
extern void iscsit_free_all_ooo_cmdsns(struct iscsi_session *);
extern int iscsit_execute_ooo_cmdsns(struct iscsi_session *);
extern int iscsit_execute_cmd(struct iscsi_cmd *, int);
extern int iscsit_handle_ooo_cmdsn(struct iscsi_session *, struct iscsi_cmd *, u32);
extern void iscsit_remove_ooo_cmdsn(struct iscsi_session *, struct iscsi_ooo_cmdsn *);
extern void iscsit_mod_dataout_timer(struct iscsi_cmd *);
extern void iscsit_start_dataout_timer(struct iscsi_cmd *, struct iscsi_conn *);
extern void iscsit_stop_dataout_timer(struct iscsi_cmd *);
#endif /* ISCSI_TARGET_ERL1_H */
/*******************************************************************************
* This file contains error recovery level two functions used by
* the iSCSI Target driver.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include "iscsi_target_core.h"
#include "iscsi_target_datain_values.h"
#include "iscsi_target_util.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target.h"
/*
* FIXME: Does RData SNACK apply here as well?
*/
void iscsit_create_conn_recovery_datain_values(
struct iscsi_cmd *cmd,
u32 exp_data_sn)
{
u32 data_sn = 0;
struct iscsi_conn *conn = cmd->conn;
cmd->next_burst_len = 0;
cmd->read_data_done = 0;
while (exp_data_sn > data_sn) {
if ((cmd->next_burst_len +
conn->conn_ops->MaxRecvDataSegmentLength) <
conn->sess->sess_ops->MaxBurstLength) {
cmd->read_data_done +=
conn->conn_ops->MaxRecvDataSegmentLength;
cmd->next_burst_len +=
conn->conn_ops->MaxRecvDataSegmentLength;
} else {
cmd->read_data_done +=
(conn->sess->sess_ops->MaxBurstLength -
cmd->next_burst_len);
cmd->next_burst_len = 0;
}
data_sn++;
}
}
void iscsit_create_conn_recovery_dataout_values(
struct iscsi_cmd *cmd)
{
u32 write_data_done = 0;
struct iscsi_conn *conn = cmd->conn;
cmd->data_sn = 0;
cmd->next_burst_len = 0;
while (cmd->write_data_done > write_data_done) {
if ((write_data_done + conn->sess->sess_ops->MaxBurstLength) <=
cmd->write_data_done)
write_data_done += conn->sess->sess_ops->MaxBurstLength;
else
break;
}
cmd->write_data_done = write_data_done;
}
static int iscsit_attach_active_connection_recovery_entry(
struct iscsi_session *sess,
struct iscsi_conn_recovery *cr)
{
spin_lock(&sess->cr_a_lock);
list_add_tail(&cr->cr_list, &sess->cr_active_list);
spin_unlock(&sess->cr_a_lock);
return 0;
}
static int iscsit_attach_inactive_connection_recovery_entry(
struct iscsi_session *sess,
struct iscsi_conn_recovery *cr)
{
spin_lock(&sess->cr_i_lock);
list_add_tail(&cr->cr_list, &sess->cr_inactive_list);
sess->conn_recovery_count++;
pr_debug("Incremented connection recovery count to %u for"
" SID: %u\n", sess->conn_recovery_count, sess->sid);
spin_unlock(&sess->cr_i_lock);
return 0;
}
struct iscsi_conn_recovery *iscsit_get_inactive_connection_recovery_entry(
struct iscsi_session *sess,
u16 cid)
{
struct iscsi_conn_recovery *cr;
spin_lock(&sess->cr_i_lock);
list_for_each_entry(cr, &sess->cr_inactive_list, cr_list) {
if (cr->cid == cid) {
spin_unlock(&sess->cr_i_lock);
return cr;
}
}
spin_unlock(&sess->cr_i_lock);
return NULL;
}
void iscsit_free_connection_recovery_entires(struct iscsi_session *sess)
{
struct iscsi_cmd *cmd, *cmd_tmp;
struct iscsi_conn_recovery *cr, *cr_tmp;
spin_lock(&sess->cr_a_lock);
list_for_each_entry_safe(cr, cr_tmp, &sess->cr_active_list, cr_list) {
list_del(&cr->cr_list);
spin_unlock(&sess->cr_a_lock);
spin_lock(&cr->conn_recovery_cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp,
&cr->conn_recovery_cmd_list, i_list) {
list_del(&cmd->i_list);
cmd->conn = NULL;
spin_unlock(&cr->conn_recovery_cmd_lock);
if (!(cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) ||
!(cmd->se_cmd.transport_wait_for_tasks))
iscsit_release_cmd(cmd);
else
cmd->se_cmd.transport_wait_for_tasks(
&cmd->se_cmd, 1, 1);
spin_lock(&cr->conn_recovery_cmd_lock);
}
spin_unlock(&cr->conn_recovery_cmd_lock);
spin_lock(&sess->cr_a_lock);
kfree(cr);
}
spin_unlock(&sess->cr_a_lock);
spin_lock(&sess->cr_i_lock);
list_for_each_entry_safe(cr, cr_tmp, &sess->cr_inactive_list, cr_list) {
list_del(&cr->cr_list);
spin_unlock(&sess->cr_i_lock);
spin_lock(&cr->conn_recovery_cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp,
&cr->conn_recovery_cmd_list, i_list) {
list_del(&cmd->i_list);
cmd->conn = NULL;
spin_unlock(&cr->conn_recovery_cmd_lock);
if (!(cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) ||
!(cmd->se_cmd.transport_wait_for_tasks))
iscsit_release_cmd(cmd);
else
cmd->se_cmd.transport_wait_for_tasks(
&cmd->se_cmd, 1, 1);
spin_lock(&cr->conn_recovery_cmd_lock);
}
spin_unlock(&cr->conn_recovery_cmd_lock);
spin_lock(&sess->cr_i_lock);
kfree(cr);
}
spin_unlock(&sess->cr_i_lock);
}
int iscsit_remove_active_connection_recovery_entry(
struct iscsi_conn_recovery *cr,
struct iscsi_session *sess)
{
spin_lock(&sess->cr_a_lock);
list_del(&cr->cr_list);
sess->conn_recovery_count--;
pr_debug("Decremented connection recovery count to %u for"
" SID: %u\n", sess->conn_recovery_count, sess->sid);
spin_unlock(&sess->cr_a_lock);
kfree(cr);
return 0;
}
int iscsit_remove_inactive_connection_recovery_entry(
struct iscsi_conn_recovery *cr,
struct iscsi_session *sess)
{
spin_lock(&sess->cr_i_lock);
list_del(&cr->cr_list);
spin_unlock(&sess->cr_i_lock);
return 0;
}
/*
* Called with cr->conn_recovery_cmd_lock help.
*/
int iscsit_remove_cmd_from_connection_recovery(
struct iscsi_cmd *cmd,
struct iscsi_session *sess)
{
struct iscsi_conn_recovery *cr;
if (!cmd->cr) {
pr_err("struct iscsi_conn_recovery pointer for ITT: 0x%08x"
" is NULL!\n", cmd->init_task_tag);
BUG();
}
cr = cmd->cr;
list_del(&cmd->i_list);
return --cr->cmd_count;
}
void iscsit_discard_cr_cmds_by_expstatsn(
struct iscsi_conn_recovery *cr,
u32 exp_statsn)
{
u32 dropped_count = 0;
struct iscsi_cmd *cmd, *cmd_tmp;
struct iscsi_session *sess = cr->sess;
spin_lock(&cr->conn_recovery_cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp,
&cr->conn_recovery_cmd_list, i_list) {
if (((cmd->deferred_i_state != ISTATE_SENT_STATUS) &&
(cmd->deferred_i_state != ISTATE_REMOVE)) ||
(cmd->stat_sn >= exp_statsn)) {
continue;
}
dropped_count++;
pr_debug("Dropping Acknowledged ITT: 0x%08x, StatSN:"
" 0x%08x, CID: %hu.\n", cmd->init_task_tag,
cmd->stat_sn, cr->cid);
iscsit_remove_cmd_from_connection_recovery(cmd, sess);
spin_unlock(&cr->conn_recovery_cmd_lock);
if (!(cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) ||
!(cmd->se_cmd.transport_wait_for_tasks))
iscsit_release_cmd(cmd);
else
cmd->se_cmd.transport_wait_for_tasks(
&cmd->se_cmd, 1, 0);
spin_lock(&cr->conn_recovery_cmd_lock);
}
spin_unlock(&cr->conn_recovery_cmd_lock);
pr_debug("Dropped %u total acknowledged commands on"
" CID: %hu less than old ExpStatSN: 0x%08x\n",
dropped_count, cr->cid, exp_statsn);
if (!cr->cmd_count) {
pr_debug("No commands to be reassigned for failed"
" connection CID: %hu on SID: %u\n",
cr->cid, sess->sid);
iscsit_remove_inactive_connection_recovery_entry(cr, sess);
iscsit_attach_active_connection_recovery_entry(sess, cr);
pr_debug("iSCSI connection recovery successful for CID:"
" %hu on SID: %u\n", cr->cid, sess->sid);
iscsit_remove_active_connection_recovery_entry(cr, sess);
} else {
iscsit_remove_inactive_connection_recovery_entry(cr, sess);
iscsit_attach_active_connection_recovery_entry(sess, cr);
}
}
int iscsit_discard_unacknowledged_ooo_cmdsns_for_conn(struct iscsi_conn *conn)
{
u32 dropped_count = 0;
struct iscsi_cmd *cmd, *cmd_tmp;
struct iscsi_ooo_cmdsn *ooo_cmdsn, *ooo_cmdsn_tmp;
struct iscsi_session *sess = conn->sess;
mutex_lock(&sess->cmdsn_mutex);
list_for_each_entry_safe(ooo_cmdsn, ooo_cmdsn_tmp,
&sess->sess_ooo_cmdsn_list, ooo_list) {
if (ooo_cmdsn->cid != conn->cid)
continue;
dropped_count++;
pr_debug("Dropping unacknowledged CmdSN:"
" 0x%08x during connection recovery on CID: %hu\n",
ooo_cmdsn->cmdsn, conn->cid);
iscsit_remove_ooo_cmdsn(sess, ooo_cmdsn);
}
mutex_unlock(&sess->cmdsn_mutex);
spin_lock_bh(&conn->cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp, &conn->conn_cmd_list, i_list) {
if (!(cmd->cmd_flags & ICF_OOO_CMDSN))
continue;
list_del(&cmd->i_list);
spin_unlock_bh(&conn->cmd_lock);
if (!(cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) ||
!(cmd->se_cmd.transport_wait_for_tasks))
iscsit_release_cmd(cmd);
else
cmd->se_cmd.transport_wait_for_tasks(
&cmd->se_cmd, 1, 1);
spin_lock_bh(&conn->cmd_lock);
}
spin_unlock_bh(&conn->cmd_lock);
pr_debug("Dropped %u total unacknowledged commands on CID:"
" %hu for ExpCmdSN: 0x%08x.\n", dropped_count, conn->cid,
sess->exp_cmd_sn);
return 0;
}
int iscsit_prepare_cmds_for_realligance(struct iscsi_conn *conn)
{
u32 cmd_count = 0;
struct iscsi_cmd *cmd, *cmd_tmp;
struct iscsi_conn_recovery *cr;
/*
* Allocate an struct iscsi_conn_recovery for this connection.
* Each struct iscsi_cmd contains an struct iscsi_conn_recovery pointer
* (struct iscsi_cmd->cr) so we need to allocate this before preparing the
* connection's command list for connection recovery.
*/
cr = kzalloc(sizeof(struct iscsi_conn_recovery), GFP_KERNEL);
if (!cr) {
pr_err("Unable to allocate memory for"
" struct iscsi_conn_recovery.\n");
return -1;
}
INIT_LIST_HEAD(&cr->cr_list);
INIT_LIST_HEAD(&cr->conn_recovery_cmd_list);
spin_lock_init(&cr->conn_recovery_cmd_lock);
/*
* Only perform connection recovery on ISCSI_OP_SCSI_CMD or
* ISCSI_OP_NOOP_OUT opcodes. For all other opcodes call
* list_del(&cmd->i_list); to release the command to the
* session pool and remove it from the connection's list.
*
* Also stop the DataOUT timer, which will be restarted after
* sending the TMR response.
*/
spin_lock_bh(&conn->cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp, &conn->conn_cmd_list, i_list) {
if ((cmd->iscsi_opcode != ISCSI_OP_SCSI_CMD) &&
(cmd->iscsi_opcode != ISCSI_OP_NOOP_OUT)) {
pr_debug("Not performing realligence on"
" Opcode: 0x%02x, ITT: 0x%08x, CmdSN: 0x%08x,"
" CID: %hu\n", cmd->iscsi_opcode,
cmd->init_task_tag, cmd->cmd_sn, conn->cid);
list_del(&cmd->i_list);
spin_unlock_bh(&conn->cmd_lock);
if (!(cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) ||
!(cmd->se_cmd.transport_wait_for_tasks))
iscsit_release_cmd(cmd);
else
cmd->se_cmd.transport_wait_for_tasks(
&cmd->se_cmd, 1, 0);
spin_lock_bh(&conn->cmd_lock);
continue;
}
/*
* Special case where commands greater than or equal to
* the session's ExpCmdSN are attached to the connection
* list but not to the out of order CmdSN list. The one
* obvious case is when a command with immediate data
* attached must only check the CmdSN against ExpCmdSN
* after the data is received. The special case below
* is when the connection fails before data is received,
* but also may apply to other PDUs, so it has been
* made generic here.
*/
if (!(cmd->cmd_flags & ICF_OOO_CMDSN) && !cmd->immediate_cmd &&
(cmd->cmd_sn >= conn->sess->exp_cmd_sn)) {
list_del(&cmd->i_list);
spin_unlock_bh(&conn->cmd_lock);
if (!(cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) ||
!(cmd->se_cmd.transport_wait_for_tasks))
iscsit_release_cmd(cmd);
else
cmd->se_cmd.transport_wait_for_tasks(
&cmd->se_cmd, 1, 1);
spin_lock_bh(&conn->cmd_lock);
continue;
}
cmd_count++;
pr_debug("Preparing Opcode: 0x%02x, ITT: 0x%08x,"
" CmdSN: 0x%08x, StatSN: 0x%08x, CID: %hu for"
" realligence.\n", cmd->iscsi_opcode,
cmd->init_task_tag, cmd->cmd_sn, cmd->stat_sn,
conn->cid);
cmd->deferred_i_state = cmd->i_state;
cmd->i_state = ISTATE_IN_CONNECTION_RECOVERY;
if (cmd->data_direction == DMA_TO_DEVICE)
iscsit_stop_dataout_timer(cmd);
cmd->sess = conn->sess;
list_del(&cmd->i_list);
spin_unlock_bh(&conn->cmd_lock);
iscsit_free_all_datain_reqs(cmd);
if ((cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) &&
cmd->se_cmd.transport_wait_for_tasks)
cmd->se_cmd.transport_wait_for_tasks(&cmd->se_cmd,
0, 0);
/*
* Add the struct iscsi_cmd to the connection recovery cmd list
*/
spin_lock(&cr->conn_recovery_cmd_lock);
list_add_tail(&cmd->i_list, &cr->conn_recovery_cmd_list);
spin_unlock(&cr->conn_recovery_cmd_lock);
spin_lock_bh(&conn->cmd_lock);
cmd->cr = cr;
cmd->conn = NULL;
}
spin_unlock_bh(&conn->cmd_lock);
/*
* Fill in the various values in the preallocated struct iscsi_conn_recovery.
*/
cr->cid = conn->cid;
cr->cmd_count = cmd_count;
cr->maxrecvdatasegmentlength = conn->conn_ops->MaxRecvDataSegmentLength;
cr->sess = conn->sess;
iscsit_attach_inactive_connection_recovery_entry(conn->sess, cr);
return 0;
}
int iscsit_connection_recovery_transport_reset(struct iscsi_conn *conn)
{
atomic_set(&conn->connection_recovery, 1);
if (iscsit_close_connection(conn) < 0)
return -1;
return 0;
}
#ifndef ISCSI_TARGET_ERL2_H
#define ISCSI_TARGET_ERL2_H
extern void iscsit_create_conn_recovery_datain_values(struct iscsi_cmd *, u32);
extern void iscsit_create_conn_recovery_dataout_values(struct iscsi_cmd *);
extern struct iscsi_conn_recovery *iscsit_get_inactive_connection_recovery_entry(
struct iscsi_session *, u16);
extern void iscsit_free_connection_recovery_entires(struct iscsi_session *);
extern int iscsit_remove_active_connection_recovery_entry(
struct iscsi_conn_recovery *, struct iscsi_session *);
extern int iscsit_remove_cmd_from_connection_recovery(struct iscsi_cmd *,
struct iscsi_session *);
extern void iscsit_discard_cr_cmds_by_expstatsn(struct iscsi_conn_recovery *, u32);
extern int iscsit_discard_unacknowledged_ooo_cmdsns_for_conn(struct iscsi_conn *);
extern int iscsit_prepare_cmds_for_realligance(struct iscsi_conn *);
extern int iscsit_connection_recovery_transport_reset(struct iscsi_conn *);
#endif /*** ISCSI_TARGET_ERL2_H ***/
/*******************************************************************************
* This file contains the login functions used by the iSCSI Target driver.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/string.h>
#include <linux/kthread.h>
#include <linux/crypto.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include "iscsi_target_core.h"
#include "iscsi_target_tq.h"
#include "iscsi_target_device.h"
#include "iscsi_target_nego.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target_login.h"
#include "iscsi_target_stat.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
#include "iscsi_target_parameters.h"
extern struct idr sess_idr;
extern struct mutex auth_id_lock;
extern spinlock_t sess_idr_lock;
static int iscsi_login_init_conn(struct iscsi_conn *conn)
{
INIT_LIST_HEAD(&conn->conn_list);
INIT_LIST_HEAD(&conn->conn_cmd_list);
INIT_LIST_HEAD(&conn->immed_queue_list);
INIT_LIST_HEAD(&conn->response_queue_list);
init_completion(&conn->conn_post_wait_comp);
init_completion(&conn->conn_wait_comp);
init_completion(&conn->conn_wait_rcfr_comp);
init_completion(&conn->conn_waiting_on_uc_comp);
init_completion(&conn->conn_logout_comp);
init_completion(&conn->rx_half_close_comp);
init_completion(&conn->tx_half_close_comp);
spin_lock_init(&conn->cmd_lock);
spin_lock_init(&conn->conn_usage_lock);
spin_lock_init(&conn->immed_queue_lock);
spin_lock_init(&conn->nopin_timer_lock);
spin_lock_init(&conn->response_queue_lock);
spin_lock_init(&conn->state_lock);
if (!zalloc_cpumask_var(&conn->conn_cpumask, GFP_KERNEL)) {
pr_err("Unable to allocate conn->conn_cpumask\n");
return -ENOMEM;
}
return 0;
}
/*
* Used by iscsi_target_nego.c:iscsi_target_locate_portal() to setup
* per struct iscsi_conn libcrypto contexts for crc32c and crc32-intel
*/
int iscsi_login_setup_crypto(struct iscsi_conn *conn)
{
/*
* Setup slicing by CRC32C algorithm for RX and TX libcrypto contexts
* which will default to crc32c_intel.ko for cpu_has_xmm4_2, or fallback
* to software 1x8 byte slicing from crc32c.ko
*/
conn->conn_rx_hash.flags = 0;
conn->conn_rx_hash.tfm = crypto_alloc_hash("crc32c", 0,
CRYPTO_ALG_ASYNC);
if (IS_ERR(conn->conn_rx_hash.tfm)) {
pr_err("crypto_alloc_hash() failed for conn_rx_tfm\n");
return -ENOMEM;
}
conn->conn_tx_hash.flags = 0;
conn->conn_tx_hash.tfm = crypto_alloc_hash("crc32c", 0,
CRYPTO_ALG_ASYNC);
if (IS_ERR(conn->conn_tx_hash.tfm)) {
pr_err("crypto_alloc_hash() failed for conn_tx_tfm\n");
crypto_free_hash(conn->conn_rx_hash.tfm);
return -ENOMEM;
}
return 0;
}
static int iscsi_login_check_initiator_version(
struct iscsi_conn *conn,
u8 version_max,
u8 version_min)
{
if ((version_max != 0x00) || (version_min != 0x00)) {
pr_err("Unsupported iSCSI IETF Pre-RFC Revision,"
" version Min/Max 0x%02x/0x%02x, rejecting login.\n",
version_min, version_max);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_NO_VERSION);
return -1;
}
return 0;
}
int iscsi_check_for_session_reinstatement(struct iscsi_conn *conn)
{
int sessiontype;
struct iscsi_param *initiatorname_param = NULL, *sessiontype_param = NULL;
struct iscsi_portal_group *tpg = conn->tpg;
struct iscsi_session *sess = NULL, *sess_p = NULL;
struct se_portal_group *se_tpg = &tpg->tpg_se_tpg;
struct se_session *se_sess, *se_sess_tmp;
initiatorname_param = iscsi_find_param_from_key(
INITIATORNAME, conn->param_list);
if (!initiatorname_param)
return -1;
sessiontype_param = iscsi_find_param_from_key(
SESSIONTYPE, conn->param_list);
if (!sessiontype_param)
return -1;
sessiontype = (strncmp(sessiontype_param->value, NORMAL, 6)) ? 1 : 0;
spin_lock_bh(&se_tpg->session_lock);
list_for_each_entry_safe(se_sess, se_sess_tmp, &se_tpg->tpg_sess_list,
sess_list) {
sess_p = (struct iscsi_session *)se_sess->fabric_sess_ptr;
spin_lock(&sess_p->conn_lock);
if (atomic_read(&sess_p->session_fall_back_to_erl0) ||
atomic_read(&sess_p->session_logout) ||
(sess_p->time2retain_timer_flags & ISCSI_TF_EXPIRED)) {
spin_unlock(&sess_p->conn_lock);
continue;
}
if (!memcmp((void *)sess_p->isid, (void *)conn->sess->isid, 6) &&
(!strcmp((void *)sess_p->sess_ops->InitiatorName,
(void *)initiatorname_param->value) &&
(sess_p->sess_ops->SessionType == sessiontype))) {
atomic_set(&sess_p->session_reinstatement, 1);
spin_unlock(&sess_p->conn_lock);
iscsit_inc_session_usage_count(sess_p);
iscsit_stop_time2retain_timer(sess_p);
sess = sess_p;
break;
}
spin_unlock(&sess_p->conn_lock);
}
spin_unlock_bh(&se_tpg->session_lock);
/*
* If the Time2Retain handler has expired, the session is already gone.
*/
if (!sess)
return 0;
pr_debug("%s iSCSI Session SID %u is still active for %s,"
" preforming session reinstatement.\n", (sessiontype) ?
"Discovery" : "Normal", sess->sid,
sess->sess_ops->InitiatorName);
spin_lock_bh(&sess->conn_lock);
if (sess->session_state == TARG_SESS_STATE_FAILED) {
spin_unlock_bh(&sess->conn_lock);
iscsit_dec_session_usage_count(sess);
return iscsit_close_session(sess);
}
spin_unlock_bh(&sess->conn_lock);
iscsit_stop_session(sess, 1, 1);
iscsit_dec_session_usage_count(sess);
return iscsit_close_session(sess);
}
static void iscsi_login_set_conn_values(
struct iscsi_session *sess,
struct iscsi_conn *conn,
u16 cid)
{
conn->sess = sess;
conn->cid = cid;
/*
* Generate a random Status sequence number (statsn) for the new
* iSCSI connection.
*/
get_random_bytes(&conn->stat_sn, sizeof(u32));
mutex_lock(&auth_id_lock);
conn->auth_id = iscsit_global->auth_id++;
mutex_unlock(&auth_id_lock);
}
/*
* This is the leading connection of a new session,
* or session reinstatement.
*/
static int iscsi_login_zero_tsih_s1(
struct iscsi_conn *conn,
unsigned char *buf)
{
struct iscsi_session *sess = NULL;
struct iscsi_login_req *pdu = (struct iscsi_login_req *)buf;
sess = kzalloc(sizeof(struct iscsi_session), GFP_KERNEL);
if (!sess) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
pr_err("Could not allocate memory for session\n");
return -1;
}
iscsi_login_set_conn_values(sess, conn, pdu->cid);
sess->init_task_tag = pdu->itt;
memcpy((void *)&sess->isid, (void *)pdu->isid, 6);
sess->exp_cmd_sn = pdu->cmdsn;
INIT_LIST_HEAD(&sess->sess_conn_list);
INIT_LIST_HEAD(&sess->sess_ooo_cmdsn_list);
INIT_LIST_HEAD(&sess->cr_active_list);
INIT_LIST_HEAD(&sess->cr_inactive_list);
init_completion(&sess->async_msg_comp);
init_completion(&sess->reinstatement_comp);
init_completion(&sess->session_wait_comp);
init_completion(&sess->session_waiting_on_uc_comp);
mutex_init(&sess->cmdsn_mutex);
spin_lock_init(&sess->conn_lock);
spin_lock_init(&sess->cr_a_lock);
spin_lock_init(&sess->cr_i_lock);
spin_lock_init(&sess->session_usage_lock);
spin_lock_init(&sess->ttt_lock);
if (!idr_pre_get(&sess_idr, GFP_KERNEL)) {
pr_err("idr_pre_get() for sess_idr failed\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
spin_lock(&sess_idr_lock);
idr_get_new(&sess_idr, NULL, &sess->session_index);
spin_unlock(&sess_idr_lock);
sess->creation_time = get_jiffies_64();
spin_lock_init(&sess->session_stats_lock);
/*
* The FFP CmdSN window values will be allocated from the TPG's
* Initiator Node's ACL once the login has been successfully completed.
*/
sess->max_cmd_sn = pdu->cmdsn;
sess->sess_ops = kzalloc(sizeof(struct iscsi_sess_ops), GFP_KERNEL);
if (!sess->sess_ops) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
pr_err("Unable to allocate memory for"
" struct iscsi_sess_ops.\n");
return -1;
}
sess->se_sess = transport_init_session();
if (!sess->se_sess) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
return 0;
}
static int iscsi_login_zero_tsih_s2(
struct iscsi_conn *conn)
{
struct iscsi_node_attrib *na;
struct iscsi_session *sess = conn->sess;
unsigned char buf[32];
sess->tpg = conn->tpg;
/*
* Assign a new TPG Session Handle. Note this is protected with
* struct iscsi_portal_group->np_login_sem from iscsit_access_np().
*/
sess->tsih = ++ISCSI_TPG_S(sess)->ntsih;
if (!sess->tsih)
sess->tsih = ++ISCSI_TPG_S(sess)->ntsih;
/*
* Create the default params from user defined values..
*/
if (iscsi_copy_param_list(&conn->param_list,
ISCSI_TPG_C(conn)->param_list, 1) < 0) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
iscsi_set_keys_to_negotiate(0, conn->param_list);
if (sess->sess_ops->SessionType)
return iscsi_set_keys_irrelevant_for_discovery(
conn->param_list);
na = iscsit_tpg_get_node_attrib(sess);
/*
* Need to send TargetPortalGroupTag back in first login response
* on any iSCSI connection where the Initiator provides TargetName.
* See 5.3.1. Login Phase Start
*
* In our case, we have already located the struct iscsi_tiqn at this point.
*/
memset(buf, 0, 32);
sprintf(buf, "TargetPortalGroupTag=%hu", ISCSI_TPG_S(sess)->tpgt);
if (iscsi_change_param_value(buf, conn->param_list, 0) < 0) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
/*
* Workaround for Initiators that have broken connection recovery logic.
*
* "We would really like to get rid of this." Linux-iSCSI.org team
*/
memset(buf, 0, 32);
sprintf(buf, "ErrorRecoveryLevel=%d", na->default_erl);
if (iscsi_change_param_value(buf, conn->param_list, 0) < 0) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
if (iscsi_login_disable_FIM_keys(conn->param_list, conn) < 0)
return -1;
return 0;
}
/*
* Remove PSTATE_NEGOTIATE for the four FIM related keys.
* The Initiator node will be able to enable FIM by proposing them itself.
*/
int iscsi_login_disable_FIM_keys(
struct iscsi_param_list *param_list,
struct iscsi_conn *conn)
{
struct iscsi_param *param;
param = iscsi_find_param_from_key("OFMarker", param_list);
if (!param) {
pr_err("iscsi_find_param_from_key() for"
" OFMarker failed\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
param->state &= ~PSTATE_NEGOTIATE;
param = iscsi_find_param_from_key("OFMarkInt", param_list);
if (!param) {
pr_err("iscsi_find_param_from_key() for"
" IFMarker failed\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
param->state &= ~PSTATE_NEGOTIATE;
param = iscsi_find_param_from_key("IFMarker", param_list);
if (!param) {
pr_err("iscsi_find_param_from_key() for"
" IFMarker failed\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
param->state &= ~PSTATE_NEGOTIATE;
param = iscsi_find_param_from_key("IFMarkInt", param_list);
if (!param) {
pr_err("iscsi_find_param_from_key() for"
" IFMarker failed\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
param->state &= ~PSTATE_NEGOTIATE;
return 0;
}
static int iscsi_login_non_zero_tsih_s1(
struct iscsi_conn *conn,
unsigned char *buf)
{
struct iscsi_login_req *pdu = (struct iscsi_login_req *)buf;
iscsi_login_set_conn_values(NULL, conn, pdu->cid);
return 0;
}
/*
* Add a new connection to an existing session.
*/
static int iscsi_login_non_zero_tsih_s2(
struct iscsi_conn *conn,
unsigned char *buf)
{
struct iscsi_portal_group *tpg = conn->tpg;
struct iscsi_session *sess = NULL, *sess_p = NULL;
struct se_portal_group *se_tpg = &tpg->tpg_se_tpg;
struct se_session *se_sess, *se_sess_tmp;
struct iscsi_login_req *pdu = (struct iscsi_login_req *)buf;
spin_lock_bh(&se_tpg->session_lock);
list_for_each_entry_safe(se_sess, se_sess_tmp, &se_tpg->tpg_sess_list,
sess_list) {
sess_p = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (atomic_read(&sess_p->session_fall_back_to_erl0) ||
atomic_read(&sess_p->session_logout) ||
(sess_p->time2retain_timer_flags & ISCSI_TF_EXPIRED))
continue;
if (!memcmp((const void *)sess_p->isid,
(const void *)pdu->isid, 6) &&
(sess_p->tsih == pdu->tsih)) {
iscsit_inc_session_usage_count(sess_p);
iscsit_stop_time2retain_timer(sess_p);
sess = sess_p;
break;
}
}
spin_unlock_bh(&se_tpg->session_lock);
/*
* If the Time2Retain handler has expired, the session is already gone.
*/
if (!sess) {
pr_err("Initiator attempting to add a connection to"
" a non-existent session, rejecting iSCSI Login.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_NO_SESSION);
return -1;
}
/*
* Stop the Time2Retain timer if this is a failed session, we restart
* the timer if the login is not successful.
*/
spin_lock_bh(&sess->conn_lock);
if (sess->session_state == TARG_SESS_STATE_FAILED)
atomic_set(&sess->session_continuation, 1);
spin_unlock_bh(&sess->conn_lock);
iscsi_login_set_conn_values(sess, conn, pdu->cid);
if (iscsi_copy_param_list(&conn->param_list,
ISCSI_TPG_C(conn)->param_list, 0) < 0) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
iscsi_set_keys_to_negotiate(0, conn->param_list);
/*
* Need to send TargetPortalGroupTag back in first login response
* on any iSCSI connection where the Initiator provides TargetName.
* See 5.3.1. Login Phase Start
*
* In our case, we have already located the struct iscsi_tiqn at this point.
*/
memset(buf, 0, 32);
sprintf(buf, "TargetPortalGroupTag=%hu", ISCSI_TPG_S(sess)->tpgt);
if (iscsi_change_param_value(buf, conn->param_list, 0) < 0) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
return -1;
}
return iscsi_login_disable_FIM_keys(conn->param_list, conn);
}
int iscsi_login_post_auth_non_zero_tsih(
struct iscsi_conn *conn,
u16 cid,
u32 exp_statsn)
{
struct iscsi_conn *conn_ptr = NULL;
struct iscsi_conn_recovery *cr = NULL;
struct iscsi_session *sess = conn->sess;
/*
* By following item 5 in the login table, if we have found
* an existing ISID and a valid/existing TSIH and an existing
* CID we do connection reinstatement. Currently we dont not
* support it so we send back an non-zero status class to the
* initiator and release the new connection.
*/
conn_ptr = iscsit_get_conn_from_cid_rcfr(sess, cid);
if ((conn_ptr)) {
pr_err("Connection exists with CID %hu for %s,"
" performing connection reinstatement.\n",
conn_ptr->cid, sess->sess_ops->InitiatorName);
iscsit_connection_reinstatement_rcfr(conn_ptr);
iscsit_dec_conn_usage_count(conn_ptr);
}
/*
* Check for any connection recovery entires containing CID.
* We use the original ExpStatSN sent in the first login request
* to acknowledge commands for the failed connection.
*
* Also note that an explict logout may have already been sent,
* but the response may not be sent due to additional connection
* loss.
*/
if (sess->sess_ops->ErrorRecoveryLevel == 2) {
cr = iscsit_get_inactive_connection_recovery_entry(
sess, cid);
if ((cr)) {
pr_debug("Performing implicit logout"
" for connection recovery on CID: %hu\n",
conn->cid);
iscsit_discard_cr_cmds_by_expstatsn(cr, exp_statsn);
}
}
/*
* Else we follow item 4 from the login table in that we have
* found an existing ISID and a valid/existing TSIH and a new
* CID we go ahead and continue to add a new connection to the
* session.
*/
pr_debug("Adding CID %hu to existing session for %s.\n",
cid, sess->sess_ops->InitiatorName);
if ((atomic_read(&sess->nconn) + 1) > sess->sess_ops->MaxConnections) {
pr_err("Adding additional connection to this session"
" would exceed MaxConnections %d, login failed.\n",
sess->sess_ops->MaxConnections);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_ISID_ERROR);
return -1;
}
return 0;
}
static void iscsi_post_login_start_timers(struct iscsi_conn *conn)
{
struct iscsi_session *sess = conn->sess;
if (!sess->sess_ops->SessionType)
iscsit_start_nopin_timer(conn);
}
static int iscsi_post_login_handler(
struct iscsi_np *np,
struct iscsi_conn *conn,
u8 zero_tsih)
{
int stop_timer = 0;
struct iscsi_session *sess = conn->sess;
struct se_session *se_sess = sess->se_sess;
struct iscsi_portal_group *tpg = ISCSI_TPG_S(sess);
struct se_portal_group *se_tpg = &tpg->tpg_se_tpg;
struct iscsi_thread_set *ts;
iscsit_inc_conn_usage_count(conn);
iscsit_collect_login_stats(conn, ISCSI_STATUS_CLS_SUCCESS,
ISCSI_LOGIN_STATUS_ACCEPT);
pr_debug("Moving to TARG_CONN_STATE_LOGGED_IN.\n");
conn->conn_state = TARG_CONN_STATE_LOGGED_IN;
iscsi_set_connection_parameters(conn->conn_ops, conn->param_list);
iscsit_set_sync_and_steering_values(conn);
/*
* SCSI Initiator -> SCSI Target Port Mapping
*/
ts = iscsi_get_thread_set();
if (!zero_tsih) {
iscsi_set_session_parameters(sess->sess_ops,
conn->param_list, 0);
iscsi_release_param_list(conn->param_list);
conn->param_list = NULL;
spin_lock_bh(&sess->conn_lock);
atomic_set(&sess->session_continuation, 0);
if (sess->session_state == TARG_SESS_STATE_FAILED) {
pr_debug("Moving to"
" TARG_SESS_STATE_LOGGED_IN.\n");
sess->session_state = TARG_SESS_STATE_LOGGED_IN;
stop_timer = 1;
}
pr_debug("iSCSI Login successful on CID: %hu from %s to"
" %s:%hu,%hu\n", conn->cid, conn->login_ip, np->np_ip,
np->np_port, tpg->tpgt);
list_add_tail(&conn->conn_list, &sess->sess_conn_list);
atomic_inc(&sess->nconn);
pr_debug("Incremented iSCSI Connection count to %hu"
" from node: %s\n", atomic_read(&sess->nconn),
sess->sess_ops->InitiatorName);
spin_unlock_bh(&sess->conn_lock);
iscsi_post_login_start_timers(conn);
iscsi_activate_thread_set(conn, ts);
/*
* Determine CPU mask to ensure connection's RX and TX kthreads
* are scheduled on the same CPU.
*/
iscsit_thread_get_cpumask(conn);
conn->conn_rx_reset_cpumask = 1;
conn->conn_tx_reset_cpumask = 1;
iscsit_dec_conn_usage_count(conn);
if (stop_timer) {
spin_lock_bh(&se_tpg->session_lock);
iscsit_stop_time2retain_timer(sess);
spin_unlock_bh(&se_tpg->session_lock);
}
iscsit_dec_session_usage_count(sess);
return 0;
}
iscsi_set_session_parameters(sess->sess_ops, conn->param_list, 1);
iscsi_release_param_list(conn->param_list);
conn->param_list = NULL;
iscsit_determine_maxcmdsn(sess);
spin_lock_bh(&se_tpg->session_lock);
__transport_register_session(&sess->tpg->tpg_se_tpg,
se_sess->se_node_acl, se_sess, (void *)sess);
pr_debug("Moving to TARG_SESS_STATE_LOGGED_IN.\n");
sess->session_state = TARG_SESS_STATE_LOGGED_IN;
pr_debug("iSCSI Login successful on CID: %hu from %s to %s:%hu,%hu\n",
conn->cid, conn->login_ip, np->np_ip, np->np_port, tpg->tpgt);
spin_lock_bh(&sess->conn_lock);
list_add_tail(&conn->conn_list, &sess->sess_conn_list);
atomic_inc(&sess->nconn);
pr_debug("Incremented iSCSI Connection count to %hu from node:"
" %s\n", atomic_read(&sess->nconn),
sess->sess_ops->InitiatorName);
spin_unlock_bh(&sess->conn_lock);
sess->sid = tpg->sid++;
if (!sess->sid)
sess->sid = tpg->sid++;
pr_debug("Established iSCSI session from node: %s\n",
sess->sess_ops->InitiatorName);
tpg->nsessions++;
if (tpg->tpg_tiqn)
tpg->tpg_tiqn->tiqn_nsessions++;
pr_debug("Incremented number of active iSCSI sessions to %u on"
" iSCSI Target Portal Group: %hu\n", tpg->nsessions, tpg->tpgt);
spin_unlock_bh(&se_tpg->session_lock);
iscsi_post_login_start_timers(conn);
iscsi_activate_thread_set(conn, ts);
/*
* Determine CPU mask to ensure connection's RX and TX kthreads
* are scheduled on the same CPU.
*/
iscsit_thread_get_cpumask(conn);
conn->conn_rx_reset_cpumask = 1;
conn->conn_tx_reset_cpumask = 1;
iscsit_dec_conn_usage_count(conn);
return 0;
}
static void iscsi_handle_login_thread_timeout(unsigned long data)
{
struct iscsi_np *np = (struct iscsi_np *) data;
spin_lock_bh(&np->np_thread_lock);
pr_err("iSCSI Login timeout on Network Portal %s:%hu\n",
np->np_ip, np->np_port);
if (np->np_login_timer_flags & ISCSI_TF_STOP) {
spin_unlock_bh(&np->np_thread_lock);
return;
}
if (np->np_thread)
send_sig(SIGINT, np->np_thread, 1);
np->np_login_timer_flags &= ~ISCSI_TF_RUNNING;
spin_unlock_bh(&np->np_thread_lock);
}
static void iscsi_start_login_thread_timer(struct iscsi_np *np)
{
/*
* This used the TA_LOGIN_TIMEOUT constant because at this
* point we do not have access to ISCSI_TPG_ATTRIB(tpg)->login_timeout
*/
spin_lock_bh(&np->np_thread_lock);
init_timer(&np->np_login_timer);
np->np_login_timer.expires = (get_jiffies_64() + TA_LOGIN_TIMEOUT * HZ);
np->np_login_timer.data = (unsigned long)np;
np->np_login_timer.function = iscsi_handle_login_thread_timeout;
np->np_login_timer_flags &= ~ISCSI_TF_STOP;
np->np_login_timer_flags |= ISCSI_TF_RUNNING;
add_timer(&np->np_login_timer);
pr_debug("Added timeout timer to iSCSI login request for"
" %u seconds.\n", TA_LOGIN_TIMEOUT);
spin_unlock_bh(&np->np_thread_lock);
}
static void iscsi_stop_login_thread_timer(struct iscsi_np *np)
{
spin_lock_bh(&np->np_thread_lock);
if (!(np->np_login_timer_flags & ISCSI_TF_RUNNING)) {
spin_unlock_bh(&np->np_thread_lock);
return;
}
np->np_login_timer_flags |= ISCSI_TF_STOP;
spin_unlock_bh(&np->np_thread_lock);
del_timer_sync(&np->np_login_timer);
spin_lock_bh(&np->np_thread_lock);
np->np_login_timer_flags &= ~ISCSI_TF_RUNNING;
spin_unlock_bh(&np->np_thread_lock);
}
int iscsi_target_setup_login_socket(
struct iscsi_np *np,
struct __kernel_sockaddr_storage *sockaddr)
{
struct socket *sock;
int backlog = 5, ret, opt = 0, len;
switch (np->np_network_transport) {
case ISCSI_TCP:
np->np_ip_proto = IPPROTO_TCP;
np->np_sock_type = SOCK_STREAM;
break;
case ISCSI_SCTP_TCP:
np->np_ip_proto = IPPROTO_SCTP;
np->np_sock_type = SOCK_STREAM;
break;
case ISCSI_SCTP_UDP:
np->np_ip_proto = IPPROTO_SCTP;
np->np_sock_type = SOCK_SEQPACKET;
break;
case ISCSI_IWARP_TCP:
case ISCSI_IWARP_SCTP:
case ISCSI_INFINIBAND:
default:
pr_err("Unsupported network_transport: %d\n",
np->np_network_transport);
return -EINVAL;
}
ret = sock_create(sockaddr->ss_family, np->np_sock_type,
np->np_ip_proto, &sock);
if (ret < 0) {
pr_err("sock_create() failed.\n");
return ret;
}
np->np_socket = sock;
/*
* The SCTP stack needs struct socket->file.
*/
if ((np->np_network_transport == ISCSI_SCTP_TCP) ||
(np->np_network_transport == ISCSI_SCTP_UDP)) {
if (!sock->file) {
sock->file = kzalloc(sizeof(struct file), GFP_KERNEL);
if (!sock->file) {
pr_err("Unable to allocate struct"
" file for SCTP\n");
ret = -ENOMEM;
goto fail;
}
np->np_flags |= NPF_SCTP_STRUCT_FILE;
}
}
/*
* Setup the np->np_sockaddr from the passed sockaddr setup
* in iscsi_target_configfs.c code..
*/
memcpy((void *)&np->np_sockaddr, (void *)sockaddr,
sizeof(struct __kernel_sockaddr_storage));
if (sockaddr->ss_family == AF_INET6)
len = sizeof(struct sockaddr_in6);
else
len = sizeof(struct sockaddr_in);
/*
* Set SO_REUSEADDR, and disable Nagel Algorithm with TCP_NODELAY.
*/
opt = 1;
if (np->np_network_transport == ISCSI_TCP) {
ret = kernel_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
(char *)&opt, sizeof(opt));
if (ret < 0) {
pr_err("kernel_setsockopt() for TCP_NODELAY"
" failed: %d\n", ret);
goto fail;
}
}
ret = kernel_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(char *)&opt, sizeof(opt));
if (ret < 0) {
pr_err("kernel_setsockopt() for SO_REUSEADDR"
" failed\n");
goto fail;
}
ret = kernel_bind(sock, (struct sockaddr *)&np->np_sockaddr, len);
if (ret < 0) {
pr_err("kernel_bind() failed: %d\n", ret);
goto fail;
}
ret = kernel_listen(sock, backlog);
if (ret != 0) {
pr_err("kernel_listen() failed: %d\n", ret);
goto fail;
}
return 0;
fail:
np->np_socket = NULL;
if (sock) {
if (np->np_flags & NPF_SCTP_STRUCT_FILE) {
kfree(sock->file);
sock->file = NULL;
}
sock_release(sock);
}
return ret;
}
static int __iscsi_target_login_thread(struct iscsi_np *np)
{
u8 buffer[ISCSI_HDR_LEN], iscsi_opcode, zero_tsih = 0;
int err, ret = 0, ip_proto, sock_type, set_sctp_conn_flag, stop;
struct iscsi_conn *conn = NULL;
struct iscsi_login *login;
struct iscsi_portal_group *tpg = NULL;
struct socket *new_sock, *sock;
struct kvec iov;
struct iscsi_login_req *pdu;
struct sockaddr_in sock_in;
struct sockaddr_in6 sock_in6;
flush_signals(current);
set_sctp_conn_flag = 0;
sock = np->np_socket;
ip_proto = np->np_ip_proto;
sock_type = np->np_sock_type;
spin_lock_bh(&np->np_thread_lock);
if (np->np_thread_state == ISCSI_NP_THREAD_RESET) {
np->np_thread_state = ISCSI_NP_THREAD_ACTIVE;
complete(&np->np_restart_comp);
} else {
np->np_thread_state = ISCSI_NP_THREAD_ACTIVE;
}
spin_unlock_bh(&np->np_thread_lock);
if (kernel_accept(sock, &new_sock, 0) < 0) {
spin_lock_bh(&np->np_thread_lock);
if (np->np_thread_state == ISCSI_NP_THREAD_RESET) {
spin_unlock_bh(&np->np_thread_lock);
complete(&np->np_restart_comp);
/* Get another socket */
return 1;
}
spin_unlock_bh(&np->np_thread_lock);
goto out;
}
/*
* The SCTP stack needs struct socket->file.
*/
if ((np->np_network_transport == ISCSI_SCTP_TCP) ||
(np->np_network_transport == ISCSI_SCTP_UDP)) {
if (!new_sock->file) {
new_sock->file = kzalloc(
sizeof(struct file), GFP_KERNEL);
if (!new_sock->file) {
pr_err("Unable to allocate struct"
" file for SCTP\n");
sock_release(new_sock);
/* Get another socket */
return 1;
}
set_sctp_conn_flag = 1;
}
}
iscsi_start_login_thread_timer(np);
conn = kzalloc(sizeof(struct iscsi_conn), GFP_KERNEL);
if (!conn) {
pr_err("Could not allocate memory for"
" new connection\n");
if (set_sctp_conn_flag) {
kfree(new_sock->file);
new_sock->file = NULL;
}
sock_release(new_sock);
/* Get another socket */
return 1;
}
pr_debug("Moving to TARG_CONN_STATE_FREE.\n");
conn->conn_state = TARG_CONN_STATE_FREE;
conn->sock = new_sock;
if (set_sctp_conn_flag)
conn->conn_flags |= CONNFLAG_SCTP_STRUCT_FILE;
pr_debug("Moving to TARG_CONN_STATE_XPT_UP.\n");
conn->conn_state = TARG_CONN_STATE_XPT_UP;
/*
* Allocate conn->conn_ops early as a failure calling
* iscsit_tx_login_rsp() below will call tx_data().
*/
conn->conn_ops = kzalloc(sizeof(struct iscsi_conn_ops), GFP_KERNEL);
if (!conn->conn_ops) {
pr_err("Unable to allocate memory for"
" struct iscsi_conn_ops.\n");
goto new_sess_out;
}
/*
* Perform the remaining iSCSI connection initialization items..
*/
if (iscsi_login_init_conn(conn) < 0)
goto new_sess_out;
memset(buffer, 0, ISCSI_HDR_LEN);
memset(&iov, 0, sizeof(struct kvec));
iov.iov_base = buffer;
iov.iov_len = ISCSI_HDR_LEN;
if (rx_data(conn, &iov, 1, ISCSI_HDR_LEN) <= 0) {
pr_err("rx_data() returned an error.\n");
goto new_sess_out;
}
iscsi_opcode = (buffer[0] & ISCSI_OPCODE_MASK);
if (!(iscsi_opcode & ISCSI_OP_LOGIN)) {
pr_err("First opcode is not login request,"
" failing login request.\n");
goto new_sess_out;
}
pdu = (struct iscsi_login_req *) buffer;
pdu->cid = be16_to_cpu(pdu->cid);
pdu->tsih = be16_to_cpu(pdu->tsih);
pdu->itt = be32_to_cpu(pdu->itt);
pdu->cmdsn = be32_to_cpu(pdu->cmdsn);
pdu->exp_statsn = be32_to_cpu(pdu->exp_statsn);
/*
* Used by iscsit_tx_login_rsp() for Login Resonses PDUs
* when Status-Class != 0.
*/
conn->login_itt = pdu->itt;
spin_lock_bh(&np->np_thread_lock);
if (np->np_thread_state != ISCSI_NP_THREAD_ACTIVE) {
spin_unlock_bh(&np->np_thread_lock);
pr_err("iSCSI Network Portal on %s:%hu currently not"
" active.\n", np->np_ip, np->np_port);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE);
goto new_sess_out;
}
spin_unlock_bh(&np->np_thread_lock);
if (np->np_sockaddr.ss_family == AF_INET6) {
memset(&sock_in6, 0, sizeof(struct sockaddr_in6));
if (conn->sock->ops->getname(conn->sock,
(struct sockaddr *)&sock_in6, &err, 1) < 0) {
pr_err("sock_ops->getname() failed.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_TARGET_ERROR);
goto new_sess_out;
}
#if 0
if (!iscsi_ntop6((const unsigned char *)
&sock_in6.sin6_addr.in6_u,
(char *)&conn->ipv6_login_ip[0],
IPV6_ADDRESS_SPACE)) {
pr_err("iscsi_ntop6() failed\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_TARGET_ERROR);
goto new_sess_out;
}
#else
pr_debug("Skipping iscsi_ntop6()\n");
#endif
} else {
memset(&sock_in, 0, sizeof(struct sockaddr_in));
if (conn->sock->ops->getname(conn->sock,
(struct sockaddr *)&sock_in, &err, 1) < 0) {
pr_err("sock_ops->getname() failed.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_TARGET_ERROR);
goto new_sess_out;
}
sprintf(conn->login_ip, "%pI4", &sock_in.sin_addr.s_addr);
conn->login_port = ntohs(sock_in.sin_port);
}
conn->network_transport = np->np_network_transport;
pr_debug("Received iSCSI login request from %s on %s Network"
" Portal %s:%hu\n", conn->login_ip,
(conn->network_transport == ISCSI_TCP) ? "TCP" : "SCTP",
np->np_ip, np->np_port);
pr_debug("Moving to TARG_CONN_STATE_IN_LOGIN.\n");
conn->conn_state = TARG_CONN_STATE_IN_LOGIN;
if (iscsi_login_check_initiator_version(conn, pdu->max_version,
pdu->min_version) < 0)
goto new_sess_out;
zero_tsih = (pdu->tsih == 0x0000);
if ((zero_tsih)) {
/*
* This is the leading connection of a new session.
* We wait until after authentication to check for
* session reinstatement.
*/
if (iscsi_login_zero_tsih_s1(conn, buffer) < 0)
goto new_sess_out;
} else {
/*
* Add a new connection to an existing session.
* We check for a non-existant session in
* iscsi_login_non_zero_tsih_s2() below based
* on ISID/TSIH, but wait until after authentication
* to check for connection reinstatement, etc.
*/
if (iscsi_login_non_zero_tsih_s1(conn, buffer) < 0)
goto new_sess_out;
}
/*
* This will process the first login request, and call
* iscsi_target_locate_portal(), and return a valid struct iscsi_login.
*/
login = iscsi_target_init_negotiation(np, conn, buffer);
if (!login) {
tpg = conn->tpg;
goto new_sess_out;
}
tpg = conn->tpg;
if (!tpg) {
pr_err("Unable to locate struct iscsi_conn->tpg\n");
goto new_sess_out;
}
if (zero_tsih) {
if (iscsi_login_zero_tsih_s2(conn) < 0) {
iscsi_target_nego_release(login, conn);
goto new_sess_out;
}
} else {
if (iscsi_login_non_zero_tsih_s2(conn, buffer) < 0) {
iscsi_target_nego_release(login, conn);
goto old_sess_out;
}
}
if (iscsi_target_start_negotiation(login, conn) < 0)
goto new_sess_out;
if (!conn->sess) {
pr_err("struct iscsi_conn session pointer is NULL!\n");
goto new_sess_out;
}
iscsi_stop_login_thread_timer(np);
if (signal_pending(current))
goto new_sess_out;
ret = iscsi_post_login_handler(np, conn, zero_tsih);
if (ret < 0)
goto new_sess_out;
iscsit_deaccess_np(np, tpg);
tpg = NULL;
/* Get another socket */
return 1;
new_sess_out:
pr_err("iSCSI Login negotiation failed.\n");
iscsit_collect_login_stats(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
if (!zero_tsih || !conn->sess)
goto old_sess_out;
if (conn->sess->se_sess)
transport_free_session(conn->sess->se_sess);
if (conn->sess->session_index != 0) {
spin_lock_bh(&sess_idr_lock);
idr_remove(&sess_idr, conn->sess->session_index);
spin_unlock_bh(&sess_idr_lock);
}
if (conn->sess->sess_ops)
kfree(conn->sess->sess_ops);
if (conn->sess)
kfree(conn->sess);
old_sess_out:
iscsi_stop_login_thread_timer(np);
/*
* If login negotiation fails check if the Time2Retain timer
* needs to be restarted.
*/
if (!zero_tsih && conn->sess) {
spin_lock_bh(&conn->sess->conn_lock);
if (conn->sess->session_state == TARG_SESS_STATE_FAILED) {
struct se_portal_group *se_tpg =
&ISCSI_TPG_C(conn)->tpg_se_tpg;
atomic_set(&conn->sess->session_continuation, 0);
spin_unlock_bh(&conn->sess->conn_lock);
spin_lock_bh(&se_tpg->session_lock);
iscsit_start_time2retain_handler(conn->sess);
spin_unlock_bh(&se_tpg->session_lock);
} else
spin_unlock_bh(&conn->sess->conn_lock);
iscsit_dec_session_usage_count(conn->sess);
}
if (!IS_ERR(conn->conn_rx_hash.tfm))
crypto_free_hash(conn->conn_rx_hash.tfm);
if (!IS_ERR(conn->conn_tx_hash.tfm))
crypto_free_hash(conn->conn_tx_hash.tfm);
if (conn->conn_cpumask)
free_cpumask_var(conn->conn_cpumask);
kfree(conn->conn_ops);
if (conn->param_list) {
iscsi_release_param_list(conn->param_list);
conn->param_list = NULL;
}
if (conn->sock) {
if (conn->conn_flags & CONNFLAG_SCTP_STRUCT_FILE) {
kfree(conn->sock->file);
conn->sock->file = NULL;
}
sock_release(conn->sock);
}
kfree(conn);
if (tpg) {
iscsit_deaccess_np(np, tpg);
tpg = NULL;
}
out:
stop = kthread_should_stop();
if (!stop && signal_pending(current)) {
spin_lock_bh(&np->np_thread_lock);
stop = (np->np_thread_state == ISCSI_NP_THREAD_SHUTDOWN);
spin_unlock_bh(&np->np_thread_lock);
}
/* Wait for another socket.. */
if (!stop)
return 1;
iscsi_stop_login_thread_timer(np);
spin_lock_bh(&np->np_thread_lock);
np->np_thread_state = ISCSI_NP_THREAD_EXIT;
spin_unlock_bh(&np->np_thread_lock);
return 0;
}
int iscsi_target_login_thread(void *arg)
{
struct iscsi_np *np = (struct iscsi_np *)arg;
int ret;
allow_signal(SIGINT);
while (!kthread_should_stop()) {
ret = __iscsi_target_login_thread(np);
/*
* We break and exit here unless another sock_accept() call
* is expected.
*/
if (ret != 1)
break;
}
return 0;
}
#ifndef ISCSI_TARGET_LOGIN_H
#define ISCSI_TARGET_LOGIN_H
extern int iscsi_login_setup_crypto(struct iscsi_conn *);
extern int iscsi_check_for_session_reinstatement(struct iscsi_conn *);
extern int iscsi_login_post_auth_non_zero_tsih(struct iscsi_conn *, u16, u32);
extern int iscsi_target_setup_login_socket(struct iscsi_np *,
struct __kernel_sockaddr_storage *);
extern int iscsi_target_login_thread(void *);
extern int iscsi_login_disable_FIM_keys(struct iscsi_param_list *, struct iscsi_conn *);
#endif /*** ISCSI_TARGET_LOGIN_H ***/
/*******************************************************************************
* This file contains main functions related to iSCSI Parameter negotiation.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/ctype.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_tpg.h>
#include "iscsi_target_core.h"
#include "iscsi_target_parameters.h"
#include "iscsi_target_login.h"
#include "iscsi_target_nego.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
#include "iscsi_target_auth.h"
#define MAX_LOGIN_PDUS 7
#define TEXT_LEN 4096
void convert_null_to_semi(char *buf, int len)
{
int i;
for (i = 0; i < len; i++)
if (buf[i] == '\0')
buf[i] = ';';
}
int strlen_semi(char *buf)
{
int i = 0;
while (buf[i] != '\0') {
if (buf[i] == ';')
return i;
i++;
}
return -1;
}
int extract_param(
const char *in_buf,
const char *pattern,
unsigned int max_length,
char *out_buf,
unsigned char *type)
{
char *ptr;
int len;
if (!in_buf || !pattern || !out_buf || !type)
return -1;
ptr = strstr(in_buf, pattern);
if (!ptr)
return -1;
ptr = strstr(ptr, "=");
if (!ptr)
return -1;
ptr += 1;
if (*ptr == '0' && (*(ptr+1) == 'x' || *(ptr+1) == 'X')) {
ptr += 2; /* skip 0x */
*type = HEX;
} else
*type = DECIMAL;
len = strlen_semi(ptr);
if (len < 0)
return -1;
if (len > max_length) {
pr_err("Length of input: %d exeeds max_length:"
" %d\n", len, max_length);
return -1;
}
memcpy(out_buf, ptr, len);
out_buf[len] = '\0';
return 0;
}
static u32 iscsi_handle_authentication(
struct iscsi_conn *conn,
char *in_buf,
char *out_buf,
int in_length,
int *out_length,
unsigned char *authtype)
{
struct iscsi_session *sess = conn->sess;
struct iscsi_node_auth *auth;
struct iscsi_node_acl *iscsi_nacl;
struct se_node_acl *se_nacl;
if (!sess->sess_ops->SessionType) {
/*
* For SessionType=Normal
*/
se_nacl = conn->sess->se_sess->se_node_acl;
if (!se_nacl) {
pr_err("Unable to locate struct se_node_acl for"
" CHAP auth\n");
return -1;
}
iscsi_nacl = container_of(se_nacl, struct iscsi_node_acl,
se_node_acl);
if (!iscsi_nacl) {
pr_err("Unable to locate struct iscsi_node_acl for"
" CHAP auth\n");
return -1;
}
auth = ISCSI_NODE_AUTH(iscsi_nacl);
} else {
/*
* For SessionType=Discovery
*/
auth = &iscsit_global->discovery_acl.node_auth;
}
if (strstr("CHAP", authtype))
strcpy(conn->sess->auth_type, "CHAP");
else
strcpy(conn->sess->auth_type, NONE);
if (strstr("None", authtype))
return 1;
#ifdef CANSRP
else if (strstr("SRP", authtype))
return srp_main_loop(conn, auth, in_buf, out_buf,
&in_length, out_length);
#endif
else if (strstr("CHAP", authtype))
return chap_main_loop(conn, auth, in_buf, out_buf,
&in_length, out_length);
else if (strstr("SPKM1", authtype))
return 2;
else if (strstr("SPKM2", authtype))
return 2;
else if (strstr("KRB5", authtype))
return 2;
else
return 2;
}
static void iscsi_remove_failed_auth_entry(struct iscsi_conn *conn)
{
kfree(conn->auth_protocol);
}
static int iscsi_target_check_login_request(
struct iscsi_conn *conn,
struct iscsi_login *login)
{
int req_csg, req_nsg, rsp_csg, rsp_nsg;
u32 payload_length;
struct iscsi_login_req *login_req;
struct iscsi_login_rsp *login_rsp;
login_req = (struct iscsi_login_req *) login->req;
login_rsp = (struct iscsi_login_rsp *) login->rsp;
payload_length = ntoh24(login_req->dlength);
switch (login_req->opcode & ISCSI_OPCODE_MASK) {
case ISCSI_OP_LOGIN:
break;
default:
pr_err("Received unknown opcode 0x%02x.\n",
login_req->opcode & ISCSI_OPCODE_MASK);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
return -1;
}
if ((login_req->flags & ISCSI_FLAG_LOGIN_CONTINUE) &&
(login_req->flags & ISCSI_FLAG_LOGIN_TRANSIT)) {
pr_err("Login request has both ISCSI_FLAG_LOGIN_CONTINUE"
" and ISCSI_FLAG_LOGIN_TRANSIT set, protocol error.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
return -1;
}
req_csg = (login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2;
rsp_csg = (login_rsp->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2;
req_nsg = (login_req->flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK);
rsp_nsg = (login_rsp->flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK);
if (req_csg != login->current_stage) {
pr_err("Initiator unexpectedly changed login stage"
" from %d to %d, login failed.\n", login->current_stage,
req_csg);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
return -1;
}
if ((req_nsg == 2) || (req_csg >= 2) ||
((login_req->flags & ISCSI_FLAG_LOGIN_TRANSIT) &&
(req_nsg <= req_csg))) {
pr_err("Illegal login_req->flags Combination, CSG: %d,"
" NSG: %d, ISCSI_FLAG_LOGIN_TRANSIT: %d.\n", req_csg,
req_nsg, (login_req->flags & ISCSI_FLAG_LOGIN_TRANSIT));
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
return -1;
}
if ((login_req->max_version != login->version_max) ||
(login_req->min_version != login->version_min)) {
pr_err("Login request changed Version Max/Nin"
" unexpectedly to 0x%02x/0x%02x, protocol error\n",
login_req->max_version, login_req->min_version);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
return -1;
}
if (memcmp(login_req->isid, login->isid, 6) != 0) {
pr_err("Login request changed ISID unexpectedly,"
" protocol error.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
return -1;
}
if (login_req->itt != login->init_task_tag) {
pr_err("Login request changed ITT unexpectedly to"
" 0x%08x, protocol error.\n", login_req->itt);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
return -1;
}
if (payload_length > MAX_KEY_VALUE_PAIRS) {
pr_err("Login request payload exceeds default"
" MaxRecvDataSegmentLength: %u, protocol error.\n",
MAX_KEY_VALUE_PAIRS);
return -1;
}
return 0;
}
static int iscsi_target_check_first_request(
struct iscsi_conn *conn,
struct iscsi_login *login)
{
struct iscsi_param *param = NULL;
struct se_node_acl *se_nacl;
login->first_request = 0;
list_for_each_entry(param, &conn->param_list->param_list, p_list) {
if (!strncmp(param->name, SESSIONTYPE, 11)) {
if (!IS_PSTATE_ACCEPTOR(param)) {
pr_err("SessionType key not received"
" in first login request.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_MISSING_FIELDS);
return -1;
}
if (!strncmp(param->value, DISCOVERY, 9))
return 0;
}
if (!strncmp(param->name, INITIATORNAME, 13)) {
if (!IS_PSTATE_ACCEPTOR(param)) {
if (!login->leading_connection)
continue;
pr_err("InitiatorName key not received"
" in first login request.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_MISSING_FIELDS);
return -1;
}
/*
* For non-leading connections, double check that the
* received InitiatorName matches the existing session's
* struct iscsi_node_acl.
*/
if (!login->leading_connection) {
se_nacl = conn->sess->se_sess->se_node_acl;
if (!se_nacl) {
pr_err("Unable to locate"
" struct se_node_acl\n");
iscsit_tx_login_rsp(conn,
ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_TGT_NOT_FOUND);
return -1;
}
if (strcmp(param->value,
se_nacl->initiatorname)) {
pr_err("Incorrect"
" InitiatorName: %s for this"
" iSCSI Initiator Node.\n",
param->value);
iscsit_tx_login_rsp(conn,
ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_TGT_NOT_FOUND);
return -1;
}
}
}
}
return 0;
}
static int iscsi_target_do_tx_login_io(struct iscsi_conn *conn, struct iscsi_login *login)
{
u32 padding = 0;
struct iscsi_session *sess = conn->sess;
struct iscsi_login_rsp *login_rsp;
login_rsp = (struct iscsi_login_rsp *) login->rsp;
login_rsp->opcode = ISCSI_OP_LOGIN_RSP;
hton24(login_rsp->dlength, login->rsp_length);
memcpy(login_rsp->isid, login->isid, 6);
login_rsp->tsih = cpu_to_be16(login->tsih);
login_rsp->itt = cpu_to_be32(login->init_task_tag);
login_rsp->statsn = cpu_to_be32(conn->stat_sn++);
login_rsp->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn);
login_rsp->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn);
pr_debug("Sending Login Response, Flags: 0x%02x, ITT: 0x%08x,"
" ExpCmdSN; 0x%08x, MaxCmdSN: 0x%08x, StatSN: 0x%08x, Length:"
" %u\n", login_rsp->flags, ntohl(login_rsp->itt),
ntohl(login_rsp->exp_cmdsn), ntohl(login_rsp->max_cmdsn),
ntohl(login_rsp->statsn), login->rsp_length);
padding = ((-login->rsp_length) & 3);
if (iscsi_login_tx_data(
conn,
login->rsp,
login->rsp_buf,
login->rsp_length + padding) < 0)
return -1;
login->rsp_length = 0;
login_rsp->tsih = be16_to_cpu(login_rsp->tsih);
login_rsp->itt = be32_to_cpu(login_rsp->itt);
login_rsp->statsn = be32_to_cpu(login_rsp->statsn);
mutex_lock(&sess->cmdsn_mutex);
login_rsp->exp_cmdsn = be32_to_cpu(sess->exp_cmd_sn);
login_rsp->max_cmdsn = be32_to_cpu(sess->max_cmd_sn);
mutex_unlock(&sess->cmdsn_mutex);
return 0;
}
static int iscsi_target_do_rx_login_io(struct iscsi_conn *conn, struct iscsi_login *login)
{
u32 padding = 0, payload_length;
struct iscsi_login_req *login_req;
if (iscsi_login_rx_data(conn, login->req, ISCSI_HDR_LEN) < 0)
return -1;
login_req = (struct iscsi_login_req *) login->req;
payload_length = ntoh24(login_req->dlength);
login_req->tsih = be16_to_cpu(login_req->tsih);
login_req->itt = be32_to_cpu(login_req->itt);
login_req->cid = be16_to_cpu(login_req->cid);
login_req->cmdsn = be32_to_cpu(login_req->cmdsn);
login_req->exp_statsn = be32_to_cpu(login_req->exp_statsn);
pr_debug("Got Login Command, Flags 0x%02x, ITT: 0x%08x,"
" CmdSN: 0x%08x, ExpStatSN: 0x%08x, CID: %hu, Length: %u\n",
login_req->flags, login_req->itt, login_req->cmdsn,
login_req->exp_statsn, login_req->cid, payload_length);
if (iscsi_target_check_login_request(conn, login) < 0)
return -1;
padding = ((-payload_length) & 3);
memset(login->req_buf, 0, MAX_KEY_VALUE_PAIRS);
if (iscsi_login_rx_data(
conn,
login->req_buf,
payload_length + padding) < 0)
return -1;
return 0;
}
static int iscsi_target_do_login_io(struct iscsi_conn *conn, struct iscsi_login *login)
{
if (iscsi_target_do_tx_login_io(conn, login) < 0)
return -1;
if (iscsi_target_do_rx_login_io(conn, login) < 0)
return -1;
return 0;
}
static int iscsi_target_get_initial_payload(
struct iscsi_conn *conn,
struct iscsi_login *login)
{
u32 padding = 0, payload_length;
struct iscsi_login_req *login_req;
login_req = (struct iscsi_login_req *) login->req;
payload_length = ntoh24(login_req->dlength);
pr_debug("Got Login Command, Flags 0x%02x, ITT: 0x%08x,"
" CmdSN: 0x%08x, ExpStatSN: 0x%08x, Length: %u\n",
login_req->flags, login_req->itt, login_req->cmdsn,
login_req->exp_statsn, payload_length);
if (iscsi_target_check_login_request(conn, login) < 0)
return -1;
padding = ((-payload_length) & 3);
if (iscsi_login_rx_data(
conn,
login->req_buf,
payload_length + padding) < 0)
return -1;
return 0;
}
/*
* NOTE: We check for existing sessions or connections AFTER the initiator
* has been successfully authenticated in order to protect against faked
* ISID/TSIH combinations.
*/
static int iscsi_target_check_for_existing_instances(
struct iscsi_conn *conn,
struct iscsi_login *login)
{
if (login->checked_for_existing)
return 0;
login->checked_for_existing = 1;
if (!login->tsih)
return iscsi_check_for_session_reinstatement(conn);
else
return iscsi_login_post_auth_non_zero_tsih(conn, login->cid,
login->initial_exp_statsn);
}
static int iscsi_target_do_authentication(
struct iscsi_conn *conn,
struct iscsi_login *login)
{
int authret;
u32 payload_length;
struct iscsi_param *param;
struct iscsi_login_req *login_req;
struct iscsi_login_rsp *login_rsp;
login_req = (struct iscsi_login_req *) login->req;
login_rsp = (struct iscsi_login_rsp *) login->rsp;
payload_length = ntoh24(login_req->dlength);
param = iscsi_find_param_from_key(AUTHMETHOD, conn->param_list);
if (!param)
return -1;
authret = iscsi_handle_authentication(
conn,
login->req_buf,
login->rsp_buf,
payload_length,
&login->rsp_length,
param->value);
switch (authret) {
case 0:
pr_debug("Received OK response"
" from LIO Authentication, continuing.\n");
break;
case 1:
pr_debug("iSCSI security negotiation"
" completed sucessfully.\n");
login->auth_complete = 1;
if ((login_req->flags & ISCSI_FLAG_LOGIN_NEXT_STAGE1) &&
(login_req->flags & ISCSI_FLAG_LOGIN_TRANSIT)) {
login_rsp->flags |= (ISCSI_FLAG_LOGIN_NEXT_STAGE1 |
ISCSI_FLAG_LOGIN_TRANSIT);
login->current_stage = 1;
}
return iscsi_target_check_for_existing_instances(
conn, login);
case 2:
pr_err("Security negotiation"
" failed.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_AUTH_FAILED);
return -1;
default:
pr_err("Received unknown error %d from LIO"
" Authentication\n", authret);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_TARGET_ERROR);
return -1;
}
return 0;
}
static int iscsi_target_handle_csg_zero(
struct iscsi_conn *conn,
struct iscsi_login *login)
{
int ret;
u32 payload_length;
struct iscsi_param *param;
struct iscsi_login_req *login_req;
struct iscsi_login_rsp *login_rsp;
login_req = (struct iscsi_login_req *) login->req;
login_rsp = (struct iscsi_login_rsp *) login->rsp;
payload_length = ntoh24(login_req->dlength);
param = iscsi_find_param_from_key(AUTHMETHOD, conn->param_list);
if (!param)
return -1;
ret = iscsi_decode_text_input(
PHASE_SECURITY|PHASE_DECLARATIVE,
SENDER_INITIATOR|SENDER_RECEIVER,
login->req_buf,
payload_length,
conn->param_list);
if (ret < 0)
return -1;
if (ret > 0) {
if (login->auth_complete) {
pr_err("Initiator has already been"
" successfully authenticated, but is still"
" sending %s keys.\n", param->value);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_INIT_ERR);
return -1;
}
goto do_auth;
}
if (login->first_request)
if (iscsi_target_check_first_request(conn, login) < 0)
return -1;
ret = iscsi_encode_text_output(
PHASE_SECURITY|PHASE_DECLARATIVE,
SENDER_TARGET,
login->rsp_buf,
&login->rsp_length,
conn->param_list);
if (ret < 0)
return -1;
if (!iscsi_check_negotiated_keys(conn->param_list)) {
if (ISCSI_TPG_ATTRIB(ISCSI_TPG_C(conn))->authentication &&
!strncmp(param->value, NONE, 4)) {
pr_err("Initiator sent AuthMethod=None but"
" Target is enforcing iSCSI Authentication,"
" login failed.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_AUTH_FAILED);
return -1;
}
if (ISCSI_TPG_ATTRIB(ISCSI_TPG_C(conn))->authentication &&
!login->auth_complete)
return 0;
if (strncmp(param->value, NONE, 4) && !login->auth_complete)
return 0;
if ((login_req->flags & ISCSI_FLAG_LOGIN_NEXT_STAGE1) &&
(login_req->flags & ISCSI_FLAG_LOGIN_TRANSIT)) {
login_rsp->flags |= ISCSI_FLAG_LOGIN_NEXT_STAGE1 |
ISCSI_FLAG_LOGIN_TRANSIT;
login->current_stage = 1;
}
}
return 0;
do_auth:
return iscsi_target_do_authentication(conn, login);
}
static int iscsi_target_handle_csg_one(struct iscsi_conn *conn, struct iscsi_login *login)
{
int ret;
u32 payload_length;
struct iscsi_login_req *login_req;
struct iscsi_login_rsp *login_rsp;
login_req = (struct iscsi_login_req *) login->req;
login_rsp = (struct iscsi_login_rsp *) login->rsp;
payload_length = ntoh24(login_req->dlength);
ret = iscsi_decode_text_input(
PHASE_OPERATIONAL|PHASE_DECLARATIVE,
SENDER_INITIATOR|SENDER_RECEIVER,
login->req_buf,
payload_length,
conn->param_list);
if (ret < 0)
return -1;
if (login->first_request)
if (iscsi_target_check_first_request(conn, login) < 0)
return -1;
if (iscsi_target_check_for_existing_instances(conn, login) < 0)
return -1;
ret = iscsi_encode_text_output(
PHASE_OPERATIONAL|PHASE_DECLARATIVE,
SENDER_TARGET,
login->rsp_buf,
&login->rsp_length,
conn->param_list);
if (ret < 0)
return -1;
if (!login->auth_complete &&
ISCSI_TPG_ATTRIB(ISCSI_TPG_C(conn))->authentication) {
pr_err("Initiator is requesting CSG: 1, has not been"
" successfully authenticated, and the Target is"
" enforcing iSCSI Authentication, login failed.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_AUTH_FAILED);
return -1;
}
if (!iscsi_check_negotiated_keys(conn->param_list))
if ((login_req->flags & ISCSI_FLAG_LOGIN_NEXT_STAGE3) &&
(login_req->flags & ISCSI_FLAG_LOGIN_TRANSIT))
login_rsp->flags |= ISCSI_FLAG_LOGIN_NEXT_STAGE3 |
ISCSI_FLAG_LOGIN_TRANSIT;
return 0;
}
static int iscsi_target_do_login(struct iscsi_conn *conn, struct iscsi_login *login)
{
int pdu_count = 0;
struct iscsi_login_req *login_req;
struct iscsi_login_rsp *login_rsp;
login_req = (struct iscsi_login_req *) login->req;
login_rsp = (struct iscsi_login_rsp *) login->rsp;
while (1) {
if (++pdu_count > MAX_LOGIN_PDUS) {
pr_err("MAX_LOGIN_PDUS count reached.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_TARGET_ERROR);
return -1;
}
switch ((login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2) {
case 0:
login_rsp->flags |= (0 & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK);
if (iscsi_target_handle_csg_zero(conn, login) < 0)
return -1;
break;
case 1:
login_rsp->flags |= ISCSI_FLAG_LOGIN_CURRENT_STAGE1;
if (iscsi_target_handle_csg_one(conn, login) < 0)
return -1;
if (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) {
login->tsih = conn->sess->tsih;
if (iscsi_target_do_tx_login_io(conn,
login) < 0)
return -1;
return 0;
}
break;
default:
pr_err("Illegal CSG: %d received from"
" Initiator, protocol error.\n",
(login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK)
>> 2);
break;
}
if (iscsi_target_do_login_io(conn, login) < 0)
return -1;
if (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) {
login_rsp->flags &= ~ISCSI_FLAG_LOGIN_TRANSIT;
login_rsp->flags &= ~ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK;
}
}
return 0;
}
static void iscsi_initiatorname_tolower(
char *param_buf)
{
char *c;
u32 iqn_size = strlen(param_buf), i;
for (i = 0; i < iqn_size; i++) {
c = (char *)&param_buf[i];
if (!isupper(*c))
continue;
*c = tolower(*c);
}
}
/*
* Processes the first Login Request..
*/
static int iscsi_target_locate_portal(
struct iscsi_np *np,
struct iscsi_conn *conn,
struct iscsi_login *login)
{
char *i_buf = NULL, *s_buf = NULL, *t_buf = NULL;
char *tmpbuf, *start = NULL, *end = NULL, *key, *value;
struct iscsi_session *sess = conn->sess;
struct iscsi_tiqn *tiqn;
struct iscsi_login_req *login_req;
struct iscsi_targ_login_rsp *login_rsp;
u32 payload_length;
int sessiontype = 0, ret = 0;
login_req = (struct iscsi_login_req *) login->req;
login_rsp = (struct iscsi_targ_login_rsp *) login->rsp;
payload_length = ntoh24(login_req->dlength);
login->first_request = 1;
login->leading_connection = (!login_req->tsih) ? 1 : 0;
login->current_stage =
(login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2;
login->version_min = login_req->min_version;
login->version_max = login_req->max_version;
memcpy(login->isid, login_req->isid, 6);
login->cmd_sn = login_req->cmdsn;
login->init_task_tag = login_req->itt;
login->initial_exp_statsn = login_req->exp_statsn;
login->cid = login_req->cid;
login->tsih = login_req->tsih;
if (iscsi_target_get_initial_payload(conn, login) < 0)
return -1;
tmpbuf = kzalloc(payload_length + 1, GFP_KERNEL);
if (!tmpbuf) {
pr_err("Unable to allocate memory for tmpbuf.\n");
return -1;
}
memcpy(tmpbuf, login->req_buf, payload_length);
tmpbuf[payload_length] = '\0';
start = tmpbuf;
end = (start + payload_length);
/*
* Locate the initial keys expected from the Initiator node in
* the first login request in order to progress with the login phase.
*/
while (start < end) {
if (iscsi_extract_key_value(start, &key, &value) < 0) {
ret = -1;
goto out;
}
if (!strncmp(key, "InitiatorName", 13))
i_buf = value;
else if (!strncmp(key, "SessionType", 11))
s_buf = value;
else if (!strncmp(key, "TargetName", 10))
t_buf = value;
start += strlen(key) + strlen(value) + 2;
}
/*
* See 5.3. Login Phase.
*/
if (!i_buf) {
pr_err("InitiatorName key not received"
" in first login request.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_MISSING_FIELDS);
ret = -1;
goto out;
}
/*
* Convert the incoming InitiatorName to lowercase following
* RFC-3720 3.2.6.1. section c) that says that iSCSI IQNs
* are NOT case sensitive.
*/
iscsi_initiatorname_tolower(i_buf);
if (!s_buf) {
if (!login->leading_connection)
goto get_target;
pr_err("SessionType key not received"
" in first login request.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_MISSING_FIELDS);
ret = -1;
goto out;
}
/*
* Use default portal group for discovery sessions.
*/
sessiontype = strncmp(s_buf, DISCOVERY, 9);
if (!sessiontype) {
conn->tpg = iscsit_global->discovery_tpg;
if (!login->leading_connection)
goto get_target;
sess->sess_ops->SessionType = 1;
/*
* Setup crc32c modules from libcrypto
*/
if (iscsi_login_setup_crypto(conn) < 0) {
pr_err("iscsi_login_setup_crypto() failed\n");
ret = -1;
goto out;
}
/*
* Serialize access across the discovery struct iscsi_portal_group to
* process login attempt.
*/
if (iscsit_access_np(np, conn->tpg) < 0) {
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE);
ret = -1;
goto out;
}
ret = 0;
goto out;
}
get_target:
if (!t_buf) {
pr_err("TargetName key not received"
" in first login request while"
" SessionType=Normal.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_MISSING_FIELDS);
ret = -1;
goto out;
}
/*
* Locate Target IQN from Storage Node.
*/
tiqn = iscsit_get_tiqn_for_login(t_buf);
if (!tiqn) {
pr_err("Unable to locate Target IQN: %s in"
" Storage Node\n", t_buf);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE);
ret = -1;
goto out;
}
pr_debug("Located Storage Object: %s\n", tiqn->tiqn);
/*
* Locate Target Portal Group from Storage Node.
*/
conn->tpg = iscsit_get_tpg_from_np(tiqn, np);
if (!conn->tpg) {
pr_err("Unable to locate Target Portal Group"
" on %s\n", tiqn->tiqn);
iscsit_put_tiqn_for_login(tiqn);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE);
ret = -1;
goto out;
}
pr_debug("Located Portal Group Object: %hu\n", conn->tpg->tpgt);
/*
* Setup crc32c modules from libcrypto
*/
if (iscsi_login_setup_crypto(conn) < 0) {
pr_err("iscsi_login_setup_crypto() failed\n");
ret = -1;
goto out;
}
/*
* Serialize access across the struct iscsi_portal_group to
* process login attempt.
*/
if (iscsit_access_np(np, conn->tpg) < 0) {
iscsit_put_tiqn_for_login(tiqn);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE);
ret = -1;
conn->tpg = NULL;
goto out;
}
/*
* conn->sess->node_acl will be set when the referenced
* struct iscsi_session is located from received ISID+TSIH in
* iscsi_login_non_zero_tsih_s2().
*/
if (!login->leading_connection) {
ret = 0;
goto out;
}
/*
* This value is required in iscsi_login_zero_tsih_s2()
*/
sess->sess_ops->SessionType = 0;
/*
* Locate incoming Initiator IQN reference from Storage Node.
*/
sess->se_sess->se_node_acl = core_tpg_check_initiator_node_acl(
&conn->tpg->tpg_se_tpg, i_buf);
if (!sess->se_sess->se_node_acl) {
pr_err("iSCSI Initiator Node: %s is not authorized to"
" access iSCSI target portal group: %hu.\n",
i_buf, conn->tpg->tpgt);
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR,
ISCSI_LOGIN_STATUS_TGT_FORBIDDEN);
ret = -1;
goto out;
}
ret = 0;
out:
kfree(tmpbuf);
return ret;
}
struct iscsi_login *iscsi_target_init_negotiation(
struct iscsi_np *np,
struct iscsi_conn *conn,
char *login_pdu)
{
struct iscsi_login *login;
login = kzalloc(sizeof(struct iscsi_login), GFP_KERNEL);
if (!login) {
pr_err("Unable to allocate memory for struct iscsi_login.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
goto out;
}
login->req = kzalloc(ISCSI_HDR_LEN, GFP_KERNEL);
if (!login->req) {
pr_err("Unable to allocate memory for Login Request.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
goto out;
}
memcpy(login->req, login_pdu, ISCSI_HDR_LEN);
login->req_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL);
if (!login->req_buf) {
pr_err("Unable to allocate memory for response buffer.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
goto out;
}
/*
* SessionType: Discovery
*
* Locates Default Portal
*
* SessionType: Normal
*
* Locates Target Portal from NP -> Target IQN
*/
if (iscsi_target_locate_portal(np, conn, login) < 0) {
pr_err("iSCSI Login negotiation failed.\n");
goto out;
}
return login;
out:
kfree(login->req);
kfree(login->req_buf);
kfree(login);
return NULL;
}
int iscsi_target_start_negotiation(
struct iscsi_login *login,
struct iscsi_conn *conn)
{
int ret = -1;
login->rsp = kzalloc(ISCSI_HDR_LEN, GFP_KERNEL);
if (!login->rsp) {
pr_err("Unable to allocate memory for"
" Login Response.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
ret = -1;
goto out;
}
login->rsp_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL);
if (!login->rsp_buf) {
pr_err("Unable to allocate memory for"
" request buffer.\n");
iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR,
ISCSI_LOGIN_STATUS_NO_RESOURCES);
ret = -1;
goto out;
}
ret = iscsi_target_do_login(conn, login);
out:
if (ret != 0)
iscsi_remove_failed_auth_entry(conn);
iscsi_target_nego_release(login, conn);
return ret;
}
void iscsi_target_nego_release(
struct iscsi_login *login,
struct iscsi_conn *conn)
{
kfree(login->req);
kfree(login->rsp);
kfree(login->req_buf);
kfree(login->rsp_buf);
kfree(login);
}
#ifndef ISCSI_TARGET_NEGO_H
#define ISCSI_TARGET_NEGO_H
#define DECIMAL 0
#define HEX 1
extern void convert_null_to_semi(char *, int);
extern int extract_param(const char *, const char *, unsigned int, char *,
unsigned char *);
extern struct iscsi_login *iscsi_target_init_negotiation(
struct iscsi_np *, struct iscsi_conn *, char *);
extern int iscsi_target_start_negotiation(
struct iscsi_login *, struct iscsi_conn *);
extern void iscsi_target_nego_release(
struct iscsi_login *, struct iscsi_conn *);
#endif /* ISCSI_TARGET_NEGO_H */
/*******************************************************************************
* This file contains the main functions related to Initiator Node Attributes.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include "iscsi_target_core.h"
#include "iscsi_target_device.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target_nodeattrib.h"
static inline char *iscsit_na_get_initiatorname(
struct iscsi_node_acl *nacl)
{
struct se_node_acl *se_nacl = &nacl->se_node_acl;
return &se_nacl->initiatorname[0];
}
void iscsit_set_default_node_attribues(
struct iscsi_node_acl *acl)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
a->dataout_timeout = NA_DATAOUT_TIMEOUT;
a->dataout_timeout_retries = NA_DATAOUT_TIMEOUT_RETRIES;
a->nopin_timeout = NA_NOPIN_TIMEOUT;
a->nopin_response_timeout = NA_NOPIN_RESPONSE_TIMEOUT;
a->random_datain_pdu_offsets = NA_RANDOM_DATAIN_PDU_OFFSETS;
a->random_datain_seq_offsets = NA_RANDOM_DATAIN_SEQ_OFFSETS;
a->random_r2t_offsets = NA_RANDOM_R2T_OFFSETS;
a->default_erl = NA_DEFAULT_ERL;
}
extern int iscsit_na_dataout_timeout(
struct iscsi_node_acl *acl,
u32 dataout_timeout)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (dataout_timeout > NA_DATAOUT_TIMEOUT_MAX) {
pr_err("Requested DataOut Timeout %u larger than"
" maximum %u\n", dataout_timeout,
NA_DATAOUT_TIMEOUT_MAX);
return -EINVAL;
} else if (dataout_timeout < NA_DATAOUT_TIMEOUT_MIX) {
pr_err("Requested DataOut Timeout %u smaller than"
" minimum %u\n", dataout_timeout,
NA_DATAOUT_TIMEOUT_MIX);
return -EINVAL;
}
a->dataout_timeout = dataout_timeout;
pr_debug("Set DataOut Timeout to %u for Initiator Node"
" %s\n", a->dataout_timeout, iscsit_na_get_initiatorname(acl));
return 0;
}
extern int iscsit_na_dataout_timeout_retries(
struct iscsi_node_acl *acl,
u32 dataout_timeout_retries)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (dataout_timeout_retries > NA_DATAOUT_TIMEOUT_RETRIES_MAX) {
pr_err("Requested DataOut Timeout Retries %u larger"
" than maximum %u", dataout_timeout_retries,
NA_DATAOUT_TIMEOUT_RETRIES_MAX);
return -EINVAL;
} else if (dataout_timeout_retries < NA_DATAOUT_TIMEOUT_RETRIES_MIN) {
pr_err("Requested DataOut Timeout Retries %u smaller"
" than minimum %u", dataout_timeout_retries,
NA_DATAOUT_TIMEOUT_RETRIES_MIN);
return -EINVAL;
}
a->dataout_timeout_retries = dataout_timeout_retries;
pr_debug("Set DataOut Timeout Retries to %u for"
" Initiator Node %s\n", a->dataout_timeout_retries,
iscsit_na_get_initiatorname(acl));
return 0;
}
extern int iscsit_na_nopin_timeout(
struct iscsi_node_acl *acl,
u32 nopin_timeout)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
struct iscsi_session *sess;
struct iscsi_conn *conn;
struct se_node_acl *se_nacl = &a->nacl->se_node_acl;
struct se_session *se_sess;
u32 orig_nopin_timeout = a->nopin_timeout;
if (nopin_timeout > NA_NOPIN_TIMEOUT_MAX) {
pr_err("Requested NopIn Timeout %u larger than maximum"
" %u\n", nopin_timeout, NA_NOPIN_TIMEOUT_MAX);
return -EINVAL;
} else if ((nopin_timeout < NA_NOPIN_TIMEOUT_MIN) &&
(nopin_timeout != 0)) {
pr_err("Requested NopIn Timeout %u smaller than"
" minimum %u and not 0\n", nopin_timeout,
NA_NOPIN_TIMEOUT_MIN);
return -EINVAL;
}
a->nopin_timeout = nopin_timeout;
pr_debug("Set NopIn Timeout to %u for Initiator"
" Node %s\n", a->nopin_timeout,
iscsit_na_get_initiatorname(acl));
/*
* Reenable disabled nopin_timeout timer for all iSCSI connections.
*/
if (!orig_nopin_timeout) {
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
spin_lock(&sess->conn_lock);
list_for_each_entry(conn, &sess->sess_conn_list,
conn_list) {
if (conn->conn_state !=
TARG_CONN_STATE_LOGGED_IN)
continue;
spin_lock(&conn->nopin_timer_lock);
__iscsit_start_nopin_timer(conn);
spin_unlock(&conn->nopin_timer_lock);
}
spin_unlock(&sess->conn_lock);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
}
return 0;
}
extern int iscsit_na_nopin_response_timeout(
struct iscsi_node_acl *acl,
u32 nopin_response_timeout)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (nopin_response_timeout > NA_NOPIN_RESPONSE_TIMEOUT_MAX) {
pr_err("Requested NopIn Response Timeout %u larger"
" than maximum %u\n", nopin_response_timeout,
NA_NOPIN_RESPONSE_TIMEOUT_MAX);
return -EINVAL;
} else if (nopin_response_timeout < NA_NOPIN_RESPONSE_TIMEOUT_MIN) {
pr_err("Requested NopIn Response Timeout %u smaller"
" than minimum %u\n", nopin_response_timeout,
NA_NOPIN_RESPONSE_TIMEOUT_MIN);
return -EINVAL;
}
a->nopin_response_timeout = nopin_response_timeout;
pr_debug("Set NopIn Response Timeout to %u for"
" Initiator Node %s\n", a->nopin_timeout,
iscsit_na_get_initiatorname(acl));
return 0;
}
extern int iscsit_na_random_datain_pdu_offsets(
struct iscsi_node_acl *acl,
u32 random_datain_pdu_offsets)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (random_datain_pdu_offsets != 0 && random_datain_pdu_offsets != 1) {
pr_err("Requested Random DataIN PDU Offsets: %u not"
" 0 or 1\n", random_datain_pdu_offsets);
return -EINVAL;
}
a->random_datain_pdu_offsets = random_datain_pdu_offsets;
pr_debug("Set Random DataIN PDU Offsets to %u for"
" Initiator Node %s\n", a->random_datain_pdu_offsets,
iscsit_na_get_initiatorname(acl));
return 0;
}
extern int iscsit_na_random_datain_seq_offsets(
struct iscsi_node_acl *acl,
u32 random_datain_seq_offsets)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (random_datain_seq_offsets != 0 && random_datain_seq_offsets != 1) {
pr_err("Requested Random DataIN Sequence Offsets: %u"
" not 0 or 1\n", random_datain_seq_offsets);
return -EINVAL;
}
a->random_datain_seq_offsets = random_datain_seq_offsets;
pr_debug("Set Random DataIN Sequence Offsets to %u for"
" Initiator Node %s\n", a->random_datain_seq_offsets,
iscsit_na_get_initiatorname(acl));
return 0;
}
extern int iscsit_na_random_r2t_offsets(
struct iscsi_node_acl *acl,
u32 random_r2t_offsets)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (random_r2t_offsets != 0 && random_r2t_offsets != 1) {
pr_err("Requested Random R2T Offsets: %u not"
" 0 or 1\n", random_r2t_offsets);
return -EINVAL;
}
a->random_r2t_offsets = random_r2t_offsets;
pr_debug("Set Random R2T Offsets to %u for"
" Initiator Node %s\n", a->random_r2t_offsets,
iscsit_na_get_initiatorname(acl));
return 0;
}
extern int iscsit_na_default_erl(
struct iscsi_node_acl *acl,
u32 default_erl)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (default_erl != 0 && default_erl != 1 && default_erl != 2) {
pr_err("Requested default ERL: %u not 0, 1, or 2\n",
default_erl);
return -EINVAL;
}
a->default_erl = default_erl;
pr_debug("Set use ERL0 flag to %u for Initiator"
" Node %s\n", a->default_erl,
iscsit_na_get_initiatorname(acl));
return 0;
}
#ifndef ISCSI_TARGET_NODEATTRIB_H
#define ISCSI_TARGET_NODEATTRIB_H
extern void iscsit_set_default_node_attribues(struct iscsi_node_acl *);
extern int iscsit_na_dataout_timeout(struct iscsi_node_acl *, u32);
extern int iscsit_na_dataout_timeout_retries(struct iscsi_node_acl *, u32);
extern int iscsit_na_nopin_timeout(struct iscsi_node_acl *, u32);
extern int iscsit_na_nopin_response_timeout(struct iscsi_node_acl *, u32);
extern int iscsit_na_random_datain_pdu_offsets(struct iscsi_node_acl *, u32);
extern int iscsit_na_random_datain_seq_offsets(struct iscsi_node_acl *, u32);
extern int iscsit_na_random_r2t_offsets(struct iscsi_node_acl *, u32);
extern int iscsit_na_default_erl(struct iscsi_node_acl *, u32);
#endif /* ISCSI_TARGET_NODEATTRIB_H */
/*******************************************************************************
* This file contains main functions related to iSCSI Parameter negotiation.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/slab.h>
#include "iscsi_target_core.h"
#include "iscsi_target_util.h"
#include "iscsi_target_parameters.h"
int iscsi_login_rx_data(
struct iscsi_conn *conn,
char *buf,
int length)
{
int rx_got;
struct kvec iov;
memset(&iov, 0, sizeof(struct kvec));
iov.iov_len = length;
iov.iov_base = buf;
/*
* Initial Marker-less Interval.
* Add the values regardless of IFMarker/OFMarker, considering
* it may not be negoitated yet.
*/
conn->of_marker += length;
rx_got = rx_data(conn, &iov, 1, length);
if (rx_got != length) {
pr_err("rx_data returned %d, expecting %d.\n",
rx_got, length);
return -1;
}
return 0 ;
}
int iscsi_login_tx_data(
struct iscsi_conn *conn,
char *pdu_buf,
char *text_buf,
int text_length)
{
int length, tx_sent;
struct kvec iov[2];
length = (ISCSI_HDR_LEN + text_length);
memset(&iov[0], 0, 2 * sizeof(struct kvec));
iov[0].iov_len = ISCSI_HDR_LEN;
iov[0].iov_base = pdu_buf;
iov[1].iov_len = text_length;
iov[1].iov_base = text_buf;
/*
* Initial Marker-less Interval.
* Add the values regardless of IFMarker/OFMarker, considering
* it may not be negoitated yet.
*/
conn->if_marker += length;
tx_sent = tx_data(conn, &iov[0], 2, length);
if (tx_sent != length) {
pr_err("tx_data returned %d, expecting %d.\n",
tx_sent, length);
return -1;
}
return 0;
}
void iscsi_dump_conn_ops(struct iscsi_conn_ops *conn_ops)
{
pr_debug("HeaderDigest: %s\n", (conn_ops->HeaderDigest) ?
"CRC32C" : "None");
pr_debug("DataDigest: %s\n", (conn_ops->DataDigest) ?
"CRC32C" : "None");
pr_debug("MaxRecvDataSegmentLength: %u\n",
conn_ops->MaxRecvDataSegmentLength);
pr_debug("OFMarker: %s\n", (conn_ops->OFMarker) ? "Yes" : "No");
pr_debug("IFMarker: %s\n", (conn_ops->IFMarker) ? "Yes" : "No");
if (conn_ops->OFMarker)
pr_debug("OFMarkInt: %u\n", conn_ops->OFMarkInt);
if (conn_ops->IFMarker)
pr_debug("IFMarkInt: %u\n", conn_ops->IFMarkInt);
}
void iscsi_dump_sess_ops(struct iscsi_sess_ops *sess_ops)
{
pr_debug("InitiatorName: %s\n", sess_ops->InitiatorName);
pr_debug("InitiatorAlias: %s\n", sess_ops->InitiatorAlias);
pr_debug("TargetName: %s\n", sess_ops->TargetName);
pr_debug("TargetAlias: %s\n", sess_ops->TargetAlias);
pr_debug("TargetPortalGroupTag: %hu\n",
sess_ops->TargetPortalGroupTag);
pr_debug("MaxConnections: %hu\n", sess_ops->MaxConnections);
pr_debug("InitialR2T: %s\n",
(sess_ops->InitialR2T) ? "Yes" : "No");
pr_debug("ImmediateData: %s\n", (sess_ops->ImmediateData) ?
"Yes" : "No");
pr_debug("MaxBurstLength: %u\n", sess_ops->MaxBurstLength);
pr_debug("FirstBurstLength: %u\n", sess_ops->FirstBurstLength);
pr_debug("DefaultTime2Wait: %hu\n", sess_ops->DefaultTime2Wait);
pr_debug("DefaultTime2Retain: %hu\n",
sess_ops->DefaultTime2Retain);
pr_debug("MaxOutstandingR2T: %hu\n",
sess_ops->MaxOutstandingR2T);
pr_debug("DataPDUInOrder: %s\n",
(sess_ops->DataPDUInOrder) ? "Yes" : "No");
pr_debug("DataSequenceInOrder: %s\n",
(sess_ops->DataSequenceInOrder) ? "Yes" : "No");
pr_debug("ErrorRecoveryLevel: %hu\n",
sess_ops->ErrorRecoveryLevel);
pr_debug("SessionType: %s\n", (sess_ops->SessionType) ?
"Discovery" : "Normal");
}
void iscsi_print_params(struct iscsi_param_list *param_list)
{
struct iscsi_param *param;
list_for_each_entry(param, &param_list->param_list, p_list)
pr_debug("%s: %s\n", param->name, param->value);
}
static struct iscsi_param *iscsi_set_default_param(struct iscsi_param_list *param_list,
char *name, char *value, u8 phase, u8 scope, u8 sender,
u16 type_range, u8 use)
{
struct iscsi_param *param = NULL;
param = kzalloc(sizeof(struct iscsi_param), GFP_KERNEL);
if (!param) {
pr_err("Unable to allocate memory for parameter.\n");
goto out;
}
INIT_LIST_HEAD(&param->p_list);
param->name = kzalloc(strlen(name) + 1, GFP_KERNEL);
if (!param->name) {
pr_err("Unable to allocate memory for parameter name.\n");
goto out;
}
param->value = kzalloc(strlen(value) + 1, GFP_KERNEL);
if (!param->value) {
pr_err("Unable to allocate memory for parameter value.\n");
goto out;
}
memcpy(param->name, name, strlen(name));
param->name[strlen(name)] = '\0';
memcpy(param->value, value, strlen(value));
param->value[strlen(value)] = '\0';
param->phase = phase;
param->scope = scope;
param->sender = sender;
param->use = use;
param->type_range = type_range;
switch (param->type_range) {
case TYPERANGE_BOOL_AND:
param->type = TYPE_BOOL_AND;
break;
case TYPERANGE_BOOL_OR:
param->type = TYPE_BOOL_OR;
break;
case TYPERANGE_0_TO_2:
case TYPERANGE_0_TO_3600:
case TYPERANGE_0_TO_32767:
case TYPERANGE_0_TO_65535:
case TYPERANGE_1_TO_65535:
case TYPERANGE_2_TO_3600:
case TYPERANGE_512_TO_16777215:
param->type = TYPE_NUMBER;
break;
case TYPERANGE_AUTH:
case TYPERANGE_DIGEST:
param->type = TYPE_VALUE_LIST | TYPE_STRING;
break;
case TYPERANGE_MARKINT:
param->type = TYPE_NUMBER_RANGE;
param->type_range |= TYPERANGE_1_TO_65535;
break;
case TYPERANGE_ISCSINAME:
case TYPERANGE_SESSIONTYPE:
case TYPERANGE_TARGETADDRESS:
case TYPERANGE_UTF8:
param->type = TYPE_STRING;
break;
default:
pr_err("Unknown type_range 0x%02x\n",
param->type_range);
goto out;
}
list_add_tail(&param->p_list, &param_list->param_list);
return param;
out:
if (param) {
kfree(param->value);
kfree(param->name);
kfree(param);
}
return NULL;
}
/* #warning Add extension keys */
int iscsi_create_default_params(struct iscsi_param_list **param_list_ptr)
{
struct iscsi_param *param = NULL;
struct iscsi_param_list *pl;
pl = kzalloc(sizeof(struct iscsi_param_list), GFP_KERNEL);
if (!pl) {
pr_err("Unable to allocate memory for"
" struct iscsi_param_list.\n");
return -1 ;
}
INIT_LIST_HEAD(&pl->param_list);
INIT_LIST_HEAD(&pl->extra_response_list);
/*
* The format for setting the initial parameter definitions are:
*
* Parameter name:
* Initial value:
* Allowable phase:
* Scope:
* Allowable senders:
* Typerange:
* Use:
*/
param = iscsi_set_default_param(pl, AUTHMETHOD, INITIAL_AUTHMETHOD,
PHASE_SECURITY, SCOPE_CONNECTION_ONLY, SENDER_BOTH,
TYPERANGE_AUTH, USE_INITIAL_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, HEADERDIGEST, INITIAL_HEADERDIGEST,
PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH,
TYPERANGE_DIGEST, USE_INITIAL_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, DATADIGEST, INITIAL_DATADIGEST,
PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH,
TYPERANGE_DIGEST, USE_INITIAL_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, MAXCONNECTIONS,
INITIAL_MAXCONNECTIONS, PHASE_OPERATIONAL,
SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_1_TO_65535, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, SENDTARGETS, INITIAL_SENDTARGETS,
PHASE_FFP0, SCOPE_SESSION_WIDE, SENDER_INITIATOR,
TYPERANGE_UTF8, 0);
if (!param)
goto out;
param = iscsi_set_default_param(pl, TARGETNAME, INITIAL_TARGETNAME,
PHASE_DECLARATIVE, SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_ISCSINAME, USE_ALL);
if (!param)
goto out;
param = iscsi_set_default_param(pl, INITIATORNAME,
INITIAL_INITIATORNAME, PHASE_DECLARATIVE,
SCOPE_SESSION_WIDE, SENDER_INITIATOR,
TYPERANGE_ISCSINAME, USE_INITIAL_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, TARGETALIAS, INITIAL_TARGETALIAS,
PHASE_DECLARATIVE, SCOPE_SESSION_WIDE, SENDER_TARGET,
TYPERANGE_UTF8, USE_ALL);
if (!param)
goto out;
param = iscsi_set_default_param(pl, INITIATORALIAS,
INITIAL_INITIATORALIAS, PHASE_DECLARATIVE,
SCOPE_SESSION_WIDE, SENDER_INITIATOR, TYPERANGE_UTF8,
USE_ALL);
if (!param)
goto out;
param = iscsi_set_default_param(pl, TARGETADDRESS,
INITIAL_TARGETADDRESS, PHASE_DECLARATIVE,
SCOPE_SESSION_WIDE, SENDER_TARGET,
TYPERANGE_TARGETADDRESS, USE_ALL);
if (!param)
goto out;
param = iscsi_set_default_param(pl, TARGETPORTALGROUPTAG,
INITIAL_TARGETPORTALGROUPTAG,
PHASE_DECLARATIVE, SCOPE_SESSION_WIDE, SENDER_TARGET,
TYPERANGE_0_TO_65535, USE_INITIAL_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, INITIALR2T, INITIAL_INITIALR2T,
PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_BOOL_OR, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, IMMEDIATEDATA,
INITIAL_IMMEDIATEDATA, PHASE_OPERATIONAL,
SCOPE_SESSION_WIDE, SENDER_BOTH, TYPERANGE_BOOL_AND,
USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, MAXRECVDATASEGMENTLENGTH,
INITIAL_MAXRECVDATASEGMENTLENGTH,
PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH,
TYPERANGE_512_TO_16777215, USE_ALL);
if (!param)
goto out;
param = iscsi_set_default_param(pl, MAXBURSTLENGTH,
INITIAL_MAXBURSTLENGTH, PHASE_OPERATIONAL,
SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_512_TO_16777215, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, FIRSTBURSTLENGTH,
INITIAL_FIRSTBURSTLENGTH,
PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_512_TO_16777215, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, DEFAULTTIME2WAIT,
INITIAL_DEFAULTTIME2WAIT,
PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_0_TO_3600, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, DEFAULTTIME2RETAIN,
INITIAL_DEFAULTTIME2RETAIN,
PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_0_TO_3600, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, MAXOUTSTANDINGR2T,
INITIAL_MAXOUTSTANDINGR2T,
PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_1_TO_65535, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, DATAPDUINORDER,
INITIAL_DATAPDUINORDER, PHASE_OPERATIONAL,
SCOPE_SESSION_WIDE, SENDER_BOTH, TYPERANGE_BOOL_OR,
USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, DATASEQUENCEINORDER,
INITIAL_DATASEQUENCEINORDER,
PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_BOOL_OR, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, ERRORRECOVERYLEVEL,
INITIAL_ERRORRECOVERYLEVEL,
PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH,
TYPERANGE_0_TO_2, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, SESSIONTYPE, INITIAL_SESSIONTYPE,
PHASE_DECLARATIVE, SCOPE_SESSION_WIDE, SENDER_INITIATOR,
TYPERANGE_SESSIONTYPE, USE_LEADING_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, IFMARKER, INITIAL_IFMARKER,
PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH,
TYPERANGE_BOOL_AND, USE_INITIAL_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, OFMARKER, INITIAL_OFMARKER,
PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH,
TYPERANGE_BOOL_AND, USE_INITIAL_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, IFMARKINT, INITIAL_IFMARKINT,
PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH,
TYPERANGE_MARKINT, USE_INITIAL_ONLY);
if (!param)
goto out;
param = iscsi_set_default_param(pl, OFMARKINT, INITIAL_OFMARKINT,
PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH,
TYPERANGE_MARKINT, USE_INITIAL_ONLY);
if (!param)
goto out;
*param_list_ptr = pl;
return 0;
out:
iscsi_release_param_list(pl);
return -1;
}
int iscsi_set_keys_to_negotiate(
int sessiontype,
struct iscsi_param_list *param_list)
{
struct iscsi_param *param;
list_for_each_entry(param, &param_list->param_list, p_list) {
param->state = 0;
if (!strcmp(param->name, AUTHMETHOD)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, HEADERDIGEST)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, DATADIGEST)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, MAXCONNECTIONS)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, TARGETNAME)) {
continue;
} else if (!strcmp(param->name, INITIATORNAME)) {
continue;
} else if (!strcmp(param->name, TARGETALIAS)) {
if (param->value)
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, INITIATORALIAS)) {
continue;
} else if (!strcmp(param->name, TARGETPORTALGROUPTAG)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, INITIALR2T)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, IMMEDIATEDATA)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, MAXRECVDATASEGMENTLENGTH)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, MAXBURSTLENGTH)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, FIRSTBURSTLENGTH)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, DEFAULTTIME2WAIT)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, DEFAULTTIME2RETAIN)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, MAXOUTSTANDINGR2T)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, DATAPDUINORDER)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, DATASEQUENCEINORDER)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, ERRORRECOVERYLEVEL)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, SESSIONTYPE)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, IFMARKER)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, OFMARKER)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, IFMARKINT)) {
SET_PSTATE_NEGOTIATE(param);
} else if (!strcmp(param->name, OFMARKINT)) {
SET_PSTATE_NEGOTIATE(param);
}
}
return 0;
}
int iscsi_set_keys_irrelevant_for_discovery(
struct iscsi_param_list *param_list)
{
struct iscsi_param *param;
list_for_each_entry(param, &param_list->param_list, p_list) {
if (!strcmp(param->name, MAXCONNECTIONS))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, INITIALR2T))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, IMMEDIATEDATA))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, MAXBURSTLENGTH))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, FIRSTBURSTLENGTH))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, MAXOUTSTANDINGR2T))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, DATAPDUINORDER))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, DATASEQUENCEINORDER))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, ERRORRECOVERYLEVEL))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, DEFAULTTIME2WAIT))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, DEFAULTTIME2RETAIN))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, IFMARKER))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, OFMARKER))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, IFMARKINT))
param->state &= ~PSTATE_NEGOTIATE;
else if (!strcmp(param->name, OFMARKINT))
param->state &= ~PSTATE_NEGOTIATE;
}
return 0;
}
int iscsi_copy_param_list(
struct iscsi_param_list **dst_param_list,
struct iscsi_param_list *src_param_list,
int leading)
{
struct iscsi_param *new_param = NULL, *param = NULL;
struct iscsi_param_list *param_list = NULL;
param_list = kzalloc(sizeof(struct iscsi_param_list), GFP_KERNEL);
if (!param_list) {
pr_err("Unable to allocate memory for"
" struct iscsi_param_list.\n");
goto err_out;
}
INIT_LIST_HEAD(&param_list->param_list);
INIT_LIST_HEAD(&param_list->extra_response_list);
list_for_each_entry(param, &src_param_list->param_list, p_list) {
if (!leading && (param->scope & SCOPE_SESSION_WIDE)) {
if ((strcmp(param->name, "TargetName") != 0) &&
(strcmp(param->name, "InitiatorName") != 0) &&
(strcmp(param->name, "TargetPortalGroupTag") != 0))
continue;
}
new_param = kzalloc(sizeof(struct iscsi_param), GFP_KERNEL);
if (!new_param) {
pr_err("Unable to allocate memory for"
" struct iscsi_param.\n");
goto err_out;
}
new_param->set_param = param->set_param;
new_param->phase = param->phase;
new_param->scope = param->scope;
new_param->sender = param->sender;
new_param->type = param->type;
new_param->use = param->use;
new_param->type_range = param->type_range;
new_param->name = kzalloc(strlen(param->name) + 1, GFP_KERNEL);
if (!new_param->name) {
pr_err("Unable to allocate memory for"
" parameter name.\n");
goto err_out;
}
new_param->value = kzalloc(strlen(param->value) + 1,
GFP_KERNEL);
if (!new_param->value) {
pr_err("Unable to allocate memory for"
" parameter value.\n");
goto err_out;
}
memcpy(new_param->name, param->name, strlen(param->name));
new_param->name[strlen(param->name)] = '\0';
memcpy(new_param->value, param->value, strlen(param->value));
new_param->value[strlen(param->value)] = '\0';
list_add_tail(&new_param->p_list, &param_list->param_list);
}
if (!list_empty(&param_list->param_list))
*dst_param_list = param_list;
else {
pr_err("No parameters allocated.\n");
goto err_out;
}
return 0;
err_out:
iscsi_release_param_list(param_list);
return -1;
}
static void iscsi_release_extra_responses(struct iscsi_param_list *param_list)
{
struct iscsi_extra_response *er, *er_tmp;
list_for_each_entry_safe(er, er_tmp, &param_list->extra_response_list,
er_list) {
list_del(&er->er_list);
kfree(er);
}
}
void iscsi_release_param_list(struct iscsi_param_list *param_list)
{
struct iscsi_param *param, *param_tmp;
list_for_each_entry_safe(param, param_tmp, &param_list->param_list,
p_list) {
list_del(&param->p_list);
kfree(param->name);
param->name = NULL;
kfree(param->value);
param->value = NULL;
kfree(param);
param = NULL;
}
iscsi_release_extra_responses(param_list);
kfree(param_list);
}
struct iscsi_param *iscsi_find_param_from_key(
char *key,
struct iscsi_param_list *param_list)
{
struct iscsi_param *param;
if (!key || !param_list) {
pr_err("Key or parameter list pointer is NULL.\n");
return NULL;
}
list_for_each_entry(param, &param_list->param_list, p_list) {
if (!strcmp(key, param->name))
return param;
}
pr_err("Unable to locate key \"%s\".\n", key);
return NULL;
}
int iscsi_extract_key_value(char *textbuf, char **key, char **value)
{
*value = strchr(textbuf, '=');
if (!*value) {
pr_err("Unable to locate \"=\" seperator for key,"
" ignoring request.\n");
return -1;
}
*key = textbuf;
**value = '\0';
*value = *value + 1;
return 0;
}
int iscsi_update_param_value(struct iscsi_param *param, char *value)
{
kfree(param->value);
param->value = kzalloc(strlen(value) + 1, GFP_KERNEL);
if (!param->value) {
pr_err("Unable to allocate memory for value.\n");
return -1;
}
memcpy(param->value, value, strlen(value));
param->value[strlen(value)] = '\0';
pr_debug("iSCSI Parameter updated to %s=%s\n",
param->name, param->value);
return 0;
}
static int iscsi_add_notunderstood_response(
char *key,
char *value,
struct iscsi_param_list *param_list)
{
struct iscsi_extra_response *extra_response;
if (strlen(value) > VALUE_MAXLEN) {
pr_err("Value for notunderstood key \"%s\" exceeds %d,"
" protocol error.\n", key, VALUE_MAXLEN);
return -1;
}
extra_response = kzalloc(sizeof(struct iscsi_extra_response), GFP_KERNEL);
if (!extra_response) {
pr_err("Unable to allocate memory for"
" struct iscsi_extra_response.\n");
return -1;
}
INIT_LIST_HEAD(&extra_response->er_list);
strncpy(extra_response->key, key, strlen(key) + 1);
strncpy(extra_response->value, NOTUNDERSTOOD,
strlen(NOTUNDERSTOOD) + 1);
list_add_tail(&extra_response->er_list,
&param_list->extra_response_list);
return 0;
}
static int iscsi_check_for_auth_key(char *key)
{
/*
* RFC 1994
*/
if (!strcmp(key, "CHAP_A") || !strcmp(key, "CHAP_I") ||
!strcmp(key, "CHAP_C") || !strcmp(key, "CHAP_N") ||
!strcmp(key, "CHAP_R"))
return 1;
/*
* RFC 2945
*/
if (!strcmp(key, "SRP_U") || !strcmp(key, "SRP_N") ||
!strcmp(key, "SRP_g") || !strcmp(key, "SRP_s") ||
!strcmp(key, "SRP_A") || !strcmp(key, "SRP_B") ||
!strcmp(key, "SRP_M") || !strcmp(key, "SRP_HM"))
return 1;
return 0;
}
static void iscsi_check_proposer_for_optional_reply(struct iscsi_param *param)
{
if (IS_TYPE_BOOL_AND(param)) {
if (!strcmp(param->value, NO))
SET_PSTATE_REPLY_OPTIONAL(param);
} else if (IS_TYPE_BOOL_OR(param)) {
if (!strcmp(param->value, YES))
SET_PSTATE_REPLY_OPTIONAL(param);
/*
* Required for gPXE iSCSI boot client
*/
if (!strcmp(param->name, IMMEDIATEDATA))
SET_PSTATE_REPLY_OPTIONAL(param);
} else if (IS_TYPE_NUMBER(param)) {
if (!strcmp(param->name, MAXRECVDATASEGMENTLENGTH))
SET_PSTATE_REPLY_OPTIONAL(param);
/*
* The GlobalSAN iSCSI Initiator for MacOSX does
* not respond to MaxBurstLength, FirstBurstLength,
* DefaultTime2Wait or DefaultTime2Retain parameter keys.
* So, we set them to 'reply optional' here, and assume the
* the defaults from iscsi_parameters.h if the initiator
* is not RFC compliant and the keys are not negotiated.
*/
if (!strcmp(param->name, MAXBURSTLENGTH))
SET_PSTATE_REPLY_OPTIONAL(param);
if (!strcmp(param->name, FIRSTBURSTLENGTH))
SET_PSTATE_REPLY_OPTIONAL(param);
if (!strcmp(param->name, DEFAULTTIME2WAIT))
SET_PSTATE_REPLY_OPTIONAL(param);
if (!strcmp(param->name, DEFAULTTIME2RETAIN))
SET_PSTATE_REPLY_OPTIONAL(param);
/*
* Required for gPXE iSCSI boot client
*/
if (!strcmp(param->name, MAXCONNECTIONS))
SET_PSTATE_REPLY_OPTIONAL(param);
} else if (IS_PHASE_DECLARATIVE(param))
SET_PSTATE_REPLY_OPTIONAL(param);
}
static int iscsi_check_boolean_value(struct iscsi_param *param, char *value)
{
if (strcmp(value, YES) && strcmp(value, NO)) {
pr_err("Illegal value for \"%s\", must be either"
" \"%s\" or \"%s\".\n", param->name, YES, NO);
return -1;
}
return 0;
}
static int iscsi_check_numerical_value(struct iscsi_param *param, char *value_ptr)
{
char *tmpptr;
int value = 0;
value = simple_strtoul(value_ptr, &tmpptr, 0);
/* #warning FIXME: Fix this */
#if 0
if (strspn(endptr, WHITE_SPACE) != strlen(endptr)) {
pr_err("Illegal value \"%s\" for \"%s\".\n",
value, param->name);
return -1;
}
#endif
if (IS_TYPERANGE_0_TO_2(param)) {
if ((value < 0) || (value > 2)) {
pr_err("Illegal value for \"%s\", must be"
" between 0 and 2.\n", param->name);
return -1;
}
return 0;
}
if (IS_TYPERANGE_0_TO_3600(param)) {
if ((value < 0) || (value > 3600)) {
pr_err("Illegal value for \"%s\", must be"
" between 0 and 3600.\n", param->name);
return -1;
}
return 0;
}
if (IS_TYPERANGE_0_TO_32767(param)) {
if ((value < 0) || (value > 32767)) {
pr_err("Illegal value for \"%s\", must be"
" between 0 and 32767.\n", param->name);
return -1;
}
return 0;
}
if (IS_TYPERANGE_0_TO_65535(param)) {
if ((value < 0) || (value > 65535)) {
pr_err("Illegal value for \"%s\", must be"
" between 0 and 65535.\n", param->name);
return -1;
}
return 0;
}
if (IS_TYPERANGE_1_TO_65535(param)) {
if ((value < 1) || (value > 65535)) {
pr_err("Illegal value for \"%s\", must be"
" between 1 and 65535.\n", param->name);
return -1;
}
return 0;
}
if (IS_TYPERANGE_2_TO_3600(param)) {
if ((value < 2) || (value > 3600)) {
pr_err("Illegal value for \"%s\", must be"
" between 2 and 3600.\n", param->name);
return -1;
}
return 0;
}
if (IS_TYPERANGE_512_TO_16777215(param)) {
if ((value < 512) || (value > 16777215)) {
pr_err("Illegal value for \"%s\", must be"
" between 512 and 16777215.\n", param->name);
return -1;
}
return 0;
}
return 0;
}
static int iscsi_check_numerical_range_value(struct iscsi_param *param, char *value)
{
char *left_val_ptr = NULL, *right_val_ptr = NULL;
char *tilde_ptr = NULL, *tmp_ptr = NULL;
u32 left_val, right_val, local_left_val, local_right_val;
if (strcmp(param->name, IFMARKINT) &&
strcmp(param->name, OFMARKINT)) {
pr_err("Only parameters \"%s\" or \"%s\" may contain a"
" numerical range value.\n", IFMARKINT, OFMARKINT);
return -1;
}
if (IS_PSTATE_PROPOSER(param))
return 0;
tilde_ptr = strchr(value, '~');
if (!tilde_ptr) {
pr_err("Unable to locate numerical range indicator"
" \"~\" for \"%s\".\n", param->name);
return -1;
}
*tilde_ptr = '\0';
left_val_ptr = value;
right_val_ptr = value + strlen(left_val_ptr) + 1;
if (iscsi_check_numerical_value(param, left_val_ptr) < 0)
return -1;
if (iscsi_check_numerical_value(param, right_val_ptr) < 0)
return -1;
left_val = simple_strtoul(left_val_ptr, &tmp_ptr, 0);
right_val = simple_strtoul(right_val_ptr, &tmp_ptr, 0);
*tilde_ptr = '~';
if (right_val < left_val) {
pr_err("Numerical range for parameter \"%s\" contains"
" a right value which is less than the left.\n",
param->name);
return -1;
}
/*
* For now, enforce reasonable defaults for [I,O]FMarkInt.
*/
tilde_ptr = strchr(param->value, '~');
if (!tilde_ptr) {
pr_err("Unable to locate numerical range indicator"
" \"~\" for \"%s\".\n", param->name);
return -1;
}
*tilde_ptr = '\0';
left_val_ptr = param->value;
right_val_ptr = param->value + strlen(left_val_ptr) + 1;
local_left_val = simple_strtoul(left_val_ptr, &tmp_ptr, 0);
local_right_val = simple_strtoul(right_val_ptr, &tmp_ptr, 0);
*tilde_ptr = '~';
if (param->set_param) {
if ((left_val < local_left_val) ||
(right_val < local_left_val)) {
pr_err("Passed value range \"%u~%u\" is below"
" minimum left value \"%u\" for key \"%s\","
" rejecting.\n", left_val, right_val,
local_left_val, param->name);
return -1;
}
} else {
if ((left_val < local_left_val) &&
(right_val < local_left_val)) {
pr_err("Received value range \"%u~%u\" is"
" below minimum left value \"%u\" for key"
" \"%s\", rejecting.\n", left_val, right_val,
local_left_val, param->name);
SET_PSTATE_REJECT(param);
if (iscsi_update_param_value(param, REJECT) < 0)
return -1;
}
}
return 0;
}
static int iscsi_check_string_or_list_value(struct iscsi_param *param, char *value)
{
if (IS_PSTATE_PROPOSER(param))
return 0;
if (IS_TYPERANGE_AUTH_PARAM(param)) {
if (strcmp(value, KRB5) && strcmp(value, SPKM1) &&
strcmp(value, SPKM2) && strcmp(value, SRP) &&
strcmp(value, CHAP) && strcmp(value, NONE)) {
pr_err("Illegal value for \"%s\", must be"
" \"%s\", \"%s\", \"%s\", \"%s\", \"%s\""
" or \"%s\".\n", param->name, KRB5,
SPKM1, SPKM2, SRP, CHAP, NONE);
return -1;
}
}
if (IS_TYPERANGE_DIGEST_PARAM(param)) {
if (strcmp(value, CRC32C) && strcmp(value, NONE)) {
pr_err("Illegal value for \"%s\", must be"
" \"%s\" or \"%s\".\n", param->name,
CRC32C, NONE);
return -1;
}
}
if (IS_TYPERANGE_SESSIONTYPE(param)) {
if (strcmp(value, DISCOVERY) && strcmp(value, NORMAL)) {
pr_err("Illegal value for \"%s\", must be"
" \"%s\" or \"%s\".\n", param->name,
DISCOVERY, NORMAL);
return -1;
}
}
return 0;
}
/*
* This function is used to pick a value range number, currently just
* returns the lesser of both right values.
*/
static char *iscsi_get_value_from_number_range(
struct iscsi_param *param,
char *value)
{
char *end_ptr, *tilde_ptr1 = NULL, *tilde_ptr2 = NULL;
u32 acceptor_right_value, proposer_right_value;
tilde_ptr1 = strchr(value, '~');
if (!tilde_ptr1)
return NULL;
*tilde_ptr1++ = '\0';
proposer_right_value = simple_strtoul(tilde_ptr1, &end_ptr, 0);
tilde_ptr2 = strchr(param->value, '~');
if (!tilde_ptr2)
return NULL;
*tilde_ptr2++ = '\0';
acceptor_right_value = simple_strtoul(tilde_ptr2, &end_ptr, 0);
return (acceptor_right_value >= proposer_right_value) ?
tilde_ptr1 : tilde_ptr2;
}
static char *iscsi_check_valuelist_for_support(
struct iscsi_param *param,
char *value)
{
char *tmp1 = NULL, *tmp2 = NULL;
char *acceptor_values = NULL, *proposer_values = NULL;
acceptor_values = param->value;
proposer_values = value;
do {
if (!proposer_values)
return NULL;
tmp1 = strchr(proposer_values, ',');
if (tmp1)
*tmp1 = '\0';
acceptor_values = param->value;
do {
if (!acceptor_values) {
if (tmp1)
*tmp1 = ',';
return NULL;
}
tmp2 = strchr(acceptor_values, ',');
if (tmp2)
*tmp2 = '\0';
if (!acceptor_values || !proposer_values) {
if (tmp1)
*tmp1 = ',';
if (tmp2)
*tmp2 = ',';
return NULL;
}
if (!strcmp(acceptor_values, proposer_values)) {
if (tmp2)
*tmp2 = ',';
goto out;
}
if (tmp2)
*tmp2++ = ',';
acceptor_values = tmp2;
if (!acceptor_values)
break;
} while (acceptor_values);
if (tmp1)
*tmp1++ = ',';
proposer_values = tmp1;
} while (proposer_values);
out:
return proposer_values;
}
static int iscsi_check_acceptor_state(struct iscsi_param *param, char *value)
{
u8 acceptor_boolean_value = 0, proposer_boolean_value = 0;
char *negoitated_value = NULL;
if (IS_PSTATE_ACCEPTOR(param)) {
pr_err("Received key \"%s\" twice, protocol error.\n",
param->name);
return -1;
}
if (IS_PSTATE_REJECT(param))
return 0;
if (IS_TYPE_BOOL_AND(param)) {
if (!strcmp(value, YES))
proposer_boolean_value = 1;
if (!strcmp(param->value, YES))
acceptor_boolean_value = 1;
if (acceptor_boolean_value && proposer_boolean_value)
do {} while (0);
else {
if (iscsi_update_param_value(param, NO) < 0)
return -1;
if (!proposer_boolean_value)
SET_PSTATE_REPLY_OPTIONAL(param);
}
} else if (IS_TYPE_BOOL_OR(param)) {
if (!strcmp(value, YES))
proposer_boolean_value = 1;
if (!strcmp(param->value, YES))
acceptor_boolean_value = 1;
if (acceptor_boolean_value || proposer_boolean_value) {
if (iscsi_update_param_value(param, YES) < 0)
return -1;
if (proposer_boolean_value)
SET_PSTATE_REPLY_OPTIONAL(param);
}
} else if (IS_TYPE_NUMBER(param)) {
char *tmpptr, buf[10];
u32 acceptor_value = simple_strtoul(param->value, &tmpptr, 0);
u32 proposer_value = simple_strtoul(value, &tmpptr, 0);
memset(buf, 0, 10);
if (!strcmp(param->name, MAXCONNECTIONS) ||
!strcmp(param->name, MAXBURSTLENGTH) ||
!strcmp(param->name, FIRSTBURSTLENGTH) ||
!strcmp(param->name, MAXOUTSTANDINGR2T) ||
!strcmp(param->name, DEFAULTTIME2RETAIN) ||
!strcmp(param->name, ERRORRECOVERYLEVEL)) {
if (proposer_value > acceptor_value) {
sprintf(buf, "%u", acceptor_value);
if (iscsi_update_param_value(param,
&buf[0]) < 0)
return -1;
} else {
if (iscsi_update_param_value(param, value) < 0)
return -1;
}
} else if (!strcmp(param->name, DEFAULTTIME2WAIT)) {
if (acceptor_value > proposer_value) {
sprintf(buf, "%u", acceptor_value);
if (iscsi_update_param_value(param,
&buf[0]) < 0)
return -1;
} else {
if (iscsi_update_param_value(param, value) < 0)
return -1;
}
} else {
if (iscsi_update_param_value(param, value) < 0)
return -1;
}
if (!strcmp(param->name, MAXRECVDATASEGMENTLENGTH))
SET_PSTATE_REPLY_OPTIONAL(param);
} else if (IS_TYPE_NUMBER_RANGE(param)) {
negoitated_value = iscsi_get_value_from_number_range(
param, value);
if (!negoitated_value)
return -1;
if (iscsi_update_param_value(param, negoitated_value) < 0)
return -1;
} else if (IS_TYPE_VALUE_LIST(param)) {
negoitated_value = iscsi_check_valuelist_for_support(
param, value);
if (!negoitated_value) {
pr_err("Proposer's value list \"%s\" contains"
" no valid values from Acceptor's value list"
" \"%s\".\n", value, param->value);
return -1;
}
if (iscsi_update_param_value(param, negoitated_value) < 0)
return -1;
} else if (IS_PHASE_DECLARATIVE(param)) {
if (iscsi_update_param_value(param, value) < 0)
return -1;
SET_PSTATE_REPLY_OPTIONAL(param);
}
return 0;
}
static int iscsi_check_proposer_state(struct iscsi_param *param, char *value)
{
if (IS_PSTATE_RESPONSE_GOT(param)) {
pr_err("Received key \"%s\" twice, protocol error.\n",
param->name);
return -1;
}
if (IS_TYPE_NUMBER_RANGE(param)) {
u32 left_val = 0, right_val = 0, recieved_value = 0;
char *left_val_ptr = NULL, *right_val_ptr = NULL;
char *tilde_ptr = NULL, *tmp_ptr = NULL;
if (!strcmp(value, IRRELEVANT) || !strcmp(value, REJECT)) {
if (iscsi_update_param_value(param, value) < 0)
return -1;
return 0;
}
tilde_ptr = strchr(value, '~');
if (tilde_ptr) {
pr_err("Illegal \"~\" in response for \"%s\".\n",
param->name);
return -1;
}
tilde_ptr = strchr(param->value, '~');
if (!tilde_ptr) {
pr_err("Unable to locate numerical range"
" indicator \"~\" for \"%s\".\n", param->name);
return -1;
}
*tilde_ptr = '\0';
left_val_ptr = param->value;
right_val_ptr = param->value + strlen(left_val_ptr) + 1;
left_val = simple_strtoul(left_val_ptr, &tmp_ptr, 0);
right_val = simple_strtoul(right_val_ptr, &tmp_ptr, 0);
recieved_value = simple_strtoul(value, &tmp_ptr, 0);
*tilde_ptr = '~';
if ((recieved_value < left_val) ||
(recieved_value > right_val)) {
pr_err("Illegal response \"%s=%u\", value must"
" be between %u and %u.\n", param->name,
recieved_value, left_val, right_val);
return -1;
}
} else if (IS_TYPE_VALUE_LIST(param)) {
char *comma_ptr = NULL, *tmp_ptr = NULL;
comma_ptr = strchr(value, ',');
if (comma_ptr) {
pr_err("Illegal \",\" in response for \"%s\".\n",
param->name);
return -1;
}
tmp_ptr = iscsi_check_valuelist_for_support(param, value);
if (!tmp_ptr)
return -1;
}
if (iscsi_update_param_value(param, value) < 0)
return -1;
return 0;
}
static int iscsi_check_value(struct iscsi_param *param, char *value)
{
char *comma_ptr = NULL;
if (!strcmp(value, REJECT)) {
if (!strcmp(param->name, IFMARKINT) ||
!strcmp(param->name, OFMARKINT)) {
/*
* Reject is not fatal for [I,O]FMarkInt, and causes
* [I,O]FMarker to be reset to No. (See iSCSI v20 A.3.2)
*/
SET_PSTATE_REJECT(param);
return 0;
}
pr_err("Received %s=%s\n", param->name, value);
return -1;
}
if (!strcmp(value, IRRELEVANT)) {
pr_debug("Received %s=%s\n", param->name, value);
SET_PSTATE_IRRELEVANT(param);
return 0;
}
if (!strcmp(value, NOTUNDERSTOOD)) {
if (!IS_PSTATE_PROPOSER(param)) {
pr_err("Received illegal offer %s=%s\n",
param->name, value);
return -1;
}
/* #warning FIXME: Add check for X-ExtensionKey here */
pr_err("Standard iSCSI key \"%s\" cannot be answered"
" with \"%s\", protocol error.\n", param->name, value);
return -1;
}
do {
comma_ptr = NULL;
comma_ptr = strchr(value, ',');
if (comma_ptr && !IS_TYPE_VALUE_LIST(param)) {
pr_err("Detected value seperator \",\", but"
" key \"%s\" does not allow a value list,"
" protocol error.\n", param->name);
return -1;
}
if (comma_ptr)
*comma_ptr = '\0';
if (strlen(value) > VALUE_MAXLEN) {
pr_err("Value for key \"%s\" exceeds %d,"
" protocol error.\n", param->name,
VALUE_MAXLEN);
return -1;
}
if (IS_TYPE_BOOL_AND(param) || IS_TYPE_BOOL_OR(param)) {
if (iscsi_check_boolean_value(param, value) < 0)
return -1;
} else if (IS_TYPE_NUMBER(param)) {
if (iscsi_check_numerical_value(param, value) < 0)
return -1;
} else if (IS_TYPE_NUMBER_RANGE(param)) {
if (iscsi_check_numerical_range_value(param, value) < 0)
return -1;
} else if (IS_TYPE_STRING(param) || IS_TYPE_VALUE_LIST(param)) {
if (iscsi_check_string_or_list_value(param, value) < 0)
return -1;
} else {
pr_err("Huh? 0x%02x\n", param->type);
return -1;
}
if (comma_ptr)
*comma_ptr++ = ',';
value = comma_ptr;
} while (value);
return 0;
}
static struct iscsi_param *__iscsi_check_key(
char *key,
int sender,
struct iscsi_param_list *param_list)
{
struct iscsi_param *param;
if (strlen(key) > KEY_MAXLEN) {
pr_err("Length of key name \"%s\" exceeds %d.\n",
key, KEY_MAXLEN);
return NULL;
}
param = iscsi_find_param_from_key(key, param_list);
if (!param)
return NULL;
if ((sender & SENDER_INITIATOR) && !IS_SENDER_INITIATOR(param)) {
pr_err("Key \"%s\" may not be sent to %s,"
" protocol error.\n", param->name,
(sender & SENDER_RECEIVER) ? "target" : "initiator");
return NULL;
}
if ((sender & SENDER_TARGET) && !IS_SENDER_TARGET(param)) {
pr_err("Key \"%s\" may not be sent to %s,"
" protocol error.\n", param->name,
(sender & SENDER_RECEIVER) ? "initiator" : "target");
return NULL;
}
return param;
}
static struct iscsi_param *iscsi_check_key(
char *key,
int phase,
int sender,
struct iscsi_param_list *param_list)
{
struct iscsi_param *param;
/*
* Key name length must not exceed 63 bytes. (See iSCSI v20 5.1)
*/
if (strlen(key) > KEY_MAXLEN) {
pr_err("Length of key name \"%s\" exceeds %d.\n",
key, KEY_MAXLEN);
return NULL;
}
param = iscsi_find_param_from_key(key, param_list);
if (!param)
return NULL;
if ((sender & SENDER_INITIATOR) && !IS_SENDER_INITIATOR(param)) {
pr_err("Key \"%s\" may not be sent to %s,"
" protocol error.\n", param->name,
(sender & SENDER_RECEIVER) ? "target" : "initiator");
return NULL;
}
if ((sender & SENDER_TARGET) && !IS_SENDER_TARGET(param)) {
pr_err("Key \"%s\" may not be sent to %s,"
" protocol error.\n", param->name,
(sender & SENDER_RECEIVER) ? "initiator" : "target");
return NULL;
}
if (IS_PSTATE_ACCEPTOR(param)) {
pr_err("Key \"%s\" received twice, protocol error.\n",
key);
return NULL;
}
if (!phase)
return param;
if (!(param->phase & phase)) {
pr_err("Key \"%s\" may not be negotiated during ",
param->name);
switch (phase) {
case PHASE_SECURITY:
pr_debug("Security phase.\n");
break;
case PHASE_OPERATIONAL:
pr_debug("Operational phase.\n");
default:
pr_debug("Unknown phase.\n");
}
return NULL;
}
return param;
}
static int iscsi_enforce_integrity_rules(
u8 phase,
struct iscsi_param_list *param_list)
{
char *tmpptr;
u8 DataSequenceInOrder = 0;
u8 ErrorRecoveryLevel = 0, SessionType = 0;
u8 IFMarker = 0, OFMarker = 0;
u8 IFMarkInt_Reject = 0, OFMarkInt_Reject = 0;
u32 FirstBurstLength = 0, MaxBurstLength = 0;
struct iscsi_param *param = NULL;
list_for_each_entry(param, &param_list->param_list, p_list) {
if (!(param->phase & phase))
continue;
if (!strcmp(param->name, SESSIONTYPE))
if (!strcmp(param->value, NORMAL))
SessionType = 1;
if (!strcmp(param->name, ERRORRECOVERYLEVEL))
ErrorRecoveryLevel = simple_strtoul(param->value,
&tmpptr, 0);
if (!strcmp(param->name, DATASEQUENCEINORDER))
if (!strcmp(param->value, YES))
DataSequenceInOrder = 1;
if (!strcmp(param->name, MAXBURSTLENGTH))
MaxBurstLength = simple_strtoul(param->value,
&tmpptr, 0);
if (!strcmp(param->name, IFMARKER))
if (!strcmp(param->value, YES))
IFMarker = 1;
if (!strcmp(param->name, OFMARKER))
if (!strcmp(param->value, YES))
OFMarker = 1;
if (!strcmp(param->name, IFMARKINT))
if (!strcmp(param->value, REJECT))
IFMarkInt_Reject = 1;
if (!strcmp(param->name, OFMARKINT))
if (!strcmp(param->value, REJECT))
OFMarkInt_Reject = 1;
}
list_for_each_entry(param, &param_list->param_list, p_list) {
if (!(param->phase & phase))
continue;
if (!SessionType && (!IS_PSTATE_ACCEPTOR(param) &&
(strcmp(param->name, IFMARKER) &&
strcmp(param->name, OFMARKER) &&
strcmp(param->name, IFMARKINT) &&
strcmp(param->name, OFMARKINT))))
continue;
if (!strcmp(param->name, MAXOUTSTANDINGR2T) &&
DataSequenceInOrder && (ErrorRecoveryLevel > 0)) {
if (strcmp(param->value, "1")) {
if (iscsi_update_param_value(param, "1") < 0)
return -1;
pr_debug("Reset \"%s\" to \"%s\".\n",
param->name, param->value);
}
}
if (!strcmp(param->name, MAXCONNECTIONS) && !SessionType) {
if (strcmp(param->value, "1")) {
if (iscsi_update_param_value(param, "1") < 0)
return -1;
pr_debug("Reset \"%s\" to \"%s\".\n",
param->name, param->value);
}
}
if (!strcmp(param->name, FIRSTBURSTLENGTH)) {
FirstBurstLength = simple_strtoul(param->value,
&tmpptr, 0);
if (FirstBurstLength > MaxBurstLength) {
char tmpbuf[10];
memset(tmpbuf, 0, 10);
sprintf(tmpbuf, "%u", MaxBurstLength);
if (iscsi_update_param_value(param, tmpbuf))
return -1;
pr_debug("Reset \"%s\" to \"%s\".\n",
param->name, param->value);
}
}
if (!strcmp(param->name, IFMARKER) && IFMarkInt_Reject) {
if (iscsi_update_param_value(param, NO) < 0)
return -1;
IFMarker = 0;
pr_debug("Reset \"%s\" to \"%s\".\n",
param->name, param->value);
}
if (!strcmp(param->name, OFMARKER) && OFMarkInt_Reject) {
if (iscsi_update_param_value(param, NO) < 0)
return -1;
OFMarker = 0;
pr_debug("Reset \"%s\" to \"%s\".\n",
param->name, param->value);
}
if (!strcmp(param->name, IFMARKINT) && !IFMarker) {
if (!strcmp(param->value, REJECT))
continue;
param->state &= ~PSTATE_NEGOTIATE;
if (iscsi_update_param_value(param, IRRELEVANT) < 0)
return -1;
pr_debug("Reset \"%s\" to \"%s\".\n",
param->name, param->value);
}
if (!strcmp(param->name, OFMARKINT) && !OFMarker) {
if (!strcmp(param->value, REJECT))
continue;
param->state &= ~PSTATE_NEGOTIATE;
if (iscsi_update_param_value(param, IRRELEVANT) < 0)
return -1;
pr_debug("Reset \"%s\" to \"%s\".\n",
param->name, param->value);
}
}
return 0;
}
int iscsi_decode_text_input(
u8 phase,
u8 sender,
char *textbuf,
u32 length,
struct iscsi_param_list *param_list)
{
char *tmpbuf, *start = NULL, *end = NULL;
tmpbuf = kzalloc(length + 1, GFP_KERNEL);
if (!tmpbuf) {
pr_err("Unable to allocate memory for tmpbuf.\n");
return -1;
}
memcpy(tmpbuf, textbuf, length);
tmpbuf[length] = '\0';
start = tmpbuf;
end = (start + length);
while (start < end) {
char *key, *value;
struct iscsi_param *param;
if (iscsi_extract_key_value(start, &key, &value) < 0) {
kfree(tmpbuf);
return -1;
}
pr_debug("Got key: %s=%s\n", key, value);
if (phase & PHASE_SECURITY) {
if (iscsi_check_for_auth_key(key) > 0) {
char *tmpptr = key + strlen(key);
*tmpptr = '=';
kfree(tmpbuf);
return 1;
}
}
param = iscsi_check_key(key, phase, sender, param_list);
if (!param) {
if (iscsi_add_notunderstood_response(key,
value, param_list) < 0) {
kfree(tmpbuf);
return -1;
}
start += strlen(key) + strlen(value) + 2;
continue;
}
if (iscsi_check_value(param, value) < 0) {
kfree(tmpbuf);
return -1;
}
start += strlen(key) + strlen(value) + 2;
if (IS_PSTATE_PROPOSER(param)) {
if (iscsi_check_proposer_state(param, value) < 0) {
kfree(tmpbuf);
return -1;
}
SET_PSTATE_RESPONSE_GOT(param);
} else {
if (iscsi_check_acceptor_state(param, value) < 0) {
kfree(tmpbuf);
return -1;
}
SET_PSTATE_ACCEPTOR(param);
}
}
kfree(tmpbuf);
return 0;
}
int iscsi_encode_text_output(
u8 phase,
u8 sender,
char *textbuf,
u32 *length,
struct iscsi_param_list *param_list)
{
char *output_buf = NULL;
struct iscsi_extra_response *er;
struct iscsi_param *param;
output_buf = textbuf + *length;
if (iscsi_enforce_integrity_rules(phase, param_list) < 0)
return -1;
list_for_each_entry(param, &param_list->param_list, p_list) {
if (!(param->sender & sender))
continue;
if (IS_PSTATE_ACCEPTOR(param) &&
!IS_PSTATE_RESPONSE_SENT(param) &&
!IS_PSTATE_REPLY_OPTIONAL(param) &&
(param->phase & phase)) {
*length += sprintf(output_buf, "%s=%s",
param->name, param->value);
*length += 1;
output_buf = textbuf + *length;
SET_PSTATE_RESPONSE_SENT(param);
pr_debug("Sending key: %s=%s\n",
param->name, param->value);
continue;
}
if (IS_PSTATE_NEGOTIATE(param) &&
!IS_PSTATE_ACCEPTOR(param) &&
!IS_PSTATE_PROPOSER(param) &&
(param->phase & phase)) {
*length += sprintf(output_buf, "%s=%s",
param->name, param->value);
*length += 1;
output_buf = textbuf + *length;
SET_PSTATE_PROPOSER(param);
iscsi_check_proposer_for_optional_reply(param);
pr_debug("Sending key: %s=%s\n",
param->name, param->value);
}
}
list_for_each_entry(er, &param_list->extra_response_list, er_list) {
*length += sprintf(output_buf, "%s=%s", er->key, er->value);
*length += 1;
output_buf = textbuf + *length;
pr_debug("Sending key: %s=%s\n", er->key, er->value);
}
iscsi_release_extra_responses(param_list);
return 0;
}
int iscsi_check_negotiated_keys(struct iscsi_param_list *param_list)
{
int ret = 0;
struct iscsi_param *param;
list_for_each_entry(param, &param_list->param_list, p_list) {
if (IS_PSTATE_NEGOTIATE(param) &&
IS_PSTATE_PROPOSER(param) &&
!IS_PSTATE_RESPONSE_GOT(param) &&
!IS_PSTATE_REPLY_OPTIONAL(param) &&
!IS_PHASE_DECLARATIVE(param)) {
pr_err("No response for proposed key \"%s\".\n",
param->name);
ret = -1;
}
}
return ret;
}
int iscsi_change_param_value(
char *keyvalue,
struct iscsi_param_list *param_list,
int check_key)
{
char *key = NULL, *value = NULL;
struct iscsi_param *param;
int sender = 0;
if (iscsi_extract_key_value(keyvalue, &key, &value) < 0)
return -1;
if (!check_key) {
param = __iscsi_check_key(keyvalue, sender, param_list);
if (!param)
return -1;
} else {
param = iscsi_check_key(keyvalue, 0, sender, param_list);
if (!param)
return -1;
param->set_param = 1;
if (iscsi_check_value(param, value) < 0) {
param->set_param = 0;
return -1;
}
param->set_param = 0;
}
if (iscsi_update_param_value(param, value) < 0)
return -1;
return 0;
}
void iscsi_set_connection_parameters(
struct iscsi_conn_ops *ops,
struct iscsi_param_list *param_list)
{
char *tmpptr;
struct iscsi_param *param;
pr_debug("---------------------------------------------------"
"---------------\n");
list_for_each_entry(param, &param_list->param_list, p_list) {
if (!IS_PSTATE_ACCEPTOR(param) && !IS_PSTATE_PROPOSER(param))
continue;
if (!strcmp(param->name, AUTHMETHOD)) {
pr_debug("AuthMethod: %s\n",
param->value);
} else if (!strcmp(param->name, HEADERDIGEST)) {
ops->HeaderDigest = !strcmp(param->value, CRC32C);
pr_debug("HeaderDigest: %s\n",
param->value);
} else if (!strcmp(param->name, DATADIGEST)) {
ops->DataDigest = !strcmp(param->value, CRC32C);
pr_debug("DataDigest: %s\n",
param->value);
} else if (!strcmp(param->name, MAXRECVDATASEGMENTLENGTH)) {
ops->MaxRecvDataSegmentLength =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("MaxRecvDataSegmentLength: %s\n",
param->value);
} else if (!strcmp(param->name, OFMARKER)) {
ops->OFMarker = !strcmp(param->value, YES);
pr_debug("OFMarker: %s\n",
param->value);
} else if (!strcmp(param->name, IFMARKER)) {
ops->IFMarker = !strcmp(param->value, YES);
pr_debug("IFMarker: %s\n",
param->value);
} else if (!strcmp(param->name, OFMARKINT)) {
ops->OFMarkInt =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("OFMarkInt: %s\n",
param->value);
} else if (!strcmp(param->name, IFMARKINT)) {
ops->IFMarkInt =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("IFMarkInt: %s\n",
param->value);
}
}
pr_debug("----------------------------------------------------"
"--------------\n");
}
void iscsi_set_session_parameters(
struct iscsi_sess_ops *ops,
struct iscsi_param_list *param_list,
int leading)
{
char *tmpptr;
struct iscsi_param *param;
pr_debug("----------------------------------------------------"
"--------------\n");
list_for_each_entry(param, &param_list->param_list, p_list) {
if (!IS_PSTATE_ACCEPTOR(param) && !IS_PSTATE_PROPOSER(param))
continue;
if (!strcmp(param->name, INITIATORNAME)) {
if (!param->value)
continue;
if (leading)
snprintf(ops->InitiatorName,
sizeof(ops->InitiatorName),
"%s", param->value);
pr_debug("InitiatorName: %s\n",
param->value);
} else if (!strcmp(param->name, INITIATORALIAS)) {
if (!param->value)
continue;
snprintf(ops->InitiatorAlias,
sizeof(ops->InitiatorAlias),
"%s", param->value);
pr_debug("InitiatorAlias: %s\n",
param->value);
} else if (!strcmp(param->name, TARGETNAME)) {
if (!param->value)
continue;
if (leading)
snprintf(ops->TargetName,
sizeof(ops->TargetName),
"%s", param->value);
pr_debug("TargetName: %s\n",
param->value);
} else if (!strcmp(param->name, TARGETALIAS)) {
if (!param->value)
continue;
snprintf(ops->TargetAlias, sizeof(ops->TargetAlias),
"%s", param->value);
pr_debug("TargetAlias: %s\n",
param->value);
} else if (!strcmp(param->name, TARGETPORTALGROUPTAG)) {
ops->TargetPortalGroupTag =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("TargetPortalGroupTag: %s\n",
param->value);
} else if (!strcmp(param->name, MAXCONNECTIONS)) {
ops->MaxConnections =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("MaxConnections: %s\n",
param->value);
} else if (!strcmp(param->name, INITIALR2T)) {
ops->InitialR2T = !strcmp(param->value, YES);
pr_debug("InitialR2T: %s\n",
param->value);
} else if (!strcmp(param->name, IMMEDIATEDATA)) {
ops->ImmediateData = !strcmp(param->value, YES);
pr_debug("ImmediateData: %s\n",
param->value);
} else if (!strcmp(param->name, MAXBURSTLENGTH)) {
ops->MaxBurstLength =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("MaxBurstLength: %s\n",
param->value);
} else if (!strcmp(param->name, FIRSTBURSTLENGTH)) {
ops->FirstBurstLength =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("FirstBurstLength: %s\n",
param->value);
} else if (!strcmp(param->name, DEFAULTTIME2WAIT)) {
ops->DefaultTime2Wait =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("DefaultTime2Wait: %s\n",
param->value);
} else if (!strcmp(param->name, DEFAULTTIME2RETAIN)) {
ops->DefaultTime2Retain =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("DefaultTime2Retain: %s\n",
param->value);
} else if (!strcmp(param->name, MAXOUTSTANDINGR2T)) {
ops->MaxOutstandingR2T =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("MaxOutstandingR2T: %s\n",
param->value);
} else if (!strcmp(param->name, DATAPDUINORDER)) {
ops->DataPDUInOrder = !strcmp(param->value, YES);
pr_debug("DataPDUInOrder: %s\n",
param->value);
} else if (!strcmp(param->name, DATASEQUENCEINORDER)) {
ops->DataSequenceInOrder = !strcmp(param->value, YES);
pr_debug("DataSequenceInOrder: %s\n",
param->value);
} else if (!strcmp(param->name, ERRORRECOVERYLEVEL)) {
ops->ErrorRecoveryLevel =
simple_strtoul(param->value, &tmpptr, 0);
pr_debug("ErrorRecoveryLevel: %s\n",
param->value);
} else if (!strcmp(param->name, SESSIONTYPE)) {
ops->SessionType = !strcmp(param->value, DISCOVERY);
pr_debug("SessionType: %s\n",
param->value);
}
}
pr_debug("----------------------------------------------------"
"--------------\n");
}
#ifndef ISCSI_PARAMETERS_H
#define ISCSI_PARAMETERS_H
struct iscsi_extra_response {
char key[64];
char value[32];
struct list_head er_list;
} ____cacheline_aligned;
struct iscsi_param {
char *name;
char *value;
u8 set_param;
u8 phase;
u8 scope;
u8 sender;
u8 type;
u8 use;
u16 type_range;
u32 state;
struct list_head p_list;
} ____cacheline_aligned;
extern int iscsi_login_rx_data(struct iscsi_conn *, char *, int);
extern int iscsi_login_tx_data(struct iscsi_conn *, char *, char *, int);
extern void iscsi_dump_conn_ops(struct iscsi_conn_ops *);
extern void iscsi_dump_sess_ops(struct iscsi_sess_ops *);
extern void iscsi_print_params(struct iscsi_param_list *);
extern int iscsi_create_default_params(struct iscsi_param_list **);
extern int iscsi_set_keys_to_negotiate(int, struct iscsi_param_list *);
extern int iscsi_set_keys_irrelevant_for_discovery(struct iscsi_param_list *);
extern int iscsi_copy_param_list(struct iscsi_param_list **,
struct iscsi_param_list *, int);
extern int iscsi_change_param_value(char *, struct iscsi_param_list *, int);
extern void iscsi_release_param_list(struct iscsi_param_list *);
extern struct iscsi_param *iscsi_find_param_from_key(char *, struct iscsi_param_list *);
extern int iscsi_extract_key_value(char *, char **, char **);
extern int iscsi_update_param_value(struct iscsi_param *, char *);
extern int iscsi_decode_text_input(u8, u8, char *, u32, struct iscsi_param_list *);
extern int iscsi_encode_text_output(u8, u8, char *, u32 *,
struct iscsi_param_list *);
extern int iscsi_check_negotiated_keys(struct iscsi_param_list *);
extern void iscsi_set_connection_parameters(struct iscsi_conn_ops *,
struct iscsi_param_list *);
extern void iscsi_set_session_parameters(struct iscsi_sess_ops *,
struct iscsi_param_list *, int);
#define YES "Yes"
#define NO "No"
#define ALL "All"
#define IRRELEVANT "Irrelevant"
#define NONE "None"
#define NOTUNDERSTOOD "NotUnderstood"
#define REJECT "Reject"
/*
* The Parameter Names.
*/
#define AUTHMETHOD "AuthMethod"
#define HEADERDIGEST "HeaderDigest"
#define DATADIGEST "DataDigest"
#define MAXCONNECTIONS "MaxConnections"
#define SENDTARGETS "SendTargets"
#define TARGETNAME "TargetName"
#define INITIATORNAME "InitiatorName"
#define TARGETALIAS "TargetAlias"
#define INITIATORALIAS "InitiatorAlias"
#define TARGETADDRESS "TargetAddress"
#define TARGETPORTALGROUPTAG "TargetPortalGroupTag"
#define INITIALR2T "InitialR2T"
#define IMMEDIATEDATA "ImmediateData"
#define MAXRECVDATASEGMENTLENGTH "MaxRecvDataSegmentLength"
#define MAXBURSTLENGTH "MaxBurstLength"
#define FIRSTBURSTLENGTH "FirstBurstLength"
#define DEFAULTTIME2WAIT "DefaultTime2Wait"
#define DEFAULTTIME2RETAIN "DefaultTime2Retain"
#define MAXOUTSTANDINGR2T "MaxOutstandingR2T"
#define DATAPDUINORDER "DataPDUInOrder"
#define DATASEQUENCEINORDER "DataSequenceInOrder"
#define ERRORRECOVERYLEVEL "ErrorRecoveryLevel"
#define SESSIONTYPE "SessionType"
#define IFMARKER "IFMarker"
#define OFMARKER "OFMarker"
#define IFMARKINT "IFMarkInt"
#define OFMARKINT "OFMarkInt"
#define X_EXTENSIONKEY "X-com.sbei.version"
#define X_EXTENSIONKEY_CISCO_NEW "X-com.cisco.protocol"
#define X_EXTENSIONKEY_CISCO_OLD "X-com.cisco.iscsi.draft"
/*
* For AuthMethod.
*/
#define KRB5 "KRB5"
#define SPKM1 "SPKM1"
#define SPKM2 "SPKM2"
#define SRP "SRP"
#define CHAP "CHAP"
/*
* Initial values for Parameter Negotiation.
*/
#define INITIAL_AUTHMETHOD CHAP
#define INITIAL_HEADERDIGEST "CRC32C,None"
#define INITIAL_DATADIGEST "CRC32C,None"
#define INITIAL_MAXCONNECTIONS "1"
#define INITIAL_SENDTARGETS ALL
#define INITIAL_TARGETNAME "LIO.Target"
#define INITIAL_INITIATORNAME "LIO.Initiator"
#define INITIAL_TARGETALIAS "LIO Target"
#define INITIAL_INITIATORALIAS "LIO Initiator"
#define INITIAL_TARGETADDRESS "0.0.0.0:0000,0"
#define INITIAL_TARGETPORTALGROUPTAG "1"
#define INITIAL_INITIALR2T YES
#define INITIAL_IMMEDIATEDATA YES
#define INITIAL_MAXRECVDATASEGMENTLENGTH "8192"
#define INITIAL_MAXBURSTLENGTH "262144"
#define INITIAL_FIRSTBURSTLENGTH "65536"
#define INITIAL_DEFAULTTIME2WAIT "2"
#define INITIAL_DEFAULTTIME2RETAIN "20"
#define INITIAL_MAXOUTSTANDINGR2T "1"
#define INITIAL_DATAPDUINORDER YES
#define INITIAL_DATASEQUENCEINORDER YES
#define INITIAL_ERRORRECOVERYLEVEL "0"
#define INITIAL_SESSIONTYPE NORMAL
#define INITIAL_IFMARKER NO
#define INITIAL_OFMARKER NO
#define INITIAL_IFMARKINT "2048~65535"
#define INITIAL_OFMARKINT "2048~65535"
/*
* For [Header,Data]Digests.
*/
#define CRC32C "CRC32C"
/*
* For SessionType.
*/
#define DISCOVERY "Discovery"
#define NORMAL "Normal"
/*
* struct iscsi_param->use
*/
#define USE_LEADING_ONLY 0x01
#define USE_INITIAL_ONLY 0x02
#define USE_ALL 0x04
#define IS_USE_LEADING_ONLY(p) ((p)->use & USE_LEADING_ONLY)
#define IS_USE_INITIAL_ONLY(p) ((p)->use & USE_INITIAL_ONLY)
#define IS_USE_ALL(p) ((p)->use & USE_ALL)
#define SET_USE_INITIAL_ONLY(p) ((p)->use |= USE_INITIAL_ONLY)
/*
* struct iscsi_param->sender
*/
#define SENDER_INITIATOR 0x01
#define SENDER_TARGET 0x02
#define SENDER_BOTH 0x03
/* Used in iscsi_check_key() */
#define SENDER_RECEIVER 0x04
#define IS_SENDER_INITIATOR(p) ((p)->sender & SENDER_INITIATOR)
#define IS_SENDER_TARGET(p) ((p)->sender & SENDER_TARGET)
#define IS_SENDER_BOTH(p) ((p)->sender & SENDER_BOTH)
/*
* struct iscsi_param->scope
*/
#define SCOPE_CONNECTION_ONLY 0x01
#define SCOPE_SESSION_WIDE 0x02
#define IS_SCOPE_CONNECTION_ONLY(p) ((p)->scope & SCOPE_CONNECTION_ONLY)
#define IS_SCOPE_SESSION_WIDE(p) ((p)->scope & SCOPE_SESSION_WIDE)
/*
* struct iscsi_param->phase
*/
#define PHASE_SECURITY 0x01
#define PHASE_OPERATIONAL 0x02
#define PHASE_DECLARATIVE 0x04
#define PHASE_FFP0 0x08
#define IS_PHASE_SECURITY(p) ((p)->phase & PHASE_SECURITY)
#define IS_PHASE_OPERATIONAL(p) ((p)->phase & PHASE_OPERATIONAL)
#define IS_PHASE_DECLARATIVE(p) ((p)->phase & PHASE_DECLARATIVE)
#define IS_PHASE_FFP0(p) ((p)->phase & PHASE_FFP0)
/*
* struct iscsi_param->type
*/
#define TYPE_BOOL_AND 0x01
#define TYPE_BOOL_OR 0x02
#define TYPE_NUMBER 0x04
#define TYPE_NUMBER_RANGE 0x08
#define TYPE_STRING 0x10
#define TYPE_VALUE_LIST 0x20
#define IS_TYPE_BOOL_AND(p) ((p)->type & TYPE_BOOL_AND)
#define IS_TYPE_BOOL_OR(p) ((p)->type & TYPE_BOOL_OR)
#define IS_TYPE_NUMBER(p) ((p)->type & TYPE_NUMBER)
#define IS_TYPE_NUMBER_RANGE(p) ((p)->type & TYPE_NUMBER_RANGE)
#define IS_TYPE_STRING(p) ((p)->type & TYPE_STRING)
#define IS_TYPE_VALUE_LIST(p) ((p)->type & TYPE_VALUE_LIST)
/*
* struct iscsi_param->type_range
*/
#define TYPERANGE_BOOL_AND 0x0001
#define TYPERANGE_BOOL_OR 0x0002
#define TYPERANGE_0_TO_2 0x0004
#define TYPERANGE_0_TO_3600 0x0008
#define TYPERANGE_0_TO_32767 0x0010
#define TYPERANGE_0_TO_65535 0x0020
#define TYPERANGE_1_TO_65535 0x0040
#define TYPERANGE_2_TO_3600 0x0080
#define TYPERANGE_512_TO_16777215 0x0100
#define TYPERANGE_AUTH 0x0200
#define TYPERANGE_DIGEST 0x0400
#define TYPERANGE_ISCSINAME 0x0800
#define TYPERANGE_MARKINT 0x1000
#define TYPERANGE_SESSIONTYPE 0x2000
#define TYPERANGE_TARGETADDRESS 0x4000
#define TYPERANGE_UTF8 0x8000
#define IS_TYPERANGE_0_TO_2(p) ((p)->type_range & TYPERANGE_0_TO_2)
#define IS_TYPERANGE_0_TO_3600(p) ((p)->type_range & TYPERANGE_0_TO_3600)
#define IS_TYPERANGE_0_TO_32767(p) ((p)->type_range & TYPERANGE_0_TO_32767)
#define IS_TYPERANGE_0_TO_65535(p) ((p)->type_range & TYPERANGE_0_TO_65535)
#define IS_TYPERANGE_1_TO_65535(p) ((p)->type_range & TYPERANGE_1_TO_65535)
#define IS_TYPERANGE_2_TO_3600(p) ((p)->type_range & TYPERANGE_2_TO_3600)
#define IS_TYPERANGE_512_TO_16777215(p) ((p)->type_range & \
TYPERANGE_512_TO_16777215)
#define IS_TYPERANGE_AUTH_PARAM(p) ((p)->type_range & TYPERANGE_AUTH)
#define IS_TYPERANGE_DIGEST_PARAM(p) ((p)->type_range & TYPERANGE_DIGEST)
#define IS_TYPERANGE_SESSIONTYPE(p) ((p)->type_range & \
TYPERANGE_SESSIONTYPE)
/*
* struct iscsi_param->state
*/
#define PSTATE_ACCEPTOR 0x01
#define PSTATE_NEGOTIATE 0x02
#define PSTATE_PROPOSER 0x04
#define PSTATE_IRRELEVANT 0x08
#define PSTATE_REJECT 0x10
#define PSTATE_REPLY_OPTIONAL 0x20
#define PSTATE_RESPONSE_GOT 0x40
#define PSTATE_RESPONSE_SENT 0x80
#define IS_PSTATE_ACCEPTOR(p) ((p)->state & PSTATE_ACCEPTOR)
#define IS_PSTATE_NEGOTIATE(p) ((p)->state & PSTATE_NEGOTIATE)
#define IS_PSTATE_PROPOSER(p) ((p)->state & PSTATE_PROPOSER)
#define IS_PSTATE_IRRELEVANT(p) ((p)->state & PSTATE_IRRELEVANT)
#define IS_PSTATE_REJECT(p) ((p)->state & PSTATE_REJECT)
#define IS_PSTATE_REPLY_OPTIONAL(p) ((p)->state & PSTATE_REPLY_OPTIONAL)
#define IS_PSTATE_RESPONSE_GOT(p) ((p)->state & PSTATE_RESPONSE_GOT)
#define IS_PSTATE_RESPONSE_SENT(p) ((p)->state & PSTATE_RESPONSE_SENT)
#define SET_PSTATE_ACCEPTOR(p) ((p)->state |= PSTATE_ACCEPTOR)
#define SET_PSTATE_NEGOTIATE(p) ((p)->state |= PSTATE_NEGOTIATE)
#define SET_PSTATE_PROPOSER(p) ((p)->state |= PSTATE_PROPOSER)
#define SET_PSTATE_IRRELEVANT(p) ((p)->state |= PSTATE_IRRELEVANT)
#define SET_PSTATE_REJECT(p) ((p)->state |= PSTATE_REJECT)
#define SET_PSTATE_REPLY_OPTIONAL(p) ((p)->state |= PSTATE_REPLY_OPTIONAL)
#define SET_PSTATE_RESPONSE_GOT(p) ((p)->state |= PSTATE_RESPONSE_GOT)
#define SET_PSTATE_RESPONSE_SENT(p) ((p)->state |= PSTATE_RESPONSE_SENT)
#endif /* ISCSI_PARAMETERS_H */
/*******************************************************************************
* This file contains main functions related to iSCSI DataSequenceInOrder=No
* and DataPDUInOrder=No.
*
\u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/slab.h>
#include <linux/random.h>
#include "iscsi_target_core.h"
#include "iscsi_target_util.h"
#include "iscsi_target_seq_pdu_list.h"
#define OFFLOAD_BUF_SIZE 32768
void iscsit_dump_seq_list(struct iscsi_cmd *cmd)
{
int i;
struct iscsi_seq *seq;
pr_debug("Dumping Sequence List for ITT: 0x%08x:\n",
cmd->init_task_tag);
for (i = 0; i < cmd->seq_count; i++) {
seq = &cmd->seq_list[i];
pr_debug("i: %d, pdu_start: %d, pdu_count: %d,"
" offset: %d, xfer_len: %d, seq_send_order: %d,"
" seq_no: %d\n", i, seq->pdu_start, seq->pdu_count,
seq->offset, seq->xfer_len, seq->seq_send_order,
seq->seq_no);
}
}
void iscsit_dump_pdu_list(struct iscsi_cmd *cmd)
{
int i;
struct iscsi_pdu *pdu;
pr_debug("Dumping PDU List for ITT: 0x%08x:\n",
cmd->init_task_tag);
for (i = 0; i < cmd->pdu_count; i++) {
pdu = &cmd->pdu_list[i];
pr_debug("i: %d, offset: %d, length: %d,"
" pdu_send_order: %d, seq_no: %d\n", i, pdu->offset,
pdu->length, pdu->pdu_send_order, pdu->seq_no);
}
}
static void iscsit_ordered_seq_lists(
struct iscsi_cmd *cmd,
u8 type)
{
u32 i, seq_count = 0;
for (i = 0; i < cmd->seq_count; i++) {
if (cmd->seq_list[i].type != SEQTYPE_NORMAL)
continue;
cmd->seq_list[i].seq_send_order = seq_count++;
}
}
static void iscsit_ordered_pdu_lists(
struct iscsi_cmd *cmd,
u8 type)
{
u32 i, pdu_send_order = 0, seq_no = 0;
for (i = 0; i < cmd->pdu_count; i++) {
redo:
if (cmd->pdu_list[i].seq_no == seq_no) {
cmd->pdu_list[i].pdu_send_order = pdu_send_order++;
continue;
}
seq_no++;
pdu_send_order = 0;
goto redo;
}
}
/*
* Generate count random values into array.
* Use 0x80000000 to mark generates valued in array[].
*/
static void iscsit_create_random_array(u32 *array, u32 count)
{
int i, j, k;
if (count == 1) {
array[0] = 0;
return;
}
for (i = 0; i < count; i++) {
redo:
get_random_bytes(&j, sizeof(u32));
j = (1 + (int) (9999 + 1) - j) % count;
for (k = 0; k < i + 1; k++) {
j |= 0x80000000;
if ((array[k] & 0x80000000) && (array[k] == j))
goto redo;
}
array[i] = j;
}
for (i = 0; i < count; i++)
array[i] &= ~0x80000000;
}
static int iscsit_randomize_pdu_lists(
struct iscsi_cmd *cmd,
u8 type)
{
int i = 0;
u32 *array, pdu_count, seq_count = 0, seq_no = 0, seq_offset = 0;
for (pdu_count = 0; pdu_count < cmd->pdu_count; pdu_count++) {
redo:
if (cmd->pdu_list[pdu_count].seq_no == seq_no) {
seq_count++;
continue;
}
array = kzalloc(seq_count * sizeof(u32), GFP_KERNEL);
if (!array) {
pr_err("Unable to allocate memory"
" for random array.\n");
return -1;
}
iscsit_create_random_array(array, seq_count);
for (i = 0; i < seq_count; i++)
cmd->pdu_list[seq_offset+i].pdu_send_order = array[i];
kfree(array);
seq_offset += seq_count;
seq_count = 0;
seq_no++;
goto redo;
}
if (seq_count) {
array = kzalloc(seq_count * sizeof(u32), GFP_KERNEL);
if (!array) {
pr_err("Unable to allocate memory for"
" random array.\n");
return -1;
}
iscsit_create_random_array(array, seq_count);
for (i = 0; i < seq_count; i++)
cmd->pdu_list[seq_offset+i].pdu_send_order = array[i];
kfree(array);
}
return 0;
}
static int iscsit_randomize_seq_lists(
struct iscsi_cmd *cmd,
u8 type)
{
int i, j = 0;
u32 *array, seq_count = cmd->seq_count;
if ((type == PDULIST_IMMEDIATE) || (type == PDULIST_UNSOLICITED))
seq_count--;
else if (type == PDULIST_IMMEDIATE_AND_UNSOLICITED)
seq_count -= 2;
if (!seq_count)
return 0;
array = kzalloc(seq_count * sizeof(u32), GFP_KERNEL);
if (!array) {
pr_err("Unable to allocate memory for random array.\n");
return -1;
}
iscsit_create_random_array(array, seq_count);
for (i = 0; i < cmd->seq_count; i++) {
if (cmd->seq_list[i].type != SEQTYPE_NORMAL)
continue;
cmd->seq_list[i].seq_send_order = array[j++];
}
kfree(array);
return 0;
}
static void iscsit_determine_counts_for_list(
struct iscsi_cmd *cmd,
struct iscsi_build_list *bl,
u32 *seq_count,
u32 *pdu_count)
{
int check_immediate = 0;
u32 burstlength = 0, offset = 0;
u32 unsolicited_data_length = 0;
struct iscsi_conn *conn = cmd->conn;
if ((bl->type == PDULIST_IMMEDIATE) ||
(bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED))
check_immediate = 1;
if ((bl->type == PDULIST_UNSOLICITED) ||
(bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED))
unsolicited_data_length = (cmd->data_length >
conn->sess->sess_ops->FirstBurstLength) ?
conn->sess->sess_ops->FirstBurstLength : cmd->data_length;
while (offset < cmd->data_length) {
*pdu_count += 1;
if (check_immediate) {
check_immediate = 0;
offset += bl->immediate_data_length;
*seq_count += 1;
if (unsolicited_data_length)
unsolicited_data_length -=
bl->immediate_data_length;
continue;
}
if (unsolicited_data_length > 0) {
if ((offset + conn->conn_ops->MaxRecvDataSegmentLength)
>= cmd->data_length) {
unsolicited_data_length -=
(cmd->data_length - offset);
offset += (cmd->data_length - offset);
continue;
}
if ((offset + conn->conn_ops->MaxRecvDataSegmentLength)
>= conn->sess->sess_ops->FirstBurstLength) {
unsolicited_data_length -=
(conn->sess->sess_ops->FirstBurstLength -
offset);
offset += (conn->sess->sess_ops->FirstBurstLength -
offset);
burstlength = 0;
*seq_count += 1;
continue;
}
offset += conn->conn_ops->MaxRecvDataSegmentLength;
unsolicited_data_length -=
conn->conn_ops->MaxRecvDataSegmentLength;
continue;
}
if ((offset + conn->conn_ops->MaxRecvDataSegmentLength) >=
cmd->data_length) {
offset += (cmd->data_length - offset);
continue;
}
if ((burstlength + conn->conn_ops->MaxRecvDataSegmentLength) >=
conn->sess->sess_ops->MaxBurstLength) {
offset += (conn->sess->sess_ops->MaxBurstLength -
burstlength);
burstlength = 0;
*seq_count += 1;
continue;
}
burstlength += conn->conn_ops->MaxRecvDataSegmentLength;
offset += conn->conn_ops->MaxRecvDataSegmentLength;
}
}
/*
* Builds PDU and/or Sequence list, called while DataSequenceInOrder=No
* and DataPDUInOrder=No.
*/
static int iscsit_build_pdu_and_seq_list(
struct iscsi_cmd *cmd,
struct iscsi_build_list *bl)
{
int check_immediate = 0, datapduinorder, datasequenceinorder;
u32 burstlength = 0, offset = 0, i = 0;
u32 pdu_count = 0, seq_no = 0, unsolicited_data_length = 0;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_pdu *pdu = cmd->pdu_list;
struct iscsi_seq *seq = cmd->seq_list;
datapduinorder = conn->sess->sess_ops->DataPDUInOrder;
datasequenceinorder = conn->sess->sess_ops->DataSequenceInOrder;
if ((bl->type == PDULIST_IMMEDIATE) ||
(bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED))
check_immediate = 1;
if ((bl->type == PDULIST_UNSOLICITED) ||
(bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED))
unsolicited_data_length = (cmd->data_length >
conn->sess->sess_ops->FirstBurstLength) ?
conn->sess->sess_ops->FirstBurstLength : cmd->data_length;
while (offset < cmd->data_length) {
pdu_count++;
if (!datapduinorder) {
pdu[i].offset = offset;
pdu[i].seq_no = seq_no;
}
if (!datasequenceinorder && (pdu_count == 1)) {
seq[seq_no].pdu_start = i;
seq[seq_no].seq_no = seq_no;
seq[seq_no].offset = offset;
seq[seq_no].orig_offset = offset;
}
if (check_immediate) {
check_immediate = 0;
if (!datapduinorder) {
pdu[i].type = PDUTYPE_IMMEDIATE;
pdu[i++].length = bl->immediate_data_length;
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_IMMEDIATE;
seq[seq_no].pdu_count = 1;
seq[seq_no].xfer_len =
bl->immediate_data_length;
}
offset += bl->immediate_data_length;
pdu_count = 0;
seq_no++;
if (unsolicited_data_length)
unsolicited_data_length -=
bl->immediate_data_length;
continue;
}
if (unsolicited_data_length > 0) {
if ((offset +
conn->conn_ops->MaxRecvDataSegmentLength) >=
cmd->data_length) {
if (!datapduinorder) {
pdu[i].type = PDUTYPE_UNSOLICITED;
pdu[i].length =
(cmd->data_length - offset);
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_UNSOLICITED;
seq[seq_no].pdu_count = pdu_count;
seq[seq_no].xfer_len = (burstlength +
(cmd->data_length - offset));
}
unsolicited_data_length -=
(cmd->data_length - offset);
offset += (cmd->data_length - offset);
continue;
}
if ((offset +
conn->conn_ops->MaxRecvDataSegmentLength) >=
conn->sess->sess_ops->FirstBurstLength) {
if (!datapduinorder) {
pdu[i].type = PDUTYPE_UNSOLICITED;
pdu[i++].length =
(conn->sess->sess_ops->FirstBurstLength -
offset);
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_UNSOLICITED;
seq[seq_no].pdu_count = pdu_count;
seq[seq_no].xfer_len = (burstlength +
(conn->sess->sess_ops->FirstBurstLength -
offset));
}
unsolicited_data_length -=
(conn->sess->sess_ops->FirstBurstLength -
offset);
offset += (conn->sess->sess_ops->FirstBurstLength -
offset);
burstlength = 0;
pdu_count = 0;
seq_no++;
continue;
}
if (!datapduinorder) {
pdu[i].type = PDUTYPE_UNSOLICITED;
pdu[i++].length =
conn->conn_ops->MaxRecvDataSegmentLength;
}
burstlength += conn->conn_ops->MaxRecvDataSegmentLength;
offset += conn->conn_ops->MaxRecvDataSegmentLength;
unsolicited_data_length -=
conn->conn_ops->MaxRecvDataSegmentLength;
continue;
}
if ((offset + conn->conn_ops->MaxRecvDataSegmentLength) >=
cmd->data_length) {
if (!datapduinorder) {
pdu[i].type = PDUTYPE_NORMAL;
pdu[i].length = (cmd->data_length - offset);
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_NORMAL;
seq[seq_no].pdu_count = pdu_count;
seq[seq_no].xfer_len = (burstlength +
(cmd->data_length - offset));
}
offset += (cmd->data_length - offset);
continue;
}
if ((burstlength + conn->conn_ops->MaxRecvDataSegmentLength) >=
conn->sess->sess_ops->MaxBurstLength) {
if (!datapduinorder) {
pdu[i].type = PDUTYPE_NORMAL;
pdu[i++].length =
(conn->sess->sess_ops->MaxBurstLength -
burstlength);
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_NORMAL;
seq[seq_no].pdu_count = pdu_count;
seq[seq_no].xfer_len = (burstlength +
(conn->sess->sess_ops->MaxBurstLength -
burstlength));
}
offset += (conn->sess->sess_ops->MaxBurstLength -
burstlength);
burstlength = 0;
pdu_count = 0;
seq_no++;
continue;
}
if (!datapduinorder) {
pdu[i].type = PDUTYPE_NORMAL;
pdu[i++].length =
conn->conn_ops->MaxRecvDataSegmentLength;
}
burstlength += conn->conn_ops->MaxRecvDataSegmentLength;
offset += conn->conn_ops->MaxRecvDataSegmentLength;
}
if (!datasequenceinorder) {
if (bl->data_direction & ISCSI_PDU_WRITE) {
if (bl->randomize & RANDOM_R2T_OFFSETS) {
if (iscsit_randomize_seq_lists(cmd, bl->type)
< 0)
return -1;
} else
iscsit_ordered_seq_lists(cmd, bl->type);
} else if (bl->data_direction & ISCSI_PDU_READ) {
if (bl->randomize & RANDOM_DATAIN_SEQ_OFFSETS) {
if (iscsit_randomize_seq_lists(cmd, bl->type)
< 0)
return -1;
} else
iscsit_ordered_seq_lists(cmd, bl->type);
}
#if 0
iscsit_dump_seq_list(cmd);
#endif
}
if (!datapduinorder) {
if (bl->data_direction & ISCSI_PDU_WRITE) {
if (bl->randomize & RANDOM_DATAOUT_PDU_OFFSETS) {
if (iscsit_randomize_pdu_lists(cmd, bl->type)
< 0)
return -1;
} else
iscsit_ordered_pdu_lists(cmd, bl->type);
} else if (bl->data_direction & ISCSI_PDU_READ) {
if (bl->randomize & RANDOM_DATAIN_PDU_OFFSETS) {
if (iscsit_randomize_pdu_lists(cmd, bl->type)
< 0)
return -1;
} else
iscsit_ordered_pdu_lists(cmd, bl->type);
}
#if 0
iscsit_dump_pdu_list(cmd);
#endif
}
return 0;
}
/*
* Only called while DataSequenceInOrder=No or DataPDUInOrder=No.
*/
int iscsit_do_build_list(
struct iscsi_cmd *cmd,
struct iscsi_build_list *bl)
{
u32 pdu_count = 0, seq_count = 1;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_pdu *pdu = NULL;
struct iscsi_seq *seq = NULL;
iscsit_determine_counts_for_list(cmd, bl, &seq_count, &pdu_count);
if (!conn->sess->sess_ops->DataSequenceInOrder) {
seq = kzalloc(seq_count * sizeof(struct iscsi_seq), GFP_ATOMIC);
if (!seq) {
pr_err("Unable to allocate struct iscsi_seq list\n");
return -1;
}
cmd->seq_list = seq;
cmd->seq_count = seq_count;
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
pdu = kzalloc(pdu_count * sizeof(struct iscsi_pdu), GFP_ATOMIC);
if (!pdu) {
pr_err("Unable to allocate struct iscsi_pdu list.\n");
kfree(seq);
return -1;
}
cmd->pdu_list = pdu;
cmd->pdu_count = pdu_count;
}
return iscsit_build_pdu_and_seq_list(cmd, bl);
}
struct iscsi_pdu *iscsit_get_pdu_holder(
struct iscsi_cmd *cmd,
u32 offset,
u32 length)
{
u32 i;
struct iscsi_pdu *pdu = NULL;
if (!cmd->pdu_list) {
pr_err("struct iscsi_cmd->pdu_list is NULL!\n");
return NULL;
}
pdu = &cmd->pdu_list[0];
for (i = 0; i < cmd->pdu_count; i++)
if ((pdu[i].offset == offset) && (pdu[i].length == length))
return &pdu[i];
pr_err("Unable to locate PDU holder for ITT: 0x%08x, Offset:"
" %u, Length: %u\n", cmd->init_task_tag, offset, length);
return NULL;
}
struct iscsi_pdu *iscsit_get_pdu_holder_for_seq(
struct iscsi_cmd *cmd,
struct iscsi_seq *seq)
{
u32 i;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_pdu *pdu = NULL;
if (!cmd->pdu_list) {
pr_err("struct iscsi_cmd->pdu_list is NULL!\n");
return NULL;
}
if (conn->sess->sess_ops->DataSequenceInOrder) {
redo:
pdu = &cmd->pdu_list[cmd->pdu_start];
for (i = 0; pdu[i].seq_no != cmd->seq_no; i++) {
#if 0
pr_debug("pdu[i].seq_no: %d, pdu[i].pdu"
"_send_order: %d, pdu[i].offset: %d,"
" pdu[i].length: %d\n", pdu[i].seq_no,
pdu[i].pdu_send_order, pdu[i].offset,
pdu[i].length);
#endif
if (pdu[i].pdu_send_order == cmd->pdu_send_order) {
cmd->pdu_send_order++;
return &pdu[i];
}
}
cmd->pdu_start += cmd->pdu_send_order;
cmd->pdu_send_order = 0;
cmd->seq_no++;
if (cmd->pdu_start < cmd->pdu_count)
goto redo;
pr_err("Command ITT: 0x%08x unable to locate"
" struct iscsi_pdu for cmd->pdu_send_order: %u.\n",
cmd->init_task_tag, cmd->pdu_send_order);
return NULL;
} else {
if (!seq) {
pr_err("struct iscsi_seq is NULL!\n");
return NULL;
}
#if 0
pr_debug("seq->pdu_start: %d, seq->pdu_count: %d,"
" seq->seq_no: %d\n", seq->pdu_start, seq->pdu_count,
seq->seq_no);
#endif
pdu = &cmd->pdu_list[seq->pdu_start];
if (seq->pdu_send_order == seq->pdu_count) {
pr_err("Command ITT: 0x%08x seq->pdu_send"
"_order: %u equals seq->pdu_count: %u\n",
cmd->init_task_tag, seq->pdu_send_order,
seq->pdu_count);
return NULL;
}
for (i = 0; i < seq->pdu_count; i++) {
if (pdu[i].pdu_send_order == seq->pdu_send_order) {
seq->pdu_send_order++;
return &pdu[i];
}
}
pr_err("Command ITT: 0x%08x unable to locate iscsi"
"_pdu_t for seq->pdu_send_order: %u.\n",
cmd->init_task_tag, seq->pdu_send_order);
return NULL;
}
return NULL;
}
struct iscsi_seq *iscsit_get_seq_holder(
struct iscsi_cmd *cmd,
u32 offset,
u32 length)
{
u32 i;
if (!cmd->seq_list) {
pr_err("struct iscsi_cmd->seq_list is NULL!\n");
return NULL;
}
for (i = 0; i < cmd->seq_count; i++) {
#if 0
pr_debug("seq_list[i].orig_offset: %d, seq_list[i]."
"xfer_len: %d, seq_list[i].seq_no %u\n",
cmd->seq_list[i].orig_offset, cmd->seq_list[i].xfer_len,
cmd->seq_list[i].seq_no);
#endif
if ((cmd->seq_list[i].orig_offset +
cmd->seq_list[i].xfer_len) >=
(offset + length))
return &cmd->seq_list[i];
}
pr_err("Unable to locate Sequence holder for ITT: 0x%08x,"
" Offset: %u, Length: %u\n", cmd->init_task_tag, offset,
length);
return NULL;
}
#ifndef ISCSI_SEQ_AND_PDU_LIST_H
#define ISCSI_SEQ_AND_PDU_LIST_H
/* struct iscsi_pdu->status */
#define DATAOUT_PDU_SENT 1
/* struct iscsi_seq->type */
#define SEQTYPE_IMMEDIATE 1
#define SEQTYPE_UNSOLICITED 2
#define SEQTYPE_NORMAL 3
/* struct iscsi_seq->status */
#define DATAOUT_SEQUENCE_GOT_R2T 1
#define DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY 2
#define DATAOUT_SEQUENCE_COMPLETE 3
/* iscsi_determine_counts_for_list() type */
#define PDULIST_NORMAL 1
#define PDULIST_IMMEDIATE 2
#define PDULIST_UNSOLICITED 3
#define PDULIST_IMMEDIATE_AND_UNSOLICITED 4
/* struct iscsi_pdu->type */
#define PDUTYPE_IMMEDIATE 1
#define PDUTYPE_UNSOLICITED 2
#define PDUTYPE_NORMAL 3
/* struct iscsi_pdu->status */
#define ISCSI_PDU_NOT_RECEIVED 0
#define ISCSI_PDU_RECEIVED_OK 1
#define ISCSI_PDU_CRC_FAILED 2
#define ISCSI_PDU_TIMED_OUT 3
/* struct iscsi_build_list->randomize */
#define RANDOM_DATAIN_PDU_OFFSETS 0x01
#define RANDOM_DATAIN_SEQ_OFFSETS 0x02
#define RANDOM_DATAOUT_PDU_OFFSETS 0x04
#define RANDOM_R2T_OFFSETS 0x08
/* struct iscsi_build_list->data_direction */
#define ISCSI_PDU_READ 0x01
#define ISCSI_PDU_WRITE 0x02
struct iscsi_build_list {
int data_direction;
int randomize;
int type;
int immediate_data_length;
};
struct iscsi_pdu {
int status;
int type;
u8 flags;
u32 data_sn;
u32 length;
u32 offset;
u32 pdu_send_order;
u32 seq_no;
} ____cacheline_aligned;
struct iscsi_seq {
int sent;
int status;
int type;
u32 data_sn;
u32 first_datasn;
u32 last_datasn;
u32 next_burst_len;
u32 pdu_start;
u32 pdu_count;
u32 offset;
u32 orig_offset;
u32 pdu_send_order;
u32 r2t_sn;
u32 seq_send_order;
u32 seq_no;
u32 xfer_len;
} ____cacheline_aligned;
extern int iscsit_do_build_list(struct iscsi_cmd *, struct iscsi_build_list *);
extern struct iscsi_pdu *iscsit_get_pdu_holder(struct iscsi_cmd *, u32, u32);
extern struct iscsi_pdu *iscsit_get_pdu_holder_for_seq(struct iscsi_cmd *, struct iscsi_seq *);
extern struct iscsi_seq *iscsit_get_seq_holder(struct iscsi_cmd *, u32, u32);
#endif /* ISCSI_SEQ_AND_PDU_LIST_H */
/*******************************************************************************
* Modern ConfigFS group context specific iSCSI statistics based on original
* iscsi_target_mib.c code
*
* Copyright (c) 2011 Rising Tide Systems
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/configfs.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include <target/configfs_macros.h>
#include "iscsi_target_core.h"
#include "iscsi_target_parameters.h"
#include "iscsi_target_device.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target_stat.h"
#ifndef INITIAL_JIFFIES
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
#endif
/* Instance Attributes Table */
#define ISCSI_INST_NUM_NODES 1
#define ISCSI_INST_DESCR "Storage Engine Target"
#define ISCSI_INST_LAST_FAILURE_TYPE 0
#define ISCSI_DISCONTINUITY_TIME 0
#define ISCSI_NODE_INDEX 1
#define ISPRINT(a) ((a >= ' ') && (a <= '~'))
/****************************************************************************
* iSCSI MIB Tables
****************************************************************************/
/*
* Instance Attributes Table
*/
CONFIGFS_EATTR_STRUCT(iscsi_stat_instance, iscsi_wwn_stat_grps);
#define ISCSI_STAT_INSTANCE_ATTR(_name, _mode) \
static struct iscsi_stat_instance_attribute \
iscsi_stat_instance_##_name = \
__CONFIGFS_EATTR(_name, _mode, \
iscsi_stat_instance_show_attr_##_name, \
iscsi_stat_instance_store_attr_##_name);
#define ISCSI_STAT_INSTANCE_ATTR_RO(_name) \
static struct iscsi_stat_instance_attribute \
iscsi_stat_instance_##_name = \
__CONFIGFS_EATTR_RO(_name, \
iscsi_stat_instance_show_attr_##_name);
static ssize_t iscsi_stat_instance_show_attr_inst(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_index);
}
ISCSI_STAT_INSTANCE_ATTR_RO(inst);
static ssize_t iscsi_stat_instance_show_attr_min_ver(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_DRAFT20_VERSION);
}
ISCSI_STAT_INSTANCE_ATTR_RO(min_ver);
static ssize_t iscsi_stat_instance_show_attr_max_ver(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_DRAFT20_VERSION);
}
ISCSI_STAT_INSTANCE_ATTR_RO(max_ver);
static ssize_t iscsi_stat_instance_show_attr_portals(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_num_tpg_nps);
}
ISCSI_STAT_INSTANCE_ATTR_RO(portals);
static ssize_t iscsi_stat_instance_show_attr_nodes(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_INST_NUM_NODES);
}
ISCSI_STAT_INSTANCE_ATTR_RO(nodes);
static ssize_t iscsi_stat_instance_show_attr_sessions(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_nsessions);
}
ISCSI_STAT_INSTANCE_ATTR_RO(sessions);
static ssize_t iscsi_stat_instance_show_attr_fail_sess(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
u32 sess_err_count;
spin_lock_bh(&sess_err->lock);
sess_err_count = (sess_err->digest_errors +
sess_err->cxn_timeout_errors +
sess_err->pdu_format_errors);
spin_unlock_bh(&sess_err->lock);
return snprintf(page, PAGE_SIZE, "%u\n", sess_err_count);
}
ISCSI_STAT_INSTANCE_ATTR_RO(fail_sess);
static ssize_t iscsi_stat_instance_show_attr_fail_type(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%u\n",
sess_err->last_sess_failure_type);
}
ISCSI_STAT_INSTANCE_ATTR_RO(fail_type);
static ssize_t iscsi_stat_instance_show_attr_fail_rem_name(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%s\n",
sess_err->last_sess_fail_rem_name[0] ?
sess_err->last_sess_fail_rem_name : NONE);
}
ISCSI_STAT_INSTANCE_ATTR_RO(fail_rem_name);
static ssize_t iscsi_stat_instance_show_attr_disc_time(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_DISCONTINUITY_TIME);
}
ISCSI_STAT_INSTANCE_ATTR_RO(disc_time);
static ssize_t iscsi_stat_instance_show_attr_description(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%s\n", ISCSI_INST_DESCR);
}
ISCSI_STAT_INSTANCE_ATTR_RO(description);
static ssize_t iscsi_stat_instance_show_attr_vendor(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "RisingTide Systems iSCSI-Target\n");
}
ISCSI_STAT_INSTANCE_ATTR_RO(vendor);
static ssize_t iscsi_stat_instance_show_attr_version(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%s\n", ISCSIT_VERSION);
}
ISCSI_STAT_INSTANCE_ATTR_RO(version);
CONFIGFS_EATTR_OPS(iscsi_stat_instance, iscsi_wwn_stat_grps,
iscsi_instance_group);
static struct configfs_attribute *iscsi_stat_instance_attrs[] = {
&iscsi_stat_instance_inst.attr,
&iscsi_stat_instance_min_ver.attr,
&iscsi_stat_instance_max_ver.attr,
&iscsi_stat_instance_portals.attr,
&iscsi_stat_instance_nodes.attr,
&iscsi_stat_instance_sessions.attr,
&iscsi_stat_instance_fail_sess.attr,
&iscsi_stat_instance_fail_type.attr,
&iscsi_stat_instance_fail_rem_name.attr,
&iscsi_stat_instance_disc_time.attr,
&iscsi_stat_instance_description.attr,
&iscsi_stat_instance_vendor.attr,
&iscsi_stat_instance_version.attr,
NULL,
};
static struct configfs_item_operations iscsi_stat_instance_item_ops = {
.show_attribute = iscsi_stat_instance_attr_show,
.store_attribute = iscsi_stat_instance_attr_store,
};
struct config_item_type iscsi_stat_instance_cit = {
.ct_item_ops = &iscsi_stat_instance_item_ops,
.ct_attrs = iscsi_stat_instance_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Instance Session Failure Stats Table
*/
CONFIGFS_EATTR_STRUCT(iscsi_stat_sess_err, iscsi_wwn_stat_grps);
#define ISCSI_STAT_SESS_ERR_ATTR(_name, _mode) \
static struct iscsi_stat_sess_err_attribute \
iscsi_stat_sess_err_##_name = \
__CONFIGFS_EATTR(_name, _mode, \
iscsi_stat_sess_err_show_attr_##_name, \
iscsi_stat_sess_err_store_attr_##_name);
#define ISCSI_STAT_SESS_ERR_ATTR_RO(_name) \
static struct iscsi_stat_sess_err_attribute \
iscsi_stat_sess_err_##_name = \
__CONFIGFS_EATTR_RO(_name, \
iscsi_stat_sess_err_show_attr_##_name);
static ssize_t iscsi_stat_sess_err_show_attr_inst(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_index);
}
ISCSI_STAT_SESS_ERR_ATTR_RO(inst);
static ssize_t iscsi_stat_sess_err_show_attr_digest_errors(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%u\n", sess_err->digest_errors);
}
ISCSI_STAT_SESS_ERR_ATTR_RO(digest_errors);
static ssize_t iscsi_stat_sess_err_show_attr_cxn_errors(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%u\n", sess_err->cxn_timeout_errors);
}
ISCSI_STAT_SESS_ERR_ATTR_RO(cxn_errors);
static ssize_t iscsi_stat_sess_err_show_attr_format_errors(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%u\n", sess_err->pdu_format_errors);
}
ISCSI_STAT_SESS_ERR_ATTR_RO(format_errors);
CONFIGFS_EATTR_OPS(iscsi_stat_sess_err, iscsi_wwn_stat_grps,
iscsi_sess_err_group);
static struct configfs_attribute *iscsi_stat_sess_err_attrs[] = {
&iscsi_stat_sess_err_inst.attr,
&iscsi_stat_sess_err_digest_errors.attr,
&iscsi_stat_sess_err_cxn_errors.attr,
&iscsi_stat_sess_err_format_errors.attr,
NULL,
};
static struct configfs_item_operations iscsi_stat_sess_err_item_ops = {
.show_attribute = iscsi_stat_sess_err_attr_show,
.store_attribute = iscsi_stat_sess_err_attr_store,
};
struct config_item_type iscsi_stat_sess_err_cit = {
.ct_item_ops = &iscsi_stat_sess_err_item_ops,
.ct_attrs = iscsi_stat_sess_err_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Target Attributes Table
*/
CONFIGFS_EATTR_STRUCT(iscsi_stat_tgt_attr, iscsi_wwn_stat_grps);
#define ISCSI_STAT_TGT_ATTR(_name, _mode) \
static struct iscsi_stat_tgt_attr_attribute \
iscsi_stat_tgt_attr_##_name = \
__CONFIGFS_EATTR(_name, _mode, \
iscsi_stat_tgt-attr_show_attr_##_name, \
iscsi_stat_tgt_attr_store_attr_##_name);
#define ISCSI_STAT_TGT_ATTR_RO(_name) \
static struct iscsi_stat_tgt_attr_attribute \
iscsi_stat_tgt_attr_##_name = \
__CONFIGFS_EATTR_RO(_name, \
iscsi_stat_tgt_attr_show_attr_##_name);
static ssize_t iscsi_stat_tgt_attr_show_attr_inst(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_index);
}
ISCSI_STAT_TGT_ATTR_RO(inst);
static ssize_t iscsi_stat_tgt_attr_show_attr_indx(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_NODE_INDEX);
}
ISCSI_STAT_TGT_ATTR_RO(indx);
static ssize_t iscsi_stat_tgt_attr_show_attr_login_fails(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
u32 fail_count;
spin_lock(&lstat->lock);
fail_count = (lstat->redirects + lstat->authorize_fails +
lstat->authenticate_fails + lstat->negotiate_fails +
lstat->other_fails);
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%u\n", fail_count);
}
ISCSI_STAT_TGT_ATTR_RO(login_fails);
static ssize_t iscsi_stat_tgt_attr_show_attr_last_fail_time(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
u32 last_fail_time;
spin_lock(&lstat->lock);
last_fail_time = lstat->last_fail_time ?
(u32)(((u32)lstat->last_fail_time -
INITIAL_JIFFIES) * 100 / HZ) : 0;
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%u\n", last_fail_time);
}
ISCSI_STAT_TGT_ATTR_RO(last_fail_time);
static ssize_t iscsi_stat_tgt_attr_show_attr_last_fail_type(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
u32 last_fail_type;
spin_lock(&lstat->lock);
last_fail_type = lstat->last_fail_type;
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%u\n", last_fail_type);
}
ISCSI_STAT_TGT_ATTR_RO(last_fail_type);
static ssize_t iscsi_stat_tgt_attr_show_attr_fail_intr_name(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
unsigned char buf[224];
spin_lock(&lstat->lock);
snprintf(buf, 224, "%s", lstat->last_intr_fail_name[0] ?
lstat->last_intr_fail_name : NONE);
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%s\n", buf);
}
ISCSI_STAT_TGT_ATTR_RO(fail_intr_name);
static ssize_t iscsi_stat_tgt_attr_show_attr_fail_intr_addr_type(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
unsigned char buf[8];
spin_lock(&lstat->lock);
snprintf(buf, 8, "%s", (lstat->last_intr_fail_ip_addr != NULL) ?
"ipv6" : "ipv4");
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%s\n", buf);
}
ISCSI_STAT_TGT_ATTR_RO(fail_intr_addr_type);
static ssize_t iscsi_stat_tgt_attr_show_attr_fail_intr_addr(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
unsigned char buf[32];
spin_lock(&lstat->lock);
if (lstat->last_intr_fail_ip_family == AF_INET6)
snprintf(buf, 32, "[%s]", lstat->last_intr_fail_ip_addr);
else
snprintf(buf, 32, "%s", lstat->last_intr_fail_ip_addr);
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%s\n", buf);
}
ISCSI_STAT_TGT_ATTR_RO(fail_intr_addr);
CONFIGFS_EATTR_OPS(iscsi_stat_tgt_attr, iscsi_wwn_stat_grps,
iscsi_tgt_attr_group);
static struct configfs_attribute *iscsi_stat_tgt_attr_attrs[] = {
&iscsi_stat_tgt_attr_inst.attr,
&iscsi_stat_tgt_attr_indx.attr,
&iscsi_stat_tgt_attr_login_fails.attr,
&iscsi_stat_tgt_attr_last_fail_time.attr,
&iscsi_stat_tgt_attr_last_fail_type.attr,
&iscsi_stat_tgt_attr_fail_intr_name.attr,
&iscsi_stat_tgt_attr_fail_intr_addr_type.attr,
&iscsi_stat_tgt_attr_fail_intr_addr.attr,
NULL,
};
static struct configfs_item_operations iscsi_stat_tgt_attr_item_ops = {
.show_attribute = iscsi_stat_tgt_attr_attr_show,
.store_attribute = iscsi_stat_tgt_attr_attr_store,
};
struct config_item_type iscsi_stat_tgt_attr_cit = {
.ct_item_ops = &iscsi_stat_tgt_attr_item_ops,
.ct_attrs = iscsi_stat_tgt_attr_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Target Login Stats Table
*/
CONFIGFS_EATTR_STRUCT(iscsi_stat_login, iscsi_wwn_stat_grps);
#define ISCSI_STAT_LOGIN(_name, _mode) \
static struct iscsi_stat_login_attribute \
iscsi_stat_login_##_name = \
__CONFIGFS_EATTR(_name, _mode, \
iscsi_stat_login_show_attr_##_name, \
iscsi_stat_login_store_attr_##_name);
#define ISCSI_STAT_LOGIN_RO(_name) \
static struct iscsi_stat_login_attribute \
iscsi_stat_login_##_name = \
__CONFIGFS_EATTR_RO(_name, \
iscsi_stat_login_show_attr_##_name);
static ssize_t iscsi_stat_login_show_attr_inst(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_index);
}
ISCSI_STAT_LOGIN_RO(inst);
static ssize_t iscsi_stat_login_show_attr_indx(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_NODE_INDEX);
}
ISCSI_STAT_LOGIN_RO(indx);
static ssize_t iscsi_stat_login_show_attr_accepts(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->accepts);
spin_unlock(&lstat->lock);
return ret;
}
ISCSI_STAT_LOGIN_RO(accepts);
static ssize_t iscsi_stat_login_show_attr_other_fails(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->other_fails);
spin_unlock(&lstat->lock);
return ret;
}
ISCSI_STAT_LOGIN_RO(other_fails);
static ssize_t iscsi_stat_login_show_attr_redirects(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->redirects);
spin_unlock(&lstat->lock);
return ret;
}
ISCSI_STAT_LOGIN_RO(redirects);
static ssize_t iscsi_stat_login_show_attr_authorize_fails(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->authorize_fails);
spin_unlock(&lstat->lock);
return ret;
}
ISCSI_STAT_LOGIN_RO(authorize_fails);
static ssize_t iscsi_stat_login_show_attr_authenticate_fails(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->authenticate_fails);
spin_unlock(&lstat->lock);
return ret;
}
ISCSI_STAT_LOGIN_RO(authenticate_fails);
static ssize_t iscsi_stat_login_show_attr_negotiate_fails(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->negotiate_fails);
spin_unlock(&lstat->lock);
return ret;
}
ISCSI_STAT_LOGIN_RO(negotiate_fails);
CONFIGFS_EATTR_OPS(iscsi_stat_login, iscsi_wwn_stat_grps,
iscsi_login_stats_group);
static struct configfs_attribute *iscsi_stat_login_stats_attrs[] = {
&iscsi_stat_login_inst.attr,
&iscsi_stat_login_indx.attr,
&iscsi_stat_login_accepts.attr,
&iscsi_stat_login_other_fails.attr,
&iscsi_stat_login_redirects.attr,
&iscsi_stat_login_authorize_fails.attr,
&iscsi_stat_login_authenticate_fails.attr,
&iscsi_stat_login_negotiate_fails.attr,
NULL,
};
static struct configfs_item_operations iscsi_stat_login_stats_item_ops = {
.show_attribute = iscsi_stat_login_attr_show,
.store_attribute = iscsi_stat_login_attr_store,
};
struct config_item_type iscsi_stat_login_cit = {
.ct_item_ops = &iscsi_stat_login_stats_item_ops,
.ct_attrs = iscsi_stat_login_stats_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Target Logout Stats Table
*/
CONFIGFS_EATTR_STRUCT(iscsi_stat_logout, iscsi_wwn_stat_grps);
#define ISCSI_STAT_LOGOUT(_name, _mode) \
static struct iscsi_stat_logout_attribute \
iscsi_stat_logout_##_name = \
__CONFIGFS_EATTR(_name, _mode, \
iscsi_stat_logout_show_attr_##_name, \
iscsi_stat_logout_store_attr_##_name);
#define ISCSI_STAT_LOGOUT_RO(_name) \
static struct iscsi_stat_logout_attribute \
iscsi_stat_logout_##_name = \
__CONFIGFS_EATTR_RO(_name, \
iscsi_stat_logout_show_attr_##_name);
static ssize_t iscsi_stat_logout_show_attr_inst(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_index);
}
ISCSI_STAT_LOGOUT_RO(inst);
static ssize_t iscsi_stat_logout_show_attr_indx(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_NODE_INDEX);
}
ISCSI_STAT_LOGOUT_RO(indx);
static ssize_t iscsi_stat_logout_show_attr_normal_logouts(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_logout_stats *lstats = &tiqn->logout_stats;
return snprintf(page, PAGE_SIZE, "%u\n", lstats->normal_logouts);
}
ISCSI_STAT_LOGOUT_RO(normal_logouts);
static ssize_t iscsi_stat_logout_show_attr_abnormal_logouts(
struct iscsi_wwn_stat_grps *igrps, char *page)
{
struct iscsi_tiqn *tiqn = container_of(igrps,
struct iscsi_tiqn, tiqn_stat_grps);
struct iscsi_logout_stats *lstats = &tiqn->logout_stats;
return snprintf(page, PAGE_SIZE, "%u\n", lstats->abnormal_logouts);
}
ISCSI_STAT_LOGOUT_RO(abnormal_logouts);
CONFIGFS_EATTR_OPS(iscsi_stat_logout, iscsi_wwn_stat_grps,
iscsi_logout_stats_group);
static struct configfs_attribute *iscsi_stat_logout_stats_attrs[] = {
&iscsi_stat_logout_inst.attr,
&iscsi_stat_logout_indx.attr,
&iscsi_stat_logout_normal_logouts.attr,
&iscsi_stat_logout_abnormal_logouts.attr,
NULL,
};
static struct configfs_item_operations iscsi_stat_logout_stats_item_ops = {
.show_attribute = iscsi_stat_logout_attr_show,
.store_attribute = iscsi_stat_logout_attr_store,
};
struct config_item_type iscsi_stat_logout_cit = {
.ct_item_ops = &iscsi_stat_logout_stats_item_ops,
.ct_attrs = iscsi_stat_logout_stats_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Session Stats Table
*/
CONFIGFS_EATTR_STRUCT(iscsi_stat_sess, iscsi_node_stat_grps);
#define ISCSI_STAT_SESS(_name, _mode) \
static struct iscsi_stat_sess_attribute \
iscsi_stat_sess_##_name = \
__CONFIGFS_EATTR(_name, _mode, \
iscsi_stat_sess_show_attr_##_name, \
iscsi_stat_sess_store_attr_##_name);
#define ISCSI_STAT_SESS_RO(_name) \
static struct iscsi_stat_sess_attribute \
iscsi_stat_sess_##_name = \
__CONFIGFS_EATTR_RO(_name, \
iscsi_stat_sess_show_attr_##_name);
static ssize_t iscsi_stat_sess_show_attr_inst(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_wwn *wwn = acl->se_node_acl.se_tpg->se_tpg_wwn;
struct iscsi_tiqn *tiqn = container_of(wwn,
struct iscsi_tiqn, tiqn_wwn);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_index);
}
ISCSI_STAT_SESS_RO(inst);
static ssize_t iscsi_stat_sess_show_attr_node(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsi_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%u\n",
sess->sess_ops->SessionType ? 0 : ISCSI_NODE_INDEX);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
ISCSI_STAT_SESS_RO(node);
static ssize_t iscsi_stat_sess_show_attr_indx(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsi_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%u\n",
sess->session_index);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
ISCSI_STAT_SESS_RO(indx);
static ssize_t iscsi_stat_sess_show_attr_cmd_pdus(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsi_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%u\n", sess->cmd_pdus);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
ISCSI_STAT_SESS_RO(cmd_pdus);
static ssize_t iscsi_stat_sess_show_attr_rsp_pdus(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsi_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%u\n", sess->rsp_pdus);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
ISCSI_STAT_SESS_RO(rsp_pdus);
static ssize_t iscsi_stat_sess_show_attr_txdata_octs(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsi_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%llu\n",
(unsigned long long)sess->tx_data_octets);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
ISCSI_STAT_SESS_RO(txdata_octs);
static ssize_t iscsi_stat_sess_show_attr_rxdata_octs(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsi_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%llu\n",
(unsigned long long)sess->rx_data_octets);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
ISCSI_STAT_SESS_RO(rxdata_octs);
static ssize_t iscsi_stat_sess_show_attr_conn_digest_errors(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsi_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%u\n",
sess->conn_digest_errors);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
ISCSI_STAT_SESS_RO(conn_digest_errors);
static ssize_t iscsi_stat_sess_show_attr_conn_timeout_errors(
struct iscsi_node_stat_grps *igrps, char *page)
{
struct iscsi_node_acl *acl = container_of(igrps,
struct iscsi_node_acl, node_stat_grps);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsi_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = (struct iscsi_session *)se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%u\n",
sess->conn_timeout_errors);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
ISCSI_STAT_SESS_RO(conn_timeout_errors);
CONFIGFS_EATTR_OPS(iscsi_stat_sess, iscsi_node_stat_grps,
iscsi_sess_stats_group);
static struct configfs_attribute *iscsi_stat_sess_stats_attrs[] = {
&iscsi_stat_sess_inst.attr,
&iscsi_stat_sess_node.attr,
&iscsi_stat_sess_indx.attr,
&iscsi_stat_sess_cmd_pdus.attr,
&iscsi_stat_sess_rsp_pdus.attr,
&iscsi_stat_sess_txdata_octs.attr,
&iscsi_stat_sess_rxdata_octs.attr,
&iscsi_stat_sess_conn_digest_errors.attr,
&iscsi_stat_sess_conn_timeout_errors.attr,
NULL,
};
static struct configfs_item_operations iscsi_stat_sess_stats_item_ops = {
.show_attribute = iscsi_stat_sess_attr_show,
.store_attribute = iscsi_stat_sess_attr_store,
};
struct config_item_type iscsi_stat_sess_cit = {
.ct_item_ops = &iscsi_stat_sess_stats_item_ops,
.ct_attrs = iscsi_stat_sess_stats_attrs,
.ct_owner = THIS_MODULE,
};
#ifndef ISCSI_TARGET_STAT_H
#define ISCSI_TARGET_STAT_H
/*
* For struct iscsi_tiqn->tiqn_wwn default groups
*/
extern struct config_item_type iscsi_stat_instance_cit;
extern struct config_item_type iscsi_stat_sess_err_cit;
extern struct config_item_type iscsi_stat_tgt_attr_cit;
extern struct config_item_type iscsi_stat_login_cit;
extern struct config_item_type iscsi_stat_logout_cit;
/*
* For struct iscsi_session->se_sess default groups
*/
extern struct config_item_type iscsi_stat_sess_cit;
/* iSCSI session error types */
#define ISCSI_SESS_ERR_UNKNOWN 0
#define ISCSI_SESS_ERR_DIGEST 1
#define ISCSI_SESS_ERR_CXN_TIMEOUT 2
#define ISCSI_SESS_ERR_PDU_FORMAT 3
/* iSCSI session error stats */
struct iscsi_sess_err_stats {
spinlock_t lock;
u32 digest_errors;
u32 cxn_timeout_errors;
u32 pdu_format_errors;
u32 last_sess_failure_type;
char last_sess_fail_rem_name[224];
} ____cacheline_aligned;
/* iSCSI login failure types (sub oids) */
#define ISCSI_LOGIN_FAIL_OTHER 2
#define ISCSI_LOGIN_FAIL_REDIRECT 3
#define ISCSI_LOGIN_FAIL_AUTHORIZE 4
#define ISCSI_LOGIN_FAIL_AUTHENTICATE 5
#define ISCSI_LOGIN_FAIL_NEGOTIATE 6
/* iSCSI login stats */
struct iscsi_login_stats {
spinlock_t lock;
u32 accepts;
u32 other_fails;
u32 redirects;
u32 authorize_fails;
u32 authenticate_fails;
u32 negotiate_fails; /* used for notifications */
u64 last_fail_time; /* time stamp (jiffies) */
u32 last_fail_type;
int last_intr_fail_ip_family;
unsigned char last_intr_fail_ip_addr[IPV6_ADDRESS_SPACE];
char last_intr_fail_name[224];
} ____cacheline_aligned;
/* iSCSI logout stats */
struct iscsi_logout_stats {
spinlock_t lock;
u32 normal_logouts;
u32 abnormal_logouts;
} ____cacheline_aligned;
#endif /*** ISCSI_TARGET_STAT_H ***/
/*******************************************************************************
* This file contains the iSCSI Target specific Task Management functions.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <asm/unaligned.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include "iscsi_target_core.h"
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_datain_values.h"
#include "iscsi_target_device.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target_tmr.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
u8 iscsit_tmr_abort_task(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
struct iscsi_cmd *ref_cmd;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_tmr_req *tmr_req = cmd->tmr_req;
struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req;
struct iscsi_tm *hdr = (struct iscsi_tm *) buf;
ref_cmd = iscsit_find_cmd_from_itt(conn, hdr->rtt);
if (!ref_cmd) {
pr_err("Unable to locate RefTaskTag: 0x%08x on CID:"
" %hu.\n", hdr->rtt, conn->cid);
return ((hdr->refcmdsn >= conn->sess->exp_cmd_sn) &&
(hdr->refcmdsn <= conn->sess->max_cmd_sn)) ?
ISCSI_TMF_RSP_COMPLETE : ISCSI_TMF_RSP_NO_TASK;
}
if (ref_cmd->cmd_sn != hdr->refcmdsn) {
pr_err("RefCmdSN 0x%08x does not equal"
" task's CmdSN 0x%08x. Rejecting ABORT_TASK.\n",
hdr->refcmdsn, ref_cmd->cmd_sn);
return ISCSI_TMF_RSP_REJECTED;
}
se_tmr->ref_task_tag = hdr->rtt;
se_tmr->ref_cmd = &ref_cmd->se_cmd;
tmr_req->ref_cmd_sn = hdr->refcmdsn;
tmr_req->exp_data_sn = hdr->exp_datasn;
return ISCSI_TMF_RSP_COMPLETE;
}
/*
* Called from iscsit_handle_task_mgt_cmd().
*/
int iscsit_tmr_task_warm_reset(
struct iscsi_conn *conn,
struct iscsi_tmr_req *tmr_req,
unsigned char *buf)
{
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);
#if 0
struct iscsi_init_task_mgt_cmnd *hdr =
(struct iscsi_init_task_mgt_cmnd *) buf;
#endif
if (!na->tmr_warm_reset) {
pr_err("TMR Opcode TARGET_WARM_RESET authorization"
" failed for Initiator Node: %s\n",
sess->se_sess->se_node_acl->initiatorname);
return -1;
}
/*
* Do the real work in transport_generic_do_tmr().
*/
return 0;
}
int iscsit_tmr_task_cold_reset(
struct iscsi_conn *conn,
struct iscsi_tmr_req *tmr_req,
unsigned char *buf)
{
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);
if (!na->tmr_cold_reset) {
pr_err("TMR Opcode TARGET_COLD_RESET authorization"
" failed for Initiator Node: %s\n",
sess->se_sess->se_node_acl->initiatorname);
return -1;
}
/*
* Do the real work in transport_generic_do_tmr().
*/
return 0;
}
u8 iscsit_tmr_task_reassign(
struct iscsi_cmd *cmd,
unsigned char *buf)
{
struct iscsi_cmd *ref_cmd = NULL;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_conn_recovery *cr = NULL;
struct iscsi_tmr_req *tmr_req = cmd->tmr_req;
struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req;
struct iscsi_tm *hdr = (struct iscsi_tm *) buf;
int ret;
pr_debug("Got TASK_REASSIGN TMR ITT: 0x%08x,"
" RefTaskTag: 0x%08x, ExpDataSN: 0x%08x, CID: %hu\n",
hdr->itt, hdr->rtt, hdr->exp_datasn, conn->cid);
if (conn->sess->sess_ops->ErrorRecoveryLevel != 2) {
pr_err("TMR TASK_REASSIGN not supported in ERL<2,"
" ignoring request.\n");
return ISCSI_TMF_RSP_NOT_SUPPORTED;
}
ret = iscsit_find_cmd_for_recovery(conn->sess, &ref_cmd, &cr, hdr->rtt);
if (ret == -2) {
pr_err("Command ITT: 0x%08x is still alligent to CID:"
" %hu\n", ref_cmd->init_task_tag, cr->cid);
return ISCSI_TMF_RSP_TASK_ALLEGIANT;
} else if (ret == -1) {
pr_err("Unable to locate RefTaskTag: 0x%08x in"
" connection recovery command list.\n", hdr->rtt);
return ISCSI_TMF_RSP_NO_TASK;
}
/*
* Temporary check to prevent connection recovery for
* connections with a differing MaxRecvDataSegmentLength.
*/
if (cr->maxrecvdatasegmentlength !=
conn->conn_ops->MaxRecvDataSegmentLength) {
pr_err("Unable to perform connection recovery for"
" differing MaxRecvDataSegmentLength, rejecting"
" TMR TASK_REASSIGN.\n");
return ISCSI_TMF_RSP_REJECTED;
}
se_tmr->ref_task_tag = hdr->rtt;
se_tmr->ref_cmd = &ref_cmd->se_cmd;
se_tmr->ref_task_lun = get_unaligned_le64(&hdr->lun);
tmr_req->ref_cmd_sn = hdr->refcmdsn;
tmr_req->exp_data_sn = hdr->exp_datasn;
tmr_req->conn_recovery = cr;
tmr_req->task_reassign = 1;
/*
* Command can now be reassigned to a new connection.
* The task management response must be sent before the
* reassignment actually happens. See iscsi_tmr_post_handler().
*/
return ISCSI_TMF_RSP_COMPLETE;
}
static void iscsit_task_reassign_remove_cmd(
struct iscsi_cmd *cmd,
struct iscsi_conn_recovery *cr,
struct iscsi_session *sess)
{
int ret;
spin_lock(&cr->conn_recovery_cmd_lock);
ret = iscsit_remove_cmd_from_connection_recovery(cmd, sess);
spin_unlock(&cr->conn_recovery_cmd_lock);
if (!ret) {
pr_debug("iSCSI connection recovery successful for CID:"
" %hu on SID: %u\n", cr->cid, sess->sid);
iscsit_remove_active_connection_recovery_entry(cr, sess);
}
}
static int iscsit_task_reassign_complete_nop_out(
struct iscsi_tmr_req *tmr_req,
struct iscsi_conn *conn)
{
struct se_tmr_req *se_tmr = tmr_req->se_tmr_req;
struct se_cmd *se_cmd = se_tmr->ref_cmd;
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
struct iscsi_conn_recovery *cr;
if (!cmd->cr) {
pr_err("struct iscsi_conn_recovery pointer for ITT: 0x%08x"
" is NULL!\n", cmd->init_task_tag);
return -1;
}
cr = cmd->cr;
/*
* Reset the StatSN so a new one for this commands new connection
* will be assigned.
* Reset the ExpStatSN as well so we may receive Status SNACKs.
*/
cmd->stat_sn = cmd->exp_stat_sn = 0;
iscsit_task_reassign_remove_cmd(cmd, cr, conn->sess);
spin_lock_bh(&conn->cmd_lock);
list_add_tail(&cmd->i_list, &conn->conn_cmd_list);
spin_unlock_bh(&conn->cmd_lock);
cmd->i_state = ISTATE_SEND_NOPIN;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
static int iscsit_task_reassign_complete_write(
struct iscsi_cmd *cmd,
struct iscsi_tmr_req *tmr_req)
{
int no_build_r2ts = 0;
u32 length = 0, offset = 0;
struct iscsi_conn *conn = cmd->conn;
struct se_cmd *se_cmd = &cmd->se_cmd;
/*
* The Initiator must not send a R2T SNACK with a Begrun less than
* the TMR TASK_REASSIGN's ExpDataSN.
*/
if (!tmr_req->exp_data_sn) {
cmd->cmd_flags &= ~ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = 0;
} else {
cmd->cmd_flags |= ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = (tmr_req->exp_data_sn - 1);
}
/*
* The TMR TASK_REASSIGN's ExpDataSN contains the next R2TSN the
* Initiator is expecting. The Target controls all WRITE operations
* so if we have received all DataOUT we can safety ignore Initiator.
*/
if (cmd->cmd_flags & ICF_GOT_LAST_DATAOUT) {
if (!atomic_read(&cmd->transport_sent)) {
pr_debug("WRITE ITT: 0x%08x: t_state: %d"
" never sent to transport\n",
cmd->init_task_tag, cmd->se_cmd.t_state);
return transport_generic_handle_data(se_cmd);
}
cmd->i_state = ISTATE_SEND_STATUS;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
/*
* Special case to deal with DataSequenceInOrder=No and Non-Immeidate
* Unsolicited DataOut.
*/
if (cmd->unsolicited_data) {
cmd->unsolicited_data = 0;
offset = cmd->next_burst_len = cmd->write_data_done;
if ((conn->sess->sess_ops->FirstBurstLength - offset) >=
cmd->data_length) {
no_build_r2ts = 1;
length = (cmd->data_length - offset);
} else
length = (conn->sess->sess_ops->FirstBurstLength - offset);
spin_lock_bh(&cmd->r2t_lock);
if (iscsit_add_r2t_to_list(cmd, offset, length, 0, 0) < 0) {
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
cmd->outstanding_r2ts++;
spin_unlock_bh(&cmd->r2t_lock);
if (no_build_r2ts)
return 0;
}
/*
* iscsit_build_r2ts_for_cmd() can handle the rest from here.
*/
return iscsit_build_r2ts_for_cmd(cmd, conn, 2);
}
static int iscsit_task_reassign_complete_read(
struct iscsi_cmd *cmd,
struct iscsi_tmr_req *tmr_req)
{
struct iscsi_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct se_cmd *se_cmd = &cmd->se_cmd;
/*
* The Initiator must not send a Data SNACK with a BegRun less than
* the TMR TASK_REASSIGN's ExpDataSN.
*/
if (!tmr_req->exp_data_sn) {
cmd->cmd_flags &= ~ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = 0;
} else {
cmd->cmd_flags |= ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = (tmr_req->exp_data_sn - 1);
}
if (!atomic_read(&cmd->transport_sent)) {
pr_debug("READ ITT: 0x%08x: t_state: %d never sent to"
" transport\n", cmd->init_task_tag,
cmd->se_cmd.t_state);
transport_generic_handle_cdb(se_cmd);
return 0;
}
if (!atomic_read(&se_cmd->t_transport_complete)) {
pr_err("READ ITT: 0x%08x: t_state: %d, never returned"
" from transport\n", cmd->init_task_tag,
cmd->se_cmd.t_state);
return -1;
}
dr = iscsit_allocate_datain_req();
if (!dr)
return -1;
/*
* The TMR TASK_REASSIGN's ExpDataSN contains the next DataSN the
* Initiator is expecting.
*/
dr->data_sn = dr->begrun = tmr_req->exp_data_sn;
dr->runlength = 0;
dr->generate_recovery_values = 1;
dr->recovery = DATAIN_CONNECTION_RECOVERY;
iscsit_attach_datain_req(cmd, dr);
cmd->i_state = ISTATE_SEND_DATAIN;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
static int iscsit_task_reassign_complete_none(
struct iscsi_cmd *cmd,
struct iscsi_tmr_req *tmr_req)
{
struct iscsi_conn *conn = cmd->conn;
cmd->i_state = ISTATE_SEND_STATUS;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
static int iscsit_task_reassign_complete_scsi_cmnd(
struct iscsi_tmr_req *tmr_req,
struct iscsi_conn *conn)
{
struct se_tmr_req *se_tmr = tmr_req->se_tmr_req;
struct se_cmd *se_cmd = se_tmr->ref_cmd;
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
struct iscsi_conn_recovery *cr;
if (!cmd->cr) {
pr_err("struct iscsi_conn_recovery pointer for ITT: 0x%08x"
" is NULL!\n", cmd->init_task_tag);
return -1;
}
cr = cmd->cr;
/*
* Reset the StatSN so a new one for this commands new connection
* will be assigned.
* Reset the ExpStatSN as well so we may receive Status SNACKs.
*/
cmd->stat_sn = cmd->exp_stat_sn = 0;
iscsit_task_reassign_remove_cmd(cmd, cr, conn->sess);
spin_lock_bh(&conn->cmd_lock);
list_add_tail(&cmd->i_list, &conn->conn_cmd_list);
spin_unlock_bh(&conn->cmd_lock);
if (se_cmd->se_cmd_flags & SCF_SENT_CHECK_CONDITION) {
cmd->i_state = ISTATE_SEND_STATUS;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
switch (cmd->data_direction) {
case DMA_TO_DEVICE:
return iscsit_task_reassign_complete_write(cmd, tmr_req);
case DMA_FROM_DEVICE:
return iscsit_task_reassign_complete_read(cmd, tmr_req);
case DMA_NONE:
return iscsit_task_reassign_complete_none(cmd, tmr_req);
default:
pr_err("Unknown cmd->data_direction: 0x%02x\n",
cmd->data_direction);
return -1;
}
return 0;
}
static int iscsit_task_reassign_complete(
struct iscsi_tmr_req *tmr_req,
struct iscsi_conn *conn)
{
struct se_tmr_req *se_tmr = tmr_req->se_tmr_req;
struct se_cmd *se_cmd;
struct iscsi_cmd *cmd;
int ret = 0;
if (!se_tmr->ref_cmd) {
pr_err("TMR Request is missing a RefCmd struct iscsi_cmd.\n");
return -1;
}
se_cmd = se_tmr->ref_cmd;
cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
cmd->conn = conn;
switch (cmd->iscsi_opcode) {
case ISCSI_OP_NOOP_OUT:
ret = iscsit_task_reassign_complete_nop_out(tmr_req, conn);
break;
case ISCSI_OP_SCSI_CMD:
ret = iscsit_task_reassign_complete_scsi_cmnd(tmr_req, conn);
break;
default:
pr_err("Illegal iSCSI Opcode 0x%02x during"
" command realligence\n", cmd->iscsi_opcode);
return -1;
}
if (ret != 0)
return ret;
pr_debug("Completed connection realligence for Opcode: 0x%02x,"
" ITT: 0x%08x to CID: %hu.\n", cmd->iscsi_opcode,
cmd->init_task_tag, conn->cid);
return 0;
}
/*
* Handles special after-the-fact actions related to TMRs.
* Right now the only one that its really needed for is
* connection recovery releated TASK_REASSIGN.
*/
extern int iscsit_tmr_post_handler(struct iscsi_cmd *cmd, struct iscsi_conn *conn)
{
struct iscsi_tmr_req *tmr_req = cmd->tmr_req;
struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req;
if (tmr_req->task_reassign &&
(se_tmr->response == ISCSI_TMF_RSP_COMPLETE))
return iscsit_task_reassign_complete(tmr_req, conn);
return 0;
}
/*
* Nothing to do here, but leave it for good measure. :-)
*/
int iscsit_task_reassign_prepare_read(
struct iscsi_tmr_req *tmr_req,
struct iscsi_conn *conn)
{
return 0;
}
static void iscsit_task_reassign_prepare_unsolicited_dataout(
struct iscsi_cmd *cmd,
struct iscsi_conn *conn)
{
int i, j;
struct iscsi_pdu *pdu = NULL;
struct iscsi_seq *seq = NULL;
if (conn->sess->sess_ops->DataSequenceInOrder) {
cmd->data_sn = 0;
if (cmd->immediate_data)
cmd->r2t_offset += (cmd->first_burst_len -
cmd->seq_start_offset);
if (conn->sess->sess_ops->DataPDUInOrder) {
cmd->write_data_done -= (cmd->immediate_data) ?
(cmd->first_burst_len -
cmd->seq_start_offset) :
cmd->first_burst_len;
cmd->first_burst_len = 0;
return;
}
for (i = 0; i < cmd->pdu_count; i++) {
pdu = &cmd->pdu_list[i];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
if ((pdu->offset >= cmd->seq_start_offset) &&
((pdu->offset + pdu->length) <=
cmd->seq_end_offset)) {
cmd->first_burst_len -= pdu->length;
cmd->write_data_done -= pdu->length;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
} else {
for (i = 0; i < cmd->seq_count; i++) {
seq = &cmd->seq_list[i];
if (seq->type != SEQTYPE_UNSOLICITED)
continue;
cmd->write_data_done -=
(seq->offset - seq->orig_offset);
cmd->first_burst_len = 0;
seq->data_sn = 0;
seq->offset = seq->orig_offset;
seq->next_burst_len = 0;
seq->status = DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY;
if (conn->sess->sess_ops->DataPDUInOrder)
continue;
for (j = 0; j < seq->pdu_count; j++) {
pdu = &cmd->pdu_list[j+seq->pdu_start];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
}
}
int iscsit_task_reassign_prepare_write(
struct iscsi_tmr_req *tmr_req,
struct iscsi_conn *conn)
{
struct se_tmr_req *se_tmr = tmr_req->se_tmr_req;
struct se_cmd *se_cmd = se_tmr->ref_cmd;
struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
struct iscsi_pdu *pdu = NULL;
struct iscsi_r2t *r2t = NULL, *r2t_tmp;
int first_incomplete_r2t = 1, i = 0;
/*
* The command was in the process of receiving Unsolicited DataOUT when
* the connection failed.
*/
if (cmd->unsolicited_data)
iscsit_task_reassign_prepare_unsolicited_dataout(cmd, conn);
/*
* The Initiator is requesting R2Ts starting from zero, skip
* checking acknowledged R2Ts and start checking struct iscsi_r2ts
* greater than zero.
*/
if (!tmr_req->exp_data_sn)
goto drop_unacknowledged_r2ts;
/*
* We now check that the PDUs in DataOUT sequences below
* the TMR TASK_REASSIGN ExpDataSN (R2TSN the Initiator is
* expecting next) have all the DataOUT they require to complete
* the DataOUT sequence. First scan from R2TSN 0 to TMR
* TASK_REASSIGN ExpDataSN-1.
*
* If we have not received all DataOUT in question, we must
* make sure to make the appropriate changes to values in
* struct iscsi_cmd (and elsewhere depending on session parameters)
* so iscsit_build_r2ts_for_cmd() in iscsit_task_reassign_complete_write()
* will resend a new R2T for the DataOUT sequences in question.
*/
spin_lock_bh(&cmd->r2t_lock);
if (list_empty(&cmd->cmd_r2t_list)) {
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
list_for_each_entry(r2t, &cmd->cmd_r2t_list, r2t_list) {
if (r2t->r2t_sn >= tmr_req->exp_data_sn)
continue;
/*
* Safely ignore Recovery R2Ts and R2Ts that have completed
* DataOUT sequences.
*/
if (r2t->seq_complete)
continue;
if (r2t->recovery_r2t)
continue;
/*
* DataSequenceInOrder=Yes:
*
* Taking into account the iSCSI implementation requirement of
* MaxOutstandingR2T=1 while ErrorRecoveryLevel>0 and
* DataSequenceInOrder=Yes, we must take into consideration
* the following:
*
* DataSequenceInOrder=No:
*
* Taking into account that the Initiator controls the (possibly
* random) PDU Order in (possibly random) Sequence Order of
* DataOUT the target requests with R2Ts, we must take into
* consideration the following:
*
* DataPDUInOrder=Yes for DataSequenceInOrder=[Yes,No]:
*
* While processing non-complete R2T DataOUT sequence requests
* the Target will re-request only the total sequence length
* minus current received offset. This is because we must
* assume the initiator will continue sending DataOUT from the
* last PDU before the connection failed.
*
* DataPDUInOrder=No for DataSequenceInOrder=[Yes,No]:
*
* While processing non-complete R2T DataOUT sequence requests
* the Target will re-request the entire DataOUT sequence if
* any single PDU is missing from the sequence. This is because
* we have no logical method to determine the next PDU offset,
* and we must assume the Initiator will be sending any random
* PDU offset in the current sequence after TASK_REASSIGN
* has completed.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
if (!first_incomplete_r2t) {
cmd->r2t_offset -= r2t->xfer_len;
goto next;
}
if (conn->sess->sess_ops->DataPDUInOrder) {
cmd->data_sn = 0;
cmd->r2t_offset -= (r2t->xfer_len -
cmd->next_burst_len);
first_incomplete_r2t = 0;
goto next;
}
cmd->data_sn = 0;
cmd->r2t_offset -= r2t->xfer_len;
for (i = 0; i < cmd->pdu_count; i++) {
pdu = &cmd->pdu_list[i];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
if ((pdu->offset >= r2t->offset) &&
(pdu->offset < (r2t->offset +
r2t->xfer_len))) {
cmd->next_burst_len -= pdu->length;
cmd->write_data_done -= pdu->length;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
first_incomplete_r2t = 0;
} else {
struct iscsi_seq *seq;
seq = iscsit_get_seq_holder(cmd, r2t->offset,
r2t->xfer_len);
if (!seq) {
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
cmd->write_data_done -=
(seq->offset - seq->orig_offset);
seq->data_sn = 0;
seq->offset = seq->orig_offset;
seq->next_burst_len = 0;
seq->status = DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY;
cmd->seq_send_order--;
if (conn->sess->sess_ops->DataPDUInOrder)
goto next;
for (i = 0; i < seq->pdu_count; i++) {
pdu = &cmd->pdu_list[i+seq->pdu_start];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
next:
cmd->outstanding_r2ts--;
}
spin_unlock_bh(&cmd->r2t_lock);
/*
* We now drop all unacknowledged R2Ts, ie: ExpDataSN from TMR
* TASK_REASSIGN to the last R2T in the list.. We are also careful
* to check that the Initiator is not requesting R2Ts for DataOUT
* sequences it has already completed.
*
* Free each R2T in question and adjust values in struct iscsi_cmd
* accordingly so iscsit_build_r2ts_for_cmd() do the rest of
* the work after the TMR TASK_REASSIGN Response is sent.
*/
drop_unacknowledged_r2ts:
cmd->cmd_flags &= ~ICF_SENT_LAST_R2T;
cmd->r2t_sn = tmr_req->exp_data_sn;
spin_lock_bh(&cmd->r2t_lock);
list_for_each_entry_safe(r2t, r2t_tmp, &cmd->cmd_r2t_list, r2t_list) {
/*
* Skip up to the R2T Sequence number provided by the
* iSCSI TASK_REASSIGN TMR
*/
if (r2t->r2t_sn < tmr_req->exp_data_sn)
continue;
if (r2t->seq_complete) {
pr_err("Initiator is requesting R2Ts from"
" R2TSN: 0x%08x, but R2TSN: 0x%08x, Offset: %u,"
" Length: %u is already complete."
" BAD INITIATOR ERL=2 IMPLEMENTATION!\n",
tmr_req->exp_data_sn, r2t->r2t_sn,
r2t->offset, r2t->xfer_len);
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
if (r2t->recovery_r2t) {
iscsit_free_r2t(r2t, cmd);
continue;
}
/* DataSequenceInOrder=Yes:
*
* Taking into account the iSCSI implementation requirement of
* MaxOutstandingR2T=1 while ErrorRecoveryLevel>0 and
* DataSequenceInOrder=Yes, it's safe to subtract the R2Ts
* entire transfer length from the commands R2T offset marker.
*
* DataSequenceInOrder=No:
*
* We subtract the difference from struct iscsi_seq between the
* current offset and original offset from cmd->write_data_done
* for account for DataOUT PDUs already received. Then reset
* the current offset to the original and zero out the current
* burst length, to make sure we re-request the entire DataOUT
* sequence.
*/
if (conn->sess->sess_ops->DataSequenceInOrder)
cmd->r2t_offset -= r2t->xfer_len;
else
cmd->seq_send_order--;
cmd->outstanding_r2ts--;
iscsit_free_r2t(r2t, cmd);
}
spin_unlock_bh(&cmd->r2t_lock);
return 0;
}
/*
* Performs sanity checks TMR TASK_REASSIGN's ExpDataSN for
* a given struct iscsi_cmd.
*/
int iscsit_check_task_reassign_expdatasn(
struct iscsi_tmr_req *tmr_req,
struct iscsi_conn *conn)
{
struct se_tmr_req *se_tmr = tmr_req->se_tmr_req;
struct se_cmd *se_cmd = se_tmr->ref_cmd;
struct iscsi_cmd *ref_cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd);
if (ref_cmd->iscsi_opcode != ISCSI_OP_SCSI_CMD)
return 0;
if (se_cmd->se_cmd_flags & SCF_SENT_CHECK_CONDITION)
return 0;
if (ref_cmd->data_direction == DMA_NONE)
return 0;
/*
* For READs the TMR TASK_REASSIGNs ExpDataSN contains the next DataSN
* of DataIN the Initiator is expecting.
*
* Also check that the Initiator is not re-requesting DataIN that has
* already been acknowledged with a DataAck SNACK.
*/
if (ref_cmd->data_direction == DMA_FROM_DEVICE) {
if (tmr_req->exp_data_sn > ref_cmd->data_sn) {
pr_err("Received ExpDataSN: 0x%08x for READ"
" in TMR TASK_REASSIGN greater than command's"
" DataSN: 0x%08x.\n", tmr_req->exp_data_sn,
ref_cmd->data_sn);
return -1;
}
if ((ref_cmd->cmd_flags & ICF_GOT_DATACK_SNACK) &&
(tmr_req->exp_data_sn <= ref_cmd->acked_data_sn)) {
pr_err("Received ExpDataSN: 0x%08x for READ"
" in TMR TASK_REASSIGN for previously"
" acknowledged DataIN: 0x%08x,"
" protocol error\n", tmr_req->exp_data_sn,
ref_cmd->acked_data_sn);
return -1;
}
return iscsit_task_reassign_prepare_read(tmr_req, conn);
}
/*
* For WRITEs the TMR TASK_REASSIGNs ExpDataSN contains the next R2TSN
* for R2Ts the Initiator is expecting.
*
* Do the magic in iscsit_task_reassign_prepare_write().
*/
if (ref_cmd->data_direction == DMA_TO_DEVICE) {
if (tmr_req->exp_data_sn > ref_cmd->r2t_sn) {
pr_err("Received ExpDataSN: 0x%08x for WRITE"
" in TMR TASK_REASSIGN greater than command's"
" R2TSN: 0x%08x.\n", tmr_req->exp_data_sn,
ref_cmd->r2t_sn);
return -1;
}
return iscsit_task_reassign_prepare_write(tmr_req, conn);
}
pr_err("Unknown iSCSI data_direction: 0x%02x\n",
ref_cmd->data_direction);
return -1;
}
#ifndef ISCSI_TARGET_TMR_H
#define ISCSI_TARGET_TMR_H
extern u8 iscsit_tmr_abort_task(struct iscsi_cmd *, unsigned char *);
extern int iscsit_tmr_task_warm_reset(struct iscsi_conn *, struct iscsi_tmr_req *,
unsigned char *);
extern int iscsit_tmr_task_cold_reset(struct iscsi_conn *, struct iscsi_tmr_req *,
unsigned char *);
extern u8 iscsit_tmr_task_reassign(struct iscsi_cmd *, unsigned char *);
extern int iscsit_tmr_post_handler(struct iscsi_cmd *, struct iscsi_conn *);
extern int iscsit_check_task_reassign_expdatasn(struct iscsi_tmr_req *,
struct iscsi_conn *);
#endif /* ISCSI_TARGET_TMR_H */
/*******************************************************************************
* This file contains iSCSI Target Portal Group related functions.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include <target/target_core_fabric_ops.h>
#include <target/target_core_configfs.h>
#include <target/target_core_tpg.h>
#include "iscsi_target_core.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_login.h"
#include "iscsi_target_nodeattrib.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
#include "iscsi_target_parameters.h"
struct iscsi_portal_group *iscsit_alloc_portal_group(struct iscsi_tiqn *tiqn, u16 tpgt)
{
struct iscsi_portal_group *tpg;
tpg = kzalloc(sizeof(struct iscsi_portal_group), GFP_KERNEL);
if (!tpg) {
pr_err("Unable to allocate struct iscsi_portal_group\n");
return NULL;
}
tpg->tpgt = tpgt;
tpg->tpg_state = TPG_STATE_FREE;
tpg->tpg_tiqn = tiqn;
INIT_LIST_HEAD(&tpg->tpg_gnp_list);
INIT_LIST_HEAD(&tpg->tpg_list);
mutex_init(&tpg->tpg_access_lock);
mutex_init(&tpg->np_login_lock);
spin_lock_init(&tpg->tpg_state_lock);
spin_lock_init(&tpg->tpg_np_lock);
return tpg;
}
static void iscsit_set_default_tpg_attribs(struct iscsi_portal_group *);
int iscsit_load_discovery_tpg(void)
{
struct iscsi_param *param;
struct iscsi_portal_group *tpg;
int ret;
tpg = iscsit_alloc_portal_group(NULL, 1);
if (!tpg) {
pr_err("Unable to allocate struct iscsi_portal_group\n");
return -1;
}
ret = core_tpg_register(
&lio_target_fabric_configfs->tf_ops,
NULL, &tpg->tpg_se_tpg, (void *)tpg,
TRANSPORT_TPG_TYPE_DISCOVERY);
if (ret < 0) {
kfree(tpg);
return -1;
}
tpg->sid = 1; /* First Assigned LIO Session ID */
iscsit_set_default_tpg_attribs(tpg);
if (iscsi_create_default_params(&tpg->param_list) < 0)
goto out;
/*
* By default we disable authentication for discovery sessions,
* this can be changed with:
*
* /sys/kernel/config/target/iscsi/discovery_auth/enforce_discovery_auth
*/
param = iscsi_find_param_from_key(AUTHMETHOD, tpg->param_list);
if (!param)
goto out;
if (iscsi_update_param_value(param, "CHAP,None") < 0)
goto out;
tpg->tpg_attrib.authentication = 0;
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_ACTIVE;
spin_unlock(&tpg->tpg_state_lock);
iscsit_global->discovery_tpg = tpg;
pr_debug("CORE[0] - Allocated Discovery TPG\n");
return 0;
out:
if (tpg->sid == 1)
core_tpg_deregister(&tpg->tpg_se_tpg);
kfree(tpg);
return -1;
}
void iscsit_release_discovery_tpg(void)
{
struct iscsi_portal_group *tpg = iscsit_global->discovery_tpg;
if (!tpg)
return;
core_tpg_deregister(&tpg->tpg_se_tpg);
kfree(tpg);
iscsit_global->discovery_tpg = NULL;
}
struct iscsi_portal_group *iscsit_get_tpg_from_np(
struct iscsi_tiqn *tiqn,
struct iscsi_np *np)
{
struct iscsi_portal_group *tpg = NULL;
struct iscsi_tpg_np *tpg_np;
spin_lock(&tiqn->tiqn_tpg_lock);
list_for_each_entry(tpg, &tiqn->tiqn_tpg_list, tpg_list) {
spin_lock(&tpg->tpg_state_lock);
if (tpg->tpg_state == TPG_STATE_FREE) {
spin_unlock(&tpg->tpg_state_lock);
continue;
}
spin_unlock(&tpg->tpg_state_lock);
spin_lock(&tpg->tpg_np_lock);
list_for_each_entry(tpg_np, &tpg->tpg_gnp_list, tpg_np_list) {
if (tpg_np->tpg_np == np) {
spin_unlock(&tpg->tpg_np_lock);
spin_unlock(&tiqn->tiqn_tpg_lock);
return tpg;
}
}
spin_unlock(&tpg->tpg_np_lock);
}
spin_unlock(&tiqn->tiqn_tpg_lock);
return NULL;
}
int iscsit_get_tpg(
struct iscsi_portal_group *tpg)
{
int ret;
ret = mutex_lock_interruptible(&tpg->tpg_access_lock);
return ((ret != 0) || signal_pending(current)) ? -1 : 0;
}
void iscsit_put_tpg(struct iscsi_portal_group *tpg)
{
mutex_unlock(&tpg->tpg_access_lock);
}
static void iscsit_clear_tpg_np_login_thread(
struct iscsi_tpg_np *tpg_np,
struct iscsi_portal_group *tpg)
{
if (!tpg_np->tpg_np) {
pr_err("struct iscsi_tpg_np->tpg_np is NULL!\n");
return;
}
iscsit_reset_np_thread(tpg_np->tpg_np, tpg_np, tpg);
}
void iscsit_clear_tpg_np_login_threads(
struct iscsi_portal_group *tpg)
{
struct iscsi_tpg_np *tpg_np;
spin_lock(&tpg->tpg_np_lock);
list_for_each_entry(tpg_np, &tpg->tpg_gnp_list, tpg_np_list) {
if (!tpg_np->tpg_np) {
pr_err("struct iscsi_tpg_np->tpg_np is NULL!\n");
continue;
}
spin_unlock(&tpg->tpg_np_lock);
iscsit_clear_tpg_np_login_thread(tpg_np, tpg);
spin_lock(&tpg->tpg_np_lock);
}
spin_unlock(&tpg->tpg_np_lock);
}
void iscsit_tpg_dump_params(struct iscsi_portal_group *tpg)
{
iscsi_print_params(tpg->param_list);
}
static void iscsit_set_default_tpg_attribs(struct iscsi_portal_group *tpg)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
a->authentication = TA_AUTHENTICATION;
a->login_timeout = TA_LOGIN_TIMEOUT;
a->netif_timeout = TA_NETIF_TIMEOUT;
a->default_cmdsn_depth = TA_DEFAULT_CMDSN_DEPTH;
a->generate_node_acls = TA_GENERATE_NODE_ACLS;
a->cache_dynamic_acls = TA_CACHE_DYNAMIC_ACLS;
a->demo_mode_write_protect = TA_DEMO_MODE_WRITE_PROTECT;
a->prod_mode_write_protect = TA_PROD_MODE_WRITE_PROTECT;
}
int iscsit_tpg_add_portal_group(struct iscsi_tiqn *tiqn, struct iscsi_portal_group *tpg)
{
if (tpg->tpg_state != TPG_STATE_FREE) {
pr_err("Unable to add iSCSI Target Portal Group: %d"
" while not in TPG_STATE_FREE state.\n", tpg->tpgt);
return -EEXIST;
}
iscsit_set_default_tpg_attribs(tpg);
if (iscsi_create_default_params(&tpg->param_list) < 0)
goto err_out;
ISCSI_TPG_ATTRIB(tpg)->tpg = tpg;
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_INACTIVE;
spin_unlock(&tpg->tpg_state_lock);
spin_lock(&tiqn->tiqn_tpg_lock);
list_add_tail(&tpg->tpg_list, &tiqn->tiqn_tpg_list);
tiqn->tiqn_ntpgs++;
pr_debug("CORE[%s]_TPG[%hu] - Added iSCSI Target Portal Group\n",
tiqn->tiqn, tpg->tpgt);
spin_unlock(&tiqn->tiqn_tpg_lock);
return 0;
err_out:
if (tpg->param_list) {
iscsi_release_param_list(tpg->param_list);
tpg->param_list = NULL;
}
kfree(tpg);
return -ENOMEM;
}
int iscsit_tpg_del_portal_group(
struct iscsi_tiqn *tiqn,
struct iscsi_portal_group *tpg,
int force)
{
u8 old_state = tpg->tpg_state;
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_INACTIVE;
spin_unlock(&tpg->tpg_state_lock);
if (iscsit_release_sessions_for_tpg(tpg, force) < 0) {
pr_err("Unable to delete iSCSI Target Portal Group:"
" %hu while active sessions exist, and force=0\n",
tpg->tpgt);
tpg->tpg_state = old_state;
return -EPERM;
}
core_tpg_clear_object_luns(&tpg->tpg_se_tpg);
if (tpg->param_list) {
iscsi_release_param_list(tpg->param_list);
tpg->param_list = NULL;
}
core_tpg_deregister(&tpg->tpg_se_tpg);
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_FREE;
spin_unlock(&tpg->tpg_state_lock);
spin_lock(&tiqn->tiqn_tpg_lock);
tiqn->tiqn_ntpgs--;
list_del(&tpg->tpg_list);
spin_unlock(&tiqn->tiqn_tpg_lock);
pr_debug("CORE[%s]_TPG[%hu] - Deleted iSCSI Target Portal Group\n",
tiqn->tiqn, tpg->tpgt);
kfree(tpg);
return 0;
}
int iscsit_tpg_enable_portal_group(struct iscsi_portal_group *tpg)
{
struct iscsi_param *param;
struct iscsi_tiqn *tiqn = tpg->tpg_tiqn;
spin_lock(&tpg->tpg_state_lock);
if (tpg->tpg_state == TPG_STATE_ACTIVE) {
pr_err("iSCSI target portal group: %hu is already"
" active, ignoring request.\n", tpg->tpgt);
spin_unlock(&tpg->tpg_state_lock);
return -EINVAL;
}
/*
* Make sure that AuthMethod does not contain None as an option
* unless explictly disabled. Set the default to CHAP if authentication
* is enforced (as per default), and remove the NONE option.
*/
param = iscsi_find_param_from_key(AUTHMETHOD, tpg->param_list);
if (!param) {
spin_unlock(&tpg->tpg_state_lock);
return -ENOMEM;
}
if (ISCSI_TPG_ATTRIB(tpg)->authentication) {
if (!strcmp(param->value, NONE))
if (iscsi_update_param_value(param, CHAP) < 0) {
spin_unlock(&tpg->tpg_state_lock);
return -ENOMEM;
}
if (iscsit_ta_authentication(tpg, 1) < 0) {
spin_unlock(&tpg->tpg_state_lock);
return -ENOMEM;
}
}
tpg->tpg_state = TPG_STATE_ACTIVE;
spin_unlock(&tpg->tpg_state_lock);
spin_lock(&tiqn->tiqn_tpg_lock);
tiqn->tiqn_active_tpgs++;
pr_debug("iSCSI_TPG[%hu] - Enabled iSCSI Target Portal Group\n",
tpg->tpgt);
spin_unlock(&tiqn->tiqn_tpg_lock);
return 0;
}
int iscsit_tpg_disable_portal_group(struct iscsi_portal_group *tpg, int force)
{
struct iscsi_tiqn *tiqn;
u8 old_state = tpg->tpg_state;
spin_lock(&tpg->tpg_state_lock);
if (tpg->tpg_state == TPG_STATE_INACTIVE) {
pr_err("iSCSI Target Portal Group: %hu is already"
" inactive, ignoring request.\n", tpg->tpgt);
spin_unlock(&tpg->tpg_state_lock);
return -EINVAL;
}
tpg->tpg_state = TPG_STATE_INACTIVE;
spin_unlock(&tpg->tpg_state_lock);
iscsit_clear_tpg_np_login_threads(tpg);
if (iscsit_release_sessions_for_tpg(tpg, force) < 0) {
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = old_state;
spin_unlock(&tpg->tpg_state_lock);
pr_err("Unable to disable iSCSI Target Portal Group:"
" %hu while active sessions exist, and force=0\n",
tpg->tpgt);
return -EPERM;
}
tiqn = tpg->tpg_tiqn;
if (!tiqn || (tpg == iscsit_global->discovery_tpg))
return 0;
spin_lock(&tiqn->tiqn_tpg_lock);
tiqn->tiqn_active_tpgs--;
pr_debug("iSCSI_TPG[%hu] - Disabled iSCSI Target Portal Group\n",
tpg->tpgt);
spin_unlock(&tiqn->tiqn_tpg_lock);
return 0;
}
struct iscsi_node_attrib *iscsit_tpg_get_node_attrib(
struct iscsi_session *sess)
{
struct se_session *se_sess = sess->se_sess;
struct se_node_acl *se_nacl = se_sess->se_node_acl;
struct iscsi_node_acl *acl = container_of(se_nacl, struct iscsi_node_acl,
se_node_acl);
return &acl->node_attrib;
}
struct iscsi_tpg_np *iscsit_tpg_locate_child_np(
struct iscsi_tpg_np *tpg_np,
int network_transport)
{
struct iscsi_tpg_np *tpg_np_child, *tpg_np_child_tmp;
spin_lock(&tpg_np->tpg_np_parent_lock);
list_for_each_entry_safe(tpg_np_child, tpg_np_child_tmp,
&tpg_np->tpg_np_parent_list, tpg_np_child_list) {
if (tpg_np_child->tpg_np->np_network_transport ==
network_transport) {
spin_unlock(&tpg_np->tpg_np_parent_lock);
return tpg_np_child;
}
}
spin_unlock(&tpg_np->tpg_np_parent_lock);
return NULL;
}
struct iscsi_tpg_np *iscsit_tpg_add_network_portal(
struct iscsi_portal_group *tpg,
struct __kernel_sockaddr_storage *sockaddr,
char *ip_str,
struct iscsi_tpg_np *tpg_np_parent,
int network_transport)
{
struct iscsi_np *np;
struct iscsi_tpg_np *tpg_np;
tpg_np = kzalloc(sizeof(struct iscsi_tpg_np), GFP_KERNEL);
if (!tpg_np) {
pr_err("Unable to allocate memory for"
" struct iscsi_tpg_np.\n");
return ERR_PTR(-ENOMEM);
}
np = iscsit_add_np(sockaddr, ip_str, network_transport);
if (IS_ERR(np)) {
kfree(tpg_np);
return ERR_CAST(np);
}
INIT_LIST_HEAD(&tpg_np->tpg_np_list);
INIT_LIST_HEAD(&tpg_np->tpg_np_child_list);
INIT_LIST_HEAD(&tpg_np->tpg_np_parent_list);
spin_lock_init(&tpg_np->tpg_np_parent_lock);
tpg_np->tpg_np = np;
tpg_np->tpg = tpg;
spin_lock(&tpg->tpg_np_lock);
list_add_tail(&tpg_np->tpg_np_list, &tpg->tpg_gnp_list);
tpg->num_tpg_nps++;
if (tpg->tpg_tiqn)
tpg->tpg_tiqn->tiqn_num_tpg_nps++;
spin_unlock(&tpg->tpg_np_lock);
if (tpg_np_parent) {
tpg_np->tpg_np_parent = tpg_np_parent;
spin_lock(&tpg_np_parent->tpg_np_parent_lock);
list_add_tail(&tpg_np->tpg_np_child_list,
&tpg_np_parent->tpg_np_parent_list);
spin_unlock(&tpg_np_parent->tpg_np_parent_lock);
}
pr_debug("CORE[%s] - Added Network Portal: %s:%hu,%hu on %s\n",
tpg->tpg_tiqn->tiqn, np->np_ip, np->np_port, tpg->tpgt,
(np->np_network_transport == ISCSI_TCP) ? "TCP" : "SCTP");
return tpg_np;
}
static int iscsit_tpg_release_np(
struct iscsi_tpg_np *tpg_np,
struct iscsi_portal_group *tpg,
struct iscsi_np *np)
{
iscsit_clear_tpg_np_login_thread(tpg_np, tpg);
pr_debug("CORE[%s] - Removed Network Portal: %s:%hu,%hu on %s\n",
tpg->tpg_tiqn->tiqn, np->np_ip, np->np_port, tpg->tpgt,
(np->np_network_transport == ISCSI_TCP) ? "TCP" : "SCTP");
tpg_np->tpg_np = NULL;
tpg_np->tpg = NULL;
kfree(tpg_np);
/*
* iscsit_del_np() will shutdown struct iscsi_np when last TPG reference is released.
*/
return iscsit_del_np(np);
}
int iscsit_tpg_del_network_portal(
struct iscsi_portal_group *tpg,
struct iscsi_tpg_np *tpg_np)
{
struct iscsi_np *np;
struct iscsi_tpg_np *tpg_np_child, *tpg_np_child_tmp;
int ret = 0;
np = tpg_np->tpg_np;
if (!np) {
pr_err("Unable to locate struct iscsi_np from"
" struct iscsi_tpg_np\n");
return -EINVAL;
}
if (!tpg_np->tpg_np_parent) {
/*
* We are the parent tpg network portal. Release all of the
* child tpg_np's (eg: the non ISCSI_TCP ones) on our parent
* list first.
*/
list_for_each_entry_safe(tpg_np_child, tpg_np_child_tmp,
&tpg_np->tpg_np_parent_list,
tpg_np_child_list) {
ret = iscsit_tpg_del_network_portal(tpg, tpg_np_child);
if (ret < 0)
pr_err("iscsit_tpg_del_network_portal()"
" failed: %d\n", ret);
}
} else {
/*
* We are not the parent ISCSI_TCP tpg network portal. Release
* our own network portals from the child list.
*/
spin_lock(&tpg_np->tpg_np_parent->tpg_np_parent_lock);
list_del(&tpg_np->tpg_np_child_list);
spin_unlock(&tpg_np->tpg_np_parent->tpg_np_parent_lock);
}
spin_lock(&tpg->tpg_np_lock);
list_del(&tpg_np->tpg_np_list);
tpg->num_tpg_nps--;
if (tpg->tpg_tiqn)
tpg->tpg_tiqn->tiqn_num_tpg_nps--;
spin_unlock(&tpg->tpg_np_lock);
return iscsit_tpg_release_np(tpg_np, tpg, np);
}
int iscsit_tpg_set_initiator_node_queue_depth(
struct iscsi_portal_group *tpg,
unsigned char *initiatorname,
u32 queue_depth,
int force)
{
return core_tpg_set_initiator_node_queue_depth(&tpg->tpg_se_tpg,
initiatorname, queue_depth, force);
}
int iscsit_ta_authentication(struct iscsi_portal_group *tpg, u32 authentication)
{
unsigned char buf1[256], buf2[256], *none = NULL;
int len;
struct iscsi_param *param;
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((authentication != 1) && (authentication != 0)) {
pr_err("Illegal value for authentication parameter:"
" %u, ignoring request.\n", authentication);
return -1;
}
memset(buf1, 0, sizeof(buf1));
memset(buf2, 0, sizeof(buf2));
param = iscsi_find_param_from_key(AUTHMETHOD, tpg->param_list);
if (!param)
return -EINVAL;
if (authentication) {
snprintf(buf1, sizeof(buf1), "%s", param->value);
none = strstr(buf1, NONE);
if (!none)
goto out;
if (!strncmp(none + 4, ",", 1)) {
if (!strcmp(buf1, none))
sprintf(buf2, "%s", none+5);
else {
none--;
*none = '\0';
len = sprintf(buf2, "%s", buf1);
none += 5;
sprintf(buf2 + len, "%s", none);
}
} else {
none--;
*none = '\0';
sprintf(buf2, "%s", buf1);
}
if (iscsi_update_param_value(param, buf2) < 0)
return -EINVAL;
} else {
snprintf(buf1, sizeof(buf1), "%s", param->value);
none = strstr(buf1, NONE);
if ((none))
goto out;
strncat(buf1, ",", strlen(","));
strncat(buf1, NONE, strlen(NONE));
if (iscsi_update_param_value(param, buf1) < 0)
return -EINVAL;
}
out:
a->authentication = authentication;
pr_debug("%s iSCSI Authentication Methods for TPG: %hu.\n",
a->authentication ? "Enforcing" : "Disabling", tpg->tpgt);
return 0;
}
int iscsit_ta_login_timeout(
struct iscsi_portal_group *tpg,
u32 login_timeout)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if (login_timeout > TA_LOGIN_TIMEOUT_MAX) {
pr_err("Requested Login Timeout %u larger than maximum"
" %u\n", login_timeout, TA_LOGIN_TIMEOUT_MAX);
return -EINVAL;
} else if (login_timeout < TA_LOGIN_TIMEOUT_MIN) {
pr_err("Requested Logout Timeout %u smaller than"
" minimum %u\n", login_timeout, TA_LOGIN_TIMEOUT_MIN);
return -EINVAL;
}
a->login_timeout = login_timeout;
pr_debug("Set Logout Timeout to %u for Target Portal Group"
" %hu\n", a->login_timeout, tpg->tpgt);
return 0;
}
int iscsit_ta_netif_timeout(
struct iscsi_portal_group *tpg,
u32 netif_timeout)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if (netif_timeout > TA_NETIF_TIMEOUT_MAX) {
pr_err("Requested Network Interface Timeout %u larger"
" than maximum %u\n", netif_timeout,
TA_NETIF_TIMEOUT_MAX);
return -EINVAL;
} else if (netif_timeout < TA_NETIF_TIMEOUT_MIN) {
pr_err("Requested Network Interface Timeout %u smaller"
" than minimum %u\n", netif_timeout,
TA_NETIF_TIMEOUT_MIN);
return -EINVAL;
}
a->netif_timeout = netif_timeout;
pr_debug("Set Network Interface Timeout to %u for"
" Target Portal Group %hu\n", a->netif_timeout, tpg->tpgt);
return 0;
}
int iscsit_ta_generate_node_acls(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->generate_node_acls = flag;
pr_debug("iSCSI_TPG[%hu] - Generate Initiator Portal Group ACLs: %s\n",
tpg->tpgt, (a->generate_node_acls) ? "Enabled" : "Disabled");
return 0;
}
int iscsit_ta_default_cmdsn_depth(
struct iscsi_portal_group *tpg,
u32 tcq_depth)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if (tcq_depth > TA_DEFAULT_CMDSN_DEPTH_MAX) {
pr_err("Requested Default Queue Depth: %u larger"
" than maximum %u\n", tcq_depth,
TA_DEFAULT_CMDSN_DEPTH_MAX);
return -EINVAL;
} else if (tcq_depth < TA_DEFAULT_CMDSN_DEPTH_MIN) {
pr_err("Requested Default Queue Depth: %u smaller"
" than minimum %u\n", tcq_depth,
TA_DEFAULT_CMDSN_DEPTH_MIN);
return -EINVAL;
}
a->default_cmdsn_depth = tcq_depth;
pr_debug("iSCSI_TPG[%hu] - Set Default CmdSN TCQ Depth to %u\n",
tpg->tpgt, a->default_cmdsn_depth);
return 0;
}
int iscsit_ta_cache_dynamic_acls(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->cache_dynamic_acls = flag;
pr_debug("iSCSI_TPG[%hu] - Cache Dynamic Initiator Portal Group"
" ACLs %s\n", tpg->tpgt, (a->cache_dynamic_acls) ?
"Enabled" : "Disabled");
return 0;
}
int iscsit_ta_demo_mode_write_protect(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->demo_mode_write_protect = flag;
pr_debug("iSCSI_TPG[%hu] - Demo Mode Write Protect bit: %s\n",
tpg->tpgt, (a->demo_mode_write_protect) ? "ON" : "OFF");
return 0;
}
int iscsit_ta_prod_mode_write_protect(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->prod_mode_write_protect = flag;
pr_debug("iSCSI_TPG[%hu] - Production Mode Write Protect bit:"
" %s\n", tpg->tpgt, (a->prod_mode_write_protect) ?
"ON" : "OFF");
return 0;
}
#ifndef ISCSI_TARGET_TPG_H
#define ISCSI_TARGET_TPG_H
extern struct iscsi_portal_group *iscsit_alloc_portal_group(struct iscsi_tiqn *, u16);
extern int iscsit_load_discovery_tpg(void);
extern void iscsit_release_discovery_tpg(void);
extern struct iscsi_portal_group *iscsit_get_tpg_from_np(struct iscsi_tiqn *,
struct iscsi_np *);
extern int iscsit_get_tpg(struct iscsi_portal_group *);
extern void iscsit_put_tpg(struct iscsi_portal_group *);
extern void iscsit_clear_tpg_np_login_threads(struct iscsi_portal_group *);
extern void iscsit_tpg_dump_params(struct iscsi_portal_group *);
extern int iscsit_tpg_add_portal_group(struct iscsi_tiqn *, struct iscsi_portal_group *);
extern int iscsit_tpg_del_portal_group(struct iscsi_tiqn *, struct iscsi_portal_group *,
int);
extern int iscsit_tpg_enable_portal_group(struct iscsi_portal_group *);
extern int iscsit_tpg_disable_portal_group(struct iscsi_portal_group *, int);
extern struct iscsi_node_acl *iscsit_tpg_add_initiator_node_acl(
struct iscsi_portal_group *, const char *, u32);
extern void iscsit_tpg_del_initiator_node_acl(struct iscsi_portal_group *,
struct se_node_acl *);
extern struct iscsi_node_attrib *iscsit_tpg_get_node_attrib(struct iscsi_session *);
extern void iscsit_tpg_del_external_nps(struct iscsi_tpg_np *);
extern struct iscsi_tpg_np *iscsit_tpg_locate_child_np(struct iscsi_tpg_np *, int);
extern struct iscsi_tpg_np *iscsit_tpg_add_network_portal(struct iscsi_portal_group *,
struct __kernel_sockaddr_storage *, char *, struct iscsi_tpg_np *,
int);
extern int iscsit_tpg_del_network_portal(struct iscsi_portal_group *,
struct iscsi_tpg_np *);
extern int iscsit_tpg_set_initiator_node_queue_depth(struct iscsi_portal_group *,
unsigned char *, u32, int);
extern int iscsit_ta_authentication(struct iscsi_portal_group *, u32);
extern int iscsit_ta_login_timeout(struct iscsi_portal_group *, u32);
extern int iscsit_ta_netif_timeout(struct iscsi_portal_group *, u32);
extern int iscsit_ta_generate_node_acls(struct iscsi_portal_group *, u32);
extern int iscsit_ta_default_cmdsn_depth(struct iscsi_portal_group *, u32);
extern int iscsit_ta_cache_dynamic_acls(struct iscsi_portal_group *, u32);
extern int iscsit_ta_demo_mode_write_protect(struct iscsi_portal_group *, u32);
extern int iscsit_ta_prod_mode_write_protect(struct iscsi_portal_group *, u32);
#endif /* ISCSI_TARGET_TPG_H */
/*******************************************************************************
* This file contains the iSCSI Login Thread and Thread Queue functions.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/bitmap.h>
#include "iscsi_target_core.h"
#include "iscsi_target_tq.h"
#include "iscsi_target.h"
static LIST_HEAD(active_ts_list);
static LIST_HEAD(inactive_ts_list);
static DEFINE_SPINLOCK(active_ts_lock);
static DEFINE_SPINLOCK(inactive_ts_lock);
static DEFINE_SPINLOCK(ts_bitmap_lock);
static void iscsi_add_ts_to_active_list(struct iscsi_thread_set *ts)
{
spin_lock(&active_ts_lock);
list_add_tail(&ts->ts_list, &active_ts_list);
iscsit_global->active_ts++;
spin_unlock(&active_ts_lock);
}
extern void iscsi_add_ts_to_inactive_list(struct iscsi_thread_set *ts)
{
spin_lock(&inactive_ts_lock);
list_add_tail(&ts->ts_list, &inactive_ts_list);
iscsit_global->inactive_ts++;
spin_unlock(&inactive_ts_lock);
}
static void iscsi_del_ts_from_active_list(struct iscsi_thread_set *ts)
{
spin_lock(&active_ts_lock);
list_del(&ts->ts_list);
iscsit_global->active_ts--;
spin_unlock(&active_ts_lock);
}
static struct iscsi_thread_set *iscsi_get_ts_from_inactive_list(void)
{
struct iscsi_thread_set *ts;
spin_lock(&inactive_ts_lock);
if (list_empty(&inactive_ts_list)) {
spin_unlock(&inactive_ts_lock);
return NULL;
}
list_for_each_entry(ts, &inactive_ts_list, ts_list)
break;
list_del(&ts->ts_list);
iscsit_global->inactive_ts--;
spin_unlock(&inactive_ts_lock);
return ts;
}
extern int iscsi_allocate_thread_sets(u32 thread_pair_count)
{
int allocated_thread_pair_count = 0, i, thread_id;
struct iscsi_thread_set *ts = NULL;
for (i = 0; i < thread_pair_count; i++) {
ts = kzalloc(sizeof(struct iscsi_thread_set), GFP_KERNEL);
if (!ts) {
pr_err("Unable to allocate memory for"
" thread set.\n");
return allocated_thread_pair_count;
}
/*
* Locate the next available regision in the thread_set_bitmap
*/
spin_lock(&ts_bitmap_lock);
thread_id = bitmap_find_free_region(iscsit_global->ts_bitmap,
iscsit_global->ts_bitmap_count, get_order(1));
spin_unlock(&ts_bitmap_lock);
if (thread_id < 0) {
pr_err("bitmap_find_free_region() failed for"
" thread_set_bitmap\n");
kfree(ts);
return allocated_thread_pair_count;
}
ts->thread_id = thread_id;
ts->status = ISCSI_THREAD_SET_FREE;
INIT_LIST_HEAD(&ts->ts_list);
spin_lock_init(&ts->ts_state_lock);
init_completion(&ts->rx_post_start_comp);
init_completion(&ts->tx_post_start_comp);
init_completion(&ts->rx_restart_comp);
init_completion(&ts->tx_restart_comp);
init_completion(&ts->rx_start_comp);
init_completion(&ts->tx_start_comp);
ts->create_threads = 1;
ts->tx_thread = kthread_run(iscsi_target_tx_thread, ts, "%s",
ISCSI_TX_THREAD_NAME);
if (IS_ERR(ts->tx_thread)) {
dump_stack();
pr_err("Unable to start iscsi_target_tx_thread\n");
break;
}
ts->rx_thread = kthread_run(iscsi_target_rx_thread, ts, "%s",
ISCSI_RX_THREAD_NAME);
if (IS_ERR(ts->rx_thread)) {
kthread_stop(ts->tx_thread);
pr_err("Unable to start iscsi_target_rx_thread\n");
break;
}
ts->create_threads = 0;
iscsi_add_ts_to_inactive_list(ts);
allocated_thread_pair_count++;
}
pr_debug("Spawned %d thread set(s) (%d total threads).\n",
allocated_thread_pair_count, allocated_thread_pair_count * 2);
return allocated_thread_pair_count;
}
extern void iscsi_deallocate_thread_sets(void)
{
u32 released_count = 0;
struct iscsi_thread_set *ts = NULL;
while ((ts = iscsi_get_ts_from_inactive_list())) {
spin_lock_bh(&ts->ts_state_lock);
ts->status = ISCSI_THREAD_SET_DIE;
spin_unlock_bh(&ts->ts_state_lock);
if (ts->rx_thread) {
send_sig(SIGINT, ts->rx_thread, 1);
kthread_stop(ts->rx_thread);
}
if (ts->tx_thread) {
send_sig(SIGINT, ts->tx_thread, 1);
kthread_stop(ts->tx_thread);
}
/*
* Release this thread_id in the thread_set_bitmap
*/
spin_lock(&ts_bitmap_lock);
bitmap_release_region(iscsit_global->ts_bitmap,
ts->thread_id, get_order(1));
spin_unlock(&ts_bitmap_lock);
released_count++;
kfree(ts);
}
if (released_count)
pr_debug("Stopped %d thread set(s) (%d total threads)."
"\n", released_count, released_count * 2);
}
static void iscsi_deallocate_extra_thread_sets(void)
{
u32 orig_count, released_count = 0;
struct iscsi_thread_set *ts = NULL;
orig_count = TARGET_THREAD_SET_COUNT;
while ((iscsit_global->inactive_ts + 1) > orig_count) {
ts = iscsi_get_ts_from_inactive_list();
if (!ts)
break;
spin_lock_bh(&ts->ts_state_lock);
ts->status = ISCSI_THREAD_SET_DIE;
spin_unlock_bh(&ts->ts_state_lock);
if (ts->rx_thread) {
send_sig(SIGINT, ts->rx_thread, 1);
kthread_stop(ts->rx_thread);
}
if (ts->tx_thread) {
send_sig(SIGINT, ts->tx_thread, 1);
kthread_stop(ts->tx_thread);
}
/*
* Release this thread_id in the thread_set_bitmap
*/
spin_lock(&ts_bitmap_lock);
bitmap_release_region(iscsit_global->ts_bitmap,
ts->thread_id, get_order(1));
spin_unlock(&ts_bitmap_lock);
released_count++;
kfree(ts);
}
if (released_count) {
pr_debug("Stopped %d thread set(s) (%d total threads)."
"\n", released_count, released_count * 2);
}
}
void iscsi_activate_thread_set(struct iscsi_conn *conn, struct iscsi_thread_set *ts)
{
iscsi_add_ts_to_active_list(ts);
spin_lock_bh(&ts->ts_state_lock);
conn->thread_set = ts;
ts->conn = conn;
spin_unlock_bh(&ts->ts_state_lock);
/*
* Start up the RX thread and wait on rx_post_start_comp. The RX
* Thread will then do the same for the TX Thread in
* iscsi_rx_thread_pre_handler().
*/
complete(&ts->rx_start_comp);
wait_for_completion(&ts->rx_post_start_comp);
}
struct iscsi_thread_set *iscsi_get_thread_set(void)
{
int allocate_ts = 0;
struct completion comp;
struct iscsi_thread_set *ts = NULL;
/*
* If no inactive thread set is available on the first call to
* iscsi_get_ts_from_inactive_list(), sleep for a second and
* try again. If still none are available after two attempts,
* allocate a set ourselves.
*/
get_set:
ts = iscsi_get_ts_from_inactive_list();
if (!ts) {
if (allocate_ts == 2)
iscsi_allocate_thread_sets(1);
init_completion(&comp);
wait_for_completion_timeout(&comp, 1 * HZ);
allocate_ts++;
goto get_set;
}
ts->delay_inactive = 1;
ts->signal_sent = 0;
ts->thread_count = 2;
init_completion(&ts->rx_restart_comp);
init_completion(&ts->tx_restart_comp);
return ts;
}
void iscsi_set_thread_clear(struct iscsi_conn *conn, u8 thread_clear)
{
struct iscsi_thread_set *ts = NULL;
if (!conn->thread_set) {
pr_err("struct iscsi_conn->thread_set is NULL\n");
return;
}
ts = conn->thread_set;
spin_lock_bh(&ts->ts_state_lock);
ts->thread_clear &= ~thread_clear;
if ((thread_clear & ISCSI_CLEAR_RX_THREAD) &&
(ts->blocked_threads & ISCSI_BLOCK_RX_THREAD))
complete(&ts->rx_restart_comp);
else if ((thread_clear & ISCSI_CLEAR_TX_THREAD) &&
(ts->blocked_threads & ISCSI_BLOCK_TX_THREAD))
complete(&ts->tx_restart_comp);
spin_unlock_bh(&ts->ts_state_lock);
}
void iscsi_set_thread_set_signal(struct iscsi_conn *conn, u8 signal_sent)
{
struct iscsi_thread_set *ts = NULL;
if (!conn->thread_set) {
pr_err("struct iscsi_conn->thread_set is NULL\n");
return;
}
ts = conn->thread_set;
spin_lock_bh(&ts->ts_state_lock);
ts->signal_sent |= signal_sent;
spin_unlock_bh(&ts->ts_state_lock);
}
int iscsi_release_thread_set(struct iscsi_conn *conn)
{
int thread_called = 0;
struct iscsi_thread_set *ts = NULL;
if (!conn || !conn->thread_set) {
pr_err("connection or thread set pointer is NULL\n");
BUG();
}
ts = conn->thread_set;
spin_lock_bh(&ts->ts_state_lock);
ts->status = ISCSI_THREAD_SET_RESET;
if (!strncmp(current->comm, ISCSI_RX_THREAD_NAME,
strlen(ISCSI_RX_THREAD_NAME)))
thread_called = ISCSI_RX_THREAD;
else if (!strncmp(current->comm, ISCSI_TX_THREAD_NAME,
strlen(ISCSI_TX_THREAD_NAME)))
thread_called = ISCSI_TX_THREAD;
if (ts->rx_thread && (thread_called == ISCSI_TX_THREAD) &&
(ts->thread_clear & ISCSI_CLEAR_RX_THREAD)) {
if (!(ts->signal_sent & ISCSI_SIGNAL_RX_THREAD)) {
send_sig(SIGINT, ts->rx_thread, 1);
ts->signal_sent |= ISCSI_SIGNAL_RX_THREAD;
}
ts->blocked_threads |= ISCSI_BLOCK_RX_THREAD;
spin_unlock_bh(&ts->ts_state_lock);
wait_for_completion(&ts->rx_restart_comp);
spin_lock_bh(&ts->ts_state_lock);
ts->blocked_threads &= ~ISCSI_BLOCK_RX_THREAD;
}
if (ts->tx_thread && (thread_called == ISCSI_RX_THREAD) &&
(ts->thread_clear & ISCSI_CLEAR_TX_THREAD)) {
if (!(ts->signal_sent & ISCSI_SIGNAL_TX_THREAD)) {
send_sig(SIGINT, ts->tx_thread, 1);
ts->signal_sent |= ISCSI_SIGNAL_TX_THREAD;
}
ts->blocked_threads |= ISCSI_BLOCK_TX_THREAD;
spin_unlock_bh(&ts->ts_state_lock);
wait_for_completion(&ts->tx_restart_comp);
spin_lock_bh(&ts->ts_state_lock);
ts->blocked_threads &= ~ISCSI_BLOCK_TX_THREAD;
}
ts->conn = NULL;
ts->status = ISCSI_THREAD_SET_FREE;
spin_unlock_bh(&ts->ts_state_lock);
return 0;
}
int iscsi_thread_set_force_reinstatement(struct iscsi_conn *conn)
{
struct iscsi_thread_set *ts;
if (!conn->thread_set)
return -1;
ts = conn->thread_set;
spin_lock_bh(&ts->ts_state_lock);
if (ts->status != ISCSI_THREAD_SET_ACTIVE) {
spin_unlock_bh(&ts->ts_state_lock);
return -1;
}
if (ts->tx_thread && (!(ts->signal_sent & ISCSI_SIGNAL_TX_THREAD))) {
send_sig(SIGINT, ts->tx_thread, 1);
ts->signal_sent |= ISCSI_SIGNAL_TX_THREAD;
}
if (ts->rx_thread && (!(ts->signal_sent & ISCSI_SIGNAL_RX_THREAD))) {
send_sig(SIGINT, ts->rx_thread, 1);
ts->signal_sent |= ISCSI_SIGNAL_RX_THREAD;
}
spin_unlock_bh(&ts->ts_state_lock);
return 0;
}
static void iscsi_check_to_add_additional_sets(void)
{
int thread_sets_add;
spin_lock(&inactive_ts_lock);
thread_sets_add = iscsit_global->inactive_ts;
spin_unlock(&inactive_ts_lock);
if (thread_sets_add == 1)
iscsi_allocate_thread_sets(1);
}
static int iscsi_signal_thread_pre_handler(struct iscsi_thread_set *ts)
{
spin_lock_bh(&ts->ts_state_lock);
if ((ts->status == ISCSI_THREAD_SET_DIE) || signal_pending(current)) {
spin_unlock_bh(&ts->ts_state_lock);
return -1;
}
spin_unlock_bh(&ts->ts_state_lock);
return 0;
}
struct iscsi_conn *iscsi_rx_thread_pre_handler(struct iscsi_thread_set *ts)
{
int ret;
spin_lock_bh(&ts->ts_state_lock);
if (ts->create_threads) {
spin_unlock_bh(&ts->ts_state_lock);
goto sleep;
}
flush_signals(current);
if (ts->delay_inactive && (--ts->thread_count == 0)) {
spin_unlock_bh(&ts->ts_state_lock);
iscsi_del_ts_from_active_list(ts);
if (!iscsit_global->in_shutdown)
iscsi_deallocate_extra_thread_sets();
iscsi_add_ts_to_inactive_list(ts);
spin_lock_bh(&ts->ts_state_lock);
}
if ((ts->status == ISCSI_THREAD_SET_RESET) &&
(ts->thread_clear & ISCSI_CLEAR_RX_THREAD))
complete(&ts->rx_restart_comp);
ts->thread_clear &= ~ISCSI_CLEAR_RX_THREAD;
spin_unlock_bh(&ts->ts_state_lock);
sleep:
ret = wait_for_completion_interruptible(&ts->rx_start_comp);
if (ret != 0)
return NULL;
if (iscsi_signal_thread_pre_handler(ts) < 0)
return NULL;
if (!ts->conn) {
pr_err("struct iscsi_thread_set->conn is NULL for"
" thread_id: %d, going back to sleep\n", ts->thread_id);
goto sleep;
}
iscsi_check_to_add_additional_sets();
/*
* The RX Thread starts up the TX Thread and sleeps.
*/
ts->thread_clear |= ISCSI_CLEAR_RX_THREAD;
complete(&ts->tx_start_comp);
wait_for_completion(&ts->tx_post_start_comp);
return ts->conn;
}
struct iscsi_conn *iscsi_tx_thread_pre_handler(struct iscsi_thread_set *ts)
{
int ret;
spin_lock_bh(&ts->ts_state_lock);
if (ts->create_threads) {
spin_unlock_bh(&ts->ts_state_lock);
goto sleep;
}
flush_signals(current);
if (ts->delay_inactive && (--ts->thread_count == 0)) {
spin_unlock_bh(&ts->ts_state_lock);
iscsi_del_ts_from_active_list(ts);
if (!iscsit_global->in_shutdown)
iscsi_deallocate_extra_thread_sets();
iscsi_add_ts_to_inactive_list(ts);
spin_lock_bh(&ts->ts_state_lock);
}
if ((ts->status == ISCSI_THREAD_SET_RESET) &&
(ts->thread_clear & ISCSI_CLEAR_TX_THREAD))
complete(&ts->tx_restart_comp);
ts->thread_clear &= ~ISCSI_CLEAR_TX_THREAD;
spin_unlock_bh(&ts->ts_state_lock);
sleep:
ret = wait_for_completion_interruptible(&ts->tx_start_comp);
if (ret != 0)
return NULL;
if (iscsi_signal_thread_pre_handler(ts) < 0)
return NULL;
if (!ts->conn) {
pr_err("struct iscsi_thread_set->conn is NULL for "
" thread_id: %d, going back to sleep\n",
ts->thread_id);
goto sleep;
}
iscsi_check_to_add_additional_sets();
/*
* From the TX thread, up the tx_post_start_comp that the RX Thread is
* sleeping on in iscsi_rx_thread_pre_handler(), then up the
* rx_post_start_comp that iscsi_activate_thread_set() is sleeping on.
*/
ts->thread_clear |= ISCSI_CLEAR_TX_THREAD;
complete(&ts->tx_post_start_comp);
complete(&ts->rx_post_start_comp);
spin_lock_bh(&ts->ts_state_lock);
ts->status = ISCSI_THREAD_SET_ACTIVE;
spin_unlock_bh(&ts->ts_state_lock);
return ts->conn;
}
int iscsi_thread_set_init(void)
{
int size;
iscsit_global->ts_bitmap_count = ISCSI_TS_BITMAP_BITS;
size = BITS_TO_LONGS(iscsit_global->ts_bitmap_count) * sizeof(long);
iscsit_global->ts_bitmap = kzalloc(size, GFP_KERNEL);
if (!iscsit_global->ts_bitmap) {
pr_err("Unable to allocate iscsit_global->ts_bitmap\n");
return -ENOMEM;
}
spin_lock_init(&active_ts_lock);
spin_lock_init(&inactive_ts_lock);
spin_lock_init(&ts_bitmap_lock);
INIT_LIST_HEAD(&active_ts_list);
INIT_LIST_HEAD(&inactive_ts_list);
return 0;
}
void iscsi_thread_set_free(void)
{
kfree(iscsit_global->ts_bitmap);
}
#ifndef ISCSI_THREAD_QUEUE_H
#define ISCSI_THREAD_QUEUE_H
/*
* Defines for thread sets.
*/
extern int iscsi_thread_set_force_reinstatement(struct iscsi_conn *);
extern void iscsi_add_ts_to_inactive_list(struct iscsi_thread_set *);
extern int iscsi_allocate_thread_sets(u32);
extern void iscsi_deallocate_thread_sets(void);
extern void iscsi_activate_thread_set(struct iscsi_conn *, struct iscsi_thread_set *);
extern struct iscsi_thread_set *iscsi_get_thread_set(void);
extern void iscsi_set_thread_clear(struct iscsi_conn *, u8);
extern void iscsi_set_thread_set_signal(struct iscsi_conn *, u8);
extern int iscsi_release_thread_set(struct iscsi_conn *);
extern struct iscsi_conn *iscsi_rx_thread_pre_handler(struct iscsi_thread_set *);
extern struct iscsi_conn *iscsi_tx_thread_pre_handler(struct iscsi_thread_set *);
extern int iscsi_thread_set_init(void);
extern void iscsi_thread_set_free(void);
extern int iscsi_target_tx_thread(void *);
extern int iscsi_target_rx_thread(void *);
#define TARGET_THREAD_SET_COUNT 4
#define ISCSI_RX_THREAD 1
#define ISCSI_TX_THREAD 2
#define ISCSI_RX_THREAD_NAME "iscsi_trx"
#define ISCSI_TX_THREAD_NAME "iscsi_ttx"
#define ISCSI_BLOCK_RX_THREAD 0x1
#define ISCSI_BLOCK_TX_THREAD 0x2
#define ISCSI_CLEAR_RX_THREAD 0x1
#define ISCSI_CLEAR_TX_THREAD 0x2
#define ISCSI_SIGNAL_RX_THREAD 0x1
#define ISCSI_SIGNAL_TX_THREAD 0x2
/* struct iscsi_thread_set->status */
#define ISCSI_THREAD_SET_FREE 1
#define ISCSI_THREAD_SET_ACTIVE 2
#define ISCSI_THREAD_SET_DIE 3
#define ISCSI_THREAD_SET_RESET 4
#define ISCSI_THREAD_SET_DEALLOCATE_THREADS 5
/* By default allow a maximum of 32K iSCSI connections */
#define ISCSI_TS_BITMAP_BITS 32768
struct iscsi_thread_set {
/* flags used for blocking and restarting sets */
int blocked_threads;
/* flag for creating threads */
int create_threads;
/* flag for delaying readding to inactive list */
int delay_inactive;
/* status for thread set */
int status;
/* which threads have had signals sent */
int signal_sent;
/* flag for which threads exited first */
int thread_clear;
/* Active threads in the thread set */
int thread_count;
/* Unique thread ID */
u32 thread_id;
/* pointer to connection if set is active */
struct iscsi_conn *conn;
/* used for controlling ts state accesses */
spinlock_t ts_state_lock;
/* Used for rx side post startup */
struct completion rx_post_start_comp;
/* Used for tx side post startup */
struct completion tx_post_start_comp;
/* used for restarting thread queue */
struct completion rx_restart_comp;
/* used for restarting thread queue */
struct completion tx_restart_comp;
/* used for normal unused blocking */
struct completion rx_start_comp;
/* used for normal unused blocking */
struct completion tx_start_comp;
/* OS descriptor for rx thread */
struct task_struct *rx_thread;
/* OS descriptor for tx thread */
struct task_struct *tx_thread;
/* struct iscsi_thread_set in list list head*/
struct list_head ts_list;
};
#endif /*** ISCSI_THREAD_QUEUE_H ***/
/*******************************************************************************
* This file contains the iSCSI Target specific utility functions.
*
* \u00a9 Copyright 2007-2011 RisingTide Systems LLC.
*
* Licensed to the Linux Foundation under the General Public License (GPL) version 2.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
* 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.
******************************************************************************/
#include <linux/list.h>
#include <scsi/scsi_tcq.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_transport.h>
#include <target/target_core_tmr.h>
#include <target/target_core_fabric_ops.h>
#include <target/target_core_configfs.h>
#include "iscsi_target_core.h"
#include "iscsi_target_parameters.h"
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_datain_values.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_tq.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
#define PRINT_BUFF(buff, len) \
{ \
int zzz; \
\
pr_debug("%d:\n", __LINE__); \
for (zzz = 0; zzz < len; zzz++) { \
if (zzz % 16 == 0) { \
if (zzz) \
pr_debug("\n"); \
pr_debug("%4i: ", zzz); \
} \
pr_debug("%02x ", (unsigned char) (buff)[zzz]); \
} \
if ((len + 1) % 16) \
pr_debug("\n"); \
}
extern struct list_head g_tiqn_list;
extern spinlock_t tiqn_lock;
/*
* Called with cmd->r2t_lock held.
*/
int iscsit_add_r2t_to_list(
struct iscsi_cmd *cmd,
u32 offset,
u32 xfer_len,
int recovery,
u32 r2t_sn)
{
struct iscsi_r2t *r2t;
r2t = kmem_cache_zalloc(lio_r2t_cache, GFP_ATOMIC);
if (!r2t) {
pr_err("Unable to allocate memory for struct iscsi_r2t.\n");
return -1;
}
INIT_LIST_HEAD(&r2t->r2t_list);
r2t->recovery_r2t = recovery;
r2t->r2t_sn = (!r2t_sn) ? cmd->r2t_sn++ : r2t_sn;
r2t->offset = offset;
r2t->xfer_len = xfer_len;
list_add_tail(&r2t->r2t_list, &cmd->cmd_r2t_list);
spin_unlock_bh(&cmd->r2t_lock);
iscsit_add_cmd_to_immediate_queue(cmd, cmd->conn, ISTATE_SEND_R2T);
spin_lock_bh(&cmd->r2t_lock);
return 0;
}
struct iscsi_r2t *iscsit_get_r2t_for_eos(
struct iscsi_cmd *cmd,
u32 offset,
u32 length)
{
struct iscsi_r2t *r2t;
spin_lock_bh(&cmd->r2t_lock);
list_for_each_entry(r2t, &cmd->cmd_r2t_list, r2t_list) {
if ((r2t->offset <= offset) &&
(r2t->offset + r2t->xfer_len) >= (offset + length)) {
spin_unlock_bh(&cmd->r2t_lock);
return r2t;
}
}
spin_unlock_bh(&cmd->r2t_lock);
pr_err("Unable to locate R2T for Offset: %u, Length:"
" %u\n", offset, length);
return NULL;
}
struct iscsi_r2t *iscsit_get_r2t_from_list(struct iscsi_cmd *cmd)
{
struct iscsi_r2t *r2t;
spin_lock_bh(&cmd->r2t_lock);
list_for_each_entry(r2t, &cmd->cmd_r2t_list, r2t_list) {
if (!r2t->sent_r2t) {
spin_unlock_bh(&cmd->r2t_lock);
return r2t;
}
}
spin_unlock_bh(&cmd->r2t_lock);
pr_err("Unable to locate next R2T to send for ITT:"
" 0x%08x.\n", cmd->init_task_tag);
return NULL;
}
/*
* Called with cmd->r2t_lock held.
*/
void iscsit_free_r2t(struct iscsi_r2t *r2t, struct iscsi_cmd *cmd)
{
list_del(&r2t->r2t_list);
kmem_cache_free(lio_r2t_cache, r2t);
}
void iscsit_free_r2ts_from_list(struct iscsi_cmd *cmd)
{
struct iscsi_r2t *r2t, *r2t_tmp;
spin_lock_bh(&cmd->r2t_lock);
list_for_each_entry_safe(r2t, r2t_tmp, &cmd->cmd_r2t_list, r2t_list)
iscsit_free_r2t(r2t, cmd);
spin_unlock_bh(&cmd->r2t_lock);
}
/*
* May be called from software interrupt (timer) context for allocating
* iSCSI NopINs.
*/
struct iscsi_cmd *iscsit_allocate_cmd(struct iscsi_conn *conn, gfp_t gfp_mask)
{
struct iscsi_cmd *cmd;
cmd = kmem_cache_zalloc(lio_cmd_cache, gfp_mask);
if (!cmd) {
pr_err("Unable to allocate memory for struct iscsi_cmd.\n");
return NULL;
}
cmd->conn = conn;
INIT_LIST_HEAD(&cmd->i_list);
INIT_LIST_HEAD(&cmd->datain_list);
INIT_LIST_HEAD(&cmd->cmd_r2t_list);
init_completion(&cmd->reject_comp);
spin_lock_init(&cmd->datain_lock);
spin_lock_init(&cmd->dataout_timeout_lock);
spin_lock_init(&cmd->istate_lock);
spin_lock_init(&cmd->error_lock);
spin_lock_init(&cmd->r2t_lock);
return cmd;
}
/*
* Called from iscsi_handle_scsi_cmd()
*/
struct iscsi_cmd *iscsit_allocate_se_cmd(
struct iscsi_conn *conn,
u32 data_length,
int data_direction,
int iscsi_task_attr)
{
struct iscsi_cmd *cmd;
struct se_cmd *se_cmd;
int sam_task_attr;
cmd = iscsit_allocate_cmd(conn, GFP_KERNEL);
if (!cmd)
return NULL;
cmd->data_direction = data_direction;
cmd->data_length = data_length;
/*
* Figure out the SAM Task Attribute for the incoming SCSI CDB
*/
if ((iscsi_task_attr == ISCSI_ATTR_UNTAGGED) ||
(iscsi_task_attr == ISCSI_ATTR_SIMPLE))
sam_task_attr = MSG_SIMPLE_TAG;
else if (iscsi_task_attr == ISCSI_ATTR_ORDERED)
sam_task_attr = MSG_ORDERED_TAG;
else if (iscsi_task_attr == ISCSI_ATTR_HEAD_OF_QUEUE)
sam_task_attr = MSG_HEAD_TAG;
else if (iscsi_task_attr == ISCSI_ATTR_ACA)
sam_task_attr = MSG_ACA_TAG;
else {
pr_debug("Unknown iSCSI Task Attribute: 0x%02x, using"
" MSG_SIMPLE_TAG\n", iscsi_task_attr);
sam_task_attr = MSG_SIMPLE_TAG;
}
se_cmd = &cmd->se_cmd;
/*
* Initialize struct se_cmd descriptor from target_core_mod infrastructure
*/
transport_init_se_cmd(se_cmd, &lio_target_fabric_configfs->tf_ops,
conn->sess->se_sess, data_length, data_direction,
sam_task_attr, &cmd->sense_buffer[0]);
return cmd;
}
struct iscsi_cmd *iscsit_allocate_se_cmd_for_tmr(
struct iscsi_conn *conn,
u8 function)
{
struct iscsi_cmd *cmd;
struct se_cmd *se_cmd;
u8 tcm_function;
cmd = iscsit_allocate_cmd(conn, GFP_KERNEL);
if (!cmd)
return NULL;
cmd->data_direction = DMA_NONE;
cmd->tmr_req = kzalloc(sizeof(struct iscsi_tmr_req), GFP_KERNEL);
if (!cmd->tmr_req) {
pr_err("Unable to allocate memory for"
" Task Management command!\n");
return NULL;
}
/*
* TASK_REASSIGN for ERL=2 / connection stays inside of
* LIO-Target $FABRIC_MOD
*/
if (function == ISCSI_TM_FUNC_TASK_REASSIGN)
return cmd;
se_cmd = &cmd->se_cmd;
/*
* Initialize struct se_cmd descriptor from target_core_mod infrastructure
*/
transport_init_se_cmd(se_cmd, &lio_target_fabric_configfs->tf_ops,
conn->sess->se_sess, 0, DMA_NONE,
MSG_SIMPLE_TAG, &cmd->sense_buffer[0]);
switch (function) {
case ISCSI_TM_FUNC_ABORT_TASK:
tcm_function = TMR_ABORT_TASK;
break;
case ISCSI_TM_FUNC_ABORT_TASK_SET:
tcm_function = TMR_ABORT_TASK_SET;
break;
case ISCSI_TM_FUNC_CLEAR_ACA:
tcm_function = TMR_CLEAR_ACA;
break;
case ISCSI_TM_FUNC_CLEAR_TASK_SET:
tcm_function = TMR_CLEAR_TASK_SET;
break;
case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:
tcm_function = TMR_LUN_RESET;
break;
case ISCSI_TM_FUNC_TARGET_WARM_RESET:
tcm_function = TMR_TARGET_WARM_RESET;
break;
case ISCSI_TM_FUNC_TARGET_COLD_RESET:
tcm_function = TMR_TARGET_COLD_RESET;
break;
default:
pr_err("Unknown iSCSI TMR Function:"
" 0x%02x\n", function);
goto out;
}
se_cmd->se_tmr_req = core_tmr_alloc_req(se_cmd,
(void *)cmd->tmr_req, tcm_function);
if (!se_cmd->se_tmr_req)
goto out;
cmd->tmr_req->se_tmr_req = se_cmd->se_tmr_req;
return cmd;
out:
iscsit_release_cmd(cmd);
if (se_cmd)
transport_free_se_cmd(se_cmd);
return NULL;
}
int iscsit_decide_list_to_build(
struct iscsi_cmd *cmd,
u32 immediate_data_length)
{
struct iscsi_build_list bl;
struct iscsi_conn *conn = cmd->conn;
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na;
if (sess->sess_ops->DataSequenceInOrder &&
sess->sess_ops->DataPDUInOrder)
return 0;
if (cmd->data_direction == DMA_NONE)
return 0;
na = iscsit_tpg_get_node_attrib(sess);
memset(&bl, 0, sizeof(struct iscsi_build_list));
if (cmd->data_direction == DMA_FROM_DEVICE) {
bl.data_direction = ISCSI_PDU_READ;
bl.type = PDULIST_NORMAL;
if (na->random_datain_pdu_offsets)
bl.randomize |= RANDOM_DATAIN_PDU_OFFSETS;
if (na->random_datain_seq_offsets)
bl.randomize |= RANDOM_DATAIN_SEQ_OFFSETS;
} else {
bl.data_direction = ISCSI_PDU_WRITE;
bl.immediate_data_length = immediate_data_length;
if (na->random_r2t_offsets)
bl.randomize |= RANDOM_R2T_OFFSETS;
if (!cmd->immediate_data && !cmd->unsolicited_data)
bl.type = PDULIST_NORMAL;
else if (cmd->immediate_data && !cmd->unsolicited_data)
bl.type = PDULIST_IMMEDIATE;
else if (!cmd->immediate_data && cmd->unsolicited_data)
bl.type = PDULIST_UNSOLICITED;
else if (cmd->immediate_data && cmd->unsolicited_data)
bl.type = PDULIST_IMMEDIATE_AND_UNSOLICITED;
}
return iscsit_do_build_list(cmd, &bl);
}
struct iscsi_seq *iscsit_get_seq_holder_for_datain(
struct iscsi_cmd *cmd,
u32 seq_send_order)
{
u32 i;
for (i = 0; i < cmd->seq_count; i++)
if (cmd->seq_list[i].seq_send_order == seq_send_order)
return &cmd->seq_list[i];
return NULL;
}
struct iscsi_seq *iscsit_get_seq_holder_for_r2t(struct iscsi_cmd *cmd)
{
u32 i;
if (!cmd->seq_list) {
pr_err("struct iscsi_cmd->seq_list is NULL!\n");
return NULL;
}
for (i = 0; i < cmd->seq_count; i++) {
if (cmd->seq_list[i].type != SEQTYPE_NORMAL)
continue;
if (cmd->seq_list[i].seq_send_order == cmd->seq_send_order) {
cmd->seq_send_order++;
return &cmd->seq_list[i];
}
}
return NULL;
}
struct iscsi_r2t *iscsit_get_holder_for_r2tsn(
struct iscsi_cmd *cmd,
u32 r2t_sn)
{
struct iscsi_r2t *r2t;
spin_lock_bh(&cmd->r2t_lock);
list_for_each_entry(r2t, &cmd->cmd_r2t_list, r2t_list) {
if (r2t->r2t_sn == r2t_sn) {
spin_unlock_bh(&cmd->r2t_lock);
return r2t;
}
}
spin_unlock_bh(&cmd->r2t_lock);
return NULL;
}
static inline int iscsit_check_received_cmdsn(struct iscsi_session *sess, u32 cmdsn)
{
int ret;
/*
* This is the proper method of checking received CmdSN against
* ExpCmdSN and MaxCmdSN values, as well as accounting for out
* or order CmdSNs due to multiple connection sessions and/or
* CRC failures.
*/
if (iscsi_sna_gt(cmdsn, sess->max_cmd_sn)) {
pr_err("Received CmdSN: 0x%08x is greater than"
" MaxCmdSN: 0x%08x, protocol error.\n", cmdsn,
sess->max_cmd_sn);
ret = CMDSN_ERROR_CANNOT_RECOVER;
} else if (cmdsn == sess->exp_cmd_sn) {
sess->exp_cmd_sn++;
pr_debug("Received CmdSN matches ExpCmdSN,"
" incremented ExpCmdSN to: 0x%08x\n",
sess->exp_cmd_sn);
ret = CMDSN_NORMAL_OPERATION;
} else if (iscsi_sna_gt(cmdsn, sess->exp_cmd_sn)) {
pr_debug("Received CmdSN: 0x%08x is greater"
" than ExpCmdSN: 0x%08x, not acknowledging.\n",
cmdsn, sess->exp_cmd_sn);
ret = CMDSN_HIGHER_THAN_EXP;
} else {
pr_err("Received CmdSN: 0x%08x is less than"
" ExpCmdSN: 0x%08x, ignoring.\n", cmdsn,
sess->exp_cmd_sn);
ret = CMDSN_LOWER_THAN_EXP;
}
return ret;
}
/*
* Commands may be received out of order if MC/S is in use.
* Ensure they are executed in CmdSN order.
*/
int iscsit_sequence_cmd(
struct iscsi_conn *conn,
struct iscsi_cmd *cmd,
u32 cmdsn)
{
int ret;
int cmdsn_ret;
mutex_lock(&conn->sess->cmdsn_mutex);
cmdsn_ret = iscsit_check_received_cmdsn(conn->sess, cmdsn);
switch (cmdsn_ret) {
case CMDSN_NORMAL_OPERATION:
ret = iscsit_execute_cmd(cmd, 0);
if ((ret >= 0) && !list_empty(&conn->sess->sess_ooo_cmdsn_list))
iscsit_execute_ooo_cmdsns(conn->sess);
break;
case CMDSN_HIGHER_THAN_EXP:
ret = iscsit_handle_ooo_cmdsn(conn->sess, cmd, cmdsn);
break;
case CMDSN_LOWER_THAN_EXP:
cmd->i_state = ISTATE_REMOVE;
iscsit_add_cmd_to_immediate_queue(cmd, conn, cmd->i_state);
ret = cmdsn_ret;
break;
default:
ret = cmdsn_ret;
break;
}
mutex_unlock(&conn->sess->cmdsn_mutex);
return ret;
}
int iscsit_check_unsolicited_dataout(struct iscsi_cmd *cmd, unsigned char *buf)
{
struct iscsi_conn *conn = cmd->conn;
struct se_cmd *se_cmd = &cmd->se_cmd;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
if (conn->sess->sess_ops->InitialR2T) {
pr_err("Received unexpected unsolicited data"
" while InitialR2T=Yes, protocol error.\n");
transport_send_check_condition_and_sense(se_cmd,
TCM_UNEXPECTED_UNSOLICITED_DATA, 0);
return -1;
}
if ((cmd->first_burst_len + payload_length) >
conn->sess->sess_ops->FirstBurstLength) {
pr_err("Total %u bytes exceeds FirstBurstLength: %u"
" for this Unsolicited DataOut Burst.\n",
(cmd->first_burst_len + payload_length),
conn->sess->sess_ops->FirstBurstLength);
transport_send_check_condition_and_sense(se_cmd,
TCM_INCORRECT_AMOUNT_OF_DATA, 0);
return -1;
}
if (!(hdr->flags & ISCSI_FLAG_CMD_FINAL))
return 0;
if (((cmd->first_burst_len + payload_length) != cmd->data_length) &&
((cmd->first_burst_len + payload_length) !=
conn->sess->sess_ops->FirstBurstLength)) {
pr_err("Unsolicited non-immediate data received %u"
" does not equal FirstBurstLength: %u, and does"
" not equal ExpXferLen %u.\n",
(cmd->first_burst_len + payload_length),
conn->sess->sess_ops->FirstBurstLength, cmd->data_length);
transport_send_check_condition_and_sense(se_cmd,
TCM_INCORRECT_AMOUNT_OF_DATA, 0);
return -1;
}
return 0;
}
struct iscsi_cmd *iscsit_find_cmd_from_itt(
struct iscsi_conn *conn,
u32 init_task_tag)
{
struct iscsi_cmd *cmd;
spin_lock_bh(&conn->cmd_lock);
list_for_each_entry(cmd, &conn->conn_cmd_list, i_list) {
if (cmd->init_task_tag == init_task_tag) {
spin_unlock_bh(&conn->cmd_lock);
return cmd;
}
}
spin_unlock_bh(&conn->cmd_lock);
pr_err("Unable to locate ITT: 0x%08x on CID: %hu",
init_task_tag, conn->cid);
return NULL;
}
struct iscsi_cmd *iscsit_find_cmd_from_itt_or_dump(
struct iscsi_conn *conn,
u32 init_task_tag,
u32 length)
{
struct iscsi_cmd *cmd;
spin_lock_bh(&conn->cmd_lock);
list_for_each_entry(cmd, &conn->conn_cmd_list, i_list) {
if (cmd->init_task_tag == init_task_tag) {
spin_unlock_bh(&conn->cmd_lock);
return cmd;
}
}
spin_unlock_bh(&conn->cmd_lock);
pr_err("Unable to locate ITT: 0x%08x on CID: %hu,"
" dumping payload\n", init_task_tag, conn->cid);
if (length)
iscsit_dump_data_payload(conn, length, 1);
return NULL;
}
struct iscsi_cmd *iscsit_find_cmd_from_ttt(
struct iscsi_conn *conn,
u32 targ_xfer_tag)
{
struct iscsi_cmd *cmd = NULL;
spin_lock_bh(&conn->cmd_lock);
list_for_each_entry(cmd, &conn->conn_cmd_list, i_list) {
if (cmd->targ_xfer_tag == targ_xfer_tag) {
spin_unlock_bh(&conn->cmd_lock);
return cmd;
}
}
spin_unlock_bh(&conn->cmd_lock);
pr_err("Unable to locate TTT: 0x%08x on CID: %hu\n",
targ_xfer_tag, conn->cid);
return NULL;
}
int iscsit_find_cmd_for_recovery(
struct iscsi_session *sess,
struct iscsi_cmd **cmd_ptr,
struct iscsi_conn_recovery **cr_ptr,
u32 init_task_tag)
{
struct iscsi_cmd *cmd = NULL;
struct iscsi_conn_recovery *cr;
/*
* Scan through the inactive connection recovery list's command list.
* If init_task_tag matches the command is still alligent.
*/
spin_lock(&sess->cr_i_lock);
list_for_each_entry(cr, &sess->cr_inactive_list, cr_list) {
spin_lock(&cr->conn_recovery_cmd_lock);
list_for_each_entry(cmd, &cr->conn_recovery_cmd_list, i_list) {
if (cmd->init_task_tag == init_task_tag) {
spin_unlock(&cr->conn_recovery_cmd_lock);
spin_unlock(&sess->cr_i_lock);
*cr_ptr = cr;
*cmd_ptr = cmd;
return -2;
}
}
spin_unlock(&cr->conn_recovery_cmd_lock);
}
spin_unlock(&sess->cr_i_lock);
/*
* Scan through the active connection recovery list's command list.
* If init_task_tag matches the command is ready to be reassigned.
*/
spin_lock(&sess->cr_a_lock);
list_for_each_entry(cr, &sess->cr_active_list, cr_list) {
spin_lock(&cr->conn_recovery_cmd_lock);
list_for_each_entry(cmd, &cr->conn_recovery_cmd_list, i_list) {
if (cmd->init_task_tag == init_task_tag) {
spin_unlock(&cr->conn_recovery_cmd_lock);
spin_unlock(&sess->cr_a_lock);
*cr_ptr = cr;
*cmd_ptr = cmd;
return 0;
}
}
spin_unlock(&cr->conn_recovery_cmd_lock);
}
spin_unlock(&sess->cr_a_lock);
return -1;
}
void iscsit_add_cmd_to_immediate_queue(
struct iscsi_cmd *cmd,
struct iscsi_conn *conn,
u8 state)
{
struct iscsi_queue_req *qr;
qr = kmem_cache_zalloc(lio_qr_cache, GFP_ATOMIC);
if (!qr) {
pr_err("Unable to allocate memory for"
" struct iscsi_queue_req\n");
return;
}
INIT_LIST_HEAD(&qr->qr_list);
qr->cmd = cmd;
qr->state = state;
spin_lock_bh(&conn->immed_queue_lock);
list_add_tail(&qr->qr_list, &conn->immed_queue_list);
atomic_inc(&cmd->immed_queue_count);
atomic_set(&conn->check_immediate_queue, 1);
spin_unlock_bh(&conn->immed_queue_lock);
wake_up_process(conn->thread_set->tx_thread);
}
struct iscsi_queue_req *iscsit_get_cmd_from_immediate_queue(struct iscsi_conn *conn)
{
struct iscsi_queue_req *qr;
spin_lock_bh(&conn->immed_queue_lock);
if (list_empty(&conn->immed_queue_list)) {
spin_unlock_bh(&conn->immed_queue_lock);
return NULL;
}
list_for_each_entry(qr, &conn->immed_queue_list, qr_list)
break;
list_del(&qr->qr_list);
if (qr->cmd)
atomic_dec(&qr->cmd->immed_queue_count);
spin_unlock_bh(&conn->immed_queue_lock);
return qr;
}
static void iscsit_remove_cmd_from_immediate_queue(
struct iscsi_cmd *cmd,
struct iscsi_conn *conn)
{
struct iscsi_queue_req *qr, *qr_tmp;
spin_lock_bh(&conn->immed_queue_lock);
if (!atomic_read(&cmd->immed_queue_count)) {
spin_unlock_bh(&conn->immed_queue_lock);
return;
}
list_for_each_entry_safe(qr, qr_tmp, &conn->immed_queue_list, qr_list) {
if (qr->cmd != cmd)
continue;
atomic_dec(&qr->cmd->immed_queue_count);
list_del(&qr->qr_list);
kmem_cache_free(lio_qr_cache, qr);
}
spin_unlock_bh(&conn->immed_queue_lock);
if (atomic_read(&cmd->immed_queue_count)) {
pr_err("ITT: 0x%08x immed_queue_count: %d\n",
cmd->init_task_tag,
atomic_read(&cmd->immed_queue_count));
}
}
void iscsit_add_cmd_to_response_queue(
struct iscsi_cmd *cmd,
struct iscsi_conn *conn,
u8 state)
{
struct iscsi_queue_req *qr;
qr = kmem_cache_zalloc(lio_qr_cache, GFP_ATOMIC);
if (!qr) {
pr_err("Unable to allocate memory for"
" struct iscsi_queue_req\n");
return;
}
INIT_LIST_HEAD(&qr->qr_list);
qr->cmd = cmd;
qr->state = state;
spin_lock_bh(&conn->response_queue_lock);
list_add_tail(&qr->qr_list, &conn->response_queue_list);
atomic_inc(&cmd->response_queue_count);
spin_unlock_bh(&conn->response_queue_lock);
wake_up_process(conn->thread_set->tx_thread);
}
struct iscsi_queue_req *iscsit_get_cmd_from_response_queue(struct iscsi_conn *conn)
{
struct iscsi_queue_req *qr;
spin_lock_bh(&conn->response_queue_lock);
if (list_empty(&conn->response_queue_list)) {
spin_unlock_bh(&conn->response_queue_lock);
return NULL;
}
list_for_each_entry(qr, &conn->response_queue_list, qr_list)
break;
list_del(&qr->qr_list);
if (qr->cmd)
atomic_dec(&qr->cmd->response_queue_count);
spin_unlock_bh(&conn->response_queue_lock);
return qr;
}
static void iscsit_remove_cmd_from_response_queue(
struct iscsi_cmd *cmd,
struct iscsi_conn *conn)
{
struct iscsi_queue_req *qr, *qr_tmp;
spin_lock_bh(&conn->response_queue_lock);
if (!atomic_read(&cmd->response_queue_count)) {
spin_unlock_bh(&conn->response_queue_lock);
return;
}
list_for_each_entry_safe(qr, qr_tmp, &conn->response_queue_list,
qr_list) {
if (qr->cmd != cmd)
continue;
atomic_dec(&qr->cmd->response_queue_count);
list_del(&qr->qr_list);
kmem_cache_free(lio_qr_cache, qr);
}
spin_unlock_bh(&conn->response_queue_lock);
if (atomic_read(&cmd->response_queue_count)) {
pr_err("ITT: 0x%08x response_queue_count: %d\n",
cmd->init_task_tag,
atomic_read(&cmd->response_queue_count));
}
}
void iscsit_free_queue_reqs_for_conn(struct iscsi_conn *conn)
{
struct iscsi_queue_req *qr, *qr_tmp;
spin_lock_bh(&conn->immed_queue_lock);
list_for_each_entry_safe(qr, qr_tmp, &conn->immed_queue_list, qr_list) {
list_del(&qr->qr_list);
if (qr->cmd)
atomic_dec(&qr->cmd->immed_queue_count);
kmem_cache_free(lio_qr_cache, qr);
}
spin_unlock_bh(&conn->immed_queue_lock);
spin_lock_bh(&conn->response_queue_lock);
list_for_each_entry_safe(qr, qr_tmp, &conn->response_queue_list,
qr_list) {
list_del(&qr->qr_list);
if (qr->cmd)
atomic_dec(&qr->cmd->response_queue_count);
kmem_cache_free(lio_qr_cache, qr);
}
spin_unlock_bh(&conn->response_queue_lock);
}
void iscsit_release_cmd(struct iscsi_cmd *cmd)
{
struct iscsi_conn *conn = cmd->conn;
int i;
iscsit_free_r2ts_from_list(cmd);
iscsit_free_all_datain_reqs(cmd);
kfree(cmd->buf_ptr);
kfree(cmd->pdu_list);
kfree(cmd->seq_list);
kfree(cmd->tmr_req);
kfree(cmd->iov_data);
for (i = 0; i < cmd->t_mem_sg_nents; i++)
__free_page(sg_page(&cmd->t_mem_sg[i]));
kfree(cmd->t_mem_sg);
if (conn) {
iscsit_remove_cmd_from_immediate_queue(cmd, conn);
iscsit_remove_cmd_from_response_queue(cmd, conn);
}
kmem_cache_free(lio_cmd_cache, cmd);
}
int iscsit_check_session_usage_count(struct iscsi_session *sess)
{
spin_lock_bh(&sess->session_usage_lock);
if (sess->session_usage_count != 0) {
sess->session_waiting_on_uc = 1;
spin_unlock_bh(&sess->session_usage_lock);
if (in_interrupt())
return 2;
wait_for_completion(&sess->session_waiting_on_uc_comp);
return 1;
}
spin_unlock_bh(&sess->session_usage_lock);
return 0;
}
void iscsit_dec_session_usage_count(struct iscsi_session *sess)
{
spin_lock_bh(&sess->session_usage_lock);
sess->session_usage_count--;
if (!sess->session_usage_count && sess->session_waiting_on_uc)
complete(&sess->session_waiting_on_uc_comp);
spin_unlock_bh(&sess->session_usage_lock);
}
void iscsit_inc_session_usage_count(struct iscsi_session *sess)
{
spin_lock_bh(&sess->session_usage_lock);
sess->session_usage_count++;
spin_unlock_bh(&sess->session_usage_lock);
}
/*
* Used before iscsi_do[rx,tx]_data() to determine iov and [rx,tx]_marker
* array counts needed for sync and steering.
*/
static int iscsit_determine_sync_and_steering_counts(
struct iscsi_conn *conn,
struct iscsi_data_count *count)
{
u32 length = count->data_length;
u32 marker, markint;
count->sync_and_steering = 1;
marker = (count->type == ISCSI_RX_DATA) ?
conn->of_marker : conn->if_marker;
markint = (count->type == ISCSI_RX_DATA) ?
(conn->conn_ops->OFMarkInt * 4) :
(conn->conn_ops->IFMarkInt * 4);
count->ss_iov_count = count->iov_count;
while (length > 0) {
if (length >= marker) {
count->ss_iov_count += 3;
count->ss_marker_count += 2;
length -= marker;
marker = markint;
} else
length = 0;
}
return 0;
}
/*
* Setup conn->if_marker and conn->of_marker values based upon
* the initial marker-less interval. (see iSCSI v19 A.2)
*/
int iscsit_set_sync_and_steering_values(struct iscsi_conn *conn)
{
int login_ifmarker_count = 0, login_ofmarker_count = 0, next_marker = 0;
/*
* IFMarkInt and OFMarkInt are negotiated as 32-bit words.
*/
u32 IFMarkInt = (conn->conn_ops->IFMarkInt * 4);
u32 OFMarkInt = (conn->conn_ops->OFMarkInt * 4);
if (conn->conn_ops->OFMarker) {
/*
* Account for the first Login Command received not
* via iscsi_recv_msg().
*/
conn->of_marker += ISCSI_HDR_LEN;
if (conn->of_marker <= OFMarkInt) {
conn->of_marker = (OFMarkInt - conn->of_marker);
} else {
login_ofmarker_count = (conn->of_marker / OFMarkInt);
next_marker = (OFMarkInt * (login_ofmarker_count + 1)) +
(login_ofmarker_count * MARKER_SIZE);
conn->of_marker = (next_marker - conn->of_marker);
}
conn->of_marker_offset = 0;
pr_debug("Setting OFMarker value to %u based on Initial"
" Markerless Interval.\n", conn->of_marker);
}
if (conn->conn_ops->IFMarker) {
if (conn->if_marker <= IFMarkInt) {
conn->if_marker = (IFMarkInt - conn->if_marker);
} else {
login_ifmarker_count = (conn->if_marker / IFMarkInt);
next_marker = (IFMarkInt * (login_ifmarker_count + 1)) +
(login_ifmarker_count * MARKER_SIZE);
conn->if_marker = (next_marker - conn->if_marker);
}
pr_debug("Setting IFMarker value to %u based on Initial"
" Markerless Interval.\n", conn->if_marker);
}
return 0;
}
struct iscsi_conn *iscsit_get_conn_from_cid(struct iscsi_session *sess, u16 cid)
{
struct iscsi_conn *conn;
spin_lock_bh(&sess->conn_lock);
list_for_each_entry(conn, &sess->sess_conn_list, conn_list) {
if ((conn->cid == cid) &&
(conn->conn_state == TARG_CONN_STATE_LOGGED_IN)) {
iscsit_inc_conn_usage_count(conn);
spin_unlock_bh(&sess->conn_lock);
return conn;
}
}
spin_unlock_bh(&sess->conn_lock);
return NULL;
}
struct iscsi_conn *iscsit_get_conn_from_cid_rcfr(struct iscsi_session *sess, u16 cid)
{
struct iscsi_conn *conn;
spin_lock_bh(&sess->conn_lock);
list_for_each_entry(conn, &sess->sess_conn_list, conn_list) {
if (conn->cid == cid) {
iscsit_inc_conn_usage_count(conn);
spin_lock(&conn->state_lock);
atomic_set(&conn->connection_wait_rcfr, 1);
spin_unlock(&conn->state_lock);
spin_unlock_bh(&sess->conn_lock);
return conn;
}
}
spin_unlock_bh(&sess->conn_lock);
return NULL;
}
void iscsit_check_conn_usage_count(struct iscsi_conn *conn)
{
spin_lock_bh(&conn->conn_usage_lock);
if (conn->conn_usage_count != 0) {
conn->conn_waiting_on_uc = 1;
spin_unlock_bh(&conn->conn_usage_lock);
wait_for_completion(&conn->conn_waiting_on_uc_comp);
return;
}
spin_unlock_bh(&conn->conn_usage_lock);
}
void iscsit_dec_conn_usage_count(struct iscsi_conn *conn)
{
spin_lock_bh(&conn->conn_usage_lock);
conn->conn_usage_count--;
if (!conn->conn_usage_count && conn->conn_waiting_on_uc)
complete(&conn->conn_waiting_on_uc_comp);
spin_unlock_bh(&conn->conn_usage_lock);
}
void iscsit_inc_conn_usage_count(struct iscsi_conn *conn)
{
spin_lock_bh(&conn->conn_usage_lock);
conn->conn_usage_count++;
spin_unlock_bh(&conn->conn_usage_lock);
}
static int iscsit_add_nopin(struct iscsi_conn *conn, int want_response)
{
u8 state;
struct iscsi_cmd *cmd;
cmd = iscsit_allocate_cmd(conn, GFP_ATOMIC);
if (!cmd)
return -1;
cmd->iscsi_opcode = ISCSI_OP_NOOP_IN;
state = (want_response) ? ISTATE_SEND_NOPIN_WANT_RESPONSE :
ISTATE_SEND_NOPIN_NO_RESPONSE;
cmd->init_task_tag = 0xFFFFFFFF;
spin_lock_bh(&conn->sess->ttt_lock);
cmd->targ_xfer_tag = (want_response) ? conn->sess->targ_xfer_tag++ :
0xFFFFFFFF;
if (want_response && (cmd->targ_xfer_tag == 0xFFFFFFFF))
cmd->targ_xfer_tag = conn->sess->targ_xfer_tag++;
spin_unlock_bh(&conn->sess->ttt_lock);
spin_lock_bh(&conn->cmd_lock);
list_add_tail(&cmd->i_list, &conn->conn_cmd_list);
spin_unlock_bh(&conn->cmd_lock);
if (want_response)
iscsit_start_nopin_response_timer(conn);
iscsit_add_cmd_to_immediate_queue(cmd, conn, state);
return 0;
}
static void iscsit_handle_nopin_response_timeout(unsigned long data)
{
struct iscsi_conn *conn = (struct iscsi_conn *) data;
iscsit_inc_conn_usage_count(conn);
spin_lock_bh(&conn->nopin_timer_lock);
if (conn->nopin_response_timer_flags & ISCSI_TF_STOP) {
spin_unlock_bh(&conn->nopin_timer_lock);
iscsit_dec_conn_usage_count(conn);
return;
}
pr_debug("Did not receive response to NOPIN on CID: %hu on"
" SID: %u, failing connection.\n", conn->cid,
conn->sess->sid);
conn->nopin_response_timer_flags &= ~ISCSI_TF_RUNNING;
spin_unlock_bh(&conn->nopin_timer_lock);
{
struct iscsi_portal_group *tpg = conn->sess->tpg;
struct iscsi_tiqn *tiqn = tpg->tpg_tiqn;
if (tiqn) {
spin_lock_bh(&tiqn->sess_err_stats.lock);
strcpy(tiqn->sess_err_stats.last_sess_fail_rem_name,
(void *)conn->sess->sess_ops->InitiatorName);
tiqn->sess_err_stats.last_sess_failure_type =
ISCSI_SESS_ERR_CXN_TIMEOUT;
tiqn->sess_err_stats.cxn_timeout_errors++;
conn->sess->conn_timeout_errors++;
spin_unlock_bh(&tiqn->sess_err_stats.lock);
}
}
iscsit_cause_connection_reinstatement(conn, 0);
iscsit_dec_conn_usage_count(conn);
}
void iscsit_mod_nopin_response_timer(struct iscsi_conn *conn)
{
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);
spin_lock_bh(&conn->nopin_timer_lock);
if (!(conn->nopin_response_timer_flags & ISCSI_TF_RUNNING)) {
spin_unlock_bh(&conn->nopin_timer_lock);
return;
}
mod_timer(&conn->nopin_response_timer,
(get_jiffies_64() + na->nopin_response_timeout * HZ));
spin_unlock_bh(&conn->nopin_timer_lock);
}
/*
* Called with conn->nopin_timer_lock held.
*/
void iscsit_start_nopin_response_timer(struct iscsi_conn *conn)
{
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);
spin_lock_bh(&conn->nopin_timer_lock);
if (conn->nopin_response_timer_flags & ISCSI_TF_RUNNING) {
spin_unlock_bh(&conn->nopin_timer_lock);
return;
}
init_timer(&conn->nopin_response_timer);
conn->nopin_response_timer.expires =
(get_jiffies_64() + na->nopin_response_timeout * HZ);
conn->nopin_response_timer.data = (unsigned long)conn;
conn->nopin_response_timer.function = iscsit_handle_nopin_response_timeout;
conn->nopin_response_timer_flags &= ~ISCSI_TF_STOP;
conn->nopin_response_timer_flags |= ISCSI_TF_RUNNING;
add_timer(&conn->nopin_response_timer);
pr_debug("Started NOPIN Response Timer on CID: %d to %u"
" seconds\n", conn->cid, na->nopin_response_timeout);
spin_unlock_bh(&conn->nopin_timer_lock);
}
void iscsit_stop_nopin_response_timer(struct iscsi_conn *conn)
{
spin_lock_bh(&conn->nopin_timer_lock);
if (!(conn->nopin_response_timer_flags & ISCSI_TF_RUNNING)) {
spin_unlock_bh(&conn->nopin_timer_lock);
return;
}
conn->nopin_response_timer_flags |= ISCSI_TF_STOP;
spin_unlock_bh(&conn->nopin_timer_lock);
del_timer_sync(&conn->nopin_response_timer);
spin_lock_bh(&conn->nopin_timer_lock);
conn->nopin_response_timer_flags &= ~ISCSI_TF_RUNNING;
spin_unlock_bh(&conn->nopin_timer_lock);
}
static void iscsit_handle_nopin_timeout(unsigned long data)
{
struct iscsi_conn *conn = (struct iscsi_conn *) data;
iscsit_inc_conn_usage_count(conn);
spin_lock_bh(&conn->nopin_timer_lock);
if (conn->nopin_timer_flags & ISCSI_TF_STOP) {
spin_unlock_bh(&conn->nopin_timer_lock);
iscsit_dec_conn_usage_count(conn);
return;
}
conn->nopin_timer_flags &= ~ISCSI_TF_RUNNING;
spin_unlock_bh(&conn->nopin_timer_lock);
iscsit_add_nopin(conn, 1);
iscsit_dec_conn_usage_count(conn);
}
/*
* Called with conn->nopin_timer_lock held.
*/
void __iscsit_start_nopin_timer(struct iscsi_conn *conn)
{
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);
/*
* NOPIN timeout is disabled.
*/
if (!na->nopin_timeout)
return;
if (conn->nopin_timer_flags & ISCSI_TF_RUNNING)
return;
init_timer(&conn->nopin_timer);
conn->nopin_timer.expires = (get_jiffies_64() + na->nopin_timeout * HZ);
conn->nopin_timer.data = (unsigned long)conn;
conn->nopin_timer.function = iscsit_handle_nopin_timeout;
conn->nopin_timer_flags &= ~ISCSI_TF_STOP;
conn->nopin_timer_flags |= ISCSI_TF_RUNNING;
add_timer(&conn->nopin_timer);
pr_debug("Started NOPIN Timer on CID: %d at %u second"
" interval\n", conn->cid, na->nopin_timeout);
}
void iscsit_start_nopin_timer(struct iscsi_conn *conn)
{
struct iscsi_session *sess = conn->sess;
struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);
/*
* NOPIN timeout is disabled..
*/
if (!na->nopin_timeout)
return;
spin_lock_bh(&conn->nopin_timer_lock);
if (conn->nopin_timer_flags & ISCSI_TF_RUNNING) {
spin_unlock_bh(&conn->nopin_timer_lock);
return;
}
init_timer(&conn->nopin_timer);
conn->nopin_timer.expires = (get_jiffies_64() + na->nopin_timeout * HZ);
conn->nopin_timer.data = (unsigned long)conn;
conn->nopin_timer.function = iscsit_handle_nopin_timeout;
conn->nopin_timer_flags &= ~ISCSI_TF_STOP;
conn->nopin_timer_flags |= ISCSI_TF_RUNNING;
add_timer(&conn->nopin_timer);
pr_debug("Started NOPIN Timer on CID: %d at %u second"
" interval\n", conn->cid, na->nopin_timeout);
spin_unlock_bh(&conn->nopin_timer_lock);
}
void iscsit_stop_nopin_timer(struct iscsi_conn *conn)
{
spin_lock_bh(&conn->nopin_timer_lock);
if (!(conn->nopin_timer_flags & ISCSI_TF_RUNNING)) {
spin_unlock_bh(&conn->nopin_timer_lock);
return;
}
conn->nopin_timer_flags |= ISCSI_TF_STOP;
spin_unlock_bh(&conn->nopin_timer_lock);
del_timer_sync(&conn->nopin_timer);
spin_lock_bh(&conn->nopin_timer_lock);
conn->nopin_timer_flags &= ~ISCSI_TF_RUNNING;
spin_unlock_bh(&conn->nopin_timer_lock);
}
int iscsit_send_tx_data(
struct iscsi_cmd *cmd,
struct iscsi_conn *conn,
int use_misc)
{
int tx_sent, tx_size;
u32 iov_count;
struct kvec *iov;
send_data:
tx_size = cmd->tx_size;
if (!use_misc) {
iov = &cmd->iov_data[0];
iov_count = cmd->iov_data_count;
} else {
iov = &cmd->iov_misc[0];
iov_count = cmd->iov_misc_count;
}
tx_sent = tx_data(conn, &iov[0], iov_count, tx_size);
if (tx_size != tx_sent) {
if (tx_sent == -EAGAIN) {
pr_err("tx_data() returned -EAGAIN\n");
goto send_data;
} else
return -1;
}
cmd->tx_size = 0;
return 0;
}
int iscsit_fe_sendpage_sg(
struct iscsi_cmd *cmd,
struct iscsi_conn *conn)
{
struct scatterlist *sg = cmd->first_data_sg;
struct kvec iov;
u32 tx_hdr_size, data_len;
u32 offset = cmd->first_data_sg_off;
int tx_sent;
send_hdr:
tx_hdr_size = ISCSI_HDR_LEN;
if (conn->conn_ops->HeaderDigest)
tx_hdr_size += ISCSI_CRC_LEN;
iov.iov_base = cmd->pdu;
iov.iov_len = tx_hdr_size;
tx_sent = tx_data(conn, &iov, 1, tx_hdr_size);
if (tx_hdr_size != tx_sent) {
if (tx_sent == -EAGAIN) {
pr_err("tx_data() returned -EAGAIN\n");
goto send_hdr;
}
return -1;
}
data_len = cmd->tx_size - tx_hdr_size - cmd->padding;
if (conn->conn_ops->DataDigest)
data_len -= ISCSI_CRC_LEN;
/*
* Perform sendpage() for each page in the scatterlist
*/
while (data_len) {
u32 space = (sg->length - offset);
u32 sub_len = min_t(u32, data_len, space);
send_pg:
tx_sent = conn->sock->ops->sendpage(conn->sock,
sg_page(sg), sg->offset + offset, sub_len, 0);
if (tx_sent != sub_len) {
if (tx_sent == -EAGAIN) {
pr_err("tcp_sendpage() returned"
" -EAGAIN\n");
goto send_pg;
}
pr_err("tcp_sendpage() failure: %d\n",
tx_sent);
return -1;
}
data_len -= sub_len;
offset = 0;
sg = sg_next(sg);
}
send_padding:
if (cmd->padding) {
struct kvec *iov_p =
&cmd->iov_data[cmd->iov_data_count-1];
tx_sent = tx_data(conn, iov_p, 1, cmd->padding);
if (cmd->padding != tx_sent) {
if (tx_sent == -EAGAIN) {
pr_err("tx_data() returned -EAGAIN\n");
goto send_padding;
}
return -1;
}
}
send_datacrc:
if (conn->conn_ops->DataDigest) {
struct kvec *iov_d =
&cmd->iov_data[cmd->iov_data_count];
tx_sent = tx_data(conn, iov_d, 1, ISCSI_CRC_LEN);
if (ISCSI_CRC_LEN != tx_sent) {
if (tx_sent == -EAGAIN) {
pr_err("tx_data() returned -EAGAIN\n");
goto send_datacrc;
}
return -1;
}
}
return 0;
}
/*
* This function is used for mainly sending a ISCSI_TARG_LOGIN_RSP PDU
* back to the Initiator when an expection condition occurs with the
* errors set in status_class and status_detail.
*
* Parameters: iSCSI Connection, Status Class, Status Detail.
* Returns: 0 on success, -1 on error.
*/
int iscsit_tx_login_rsp(struct iscsi_conn *conn, u8 status_class, u8 status_detail)
{
u8 iscsi_hdr[ISCSI_HDR_LEN];
int err;
struct kvec iov;
struct iscsi_login_rsp *hdr;
iscsit_collect_login_stats(conn, status_class, status_detail);
memset(&iov, 0, sizeof(struct kvec));
memset(&iscsi_hdr, 0x0, ISCSI_HDR_LEN);
hdr = (struct iscsi_login_rsp *)&iscsi_hdr;
hdr->opcode = ISCSI_OP_LOGIN_RSP;
hdr->status_class = status_class;
hdr->status_detail = status_detail;
hdr->itt = cpu_to_be32(conn->login_itt);
iov.iov_base = &iscsi_hdr;
iov.iov_len = ISCSI_HDR_LEN;
PRINT_BUFF(iscsi_hdr, ISCSI_HDR_LEN);
err = tx_data(conn, &iov, 1, ISCSI_HDR_LEN);
if (err != ISCSI_HDR_LEN) {
pr_err("tx_data returned less than expected\n");
return -1;
}
return 0;
}
void iscsit_print_session_params(struct iscsi_session *sess)
{
struct iscsi_conn *conn;
pr_debug("-----------------------------[Session Params for"
" SID: %u]-----------------------------\n", sess->sid);
spin_lock_bh(&sess->conn_lock);
list_for_each_entry(conn, &sess->sess_conn_list, conn_list)
iscsi_dump_conn_ops(conn->conn_ops);
spin_unlock_bh(&sess->conn_lock);
iscsi_dump_sess_ops(sess->sess_ops);
}
static int iscsit_do_rx_data(
struct iscsi_conn *conn,
struct iscsi_data_count *count)
{
int data = count->data_length, rx_loop = 0, total_rx = 0, iov_len;
u32 rx_marker_val[count->ss_marker_count], rx_marker_iov = 0;
struct kvec iov[count->ss_iov_count], *iov_p;
struct msghdr msg;
if (!conn || !conn->sock || !conn->conn_ops)
return -1;
memset(&msg, 0, sizeof(struct msghdr));
if (count->sync_and_steering) {
int size = 0;
u32 i, orig_iov_count = 0;
u32 orig_iov_len = 0, orig_iov_loc = 0;
u32 iov_count = 0, per_iov_bytes = 0;
u32 *rx_marker, old_rx_marker = 0;
struct kvec *iov_record;
memset(&rx_marker_val, 0,
count->ss_marker_count * sizeof(u32));
memset(&iov, 0, count->ss_iov_count * sizeof(struct kvec));
iov_record = count->iov;
orig_iov_count = count->iov_count;
rx_marker = &conn->of_marker;
i = 0;
size = data;
orig_iov_len = iov_record[orig_iov_loc].iov_len;
while (size > 0) {
pr_debug("rx_data: #1 orig_iov_len %u,"
" orig_iov_loc %u\n", orig_iov_len, orig_iov_loc);
pr_debug("rx_data: #2 rx_marker %u, size"
" %u\n", *rx_marker, size);
if (orig_iov_len >= *rx_marker) {
iov[iov_count].iov_len = *rx_marker;
iov[iov_count++].iov_base =
(iov_record[orig_iov_loc].iov_base +
per_iov_bytes);
iov[iov_count].iov_len = (MARKER_SIZE / 2);
iov[iov_count++].iov_base =
&rx_marker_val[rx_marker_iov++];
iov[iov_count].iov_len = (MARKER_SIZE / 2);
iov[iov_count++].iov_base =
&rx_marker_val[rx_marker_iov++];
old_rx_marker = *rx_marker;
/*
* OFMarkInt is in 32-bit words.
*/
*rx_marker = (conn->conn_ops->OFMarkInt * 4);
size -= old_rx_marker;
orig_iov_len -= old_rx_marker;
per_iov_bytes += old_rx_marker;
pr_debug("rx_data: #3 new_rx_marker"
" %u, size %u\n", *rx_marker, size);
} else {
iov[iov_count].iov_len = orig_iov_len;
iov[iov_count++].iov_base =
(iov_record[orig_iov_loc].iov_base +
per_iov_bytes);
per_iov_bytes = 0;
*rx_marker -= orig_iov_len;
size -= orig_iov_len;
if (size)
orig_iov_len =
iov_record[++orig_iov_loc].iov_len;
pr_debug("rx_data: #4 new_rx_marker"
" %u, size %u\n", *rx_marker, size);
}
}
data += (rx_marker_iov * (MARKER_SIZE / 2));
iov_p = &iov[0];
iov_len = iov_count;
if (iov_count > count->ss_iov_count) {
pr_err("iov_count: %d, count->ss_iov_count:"
" %d\n", iov_count, count->ss_iov_count);
return -1;
}
if (rx_marker_iov > count->ss_marker_count) {
pr_err("rx_marker_iov: %d, count->ss_marker"
"_count: %d\n", rx_marker_iov,
count->ss_marker_count);
return -1;
}
} else {
iov_p = count->iov;
iov_len = count->iov_count;
}
while (total_rx < data) {
rx_loop = kernel_recvmsg(conn->sock, &msg, iov_p, iov_len,
(data - total_rx), MSG_WAITALL);
if (rx_loop <= 0) {
pr_debug("rx_loop: %d total_rx: %d\n",
rx_loop, total_rx);
return rx_loop;
}
total_rx += rx_loop;
pr_debug("rx_loop: %d, total_rx: %d, data: %d\n",
rx_loop, total_rx, data);
}
if (count->sync_and_steering) {
int j;
for (j = 0; j < rx_marker_iov; j++) {
pr_debug("rx_data: #5 j: %d, offset: %d\n",
j, rx_marker_val[j]);
conn->of_marker_offset = rx_marker_val[j];
}
total_rx -= (rx_marker_iov * (MARKER_SIZE / 2));
}
return total_rx;
}
static int iscsit_do_tx_data(
struct iscsi_conn *conn,
struct iscsi_data_count *count)
{
int data = count->data_length, total_tx = 0, tx_loop = 0, iov_len;
u32 tx_marker_val[count->ss_marker_count], tx_marker_iov = 0;
struct kvec iov[count->ss_iov_count], *iov_p;
struct msghdr msg;
if (!conn || !conn->sock || !conn->conn_ops)
return -1;
if (data <= 0) {
pr_err("Data length is: %d\n", data);
return -1;
}
memset(&msg, 0, sizeof(struct msghdr));
if (count->sync_and_steering) {
int size = 0;
u32 i, orig_iov_count = 0;
u32 orig_iov_len = 0, orig_iov_loc = 0;
u32 iov_count = 0, per_iov_bytes = 0;
u32 *tx_marker, old_tx_marker = 0;
struct kvec *iov_record;
memset(&tx_marker_val, 0,
count->ss_marker_count * sizeof(u32));
memset(&iov, 0, count->ss_iov_count * sizeof(struct kvec));
iov_record = count->iov;
orig_iov_count = count->iov_count;
tx_marker = &conn->if_marker;
i = 0;
size = data;
orig_iov_len = iov_record[orig_iov_loc].iov_len;
while (size > 0) {
pr_debug("tx_data: #1 orig_iov_len %u,"
" orig_iov_loc %u\n", orig_iov_len, orig_iov_loc);
pr_debug("tx_data: #2 tx_marker %u, size"
" %u\n", *tx_marker, size);
if (orig_iov_len >= *tx_marker) {
iov[iov_count].iov_len = *tx_marker;
iov[iov_count++].iov_base =
(iov_record[orig_iov_loc].iov_base +
per_iov_bytes);
tx_marker_val[tx_marker_iov] =
(size - *tx_marker);
iov[iov_count].iov_len = (MARKER_SIZE / 2);
iov[iov_count++].iov_base =
&tx_marker_val[tx_marker_iov++];
iov[iov_count].iov_len = (MARKER_SIZE / 2);
iov[iov_count++].iov_base =
&tx_marker_val[tx_marker_iov++];
old_tx_marker = *tx_marker;
/*
* IFMarkInt is in 32-bit words.
*/
*tx_marker = (conn->conn_ops->IFMarkInt * 4);
size -= old_tx_marker;
orig_iov_len -= old_tx_marker;
per_iov_bytes += old_tx_marker;
pr_debug("tx_data: #3 new_tx_marker"
" %u, size %u\n", *tx_marker, size);
pr_debug("tx_data: #4 offset %u\n",
tx_marker_val[tx_marker_iov-1]);
} else {
iov[iov_count].iov_len = orig_iov_len;
iov[iov_count++].iov_base
= (iov_record[orig_iov_loc].iov_base +
per_iov_bytes);
per_iov_bytes = 0;
*tx_marker -= orig_iov_len;
size -= orig_iov_len;
if (size)
orig_iov_len =
iov_record[++orig_iov_loc].iov_len;
pr_debug("tx_data: #5 new_tx_marker"
" %u, size %u\n", *tx_marker, size);
}
}
data += (tx_marker_iov * (MARKER_SIZE / 2));
iov_p = &iov[0];
iov_len = iov_count;
if (iov_count > count->ss_iov_count) {
pr_err("iov_count: %d, count->ss_iov_count:"
" %d\n", iov_count, count->ss_iov_count);
return -1;
}
if (tx_marker_iov > count->ss_marker_count) {
pr_err("tx_marker_iov: %d, count->ss_marker"
"_count: %d\n", tx_marker_iov,
count->ss_marker_count);
return -1;
}
} else {
iov_p = count->iov;
iov_len = count->iov_count;
}
while (total_tx < data) {
tx_loop = kernel_sendmsg(conn->sock, &msg, iov_p, iov_len,
(data - total_tx));
if (tx_loop <= 0) {
pr_debug("tx_loop: %d total_tx %d\n",
tx_loop, total_tx);
return tx_loop;
}
total_tx += tx_loop;
pr_debug("tx_loop: %d, total_tx: %d, data: %d\n",
tx_loop, total_tx, data);
}
if (count->sync_and_steering)
total_tx -= (tx_marker_iov * (MARKER_SIZE / 2));
return total_tx;
}
int rx_data(
struct iscsi_conn *conn,
struct kvec *iov,
int iov_count,
int data)
{
struct iscsi_data_count c;
if (!conn || !conn->sock || !conn->conn_ops)
return -1;
memset(&c, 0, sizeof(struct iscsi_data_count));
c.iov = iov;
c.iov_count = iov_count;
c.data_length = data;
c.type = ISCSI_RX_DATA;
if (conn->conn_ops->OFMarker &&
(conn->conn_state >= TARG_CONN_STATE_LOGGED_IN)) {
if (iscsit_determine_sync_and_steering_counts(conn, &c) < 0)
return -1;
}
return iscsit_do_rx_data(conn, &c);
}
int tx_data(
struct iscsi_conn *conn,
struct kvec *iov,
int iov_count,
int data)
{
struct iscsi_data_count c;
if (!conn || !conn->sock || !conn->conn_ops)
return -1;
memset(&c, 0, sizeof(struct iscsi_data_count));
c.iov = iov;
c.iov_count = iov_count;
c.data_length = data;
c.type = ISCSI_TX_DATA;
if (conn->conn_ops->IFMarker &&
(conn->conn_state >= TARG_CONN_STATE_LOGGED_IN)) {
if (iscsit_determine_sync_and_steering_counts(conn, &c) < 0)
return -1;
}
return iscsit_do_tx_data(conn, &c);
}
void iscsit_collect_login_stats(
struct iscsi_conn *conn,
u8 status_class,
u8 status_detail)
{
struct iscsi_param *intrname = NULL;
struct iscsi_tiqn *tiqn;
struct iscsi_login_stats *ls;
tiqn = iscsit_snmp_get_tiqn(conn);
if (!tiqn)
return;
ls = &tiqn->login_stats;
spin_lock(&ls->lock);
if (!strcmp(conn->login_ip, ls->last_intr_fail_ip_addr) &&
((get_jiffies_64() - ls->last_fail_time) < 10)) {
/* We already have the failure info for this login */
spin_unlock(&ls->lock);
return;
}
if (status_class == ISCSI_STATUS_CLS_SUCCESS)
ls->accepts++;
else if (status_class == ISCSI_STATUS_CLS_REDIRECT) {
ls->redirects++;
ls->last_fail_type = ISCSI_LOGIN_FAIL_REDIRECT;
} else if ((status_class == ISCSI_STATUS_CLS_INITIATOR_ERR) &&
(status_detail == ISCSI_LOGIN_STATUS_AUTH_FAILED)) {
ls->authenticate_fails++;
ls->last_fail_type = ISCSI_LOGIN_FAIL_AUTHENTICATE;
} else if ((status_class == ISCSI_STATUS_CLS_INITIATOR_ERR) &&
(status_detail == ISCSI_LOGIN_STATUS_TGT_FORBIDDEN)) {
ls->authorize_fails++;
ls->last_fail_type = ISCSI_LOGIN_FAIL_AUTHORIZE;
} else if ((status_class == ISCSI_STATUS_CLS_INITIATOR_ERR) &&
(status_detail == ISCSI_LOGIN_STATUS_INIT_ERR)) {
ls->negotiate_fails++;
ls->last_fail_type = ISCSI_LOGIN_FAIL_NEGOTIATE;
} else {
ls->other_fails++;
ls->last_fail_type = ISCSI_LOGIN_FAIL_OTHER;
}
/* Save initiator name, ip address and time, if it is a failed login */
if (status_class != ISCSI_STATUS_CLS_SUCCESS) {
if (conn->param_list)
intrname = iscsi_find_param_from_key(INITIATORNAME,
conn->param_list);
strcpy(ls->last_intr_fail_name,
(intrname ? intrname->value : "Unknown"));
ls->last_intr_fail_ip_family = conn->sock->sk->sk_family;
snprintf(ls->last_intr_fail_ip_addr, IPV6_ADDRESS_SPACE,
"%s", conn->login_ip);
ls->last_fail_time = get_jiffies_64();
}
spin_unlock(&ls->lock);
}
struct iscsi_tiqn *iscsit_snmp_get_tiqn(struct iscsi_conn *conn)
{
struct iscsi_portal_group *tpg;
if (!conn || !conn->sess)
return NULL;
tpg = conn->sess->tpg;
if (!tpg)
return NULL;
if (!tpg->tpg_tiqn)
return NULL;
return tpg->tpg_tiqn;
}
#ifndef ISCSI_TARGET_UTIL_H
#define ISCSI_TARGET_UTIL_H
#define MARKER_SIZE 8
extern int iscsit_add_r2t_to_list(struct iscsi_cmd *, u32, u32, int, u32);
extern struct iscsi_r2t *iscsit_get_r2t_for_eos(struct iscsi_cmd *, u32, u32);
extern struct iscsi_r2t *iscsit_get_r2t_from_list(struct iscsi_cmd *);
extern void iscsit_free_r2t(struct iscsi_r2t *, struct iscsi_cmd *);
extern void iscsit_free_r2ts_from_list(struct iscsi_cmd *);
extern struct iscsi_cmd *iscsit_allocate_cmd(struct iscsi_conn *, gfp_t);
extern struct iscsi_cmd *iscsit_allocate_se_cmd(struct iscsi_conn *, u32, int, int);
extern struct iscsi_cmd *iscsit_allocate_se_cmd_for_tmr(struct iscsi_conn *, u8);
extern int iscsit_decide_list_to_build(struct iscsi_cmd *, u32);
extern struct iscsi_seq *iscsit_get_seq_holder_for_datain(struct iscsi_cmd *, u32);
extern struct iscsi_seq *iscsit_get_seq_holder_for_r2t(struct iscsi_cmd *);
extern struct iscsi_r2t *iscsit_get_holder_for_r2tsn(struct iscsi_cmd *, u32);
int iscsit_sequence_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, u32 cmdsn);
extern int iscsit_check_unsolicited_dataout(struct iscsi_cmd *, unsigned char *);
extern struct iscsi_cmd *iscsit_find_cmd_from_itt(struct iscsi_conn *, u32);
extern struct iscsi_cmd *iscsit_find_cmd_from_itt_or_dump(struct iscsi_conn *,
u32, u32);
extern struct iscsi_cmd *iscsit_find_cmd_from_ttt(struct iscsi_conn *, u32);
extern int iscsit_find_cmd_for_recovery(struct iscsi_session *, struct iscsi_cmd **,
struct iscsi_conn_recovery **, u32);
extern void iscsit_add_cmd_to_immediate_queue(struct iscsi_cmd *, struct iscsi_conn *, u8);
extern struct iscsi_queue_req *iscsit_get_cmd_from_immediate_queue(struct iscsi_conn *);
extern void iscsit_add_cmd_to_response_queue(struct iscsi_cmd *, struct iscsi_conn *, u8);
extern struct iscsi_queue_req *iscsit_get_cmd_from_response_queue(struct iscsi_conn *);
extern void iscsit_remove_cmd_from_tx_queues(struct iscsi_cmd *, struct iscsi_conn *);
extern void iscsit_free_queue_reqs_for_conn(struct iscsi_conn *);
extern void iscsit_release_cmd(struct iscsi_cmd *);
extern int iscsit_check_session_usage_count(struct iscsi_session *);
extern void iscsit_dec_session_usage_count(struct iscsi_session *);
extern void iscsit_inc_session_usage_count(struct iscsi_session *);
extern int iscsit_set_sync_and_steering_values(struct iscsi_conn *);
extern struct iscsi_conn *iscsit_get_conn_from_cid(struct iscsi_session *, u16);
extern struct iscsi_conn *iscsit_get_conn_from_cid_rcfr(struct iscsi_session *, u16);
extern void iscsit_check_conn_usage_count(struct iscsi_conn *);
extern void iscsit_dec_conn_usage_count(struct iscsi_conn *);
extern void iscsit_inc_conn_usage_count(struct iscsi_conn *);
extern void iscsit_mod_nopin_response_timer(struct iscsi_conn *);
extern void iscsit_start_nopin_response_timer(struct iscsi_conn *);
extern void iscsit_stop_nopin_response_timer(struct iscsi_conn *);
extern void __iscsit_start_nopin_timer(struct iscsi_conn *);
extern void iscsit_start_nopin_timer(struct iscsi_conn *);
extern void iscsit_stop_nopin_timer(struct iscsi_conn *);
extern int iscsit_send_tx_data(struct iscsi_cmd *, struct iscsi_conn *, int);
extern int iscsit_fe_sendpage_sg(struct iscsi_cmd *, struct iscsi_conn *);
extern int iscsit_tx_login_rsp(struct iscsi_conn *, u8, u8);
extern void iscsit_print_session_params(struct iscsi_session *);
extern int iscsit_print_dev_to_proc(char *, char **, off_t, int);
extern int iscsit_print_sessions_to_proc(char *, char **, off_t, int);
extern int iscsit_print_tpg_to_proc(char *, char **, off_t, int);
extern int rx_data(struct iscsi_conn *, struct kvec *, int, int);
extern int tx_data(struct iscsi_conn *, struct kvec *, int, int);
extern void iscsit_collect_login_stats(struct iscsi_conn *, u8, u8);
extern struct iscsi_tiqn *iscsit_snmp_get_tiqn(struct iscsi_conn *);
#endif /*** ISCSI_TARGET_UTIL_H ***/
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment