diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2011-07-28 00:21:40 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-07-28 00:21:40 +0400 |
commit | 91d41fdf31f74e6e2e5f3cb018eca4200e36e202 (patch) | |
tree | 81ef11f06bd8047031a2d93706dc263fa1bacd56 /drivers | |
parent | c1095c6da518b0b64e724f629051fa67655cd8d9 (diff) | |
parent | 277c5f27a2c86a9a733c0ec0f6a9b1032dfa3e15 (diff) | |
download | linux-91d41fdf31f74e6e2e5f3cb018eca4200e36e202.tar.xz |
Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/nab/target-pending
* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/nab/target-pending:
target: Convert to DIV_ROUND_UP_SECTOR_T usage for sectors / dev_max_sectors
kernel.h: Add DIV_ROUND_UP_ULL and DIV_ROUND_UP_SECTOR_T macro usage
iscsi-target: Add iSCSI fabric support for target v4.1
iscsi: Add Serial Number Arithmetic LT and GT into iscsi_proto.h
iscsi: Use struct scsi_lun in iscsi structs instead of u8[8]
iscsi: Resolve iscsi_proto.h naming conflicts with drivers/target/iscsi
Diffstat (limited to 'drivers')
47 files changed, 22113 insertions, 32 deletions
diff --git a/drivers/infiniband/ulp/iser/iser_initiator.c b/drivers/infiniband/ulp/iser/iser_initiator.c index 95a08a8ca8aa..5745b7fe158c 100644 --- a/drivers/infiniband/ulp/iser/iser_initiator.c +++ b/drivers/infiniband/ulp/iser/iser_initiator.c @@ -271,7 +271,7 @@ int iser_send_command(struct iscsi_conn *conn, unsigned long edtl; int err; struct iser_data_buf *data_buf; - struct iscsi_cmd *hdr = (struct iscsi_cmd *)task->hdr; + struct iscsi_scsi_req *hdr = (struct iscsi_scsi_req *)task->hdr; struct scsi_cmnd *sc = task->sc; struct iser_tx_desc *tx_desc = &iser_task->desc; diff --git a/drivers/scsi/be2iscsi/be_main.h b/drivers/scsi/be2iscsi/be_main.h index 081c171a1ed6..5ce5170254ca 100644 --- a/drivers/scsi/be2iscsi/be_main.h +++ b/drivers/scsi/be2iscsi/be_main.h @@ -397,7 +397,7 @@ struct amap_pdu_data_out { }; struct be_cmd_bhs { - struct iscsi_cmd iscsi_hdr; + struct iscsi_scsi_req iscsi_hdr; unsigned char pad1[16]; struct pdu_data_out iscsi_data_pdu; unsigned char pad2[BE_SENSE_INFO_SIZE - @@ -428,7 +428,7 @@ struct be_nonio_bhs { }; struct be_status_bhs { - struct iscsi_cmd iscsi_hdr; + struct iscsi_scsi_req iscsi_hdr; unsigned char pad1[16]; /** * The plus 2 below is to hold the sense info length that gets diff --git a/drivers/scsi/bnx2i/bnx2i_hwi.c b/drivers/scsi/bnx2i/bnx2i_hwi.c index 030a96c646c3..9ae80cd5953b 100644 --- a/drivers/scsi/bnx2i/bnx2i_hwi.c +++ b/drivers/scsi/bnx2i/bnx2i_hwi.c @@ -332,11 +332,11 @@ int bnx2i_send_iscsi_login(struct bnx2i_conn *bnx2i_conn, { struct bnx2i_cmd *bnx2i_cmd; struct bnx2i_login_request *login_wqe; - struct iscsi_login *login_hdr; + struct iscsi_login_req *login_hdr; u32 dword; bnx2i_cmd = (struct bnx2i_cmd *)task->dd_data; - login_hdr = (struct iscsi_login *)task->hdr; + login_hdr = (struct iscsi_login_req *)task->hdr; login_wqe = (struct bnx2i_login_request *) bnx2i_conn->ep->qp.sq_prod_qe; @@ -1349,7 +1349,7 @@ int bnx2i_process_scsi_cmd_resp(struct iscsi_session *session, struct bnx2i_cmd_response *resp_cqe; struct bnx2i_cmd *bnx2i_cmd; struct iscsi_task *task; - struct iscsi_cmd_rsp *hdr; + struct iscsi_scsi_rsp *hdr; u32 datalen = 0; resp_cqe = (struct bnx2i_cmd_response *)cqe; @@ -1376,7 +1376,7 @@ int bnx2i_process_scsi_cmd_resp(struct iscsi_session *session, } bnx2i_iscsi_unmap_sg_list(bnx2i_cmd); - hdr = (struct iscsi_cmd_rsp *)task->hdr; + hdr = (struct iscsi_scsi_rsp *)task->hdr; resp_cqe = (struct bnx2i_cmd_response *)cqe; hdr->opcode = resp_cqe->op_code; hdr->max_cmdsn = cpu_to_be32(resp_cqe->max_cmd_sn); diff --git a/drivers/scsi/bnx2i/bnx2i_iscsi.c b/drivers/scsi/bnx2i/bnx2i_iscsi.c index 5c55a75ae597..cffd4d75df56 100644 --- a/drivers/scsi/bnx2i/bnx2i_iscsi.c +++ b/drivers/scsi/bnx2i/bnx2i_iscsi.c @@ -1213,7 +1213,7 @@ static int bnx2i_task_xmit(struct iscsi_task *task) struct bnx2i_conn *bnx2i_conn = conn->dd_data; struct scsi_cmnd *sc = task->sc; struct bnx2i_cmd *cmd = task->dd_data; - struct iscsi_cmd *hdr = (struct iscsi_cmd *) task->hdr; + struct iscsi_scsi_req *hdr = (struct iscsi_scsi_req *)task->hdr; if (atomic_read(&bnx2i_conn->ep->num_active_cmds) + 1 > hba->max_sqes) diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c index d7a4120034a2..256a999d010b 100644 --- a/drivers/scsi/libiscsi.c +++ b/drivers/scsi/libiscsi.c @@ -84,22 +84,6 @@ MODULE_PARM_DESC(debug_libiscsi_eh, __func__, ##arg); \ } while (0); -/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */ -#define SNA32_CHECK 2147483648UL - -static int iscsi_sna_lt(u32 n1, u32 n2) -{ - return n1 != n2 && ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) || - (n1 > n2 && (n2 - n1 < SNA32_CHECK))); -} - -/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */ -static int iscsi_sna_lte(u32 n1, u32 n2) -{ - return n1 == n2 || ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) || - (n1 > n2 && (n2 - n1 < SNA32_CHECK))); -} - inline void iscsi_conn_queue_work(struct iscsi_conn *conn) { struct Scsi_Host *shost = conn->session->host; @@ -360,7 +344,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task) struct iscsi_conn *conn = task->conn; struct iscsi_session *session = conn->session; struct scsi_cmnd *sc = task->sc; - struct iscsi_cmd *hdr; + struct iscsi_scsi_req *hdr; unsigned hdrlength, cmd_len; itt_t itt; int rc; @@ -374,7 +358,7 @@ static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task) if (rc) return rc; } - hdr = (struct iscsi_cmd *) task->hdr; + hdr = (struct iscsi_scsi_req *)task->hdr; itt = hdr->itt; memset(hdr, 0, sizeof(*hdr)); @@ -830,7 +814,7 @@ static void iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr, struct iscsi_task *task, char *data, int datalen) { - struct iscsi_cmd_rsp *rhdr = (struct iscsi_cmd_rsp *)hdr; + struct iscsi_scsi_rsp *rhdr = (struct iscsi_scsi_rsp *)hdr; struct iscsi_session *session = conn->session; struct scsi_cmnd *sc = task->sc; diff --git a/drivers/target/Kconfig b/drivers/target/Kconfig index 5cb0f0ef6af0..b28794b72125 100644 --- a/drivers/target/Kconfig +++ b/drivers/target/Kconfig @@ -31,5 +31,6 @@ config TCM_PSCSI source "drivers/target/loopback/Kconfig" source "drivers/target/tcm_fc/Kconfig" +source "drivers/target/iscsi/Kconfig" endif diff --git a/drivers/target/Makefile b/drivers/target/Makefile index 21df808a992c..1060c7b7f803 100644 --- a/drivers/target/Makefile +++ b/drivers/target/Makefile @@ -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/ diff --git a/drivers/target/iscsi/Kconfig b/drivers/target/iscsi/Kconfig new file mode 100644 index 000000000000..564ff4e0dbc4 --- /dev/null +++ b/drivers/target/iscsi/Kconfig @@ -0,0 +1,8 @@ +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. diff --git a/drivers/target/iscsi/Makefile b/drivers/target/iscsi/Makefile new file mode 100644 index 000000000000..5b9a2cf7f0a9 --- /dev/null +++ b/drivers/target/iscsi/Makefile @@ -0,0 +1,20 @@ +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 diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c new file mode 100644 index 000000000000..14c81c4265bd --- /dev/null +++ b/drivers/target/iscsi/iscsi_target.c @@ -0,0 +1,4559 @@ +/******************************************************************************* + * This file contains main functions related to the iSCSI Target Core 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 <linux/completion.h> +#include <asm/unaligned.h> +#include <scsi/scsi_device.h> +#include <scsi/iscsi_proto.h> +#include <target/target_core_base.h> +#include <target/target_core_tmr.h> +#include <target/target_core_transport.h> + +#include "iscsi_target_core.h" +#include "iscsi_target_parameters.h" +#include "iscsi_target_seq_pdu_list.h" +#include "iscsi_target_tq.h" +#include "iscsi_target_configfs.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_login.h" +#include "iscsi_target_tmr.h" +#include "iscsi_target_tpg.h" +#include "iscsi_target_util.h" +#include "iscsi_target.h" +#include "iscsi_target_device.h" +#include "iscsi_target_stat.h" + +static LIST_HEAD(g_tiqn_list); +static LIST_HEAD(g_np_list); +static DEFINE_SPINLOCK(tiqn_lock); +static DEFINE_SPINLOCK(np_lock); + +static struct idr tiqn_idr; +struct idr sess_idr; +struct mutex auth_id_lock; +spinlock_t sess_idr_lock; + +struct iscsit_global *iscsit_global; + +struct kmem_cache *lio_cmd_cache; +struct kmem_cache *lio_qr_cache; +struct kmem_cache *lio_dr_cache; +struct kmem_cache *lio_ooo_cache; +struct kmem_cache *lio_r2t_cache; + +static int iscsit_handle_immediate_data(struct iscsi_cmd *, + unsigned char *buf, u32); +static int iscsit_logout_post_handler(struct iscsi_cmd *, struct iscsi_conn *); + +struct iscsi_tiqn *iscsit_get_tiqn_for_login(unsigned char *buf) +{ + struct iscsi_tiqn *tiqn = NULL; + + spin_lock(&tiqn_lock); + list_for_each_entry(tiqn, &g_tiqn_list, tiqn_list) { + if (!strcmp(tiqn->tiqn, buf)) { + + spin_lock(&tiqn->tiqn_state_lock); + if (tiqn->tiqn_state == TIQN_STATE_ACTIVE) { + tiqn->tiqn_access_count++; + spin_unlock(&tiqn->tiqn_state_lock); + spin_unlock(&tiqn_lock); + return tiqn; + } + spin_unlock(&tiqn->tiqn_state_lock); + } + } + spin_unlock(&tiqn_lock); + + return NULL; +} + +static int iscsit_set_tiqn_shutdown(struct iscsi_tiqn *tiqn) +{ + spin_lock(&tiqn->tiqn_state_lock); + if (tiqn->tiqn_state == TIQN_STATE_ACTIVE) { + tiqn->tiqn_state = TIQN_STATE_SHUTDOWN; + spin_unlock(&tiqn->tiqn_state_lock); + return 0; + } + spin_unlock(&tiqn->tiqn_state_lock); + + return -1; +} + +void iscsit_put_tiqn_for_login(struct iscsi_tiqn *tiqn) +{ + spin_lock(&tiqn->tiqn_state_lock); + tiqn->tiqn_access_count--; + spin_unlock(&tiqn->tiqn_state_lock); +} + +/* + * Note that IQN formatting is expected to be done in userspace, and + * no explict IQN format checks are done here. + */ +struct iscsi_tiqn *iscsit_add_tiqn(unsigned char *buf) +{ + struct iscsi_tiqn *tiqn = NULL; + int ret; + + if (strlen(buf) > ISCSI_IQN_LEN) { + pr_err("Target IQN exceeds %d bytes\n", + ISCSI_IQN_LEN); + return ERR_PTR(-EINVAL); + } + + tiqn = kzalloc(sizeof(struct iscsi_tiqn), GFP_KERNEL); + if (!tiqn) { + pr_err("Unable to allocate struct iscsi_tiqn\n"); + return ERR_PTR(-ENOMEM); + } + + sprintf(tiqn->tiqn, "%s", buf); + INIT_LIST_HEAD(&tiqn->tiqn_list); + INIT_LIST_HEAD(&tiqn->tiqn_tpg_list); + spin_lock_init(&tiqn->tiqn_state_lock); + spin_lock_init(&tiqn->tiqn_tpg_lock); + spin_lock_init(&tiqn->sess_err_stats.lock); + spin_lock_init(&tiqn->login_stats.lock); + spin_lock_init(&tiqn->logout_stats.lock); + + if (!idr_pre_get(&tiqn_idr, GFP_KERNEL)) { + pr_err("idr_pre_get() for tiqn_idr failed\n"); + kfree(tiqn); + return ERR_PTR(-ENOMEM); + } + tiqn->tiqn_state = TIQN_STATE_ACTIVE; + + spin_lock(&tiqn_lock); + ret = idr_get_new(&tiqn_idr, NULL, &tiqn->tiqn_index); + if (ret < 0) { + pr_err("idr_get_new() failed for tiqn->tiqn_index\n"); + spin_unlock(&tiqn_lock); + kfree(tiqn); + return ERR_PTR(ret); + } + list_add_tail(&tiqn->tiqn_list, &g_tiqn_list); + spin_unlock(&tiqn_lock); + + pr_debug("CORE[0] - Added iSCSI Target IQN: %s\n", tiqn->tiqn); + + return tiqn; + +} + +static void iscsit_wait_for_tiqn(struct iscsi_tiqn *tiqn) +{ + /* + * Wait for accesses to said struct iscsi_tiqn to end. + */ + spin_lock(&tiqn->tiqn_state_lock); + while (tiqn->tiqn_access_count != 0) { + spin_unlock(&tiqn->tiqn_state_lock); + msleep(10); + spin_lock(&tiqn->tiqn_state_lock); + } + spin_unlock(&tiqn->tiqn_state_lock); +} + +void iscsit_del_tiqn(struct iscsi_tiqn *tiqn) +{ + /* + * iscsit_set_tiqn_shutdown sets tiqn->tiqn_state = TIQN_STATE_SHUTDOWN + * while holding tiqn->tiqn_state_lock. This means that all subsequent + * attempts to access this struct iscsi_tiqn will fail from both transport + * fabric and control code paths. + */ + if (iscsit_set_tiqn_shutdown(tiqn) < 0) { + pr_err("iscsit_set_tiqn_shutdown() failed\n"); + return; + } + + iscsit_wait_for_tiqn(tiqn); + + spin_lock(&tiqn_lock); + list_del(&tiqn->tiqn_list); + idr_remove(&tiqn_idr, tiqn->tiqn_index); + spin_unlock(&tiqn_lock); + + pr_debug("CORE[0] - Deleted iSCSI Target IQN: %s\n", + tiqn->tiqn); + kfree(tiqn); +} + +int iscsit_access_np(struct iscsi_np *np, struct iscsi_portal_group *tpg) +{ + int ret; + /* + * Determine if the network portal is accepting storage traffic. + */ + spin_lock_bh(&np->np_thread_lock); + if (np->np_thread_state != ISCSI_NP_THREAD_ACTIVE) { + spin_unlock_bh(&np->np_thread_lock); + return -1; + } + if (np->np_login_tpg) { + pr_err("np->np_login_tpg() is not NULL!\n"); + spin_unlock_bh(&np->np_thread_lock); + return -1; + } + spin_unlock_bh(&np->np_thread_lock); + /* + * Determine if the portal group is accepting storage traffic. + */ + spin_lock_bh(&tpg->tpg_state_lock); + if (tpg->tpg_state != TPG_STATE_ACTIVE) { + spin_unlock_bh(&tpg->tpg_state_lock); + return -1; + } + spin_unlock_bh(&tpg->tpg_state_lock); + + /* + * Here we serialize access across the TIQN+TPG Tuple. + */ + ret = mutex_lock_interruptible(&tpg->np_login_lock); + if ((ret != 0) || signal_pending(current)) + return -1; + + spin_lock_bh(&np->np_thread_lock); + np->np_login_tpg = tpg; + spin_unlock_bh(&np->np_thread_lock); + + return 0; +} + +int iscsit_deaccess_np(struct iscsi_np *np, struct iscsi_portal_group *tpg) +{ + struct iscsi_tiqn *tiqn = tpg->tpg_tiqn; + + spin_lock_bh(&np->np_thread_lock); + np->np_login_tpg = NULL; + spin_unlock_bh(&np->np_thread_lock); + + mutex_unlock(&tpg->np_login_lock); + + if (tiqn) + iscsit_put_tiqn_for_login(tiqn); + + return 0; +} + +static struct iscsi_np *iscsit_get_np( + struct __kernel_sockaddr_storage *sockaddr, + int network_transport) +{ + struct sockaddr_in *sock_in, *sock_in_e; + struct sockaddr_in6 *sock_in6, *sock_in6_e; + struct iscsi_np *np; + int ip_match = 0; + u16 port; + + spin_lock_bh(&np_lock); + list_for_each_entry(np, &g_np_list, np_list) { + spin_lock(&np->np_thread_lock); + if (np->np_thread_state != ISCSI_NP_THREAD_ACTIVE) { + spin_unlock(&np->np_thread_lock); + continue; + } + + if (sockaddr->ss_family == AF_INET6) { + sock_in6 = (struct sockaddr_in6 *)sockaddr; + sock_in6_e = (struct sockaddr_in6 *)&np->np_sockaddr; + + if (!memcmp((void *)&sock_in6->sin6_addr.in6_u, + (void *)&sock_in6_e->sin6_addr.in6_u, + sizeof(struct in6_addr))) + ip_match = 1; + + port = ntohs(sock_in6->sin6_port); + } else { + sock_in = (struct sockaddr_in *)sockaddr; + sock_in_e = (struct sockaddr_in *)&np->np_sockaddr; + + if (sock_in->sin_addr.s_addr == + sock_in_e->sin_addr.s_addr) + ip_match = 1; + + port = ntohs(sock_in->sin_port); + } + + if ((ip_match == 1) && (np->np_port == port) && + (np->np_network_transport == network_transport)) { + /* + * Increment the np_exports reference count now to + * prevent iscsit_del_np() below from being called + * while iscsi_tpg_add_network_portal() is called. + */ + np->np_exports++; + spin_unlock(&np->np_thread_lock); + spin_unlock_bh(&np_lock); + return np; + } + spin_unlock(&np->np_thread_lock); + } + spin_unlock_bh(&np_lock); + + return NULL; +} + +struct iscsi_np *iscsit_add_np( + struct __kernel_sockaddr_storage *sockaddr, + char *ip_str, + int network_transport) +{ + struct sockaddr_in *sock_in; + struct sockaddr_in6 *sock_in6; + struct iscsi_np *np; + int ret; + /* + * Locate the existing struct iscsi_np if already active.. + */ + np = iscsit_get_np(sockaddr, network_transport); + if (np) + return np; + + np = kzalloc(sizeof(struct iscsi_np), GFP_KERNEL); + if (!np) { + pr_err("Unable to allocate memory for struct iscsi_np\n"); + return ERR_PTR(-ENOMEM); + } + + np->np_flags |= NPF_IP_NETWORK; + if (sockaddr->ss_family == AF_INET6) { + sock_in6 = (struct sockaddr_in6 *)sockaddr; + snprintf(np->np_ip, IPV6_ADDRESS_SPACE, "%s", ip_str); + np->np_port = ntohs(sock_in6->sin6_port); + } else { + sock_in = (struct sockaddr_in *)sockaddr; + sprintf(np->np_ip, "%s", ip_str); + np->np_port = ntohs(sock_in->sin_port); + } + + np->np_network_transport = network_transport; + spin_lock_init(&np->np_thread_lock); + init_completion(&np->np_restart_comp); + INIT_LIST_HEAD(&np->np_list); + + ret = iscsi_target_setup_login_socket(np, sockaddr); + if (ret != 0) { + kfree(np); + return ERR_PTR(ret); + } + + np->np_thread = kthread_run(iscsi_target_login_thread, np, "iscsi_np"); + if (IS_ERR(np->np_thread)) { + pr_err("Unable to create kthread: iscsi_np\n"); + ret = PTR_ERR(np->np_thread); + kfree(np); + return ERR_PTR(ret); + } + /* + * Increment the np_exports reference count now to prevent + * iscsit_del_np() below from being run while a new call to + * iscsi_tpg_add_network_portal() for a matching iscsi_np is + * active. We don't need to hold np->np_thread_lock at this + * point because iscsi_np has not been added to g_np_list yet. + */ + np->np_exports = 1; + + spin_lock_bh(&np_lock); + list_add_tail(&np->np_list, &g_np_list); + spin_unlock_bh(&np_lock); + + pr_debug("CORE[0] - Added Network Portal: %s:%hu on %s\n", + np->np_ip, np->np_port, (np->np_network_transport == ISCSI_TCP) ? + "TCP" : "SCTP"); + + return np; +} + +int iscsit_reset_np_thread( + struct iscsi_np *np, + struct iscsi_tpg_np *tpg_np, + struct iscsi_portal_group *tpg) +{ + spin_lock_bh(&np->np_thread_lock); + if (tpg && tpg_np) { + /* + * The reset operation need only be performed when the + * passed struct iscsi_portal_group has a login in progress + * to one of the network portals. + */ + if (tpg_np->tpg_np->np_login_tpg != tpg) { + spin_unlock_bh(&np->np_thread_lock); + return 0; + } + } + if (np->np_thread_state == ISCSI_NP_THREAD_INACTIVE) { + spin_unlock_bh(&np->np_thread_lock); + return 0; + } + np->np_thread_state = ISCSI_NP_THREAD_RESET; + + if (np->np_thread) { + spin_unlock_bh(&np->np_thread_lock); + send_sig(SIGINT, np->np_thread, 1); + wait_for_completion(&np->np_restart_comp); + spin_lock_bh(&np->np_thread_lock); + } + spin_unlock_bh(&np->np_thread_lock); + + return 0; +} + +int iscsit_del_np_comm(struct iscsi_np *np) +{ + if (!np->np_socket) + return 0; + + /* + * Some network transports allocate their own struct sock->file, + * see if we need to free any additional allocated resources. + */ + if (np->np_flags & NPF_SCTP_STRUCT_FILE) { + kfree(np->np_socket->file); + np->np_socket->file = NULL; + } + + sock_release(np->np_socket); + return 0; +} + +int iscsit_del_np(struct iscsi_np *np) +{ + spin_lock_bh(&np->np_thread_lock); + np->np_exports--; + if (np->np_exports) { + spin_unlock_bh(&np->np_thread_lock); + return 0; + } + np->np_thread_state = ISCSI_NP_THREAD_SHUTDOWN; + spin_unlock_bh(&np->np_thread_lock); + + if (np->np_thread) { + /* + * We need to send the signal to wakeup Linux/Net + * which may be sleeping in sock_accept().. + */ + send_sig(SIGINT, np->np_thread, 1); + kthread_stop(np->np_thread); + } + iscsit_del_np_comm(np); + + spin_lock_bh(&np_lock); + list_del(&np->np_list); + spin_unlock_bh(&np_lock); + + pr_debug("CORE[0] - Removed Network Portal: %s:%hu on %s\n", + np->np_ip, np->np_port, (np->np_network_transport == ISCSI_TCP) ? + "TCP" : "SCTP"); + + kfree(np); + return 0; +} + +static int __init iscsi_target_init_module(void) +{ + int ret = 0; + + pr_debug("iSCSI-Target "ISCSIT_VERSION"\n"); + + iscsit_global = kzalloc(sizeof(struct iscsit_global), GFP_KERNEL); + if (!iscsit_global) { + pr_err("Unable to allocate memory for iscsit_global\n"); + return -1; + } + mutex_init(&auth_id_lock); + spin_lock_init(&sess_idr_lock); + idr_init(&tiqn_idr); + idr_init(&sess_idr); + + ret = iscsi_target_register_configfs(); + if (ret < 0) + goto out; + + ret = iscsi_thread_set_init(); + if (ret < 0) + goto configfs_out; + + if (iscsi_allocate_thread_sets(TARGET_THREAD_SET_COUNT) != + TARGET_THREAD_SET_COUNT) { + pr_err("iscsi_allocate_thread_sets() returned" + " unexpected value!\n"); + goto ts_out1; + } + + lio_cmd_cache = kmem_cache_create("lio_cmd_cache", + sizeof(struct iscsi_cmd), __alignof__(struct iscsi_cmd), + 0, NULL); + if (!lio_cmd_cache) { + pr_err("Unable to kmem_cache_create() for" + " lio_cmd_cache\n"); + goto ts_out2; + } + + lio_qr_cache = kmem_cache_create("lio_qr_cache", + sizeof(struct iscsi_queue_req), + __alignof__(struct iscsi_queue_req), 0, NULL); + if (!lio_qr_cache) { + pr_err("nable to kmem_cache_create() for" + " lio_qr_cache\n"); + goto cmd_out; + } + + lio_dr_cache = kmem_cache_create("lio_dr_cache", + sizeof(struct iscsi_datain_req), + __alignof__(struct iscsi_datain_req), 0, NULL); + if (!lio_dr_cache) { + pr_err("Unable to kmem_cache_create() for" + " lio_dr_cache\n"); + goto qr_out; + } + + lio_ooo_cache = kmem_cache_create("lio_ooo_cache", + sizeof(struct iscsi_ooo_cmdsn), + __alignof__(struct iscsi_ooo_cmdsn), 0, NULL); + if (!lio_ooo_cache) { + pr_err("Unable to kmem_cache_create() for" + " lio_ooo_cache\n"); + goto dr_out; + } + + lio_r2t_cache = kmem_cache_create("lio_r2t_cache", + sizeof(struct iscsi_r2t), __alignof__(struct iscsi_r2t), + 0, NULL); + if (!lio_r2t_cache) { + pr_err("Unable to kmem_cache_create() for" + " lio_r2t_cache\n"); + goto ooo_out; + } + + if (iscsit_load_discovery_tpg() < 0) + goto r2t_out; + + return ret; +r2t_out: + kmem_cache_destroy(lio_r2t_cache); +ooo_out: + kmem_cache_destroy(lio_ooo_cache); +dr_out: + kmem_cache_destroy(lio_dr_cache); +qr_out: + kmem_cache_destroy(lio_qr_cache); +cmd_out: + kmem_cache_destroy(lio_cmd_cache); +ts_out2: + iscsi_deallocate_thread_sets(); +ts_out1: + iscsi_thread_set_free(); +configfs_out: + iscsi_target_deregister_configfs(); +out: + kfree(iscsit_global); + return -ENOMEM; +} + +static void __exit iscsi_target_cleanup_module(void) +{ + iscsi_deallocate_thread_sets(); + iscsi_thread_set_free(); + iscsit_release_discovery_tpg(); + kmem_cache_destroy(lio_cmd_cache); + kmem_cache_destroy(lio_qr_cache); + kmem_cache_destroy(lio_dr_cache); + kmem_cache_destroy(lio_ooo_cache); + kmem_cache_destroy(lio_r2t_cache); + + iscsi_target_deregister_configfs(); + + kfree(iscsit_global); +} + +int iscsit_add_reject( + u8 reason, + int fail_conn, + unsigned char *buf, + struct iscsi_conn *conn) +{ + struct iscsi_cmd *cmd; + struct iscsi_reject *hdr; + int ret; + + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + return -1; + + cmd->iscsi_opcode = ISCSI_OP_REJECT; + if (fail_conn) + cmd->cmd_flags |= ICF_REJECT_FAIL_CONN; + + hdr = (struct iscsi_reject *) cmd->pdu; + hdr->reason = reason; + + cmd->buf_ptr = kzalloc(ISCSI_HDR_LEN, GFP_KERNEL); + if (!cmd->buf_ptr) { + pr_err("Unable to allocate memory for cmd->buf_ptr\n"); + iscsit_release_cmd(cmd); + return -1; + } + memcpy(cmd->buf_ptr, buf, ISCSI_HDR_LEN); + + 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_REJECT; + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + + ret = wait_for_completion_interruptible(&cmd->reject_comp); + if (ret != 0) + return -1; + + return (!fail_conn) ? 0 : -1; +} + +int iscsit_add_reject_from_cmd( + u8 reason, + int fail_conn, + int add_to_conn, + unsigned char *buf, + struct iscsi_cmd *cmd) +{ + struct iscsi_conn *conn; + struct iscsi_reject *hdr; + int ret; + + if (!cmd->conn) { + pr_err("cmd->conn is NULL for ITT: 0x%08x\n", + cmd->init_task_tag); + return -1; + } + conn = cmd->conn; + + cmd->iscsi_opcode = ISCSI_OP_REJECT; + if (fail_conn) + cmd->cmd_flags |= ICF_REJECT_FAIL_CONN; + + hdr = (struct iscsi_reject *) cmd->pdu; + hdr->reason = reason; + + cmd->buf_ptr = kzalloc(ISCSI_HDR_LEN, GFP_KERNEL); + if (!cmd->buf_ptr) { + pr_err("Unable to allocate memory for cmd->buf_ptr\n"); + iscsit_release_cmd(cmd); + return -1; + } + memcpy(cmd->buf_ptr, buf, ISCSI_HDR_LEN); + + if (add_to_conn) { + 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_REJECT; + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + + ret = wait_for_completion_interruptible(&cmd->reject_comp); + if (ret != 0) + return -1; + + return (!fail_conn) ? 0 : -1; +} + +/* + * Map some portion of the allocated scatterlist to an iovec, suitable for + * kernel sockets to copy data in/out. This handles both pages and slab-allocated + * buffers, since we have been tricky and mapped t_mem_sg to the buffer in + * either case (see iscsit_alloc_buffs) + */ +static int iscsit_map_iovec( + struct iscsi_cmd *cmd, + struct kvec *iov, + u32 data_offset, + u32 data_length) +{ + u32 i = 0; + struct scatterlist *sg; + unsigned int page_off; + + /* + * We have a private mapping of the allocated pages in t_mem_sg. + * At this point, we also know each contains a page. + */ + sg = &cmd->t_mem_sg[data_offset / PAGE_SIZE]; + page_off = (data_offset % PAGE_SIZE); + + cmd->first_data_sg = sg; + cmd->first_data_sg_off = page_off; + + while (data_length) { + u32 cur_len = min_t(u32, data_length, sg->length - page_off); + + iov[i].iov_base = kmap(sg_page(sg)) + sg->offset + page_off; + iov[i].iov_len = cur_len; + + data_length -= cur_len; + page_off = 0; + sg = sg_next(sg); + i++; + } + + cmd->kmapped_nents = i; + + return i; +} + +static void iscsit_unmap_iovec(struct iscsi_cmd *cmd) +{ + u32 i; + struct scatterlist *sg; + + sg = cmd->first_data_sg; + + for (i = 0; i < cmd->kmapped_nents; i++) + kunmap(sg_page(&sg[i])); +} + +static void iscsit_ack_from_expstatsn(struct iscsi_conn *conn, u32 exp_statsn) +{ + struct iscsi_cmd *cmd; + + conn->exp_statsn = exp_statsn; + + spin_lock_bh(&conn->cmd_lock); + list_for_each_entry(cmd, &conn->conn_cmd_list, i_list) { + spin_lock(&cmd->istate_lock); + if ((cmd->i_state == ISTATE_SENT_STATUS) && + (cmd->stat_sn < exp_statsn)) { + cmd->i_state = ISTATE_REMOVE; + spin_unlock(&cmd->istate_lock); + iscsit_add_cmd_to_immediate_queue(cmd, conn, + cmd->i_state); + continue; + } + spin_unlock(&cmd->istate_lock); + } + spin_unlock_bh(&conn->cmd_lock); +} + +static int iscsit_allocate_iovecs(struct iscsi_cmd *cmd) +{ + u32 iov_count = (cmd->se_cmd.t_data_nents == 0) ? 1 : + cmd->se_cmd.t_data_nents; + + iov_count += TRANSPORT_IOV_DATA_BUFFER; + + cmd->iov_data = kzalloc(iov_count * sizeof(struct kvec), GFP_KERNEL); + if (!cmd->iov_data) { + pr_err("Unable to allocate cmd->iov_data\n"); + return -ENOMEM; + } + + cmd->orig_iov_data_count = iov_count; + return 0; +} + +static int iscsit_alloc_buffs(struct iscsi_cmd *cmd) +{ + struct scatterlist *sgl; + u32 length = cmd->se_cmd.data_length; + int nents = DIV_ROUND_UP(length, PAGE_SIZE); + int i = 0, ret; + /* + * If no SCSI payload is present, allocate the default iovecs used for + * iSCSI PDU Header + */ + if (!length) + return iscsit_allocate_iovecs(cmd); + + sgl = kzalloc(sizeof(*sgl) * nents, GFP_KERNEL); + if (!sgl) + return -ENOMEM; + + sg_init_table(sgl, nents); + + while (length) { + int buf_size = min_t(int, length, PAGE_SIZE); + struct page *page; + + page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!page) + goto page_alloc_failed; + + sg_set_page(&sgl[i], page, buf_size, 0); + + length -= buf_size; + i++; + } + + cmd->t_mem_sg = sgl; + cmd->t_mem_sg_nents = nents; + + /* BIDI ops not supported */ + + /* Tell the core about our preallocated memory */ + transport_generic_map_mem_to_cmd(&cmd->se_cmd, sgl, nents, NULL, 0); + /* + * Allocate iovecs for SCSI payload after transport_generic_map_mem_to_cmd + * so that cmd->se_cmd.t_tasks_se_num has been set. + */ + ret = iscsit_allocate_iovecs(cmd); + if (ret < 0) + goto page_alloc_failed; + + return 0; + +page_alloc_failed: + while (i >= 0) { + __free_page(sg_page(&sgl[i])); + i--; + } + kfree(cmd->t_mem_sg); + cmd->t_mem_sg = NULL; + return -ENOMEM; +} + +static int iscsit_handle_scsi_cmd( + struct iscsi_conn *conn, + unsigned char *buf) +{ + int data_direction, cmdsn_ret = 0, immed_ret, ret, transport_ret; + int dump_immediate_data = 0, send_check_condition = 0, payload_length; + struct iscsi_cmd *cmd = NULL; + struct iscsi_scsi_req *hdr; + + spin_lock_bh(&conn->sess->session_stats_lock); + conn->sess->cmd_pdus++; + if (conn->sess->se_sess->se_node_acl) { + spin_lock(&conn->sess->se_sess->se_node_acl->stats_lock); + conn->sess->se_sess->se_node_acl->num_cmds++; + spin_unlock(&conn->sess->se_sess->se_node_acl->stats_lock); + } + spin_unlock_bh(&conn->sess->session_stats_lock); + + hdr = (struct iscsi_scsi_req *) buf; + payload_length = ntoh24(hdr->dlength); + hdr->itt = be32_to_cpu(hdr->itt); + hdr->data_length = be32_to_cpu(hdr->data_length); + hdr->cmdsn = be32_to_cpu(hdr->cmdsn); + hdr->exp_statsn = be32_to_cpu(hdr->exp_statsn); + + /* FIXME; Add checks for AdditionalHeaderSegment */ + + if (!(hdr->flags & ISCSI_FLAG_CMD_WRITE) && + !(hdr->flags & ISCSI_FLAG_CMD_FINAL)) { + pr_err("ISCSI_FLAG_CMD_WRITE & ISCSI_FLAG_CMD_FINAL" + " not set. Bad iSCSI Initiator.\n"); + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, + buf, conn); + } + + if (((hdr->flags & ISCSI_FLAG_CMD_READ) || + (hdr->flags & ISCSI_FLAG_CMD_WRITE)) && !hdr->data_length) { + /* + * Vmware ESX v3.0 uses a modified Cisco Initiator (v3.4.2) + * that adds support for RESERVE/RELEASE. There is a bug + * add with this new functionality that sets R/W bits when + * neither CDB carries any READ or WRITE datapayloads. + */ + if ((hdr->cdb[0] == 0x16) || (hdr->cdb[0] == 0x17)) { + hdr->flags &= ~ISCSI_FLAG_CMD_READ; + hdr->flags &= ~ISCSI_FLAG_CMD_WRITE; + goto done; + } + + pr_err("ISCSI_FLAG_CMD_READ or ISCSI_FLAG_CMD_WRITE" + " set when Expected Data Transfer Length is 0 for" + " CDB: 0x%02x. Bad iSCSI Initiator.\n", hdr->cdb[0]); + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, + buf, conn); + } +done: + + if (!(hdr->flags & ISCSI_FLAG_CMD_READ) && + !(hdr->flags & ISCSI_FLAG_CMD_WRITE) && (hdr->data_length != 0)) { + pr_err("ISCSI_FLAG_CMD_READ and/or ISCSI_FLAG_CMD_WRITE" + " MUST be set if Expected Data Transfer Length is not 0." + " Bad iSCSI Initiator\n"); + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, + buf, conn); + } + + if ((hdr->flags & ISCSI_FLAG_CMD_READ) && + (hdr->flags & ISCSI_FLAG_CMD_WRITE)) { + pr_err("Bidirectional operations not supported!\n"); + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, + buf, conn); + } + + if (hdr->opcode & ISCSI_OP_IMMEDIATE) { + pr_err("Illegally set Immediate Bit in iSCSI Initiator" + " Scsi Command PDU.\n"); + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, + buf, conn); + } + + if (payload_length && !conn->sess->sess_ops->ImmediateData) { + pr_err("ImmediateData=No but DataSegmentLength=%u," + " protocol error.\n", payload_length); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + if ((hdr->data_length == payload_length) && + (!(hdr->flags & ISCSI_FLAG_CMD_FINAL))) { + pr_err("Expected Data Transfer Length and Length of" + " Immediate Data are the same, but ISCSI_FLAG_CMD_FINAL" + " bit is not set protocol error\n"); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + if (payload_length > hdr->data_length) { + pr_err("DataSegmentLength: %u is greater than" + " EDTL: %u, protocol error.\n", payload_length, + hdr->data_length); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + if (payload_length > conn->conn_ops->MaxRecvDataSegmentLength) { + pr_err("DataSegmentLength: %u is greater than" + " MaxRecvDataSegmentLength: %u, protocol error.\n", + payload_length, conn->conn_ops->MaxRecvDataSegmentLength); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + if (payload_length > conn->sess->sess_ops->FirstBurstLength) { + pr_err("DataSegmentLength: %u is greater than" + " FirstBurstLength: %u, protocol error.\n", + payload_length, conn->sess->sess_ops->FirstBurstLength); + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, + buf, conn); + } + + data_direction = (hdr->flags & ISCSI_FLAG_CMD_WRITE) ? DMA_TO_DEVICE : + (hdr->flags & ISCSI_FLAG_CMD_READ) ? DMA_FROM_DEVICE : + DMA_NONE; + + cmd = iscsit_allocate_se_cmd(conn, hdr->data_length, data_direction, + (hdr->flags & ISCSI_FLAG_CMD_ATTR_MASK)); + if (!cmd) + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, 1, + buf, conn); + + pr_debug("Got SCSI Command, ITT: 0x%08x, CmdSN: 0x%08x," + " ExpXferLen: %u, Length: %u, CID: %hu\n", hdr->itt, + hdr->cmdsn, hdr->data_length, payload_length, conn->cid); + + cmd->iscsi_opcode = ISCSI_OP_SCSI_CMD; + cmd->i_state = ISTATE_NEW_CMD; + cmd->immediate_cmd = ((hdr->opcode & ISCSI_OP_IMMEDIATE) ? 1 : 0); + cmd->immediate_data = (payload_length) ? 1 : 0; + cmd->unsolicited_data = ((!(hdr->flags & ISCSI_FLAG_CMD_FINAL) && + (hdr->flags & ISCSI_FLAG_CMD_WRITE)) ? 1 : 0); + if (cmd->unsolicited_data) + cmd->cmd_flags |= ICF_NON_IMMEDIATE_UNSOLICITED_DATA; + + conn->sess->init_task_tag = cmd->init_task_tag = hdr->itt; + if (hdr->flags & ISCSI_FLAG_CMD_READ) { + spin_lock_bh(&conn->sess->ttt_lock); + cmd->targ_xfer_tag = conn->sess->targ_xfer_tag++; + if (cmd->targ_xfer_tag == 0xFFFFFFFF) + cmd->targ_xfer_tag = conn->sess->targ_xfer_tag++; + spin_unlock_bh(&conn->sess->ttt_lock); + } else if (hdr->flags & ISCSI_FLAG_CMD_WRITE) + cmd->targ_xfer_tag = 0xFFFFFFFF; + cmd->cmd_sn = hdr->cmdsn; + cmd->exp_stat_sn = hdr->exp_statsn; + cmd->first_burst_len = payload_length; + + if (cmd->data_direction == DMA_FROM_DEVICE) { + struct iscsi_datain_req *dr; + + dr = iscsit_allocate_datain_req(); + if (!dr) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, 1, buf, cmd); + + iscsit_attach_datain_req(cmd, dr); + } + + /* + * The CDB is going to an se_device_t. + */ + ret = iscsit_get_lun_for_cmd(cmd, hdr->cdb, + get_unaligned_le64(&hdr->lun)); + if (ret < 0) { + if (cmd->se_cmd.scsi_sense_reason == TCM_NON_EXISTENT_LUN) { + pr_debug("Responding to non-acl'ed," + " non-existent or non-exported iSCSI LUN:" + " 0x%016Lx\n", get_unaligned_le64(&hdr->lun)); + } + if (ret == PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, 1, buf, cmd); + + send_check_condition = 1; + goto attach_cmd; + } + /* + * The Initiator Node has access to the LUN (the addressing method + * is handled inside of iscsit_get_lun_for_cmd()). Now it's time to + * allocate 1->N transport tasks (depending on sector count and + * maximum request size the physical HBA(s) can handle. + */ + transport_ret = transport_generic_allocate_tasks(&cmd->se_cmd, hdr->cdb); + if (transport_ret == -ENOMEM) { + return iscsit_add_reject_from_cmd( + ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, 1, buf, cmd); + } else if (transport_ret == -EINVAL) { + /* + * Unsupported SAM Opcode. CHECK_CONDITION will be sent + * in iscsit_execute_cmd() during the CmdSN OOO Execution + * Mechinism. + */ + send_check_condition = 1; + } else { + if (iscsit_decide_list_to_build(cmd, payload_length) < 0) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, 1, buf, cmd); + } + +attach_cmd: + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_list, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + /* + * Check if we need to delay processing because of ALUA + * Active/NonOptimized primary access state.. + */ + core_alua_check_nonop_delay(&cmd->se_cmd); + /* + * Allocate and setup SGL used with transport_generic_map_mem_to_cmd(). + * also call iscsit_allocate_iovecs() + */ + ret = iscsit_alloc_buffs(cmd); + if (ret < 0) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, 1, buf, cmd); + /* + * Check the CmdSN against ExpCmdSN/MaxCmdSN here if + * the Immediate Bit is not set, and no Immediate + * Data is attached. + * + * A PDU/CmdSN carrying Immediate Data can only + * be processed after the DataCRC has passed. + * If the DataCRC fails, the CmdSN MUST NOT + * be acknowledged. (See below) + */ + if (!cmd->immediate_data) { + cmdsn_ret = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_PROTOCOL_ERROR, + 1, 0, buf, cmd); + } + + iscsit_ack_from_expstatsn(conn, hdr->exp_statsn); + + /* + * If no Immediate Data is attached, it's OK to return now. + */ + if (!cmd->immediate_data) { + if (send_check_condition) + return 0; + + if (cmd->unsolicited_data) { + 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 0; + } + + /* + * Early CHECK_CONDITIONs never make it to the transport processing + * thread. They are processed in CmdSN order by + * iscsit_check_received_cmdsn() below. + */ + if (send_check_condition) { + immed_ret = IMMEDIATE_DATA_NORMAL_OPERATION; + dump_immediate_data = 1; + goto after_immediate_data; + } + /* + * Call directly into transport_generic_new_cmd() to perform + * the backend memory allocation. + */ + ret = transport_generic_new_cmd(&cmd->se_cmd); + if ((ret < 0) || (cmd->se_cmd.se_cmd_flags & SCF_SE_CMD_FAILED)) { + immed_ret = IMMEDIATE_DATA_NORMAL_OPERATION; + dump_immediate_data = 1; + goto after_immediate_data; + } + + immed_ret = iscsit_handle_immediate_data(cmd, buf, payload_length); +after_immediate_data: + if (immed_ret == IMMEDIATE_DATA_NORMAL_OPERATION) { + /* + * A PDU/CmdSN carrying Immediate Data passed + * DataCRC, check against ExpCmdSN/MaxCmdSN if + * Immediate Bit is not set. + */ + cmdsn_ret = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); + /* + * Special case for Unsupported SAM WRITE Opcodes + * and ImmediateData=Yes. + */ + if (dump_immediate_data) { + if (iscsit_dump_data_payload(conn, payload_length, 1) < 0) + return -1; + } else if (cmd->unsolicited_data) { + 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); + } + + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_PROTOCOL_ERROR, + 1, 0, buf, cmd); + + } else if (immed_ret == IMMEDIATE_DATA_ERL1_CRC_FAILURE) { + /* + * Immediate Data failed DataCRC and ERL>=1, + * silently drop this PDU and let the initiator + * plug the CmdSN gap. + * + * FIXME: Send Unsolicited NOPIN with reserved + * TTT here to help the initiator figure out + * the missing CmdSN, although they should be + * intelligent enough to determine the missing + * CmdSN and issue a retry to plug the sequence. + */ + cmd->i_state = ISTATE_REMOVE; + iscsit_add_cmd_to_immediate_queue(cmd, conn, cmd->i_state); + } else /* immed_ret == IMMEDIATE_DATA_CANNOT_RECOVER */ + return -1; + + return 0; +} + +static u32 iscsit_do_crypto_hash_sg( + struct hash_desc *hash, + struct iscsi_cmd *cmd, + u32 data_offset, + u32 data_length, + u32 padding, + u8 *pad_bytes) +{ + u32 data_crc; + u32 i; + struct scatterlist *sg; + unsigned int page_off; + + crypto_hash_init(hash); + + sg = cmd->first_data_sg; + page_off = cmd->first_data_sg_off; + + i = 0; + while (data_length) { + u32 cur_len = min_t(u32, data_length, (sg[i].length - page_off)); + + crypto_hash_update(hash, &sg[i], cur_len); + + data_length -= cur_len; + page_off = 0; + i++; + } + + if (padding) { + struct scatterlist pad_sg; + + sg_init_one(&pad_sg, pad_bytes, padding); + crypto_hash_update(hash, &pad_sg, padding); + } + crypto_hash_final(hash, (u8 *) &data_crc); + + return data_crc; +} + +static void iscsit_do_crypto_hash_buf( + struct hash_desc *hash, + unsigned char *buf, + u32 payload_length, + u32 padding, + u8 *pad_bytes, + u8 *data_crc) +{ + struct scatterlist sg; + + crypto_hash_init(hash); + + sg_init_one(&sg, (u8 *)buf, payload_length); + crypto_hash_update(hash, &sg, payload_length); + + if (padding) { + sg_init_one(&sg, pad_bytes, padding); + crypto_hash_update(hash, &sg, padding); + } + crypto_hash_final(hash, data_crc); +} + +static int iscsit_handle_data_out(struct iscsi_conn *conn, unsigned char *buf) +{ + int iov_ret, ooo_cmdsn = 0, ret; + u8 data_crc_failed = 0; + u32 checksum, iov_count = 0, padding = 0, rx_got = 0; + u32 rx_size = 0, payload_length; + struct iscsi_cmd *cmd = NULL; + struct se_cmd *se_cmd; + struct iscsi_data *hdr; + struct kvec *iov; + unsigned long flags; + + hdr = (struct iscsi_data *) buf; + payload_length = ntoh24(hdr->dlength); + hdr->itt = be32_to_cpu(hdr->itt); + hdr->ttt = be32_to_cpu(hdr->ttt); + hdr->exp_statsn = be32_to_cpu(hdr->exp_statsn); + hdr->datasn = be32_to_cpu(hdr->datasn); + hdr->offset = be32_to_cpu(hdr->offset); + + if (!payload_length) { + pr_err("DataOUT payload is ZERO, protocol error.\n"); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + /* iSCSI write */ + spin_lock_bh(&conn->sess->session_stats_lock); + conn->sess->rx_data_octets += payload_length; + if (conn->sess->se_sess->se_node_acl) { + spin_lock(&conn->sess->se_sess->se_node_acl->stats_lock); + conn->sess->se_sess->se_node_acl->write_bytes += payload_length; + spin_unlock(&conn->sess->se_sess->se_node_acl->stats_lock); + } + spin_unlock_bh(&conn->sess->session_stats_lock); + + if (payload_length > conn->conn_ops->MaxRecvDataSegmentLength) { + pr_err("DataSegmentLength: %u is greater than" + " MaxRecvDataSegmentLength: %u\n", payload_length, + conn->conn_ops->MaxRecvDataSegmentLength); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + cmd = iscsit_find_cmd_from_itt_or_dump(conn, hdr->itt, + payload_length); + if (!cmd) + return 0; + + pr_debug("Got DataOut ITT: 0x%08x, TTT: 0x%08x," + " DataSN: 0x%08x, Offset: %u, Length: %u, CID: %hu\n", + hdr->itt, hdr->ttt, hdr->datasn, hdr->offset, + payload_length, conn->cid); + + if (cmd->cmd_flags & ICF_GOT_LAST_DATAOUT) { + pr_err("Command ITT: 0x%08x received DataOUT after" + " last DataOUT received, dumping payload\n", + cmd->init_task_tag); + return iscsit_dump_data_payload(conn, payload_length, 1); + } + + if (cmd->data_direction != DMA_TO_DEVICE) { + pr_err("Command ITT: 0x%08x received DataOUT for a" + " NON-WRITE command.\n", cmd->init_task_tag); + return iscsit_add_reject_from_cmd(ISCSI_REASON_PROTOCOL_ERROR, + 1, 0, buf, cmd); + } + se_cmd = &cmd->se_cmd; + iscsit_mod_dataout_timer(cmd); + + if ((hdr->offset + payload_length) > cmd->data_length) { + pr_err("DataOut Offset: %u, Length %u greater than" + " iSCSI Command EDTL %u, protocol error.\n", + hdr->offset, payload_length, cmd->data_length); + return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_INVALID, + 1, 0, buf, cmd); + } + + if (cmd->unsolicited_data) { + int dump_unsolicited_data = 0; + + if (conn->sess->sess_ops->InitialR2T) { + pr_err("Received unexpected unsolicited data" + " while InitialR2T=Yes, protocol error.\n"); + transport_send_check_condition_and_sense(&cmd->se_cmd, + TCM_UNEXPECTED_UNSOLICITED_DATA, 0); + return -1; + } + /* + * Special case for dealing with Unsolicited DataOUT + * and Unsupported SAM WRITE Opcodes and SE resource allocation + * failures; + */ + + /* Something's amiss if we're not in WRITE_PENDING state... */ + spin_lock_irqsave(&se_cmd->t_state_lock, flags); + WARN_ON(se_cmd->t_state != TRANSPORT_WRITE_PENDING); + spin_unlock_irqrestore(&se_cmd->t_state_lock, flags); + + spin_lock_irqsave(&se_cmd->t_state_lock, flags); + if (!(se_cmd->se_cmd_flags & SCF_SUPPORTED_SAM_OPCODE) || + (se_cmd->se_cmd_flags & SCF_SE_CMD_FAILED)) + dump_unsolicited_data = 1; + spin_unlock_irqrestore(&se_cmd->t_state_lock, flags); + + if (dump_unsolicited_data) { + /* + * Check if a delayed TASK_ABORTED status needs to + * be sent now if the ISCSI_FLAG_CMD_FINAL has been + * received with the unsolicitied data out. + */ + if (hdr->flags & ISCSI_FLAG_CMD_FINAL) + iscsit_stop_dataout_timer(cmd); + + transport_check_aborted_status(se_cmd, + (hdr->flags & ISCSI_FLAG_CMD_FINAL)); + return iscsit_dump_data_payload(conn, payload_length, 1); + } + } else { + /* + * For the normal solicited data path: + * + * Check for a delayed TASK_ABORTED status and dump any + * incoming data out payload if one exists. Also, when the + * ISCSI_FLAG_CMD_FINAL is set to denote the end of the current + * data out sequence, we decrement outstanding_r2ts. Once + * outstanding_r2ts reaches zero, go ahead and send the delayed + * TASK_ABORTED status. + */ + if (atomic_read(&se_cmd->t_transport_aborted) != 0) { + if (hdr->flags & ISCSI_FLAG_CMD_FINAL) + if (--cmd->outstanding_r2ts < 1) { + iscsit_stop_dataout_timer(cmd); + transport_check_aborted_status( + se_cmd, 1); + } + + return iscsit_dump_data_payload(conn, payload_length, 1); + } + } + /* + * Preform DataSN, DataSequenceInOrder, DataPDUInOrder, and + * within-command recovery checks before receiving the payload. + */ + ret = iscsit_check_pre_dataout(cmd, buf); + if (ret == DATAOUT_WITHIN_COMMAND_RECOVERY) + return 0; + else if (ret == DATAOUT_CANNOT_RECOVER) + return -1; + + rx_size += payload_length; + iov = &cmd->iov_data[0]; + + iov_ret = iscsit_map_iovec(cmd, iov, hdr->offset, payload_length); + if (iov_ret < 0) + return -1; + + iov_count += iov_ret; + + padding = ((-payload_length) & 3); + if (padding != 0) { + iov[iov_count].iov_base = cmd->pad_bytes; + iov[iov_count++].iov_len = padding; + rx_size += padding; + pr_debug("Receiving %u padding bytes.\n", padding); + } + + if (conn->conn_ops->DataDigest) { + iov[iov_count].iov_base = &checksum; + iov[iov_count++].iov_len = ISCSI_CRC_LEN; + rx_size += ISCSI_CRC_LEN; + } + + rx_got = rx_data(conn, &cmd->iov_data[0], iov_count, rx_size); + + iscsit_unmap_iovec(cmd); + + if (rx_got != rx_size) + return -1; + + if (conn->conn_ops->DataDigest) { + u32 data_crc; + + data_crc = iscsit_do_crypto_hash_sg(&conn->conn_rx_hash, cmd, + hdr->offset, payload_length, padding, + cmd->pad_bytes); + + if (checksum != data_crc) { + pr_err("ITT: 0x%08x, Offset: %u, Length: %u," + " DataSN: 0x%08x, CRC32C DataDigest 0x%08x" + " does not match computed 0x%08x\n", + hdr->itt, hdr->offset, payload_length, + hdr->datasn, checksum, data_crc); + data_crc_failed = 1; + } else { + pr_debug("Got CRC32C DataDigest 0x%08x for" + " %u bytes of Data Out\n", checksum, + payload_length); + } + } + /* + * Increment post receive data and CRC values or perform + * within-command recovery. + */ + ret = iscsit_check_post_dataout(cmd, buf, data_crc_failed); + if ((ret == DATAOUT_NORMAL) || (ret == DATAOUT_WITHIN_COMMAND_RECOVERY)) + return 0; + else if (ret == DATAOUT_SEND_R2T) { + iscsit_set_dataout_sequence_values(cmd); + iscsit_build_r2ts_for_cmd(cmd, conn, 0); + } else if (ret == DATAOUT_SEND_TO_TRANSPORT) { + /* + * Handle extra special case for out of order + * Unsolicited Data Out. + */ + spin_lock_bh(&cmd->istate_lock); + ooo_cmdsn = (cmd->cmd_flags & ICF_OOO_CMDSN); + cmd->cmd_flags |= ICF_GOT_LAST_DATAOUT; + cmd->i_state = ISTATE_RECEIVED_LAST_DATAOUT; + spin_unlock_bh(&cmd->istate_lock); + + iscsit_stop_dataout_timer(cmd); + return (!ooo_cmdsn) ? transport_generic_handle_data( + &cmd->se_cmd) : 0; + } else /* DATAOUT_CANNOT_RECOVER */ + return -1; + + return 0; +} + +static int iscsit_handle_nop_out( + struct iscsi_conn *conn, + unsigned char *buf) +{ + unsigned char *ping_data = NULL; + int cmdsn_ret, niov = 0, ret = 0, rx_got, rx_size; + u32 checksum, data_crc, padding = 0, payload_length; + u64 lun; + struct iscsi_cmd *cmd = NULL; + struct kvec *iov = NULL; + struct iscsi_nopout *hdr; + + hdr = (struct iscsi_nopout *) buf; + payload_length = ntoh24(hdr->dlength); + lun = get_unaligned_le64(&hdr->lun); + hdr->itt = be32_to_cpu(hdr->itt); + hdr->ttt = be32_to_cpu(hdr->ttt); + hdr->cmdsn = be32_to_cpu(hdr->cmdsn); + hdr->exp_statsn = be32_to_cpu(hdr->exp_statsn); + + if ((hdr->itt == 0xFFFFFFFF) && !(hdr->opcode & ISCSI_OP_IMMEDIATE)) { + pr_err("NOPOUT ITT is reserved, but Immediate Bit is" + " not set, protocol error.\n"); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + if (payload_length > conn->conn_ops->MaxRecvDataSegmentLength) { + pr_err("NOPOUT Ping Data DataSegmentLength: %u is" + " greater than MaxRecvDataSegmentLength: %u, protocol" + " error.\n", payload_length, + conn->conn_ops->MaxRecvDataSegmentLength); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + pr_debug("Got NOPOUT Ping %s ITT: 0x%08x, TTT: 0x%09x," + " CmdSN: 0x%08x, ExpStatSN: 0x%08x, Length: %u\n", + (hdr->itt == 0xFFFFFFFF) ? "Response" : "Request", + hdr->itt, hdr->ttt, hdr->cmdsn, hdr->exp_statsn, + payload_length); + /* + * This is not a response to a Unsolicited NopIN, which means + * it can either be a NOPOUT ping request (with a valid ITT), + * or a NOPOUT not requesting a NOPIN (with a reserved ITT). + * Either way, make sure we allocate an struct iscsi_cmd, as both + * can contain ping data. + */ + if (hdr->ttt == 0xFFFFFFFF) { + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + return iscsit_add_reject( + ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, buf, conn); + + cmd->iscsi_opcode = ISCSI_OP_NOOP_OUT; + cmd->i_state = ISTATE_SEND_NOPIN; + cmd->immediate_cmd = ((hdr->opcode & ISCSI_OP_IMMEDIATE) ? + 1 : 0); + conn->sess->init_task_tag = cmd->init_task_tag = hdr->itt; + cmd->targ_xfer_tag = 0xFFFFFFFF; + cmd->cmd_sn = hdr->cmdsn; + cmd->exp_stat_sn = hdr->exp_statsn; + cmd->data_direction = DMA_NONE; + } + + if (payload_length && (hdr->ttt == 0xFFFFFFFF)) { + rx_size = payload_length; + ping_data = kzalloc(payload_length + 1, GFP_KERNEL); + if (!ping_data) { + pr_err("Unable to allocate memory for" + " NOPOUT ping data.\n"); + ret = -1; + goto out; + } + + iov = &cmd->iov_misc[0]; + iov[niov].iov_base = ping_data; + iov[niov++].iov_len = payload_length; + + padding = ((-payload_length) & 3); + if (padding != 0) { + pr_debug("Receiving %u additional bytes" + " for padding.\n", padding); + iov[niov].iov_base = &cmd->pad_bytes; + iov[niov++].iov_len = padding; + rx_size += padding; + } + if (conn->conn_ops->DataDigest) { + iov[niov].iov_base = &checksum; + iov[niov++].iov_len = ISCSI_CRC_LEN; + rx_size += ISCSI_CRC_LEN; + } + + rx_got = rx_data(conn, &cmd->iov_misc[0], niov, rx_size); + if (rx_got != rx_size) { + ret = -1; + goto out; + } + + if (conn->conn_ops->DataDigest) { + iscsit_do_crypto_hash_buf(&conn->conn_rx_hash, + ping_data, payload_length, + padding, cmd->pad_bytes, + (u8 *)&data_crc); + + if (checksum != data_crc) { + pr_err("Ping data CRC32C DataDigest" + " 0x%08x does not match computed 0x%08x\n", + checksum, data_crc); + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Unable to recover from" + " NOPOUT Ping DataCRC failure while in" + " ERL=0.\n"); + ret = -1; + goto out; + } else { + /* + * Silently drop this PDU and let the + * initiator plug the CmdSN gap. + */ + pr_debug("Dropping NOPOUT" + " Command CmdSN: 0x%08x due to" + " DataCRC error.\n", hdr->cmdsn); + ret = 0; + goto out; + } + } else { + pr_debug("Got CRC32C DataDigest" + " 0x%08x for %u bytes of ping data.\n", + checksum, payload_length); + } + } + + ping_data[payload_length] = '\0'; + /* + * Attach ping data to struct iscsi_cmd->buf_ptr. + */ + cmd->buf_ptr = (void *)ping_data; + cmd->buf_ptr_size = payload_length; + + pr_debug("Got %u bytes of NOPOUT ping" + " data.\n", payload_length); + pr_debug("Ping Data: \"%s\"\n", ping_data); + } + + if (hdr->itt != 0xFFFFFFFF) { + if (!cmd) { + pr_err("Checking CmdSN for NOPOUT," + " but cmd is NULL!\n"); + return -1; + } + /* + * Initiator is expecting a NopIN ping reply, + */ + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_list, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + iscsit_ack_from_expstatsn(conn, hdr->exp_statsn); + + if (hdr->opcode & ISCSI_OP_IMMEDIATE) { + iscsit_add_cmd_to_response_queue(cmd, conn, + cmd->i_state); + return 0; + } + + cmdsn_ret = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); + if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) { + ret = 0; + goto ping_out; + } + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_PROTOCOL_ERROR, + 1, 0, buf, cmd); + + return 0; + } + + if (hdr->ttt != 0xFFFFFFFF) { + /* + * This was a response to a unsolicited NOPIN ping. + */ + cmd = iscsit_find_cmd_from_ttt(conn, hdr->ttt); + if (!cmd) + return -1; + + iscsit_stop_nopin_response_timer(conn); + + cmd->i_state = ISTATE_REMOVE; + iscsit_add_cmd_to_immediate_queue(cmd, conn, cmd->i_state); + iscsit_start_nopin_timer(conn); + } else { + /* + * Initiator is not expecting a NOPIN is response. + * Just ignore for now. + * + * iSCSI v19-91 10.18 + * "A NOP-OUT may also be used to confirm a changed + * ExpStatSN if another PDU will not be available + * for a long time." + */ + ret = 0; + goto out; + } + + return 0; +out: + if (cmd) + iscsit_release_cmd(cmd); +ping_out: + kfree(ping_data); + return ret; +} + +static int iscsit_handle_task_mgt_cmd( + struct iscsi_conn *conn, + unsigned char *buf) +{ + struct iscsi_cmd *cmd; + struct se_tmr_req *se_tmr; + struct iscsi_tmr_req *tmr_req; + struct iscsi_tm *hdr; + u32 payload_length; + int out_of_order_cmdsn = 0; + int ret; + u8 function; + + hdr = (struct iscsi_tm *) buf; + payload_length = ntoh24(hdr->dlength); + hdr->itt = be32_to_cpu(hdr->itt); + hdr->rtt = be32_to_cpu(hdr->rtt); + hdr->cmdsn = be32_to_cpu(hdr->cmdsn); + hdr->exp_statsn = be32_to_cpu(hdr->exp_statsn); + hdr->refcmdsn = be32_to_cpu(hdr->refcmdsn); + hdr->exp_datasn = be32_to_cpu(hdr->exp_datasn); + hdr->flags &= ~ISCSI_FLAG_CMD_FINAL; + function = hdr->flags; + + pr_debug("Got Task Management Request ITT: 0x%08x, CmdSN:" + " 0x%08x, Function: 0x%02x, RefTaskTag: 0x%08x, RefCmdSN:" + " 0x%08x, CID: %hu\n", hdr->itt, hdr->cmdsn, function, + hdr->rtt, hdr->refcmdsn, conn->cid); + + if ((function != ISCSI_TM_FUNC_ABORT_TASK) && + ((function != ISCSI_TM_FUNC_TASK_REASSIGN) && + (hdr->rtt != ISCSI_RESERVED_TAG))) { + pr_err("RefTaskTag should be set to 0xFFFFFFFF.\n"); + hdr->rtt = ISCSI_RESERVED_TAG; + } + + if ((function == ISCSI_TM_FUNC_TASK_REASSIGN) && + !(hdr->opcode & ISCSI_OP_IMMEDIATE)) { + pr_err("Task Management Request TASK_REASSIGN not" + " issued as immediate command, bad iSCSI Initiator" + "implementation\n"); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + if ((function != ISCSI_TM_FUNC_ABORT_TASK) && + (hdr->refcmdsn != ISCSI_RESERVED_TAG)) + hdr->refcmdsn = ISCSI_RESERVED_TAG; + + cmd = iscsit_allocate_se_cmd_for_tmr(conn, function); + if (!cmd) + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, buf, conn); + + cmd->iscsi_opcode = ISCSI_OP_SCSI_TMFUNC; + cmd->i_state = ISTATE_SEND_TASKMGTRSP; + cmd->immediate_cmd = ((hdr->opcode & ISCSI_OP_IMMEDIATE) ? 1 : 0); + cmd->init_task_tag = hdr->itt; + cmd->targ_xfer_tag = 0xFFFFFFFF; + cmd->cmd_sn = hdr->cmdsn; + cmd->exp_stat_sn = hdr->exp_statsn; + se_tmr = cmd->se_cmd.se_tmr_req; + tmr_req = cmd->tmr_req; + /* + * Locate the struct se_lun for all TMRs not related to ERL=2 TASK_REASSIGN + */ + if (function != ISCSI_TM_FUNC_TASK_REASSIGN) { + ret = iscsit_get_lun_for_tmr(cmd, + get_unaligned_le64(&hdr->lun)); + if (ret < 0) { + cmd->se_cmd.se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; + se_tmr->response = ISCSI_TMF_RSP_NO_LUN; + goto attach; + } + } + + switch (function) { + case ISCSI_TM_FUNC_ABORT_TASK: + se_tmr->response = iscsit_tmr_abort_task(cmd, buf); + if (se_tmr->response != ISCSI_TMF_RSP_COMPLETE) { + cmd->se_cmd.se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; + goto attach; + } + break; + case ISCSI_TM_FUNC_ABORT_TASK_SET: + case ISCSI_TM_FUNC_CLEAR_ACA: + case ISCSI_TM_FUNC_CLEAR_TASK_SET: + case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET: + break; + case ISCSI_TM_FUNC_TARGET_WARM_RESET: + if (iscsit_tmr_task_warm_reset(conn, tmr_req, buf) < 0) { + cmd->se_cmd.se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; + se_tmr->response = ISCSI_TMF_RSP_AUTH_FAILED; + goto attach; + } + break; + case ISCSI_TM_FUNC_TARGET_COLD_RESET: + if (iscsit_tmr_task_cold_reset(conn, tmr_req, buf) < 0) { + cmd->se_cmd.se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; + se_tmr->response = ISCSI_TMF_RSP_AUTH_FAILED; + goto attach; + } + break; + case ISCSI_TM_FUNC_TASK_REASSIGN: + se_tmr->response = iscsit_tmr_task_reassign(cmd, buf); + /* + * Perform sanity checks on the ExpDataSN only if the + * TASK_REASSIGN was successful. + */ + if (se_tmr->response != ISCSI_TMF_RSP_COMPLETE) + break; + + if (iscsit_check_task_reassign_expdatasn(tmr_req, conn) < 0) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_BOOKMARK_INVALID, 1, 1, + buf, cmd); + break; + default: + pr_err("Unknown TMR function: 0x%02x, protocol" + " error.\n", function); + cmd->se_cmd.se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; + se_tmr->response = ISCSI_TMF_RSP_NOT_SUPPORTED; + goto attach; + } + + if ((function != ISCSI_TM_FUNC_TASK_REASSIGN) && + (se_tmr->response == ISCSI_TMF_RSP_COMPLETE)) + se_tmr->call_transport = 1; +attach: + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_list, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + if (!(hdr->opcode & ISCSI_OP_IMMEDIATE)) { + int cmdsn_ret = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); + if (cmdsn_ret == CMDSN_HIGHER_THAN_EXP) + out_of_order_cmdsn = 1; + else if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) { + return 0; + } else { /* (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) */ + return iscsit_add_reject_from_cmd( + ISCSI_REASON_PROTOCOL_ERROR, + 1, 0, buf, cmd); + } + } + iscsit_ack_from_expstatsn(conn, hdr->exp_statsn); + + if (out_of_order_cmdsn) + return 0; + /* + * Found the referenced task, send to transport for processing. + */ + if (se_tmr->call_transport) + return transport_generic_handle_tmr(&cmd->se_cmd); + + /* + * Could not find the referenced LUN, task, or Task Management + * command not authorized or supported. Change state and + * let the tx_thread send the response. + * + * For connection recovery, this is also the default action for + * TMR TASK_REASSIGN. + */ + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + return 0; +} + +/* #warning FIXME: Support Text Command parameters besides SendTargets */ +static int iscsit_handle_text_cmd( + struct iscsi_conn *conn, + unsigned char *buf) +{ + char *text_ptr, *text_in; + int cmdsn_ret, niov = 0, rx_got, rx_size; + u32 checksum = 0, data_crc = 0, payload_length; + u32 padding = 0, text_length = 0; + struct iscsi_cmd *cmd; + struct kvec iov[3]; + struct iscsi_text *hdr; + + hdr = (struct iscsi_text *) buf; + payload_length = ntoh24(hdr->dlength); + hdr->itt = be32_to_cpu(hdr->itt); + hdr->ttt = be32_to_cpu(hdr->ttt); + hdr->cmdsn = be32_to_cpu(hdr->cmdsn); + hdr->exp_statsn = be32_to_cpu(hdr->exp_statsn); + + if (payload_length > conn->conn_ops->MaxRecvDataSegmentLength) { + pr_err("Unable to accept text parameter length: %u" + "greater than MaxRecvDataSegmentLength %u.\n", + payload_length, conn->conn_ops->MaxRecvDataSegmentLength); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + pr_debug("Got Text Request: ITT: 0x%08x, CmdSN: 0x%08x," + " ExpStatSN: 0x%08x, Length: %u\n", hdr->itt, hdr->cmdsn, + hdr->exp_statsn, payload_length); + + rx_size = text_length = payload_length; + if (text_length) { + text_in = kzalloc(text_length, GFP_KERNEL); + if (!text_in) { + pr_err("Unable to allocate memory for" + " incoming text parameters\n"); + return -1; + } + + memset(iov, 0, 3 * sizeof(struct kvec)); + iov[niov].iov_base = text_in; + iov[niov++].iov_len = text_length; + + padding = ((-payload_length) & 3); + if (padding != 0) { + iov[niov].iov_base = cmd->pad_bytes; + iov[niov++].iov_len = padding; + rx_size += padding; + pr_debug("Receiving %u additional bytes" + " for padding.\n", padding); + } + if (conn->conn_ops->DataDigest) { + iov[niov].iov_base = &checksum; + iov[niov++].iov_len = ISCSI_CRC_LEN; + rx_size += ISCSI_CRC_LEN; + } + + rx_got = rx_data(conn, &iov[0], niov, rx_size); + if (rx_got != rx_size) { + kfree(text_in); + return -1; + } + + if (conn->conn_ops->DataDigest) { + iscsit_do_crypto_hash_buf(&conn->conn_rx_hash, + text_in, text_length, + padding, cmd->pad_bytes, + (u8 *)&data_crc); + + if (checksum != data_crc) { + pr_err("Text data CRC32C DataDigest" + " 0x%08x does not match computed" + " 0x%08x\n", checksum, data_crc); + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Unable to recover from" + " Text Data digest failure while in" + " ERL=0.\n"); + kfree(text_in); + return -1; + } else { + /* + * Silently drop this PDU and let the + * initiator plug the CmdSN gap. + */ + pr_debug("Dropping Text" + " Command CmdSN: 0x%08x due to" + " DataCRC error.\n", hdr->cmdsn); + kfree(text_in); + return 0; + } + } else { + pr_debug("Got CRC32C DataDigest" + " 0x%08x for %u bytes of text data.\n", + checksum, text_length); + } + } + text_in[text_length - 1] = '\0'; + pr_debug("Successfully read %d bytes of text" + " data.\n", text_length); + + if (strncmp("SendTargets", text_in, 11) != 0) { + pr_err("Received Text Data that is not" + " SendTargets, cannot continue.\n"); + kfree(text_in); + return -1; + } + text_ptr = strchr(text_in, '='); + if (!text_ptr) { + pr_err("No \"=\" separator found in Text Data," + " cannot continue.\n"); + kfree(text_in); + return -1; + } + if (strncmp("=All", text_ptr, 4) != 0) { + pr_err("Unable to locate All value for" + " SendTargets key, cannot continue.\n"); + kfree(text_in); + return -1; + } +/*#warning Support SendTargets=(iSCSI Target Name/Nothing) values. */ + kfree(text_in); + } + + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, buf, conn); + + cmd->iscsi_opcode = ISCSI_OP_TEXT; + cmd->i_state = ISTATE_SEND_TEXTRSP; + cmd->immediate_cmd = ((hdr->opcode & ISCSI_OP_IMMEDIATE) ? 1 : 0); + conn->sess->init_task_tag = cmd->init_task_tag = hdr->itt; + cmd->targ_xfer_tag = 0xFFFFFFFF; + cmd->cmd_sn = hdr->cmdsn; + cmd->exp_stat_sn = hdr->exp_statsn; + cmd->data_direction = DMA_NONE; + + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_list, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + iscsit_ack_from_expstatsn(conn, hdr->exp_statsn); + + if (!(hdr->opcode & ISCSI_OP_IMMEDIATE)) { + cmdsn_ret = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_PROTOCOL_ERROR, + 1, 0, buf, cmd); + + return 0; + } + + return iscsit_execute_cmd(cmd, 0); +} + +int iscsit_logout_closesession(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_conn *conn_p; + struct iscsi_session *sess = conn->sess; + + pr_debug("Received logout request CLOSESESSION on CID: %hu" + " for SID: %u.\n", conn->cid, conn->sess->sid); + + atomic_set(&sess->session_logout, 1); + atomic_set(&conn->conn_logout_remove, 1); + conn->conn_logout_reason = ISCSI_LOGOUT_REASON_CLOSE_SESSION; + + iscsit_inc_conn_usage_count(conn); + iscsit_inc_session_usage_count(sess); + + spin_lock_bh(&sess->conn_lock); + list_for_each_entry(conn_p, &sess->sess_conn_list, conn_list) { + if (conn_p->conn_state != TARG_CONN_STATE_LOGGED_IN) + continue; + + pr_debug("Moving to TARG_CONN_STATE_IN_LOGOUT.\n"); + conn_p->conn_state = TARG_CONN_STATE_IN_LOGOUT; + } + spin_unlock_bh(&sess->conn_lock); + + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + + return 0; +} + +int iscsit_logout_closeconnection(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_conn *l_conn; + struct iscsi_session *sess = conn->sess; + + pr_debug("Received logout request CLOSECONNECTION for CID:" + " %hu on CID: %hu.\n", cmd->logout_cid, conn->cid); + + /* + * A Logout Request with a CLOSECONNECTION reason code for a CID + * can arrive on a connection with a differing CID. + */ + if (conn->cid == cmd->logout_cid) { + spin_lock_bh(&conn->state_lock); + pr_debug("Moving to TARG_CONN_STATE_IN_LOGOUT.\n"); + conn->conn_state = TARG_CONN_STATE_IN_LOGOUT; + + atomic_set(&conn->conn_logout_remove, 1); + conn->conn_logout_reason = ISCSI_LOGOUT_REASON_CLOSE_CONNECTION; + iscsit_inc_conn_usage_count(conn); + + spin_unlock_bh(&conn->state_lock); + } else { + /* + * Handle all different cid CLOSECONNECTION requests in + * iscsit_logout_post_handler_diffcid() as to give enough + * time for any non immediate command's CmdSN to be + * acknowledged on the connection in question. + * + * Here we simply make sure the CID is still around. + */ + l_conn = iscsit_get_conn_from_cid(sess, + cmd->logout_cid); + if (!l_conn) { + cmd->logout_response = ISCSI_LOGOUT_CID_NOT_FOUND; + iscsit_add_cmd_to_response_queue(cmd, conn, + cmd->i_state); + return 0; + } + + iscsit_dec_conn_usage_count(l_conn); + } + + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + + return 0; +} + +int iscsit_logout_removeconnforrecovery(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_session *sess = conn->sess; + + pr_debug("Received explicit REMOVECONNFORRECOVERY logout for" + " CID: %hu on CID: %hu.\n", cmd->logout_cid, conn->cid); + + if (sess->sess_ops->ErrorRecoveryLevel != 2) { + pr_err("Received Logout Request REMOVECONNFORRECOVERY" + " while ERL!=2.\n"); + cmd->logout_response = ISCSI_LOGOUT_RECOVERY_UNSUPPORTED; + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + return 0; + } + + if (conn->cid == cmd->logout_cid) { + pr_err("Received Logout Request REMOVECONNFORRECOVERY" + " with CID: %hu on CID: %hu, implementation error.\n", + cmd->logout_cid, conn->cid); + cmd->logout_response = ISCSI_LOGOUT_CLEANUP_FAILED; + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + return 0; + } + + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + + return 0; +} + +static int iscsit_handle_logout_cmd( + struct iscsi_conn *conn, + unsigned char *buf) +{ + int cmdsn_ret, logout_remove = 0; + u8 reason_code = 0; + struct iscsi_cmd *cmd; + struct iscsi_logout *hdr; + struct iscsi_tiqn *tiqn = iscsit_snmp_get_tiqn(conn); + + hdr = (struct iscsi_logout *) buf; + reason_code = (hdr->flags & 0x7f); + hdr->itt = be32_to_cpu(hdr->itt); + hdr->cid = be16_to_cpu(hdr->cid); + hdr->cmdsn = be32_to_cpu(hdr->cmdsn); + hdr->exp_statsn = be32_to_cpu(hdr->exp_statsn); + + if (tiqn) { + spin_lock(&tiqn->logout_stats.lock); + if (reason_code == ISCSI_LOGOUT_REASON_CLOSE_SESSION) + tiqn->logout_stats.normal_logouts++; + else + tiqn->logout_stats.abnormal_logouts++; + spin_unlock(&tiqn->logout_stats.lock); + } + + pr_debug("Got Logout Request ITT: 0x%08x CmdSN: 0x%08x" + " ExpStatSN: 0x%08x Reason: 0x%02x CID: %hu on CID: %hu\n", + hdr->itt, hdr->cmdsn, hdr->exp_statsn, reason_code, + hdr->cid, conn->cid); + + if (conn->conn_state != TARG_CONN_STATE_LOGGED_IN) { + pr_err("Received logout request on connection that" + " is not in logged in state, ignoring request.\n"); + return 0; + } + + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, 1, + buf, conn); + + cmd->iscsi_opcode = ISCSI_OP_LOGOUT; + cmd->i_state = ISTATE_SEND_LOGOUTRSP; + cmd->immediate_cmd = ((hdr->opcode & ISCSI_OP_IMMEDIATE) ? 1 : 0); + conn->sess->init_task_tag = cmd->init_task_tag = hdr->itt; + cmd->targ_xfer_tag = 0xFFFFFFFF; + cmd->cmd_sn = hdr->cmdsn; + cmd->exp_stat_sn = hdr->exp_statsn; + cmd->logout_cid = hdr->cid; + cmd->logout_reason = reason_code; + cmd->data_direction = DMA_NONE; + + /* + * We need to sleep in these cases (by returning 1) until the Logout + * Response gets sent in the tx thread. + */ + if ((reason_code == ISCSI_LOGOUT_REASON_CLOSE_SESSION) || + ((reason_code == ISCSI_LOGOUT_REASON_CLOSE_CONNECTION) && + (hdr->cid == conn->cid))) + logout_remove = 1; + + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_list, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + if (reason_code != ISCSI_LOGOUT_REASON_RECOVERY) + iscsit_ack_from_expstatsn(conn, hdr->exp_statsn); + + /* + * Immediate commands are executed, well, immediately. + * Non-Immediate Logout Commands are executed in CmdSN order. + */ + if (hdr->opcode & ISCSI_OP_IMMEDIATE) { + int ret = iscsit_execute_cmd(cmd, 0); + + if (ret < 0) + return ret; + } else { + cmdsn_ret = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); + if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) { + logout_remove = 0; + } else if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) { + return iscsit_add_reject_from_cmd( + ISCSI_REASON_PROTOCOL_ERROR, + 1, 0, buf, cmd); + } + } + + return logout_remove; +} + +static int iscsit_handle_snack( + struct iscsi_conn *conn, + unsigned char *buf) +{ + u32 unpacked_lun; + u64 lun; + struct iscsi_snack *hdr; + + hdr = (struct iscsi_snack *) buf; + hdr->flags &= ~ISCSI_FLAG_CMD_FINAL; + lun = get_unaligned_le64(&hdr->lun); + unpacked_lun = scsilun_to_int((struct scsi_lun *)&lun); + hdr->itt = be32_to_cpu(hdr->itt); + hdr->ttt = be32_to_cpu(hdr->ttt); + hdr->exp_statsn = be32_to_cpu(hdr->exp_statsn); + hdr->begrun = be32_to_cpu(hdr->begrun); + hdr->runlength = be32_to_cpu(hdr->runlength); + + pr_debug("Got ISCSI_INIT_SNACK, ITT: 0x%08x, ExpStatSN:" + " 0x%08x, Type: 0x%02x, BegRun: 0x%08x, RunLength: 0x%08x," + " CID: %hu\n", hdr->itt, hdr->exp_statsn, hdr->flags, + hdr->begrun, hdr->runlength, conn->cid); + + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Initiator sent SNACK request while in" + " ErrorRecoveryLevel=0.\n"); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + /* + * SNACK_DATA and SNACK_R2T are both 0, so check which function to + * call from inside iscsi_send_recovery_datain_or_r2t(). + */ + switch (hdr->flags & ISCSI_FLAG_SNACK_TYPE_MASK) { + case 0: + return iscsit_handle_recovery_datain_or_r2t(conn, buf, + hdr->itt, hdr->ttt, hdr->begrun, hdr->runlength); + return 0; + case ISCSI_FLAG_SNACK_TYPE_STATUS: + return iscsit_handle_status_snack(conn, hdr->itt, hdr->ttt, + hdr->begrun, hdr->runlength); + case ISCSI_FLAG_SNACK_TYPE_DATA_ACK: + return iscsit_handle_data_ack(conn, hdr->ttt, hdr->begrun, + hdr->runlength); + case ISCSI_FLAG_SNACK_TYPE_RDATA: + /* FIXME: Support R-Data SNACK */ + pr_err("R-Data SNACK Not Supported.\n"); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + default: + pr_err("Unknown SNACK type 0x%02x, protocol" + " error.\n", hdr->flags & 0x0f); + return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buf, conn); + } + + return 0; +} + +static void iscsit_rx_thread_wait_for_tcp(struct iscsi_conn *conn) +{ + if ((conn->sock->sk->sk_shutdown & SEND_SHUTDOWN) || + (conn->sock->sk->sk_shutdown & RCV_SHUTDOWN)) { + wait_for_completion_interruptible_timeout( + &conn->rx_half_close_comp, + ISCSI_RX_THREAD_TCP_TIMEOUT * HZ); + } +} + +static int iscsit_handle_immediate_data( + struct iscsi_cmd *cmd, + unsigned char *buf, + u32 length) +{ + int iov_ret, rx_got = 0, rx_size = 0; + u32 checksum, iov_count = 0, padding = 0; + struct iscsi_conn *conn = cmd->conn; + struct kvec *iov; + + iov_ret = iscsit_map_iovec(cmd, cmd->iov_data, cmd->write_data_done, length); + if (iov_ret < 0) + return IMMEDIATE_DATA_CANNOT_RECOVER; + + rx_size = length; + iov_count = iov_ret; + iov = &cmd->iov_data[0]; + + padding = ((-length) & 3); + if (padding != 0) { + iov[iov_count].iov_base = cmd->pad_bytes; + iov[iov_count++].iov_len = padding; + rx_size += padding; + } + + if (conn->conn_ops->DataDigest) { + iov[iov_count].iov_base = &checksum; + iov[iov_count++].iov_len = ISCSI_CRC_LEN; + rx_size += ISCSI_CRC_LEN; + } + + rx_got = rx_data(conn, &cmd->iov_data[0], iov_count, rx_size); + + iscsit_unmap_iovec(cmd); + + if (rx_got != rx_size) { + iscsit_rx_thread_wait_for_tcp(conn); + return IMMEDIATE_DATA_CANNOT_RECOVER; + } + + if (conn->conn_ops->DataDigest) { + u32 data_crc; + + data_crc = iscsit_do_crypto_hash_sg(&conn->conn_rx_hash, cmd, + cmd->write_data_done, length, padding, + cmd->pad_bytes); + + if (checksum != data_crc) { + pr_err("ImmediateData CRC32C DataDigest 0x%08x" + " does not match computed 0x%08x\n", checksum, + data_crc); + + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Unable to recover from" + " Immediate Data digest failure while" + " in ERL=0.\n"); + iscsit_add_reject_from_cmd( + ISCSI_REASON_DATA_DIGEST_ERROR, + 1, 0, buf, cmd); + return IMMEDIATE_DATA_CANNOT_RECOVER; + } else { + iscsit_add_reject_from_cmd( + ISCSI_REASON_DATA_DIGEST_ERROR, + 0, 0, buf, cmd); + return IMMEDIATE_DATA_ERL1_CRC_FAILURE; + } + } else { + pr_debug("Got CRC32C DataDigest 0x%08x for" + " %u bytes of Immediate Data\n", checksum, + length); + } + } + + cmd->write_data_done += length; + + if (cmd->write_data_done == cmd->data_length) { + spin_lock_bh(&cmd->istate_lock); + cmd->cmd_flags |= ICF_GOT_LAST_DATAOUT; + cmd->i_state = ISTATE_RECEIVED_LAST_DATAOUT; + spin_unlock_bh(&cmd->istate_lock); + } + + return IMMEDIATE_DATA_NORMAL_OPERATION; +} + +/* + * Called with sess->conn_lock held. + */ +/* #warning iscsi_build_conn_drop_async_message() only sends out on connections + with active network interface */ +static void iscsit_build_conn_drop_async_message(struct iscsi_conn *conn) +{ + struct iscsi_cmd *cmd; + struct iscsi_conn *conn_p; + + /* + * Only send a Asynchronous Message on connections whos network + * interface is still functional. + */ + list_for_each_entry(conn_p, &conn->sess->sess_conn_list, conn_list) { + if (conn_p->conn_state == TARG_CONN_STATE_LOGGED_IN) { + iscsit_inc_conn_usage_count(conn_p); + break; + } + } + + if (!conn_p) + return; + + cmd = iscsit_allocate_cmd(conn_p, GFP_KERNEL); + if (!cmd) { + iscsit_dec_conn_usage_count(conn_p); + return; + } + + cmd->logout_cid = conn->cid; + cmd->iscsi_opcode = ISCSI_OP_ASYNC_EVENT; + cmd->i_state = ISTATE_SEND_ASYNCMSG; + + spin_lock_bh(&conn_p->cmd_lock); + list_add_tail(&cmd->i_list, &conn_p->conn_cmd_list); + spin_unlock_bh(&conn_p->cmd_lock); + + iscsit_add_cmd_to_response_queue(cmd, conn_p, cmd->i_state); + iscsit_dec_conn_usage_count(conn_p); +} + +static int iscsit_send_conn_drop_async_message( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + struct iscsi_async *hdr; + + cmd->tx_size = ISCSI_HDR_LEN; + cmd->iscsi_opcode = ISCSI_OP_ASYNC_EVENT; + + hdr = (struct iscsi_async *) cmd->pdu; + hdr->opcode = ISCSI_OP_ASYNC_EVENT; + hdr->flags = ISCSI_FLAG_CMD_FINAL; + cmd->init_task_tag = 0xFFFFFFFF; + cmd->targ_xfer_tag = 0xFFFFFFFF; + put_unaligned_be64(0xFFFFFFFFFFFFFFFFULL, &hdr->rsvd4[0]); + cmd->stat_sn = conn->stat_sn++; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + hdr->async_event = ISCSI_ASYNC_MSG_DROPPING_CONNECTION; + hdr->param1 = cpu_to_be16(cmd->logout_cid); + hdr->param2 = cpu_to_be16(conn->sess->sess_ops->DefaultTime2Wait); + hdr->param3 = cpu_to_be16(conn->sess->sess_ops->DefaultTime2Retain); + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + cmd->tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32C HeaderDigest to" + " Async Message 0x%08x\n", *header_digest); + } + + cmd->iov_misc[0].iov_base = cmd->pdu; + cmd->iov_misc[0].iov_len = cmd->tx_size; + cmd->iov_misc_count = 1; + + pr_debug("Sending Connection Dropped Async Message StatSN:" + " 0x%08x, for CID: %hu on CID: %hu\n", cmd->stat_sn, + cmd->logout_cid, conn->cid); + return 0; +} + +static int iscsit_send_data_in( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn, + int *eodr) +{ + int iov_ret = 0, set_statsn = 0; + u32 iov_count = 0, tx_size = 0; + struct iscsi_datain datain; + struct iscsi_datain_req *dr; + struct iscsi_data_rsp *hdr; + struct kvec *iov; + + memset(&datain, 0, sizeof(struct iscsi_datain)); + dr = iscsit_get_datain_values(cmd, &datain); + if (!dr) { + pr_err("iscsit_get_datain_values failed for ITT: 0x%08x\n", + cmd->init_task_tag); + return -1; + } + + /* + * Be paranoid and double check the logic for now. + */ + if ((datain.offset + datain.length) > cmd->data_length) { + pr_err("Command ITT: 0x%08x, datain.offset: %u and" + " datain.length: %u exceeds cmd->data_length: %u\n", + cmd->init_task_tag, datain.offset, datain.length, + cmd->data_length); + return -1; + } + + spin_lock_bh(&conn->sess->session_stats_lock); + conn->sess->tx_data_octets += datain.length; + if (conn->sess->se_sess->se_node_acl) { + spin_lock(&conn->sess->se_sess->se_node_acl->stats_lock); + conn->sess->se_sess->se_node_acl->read_bytes += datain.length; + spin_unlock(&conn->sess->se_sess->se_node_acl->stats_lock); + } + spin_unlock_bh(&conn->sess->session_stats_lock); + /* + * Special case for successfully execution w/ both DATAIN + * and Sense Data. + */ + if ((datain.flags & ISCSI_FLAG_DATA_STATUS) && + (cmd->se_cmd.se_cmd_flags & SCF_TRANSPORT_TASK_SENSE)) + datain.flags &= ~ISCSI_FLAG_DATA_STATUS; + else { + if ((dr->dr_complete == DATAIN_COMPLETE_NORMAL) || + (dr->dr_complete == DATAIN_COMPLETE_CONNECTION_RECOVERY)) { + iscsit_increment_maxcmdsn(cmd, conn->sess); + cmd->stat_sn = conn->stat_sn++; + set_statsn = 1; + } else if (dr->dr_complete == + DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY) + set_statsn = 1; + } + + hdr = (struct iscsi_data_rsp *) cmd->pdu; + memset(hdr, 0, ISCSI_HDR_LEN); + hdr->opcode = ISCSI_OP_SCSI_DATA_IN; + hdr->flags = datain.flags; + if (hdr->flags & ISCSI_FLAG_DATA_STATUS) { + if (cmd->se_cmd.se_cmd_flags & SCF_OVERFLOW_BIT) { + hdr->flags |= ISCSI_FLAG_DATA_OVERFLOW; + hdr->residual_count = cpu_to_be32(cmd->residual_count); + } else if (cmd->se_cmd.se_cmd_flags & SCF_UNDERFLOW_BIT) { + hdr->flags |= ISCSI_FLAG_DATA_UNDERFLOW; + hdr->residual_count = cpu_to_be32(cmd->residual_count); + } + } + hton24(hdr->dlength, datain.length); + if (hdr->flags & ISCSI_FLAG_DATA_ACK) + int_to_scsilun(cmd->se_cmd.orig_fe_lun, + (struct scsi_lun *)&hdr->lun); + else + put_unaligned_le64(0xFFFFFFFFFFFFFFFFULL, &hdr->lun); + + hdr->itt = cpu_to_be32(cmd->init_task_tag); + hdr->ttt = (hdr->flags & ISCSI_FLAG_DATA_ACK) ? + cpu_to_be32(cmd->targ_xfer_tag) : + 0xFFFFFFFF; + hdr->statsn = (set_statsn) ? cpu_to_be32(cmd->stat_sn) : + 0xFFFFFFFF; + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + hdr->datasn = cpu_to_be32(datain.data_sn); + hdr->offset = cpu_to_be32(datain.offset); + + iov = &cmd->iov_data[0]; + iov[iov_count].iov_base = cmd->pdu; + iov[iov_count++].iov_len = ISCSI_HDR_LEN; + tx_size += ISCSI_HDR_LEN; + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + iov[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + + pr_debug("Attaching CRC32 HeaderDigest" + " for DataIN PDU 0x%08x\n", *header_digest); + } + + iov_ret = iscsit_map_iovec(cmd, &cmd->iov_data[1], datain.offset, datain.length); + if (iov_ret < 0) + return -1; + + iov_count += iov_ret; + tx_size += datain.length; + + cmd->padding = ((-datain.length) & 3); + if (cmd->padding) { + iov[iov_count].iov_base = cmd->pad_bytes; + iov[iov_count++].iov_len = cmd->padding; + tx_size += cmd->padding; + + pr_debug("Attaching %u padding bytes\n", + cmd->padding); + } + if (conn->conn_ops->DataDigest) { + cmd->data_crc = iscsit_do_crypto_hash_sg(&conn->conn_tx_hash, cmd, + datain.offset, datain.length, cmd->padding, cmd->pad_bytes); + + iov[iov_count].iov_base = &cmd->data_crc; + iov[iov_count++].iov_len = ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + + pr_debug("Attached CRC32C DataDigest %d bytes, crc" + " 0x%08x\n", datain.length+cmd->padding, cmd->data_crc); + } + + cmd->iov_data_count = iov_count; + cmd->tx_size = tx_size; + + pr_debug("Built DataIN ITT: 0x%08x, StatSN: 0x%08x," + " DataSN: 0x%08x, Offset: %u, Length: %u, CID: %hu\n", + cmd->init_task_tag, ntohl(hdr->statsn), ntohl(hdr->datasn), + ntohl(hdr->offset), datain.length, conn->cid); + + if (dr->dr_complete) { + *eodr = (cmd->se_cmd.se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) ? + 2 : 1; + iscsit_free_datain_req(cmd, dr); + } + + return 0; +} + +static int iscsit_send_logout_response( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + int niov = 0, tx_size; + struct iscsi_conn *logout_conn = NULL; + struct iscsi_conn_recovery *cr = NULL; + struct iscsi_session *sess = conn->sess; + struct kvec *iov; + struct iscsi_logout_rsp *hdr; + /* + * The actual shutting down of Sessions and/or Connections + * for CLOSESESSION and CLOSECONNECTION Logout Requests + * is done in scsi_logout_post_handler(). + */ + switch (cmd->logout_reason) { + case ISCSI_LOGOUT_REASON_CLOSE_SESSION: + pr_debug("iSCSI session logout successful, setting" + " logout response to ISCSI_LOGOUT_SUCCESS.\n"); + cmd->logout_response = ISCSI_LOGOUT_SUCCESS; + break; + case ISCSI_LOGOUT_REASON_CLOSE_CONNECTION: + if (cmd->logout_response == ISCSI_LOGOUT_CID_NOT_FOUND) + break; + /* + * For CLOSECONNECTION logout requests carrying + * a matching logout CID -> local CID, the reference + * for the local CID will have been incremented in + * iscsi_logout_closeconnection(). + * + * For CLOSECONNECTION logout requests carrying + * a different CID than the connection it arrived + * on, the connection responding to cmd->logout_cid + * is stopped in iscsit_logout_post_handler_diffcid(). + */ + + pr_debug("iSCSI CID: %hu logout on CID: %hu" + " successful.\n", cmd->logout_cid, conn->cid); + cmd->logout_response = ISCSI_LOGOUT_SUCCESS; + break; + case ISCSI_LOGOUT_REASON_RECOVERY: + if ((cmd->logout_response == ISCSI_LOGOUT_RECOVERY_UNSUPPORTED) || + (cmd->logout_response == ISCSI_LOGOUT_CLEANUP_FAILED)) + break; + /* + * If the connection is still active from our point of view + * force connection recovery to occur. + */ + logout_conn = iscsit_get_conn_from_cid_rcfr(sess, + cmd->logout_cid); + if ((logout_conn)) { + iscsit_connection_reinstatement_rcfr(logout_conn); + iscsit_dec_conn_usage_count(logout_conn); + } + + cr = iscsit_get_inactive_connection_recovery_entry( + conn->sess, cmd->logout_cid); + if (!cr) { + pr_err("Unable to locate CID: %hu for" + " REMOVECONNFORRECOVERY Logout Request.\n", + cmd->logout_cid); + cmd->logout_response = ISCSI_LOGOUT_CID_NOT_FOUND; + break; + } + + iscsit_discard_cr_cmds_by_expstatsn(cr, cmd->exp_stat_sn); + + pr_debug("iSCSI REMOVECONNFORRECOVERY logout" + " for recovery for CID: %hu on CID: %hu successful.\n", + cmd->logout_cid, conn->cid); + cmd->logout_response = ISCSI_LOGOUT_SUCCESS; + break; + default: + pr_err("Unknown cmd->logout_reason: 0x%02x\n", + cmd->logout_reason); + return -1; + } + + tx_size = ISCSI_HDR_LEN; + hdr = (struct iscsi_logout_rsp *)cmd->pdu; + memset(hdr, 0, ISCSI_HDR_LEN); + hdr->opcode = ISCSI_OP_LOGOUT_RSP; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hdr->response = cmd->logout_response; + hdr->itt = cpu_to_be32(cmd->init_task_tag); + cmd->stat_sn = conn->stat_sn++; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + + iscsit_increment_maxcmdsn(cmd, conn->sess); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + + iov = &cmd->iov_misc[0]; + iov[niov].iov_base = cmd->pdu; + iov[niov++].iov_len = ISCSI_HDR_LEN; + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + iov[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32C HeaderDigest to" + " Logout Response 0x%08x\n", *header_digest); + } + cmd->iov_misc_count = niov; + cmd->tx_size = tx_size; + + pr_debug("Sending Logout Response ITT: 0x%08x StatSN:" + " 0x%08x Response: 0x%02x CID: %hu on CID: %hu\n", + cmd->init_task_tag, cmd->stat_sn, hdr->response, + cmd->logout_cid, conn->cid); + + return 0; +} + +/* + * Unsolicited NOPIN, either requesting a response or not. + */ +static int iscsit_send_unsolicited_nopin( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn, + int want_response) +{ + int tx_size = ISCSI_HDR_LEN; + struct iscsi_nopin *hdr; + + hdr = (struct iscsi_nopin *) cmd->pdu; + memset(hdr, 0, ISCSI_HDR_LEN); + hdr->opcode = ISCSI_OP_NOOP_IN; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hdr->itt = cpu_to_be32(cmd->init_task_tag); + hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); + cmd->stat_sn = conn->stat_sn; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32C HeaderDigest to" + " NopIN 0x%08x\n", *header_digest); + } + + cmd->iov_misc[0].iov_base = cmd->pdu; + cmd->iov_misc[0].iov_len = tx_size; + cmd->iov_misc_count = 1; + cmd->tx_size = tx_size; + + pr_debug("Sending Unsolicited NOPIN TTT: 0x%08x StatSN:" + " 0x%08x CID: %hu\n", hdr->ttt, cmd->stat_sn, conn->cid); + + return 0; +} + +static int iscsit_send_nopin_response( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + int niov = 0, tx_size; + u32 padding = 0; + struct kvec *iov; + struct iscsi_nopin *hdr; + + tx_size = ISCSI_HDR_LEN; + hdr = (struct iscsi_nopin *) cmd->pdu; + memset(hdr, 0, ISCSI_HDR_LEN); + hdr->opcode = ISCSI_OP_NOOP_IN; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hton24(hdr->dlength, cmd->buf_ptr_size); + put_unaligned_le64(0xFFFFFFFFFFFFFFFFULL, &hdr->lun); + hdr->itt = cpu_to_be32(cmd->init_task_tag); + hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); + cmd->stat_sn = conn->stat_sn++; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + + iscsit_increment_maxcmdsn(cmd, conn->sess); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + + iov = &cmd->iov_misc[0]; + iov[niov].iov_base = cmd->pdu; + iov[niov++].iov_len = ISCSI_HDR_LEN; + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + iov[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32C HeaderDigest" + " to NopIn 0x%08x\n", *header_digest); + } + + /* + * NOPOUT Ping Data is attached to struct iscsi_cmd->buf_ptr. + * NOPOUT DataSegmentLength is at struct iscsi_cmd->buf_ptr_size. + */ + if (cmd->buf_ptr_size) { + iov[niov].iov_base = cmd->buf_ptr; + iov[niov++].iov_len = cmd->buf_ptr_size; + tx_size += cmd->buf_ptr_size; + + pr_debug("Echoing back %u bytes of ping" + " data.\n", cmd->buf_ptr_size); + + padding = ((-cmd->buf_ptr_size) & 3); + if (padding != 0) { + iov[niov].iov_base = &cmd->pad_bytes; + iov[niov++].iov_len = padding; + tx_size += padding; + pr_debug("Attaching %u additional" + " padding bytes.\n", padding); + } + if (conn->conn_ops->DataDigest) { + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + cmd->buf_ptr, cmd->buf_ptr_size, + padding, (u8 *)&cmd->pad_bytes, + (u8 *)&cmd->data_crc); + + iov[niov].iov_base = &cmd->data_crc; + iov[niov++].iov_len = ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attached DataDigest for %u" + " bytes of ping data, CRC 0x%08x\n", + cmd->buf_ptr_size, cmd->data_crc); + } + } + + cmd->iov_misc_count = niov; + cmd->tx_size = tx_size; + + pr_debug("Sending NOPIN Response ITT: 0x%08x, TTT:" + " 0x%08x, StatSN: 0x%08x, Length %u\n", cmd->init_task_tag, + cmd->targ_xfer_tag, cmd->stat_sn, cmd->buf_ptr_size); + + return 0; +} + +int iscsit_send_r2t( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + int tx_size = 0; + struct iscsi_r2t *r2t; + struct iscsi_r2t_rsp *hdr; + + r2t = iscsit_get_r2t_from_list(cmd); + if (!r2t) + return -1; + + hdr = (struct iscsi_r2t_rsp *) cmd->pdu; + memset(hdr, 0, ISCSI_HDR_LEN); + hdr->opcode = ISCSI_OP_R2T; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + int_to_scsilun(cmd->se_cmd.orig_fe_lun, + (struct scsi_lun *)&hdr->lun); + hdr->itt = cpu_to_be32(cmd->init_task_tag); + spin_lock_bh(&conn->sess->ttt_lock); + r2t->targ_xfer_tag = conn->sess->targ_xfer_tag++; + if (r2t->targ_xfer_tag == 0xFFFFFFFF) + r2t->targ_xfer_tag = conn->sess->targ_xfer_tag++; + spin_unlock_bh(&conn->sess->ttt_lock); + hdr->ttt = cpu_to_be32(r2t->targ_xfer_tag); + hdr->statsn = cpu_to_be32(conn->stat_sn); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + hdr->r2tsn = cpu_to_be32(r2t->r2t_sn); + hdr->data_offset = cpu_to_be32(r2t->offset); + hdr->data_length = cpu_to_be32(r2t->xfer_len); + + cmd->iov_misc[0].iov_base = cmd->pdu; + cmd->iov_misc[0].iov_len = ISCSI_HDR_LEN; + tx_size += ISCSI_HDR_LEN; + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + cmd->iov_misc[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32 HeaderDigest for R2T" + " PDU 0x%08x\n", *header_digest); + } + + pr_debug("Built %sR2T, ITT: 0x%08x, TTT: 0x%08x, StatSN:" + " 0x%08x, R2TSN: 0x%08x, Offset: %u, DDTL: %u, CID: %hu\n", + (!r2t->recovery_r2t) ? "" : "Recovery ", cmd->init_task_tag, + r2t->targ_xfer_tag, ntohl(hdr->statsn), r2t->r2t_sn, + r2t->offset, r2t->xfer_len, conn->cid); + + cmd->iov_misc_count = 1; + cmd->tx_size = tx_size; + + spin_lock_bh(&cmd->r2t_lock); + r2t->sent_r2t = 1; + spin_unlock_bh(&cmd->r2t_lock); + + return 0; +} + +/* + * type 0: Normal Operation. + * type 1: Called from Storage Transport. + * type 2: Called from iscsi_task_reassign_complete_write() for + * connection recovery. + */ +int iscsit_build_r2ts_for_cmd( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn, + int type) +{ + int first_r2t = 1; + u32 offset = 0, xfer_len = 0; + + spin_lock_bh(&cmd->r2t_lock); + if (cmd->cmd_flags & ICF_SENT_LAST_R2T) { + spin_unlock_bh(&cmd->r2t_lock); + return 0; + } + + if (conn->sess->sess_ops->DataSequenceInOrder && (type != 2)) + if (cmd->r2t_offset < cmd->write_data_done) + cmd->r2t_offset = cmd->write_data_done; + + while (cmd->outstanding_r2ts < conn->sess->sess_ops->MaxOutstandingR2T) { + if (conn->sess->sess_ops->DataSequenceInOrder) { + offset = cmd->r2t_offset; + + if (first_r2t && (type == 2)) { + xfer_len = ((offset + + (conn->sess->sess_ops->MaxBurstLength - + cmd->next_burst_len) > + cmd->data_length) ? + (cmd->data_length - offset) : + (conn->sess->sess_ops->MaxBurstLength - + cmd->next_burst_len)); + } else { + xfer_len = ((offset + + conn->sess->sess_ops->MaxBurstLength) > + cmd->data_length) ? + (cmd->data_length - offset) : + conn->sess->sess_ops->MaxBurstLength; + } + cmd->r2t_offset += xfer_len; + + if (cmd->r2t_offset == cmd->data_length) + cmd->cmd_flags |= ICF_SENT_LAST_R2T; + } else { + struct iscsi_seq *seq; + + seq = iscsit_get_seq_holder_for_r2t(cmd); + if (!seq) { + spin_unlock_bh(&cmd->r2t_lock); + return -1; + } + + offset = seq->offset; + xfer_len = seq->xfer_len; + + if (cmd->seq_send_order == cmd->seq_count) + cmd->cmd_flags |= ICF_SENT_LAST_R2T; + } + cmd->outstanding_r2ts++; + first_r2t = 0; + + if (iscsit_add_r2t_to_list(cmd, offset, xfer_len, 0, 0) < 0) { + spin_unlock_bh(&cmd->r2t_lock); + return -1; + } + + if (cmd->cmd_flags & ICF_SENT_LAST_R2T) + break; + } + spin_unlock_bh(&cmd->r2t_lock); + + return 0; +} + +static int iscsit_send_status( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + u8 iov_count = 0, recovery; + u32 padding = 0, tx_size = 0; + struct iscsi_scsi_rsp *hdr; + struct kvec *iov; + + recovery = (cmd->i_state != ISTATE_SEND_STATUS); + if (!recovery) + cmd->stat_sn = conn->stat_sn++; + + spin_lock_bh(&conn->sess->session_stats_lock); + conn->sess->rsp_pdus++; + spin_unlock_bh(&conn->sess->session_stats_lock); + + hdr = (struct iscsi_scsi_rsp *) cmd->pdu; + memset(hdr, 0, ISCSI_HDR_LEN); + hdr->opcode = ISCSI_OP_SCSI_CMD_RSP; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + if (cmd->se_cmd.se_cmd_flags & SCF_OVERFLOW_BIT) { + hdr->flags |= ISCSI_FLAG_CMD_OVERFLOW; + hdr->residual_count = cpu_to_be32(cmd->residual_count); + } else if (cmd->se_cmd.se_cmd_flags & SCF_UNDERFLOW_BIT) { + hdr->flags |= ISCSI_FLAG_CMD_UNDERFLOW; + hdr->residual_count = cpu_to_be32(cmd->residual_count); + } + hdr->response = cmd->iscsi_response; + hdr->cmd_status = cmd->se_cmd.scsi_status; + hdr->itt = cpu_to_be32(cmd->init_task_tag); + hdr->statsn = cpu_to_be32(cmd->stat_sn); + + iscsit_increment_maxcmdsn(cmd, conn->sess); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + + iov = &cmd->iov_misc[0]; + iov[iov_count].iov_base = cmd->pdu; + iov[iov_count++].iov_len = ISCSI_HDR_LEN; + tx_size += ISCSI_HDR_LEN; + + /* + * Attach SENSE DATA payload to iSCSI Response PDU + */ + if (cmd->se_cmd.sense_buffer && + ((cmd->se_cmd.se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) || + (cmd->se_cmd.se_cmd_flags & SCF_EMULATED_TASK_SENSE))) { + padding = -(cmd->se_cmd.scsi_sense_length) & 3; + hton24(hdr->dlength, cmd->se_cmd.scsi_sense_length); + iov[iov_count].iov_base = cmd->se_cmd.sense_buffer; + iov[iov_count++].iov_len = + (cmd->se_cmd.scsi_sense_length + padding); + tx_size += cmd->se_cmd.scsi_sense_length; + + if (padding) { + memset(cmd->se_cmd.sense_buffer + + cmd->se_cmd.scsi_sense_length, 0, padding); + tx_size += padding; + pr_debug("Adding %u bytes of padding to" + " SENSE.\n", padding); + } + + if (conn->conn_ops->DataDigest) { + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + cmd->se_cmd.sense_buffer, + (cmd->se_cmd.scsi_sense_length + padding), + 0, NULL, (u8 *)&cmd->data_crc); + + iov[iov_count].iov_base = &cmd->data_crc; + iov[iov_count++].iov_len = ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + + pr_debug("Attaching CRC32 DataDigest for" + " SENSE, %u bytes CRC 0x%08x\n", + (cmd->se_cmd.scsi_sense_length + padding), + cmd->data_crc); + } + + pr_debug("Attaching SENSE DATA: %u bytes to iSCSI" + " Response PDU\n", + cmd->se_cmd.scsi_sense_length); + } + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + iov[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32 HeaderDigest for Response" + " PDU 0x%08x\n", *header_digest); + } + + cmd->iov_misc_count = iov_count; + cmd->tx_size = tx_size; + + pr_debug("Built %sSCSI Response, ITT: 0x%08x, StatSN: 0x%08x," + " Response: 0x%02x, SAM Status: 0x%02x, CID: %hu\n", + (!recovery) ? "" : "Recovery ", cmd->init_task_tag, + cmd->stat_sn, 0x00, cmd->se_cmd.scsi_status, conn->cid); + + return 0; +} + +static u8 iscsit_convert_tcm_tmr_rsp(struct se_tmr_req *se_tmr) +{ + switch (se_tmr->response) { + case TMR_FUNCTION_COMPLETE: + return ISCSI_TMF_RSP_COMPLETE; + case TMR_TASK_DOES_NOT_EXIST: + return ISCSI_TMF_RSP_NO_TASK; + case TMR_LUN_DOES_NOT_EXIST: + return ISCSI_TMF_RSP_NO_LUN; + case TMR_TASK_MGMT_FUNCTION_NOT_SUPPORTED: + return ISCSI_TMF_RSP_NOT_SUPPORTED; + case TMR_FUNCTION_AUTHORIZATION_FAILED: + return ISCSI_TMF_RSP_AUTH_FAILED; + case TMR_FUNCTION_REJECTED: + default: + return ISCSI_TMF_RSP_REJECTED; + } +} + +static int iscsit_send_task_mgt_rsp( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req; + struct iscsi_tm_rsp *hdr; + u32 tx_size = 0; + + hdr = (struct iscsi_tm_rsp *) cmd->pdu; + memset(hdr, 0, ISCSI_HDR_LEN); + hdr->opcode = ISCSI_OP_SCSI_TMFUNC_RSP; + hdr->response = iscsit_convert_tcm_tmr_rsp(se_tmr); + hdr->itt = cpu_to_be32(cmd->init_task_tag); + cmd->stat_sn = conn->stat_sn++; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + + iscsit_increment_maxcmdsn(cmd, conn->sess); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + + cmd->iov_misc[0].iov_base = cmd->pdu; + cmd->iov_misc[0].iov_len = ISCSI_HDR_LEN; + tx_size += ISCSI_HDR_LEN; + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + cmd->iov_misc[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32 HeaderDigest for Task" + " Mgmt Response PDU 0x%08x\n", *header_digest); + } + + cmd->iov_misc_count = 1; + cmd->tx_size = tx_size; + + pr_debug("Built Task Management Response ITT: 0x%08x," + " StatSN: 0x%08x, Response: 0x%02x, CID: %hu\n", + cmd->init_task_tag, cmd->stat_sn, hdr->response, conn->cid); + + return 0; +} + +static int iscsit_build_sendtargets_response(struct iscsi_cmd *cmd) +{ + char *payload = NULL; + struct iscsi_conn *conn = cmd->conn; + struct iscsi_portal_group *tpg; + struct iscsi_tiqn *tiqn; + struct iscsi_tpg_np *tpg_np; + int buffer_len, end_of_buf = 0, len = 0, payload_len = 0; + unsigned char buf[256]; + + buffer_len = (conn->conn_ops->MaxRecvDataSegmentLength > 32768) ? + 32768 : conn->conn_ops->MaxRecvDataSegmentLength; + + memset(buf, 0, 256); + + payload = kzalloc(buffer_len, GFP_KERNEL); + if (!payload) { + pr_err("Unable to allocate memory for sendtargets" + " response.\n"); + return -ENOMEM; + } + + spin_lock(&tiqn_lock); + list_for_each_entry(tiqn, &g_tiqn_list, tiqn_list) { + len = sprintf(buf, "TargetName=%s", tiqn->tiqn); + len += 1; + + if ((len + payload_len) > buffer_len) { + spin_unlock(&tiqn->tiqn_tpg_lock); + end_of_buf = 1; + goto eob; + } + memcpy((void *)payload + payload_len, buf, len); + payload_len += len; + + 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) || + (tpg->tpg_state == TPG_STATE_INACTIVE)) { + 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) { + len = sprintf(buf, "TargetAddress=" + "%s%s%s:%hu,%hu", + (tpg_np->tpg_np->np_sockaddr.ss_family == AF_INET6) ? + "[" : "", tpg_np->tpg_np->np_ip, + (tpg_np->tpg_np->np_sockaddr.ss_family == AF_INET6) ? + "]" : "", tpg_np->tpg_np->np_port, + tpg->tpgt); + len += 1; + + if ((len + payload_len) > buffer_len) { + spin_unlock(&tpg->tpg_np_lock); + spin_unlock(&tiqn->tiqn_tpg_lock); + end_of_buf = 1; + goto eob; + } + memcpy((void *)payload + payload_len, buf, len); + payload_len += len; + } + spin_unlock(&tpg->tpg_np_lock); + } + spin_unlock(&tiqn->tiqn_tpg_lock); +eob: + if (end_of_buf) + break; + } + spin_unlock(&tiqn_lock); + + cmd->buf_ptr = payload; + + return payload_len; +} + +/* + * FIXME: Add support for F_BIT and C_BIT when the length is longer than + * MaxRecvDataSegmentLength. + */ +static int iscsit_send_text_rsp( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + struct iscsi_text_rsp *hdr; + struct kvec *iov; + u32 padding = 0, tx_size = 0; + int text_length, iov_count = 0; + + text_length = iscsit_build_sendtargets_response(cmd); + if (text_length < 0) + return text_length; + + padding = ((-text_length) & 3); + if (padding != 0) { + memset(cmd->buf_ptr + text_length, 0, padding); + pr_debug("Attaching %u additional bytes for" + " padding.\n", padding); + } + + hdr = (struct iscsi_text_rsp *) cmd->pdu; + memset(hdr, 0, ISCSI_HDR_LEN); + hdr->opcode = ISCSI_OP_TEXT_RSP; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hton24(hdr->dlength, text_length); + hdr->itt = cpu_to_be32(cmd->init_task_tag); + hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); + cmd->stat_sn = conn->stat_sn++; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + + iscsit_increment_maxcmdsn(cmd, conn->sess); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + + iov = &cmd->iov_misc[0]; + + iov[iov_count].iov_base = cmd->pdu; + iov[iov_count++].iov_len = ISCSI_HDR_LEN; + iov[iov_count].iov_base = cmd->buf_ptr; + iov[iov_count++].iov_len = text_length + padding; + + tx_size += (ISCSI_HDR_LEN + text_length + padding); + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + iov[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32 HeaderDigest for" + " Text Response PDU 0x%08x\n", *header_digest); + } + + if (conn->conn_ops->DataDigest) { + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + cmd->buf_ptr, (text_length + padding), + 0, NULL, (u8 *)&cmd->data_crc); + + iov[iov_count].iov_base = &cmd->data_crc; + iov[iov_count++].iov_len = ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + + pr_debug("Attaching DataDigest for %u bytes of text" + " data, CRC 0x%08x\n", (text_length + padding), + cmd->data_crc); + } + + cmd->iov_misc_count = iov_count; + cmd->tx_size = tx_size; + + pr_debug("Built Text Response: ITT: 0x%08x, StatSN: 0x%08x," + " Length: %u, CID: %hu\n", cmd->init_task_tag, cmd->stat_sn, + text_length, conn->cid); + return 0; +} + +static int iscsit_send_reject( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + u32 iov_count = 0, tx_size = 0; + struct iscsi_reject *hdr; + struct kvec *iov; + + hdr = (struct iscsi_reject *) cmd->pdu; + hdr->opcode = ISCSI_OP_REJECT; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hton24(hdr->dlength, ISCSI_HDR_LEN); + cmd->stat_sn = conn->stat_sn++; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + + iov = &cmd->iov_misc[0]; + + iov[iov_count].iov_base = cmd->pdu; + iov[iov_count++].iov_len = ISCSI_HDR_LEN; + iov[iov_count].iov_base = cmd->buf_ptr; + iov[iov_count++].iov_len = ISCSI_HDR_LEN; + + tx_size = (ISCSI_HDR_LEN + ISCSI_HDR_LEN); + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)hdr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)header_digest); + + iov[0].iov_len += ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32 HeaderDigest for" + " REJECT PDU 0x%08x\n", *header_digest); + } + + if (conn->conn_ops->DataDigest) { + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, + (unsigned char *)cmd->buf_ptr, ISCSI_HDR_LEN, + 0, NULL, (u8 *)&cmd->data_crc); + + iov[iov_count].iov_base = &cmd->data_crc; + iov[iov_count++].iov_len = ISCSI_CRC_LEN; + tx_size += ISCSI_CRC_LEN; + pr_debug("Attaching CRC32 DataDigest for REJECT" + " PDU 0x%08x\n", cmd->data_crc); + } + + cmd->iov_misc_count = iov_count; + cmd->tx_size = tx_size; + + pr_debug("Built Reject PDU StatSN: 0x%08x, Reason: 0x%02x," + " CID: %hu\n", ntohl(hdr->statsn), hdr->reason, conn->cid); + + return 0; +} + +static void iscsit_tx_thread_wait_for_tcp(struct iscsi_conn *conn) +{ + if ((conn->sock->sk->sk_shutdown & SEND_SHUTDOWN) || + (conn->sock->sk->sk_shutdown & RCV_SHUTDOWN)) { + wait_for_completion_interruptible_timeout( + &conn->tx_half_close_comp, + ISCSI_TX_THREAD_TCP_TIMEOUT * HZ); + } +} + +#ifdef CONFIG_SMP + +void iscsit_thread_get_cpumask(struct iscsi_conn *conn) +{ + struct iscsi_thread_set *ts = conn->thread_set; + int ord, cpu; + /* + * thread_id is assigned from iscsit_global->ts_bitmap from + * within iscsi_thread_set.c:iscsi_allocate_thread_sets() + * + * Here we use thread_id to determine which CPU that this + * iSCSI connection's iscsi_thread_set will be scheduled to + * execute upon. + */ + ord = ts->thread_id % cpumask_weight(cpu_online_mask); +#if 0 + pr_debug(">>>>>>>>>>>>>>>>>>>> Generated ord: %d from" + " thread_id: %d\n", ord, ts->thread_id); +#endif + for_each_online_cpu(cpu) { + if (ord-- == 0) { + cpumask_set_cpu(cpu, conn->conn_cpumask); + return; + } + } + /* + * This should never be reached.. + */ + dump_stack(); + cpumask_setall(conn->conn_cpumask); +} + +static inline void iscsit_thread_check_cpumask( + struct iscsi_conn *conn, + struct task_struct *p, + int mode) +{ + char buf[128]; + /* + * mode == 1 signals iscsi_target_tx_thread() usage. + * mode == 0 signals iscsi_target_rx_thread() usage. + */ + if (mode == 1) { + if (!conn->conn_tx_reset_cpumask) + return; + conn->conn_tx_reset_cpumask = 0; + } else { + if (!conn->conn_rx_reset_cpumask) + return; + conn->conn_rx_reset_cpumask = 0; + } + /* + * Update the CPU mask for this single kthread so that + * both TX and RX kthreads are scheduled to run on the + * same CPU. + */ + memset(buf, 0, 128); + cpumask_scnprintf(buf, 128, conn->conn_cpumask); +#if 0 + pr_debug(">>>>>>>>>>>>>> Calling set_cpus_allowed_ptr():" + " %s for %s\n", buf, p->comm); +#endif + set_cpus_allowed_ptr(p, conn->conn_cpumask); +} + +#else +#define iscsit_thread_get_cpumask(X) ({}) +#define iscsit_thread_check_cpumask(X, Y, Z) ({}) +#endif /* CONFIG_SMP */ + +int iscsi_target_tx_thread(void *arg) +{ + u8 state; + int eodr = 0; + int ret = 0; + int sent_status = 0; + int use_misc = 0; + int map_sg = 0; + struct iscsi_cmd *cmd = NULL; + struct iscsi_conn *conn; + struct iscsi_queue_req *qr = NULL; + struct se_cmd *se_cmd; + struct iscsi_thread_set *ts = (struct iscsi_thread_set *)arg; + /* + * Allow ourselves to be interrupted by SIGINT so that a + * connection recovery / failure event can be triggered externally. + */ + allow_signal(SIGINT); + +restart: + conn = iscsi_tx_thread_pre_handler(ts); + if (!conn) + goto out; + + eodr = map_sg = ret = sent_status = use_misc = 0; + + while (!kthread_should_stop()) { + /* + * Ensure that both TX and RX per connection kthreads + * are scheduled to run on the same CPU. + */ + iscsit_thread_check_cpumask(conn, current, 1); + + schedule_timeout_interruptible(MAX_SCHEDULE_TIMEOUT); + + if ((ts->status == ISCSI_THREAD_SET_RESET) || + signal_pending(current)) + goto transport_err; + +get_immediate: + qr = iscsit_get_cmd_from_immediate_queue(conn); + if (qr) { + atomic_set(&conn->check_immediate_queue, 0); + cmd = qr->cmd; + state = qr->state; + kmem_cache_free(lio_qr_cache, qr); + + spin_lock_bh(&cmd->istate_lock); + switch (state) { + case ISTATE_SEND_R2T: + spin_unlock_bh(&cmd->istate_lock); + ret = iscsit_send_r2t(cmd, conn); + break; + case ISTATE_REMOVE: + spin_unlock_bh(&cmd->istate_lock); + + if (cmd->data_direction == DMA_TO_DEVICE) + iscsit_stop_dataout_timer(cmd); + + spin_lock_bh(&conn->cmd_lock); + list_del(&cmd->i_list); + spin_unlock_bh(&conn->cmd_lock); + /* + * Determine if a struct se_cmd is assoicated with + * this struct iscsi_cmd. + */ + if (!(cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) && + !(cmd->tmr_req)) + iscsit_release_cmd(cmd); + else + transport_generic_free_cmd(&cmd->se_cmd, + 1, 0); + goto get_immediate; + case ISTATE_SEND_NOPIN_WANT_RESPONSE: + spin_unlock_bh(&cmd->istate_lock); + iscsit_mod_nopin_response_timer(conn); + ret = iscsit_send_unsolicited_nopin(cmd, + conn, 1); + break; + case ISTATE_SEND_NOPIN_NO_RESPONSE: + spin_unlock_bh(&cmd->istate_lock); + ret = iscsit_send_unsolicited_nopin(cmd, + conn, 0); + break; + default: + pr_err("Unknown Opcode: 0x%02x ITT:" + " 0x%08x, i_state: %d on CID: %hu\n", + cmd->iscsi_opcode, cmd->init_task_tag, state, + conn->cid); + spin_unlock_bh(&cmd->istate_lock); + goto transport_err; + } + if (ret < 0) { + conn->tx_immediate_queue = 0; + goto transport_err; + } + + if (iscsit_send_tx_data(cmd, conn, 1) < 0) { + conn->tx_immediate_queue = 0; + iscsit_tx_thread_wait_for_tcp(conn); + goto transport_err; + } + + spin_lock_bh(&cmd->istate_lock); + switch (state) { + case ISTATE_SEND_R2T: + spin_unlock_bh(&cmd->istate_lock); + spin_lock_bh(&cmd->dataout_timeout_lock); + iscsit_start_dataout_timer(cmd, conn); + spin_unlock_bh(&cmd->dataout_timeout_lock); + break; + case ISTATE_SEND_NOPIN_WANT_RESPONSE: + cmd->i_state = ISTATE_SENT_NOPIN_WANT_RESPONSE; + spin_unlock_bh(&cmd->istate_lock); + break; + case ISTATE_SEND_NOPIN_NO_RESPONSE: + cmd->i_state = ISTATE_SENT_STATUS; + spin_unlock_bh(&cmd->istate_lock); + break; + default: + pr_err("Unknown Opcode: 0x%02x ITT:" + " 0x%08x, i_state: %d on CID: %hu\n", + cmd->iscsi_opcode, cmd->init_task_tag, + state, conn->cid); + spin_unlock_bh(&cmd->istate_lock); + goto transport_err; + } + goto get_immediate; + } else + conn->tx_immediate_queue = 0; + +get_response: + qr = iscsit_get_cmd_from_response_queue(conn); + if (qr) { + cmd = qr->cmd; + state = qr->state; + kmem_cache_free(lio_qr_cache, qr); + + spin_lock_bh(&cmd->istate_lock); +check_rsp_state: + switch (state) { + case ISTATE_SEND_DATAIN: + spin_unlock_bh(&cmd->istate_lock); + ret = iscsit_send_data_in(cmd, conn, + &eodr); + map_sg = 1; + break; + case ISTATE_SEND_STATUS: + case ISTATE_SEND_STATUS_RECOVERY: + spin_unlock_bh(&cmd->istate_lock); + use_misc = 1; + ret = iscsit_send_status(cmd, conn); + break; + case ISTATE_SEND_LOGOUTRSP: + spin_unlock_bh(&cmd->istate_lock); + use_misc = 1; + ret = iscsit_send_logout_response(cmd, conn); + break; + case ISTATE_SEND_ASYNCMSG: + spin_unlock_bh(&cmd->istate_lock); + use_misc = 1; + ret = iscsit_send_conn_drop_async_message( + cmd, conn); + break; + case ISTATE_SEND_NOPIN: + spin_unlock_bh(&cmd->istate_lock); + use_misc = 1; + ret = iscsit_send_nopin_response(cmd, conn); + break; + case ISTATE_SEND_REJECT: + spin_unlock_bh(&cmd->istate_lock); + use_misc = 1; + ret = iscsit_send_reject(cmd, conn); + break; + case ISTATE_SEND_TASKMGTRSP: + spin_unlock_bh(&cmd->istate_lock); + use_misc = 1; + ret = iscsit_send_task_mgt_rsp(cmd, conn); + if (ret != 0) + break; + ret = iscsit_tmr_post_handler(cmd, conn); + if (ret != 0) + iscsit_fall_back_to_erl0(conn->sess); + break; + case ISTATE_SEND_TEXTRSP: + spin_unlock_bh(&cmd->istate_lock); + use_misc = 1; + ret = iscsit_send_text_rsp(cmd, conn); + break; + default: + pr_err("Unknown Opcode: 0x%02x ITT:" + " 0x%08x, i_state: %d on CID: %hu\n", + cmd->iscsi_opcode, cmd->init_task_tag, + state, conn->cid); + spin_unlock_bh(&cmd->istate_lock); + goto transport_err; + } + if (ret < 0) { + conn->tx_response_queue = 0; + goto transport_err; + } + + se_cmd = &cmd->se_cmd; + + if (map_sg && !conn->conn_ops->IFMarker) { + if (iscsit_fe_sendpage_sg(cmd, conn) < 0) { + conn->tx_response_queue = 0; + iscsit_tx_thread_wait_for_tcp(conn); + iscsit_unmap_iovec(cmd); + goto transport_err; + } + } else { + if (iscsit_send_tx_data(cmd, conn, use_misc) < 0) { + conn->tx_response_queue = 0; + iscsit_tx_thread_wait_for_tcp(conn); + iscsit_unmap_iovec(cmd); + goto transport_err; + } + } + map_sg = 0; + iscsit_unmap_iovec(cmd); + + spin_lock_bh(&cmd->istate_lock); + switch (state) { + case ISTATE_SEND_DATAIN: + if (!eodr) + goto check_rsp_state; + + if (eodr == 1) { + cmd->i_state = ISTATE_SENT_LAST_DATAIN; + sent_status = 1; + eodr = use_misc = 0; + } else if (eodr == 2) { + cmd->i_state = state = + ISTATE_SEND_STATUS; + sent_status = 0; + eodr = use_misc = 0; + goto check_rsp_state; + } + break; + case ISTATE_SEND_STATUS: + use_misc = 0; + sent_status = 1; + break; + case ISTATE_SEND_ASYNCMSG: + case ISTATE_SEND_NOPIN: + case ISTATE_SEND_STATUS_RECOVERY: + case ISTATE_SEND_TEXTRSP: + use_misc = 0; + sent_status = 1; + break; + case ISTATE_SEND_REJECT: + use_misc = 0; + if (cmd->cmd_flags & ICF_REJECT_FAIL_CONN) { + cmd->cmd_flags &= ~ICF_REJECT_FAIL_CONN; + spin_unlock_bh(&cmd->istate_lock); + complete(&cmd->reject_comp); + goto transport_err; + } + complete(&cmd->reject_comp); + break; + case ISTATE_SEND_TASKMGTRSP: + use_misc = 0; + sent_status = 1; + break; + case ISTATE_SEND_LOGOUTRSP: + spin_unlock_bh(&cmd->istate_lock); + if (!iscsit_logout_post_handler(cmd, conn)) + goto restart; + spin_lock_bh(&cmd->istate_lock); + use_misc = 0; + sent_status = 1; + break; + default: + pr_err("Unknown Opcode: 0x%02x ITT:" + " 0x%08x, i_state: %d on CID: %hu\n", + cmd->iscsi_opcode, cmd->init_task_tag, + cmd->i_state, conn->cid); + spin_unlock_bh(&cmd->istate_lock); + goto transport_err; + } + + if (sent_status) { + cmd->i_state = ISTATE_SENT_STATUS; + sent_status = 0; + } + spin_unlock_bh(&cmd->istate_lock); + + if (atomic_read(&conn->check_immediate_queue)) + goto get_immediate; + + goto get_response; + } else + conn->tx_response_queue = 0; + } + +transport_err: + iscsit_take_action_for_connection_exit(conn); + goto restart; +out: + return 0; +} + +int iscsi_target_rx_thread(void *arg) +{ + int ret; + u8 buffer[ISCSI_HDR_LEN], opcode; + u32 checksum = 0, digest = 0; + struct iscsi_conn *conn = NULL; + struct iscsi_thread_set *ts = (struct iscsi_thread_set *)arg; + struct kvec iov; + /* + * Allow ourselves to be interrupted by SIGINT so that a + * connection recovery / failure event can be triggered externally. + */ + allow_signal(SIGINT); + +restart: + conn = iscsi_rx_thread_pre_handler(ts); + if (!conn) + goto out; + + while (!kthread_should_stop()) { + /* + * Ensure that both TX and RX per connection kthreads + * are scheduled to run on the same CPU. + */ + iscsit_thread_check_cpumask(conn, current, 0); + + memset(buffer, 0, ISCSI_HDR_LEN); + memset(&iov, 0, sizeof(struct kvec)); + + iov.iov_base = buffer; + iov.iov_len = ISCSI_HDR_LEN; + + ret = rx_data(conn, &iov, 1, ISCSI_HDR_LEN); + if (ret != ISCSI_HDR_LEN) { + iscsit_rx_thread_wait_for_tcp(conn); + goto transport_err; + } + + /* + * Set conn->bad_hdr for use with REJECT PDUs. + */ + memcpy(&conn->bad_hdr, &buffer, ISCSI_HDR_LEN); + + if (conn->conn_ops->HeaderDigest) { + iov.iov_base = &digest; + iov.iov_len = ISCSI_CRC_LEN; + + ret = rx_data(conn, &iov, 1, ISCSI_CRC_LEN); + if (ret != ISCSI_CRC_LEN) { + iscsit_rx_thread_wait_for_tcp(conn); + goto transport_err; + } + + iscsit_do_crypto_hash_buf(&conn->conn_rx_hash, + buffer, ISCSI_HDR_LEN, + 0, NULL, (u8 *)&checksum); + + if (digest != checksum) { + pr_err("HeaderDigest CRC32C failed," + " received 0x%08x, computed 0x%08x\n", + digest, checksum); + /* + * Set the PDU to 0xff so it will intentionally + * hit default in the switch below. + */ + memset(buffer, 0xff, ISCSI_HDR_LEN); + spin_lock_bh(&conn->sess->session_stats_lock); + conn->sess->conn_digest_errors++; + spin_unlock_bh(&conn->sess->session_stats_lock); + } else { + pr_debug("Got HeaderDigest CRC32C" + " 0x%08x\n", checksum); + } + } + + if (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT) + goto transport_err; + + opcode = buffer[0] & ISCSI_OPCODE_MASK; + + if (conn->sess->sess_ops->SessionType && + ((!(opcode & ISCSI_OP_TEXT)) || + (!(opcode & ISCSI_OP_LOGOUT)))) { + pr_err("Received illegal iSCSI Opcode: 0x%02x" + " while in Discovery Session, rejecting.\n", opcode); + iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, + buffer, conn); + goto transport_err; + } + + switch (opcode) { + case ISCSI_OP_SCSI_CMD: + if (iscsit_handle_scsi_cmd(conn, buffer) < 0) + goto transport_err; + break; + case ISCSI_OP_SCSI_DATA_OUT: + if (iscsit_handle_data_out(conn, buffer) < 0) + goto transport_err; + break; + case ISCSI_OP_NOOP_OUT: + if (iscsit_handle_nop_out(conn, buffer) < 0) + goto transport_err; + break; + case ISCSI_OP_SCSI_TMFUNC: + if (iscsit_handle_task_mgt_cmd(conn, buffer) < 0) + goto transport_err; + break; + case ISCSI_OP_TEXT: + if (iscsit_handle_text_cmd(conn, buffer) < 0) + goto transport_err; + break; + case ISCSI_OP_LOGOUT: + ret = iscsit_handle_logout_cmd(conn, buffer); + if (ret > 0) { + wait_for_completion_timeout(&conn->conn_logout_comp, + SECONDS_FOR_LOGOUT_COMP * HZ); + goto transport_err; + } else if (ret < 0) + goto transport_err; + break; + case ISCSI_OP_SNACK: + if (iscsit_handle_snack(conn, buffer) < 0) + goto transport_err; + break; + default: + pr_err("Got unknown iSCSI OpCode: 0x%02x\n", + opcode); + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Cannot recover from unknown" + " opcode while ERL=0, closing iSCSI connection" + ".\n"); + goto transport_err; + } + if (!conn->conn_ops->OFMarker) { + pr_err("Unable to recover from unknown" + " opcode while OFMarker=No, closing iSCSI" + " connection.\n"); + goto transport_err; + } + if (iscsit_recover_from_unknown_opcode(conn) < 0) { + pr_err("Unable to recover from unknown" + " opcode, closing iSCSI connection.\n"); + goto transport_err; + } + break; + } + } + +transport_err: + if (!signal_pending(current)) + atomic_set(&conn->transport_failed, 1); + iscsit_take_action_for_connection_exit(conn); + goto restart; +out: + return 0; +} + +static void iscsit_release_commands_from_conn(struct iscsi_conn *conn) +{ + struct iscsi_cmd *cmd = NULL, *cmd_tmp = NULL; + struct iscsi_session *sess = conn->sess; + struct se_cmd *se_cmd; + /* + * We expect this function to only ever be called from either RX or TX + * thread context via iscsit_close_connection() once the other context + * has been reset -> returned sleeping pre-handler state. + */ + spin_lock_bh(&conn->cmd_lock); + list_for_each_entry_safe(cmd, cmd_tmp, &conn->conn_cmd_list, i_list) { + if (!(cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD)) { + + list_del(&cmd->i_list); + spin_unlock_bh(&conn->cmd_lock); + iscsit_increment_maxcmdsn(cmd, sess); + se_cmd = &cmd->se_cmd; + /* + * Special cases for active iSCSI TMR, and + * transport_lookup_cmd_lun() failing from + * iscsit_get_lun_for_cmd() in iscsit_handle_scsi_cmd(). + */ + if (cmd->tmr_req && se_cmd->transport_wait_for_tasks) + se_cmd->transport_wait_for_tasks(se_cmd, 1, 1); + else if (cmd->se_cmd.se_cmd_flags & SCF_SE_LUN_CMD) + transport_release_cmd(se_cmd); + else + iscsit_release_cmd(cmd); + + spin_lock_bh(&conn->cmd_lock); + continue; + } + list_del(&cmd->i_list); + spin_unlock_bh(&conn->cmd_lock); + + iscsit_increment_maxcmdsn(cmd, sess); + se_cmd = &cmd->se_cmd; + + if (se_cmd->transport_wait_for_tasks) + se_cmd->transport_wait_for_tasks(se_cmd, 1, 1); + + spin_lock_bh(&conn->cmd_lock); + } + spin_unlock_bh(&conn->cmd_lock); +} + +static void iscsit_stop_timers_for_cmds( + struct iscsi_conn *conn) +{ + struct iscsi_cmd *cmd; + + spin_lock_bh(&conn->cmd_lock); + list_for_each_entry(cmd, &conn->conn_cmd_list, i_list) { + if (cmd->data_direction == DMA_TO_DEVICE) + iscsit_stop_dataout_timer(cmd); + } + spin_unlock_bh(&conn->cmd_lock); +} + +int iscsit_close_connection( + struct iscsi_conn *conn) +{ + int conn_logout = (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT); + struct iscsi_session *sess = conn->sess; + + pr_debug("Closing iSCSI connection CID %hu on SID:" + " %u\n", conn->cid, sess->sid); + /* + * Always up conn_logout_comp just in case the RX Thread is sleeping + * and the logout response never got sent because the connection + * failed. + */ + complete(&conn->conn_logout_comp); + + iscsi_release_thread_set(conn); + + iscsit_stop_timers_for_cmds(conn); + iscsit_stop_nopin_response_timer(conn); + iscsit_stop_nopin_timer(conn); + iscsit_free_queue_reqs_for_conn(conn); + + /* + * During Connection recovery drop unacknowledged out of order + * commands for this connection, and prepare the other commands + * for realligence. + * + * During normal operation clear the out of order commands (but + * do not free the struct iscsi_ooo_cmdsn's) and release all + * struct iscsi_cmds. + */ + if (atomic_read(&conn->connection_recovery)) { + iscsit_discard_unacknowledged_ooo_cmdsns_for_conn(conn); + iscsit_prepare_cmds_for_realligance(conn); + } else { + iscsit_clear_ooo_cmdsns_for_conn(conn); + iscsit_release_commands_from_conn(conn); + } + + /* + * Handle decrementing session or connection usage count if + * a logout response was not able to be sent because the + * connection failed. Fall back to Session Recovery here. + */ + if (atomic_read(&conn->conn_logout_remove)) { + if (conn->conn_logout_reason == ISCSI_LOGOUT_REASON_CLOSE_SESSION) { + iscsit_dec_conn_usage_count(conn); + iscsit_dec_session_usage_count(sess); + } + if (conn->conn_logout_reason == ISCSI_LOGOUT_REASON_CLOSE_CONNECTION) + iscsit_dec_conn_usage_count(conn); + + atomic_set(&conn->conn_logout_remove, 0); + atomic_set(&sess->session_reinstatement, 0); + atomic_set(&sess->session_fall_back_to_erl0, 1); + } + + spin_lock_bh(&sess->conn_lock); + list_del(&conn->conn_list); + + /* + * Attempt to let the Initiator know this connection failed by + * sending an Connection Dropped Async Message on another + * active connection. + */ + if (atomic_read(&conn->connection_recovery)) + iscsit_build_conn_drop_async_message(conn); + + spin_unlock_bh(&sess->conn_lock); + + /* + * If connection reinstatement is being performed on this connection, + * up the connection reinstatement semaphore that is being blocked on + * in iscsit_cause_connection_reinstatement(). + */ + spin_lock_bh(&conn->state_lock); + if (atomic_read(&conn->sleep_on_conn_wait_comp)) { + spin_unlock_bh(&conn->state_lock); + complete(&conn->conn_wait_comp); + wait_for_completion(&conn->conn_post_wait_comp); + spin_lock_bh(&conn->state_lock); + } + + /* + * If connection reinstatement is being performed on this connection + * by receiving a REMOVECONNFORRECOVERY logout request, up the + * connection wait rcfr semaphore that is being blocked on + * an iscsit_connection_reinstatement_rcfr(). + */ + if (atomic_read(&conn->connection_wait_rcfr)) { + spin_unlock_bh(&conn->state_lock); + complete(&conn->conn_wait_rcfr_comp); + wait_for_completion(&conn->conn_post_wait_comp); + spin_lock_bh(&conn->state_lock); + } + atomic_set(&conn->connection_reinstatement, 1); + spin_unlock_bh(&conn->state_lock); + + /* + * If any other processes are accessing this connection pointer we + * must wait until they have completed. + */ + iscsit_check_conn_usage_count(conn); + + if (conn->conn_rx_hash.tfm) + crypto_free_hash(conn->conn_rx_hash.tfm); + if (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); + conn->conn_ops = NULL; + + if (conn->sock) { + if (conn->conn_flags & CONNFLAG_SCTP_STRUCT_FILE) { + kfree(conn->sock->file); + conn->sock->file = NULL; + } + sock_release(conn->sock); + } + conn->thread_set = NULL; + + pr_debug("Moving to TARG_CONN_STATE_FREE.\n"); + conn->conn_state = TARG_CONN_STATE_FREE; + kfree(conn); + + spin_lock_bh(&sess->conn_lock); + atomic_dec(&sess->nconn); + pr_debug("Decremented iSCSI connection count to %hu from node:" + " %s\n", atomic_read(&sess->nconn), + sess->sess_ops->InitiatorName); + /* + * Make sure that if one connection fails in an non ERL=2 iSCSI + * Session that they all fail. + */ + if ((sess->sess_ops->ErrorRecoveryLevel != 2) && !conn_logout && + !atomic_read(&sess->session_logout)) + atomic_set(&sess->session_fall_back_to_erl0, 1); + + /* + * If this was not the last connection in the session, and we are + * performing session reinstatement or falling back to ERL=0, call + * iscsit_stop_session() without sleeping to shutdown the other + * active connections. + */ + if (atomic_read(&sess->nconn)) { + if (!atomic_read(&sess->session_reinstatement) && + !atomic_read(&sess->session_fall_back_to_erl0)) { + spin_unlock_bh(&sess->conn_lock); + return 0; + } + if (!atomic_read(&sess->session_stop_active)) { + atomic_set(&sess->session_stop_active, 1); + spin_unlock_bh(&sess->conn_lock); + iscsit_stop_session(sess, 0, 0); + return 0; + } + spin_unlock_bh(&sess->conn_lock); + return 0; + } + + /* + * If this was the last connection in the session and one of the + * following is occurring: + * + * Session Reinstatement is not being performed, and are falling back + * to ERL=0 call iscsit_close_session(). + * + * Session Logout was requested. iscsit_close_session() will be called + * elsewhere. + * + * Session Continuation is not being performed, start the Time2Retain + * handler and check if sleep_on_sess_wait_sem is active. + */ + if (!atomic_read(&sess->session_reinstatement) && + atomic_read(&sess->session_fall_back_to_erl0)) { + spin_unlock_bh(&sess->conn_lock); + iscsit_close_session(sess); + + return 0; + } else if (atomic_read(&sess->session_logout)) { + pr_debug("Moving to TARG_SESS_STATE_FREE.\n"); + sess->session_state = TARG_SESS_STATE_FREE; + spin_unlock_bh(&sess->conn_lock); + + if (atomic_read(&sess->sleep_on_sess_wait_comp)) + complete(&sess->session_wait_comp); + + return 0; + } else { + pr_debug("Moving to TARG_SESS_STATE_FAILED.\n"); + sess->session_state = TARG_SESS_STATE_FAILED; + + if (!atomic_read(&sess->session_continuation)) { + spin_unlock_bh(&sess->conn_lock); + iscsit_start_time2retain_handler(sess); + } else + spin_unlock_bh(&sess->conn_lock); + + if (atomic_read(&sess->sleep_on_sess_wait_comp)) + complete(&sess->session_wait_comp); + + return 0; + } + spin_unlock_bh(&sess->conn_lock); + + return 0; +} + +int iscsit_close_session(struct iscsi_session *sess) +{ + struct iscsi_portal_group *tpg = ISCSI_TPG_S(sess); + struct se_portal_group *se_tpg = &tpg->tpg_se_tpg; + + if (atomic_read(&sess->nconn)) { + pr_err("%d connection(s) still exist for iSCSI session" + " to %s\n", atomic_read(&sess->nconn), + sess->sess_ops->InitiatorName); + BUG(); + } + + spin_lock_bh(&se_tpg->session_lock); + atomic_set(&sess->session_logout, 1); + atomic_set(&sess->session_reinstatement, 1); + iscsit_stop_time2retain_timer(sess); + spin_unlock_bh(&se_tpg->session_lock); + + /* + * transport_deregister_session_configfs() will clear the + * struct se_node_acl->nacl_sess pointer now as a iscsi_np process context + * can be setting it again with __transport_register_session() in + * iscsi_post_login_handler() again after the iscsit_stop_session() + * completes in iscsi_np context. + */ + transport_deregister_session_configfs(sess->se_sess); + + /* + * If any other processes are accessing this session pointer we must + * wait until they have completed. If we are in an interrupt (the + * time2retain handler) and contain and active session usage count we + * restart the timer and exit. + */ + if (!in_interrupt()) { + if (iscsit_check_session_usage_count(sess) == 1) + iscsit_stop_session(sess, 1, 1); + } else { + if (iscsit_check_session_usage_count(sess) == 2) { + atomic_set(&sess->session_logout, 0); + iscsit_start_time2retain_handler(sess); + return 0; + } + } + + transport_deregister_session(sess->se_sess); + + if (sess->sess_ops->ErrorRecoveryLevel == 2) + iscsit_free_connection_recovery_entires(sess); + + iscsit_free_all_ooo_cmdsns(sess); + + spin_lock_bh(&se_tpg->session_lock); + pr_debug("Moving to TARG_SESS_STATE_FREE.\n"); + sess->session_state = TARG_SESS_STATE_FREE; + pr_debug("Released iSCSI session from node: %s\n", + sess->sess_ops->InitiatorName); + tpg->nsessions--; + if (tpg->tpg_tiqn) + tpg->tpg_tiqn->tiqn_nsessions--; + + pr_debug("Decremented number of active iSCSI Sessions on" + " iSCSI TPG: %hu to %u\n", tpg->tpgt, tpg->nsessions); + + spin_lock(&sess_idr_lock); + idr_remove(&sess_idr, sess->session_index); + spin_unlock(&sess_idr_lock); + + kfree(sess->sess_ops); + sess->sess_ops = NULL; + spin_unlock_bh(&se_tpg->session_lock); + + kfree(sess); + return 0; +} + +static void iscsit_logout_post_handler_closesession( + struct iscsi_conn *conn) +{ + struct iscsi_session *sess = conn->sess; + + iscsi_set_thread_clear(conn, ISCSI_CLEAR_TX_THREAD); + iscsi_set_thread_set_signal(conn, ISCSI_SIGNAL_TX_THREAD); + + atomic_set(&conn->conn_logout_remove, 0); + complete(&conn->conn_logout_comp); + + iscsit_dec_conn_usage_count(conn); + iscsit_stop_session(sess, 1, 1); + iscsit_dec_session_usage_count(sess); + iscsit_close_session(sess); +} + +static void iscsit_logout_post_handler_samecid( + struct iscsi_conn *conn) +{ + iscsi_set_thread_clear(conn, ISCSI_CLEAR_TX_THREAD); + iscsi_set_thread_set_signal(conn, ISCSI_SIGNAL_TX_THREAD); + + atomic_set(&conn->conn_logout_remove, 0); + complete(&conn->conn_logout_comp); + + iscsit_cause_connection_reinstatement(conn, 1); + iscsit_dec_conn_usage_count(conn); +} + +static void iscsit_logout_post_handler_diffcid( + struct iscsi_conn *conn, + u16 cid) +{ + struct iscsi_conn *l_conn; + struct iscsi_session *sess = conn->sess; + + if (!sess) + return; + + spin_lock_bh(&sess->conn_lock); + list_for_each_entry(l_conn, &sess->sess_conn_list, conn_list) { + if (l_conn->cid == cid) { + iscsit_inc_conn_usage_count(l_conn); + break; + } + } + spin_unlock_bh(&sess->conn_lock); + + if (!l_conn) + return; + + if (l_conn->sock) + l_conn->sock->ops->shutdown(l_conn->sock, RCV_SHUTDOWN); + + spin_lock_bh(&l_conn->state_lock); + pr_debug("Moving to TARG_CONN_STATE_IN_LOGOUT.\n"); + l_conn->conn_state = TARG_CONN_STATE_IN_LOGOUT; + spin_unlock_bh(&l_conn->state_lock); + + iscsit_cause_connection_reinstatement(l_conn, 1); + iscsit_dec_conn_usage_count(l_conn); +} + +/* + * Return of 0 causes the TX thread to restart. + */ +static int iscsit_logout_post_handler( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + int ret = 0; + + switch (cmd->logout_reason) { + case ISCSI_LOGOUT_REASON_CLOSE_SESSION: + switch (cmd->logout_response) { + case ISCSI_LOGOUT_SUCCESS: + case ISCSI_LOGOUT_CLEANUP_FAILED: + default: + iscsit_logout_post_handler_closesession(conn); + break; + } + ret = 0; + break; + case ISCSI_LOGOUT_REASON_CLOSE_CONNECTION: + if (conn->cid == cmd->logout_cid) { + switch (cmd->logout_response) { + case ISCSI_LOGOUT_SUCCESS: + case ISCSI_LOGOUT_CLEANUP_FAILED: + default: + iscsit_logout_post_handler_samecid(conn); + break; + } + ret = 0; + } else { + switch (cmd->logout_response) { + case ISCSI_LOGOUT_SUCCESS: + iscsit_logout_post_handler_diffcid(conn, + cmd->logout_cid); + break; + case ISCSI_LOGOUT_CID_NOT_FOUND: + case ISCSI_LOGOUT_CLEANUP_FAILED: + default: + break; + } + ret = 1; + } + break; + case ISCSI_LOGOUT_REASON_RECOVERY: + switch (cmd->logout_response) { + case ISCSI_LOGOUT_SUCCESS: + case ISCSI_LOGOUT_CID_NOT_FOUND: + case ISCSI_LOGOUT_RECOVERY_UNSUPPORTED: + case ISCSI_LOGOUT_CLEANUP_FAILED: + default: + break; + } + ret = 1; + break; + default: + break; + + } + return ret; +} + +void iscsit_fail_session(struct iscsi_session *sess) +{ + struct iscsi_conn *conn; + + spin_lock_bh(&sess->conn_lock); + list_for_each_entry(conn, &sess->sess_conn_list, conn_list) { + pr_debug("Moving to TARG_CONN_STATE_CLEANUP_WAIT.\n"); + conn->conn_state = TARG_CONN_STATE_CLEANUP_WAIT; + } + spin_unlock_bh(&sess->conn_lock); + + pr_debug("Moving to TARG_SESS_STATE_FAILED.\n"); + sess->session_state = TARG_SESS_STATE_FAILED; +} + +int iscsit_free_session(struct iscsi_session *sess) +{ + u16 conn_count = atomic_read(&sess->nconn); + struct iscsi_conn *conn, *conn_tmp = NULL; + int is_last; + + spin_lock_bh(&sess->conn_lock); + atomic_set(&sess->sleep_on_sess_wait_comp, 1); + + list_for_each_entry_safe(conn, conn_tmp, &sess->sess_conn_list, + conn_list) { + if (conn_count == 0) + break; + + if (list_is_last(&conn->conn_list, &sess->sess_conn_list)) { + is_last = 1; + } else { + iscsit_inc_conn_usage_count(conn_tmp); + is_last = 0; + } + iscsit_inc_conn_usage_count(conn); + + spin_unlock_bh(&sess->conn_lock); + iscsit_cause_connection_reinstatement(conn, 1); + spin_lock_bh(&sess->conn_lock); + + iscsit_dec_conn_usage_count(conn); + if (is_last == 0) + iscsit_dec_conn_usage_count(conn_tmp); + + conn_count--; + } + + if (atomic_read(&sess->nconn)) { + spin_unlock_bh(&sess->conn_lock); + wait_for_completion(&sess->session_wait_comp); + } else + spin_unlock_bh(&sess->conn_lock); + + iscsit_close_session(sess); + return 0; +} + +void iscsit_stop_session( + struct iscsi_session *sess, + int session_sleep, + int connection_sleep) +{ + u16 conn_count = atomic_read(&sess->nconn); + struct iscsi_conn *conn, *conn_tmp = NULL; + int is_last; + + spin_lock_bh(&sess->conn_lock); + if (session_sleep) + atomic_set(&sess->sleep_on_sess_wait_comp, 1); + + if (connection_sleep) { + list_for_each_entry_safe(conn, conn_tmp, &sess->sess_conn_list, + conn_list) { + if (conn_count == 0) + break; + + if (list_is_last(&conn->conn_list, &sess->sess_conn_list)) { + is_last = 1; + } else { + iscsit_inc_conn_usage_count(conn_tmp); + is_last = 0; + } + iscsit_inc_conn_usage_count(conn); + + spin_unlock_bh(&sess->conn_lock); + iscsit_cause_connection_reinstatement(conn, 1); + spin_lock_bh(&sess->conn_lock); + + iscsit_dec_conn_usage_count(conn); + if (is_last == 0) + iscsit_dec_conn_usage_count(conn_tmp); + conn_count--; + } + } else { + list_for_each_entry(conn, &sess->sess_conn_list, conn_list) + iscsit_cause_connection_reinstatement(conn, 0); + } + + if (session_sleep && atomic_read(&sess->nconn)) { + spin_unlock_bh(&sess->conn_lock); + wait_for_completion(&sess->session_wait_comp); + } else + spin_unlock_bh(&sess->conn_lock); +} + +int iscsit_release_sessions_for_tpg(struct iscsi_portal_group *tpg, int force) +{ + struct iscsi_session *sess; + struct se_portal_group *se_tpg = &tpg->tpg_se_tpg; + struct se_session *se_sess, *se_sess_tmp; + int session_count = 0; + + spin_lock_bh(&se_tpg->session_lock); + if (tpg->nsessions && !force) { + spin_unlock_bh(&se_tpg->session_lock); + return -1; + } + + list_for_each_entry_safe(se_sess, se_sess_tmp, &se_tpg->tpg_sess_list, + sess_list) { + sess = (struct iscsi_session *)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); + continue; + } + atomic_set(&sess->session_reinstatement, 1); + spin_unlock(&sess->conn_lock); + spin_unlock_bh(&se_tpg->session_lock); + + iscsit_free_session(sess); + spin_lock_bh(&se_tpg->session_lock); + + session_count++; + } + spin_unlock_bh(&se_tpg->session_lock); + + pr_debug("Released %d iSCSI Session(s) from Target Portal" + " Group: %hu\n", session_count, tpg->tpgt); + return 0; +} + +MODULE_DESCRIPTION("iSCSI-Target Driver for mainline target infrastructure"); +MODULE_VERSION("4.1.x"); +MODULE_AUTHOR("nab@Linux-iSCSI.org"); +MODULE_LICENSE("GPL"); + +module_init(iscsi_target_init_module); +module_exit(iscsi_target_cleanup_module); diff --git a/drivers/target/iscsi/iscsi_target.h b/drivers/target/iscsi/iscsi_target.h new file mode 100644 index 000000000000..5db2ddeed5eb --- /dev/null +++ b/drivers/target/iscsi/iscsi_target.h @@ -0,0 +1,42 @@ +#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 ***/ diff --git a/drivers/target/iscsi/iscsi_target_auth.c b/drivers/target/iscsi/iscsi_target_auth.c new file mode 100644 index 000000000000..11fd74307811 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_auth.c @@ -0,0 +1,490 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_auth.h b/drivers/target/iscsi/iscsi_target_auth.h new file mode 100644 index 000000000000..2f463c09626d --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_auth.h @@ -0,0 +1,31 @@ +#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_ ***/ diff --git a/drivers/target/iscsi/iscsi_target_configfs.c b/drivers/target/iscsi/iscsi_target_configfs.c new file mode 100644 index 000000000000..32bb92c44450 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_configfs.c @@ -0,0 +1,1882 @@ +/******************************************************************************* + * 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"); +} diff --git a/drivers/target/iscsi/iscsi_target_configfs.h b/drivers/target/iscsi/iscsi_target_configfs.h new file mode 100644 index 000000000000..8cd5a63c4edc --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_configfs.h @@ -0,0 +1,7 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_core.h b/drivers/target/iscsi/iscsi_target_core.h new file mode 100644 index 000000000000..470ed551eeb5 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_core.h @@ -0,0 +1,859 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_datain_values.c b/drivers/target/iscsi/iscsi_target_datain_values.c new file mode 100644 index 000000000000..8c0495129513 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_datain_values.c @@ -0,0 +1,531 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_datain_values.h b/drivers/target/iscsi/iscsi_target_datain_values.h new file mode 100644 index 000000000000..646429ac5a02 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_datain_values.h @@ -0,0 +1,12 @@ +#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 ***/ diff --git a/drivers/target/iscsi/iscsi_target_device.c b/drivers/target/iscsi/iscsi_target_device.c new file mode 100644 index 000000000000..a19fa5eea88e --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_device.c @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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); +} diff --git a/drivers/target/iscsi/iscsi_target_device.h b/drivers/target/iscsi/iscsi_target_device.h new file mode 100644 index 000000000000..bef1cada15f8 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_device.h @@ -0,0 +1,9 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_erl0.c b/drivers/target/iscsi/iscsi_target_erl0.c new file mode 100644 index 000000000000..b7ffc3cd40cc --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl0.c @@ -0,0 +1,1004 @@ +/****************************************************************************** + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_erl0.h b/drivers/target/iscsi/iscsi_target_erl0.h new file mode 100644 index 000000000000..21acc9a06376 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl0.h @@ -0,0 +1,15 @@ +#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 ***/ diff --git a/drivers/target/iscsi/iscsi_target_erl1.c b/drivers/target/iscsi/iscsi_target_erl1.c new file mode 100644 index 000000000000..980650792cf6 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl1.c @@ -0,0 +1,1299 @@ +/******************************************************************************* + * 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); +} diff --git a/drivers/target/iscsi/iscsi_target_erl1.h b/drivers/target/iscsi/iscsi_target_erl1.h new file mode 100644 index 000000000000..85e67e29de6b --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl1.h @@ -0,0 +1,26 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_erl2.c b/drivers/target/iscsi/iscsi_target_erl2.c new file mode 100644 index 000000000000..91a4d170bda4 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl2.c @@ -0,0 +1,474 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_erl2.h b/drivers/target/iscsi/iscsi_target_erl2.h new file mode 100644 index 000000000000..22f8d24780a6 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl2.h @@ -0,0 +1,18 @@ +#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 ***/ diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c new file mode 100644 index 000000000000..bcaf82f47037 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_login.c @@ -0,0 +1,1232 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_login.h b/drivers/target/iscsi/iscsi_target_login.h new file mode 100644 index 000000000000..091dcae2532b --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_login.h @@ -0,0 +1,12 @@ +#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 ***/ diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c new file mode 100644 index 000000000000..713a4d23557a --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_nego.c @@ -0,0 +1,1067 @@ +/******************************************************************************* + * 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 *)¶m_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); +} diff --git a/drivers/target/iscsi/iscsi_target_nego.h b/drivers/target/iscsi/iscsi_target_nego.h new file mode 100644 index 000000000000..92e133a5158f --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_nego.h @@ -0,0 +1,17 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_nodeattrib.c b/drivers/target/iscsi/iscsi_target_nodeattrib.c new file mode 100644 index 000000000000..aeafbe0cd7d1 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_nodeattrib.c @@ -0,0 +1,263 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_nodeattrib.h b/drivers/target/iscsi/iscsi_target_nodeattrib.h new file mode 100644 index 000000000000..c970b326ef23 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_nodeattrib.h @@ -0,0 +1,14 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_parameters.c b/drivers/target/iscsi/iscsi_target_parameters.c new file mode 100644 index 000000000000..252e246cf51e --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_parameters.c @@ -0,0 +1,1905 @@ +/******************************************************************************* + * 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, ¶m_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(¶m->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(¶m->p_list, ¶m_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, ¶m_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, ¶m_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(¶m_list->param_list); + INIT_LIST_HEAD(¶m_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, ¶m_list->param_list); + } + + if (!list_empty(¶m_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, ¶m_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, ¶m_list->param_list, + p_list) { + list_del(¶m->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, ¶m_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, + ¶m_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, ¶m_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, ¶m_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, ¶m_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, ¶m_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, ¶m_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, ¶m_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, ¶m_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"); + +} diff --git a/drivers/target/iscsi/iscsi_target_parameters.h b/drivers/target/iscsi/iscsi_target_parameters.h new file mode 100644 index 000000000000..6a37fd6f1285 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_parameters.h @@ -0,0 +1,269 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_seq_pdu_list.c b/drivers/target/iscsi/iscsi_target_seq_pdu_list.c new file mode 100644 index 000000000000..fc694082bfc0 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_seq_pdu_list.c @@ -0,0 +1,664 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_seq_pdu_list.h b/drivers/target/iscsi/iscsi_target_seq_pdu_list.h new file mode 100644 index 000000000000..0d52a10e3069 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_seq_pdu_list.h @@ -0,0 +1,86 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_stat.c b/drivers/target/iscsi/iscsi_target_stat.c new file mode 100644 index 000000000000..bbdbe9301b27 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_stat.c @@ -0,0 +1,950 @@ +/******************************************************************************* + * 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, +}; diff --git a/drivers/target/iscsi/iscsi_target_stat.h b/drivers/target/iscsi/iscsi_target_stat.h new file mode 100644 index 000000000000..3ff76b4faad3 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_stat.h @@ -0,0 +1,64 @@ +#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 ***/ diff --git a/drivers/target/iscsi/iscsi_target_tmr.c b/drivers/target/iscsi/iscsi_target_tmr.c new file mode 100644 index 000000000000..db1fe1ec84df --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tmr.c @@ -0,0 +1,849 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_tmr.h b/drivers/target/iscsi/iscsi_target_tmr.h new file mode 100644 index 000000000000..142e992cb097 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tmr.h @@ -0,0 +1,14 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_tpg.c b/drivers/target/iscsi/iscsi_target_tpg.c new file mode 100644 index 000000000000..d4cf2cd25c44 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tpg.c @@ -0,0 +1,759 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_tpg.h b/drivers/target/iscsi/iscsi_target_tpg.h new file mode 100644 index 000000000000..dda48c141a8c --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tpg.h @@ -0,0 +1,41 @@ +#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 */ diff --git a/drivers/target/iscsi/iscsi_target_tq.c b/drivers/target/iscsi/iscsi_target_tq.c new file mode 100644 index 000000000000..0baac5bcebd4 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tq.c @@ -0,0 +1,551 @@ +/******************************************************************************* + * 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); +} diff --git a/drivers/target/iscsi/iscsi_target_tq.h b/drivers/target/iscsi/iscsi_target_tq.h new file mode 100644 index 000000000000..26e6a95ec203 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tq.h @@ -0,0 +1,88 @@ +#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 ***/ diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c new file mode 100644 index 000000000000..a1acb0167902 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -0,0 +1,1819 @@ +/******************************************************************************* + * 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; +} diff --git a/drivers/target/iscsi/iscsi_target_util.h b/drivers/target/iscsi/iscsi_target_util.h new file mode 100644 index 000000000000..2cd49d607bda --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_util.h @@ -0,0 +1,60 @@ +#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 ***/ diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 46352d658e35..c75a01a1c475 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -4052,17 +4052,16 @@ static int transport_allocate_data_tasks( struct se_task *task; struct se_device *dev = cmd->se_dev; unsigned long flags; - sector_t sectors; int task_count, i, ret; - sector_t dev_max_sectors = dev->se_sub_dev->se_dev_attrib.max_sectors; + sector_t sectors, dev_max_sectors = dev->se_sub_dev->se_dev_attrib.max_sectors; u32 sector_size = dev->se_sub_dev->se_dev_attrib.block_size; struct scatterlist *sg; struct scatterlist *cmd_sg; WARN_ON(cmd->data_length % sector_size); sectors = DIV_ROUND_UP(cmd->data_length, sector_size); - task_count = DIV_ROUND_UP(sectors, dev_max_sectors); - + task_count = DIV_ROUND_UP_SECTOR_T(sectors, dev_max_sectors); + cmd_sg = sgl; for (i = 0; i < task_count; i++) { unsigned int task_size; |