diff options
Diffstat (limited to 'drivers/target')
89 files changed, 43953 insertions, 12719 deletions
diff --git a/drivers/target/Kconfig b/drivers/target/Kconfig index 2fac3be209a..dc2d84ac5a0 100644 --- a/drivers/target/Kconfig +++ b/drivers/target/Kconfig @@ -3,6 +3,7 @@ menuconfig TARGET_CORE tristate "Generic Target Core Mod (TCM) and ConfigFS Infrastructure" depends on SCSI && BLOCK select CONFIGFS_FS + select CRC_T10DIF default n help Say Y or M here to enable the TCM Storage Engine and ConfigFS enabled @@ -13,6 +14,7 @@ if TARGET_CORE config TCM_IBLOCK tristate "TCM/IBLOCK Subsystem Plugin for Linux/BLOCK" + select BLK_DEV_INTEGRITY help Say Y here to enable the TCM/IBLOCK subsystem plugin for non-buffered access to Linux/Block devices using BIO @@ -29,4 +31,9 @@ config TCM_PSCSI Say Y here to enable the TCM/pSCSI subsystem plugin for non-buffered passthrough access to Linux/SCSI device +source "drivers/target/loopback/Kconfig" +source "drivers/target/tcm_fc/Kconfig" +source "drivers/target/iscsi/Kconfig" +source "drivers/target/sbp/Kconfig" + endif diff --git a/drivers/target/Makefile b/drivers/target/Makefile index 973bb190ef5..85b012d2f89 100644 --- a/drivers/target/Makefile +++ b/drivers/target/Makefile @@ -1,4 +1,3 @@ -EXTRA_CFLAGS += -I$(srctree)/drivers/target/ -I$(srctree)/drivers/scsi/ target_core_mod-y := target_core_configfs.o \ target_core_device.o \ @@ -7,13 +6,15 @@ target_core_mod-y := target_core_configfs.o \ target_core_hba.o \ target_core_pr.o \ target_core_alua.o \ - target_core_scdb.o \ target_core_tmr.o \ target_core_tpg.o \ target_core_transport.o \ - target_core_cdb.o \ + target_core_sbc.o \ + target_core_spc.o \ target_core_ua.o \ - target_core_rd.o + target_core_rd.o \ + target_core_stat.o \ + target_core_xcopy.o obj-$(CONFIG_TARGET_CORE) += target_core_mod.o @@ -21,3 +22,9 @@ obj-$(CONFIG_TARGET_CORE) += target_core_mod.o obj-$(CONFIG_TCM_IBLOCK) += target_core_iblock.o obj-$(CONFIG_TCM_FILEIO) += target_core_file.o 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/ +obj-$(CONFIG_SBP_TARGET) += sbp/ diff --git a/drivers/target/iscsi/Kconfig b/drivers/target/iscsi/Kconfig new file mode 100644 index 00000000000..8345fb457a4 --- /dev/null +++ b/drivers/target/iscsi/Kconfig @@ -0,0 +1,9 @@ +config ISCSI_TARGET + tristate "Linux-iSCSI.org iSCSI Target Mode Stack" + depends on NET + 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 00000000000..13a92403fe3 --- /dev/null +++ b/drivers/target/iscsi/Makefile @@ -0,0 +1,21 @@ +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 \ + iscsi_target_transport.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 00000000000..1f4c794f5fc --- /dev/null +++ b/drivers/target/iscsi/iscsi_target.c @@ -0,0 +1,4786 @@ +/******************************************************************************* + * This file contains main functions related to the iSCSI Target Core Driver. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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 <linux/module.h> +#include <linux/idr.h> +#include <asm/unaligned.h> +#include <scsi/scsi_device.h> +#include <scsi/iscsi_proto.h> +#include <scsi/scsi_tcq.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.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_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" + +#include <target/iscsi/iscsi_transport.h> + +static LIST_HEAD(g_tiqn_list); +static LIST_HEAD(g_np_list); +static DEFINE_SPINLOCK(tiqn_lock); +static DEFINE_MUTEX(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_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 *, + struct iscsi_scsi_req *, u32); + +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); + + tiqn->tiqn_state = TIQN_STATE_ACTIVE; + + idr_preload(GFP_KERNEL); + spin_lock(&tiqn_lock); + + ret = idr_alloc(&tiqn_idr, NULL, 0, 0, GFP_NOWAIT); + if (ret < 0) { + pr_err("idr_alloc() failed for tiqn->tiqn_index\n"); + spin_unlock(&tiqn_lock); + idr_preload_end(); + kfree(tiqn); + return ERR_PTR(ret); + } + tiqn->tiqn_index = ret; + list_add_tail(&tiqn->tiqn_list, &g_tiqn_list); + + spin_unlock(&tiqn_lock); + idr_preload_end(); + + 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; + } + 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 = down_interruptible(&tpg->np_login_sem); + if ((ret != 0) || signal_pending(current)) + return -1; + + spin_lock_bh(&tpg->tpg_state_lock); + if (tpg->tpg_state != TPG_STATE_ACTIVE) { + spin_unlock_bh(&tpg->tpg_state_lock); + up(&tpg->np_login_sem); + return -1; + } + spin_unlock_bh(&tpg->tpg_state_lock); + + return 0; +} + +void iscsit_login_kref_put(struct kref *kref) +{ + struct iscsi_tpg_np *tpg_np = container_of(kref, + struct iscsi_tpg_np, tpg_np_kref); + + complete(&tpg_np->tpg_np_comp); +} + +int iscsit_deaccess_np(struct iscsi_np *np, struct iscsi_portal_group *tpg, + struct iscsi_tpg_np *tpg_np) +{ + struct iscsi_tiqn *tiqn = tpg->tpg_tiqn; + + up(&tpg->np_login_sem); + + if (tpg_np) + kref_put(&tpg_np->tpg_np_kref, iscsit_login_kref_put); + + if (tiqn) + iscsit_put_tiqn_for_login(tiqn); + + return 0; +} + +bool iscsit_check_np_match( + struct __kernel_sockaddr_storage *sockaddr, + struct iscsi_np *np, + int network_transport) +{ + struct sockaddr_in *sock_in, *sock_in_e; + struct sockaddr_in6 *sock_in6, *sock_in6_e; + bool ip_match = false; + u16 port; + + if (sockaddr->ss_family == AF_INET6) { + sock_in6 = (struct sockaddr_in6 *)sockaddr; + sock_in6_e = (struct sockaddr_in6 *)&np->np_sockaddr; + + if (!memcmp(&sock_in6->sin6_addr.in6_u, + &sock_in6_e->sin6_addr.in6_u, + sizeof(struct in6_addr))) + ip_match = true; + + 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 = true; + + port = ntohs(sock_in->sin_port); + } + + if (ip_match && (np->np_port == port) && + (np->np_network_transport == network_transport)) + return true; + + return false; +} + +/* + * Called with mutex np_lock held + */ +static struct iscsi_np *iscsit_get_np( + struct __kernel_sockaddr_storage *sockaddr, + int network_transport) +{ + struct iscsi_np *np; + bool match; + + list_for_each_entry(np, &g_np_list, np_list) { + spin_lock_bh(&np->np_thread_lock); + if (np->np_thread_state != ISCSI_NP_THREAD_ACTIVE) { + spin_unlock_bh(&np->np_thread_lock); + continue; + } + + match = iscsit_check_np_match(sockaddr, np, network_transport); + if (match) { + /* + * 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_bh(&np->np_thread_lock); + return np; + } + spin_unlock_bh(&np->np_thread_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; + + mutex_lock(&np_lock); + + /* + * Locate the existing struct iscsi_np if already active.. + */ + np = iscsit_get_np(sockaddr, network_transport); + if (np) { + mutex_unlock(&np_lock); + return np; + } + + np = kzalloc(sizeof(struct iscsi_np), GFP_KERNEL); + if (!np) { + pr_err("Unable to allocate memory for struct iscsi_np\n"); + mutex_unlock(&np_lock); + 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); + mutex_unlock(&np_lock); + 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); + mutex_unlock(&np_lock); + 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; + np->np_thread_state = ISCSI_NP_THREAD_ACTIVE; + + list_add_tail(&np->np_list, &g_np_list); + mutex_unlock(&np_lock); + + pr_debug("CORE[0] - Added Network Portal: %s:%hu on %s\n", + np->np_ip, np->np_port, np->np_transport->name); + + return np; +} + +int iscsit_reset_np_thread( + struct iscsi_np *np, + struct iscsi_tpg_np *tpg_np, + struct iscsi_portal_group *tpg, + bool shutdown) +{ + spin_lock_bh(&np->np_thread_lock); + 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); + + if (tpg_np && shutdown) { + kref_put(&tpg_np->tpg_np_kref, iscsit_login_kref_put); + + wait_for_completion(&tpg_np->tpg_np_comp); + } + + return 0; +} + +static void iscsit_free_np(struct iscsi_np *np) +{ + if (np->np_socket) + sock_release(np->np_socket); +} + +int iscsit_del_np(struct iscsi_np *np) +{ + spin_lock_bh(&np->np_thread_lock); + np->np_exports--; + if (np->np_exports) { + np->enabled = true; + 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); + np->np_thread = NULL; + } + + np->np_transport->iscsit_free_np(np); + + mutex_lock(&np_lock); + list_del(&np->np_list); + mutex_unlock(&np_lock); + + pr_debug("CORE[0] - Removed Network Portal: %s:%hu on %s\n", + np->np_ip, np->np_port, np->np_transport->name); + + iscsit_put_transport(np->np_transport); + kfree(np); + return 0; +} + +static int iscsit_immediate_queue(struct iscsi_conn *, struct iscsi_cmd *, int); +static int iscsit_response_queue(struct iscsi_conn *, struct iscsi_cmd *, int); + +static int iscsit_queue_rsp(struct iscsi_conn *conn, struct iscsi_cmd *cmd) +{ + iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state); + return 0; +} + +static void iscsit_aborted_task(struct iscsi_conn *conn, struct iscsi_cmd *cmd) +{ + bool scsi_cmd = (cmd->iscsi_opcode == ISCSI_OP_SCSI_CMD); + + spin_lock_bh(&conn->cmd_lock); + if (!list_empty(&cmd->i_conn_node)) + list_del_init(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); + + __iscsit_free_cmd(cmd, scsi_cmd, true); +} + +static enum target_prot_op iscsit_get_sup_prot_ops(struct iscsi_conn *conn) +{ + return TARGET_PROT_NORMAL; +} + +static struct iscsit_transport iscsi_target_transport = { + .name = "iSCSI/TCP", + .transport_type = ISCSI_TCP, + .owner = NULL, + .iscsit_setup_np = iscsit_setup_np, + .iscsit_accept_np = iscsit_accept_np, + .iscsit_free_np = iscsit_free_np, + .iscsit_get_login_rx = iscsit_get_login_rx, + .iscsit_put_login_tx = iscsit_put_login_tx, + .iscsit_get_dataout = iscsit_build_r2ts_for_cmd, + .iscsit_immediate_queue = iscsit_immediate_queue, + .iscsit_response_queue = iscsit_response_queue, + .iscsit_queue_data_in = iscsit_queue_rsp, + .iscsit_queue_status = iscsit_queue_rsp, + .iscsit_aborted_task = iscsit_aborted_task, + .iscsit_get_sup_prot_ops = iscsit_get_sup_prot_ops, +}; + +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_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 ts_out2; + } + + 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; + } + + iscsit_register_transport(&iscsi_target_transport); + + 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); +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(); + iscsit_unregister_transport(&iscsi_target_transport); + 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); +} + +static int iscsit_add_reject( + struct iscsi_conn *conn, + u8 reason, + unsigned char *buf) +{ + struct iscsi_cmd *cmd; + + cmd = iscsit_allocate_cmd(conn, TASK_INTERRUPTIBLE); + if (!cmd) + return -1; + + cmd->iscsi_opcode = ISCSI_OP_REJECT; + cmd->reject_reason = reason; + + cmd->buf_ptr = kmemdup(buf, ISCSI_HDR_LEN, GFP_KERNEL); + if (!cmd->buf_ptr) { + pr_err("Unable to allocate memory for cmd->buf_ptr\n"); + iscsit_free_cmd(cmd, false); + return -1; + } + + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_conn_node, &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); + + return -1; +} + +static int iscsit_add_reject_from_cmd( + struct iscsi_cmd *cmd, + u8 reason, + bool add_to_conn, + unsigned char *buf) +{ + struct iscsi_conn *conn; + + 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; + cmd->reject_reason = reason; + + cmd->buf_ptr = kmemdup(buf, ISCSI_HDR_LEN, GFP_KERNEL); + if (!cmd->buf_ptr) { + pr_err("Unable to allocate memory for cmd->buf_ptr\n"); + iscsit_free_cmd(cmd, false); + return -1; + } + + if (add_to_conn) { + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_conn_node, &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); + /* + * Perform the kref_put now if se_cmd has already been setup by + * scsit_setup_scsi_cmd() + */ + if (cmd->se_cmd.se_tfo != NULL) { + pr_debug("iscsi reject: calling target_put_sess_cmd >>>>>>\n"); + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); + } + return -1; +} + +static int iscsit_add_reject_cmd(struct iscsi_cmd *cmd, u8 reason, + unsigned char *buf) +{ + return iscsit_add_reject_from_cmd(cmd, reason, true, buf); +} + +int iscsit_reject_cmd(struct iscsi_cmd *cmd, u8 reason, unsigned char *buf) +{ + return iscsit_add_reject_from_cmd(cmd, reason, false, buf); +} + +/* + * Map some portion of the allocated scatterlist to an iovec, suitable for + * kernel sockets to copy data in/out. + */ +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 know each entry in t_data_sg contains a page. + */ + sg = &cmd->se_cmd.t_data_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) +{ + LIST_HEAD(ack_list); + struct iscsi_cmd *cmd, *cmd_p; + + conn->exp_statsn = exp_statsn; + + if (conn->sess->sess_ops->RDMAExtensions) + return; + + spin_lock_bh(&conn->cmd_lock); + list_for_each_entry_safe(cmd, cmd_p, &conn->conn_cmd_list, i_conn_node) { + spin_lock(&cmd->istate_lock); + if ((cmd->i_state == ISTATE_SENT_STATUS) && + iscsi_sna_lt(cmd->stat_sn, exp_statsn)) { + cmd->i_state = ISTATE_REMOVE; + spin_unlock(&cmd->istate_lock); + list_move_tail(&cmd->i_conn_node, &ack_list); + continue; + } + spin_unlock(&cmd->istate_lock); + } + spin_unlock_bh(&conn->cmd_lock); + + list_for_each_entry_safe(cmd, cmd_p, &ack_list, i_conn_node) { + list_del_init(&cmd->i_conn_node); + iscsit_free_cmd(cmd, false); + } +} + +static int iscsit_allocate_iovecs(struct iscsi_cmd *cmd) +{ + u32 iov_count = max(1UL, DIV_ROUND_UP(cmd->se_cmd.data_length, PAGE_SIZE)); + + iov_count += ISCSI_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; +} + +int iscsit_setup_scsi_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) +{ + int data_direction, payload_length; + struct iscsi_scsi_req *hdr; + int iscsi_task_attr; + int sam_task_attr; + + atomic_long_inc(&conn->sess->cmd_pdus); + + hdr = (struct iscsi_scsi_req *) buf; + payload_length = ntoh24(hdr->dlength); + + /* 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_cmd(cmd, + ISCSI_REASON_BOOKMARK_INVALID, buf); + } + + if (((hdr->flags & ISCSI_FLAG_CMD_READ) || + (hdr->flags & ISCSI_FLAG_CMD_WRITE)) && !hdr->data_length) { + /* + * From RFC-3720 Section 10.3.1: + * + * "Either or both of R and W MAY be 1 when either the + * Expected Data Transfer Length and/or Bidirectional Read + * Expected Data Transfer Length are 0" + * + * For this case, go ahead and clear the unnecssary bits + * to avoid any confusion with ->data_direction. + */ + hdr->flags &= ~ISCSI_FLAG_CMD_READ; + hdr->flags &= ~ISCSI_FLAG_CMD_WRITE; + + pr_warn("ISCSI_FLAG_CMD_READ or ISCSI_FLAG_CMD_WRITE" + " set when Expected Data Transfer Length is 0 for" + " CDB: 0x%02x, Fixing up flags\n", hdr->cdb[0]); + } + + 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_cmd(cmd, + ISCSI_REASON_BOOKMARK_INVALID, buf); + } + + if ((hdr->flags & ISCSI_FLAG_CMD_READ) && + (hdr->flags & ISCSI_FLAG_CMD_WRITE)) { + pr_err("Bidirectional operations not supported!\n"); + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_INVALID, buf); + } + + if (hdr->opcode & ISCSI_OP_IMMEDIATE) { + pr_err("Illegally set Immediate Bit in iSCSI Initiator" + " Scsi Command PDU.\n"); + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_INVALID, buf); + } + + if (payload_length && !conn->sess->sess_ops->ImmediateData) { + pr_err("ImmediateData=No but DataSegmentLength=%u," + " protocol error.\n", payload_length); + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_PROTOCOL_ERROR, buf); + } + + if ((be32_to_cpu(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_cmd(cmd, + ISCSI_REASON_PROTOCOL_ERROR, buf); + } + + if (payload_length > be32_to_cpu(hdr->data_length)) { + pr_err("DataSegmentLength: %u is greater than" + " EDTL: %u, protocol error.\n", payload_length, + hdr->data_length); + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_PROTOCOL_ERROR, buf); + } + + if (payload_length > conn->conn_ops->MaxXmitDataSegmentLength) { + pr_err("DataSegmentLength: %u is greater than" + " MaxXmitDataSegmentLength: %u, protocol error.\n", + payload_length, conn->conn_ops->MaxXmitDataSegmentLength); + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_PROTOCOL_ERROR, buf); + } + + 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_cmd(cmd, + ISCSI_REASON_BOOKMARK_INVALID, buf); + } + + data_direction = (hdr->flags & ISCSI_FLAG_CMD_WRITE) ? DMA_TO_DEVICE : + (hdr->flags & ISCSI_FLAG_CMD_READ) ? DMA_FROM_DEVICE : + DMA_NONE; + + cmd->data_direction = data_direction; + iscsi_task_attr = hdr->flags & ISCSI_FLAG_CMD_ATTR_MASK; + /* + * 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; + } + + 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 = be32_to_cpu(hdr->cmdsn); + cmd->exp_stat_sn = be32_to_cpu(hdr->exp_statsn); + cmd->first_burst_len = payload_length; + + if (!conn->sess->sess_ops->RDMAExtensions && + cmd->data_direction == DMA_FROM_DEVICE) { + struct iscsi_datain_req *dr; + + dr = iscsit_allocate_datain_req(); + if (!dr) + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_NO_RESOURCES, buf); + + iscsit_attach_datain_req(cmd, dr); + } + + /* + * Initialize struct se_cmd descriptor from target_core_mod infrastructure + */ + transport_init_se_cmd(&cmd->se_cmd, &lio_target_fabric_configfs->tf_ops, + conn->sess->se_sess, be32_to_cpu(hdr->data_length), + cmd->data_direction, sam_task_attr, + cmd->sense_buffer + 2); + + pr_debug("Got SCSI Command, ITT: 0x%08x, CmdSN: 0x%08x," + " ExpXferLen: %u, Length: %u, CID: %hu\n", hdr->itt, + hdr->cmdsn, be32_to_cpu(hdr->data_length), payload_length, + conn->cid); + + target_get_sess_cmd(conn->sess->se_sess, &cmd->se_cmd, true); + + cmd->sense_reason = transport_lookup_cmd_lun(&cmd->se_cmd, + scsilun_to_int(&hdr->lun)); + if (cmd->sense_reason) + goto attach_cmd; + + cmd->sense_reason = target_setup_cmd_from_cdb(&cmd->se_cmd, hdr->cdb); + if (cmd->sense_reason) { + if (cmd->sense_reason == TCM_OUT_OF_RESOURCES) { + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_NO_RESOURCES, buf); + } + + goto attach_cmd; + } + + if (iscsit_build_pdu_and_seq_lists(cmd, payload_length) < 0) { + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_NO_RESOURCES, buf); + } + +attach_cmd: + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_conn_node, &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); + + return 0; +} +EXPORT_SYMBOL(iscsit_setup_scsi_cmd); + +void iscsit_set_unsoliticed_dataout(struct iscsi_cmd *cmd) +{ + 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); +} +EXPORT_SYMBOL(iscsit_set_unsoliticed_dataout); + +int iscsit_process_scsi_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_scsi_req *hdr) +{ + int cmdsn_ret = 0; + /* + * 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, + (unsigned char *)hdr, hdr->cmdsn); + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return -1; + else if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) { + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); + return 0; + } + } + + iscsit_ack_from_expstatsn(conn, be32_to_cpu(hdr->exp_statsn)); + + /* + * If no Immediate Data is attached, it's OK to return now. + */ + if (!cmd->immediate_data) { + if (!cmd->sense_reason && cmd->unsolicited_data) + iscsit_set_unsoliticed_dataout(cmd); + if (!cmd->sense_reason) + return 0; + + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); + return 0; + } + + /* + * Early CHECK_CONDITIONs with ImmediateData never make it to command + * execution. These exceptions are processed in CmdSN order using + * iscsit_check_received_cmdsn() in iscsit_get_immediate_data() below. + */ + if (cmd->sense_reason) { + if (cmd->reject_reason) + return 0; + + return 1; + } + /* + * Call directly into transport_generic_new_cmd() to perform + * the backend memory allocation. + */ + cmd->sense_reason = transport_generic_new_cmd(&cmd->se_cmd); + if (cmd->sense_reason) + return 1; + + return 0; +} +EXPORT_SYMBOL(iscsit_process_scsi_cmd); + +static int +iscsit_get_immediate_data(struct iscsi_cmd *cmd, struct iscsi_scsi_req *hdr, + bool dump_payload) +{ + struct iscsi_conn *conn = cmd->conn; + int cmdsn_ret = 0, immed_ret = IMMEDIATE_DATA_NORMAL_OPERATION; + /* + * Special case for Unsupported SAM WRITE Opcodes and ImmediateData=Yes. + */ + if (dump_payload) + goto after_immediate_data; + + immed_ret = iscsit_handle_immediate_data(cmd, hdr, + cmd->first_burst_len); +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(cmd->conn, cmd, + (unsigned char *)hdr, hdr->cmdsn); + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return -1; + + if (cmd->sense_reason || cmdsn_ret == CMDSN_LOWER_THAN_EXP) { + int rc; + + rc = iscsit_dump_data_payload(cmd->conn, + cmd->first_burst_len, 1); + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); + return rc; + } else if (cmd->unsolicited_data) + iscsit_set_unsoliticed_dataout(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, cmd->conn, cmd->i_state); + } else /* immed_ret == IMMEDIATE_DATA_CANNOT_RECOVER */ + return -1; + + return 0; +} + +static int +iscsit_handle_scsi_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) +{ + struct iscsi_scsi_req *hdr = (struct iscsi_scsi_req *)buf; + int rc, immed_data; + bool dump_payload = false; + + rc = iscsit_setup_scsi_cmd(conn, cmd, buf); + if (rc < 0) + return 0; + /* + * Allocation iovecs needed for struct socket operations for + * traditional iSCSI block I/O. + */ + if (iscsit_allocate_iovecs(cmd) < 0) { + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_NO_RESOURCES, buf); + } + immed_data = cmd->immediate_data; + + rc = iscsit_process_scsi_cmd(conn, cmd, hdr); + if (rc < 0) + return rc; + else if (rc > 0) + dump_payload = true; + + if (!immed_data) + return 0; + + return iscsit_get_immediate_data(cmd, hdr, dump_payload); +} + +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, + const void *buf, + u32 payload_length, + u32 padding, + u8 *pad_bytes, + u8 *data_crc) +{ + struct scatterlist sg; + + crypto_hash_init(hash); + + sg_init_one(&sg, 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); +} + +int +iscsit_check_dataout_hdr(struct iscsi_conn *conn, unsigned char *buf, + struct iscsi_cmd **out_cmd) +{ + struct iscsi_data *hdr = (struct iscsi_data *)buf; + struct iscsi_cmd *cmd = NULL; + struct se_cmd *se_cmd; + u32 payload_length = ntoh24(hdr->dlength); + int rc; + + if (!payload_length) { + pr_warn("DataOUT payload is ZERO, ignoring.\n"); + return 0; + } + + /* iSCSI write */ + atomic_long_add(payload_length, &conn->sess->rx_data_octets); + + if (payload_length > conn->conn_ops->MaxXmitDataSegmentLength) { + pr_err("DataSegmentLength: %u is greater than" + " MaxXmitDataSegmentLength: %u\n", payload_length, + conn->conn_ops->MaxXmitDataSegmentLength); + return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR, + buf); + } + + 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, ntohl(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_dump_data_payload(conn, payload_length, 1); + } + se_cmd = &cmd->se_cmd; + iscsit_mod_dataout_timer(cmd); + + if ((be32_to_cpu(hdr->offset) + payload_length) > cmd->se_cmd.data_length) { + pr_err("DataOut Offset: %u, Length %u greater than" + " iSCSI Command EDTL %u, protocol error.\n", + hdr->offset, payload_length, cmd->se_cmd.data_length); + return iscsit_reject_cmd(cmd, ISCSI_REASON_BOOKMARK_INVALID, buf); + } + + 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... */ + WARN_ON(se_cmd->t_state != TRANSPORT_WRITE_PENDING); + if (!(se_cmd->se_cmd_flags & SCF_SUPPORTED_SAM_OPCODE)) + dump_unsolicited_data = 1; + + 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 (se_cmd->transport_state & CMD_T_ABORTED) { + 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. + */ + rc = iscsit_check_pre_dataout(cmd, buf); + if (rc == DATAOUT_WITHIN_COMMAND_RECOVERY) + return 0; + else if (rc == DATAOUT_CANNOT_RECOVER) + return -1; + + *out_cmd = cmd; + return 0; +} +EXPORT_SYMBOL(iscsit_check_dataout_hdr); + +static int +iscsit_get_dataout(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_data *hdr) +{ + struct kvec *iov; + u32 checksum, iov_count = 0, padding = 0, rx_got = 0, rx_size = 0; + u32 payload_length = ntoh24(hdr->dlength); + int iov_ret, data_crc_failed = 0; + + rx_size += payload_length; + iov = &cmd->iov_data[0]; + + iov_ret = iscsit_map_iovec(cmd, iov, be32_to_cpu(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, + be32_to_cpu(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); + } + } + + return data_crc_failed; +} + +int +iscsit_check_dataout_payload(struct iscsi_cmd *cmd, struct iscsi_data *hdr, + bool data_crc_failed) +{ + struct iscsi_conn *conn = cmd->conn; + int rc, ooo_cmdsn; + /* + * Increment post receive data and CRC values or perform + * within-command recovery. + */ + rc = iscsit_check_post_dataout(cmd, (unsigned char *)hdr, data_crc_failed); + if ((rc == DATAOUT_NORMAL) || (rc == DATAOUT_WITHIN_COMMAND_RECOVERY)) + return 0; + else if (rc == DATAOUT_SEND_R2T) { + iscsit_set_dataout_sequence_values(cmd); + conn->conn_transport->iscsit_get_dataout(conn, cmd, false); + } else if (rc == 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); + if (ooo_cmdsn) + return 0; + target_execute_cmd(&cmd->se_cmd); + return 0; + } else /* DATAOUT_CANNOT_RECOVER */ + return -1; + + return 0; +} +EXPORT_SYMBOL(iscsit_check_dataout_payload); + +static int iscsit_handle_data_out(struct iscsi_conn *conn, unsigned char *buf) +{ + struct iscsi_cmd *cmd = NULL; + struct iscsi_data *hdr = (struct iscsi_data *)buf; + int rc; + bool data_crc_failed = false; + + rc = iscsit_check_dataout_hdr(conn, buf, &cmd); + if (rc < 0) + return 0; + else if (!cmd) + return 0; + + rc = iscsit_get_dataout(conn, cmd, hdr); + if (rc < 0) + return rc; + else if (rc > 0) + data_crc_failed = true; + + return iscsit_check_dataout_payload(cmd, hdr, data_crc_failed); +} + +int iscsit_setup_nop_out(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_nopout *hdr) +{ + u32 payload_length = ntoh24(hdr->dlength); + + if (!(hdr->flags & ISCSI_FLAG_CMD_FINAL)) { + pr_err("NopOUT Flag's, Left Most Bit not set, protocol error.\n"); + if (!cmd) + return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + + return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + } + + if (hdr->itt == RESERVED_ITT && !(hdr->opcode & ISCSI_OP_IMMEDIATE)) { + pr_err("NOPOUT ITT is reserved, but Immediate Bit is" + " not set, protocol error.\n"); + if (!cmd) + return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + + return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + } + + if (payload_length > conn->conn_ops->MaxXmitDataSegmentLength) { + pr_err("NOPOUT Ping Data DataSegmentLength: %u is" + " greater than MaxXmitDataSegmentLength: %u, protocol" + " error.\n", payload_length, + conn->conn_ops->MaxXmitDataSegmentLength); + if (!cmd) + return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + + return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + } + + pr_debug("Got NOPOUT Ping %s ITT: 0x%08x, TTT: 0x%08x," + " CmdSN: 0x%08x, ExpStatSN: 0x%08x, Length: %u\n", + hdr->itt == RESERVED_ITT ? "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 == cpu_to_be32(0xFFFFFFFF)) { + 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 = be32_to_cpu(hdr->cmdsn); + cmd->exp_stat_sn = be32_to_cpu(hdr->exp_statsn); + cmd->data_direction = DMA_NONE; + } + + return 0; +} +EXPORT_SYMBOL(iscsit_setup_nop_out); + +int iscsit_process_nop_out(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_nopout *hdr) +{ + struct iscsi_cmd *cmd_p = NULL; + int cmdsn_ret = 0; + /* + * Initiator is expecting a NopIN ping reply.. + */ + if (hdr->itt != RESERVED_ITT) { + if (!cmd) + return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_conn_node, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + iscsit_ack_from_expstatsn(conn, be32_to_cpu(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, + (unsigned char *)hdr, hdr->cmdsn); + if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) + return 0; + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return -1; + + return 0; + } + /* + * This was a response to a unsolicited NOPIN ping. + */ + if (hdr->ttt != cpu_to_be32(0xFFFFFFFF)) { + cmd_p = iscsit_find_cmd_from_ttt(conn, be32_to_cpu(hdr->ttt)); + if (!cmd_p) + return -EINVAL; + + iscsit_stop_nopin_response_timer(conn); + + cmd_p->i_state = ISTATE_REMOVE; + iscsit_add_cmd_to_immediate_queue(cmd_p, conn, cmd_p->i_state); + + iscsit_start_nopin_timer(conn); + return 0; + } + /* + * Otherwise, initiator is not expecting a NOPIN is response. + * Just ignore for now. + */ + return 0; +} +EXPORT_SYMBOL(iscsit_process_nop_out); + +static int iscsit_handle_nop_out(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) +{ + unsigned char *ping_data = NULL; + struct iscsi_nopout *hdr = (struct iscsi_nopout *)buf; + struct kvec *iov = NULL; + u32 payload_length = ntoh24(hdr->dlength); + int ret; + + ret = iscsit_setup_nop_out(conn, cmd, hdr); + if (ret < 0) + return 0; + /* + * Handle NOP-OUT payload for traditional iSCSI sockets + */ + if (payload_length && hdr->ttt == cpu_to_be32(0xFFFFFFFF)) { + u32 checksum, data_crc, padding = 0; + int niov = 0, rx_got, 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 = 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); + } + + return iscsit_process_nop_out(conn, cmd, hdr); +out: + if (cmd) + iscsit_free_cmd(cmd, false); + + kfree(ping_data); + return ret; +} + +int +iscsit_handle_task_mgt_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) +{ + struct se_tmr_req *se_tmr; + struct iscsi_tmr_req *tmr_req; + struct iscsi_tm *hdr; + int out_of_order_cmdsn = 0, ret; + bool sess_ref = false; + u8 function; + + hdr = (struct iscsi_tm *) buf; + 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 != RESERVED_ITT)) { + pr_err("RefTaskTag should be set to 0xFFFFFFFF.\n"); + hdr->rtt = RESERVED_ITT; + } + + 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_cmd(cmd, + ISCSI_REASON_PROTOCOL_ERROR, buf); + } + if ((function != ISCSI_TM_FUNC_ABORT_TASK) && + be32_to_cpu(hdr->refcmdsn) != ISCSI_RESERVED_TAG) + hdr->refcmdsn = cpu_to_be32(ISCSI_RESERVED_TAG); + + 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 iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_NO_RESOURCES, + buf); + } + + /* + * TASK_REASSIGN for ERL=2 / connection stays inside of + * LIO-Target $FABRIC_MOD + */ + if (function != ISCSI_TM_FUNC_TASK_REASSIGN) { + + u8 tcm_function; + int ret; + + transport_init_se_cmd(&cmd->se_cmd, + &lio_target_fabric_configfs->tf_ops, + conn->sess->se_sess, 0, DMA_NONE, + MSG_SIMPLE_TAG, cmd->sense_buffer + 2); + + target_get_sess_cmd(conn->sess->se_sess, &cmd->se_cmd, true); + sess_ref = true; + + 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); + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_NO_RESOURCES, buf); + } + + ret = core_tmr_alloc_req(&cmd->se_cmd, cmd->tmr_req, + tcm_function, GFP_KERNEL); + if (ret < 0) + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_NO_RESOURCES, buf); + + cmd->tmr_req->se_tmr_req = cmd->se_cmd.se_tmr_req; + } + + 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 = be32_to_cpu(hdr->cmdsn); + cmd->exp_stat_sn = be32_to_cpu(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 = transport_lookup_tmr_lun(&cmd->se_cmd, + scsilun_to_int(&hdr->lun)); + if (ret < 0) { + 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) + 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) { + 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) { + 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) + break; + + if (iscsit_check_task_reassign_expdatasn(tmr_req, conn) < 0) + return iscsit_add_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_INVALID, buf); + break; + default: + pr_err("Unknown TMR function: 0x%02x, protocol" + " error.\n", function); + 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_conn_node, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + if (!(hdr->opcode & ISCSI_OP_IMMEDIATE)) { + int cmdsn_ret = iscsit_sequence_cmd(conn, cmd, buf, 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 if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return -1; + } + iscsit_ack_from_expstatsn(conn, be32_to_cpu(hdr->exp_statsn)); + + if (out_of_order_cmdsn || !(hdr->opcode & ISCSI_OP_IMMEDIATE)) + 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. + */ + if (sess_ref) { + pr_debug("Handle TMR, using sess_ref=true check\n"); + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); + } + + iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); + return 0; +} +EXPORT_SYMBOL(iscsit_handle_task_mgt_cmd); + +/* #warning FIXME: Support Text Command parameters besides SendTargets */ +int +iscsit_setup_text_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_text *hdr) +{ + u32 payload_length = ntoh24(hdr->dlength); + + if (payload_length > conn->conn_ops->MaxXmitDataSegmentLength) { + pr_err("Unable to accept text parameter length: %u" + "greater than MaxXmitDataSegmentLength %u.\n", + payload_length, conn->conn_ops->MaxXmitDataSegmentLength); + return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); + } + + if (!(hdr->flags & ISCSI_FLAG_CMD_FINAL) || + (hdr->flags & ISCSI_FLAG_TEXT_CONTINUE)) { + pr_err("Multi sequence text commands currently not supported\n"); + return iscsit_reject_cmd(cmd, ISCSI_REASON_CMD_NOT_SUPPORTED, + (unsigned char *)hdr); + } + + 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); + + 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 = be32_to_cpu(hdr->cmdsn); + cmd->exp_stat_sn = be32_to_cpu(hdr->exp_statsn); + cmd->data_direction = DMA_NONE; + + return 0; +} +EXPORT_SYMBOL(iscsit_setup_text_cmd); + +int +iscsit_process_text_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_text *hdr) +{ + unsigned char *text_in = cmd->text_in_ptr, *text_ptr; + int cmdsn_ret; + + if (!text_in) { + pr_err("Unable to locate text_in buffer for sendtargets" + " discovery\n"); + goto reject; + } + if (strncmp("SendTargets", text_in, 11) != 0) { + pr_err("Received Text Data that is not" + " SendTargets, cannot continue.\n"); + goto reject; + } + text_ptr = strchr(text_in, '='); + if (!text_ptr) { + pr_err("No \"=\" separator found in Text Data," + " cannot continue.\n"); + goto reject; + } + if (!strncmp("=All", text_ptr, 4)) { + cmd->cmd_flags |= IFC_SENDTARGETS_ALL; + } else if (!strncmp("=iqn.", text_ptr, 5) || + !strncmp("=eui.", text_ptr, 5)) { + cmd->cmd_flags |= IFC_SENDTARGETS_SINGLE; + } else { + pr_err("Unable to locate valid SendTargets=%s value\n", text_ptr); + goto reject; + } + + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_conn_node, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + iscsit_ack_from_expstatsn(conn, be32_to_cpu(hdr->exp_statsn)); + + if (!(hdr->opcode & ISCSI_OP_IMMEDIATE)) { + cmdsn_ret = iscsit_sequence_cmd(conn, cmd, + (unsigned char *)hdr, hdr->cmdsn); + if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return -1; + + return 0; + } + + return iscsit_execute_cmd(cmd, 0); + +reject: + return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, + (unsigned char *)hdr); +} +EXPORT_SYMBOL(iscsit_process_text_cmd); + +static int +iscsit_handle_text_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) +{ + struct iscsi_text *hdr = (struct iscsi_text *)buf; + char *text_in = NULL; + u32 payload_length = ntoh24(hdr->dlength); + int rx_size, rc; + + rc = iscsit_setup_text_cmd(conn, cmd, hdr); + if (rc < 0) + return 0; + + rx_size = payload_length; + if (payload_length) { + u32 checksum = 0, data_crc = 0; + u32 padding = 0, pad_bytes = 0; + int niov = 0, rx_got; + struct kvec iov[3]; + + text_in = kzalloc(payload_length, GFP_KERNEL); + if (!text_in) { + pr_err("Unable to allocate memory for" + " incoming text parameters\n"); + goto reject; + } + cmd->text_in_ptr = text_in; + + memset(iov, 0, 3 * sizeof(struct kvec)); + iov[niov].iov_base = text_in; + iov[niov++].iov_len = payload_length; + + padding = ((-payload_length) & 3); + if (padding != 0) { + iov[niov].iov_base = &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) + goto reject; + + if (conn->conn_ops->DataDigest) { + iscsit_do_crypto_hash_buf(&conn->conn_rx_hash, + text_in, payload_length, + padding, (u8 *)&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"); + goto reject; + } 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, payload_length); + } + } + text_in[payload_length - 1] = '\0'; + pr_debug("Successfully read %d bytes of text" + " data.\n", payload_length); + } + + return iscsit_process_text_cmd(conn, cmd, hdr); + +reject: + kfree(cmd->text_in_ptr); + cmd->text_in_ptr = NULL; + return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, buf); +} +EXPORT_SYMBOL(iscsit_handle_text_cmd); + +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; +} + +int +iscsit_handle_logout_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) +{ + int cmdsn_ret, logout_remove = 0; + u8 reason_code = 0; + struct iscsi_logout *hdr; + struct iscsi_tiqn *tiqn = iscsit_snmp_get_tiqn(conn); + + hdr = (struct iscsi_logout *) buf; + reason_code = (hdr->flags & 0x7f); + + 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"); + iscsit_free_cmd(cmd, false); + return 0; + } + + 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 = be32_to_cpu(hdr->cmdsn); + cmd->exp_stat_sn = be32_to_cpu(hdr->exp_statsn); + cmd->logout_cid = be16_to_cpu(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) && + be16_to_cpu(hdr->cid) == conn->cid)) + logout_remove = 1; + + spin_lock_bh(&conn->cmd_lock); + list_add_tail(&cmd->i_conn_node, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + if (reason_code != ISCSI_LOGOUT_REASON_RECOVERY) + iscsit_ack_from_expstatsn(conn, be32_to_cpu(hdr->exp_statsn)); + + /* + * Immediate commands are executed, well, immediately. + * Non-Immediate Logout Commands are executed in CmdSN order. + */ + if (cmd->immediate_cmd) { + int ret = iscsit_execute_cmd(cmd, 0); + + if (ret < 0) + return ret; + } else { + cmdsn_ret = iscsit_sequence_cmd(conn, cmd, buf, hdr->cmdsn); + if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) + logout_remove = 0; + else if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + return -1; + } + + return logout_remove; +} +EXPORT_SYMBOL(iscsit_handle_logout_cmd); + +static int iscsit_handle_snack( + struct iscsi_conn *conn, + unsigned char *buf) +{ + struct iscsi_snack *hdr; + + hdr = (struct iscsi_snack *) buf; + hdr->flags &= ~ISCSI_FLAG_CMD_FINAL; + + 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(conn, ISCSI_REASON_PROTOCOL_ERROR, + buf); + } + /* + * 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, + be32_to_cpu(hdr->ttt), + be32_to_cpu(hdr->begrun), + be32_to_cpu(hdr->runlength)); + case ISCSI_FLAG_SNACK_TYPE_STATUS: + return iscsit_handle_status_snack(conn, hdr->itt, + be32_to_cpu(hdr->ttt), + be32_to_cpu(hdr->begrun), be32_to_cpu(hdr->runlength)); + case ISCSI_FLAG_SNACK_TYPE_DATA_ACK: + return iscsit_handle_data_ack(conn, be32_to_cpu(hdr->ttt), + be32_to_cpu(hdr->begrun), + be32_to_cpu(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(conn, ISCSI_REASON_PROTOCOL_ERROR, + buf); + default: + pr_err("Unknown SNACK type 0x%02x, protocol" + " error.\n", hdr->flags & 0x0f); + return iscsit_add_reject(conn, ISCSI_REASON_PROTOCOL_ERROR, + buf); + } + + 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, + struct iscsi_scsi_req *hdr, + 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_reject_cmd(cmd, + ISCSI_REASON_DATA_DIGEST_ERROR, + (unsigned char *)hdr); + return IMMEDIATE_DATA_CANNOT_RECOVER; + } else { + iscsit_reject_cmd(cmd, + ISCSI_REASON_DATA_DIGEST_ERROR, + (unsigned char *)hdr); + 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->se_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; + bool found = false; + + /* + * 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); + found = true; + break; + } + } + + if (!found) + return; + + cmd = iscsit_allocate_cmd(conn_p, TASK_RUNNING); + 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_conn_node, &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 = RESERVED_ITT; + 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, 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 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); + } +} + +static void +iscsit_build_datain_pdu(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_datain *datain, struct iscsi_data_rsp *hdr, + bool set_statsn) +{ + 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->se_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->se_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 = cmd->init_task_tag; + + if (hdr->flags & ISCSI_FLAG_DATA_ACK) + hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); + else + hdr->ttt = cpu_to_be32(0xFFFFFFFF); + if (set_statsn) + hdr->statsn = cpu_to_be32(cmd->stat_sn); + else + hdr->statsn = cpu_to_be32(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); + + 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); +} + +static int iscsit_send_datain(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_data_rsp *hdr = (struct iscsi_data_rsp *)&cmd->pdu[0]; + struct iscsi_datain datain; + struct iscsi_datain_req *dr; + struct kvec *iov; + u32 iov_count = 0, tx_size = 0; + int eodr = 0, ret, iov_ret; + bool set_statsn = false; + + 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->se_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->se_cmd.data_length); + return -1; + } + + atomic_long_add(datain.length, &conn->sess->tx_data_octets); + /* + * 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 = true; + } else if (dr->dr_complete == + DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY) + set_statsn = true; + } + + iscsit_build_datain_pdu(cmd, conn, &datain, hdr, set_statsn); + + 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, cmd->pdu, + 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; + + /* sendpage is preferred but can't insert markers */ + if (!conn->conn_ops->IFMarker) + ret = iscsit_fe_sendpage_sg(cmd, conn); + else + ret = iscsit_send_tx_data(cmd, conn, 0); + + iscsit_unmap_iovec(cmd); + + if (ret < 0) { + iscsit_tx_thread_wait_for_tcp(conn); + return ret; + } + + if (dr->dr_complete) { + eodr = (cmd->se_cmd.se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) ? + 2 : 1; + iscsit_free_datain_req(cmd, dr); + } + + return eodr; +} + +int +iscsit_build_logout_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_logout_rsp *hdr) +{ + struct iscsi_conn *logout_conn = NULL; + struct iscsi_conn_recovery *cr = NULL; + struct iscsi_session *sess = conn->sess; + /* + * 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; + } + + hdr->opcode = ISCSI_OP_LOGOUT_RSP; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hdr->response = cmd->logout_response; + hdr->itt = 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); + + pr_debug("Built 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; +} +EXPORT_SYMBOL(iscsit_build_logout_rsp); + +static int +iscsit_send_logout(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct kvec *iov; + int niov = 0, tx_size, rc; + + rc = iscsit_build_logout_rsp(cmd, conn, + (struct iscsi_logout_rsp *)&cmd->pdu[0]); + if (rc < 0) + return rc; + + tx_size = ISCSI_HDR_LEN; + 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, &cmd->pdu[0], + 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; + + return 0; +} + +void +iscsit_build_nopin_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_nopin *hdr, bool nopout_response) +{ + hdr->opcode = ISCSI_OP_NOOP_IN; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hton24(hdr->dlength, cmd->buf_ptr_size); + if (nopout_response) + put_unaligned_le64(0xFFFFFFFFFFFFFFFFULL, &hdr->lun); + hdr->itt = cmd->init_task_tag; + hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); + cmd->stat_sn = (nopout_response) ? conn->stat_sn++ : + conn->stat_sn; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + + if (nopout_response) + 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); + + pr_debug("Built NOPIN %s Response ITT: 0x%08x, TTT: 0x%08x," + " StatSN: 0x%08x, Length %u\n", (nopout_response) ? + "Solicitied" : "Unsolicitied", cmd->init_task_tag, + cmd->targ_xfer_tag, cmd->stat_sn, cmd->buf_ptr_size); +} +EXPORT_SYMBOL(iscsit_build_nopin_rsp); + +/* + * 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) +{ + struct iscsi_nopin *hdr = (struct iscsi_nopin *)&cmd->pdu[0]; + int tx_size = ISCSI_HDR_LEN, ret; + + iscsit_build_nopin_rsp(cmd, conn, hdr, false); + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, 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); + + ret = iscsit_send_tx_data(cmd, conn, 1); + if (ret < 0) { + iscsit_tx_thread_wait_for_tcp(conn); + return ret; + } + + spin_lock_bh(&cmd->istate_lock); + cmd->i_state = want_response ? + ISTATE_SENT_NOPIN_WANT_RESPONSE : ISTATE_SENT_STATUS; + spin_unlock_bh(&cmd->istate_lock); + + return 0; +} + +static int +iscsit_send_nopin(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_nopin *hdr = (struct iscsi_nopin *)&cmd->pdu[0]; + struct kvec *iov; + u32 padding = 0; + int niov = 0, tx_size; + + iscsit_build_nopin_rsp(cmd, conn, hdr, true); + + tx_size = ISCSI_HDR_LEN; + 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, 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; + + return 0; +} + +static 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; + int ret; + + 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 = 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, 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); + + ret = iscsit_send_tx_data(cmd, conn, 1); + if (ret < 0) { + iscsit_tx_thread_wait_for_tcp(conn); + return ret; + } + + spin_lock_bh(&cmd->dataout_timeout_lock); + iscsit_start_dataout_timer(cmd, conn); + spin_unlock_bh(&cmd->dataout_timeout_lock); + + return 0; +} + +/* + * @recovery: If called from iscsi_task_reassign_complete_write() for + * connection recovery. + */ +int iscsit_build_r2ts_for_cmd( + struct iscsi_conn *conn, + struct iscsi_cmd *cmd, + bool recovery) +{ + 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 && + !recovery) + cmd->r2t_offset = max(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 && recovery) { + int new_data_end = offset + + conn->sess->sess_ops->MaxBurstLength - + cmd->next_burst_len; + + if (new_data_end > cmd->se_cmd.data_length) + xfer_len = cmd->se_cmd.data_length - offset; + else + xfer_len = + conn->sess->sess_ops->MaxBurstLength - + cmd->next_burst_len; + } else { + int new_data_end = offset + + conn->sess->sess_ops->MaxBurstLength; + + if (new_data_end > cmd->se_cmd.data_length) + xfer_len = cmd->se_cmd.data_length - offset; + else + xfer_len = conn->sess->sess_ops->MaxBurstLength; + } + cmd->r2t_offset += xfer_len; + + if (cmd->r2t_offset == cmd->se_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; +} + +void iscsit_build_rsp_pdu(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + bool inc_stat_sn, struct iscsi_scsi_rsp *hdr) +{ + if (inc_stat_sn) + cmd->stat_sn = conn->stat_sn++; + + atomic_long_inc(&conn->sess->rsp_pdus); + + 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->se_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->se_cmd.residual_count); + } + hdr->response = cmd->iscsi_response; + hdr->cmd_status = cmd->se_cmd.scsi_status; + hdr->itt = 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); + + pr_debug("Built SCSI Response, ITT: 0x%08x, StatSN: 0x%08x," + " Response: 0x%02x, SAM Status: 0x%02x, CID: %hu\n", + cmd->init_task_tag, cmd->stat_sn, cmd->se_cmd.scsi_status, + cmd->se_cmd.scsi_status, conn->cid); +} +EXPORT_SYMBOL(iscsit_build_rsp_pdu); + +static int iscsit_send_response(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_scsi_rsp *hdr = (struct iscsi_scsi_rsp *)&cmd->pdu[0]; + struct kvec *iov; + u32 padding = 0, tx_size = 0; + int iov_count = 0; + bool inc_stat_sn = (cmd->i_state == ISTATE_SEND_STATUS); + + iscsit_build_rsp_pdu(cmd, conn, inc_stat_sn, hdr); + + 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))) { + put_unaligned_be16(cmd->se_cmd.scsi_sense_length, cmd->sense_buffer); + cmd->se_cmd.scsi_sense_length += sizeof (__be16); + + padding = -(cmd->se_cmd.scsi_sense_length) & 3; + hton24(hdr->dlength, (u32)cmd->se_cmd.scsi_sense_length); + iov[iov_count].iov_base = 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->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->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, cmd->pdu, + 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; + + 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_REJECTED: + default: + return ISCSI_TMF_RSP_REJECTED; + } +} + +void +iscsit_build_task_mgt_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_tm_rsp *hdr) +{ + struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req; + + hdr->opcode = ISCSI_OP_SCSI_TMFUNC_RSP; + hdr->flags = ISCSI_FLAG_CMD_FINAL; + hdr->response = iscsit_convert_tcm_tmr_rsp(se_tmr); + hdr->itt = 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); + + 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); +} +EXPORT_SYMBOL(iscsit_build_task_mgt_rsp); + +static int +iscsit_send_task_mgt_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_tm_rsp *hdr = (struct iscsi_tm_rsp *)&cmd->pdu[0]; + u32 tx_size = 0; + + iscsit_build_task_mgt_rsp(cmd, conn, hdr); + + 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, 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; + + return 0; +} + +static bool iscsit_check_inaddr_any(struct iscsi_np *np) +{ + bool ret = false; + + if (np->np_sockaddr.ss_family == AF_INET6) { + const struct sockaddr_in6 sin6 = { + .sin6_addr = IN6ADDR_ANY_INIT }; + struct sockaddr_in6 *sock_in6 = + (struct sockaddr_in6 *)&np->np_sockaddr; + + if (!memcmp(sock_in6->sin6_addr.s6_addr, + sin6.sin6_addr.s6_addr, 16)) + ret = true; + } else { + struct sockaddr_in * sock_in = + (struct sockaddr_in *)&np->np_sockaddr; + + if (sock_in->sin_addr.s_addr == htonl(INADDR_ANY)) + ret = true; + } + + return ret; +} + +#define SENDTARGETS_BUF_LIMIT 32768U + +static int +iscsit_build_sendtargets_response(struct iscsi_cmd *cmd, + enum iscsit_transport_type network_transport) +{ + 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; + int target_name_printed; + unsigned char buf[ISCSI_IQN_LEN+12]; /* iqn + "TargetName=" + \0 */ + unsigned char *text_in = cmd->text_in_ptr, *text_ptr = NULL; + + buffer_len = max(conn->conn_ops->MaxRecvDataSegmentLength, + SENDTARGETS_BUF_LIMIT); + + payload = kzalloc(buffer_len, GFP_KERNEL); + if (!payload) { + pr_err("Unable to allocate memory for sendtargets" + " response.\n"); + return -ENOMEM; + } + /* + * Locate pointer to iqn./eui. string for IFC_SENDTARGETS_SINGLE + * explicit case.. + */ + if (cmd->cmd_flags & IFC_SENDTARGETS_SINGLE) { + text_ptr = strchr(text_in, '='); + if (!text_ptr) { + pr_err("Unable to locate '=' string in text_in:" + " %s\n", text_in); + kfree(payload); + return -EINVAL; + } + /* + * Skip over '=' character.. + */ + text_ptr += 1; + } + + spin_lock(&tiqn_lock); + list_for_each_entry(tiqn, &g_tiqn_list, tiqn_list) { + if ((cmd->cmd_flags & IFC_SENDTARGETS_SINGLE) && + strcmp(tiqn->tiqn, text_ptr)) { + continue; + } + + target_name_printed = 0; + + spin_lock(&tiqn->tiqn_tpg_lock); + list_for_each_entry(tpg, &tiqn->tiqn_tpg_list, tpg_list) { + + /* If demo_mode_discovery=0 and generate_node_acls=0 + * (demo mode dislabed) do not return + * TargetName+TargetAddress unless a NodeACL exists. + */ + + if ((tpg->tpg_attrib.generate_node_acls == 0) && + (tpg->tpg_attrib.demo_mode_discovery == 0) && + (!core_tpg_get_initiator_node_acl(&tpg->tpg_se_tpg, + cmd->conn->sess->sess_ops->InitiatorName))) { + continue; + } + + 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) { + struct iscsi_np *np = tpg_np->tpg_np; + bool inaddr_any = iscsit_check_inaddr_any(np); + + if (np->np_network_transport != network_transport) + continue; + + if (!target_name_printed) { + len = sprintf(buf, "TargetName=%s", + tiqn->tiqn); + 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(payload + payload_len, buf, len); + payload_len += len; + target_name_printed = 1; + } + + len = sprintf(buf, "TargetAddress=" + "%s:%hu,%hu", + inaddr_any ? conn->local_ip : np->np_ip, + inaddr_any ? conn->local_port : 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(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; + + if (cmd->cmd_flags & IFC_SENDTARGETS_SINGLE) + break; + } + spin_unlock(&tiqn_lock); + + cmd->buf_ptr = payload; + + return payload_len; +} + +int +iscsit_build_text_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_text_rsp *hdr, + enum iscsit_transport_type network_transport) +{ + int text_length, padding; + + text_length = iscsit_build_sendtargets_response(cmd, network_transport); + if (text_length < 0) + return text_length; + + hdr->opcode = ISCSI_OP_TEXT_RSP; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + padding = ((-text_length) & 3); + hton24(hdr->dlength, text_length); + hdr->itt = 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); + + 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 text_length + padding; +} +EXPORT_SYMBOL(iscsit_build_text_rsp); + +/* + * 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 iscsi_text_rsp *)cmd->pdu; + struct kvec *iov; + u32 tx_size = 0; + int text_length, iov_count = 0, rc; + + rc = iscsit_build_text_rsp(cmd, conn, hdr, ISCSI_TCP); + if (rc < 0) + return rc; + + text_length = rc; + 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; + + tx_size += (ISCSI_HDR_LEN + text_length); + + if (conn->conn_ops->HeaderDigest) { + u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; + + iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, 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, + 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, + cmd->data_crc); + } + + cmd->iov_misc_count = iov_count; + cmd->tx_size = tx_size; + + return 0; +} + +void +iscsit_build_reject(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_reject *hdr) +{ + hdr->opcode = ISCSI_OP_REJECT; + hdr->reason = cmd->reject_reason; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hton24(hdr->dlength, ISCSI_HDR_LEN); + hdr->ffffffff = cpu_to_be32(0xffffffff); + 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); + +} +EXPORT_SYMBOL(iscsit_build_reject); + +static int iscsit_send_reject( + struct iscsi_cmd *cmd, + struct iscsi_conn *conn) +{ + struct iscsi_reject *hdr = (struct iscsi_reject *)&cmd->pdu[0]; + struct kvec *iov; + u32 iov_count = 0, tx_size; + + iscsit_build_reject(cmd, conn, hdr); + + 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, 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, 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; +} + +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); + 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); + set_cpus_allowed_ptr(p, conn->conn_cpumask); +} + +static int +iscsit_immediate_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) +{ + int ret; + + switch (state) { + case ISTATE_SEND_R2T: + ret = iscsit_send_r2t(cmd, conn); + if (ret < 0) + goto err; + break; + case ISTATE_REMOVE: + spin_lock_bh(&conn->cmd_lock); + list_del_init(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); + + iscsit_free_cmd(cmd, false); + break; + case ISTATE_SEND_NOPIN_WANT_RESPONSE: + iscsit_mod_nopin_response_timer(conn); + ret = iscsit_send_unsolicited_nopin(cmd, conn, 1); + if (ret < 0) + goto err; + break; + case ISTATE_SEND_NOPIN_NO_RESPONSE: + ret = iscsit_send_unsolicited_nopin(cmd, conn, 0); + if (ret < 0) + goto err; + 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); + goto err; + } + + return 0; + +err: + return -1; +} + +static int +iscsit_handle_immediate_queue(struct iscsi_conn *conn) +{ + struct iscsit_transport *t = conn->conn_transport; + struct iscsi_queue_req *qr; + struct iscsi_cmd *cmd; + u8 state; + int ret; + + while ((qr = iscsit_get_cmd_from_immediate_queue(conn))) { + atomic_set(&conn->check_immediate_queue, 0); + cmd = qr->cmd; + state = qr->state; + kmem_cache_free(lio_qr_cache, qr); + + ret = t->iscsit_immediate_queue(conn, cmd, state); + if (ret < 0) + return ret; + } + + return 0; +} + +static int +iscsit_response_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) +{ + int ret; + +check_rsp_state: + switch (state) { + case ISTATE_SEND_DATAIN: + ret = iscsit_send_datain(cmd, conn); + if (ret < 0) + goto err; + else if (!ret) + /* more drs */ + goto check_rsp_state; + else if (ret == 1) { + /* all done */ + spin_lock_bh(&cmd->istate_lock); + cmd->i_state = ISTATE_SENT_STATUS; + spin_unlock_bh(&cmd->istate_lock); + + if (atomic_read(&conn->check_immediate_queue)) + return 1; + + return 0; + } else if (ret == 2) { + /* Still must send status, + SCF_TRANSPORT_TASK_SENSE was set */ + spin_lock_bh(&cmd->istate_lock); + cmd->i_state = ISTATE_SEND_STATUS; + spin_unlock_bh(&cmd->istate_lock); + state = ISTATE_SEND_STATUS; + goto check_rsp_state; + } + + break; + case ISTATE_SEND_STATUS: + case ISTATE_SEND_STATUS_RECOVERY: + ret = iscsit_send_response(cmd, conn); + break; + case ISTATE_SEND_LOGOUTRSP: + ret = iscsit_send_logout(cmd, conn); + break; + case ISTATE_SEND_ASYNCMSG: + ret = iscsit_send_conn_drop_async_message( + cmd, conn); + break; + case ISTATE_SEND_NOPIN: + ret = iscsit_send_nopin(cmd, conn); + break; + case ISTATE_SEND_REJECT: + ret = iscsit_send_reject(cmd, conn); + break; + case ISTATE_SEND_TASKMGTRSP: + 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: + 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); + goto err; + } + if (ret < 0) + goto err; + + if (iscsit_send_tx_data(cmd, conn, 1) < 0) { + iscsit_tx_thread_wait_for_tcp(conn); + iscsit_unmap_iovec(cmd); + goto err; + } + iscsit_unmap_iovec(cmd); + + switch (state) { + case ISTATE_SEND_LOGOUTRSP: + if (!iscsit_logout_post_handler(cmd, conn)) + goto restart; + /* fall through */ + case ISTATE_SEND_STATUS: + case ISTATE_SEND_ASYNCMSG: + case ISTATE_SEND_NOPIN: + case ISTATE_SEND_STATUS_RECOVERY: + case ISTATE_SEND_TEXTRSP: + case ISTATE_SEND_TASKMGTRSP: + case ISTATE_SEND_REJECT: + spin_lock_bh(&cmd->istate_lock); + 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, + cmd->i_state, conn->cid); + goto err; + } + + if (atomic_read(&conn->check_immediate_queue)) + return 1; + + return 0; + +err: + return -1; +restart: + return -EAGAIN; +} + +static int iscsit_handle_response_queue(struct iscsi_conn *conn) +{ + struct iscsit_transport *t = conn->conn_transport; + struct iscsi_queue_req *qr; + struct iscsi_cmd *cmd; + u8 state; + int ret; + + while ((qr = iscsit_get_cmd_from_response_queue(conn))) { + cmd = qr->cmd; + state = qr->state; + kmem_cache_free(lio_qr_cache, qr); + + ret = t->iscsit_response_queue(conn, cmd, state); + if (ret == 1 || ret < 0) + return ret; + } + + return 0; +} + +int iscsi_target_tx_thread(void *arg) +{ + int ret = 0; + struct iscsi_conn *conn; + struct iscsi_thread_set *ts = 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; + + ret = 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); + + wait_event_interruptible(conn->queues_wq, + !iscsit_conn_all_queues_empty(conn) || + ts->status == ISCSI_THREAD_SET_RESET); + + if ((ts->status == ISCSI_THREAD_SET_RESET) || + signal_pending(current)) + goto transport_err; + +get_immediate: + ret = iscsit_handle_immediate_queue(conn); + if (ret < 0) + goto transport_err; + + ret = iscsit_handle_response_queue(conn); + if (ret == 1) + goto get_immediate; + else if (ret == -EAGAIN) + goto restart; + else if (ret < 0) + goto transport_err; + } + +transport_err: + iscsit_take_action_for_connection_exit(conn); + goto restart; +out: + return 0; +} + +static int iscsi_target_rx_opcode(struct iscsi_conn *conn, unsigned char *buf) +{ + struct iscsi_hdr *hdr = (struct iscsi_hdr *)buf; + struct iscsi_cmd *cmd; + int ret = 0; + + switch (hdr->opcode & ISCSI_OPCODE_MASK) { + case ISCSI_OP_SCSI_CMD: + cmd = iscsit_allocate_cmd(conn, TASK_INTERRUPTIBLE); + if (!cmd) + goto reject; + + ret = iscsit_handle_scsi_cmd(conn, cmd, buf); + break; + case ISCSI_OP_SCSI_DATA_OUT: + ret = iscsit_handle_data_out(conn, buf); + break; + case ISCSI_OP_NOOP_OUT: + cmd = NULL; + if (hdr->ttt == cpu_to_be32(0xFFFFFFFF)) { + cmd = iscsit_allocate_cmd(conn, TASK_INTERRUPTIBLE); + if (!cmd) + goto reject; + } + ret = iscsit_handle_nop_out(conn, cmd, buf); + break; + case ISCSI_OP_SCSI_TMFUNC: + cmd = iscsit_allocate_cmd(conn, TASK_INTERRUPTIBLE); + if (!cmd) + goto reject; + + ret = iscsit_handle_task_mgt_cmd(conn, cmd, buf); + break; + case ISCSI_OP_TEXT: + cmd = iscsit_allocate_cmd(conn, TASK_INTERRUPTIBLE); + if (!cmd) + goto reject; + + ret = iscsit_handle_text_cmd(conn, cmd, buf); + break; + case ISCSI_OP_LOGOUT: + cmd = iscsit_allocate_cmd(conn, TASK_INTERRUPTIBLE); + if (!cmd) + goto reject; + + ret = iscsit_handle_logout_cmd(conn, cmd, buf); + if (ret > 0) + wait_for_completion_timeout(&conn->conn_logout_comp, + SECONDS_FOR_LOGOUT_COMP * HZ); + break; + case ISCSI_OP_SNACK: + ret = iscsit_handle_snack(conn, buf); + break; + default: + pr_err("Got unknown iSCSI OpCode: 0x%02x\n", hdr->opcode); + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Cannot recover from unknown" + " opcode while ERL=0, closing iSCSI connection.\n"); + return -1; + } + if (!conn->conn_ops->OFMarker) { + pr_err("Unable to recover from unknown" + " opcode while OFMarker=No, closing iSCSI" + " connection.\n"); + return -1; + } + if (iscsit_recover_from_unknown_opcode(conn) < 0) { + pr_err("Unable to recover from unknown" + " opcode, closing iSCSI connection.\n"); + return -1; + } + break; + } + + return ret; +reject: + return iscsit_add_reject(conn, ISCSI_REASON_BOOKMARK_NO_RESOURCES, buf); +} + +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 = 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; + + if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) { + struct completion comp; + int rc; + + init_completion(&comp); + rc = wait_for_completion_interruptible(&comp); + if (rc < 0) + goto transport_err; + + 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; + } + + 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); + atomic_long_inc(&conn->sess->conn_digest_errors); + } 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(conn, ISCSI_REASON_PROTOCOL_ERROR, + buffer); + goto transport_err; + } + + ret = iscsi_target_rx_opcode(conn, buffer); + if (ret < 0) + goto transport_err; + } + +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; + /* + * 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_conn_node) { + + list_del_init(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); + + iscsit_increment_maxcmdsn(cmd, sess); + + iscsit_free_cmd(cmd, true); + + 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_conn_node) { + 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); + + if (conn->conn_transport->iscsit_wait_conn) + conn->conn_transport->iscsit_wait_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); + } + iscsit_free_queue_reqs_for_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) + sock_release(conn->sock); + + if (conn->conn_transport->iscsit_free_conn) + conn->conn_transport->iscsit_free_conn(conn); + + iscsit_put_transport(conn->conn_transport); + + 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); + target_put_session(sess->se_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 = sess->tpg; + 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); + target_put_session(sess->se_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. + */ +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; +} +EXPORT_SYMBOL(iscsit_logout_post_handler); + +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); + + target_put_session(sess->se_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 00000000000..e936d56fb52 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target.h @@ -0,0 +1,50 @@ +#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 void iscsit_login_kref_put(struct kref *); +extern int iscsit_deaccess_np(struct iscsi_np *, struct iscsi_portal_group *, + struct iscsi_tpg_np *); +extern bool iscsit_check_np_match(struct __kernel_sockaddr_storage *, + struct iscsi_np *, int); +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 *, bool); +extern int iscsit_del_np(struct iscsi_np *); +extern int iscsit_reject_cmd(struct iscsi_cmd *cmd, u8, unsigned char *); +extern void iscsit_set_unsoliticed_dataout(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_build_r2ts_for_cmd(struct iscsi_conn *, struct iscsi_cmd *, bool recovery); +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_qr_cache; +extern struct kmem_cache *lio_r2t_cache; + +extern struct idr sess_idr; +extern struct mutex auth_id_lock; +extern spinlock_t sess_idr_lock; + + +#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 00000000000..ab4915c0d93 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_auth.c @@ -0,0 +1,502 @@ +/******************************************************************************* + * This file houses the main functions for the iSCSI CHAP support + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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/kernel.h> +#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 int chap_string_to_hex(unsigned char *dst, unsigned char *src, int len) +{ + int j = DIV_ROUND_UP(len, 2), rc; + + rc = hex2bin(dst, src, j); + if (rc < 0) + pr_debug("CHAP string contains non hex digit symbols\n"); + + 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_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 = conn->auth_protocol; + + memset(challenge_asciihex, 0, CHAP_CHALLENGE_LENGTH * 2 + 1); + + get_random_bytes(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 int chap_check_algorithm(const char *a_str) +{ + char *tmp, *orig, *token; + + tmp = kstrdup(a_str, GFP_KERNEL); + if (!tmp) { + pr_err("Memory allocation failed for CHAP_A temporary buffer\n"); + return CHAP_DIGEST_UNKNOWN; + } + orig = tmp; + + token = strsep(&tmp, "="); + if (!token) + goto out; + + if (strcmp(token, "CHAP_A")) { + pr_err("Unable to locate CHAP_A key\n"); + goto out; + } + while (token) { + token = strsep(&tmp, ","); + if (!token) + goto out; + + if (!strncmp(token, "5", 1)) { + pr_debug("Selected MD5 Algorithm\n"); + kfree(orig); + return CHAP_DIGEST_MD5; + } + } +out: + kfree(orig); + return CHAP_DIGEST_UNKNOWN; +} + +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) +{ + int ret; + 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 = conn->auth_protocol; + ret = chap_check_algorithm(a_str); + switch (ret) { + case CHAP_DIGEST_MD5: + 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); + break; + case CHAP_DIGEST_UNKNOWN: + default: + pr_err("Unsupported CHAP_A value\n"); + return NULL; + } + + /* + * Set Identifier. + */ + chap->id = conn->tpg->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) +{ + unsigned long id; + unsigned char id_as_uchar; + unsigned char 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]; + size_t compare_len; + struct iscsi_chap *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; + } + + /* Include the terminating NULL in the compare */ + compare_len = strlen(auth->userid) + 1; + if (strncmp(chap_n, auth->userid, compare_len) != 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, &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, &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, 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) + ret = kstrtoul(&identifier[2], 0, &id); + else + ret = kstrtoul(identifier, 0, &id); + + if (ret < 0) { + pr_err("kstrtoul() failed for CHAP identifier: %d\n", ret); + goto out; + } + if (id > 255) { + pr_err("chap identifier: %lu greater than 255\n", id); + goto out; + } + /* + * RFC 1994 says Identifier is no more than octet (8 bits). + */ + pr_debug("[server] Got CHAP_I=%lu\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; + } + if (challenge_len > 1024) { + pr_err("CHAP_C exceeds maximum binary size of 1024 bytes\n"); + goto out; + } + /* + * During mutual authentication, the CHAP_C generated by the + * initiator must not match the original CHAP_C generated by + * the target. + */ + if (!memcmp(challenge_binhex, chap->challenge, CHAP_CHALLENGE_LENGTH)) { + pr_err("initiator CHAP_C matches target CHAP_C, failing" + " login attempt\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; + } + + /* To handle both endiannesses */ + id_as_uchar = id; + sg_init_one(&sg, &id_as_uchar, 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, 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, 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 = 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 = 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 00000000000..d22f7b96a06 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_auth.h @@ -0,0 +1,32 @@ +#ifndef _ISCSI_CHAP_H_ +#define _ISCSI_CHAP_H_ + +#define CHAP_DIGEST_UNKNOWN 0 +#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 00000000000..ae03f3e5de1 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_configfs.c @@ -0,0 +1,2074 @@ +/******************************************************************************* + * This file contains the configfs implementation for iSCSI Target mode + * from the LIO-Target Project. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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 <linux/ctype.h> +#include <linux/export.h> +#include <linux/inet.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_fabric_configfs.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> +#include <target/iscsi/iscsi_transport.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); +}; + +/* 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; + u32 op; + int ret; + + ret = kstrtou32(page, 0, &op); + if (ret) + return ret; + 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 ssize_t lio_target_np_show_iser( + 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_iser; + ssize_t rb; + + tpg_np_iser = iscsit_tpg_locate_child_np(tpg_np, ISCSI_INFINIBAND); + if (tpg_np_iser) + rb = sprintf(page, "1\n"); + else + rb = sprintf(page, "0\n"); + + return rb; +} + +static ssize_t lio_target_np_store_iser( + 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_iser = NULL; + char *endptr; + u32 op; + int rc = 0; + + 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) { + rc = request_module("ib_isert"); + if (rc != 0) { + pr_warn("Unable to request_module for ib_isert\n"); + rc = 0; + } + + tpg_np_iser = iscsit_tpg_add_network_portal(tpg, &np->np_sockaddr, + np->np_ip, tpg_np, ISCSI_INFINIBAND); + if (IS_ERR(tpg_np_iser)) { + rc = PTR_ERR(tpg_np_iser); + goto out; + } + } else { + tpg_np_iser = iscsit_tpg_locate_child_np(tpg_np, ISCSI_INFINIBAND); + if (tpg_np_iser) { + rc = iscsit_tpg_del_network_portal(tpg, tpg_np_iser); + if (rc < 0) + goto out; + } + } + + iscsit_put_tpg(tpg); + return count; +out: + iscsit_put_tpg(tpg); + return rc; +} + +TF_NP_BASE_ATTR(lio_target, iser, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *lio_target_portal_attrs[] = { + &lio_target_np_sctp.attr, + &lio_target_np_iser.attr, + NULL, +}; + +/* Stop items for lio_target_portal_cit */ + +/* Start items for lio_target_np_cit */ + +#define MAX_PORTAL_LEN 256 + +static 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 + 1, "%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 = kstrtoul(port_str, 0, &port); + if (ret < 0) { + pr_err("kstrtoul() 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 = kstrtoul(port_str, 0, &port); + if (ret < 0) { + pr_err("kstrtoul() 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 attributes 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_CAST(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", nacl->node_attrib.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); \ + u32 val; \ + int ret; \ + \ + ret = kstrtou32(page, 0, &val); \ + if (ret) \ + return ret; \ + 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; \ + if (count >= sizeof(auth->name)) \ + return -EINVAL; \ + snprintf(auth->name, sizeof(auth->name), "%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; + u32 cmdsn_depth = 0; + int ret; + + ret = kstrtou32(page, 0, &cmdsn_depth); + if (ret) + return ret; + 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 ssize_t lio_target_nacl_show_tag( + struct se_node_acl *se_nacl, + char *page) +{ + return snprintf(page, PAGE_SIZE, "%s", se_nacl->acl_tag); +} + +static ssize_t lio_target_nacl_store_tag( + struct se_node_acl *se_nacl, + const char *page, + size_t count) +{ + int ret; + + ret = core_tpg_set_initiator_node_tag(se_nacl->se_tpg, se_nacl, page); + + if (ret < 0) + return ret; + return count; +} + +TF_NACL_BASE_ATTR(lio_target, tag, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *lio_target_initiator_attrs[] = { + &lio_target_nacl_info.attr, + &lio_target_nacl_cmdsn_depth.attr, + &lio_target_nacl_tag.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); + + cmdsn_depth = tpg->tpg_attrib.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; + + acl = container_of(se_nacl, struct iscsi_node_acl, se_node_acl); + stats_cg = &se_nacl->acl_fabric_stat_group; + + stats_cg->default_groups = kmalloc(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] = &acl->node_stat_grps.iscsi_sess_stats_group; + stats_cg->default_groups[1] = NULL; + config_group_init_type_name(&acl->node_stat_grps.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", tpg->tpg_attrib.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); \ + u32 val; \ + int ret; \ + \ + if (iscsit_get_tpg(tpg) < 0) \ + return -EINVAL; \ + \ + ret = kstrtou32(page, 0, &val); \ + if (ret) \ + goto out; \ + 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); +/* + * Define iscsi_tpg_attrib_s_demo_mode_discovery, + */ +DEF_TPG_ATTRIB(demo_mode_discovery); +TPG_ATTR(demo_mode_discovery, S_IRUGO | S_IWUSR); +/* + * Define iscsi_tpg_attrib_s_default_erl + */ +DEF_TPG_ATTRIB(default_erl); +TPG_ATTR(default_erl, S_IRUGO | S_IWUSR); +/* + * Define iscsi_tpg_attrib_s_t10_pi + */ +DEF_TPG_ATTRIB(t10_pi); +TPG_ATTR(t10_pi, 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, + &iscsi_tpg_attrib_demo_mode_discovery.attr, + &iscsi_tpg_attrib_default_erl.attr, + &iscsi_tpg_attrib_t10_pi.attr, + NULL, +}; + +/* End items for lio_target_tpg_attrib_cit */ + +/* Start items for lio_target_tpg_auth_cit */ + +#define __DEF_TPG_AUTH_STR(prefix, name, flags) \ +static ssize_t __iscsi_##prefix##_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_node_auth *auth = &tpg->tpg_demo_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 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); \ + struct iscsi_node_auth *auth = &tpg->tpg_demo_auth; \ + \ + if (!capable(CAP_SYS_ADMIN)) \ + return -EPERM; \ + \ + snprintf(auth->name, sizeof(auth->name), "%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_TPG_AUTH_INT(prefix, name) \ +static ssize_t __iscsi_##prefix##_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_node_auth *auth = &tpg->tpg_demo_auth; \ + \ + if (!capable(CAP_SYS_ADMIN)) \ + return -EPERM; \ + \ + return snprintf(page, PAGE_SIZE, "%d\n", auth->name); \ +} + +#define DEF_TPG_AUTH_STR(name, flags) \ + __DEF_TPG_AUTH_STR(tpg_auth, name, flags) \ +static ssize_t iscsi_tpg_auth_show_##name( \ + struct se_portal_group *se_tpg, \ + char *page) \ +{ \ + return __iscsi_tpg_auth_show_##name(se_tpg, page); \ +} \ + \ +static ssize_t iscsi_tpg_auth_store_##name( \ + struct se_portal_group *se_tpg, \ + const char *page, \ + size_t count) \ +{ \ + return __iscsi_tpg_auth_store_##name(se_tpg, page, count); \ +} + +#define DEF_TPG_AUTH_INT(name) \ + __DEF_TPG_AUTH_INT(tpg_auth, name) \ +static ssize_t iscsi_tpg_auth_show_##name( \ + struct se_portal_group *se_tpg, \ + char *page) \ +{ \ + return __iscsi_tpg_auth_show_##name(se_tpg, page); \ +} + +#define TPG_AUTH_ATTR(_name, _mode) TF_TPG_AUTH_ATTR(iscsi, _name, _mode); +#define TPG_AUTH_ATTR_RO(_name) TF_TPG_AUTH_ATTR_RO(iscsi, _name); + +/* + * * One-way authentication userid + * */ +DEF_TPG_AUTH_STR(userid, NAF_USERID_SET); +TPG_AUTH_ATTR(userid, S_IRUGO | S_IWUSR); +/* + * * One-way authentication password + * */ +DEF_TPG_AUTH_STR(password, NAF_PASSWORD_SET); +TPG_AUTH_ATTR(password, S_IRUGO | S_IWUSR); +/* + * * Enforce mutual authentication + * */ +DEF_TPG_AUTH_INT(authenticate_target); +TPG_AUTH_ATTR_RO(authenticate_target); +/* + * * Mutual authentication userid + * */ +DEF_TPG_AUTH_STR(userid_mutual, NAF_USERID_IN_SET); +TPG_AUTH_ATTR(userid_mutual, S_IRUGO | S_IWUSR); +/* + * * Mutual authentication password + * */ +DEF_TPG_AUTH_STR(password_mutual, NAF_PASSWORD_IN_SET); +TPG_AUTH_ATTR(password_mutual, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *lio_target_tpg_auth_attrs[] = { + &iscsi_tpg_auth_userid.attr, + &iscsi_tpg_auth_password.attr, + &iscsi_tpg_auth_authenticate_target.attr, + &iscsi_tpg_auth_userid_mutual.attr, + &iscsi_tpg_auth_password_mutual.attr, + NULL, +}; + +/* End items for lio_target_tpg_auth_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, len; \ + \ + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); \ + if (!buf) \ + return -ENOMEM; \ + len = snprintf(buf, PAGE_SIZE, "%s=%s", __stringify(name), page); \ + if (isspace(buf[len-1])) \ + buf[len-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(MaxXmitDataSegmentLength); +TPG_PARAM_ATTR(MaxXmitDataSegmentLength, 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_MaxXmitDataSegmentLength.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); + u32 op; + int ret; + + ret = kstrtou32(page, 0, &op); + if (ret) + return ret; + 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 */ + +static 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; + int ret; + u16 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_" */ + ret = kstrtou16(tpgt_str, 0, &tpgt); + if (ret) + return NULL; + + 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, 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; +} + +static 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, "Datera Inc. 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, +}; + +static 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_CAST(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 = kmalloc(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] = &tiqn->tiqn_stat_grps.iscsi_instance_group; + stats_cg->default_groups[1] = &tiqn->tiqn_stat_grps.iscsi_sess_err_group; + stats_cg->default_groups[2] = &tiqn->tiqn_stat_grps.iscsi_tgt_attr_group; + stats_cg->default_groups[3] = &tiqn->tiqn_stat_grps.iscsi_login_stats_group; + stats_cg->default_groups[4] = &tiqn->tiqn_stat_grps.iscsi_logout_stats_group; + stats_cg->default_groups[5] = NULL; + config_group_init_type_name(&tiqn->tiqn_stat_grps.iscsi_instance_group, + "iscsi_instance", &iscsi_stat_instance_cit); + config_group_init_type_name(&tiqn->tiqn_stat_grps.iscsi_sess_err_group, + "iscsi_sess_err", &iscsi_stat_sess_err_cit); + config_group_init_type_name(&tiqn->tiqn_stat_grps.iscsi_tgt_attr_group, + "iscsi_tgt_attr", &iscsi_stat_tgt_attr_cit); + config_group_init_type_name(&tiqn->tiqn_stat_grps.iscsi_login_stats_group, + "iscsi_login_stats", &iscsi_stat_login_cit); + config_group_init_type_name(&tiqn->tiqn_stat_grps.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; +} + +static 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; + u32 op; + int err; + + err = kstrtou32(page, 0, &op); + if (err) + return -EINVAL; + 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); + + /* only used for printks or comparism with ->ref_task_tag */ + return (__force u32)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 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; + cmd->conn->conn_transport->iscsit_queue_data_in(cmd->conn, cmd); + + 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); + struct iscsi_conn *conn = cmd->conn; + + if (!cmd->immediate_data && !cmd->unsolicited_data) + return conn->conn_transport->iscsit_get_dataout(conn, cmd, false); + + 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; + + if (cmd->se_cmd.scsi_status || cmd->sense_reason) { + iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state); + return 0; + } + cmd->conn->conn_transport->iscsit_queue_status(cmd->conn, cmd); + + return 0; +} + +static void 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); +} + +static void lio_aborted_task(struct se_cmd *se_cmd) +{ + struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd); + + cmd->conn->conn_transport->iscsit_aborted_task(cmd->conn, cmd); +} + +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 tpg->tpg_attrib.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 tpg->tpg_attrib.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 tpg->tpg_attrib.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 tpg->tpg_attrib.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 tpg->tpg_attrib.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_stop_time2retain_timer(sess); + iscsit_stop_session(sess, 1, 1); + + 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_close_session(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); + struct se_portal_group *se_tpg = se_acl->se_tpg; + struct iscsi_portal_group *tpg = container_of(se_tpg, + struct iscsi_portal_group, tpg_se_tpg); + + acl->node_attrib.nacl = acl; + iscsit_set_default_node_attribues(acl, tpg); +} + +static int lio_check_stop_free(struct se_cmd *se_cmd) +{ + return target_put_sess_cmd(se_cmd->se_sess, se_cmd); +} + +static void lio_release_cmd(struct se_cmd *se_cmd) +{ + struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd); + + pr_debug("Entering lio_release_cmd for se_cmd: %p\n", 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.check_stop_free = &lio_check_stop_free, + 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.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.aborted_task = &lio_aborted_task; + /* + * 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 + */ + fabric->tf_cit_tmpl.tfc_discovery_cit.ct_attrs = lio_target_discovery_auth_attrs; + fabric->tf_cit_tmpl.tfc_wwn_cit.ct_attrs = lio_target_wwn_attrs; + fabric->tf_cit_tmpl.tfc_tpg_base_cit.ct_attrs = lio_target_tpg_attrs; + fabric->tf_cit_tmpl.tfc_tpg_attrib_cit.ct_attrs = lio_target_tpg_attrib_attrs; + fabric->tf_cit_tmpl.tfc_tpg_auth_cit.ct_attrs = lio_target_tpg_auth_attrs; + fabric->tf_cit_tmpl.tfc_tpg_param_cit.ct_attrs = lio_target_tpg_param_attrs; + fabric->tf_cit_tmpl.tfc_tpg_np_base_cit.ct_attrs = lio_target_portal_attrs; + fabric->tf_cit_tmpl.tfc_tpg_nacl_base_cit.ct_attrs = lio_target_initiator_attrs; + fabric->tf_cit_tmpl.tfc_tpg_nacl_attrib_cit.ct_attrs = lio_target_nacl_attrib_attrs; + fabric->tf_cit_tmpl.tfc_tpg_nacl_auth_cit.ct_attrs = lio_target_nacl_auth_attrs; + fabric->tf_cit_tmpl.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 00000000000..8cd5a63c4ed --- /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 00000000000..302eb3b7871 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_core.h @@ -0,0 +1,884 @@ +#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" +#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" +#define ISCSIT_MIN_TAGS 16 +#define ISCSIT_EXTRA_TAGS 8 +#define ISCSIT_TCP_BACKLOG 256 + +/* 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 15 +#define NA_NOPIN_TIMEOUT_MAX 60 +#define NA_NOPIN_TIMEOUT_MIN 3 +#define NA_NOPIN_RESPONSE_TIMEOUT 30 +#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 + +/* 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 64 +#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_DEMO_MODE_DISCOVERY 1 +#define TA_DEFAULT_ERL 0 +#define TA_CACHE_CORE_NPS 0 +/* T10 protection information disabled by default */ +#define TA_DEFAULT_T10_PI 0 + +#define ISCSI_IOV_DATA_BUFFER 5 + +enum iscsit_transport_type { + 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, + IFC_SENDTARGETS_ALL = 0x00000100, + IFC_SENDTARGETS_SINGLE = 0x00000200, +}; + +/* 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, + CMDSN_MAXCMDSN_OVERRUN = 3, +}; + +/* 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, +}; + +/* 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] */ + u32 MaxXmitDataSegmentLength; /* [512..2**24-1] */ + u8 OFMarker; /* [0,1] == [No,Yes] */ + u8 IFMarker; /* [0,1] == [No,Yes] */ + u32 OFMarkInt; /* [1..65535] */ + u32 IFMarkInt; /* [1..65535] */ + /* + * iSER specific connection parameters + */ + u32 InitiatorRecvDataSegmentLength; /* [512..2**24-1] */ + u32 TargetRecvDataSegmentLength; /* [512..2**24-1] */ +}; + +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]*/ + /* + * iSER specific session parameters + */ + u8 RDMAExtensions; /* [0,1] == [No,Yes] */ +}; + +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 { + bool iser; + 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_sn; + u32 next_burst_len; + u32 read_data_done; + u32 seq_send_order; + struct list_head cmd_datain_node; +} ____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; + /* Reject reason code */ + u8 reject_reason; + /* 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 */ + itt_t 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; + /* 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; + /* 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; + /* Used by SendTargets=[iqn.,eui.] discovery */ + void *text_in_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; + 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; + /* 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_conn_node; + /* 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]; + + u32 padding; + u8 pad_bytes[4]; + + struct scatterlist *first_data_sg; + u32 first_data_sg_off; + u32 kmapped_nents; + sense_reason_t sense_reason; +} ____cacheline_aligned; + +struct iscsi_tmr_req { + bool task_reassign:1; + u32 exp_data_sn; + struct iscsi_cmd *ref_cmd; + struct iscsi_conn_recovery *conn_recovery; + struct se_tmr_req *se_tmr_req; +}; + +struct iscsi_conn { + wait_queue_head_t queues_wq; + /* 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; + /* 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; + u16 local_port; + int net_size; + int login_family; + u32 auth_id; + u32 conn_flags; + /* Used for iscsi_tx_login_rsp() */ + itt_t 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; +#define IPV6_ADDRESS_SPACE 48 + unsigned char login_ip[IPV6_ADDRESS_SPACE]; + unsigned char local_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_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; + void (*orig_data_ready)(struct sock *); + void (*orig_state_change)(struct sock *); +#define LOGIN_FLAGS_READ_ACTIVE 1 +#define LOGIN_FLAGS_CLOSED 2 +#define LOGIN_FLAGS_READY 4 + unsigned long login_flags; + struct delayed_work login_work; + struct delayed_work login_cleanup_work; + struct iscsi_login *login; + struct timer_list nopin_timer; + struct timer_list nopin_response_timer; + struct timer_list transport_timer; + struct task_struct *login_kworker; + /* 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; + unsigned int conn_rx_reset_cpumask:1; + unsigned 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_login *conn_login; + struct iscsit_transport *conn_transport; + struct iscsi_param_list *param_list; + /* Used for per connection auth state machine */ + void *auth_protocol; + void *context; + struct iscsi_login_thread_s *login_thread; + struct iscsi_portal_group *tpg; + struct iscsi_tpg_np *tpg_np; + /* 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; + u32 maxxmitdatasegmentlength; + 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 */ + itt_t 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; + atomic_long_t cmd_pdus; + atomic_long_t rsp_pdus; + atomic_long_t tx_data_octets; + atomic_long_t rx_data_octets; + atomic_long_t conn_digest_errors; + atomic_long_t conn_timeout_errors; + u64 creation_time; + /* 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; + /* 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; + u8 login_complete; + u8 login_failed; + bool zero_tsih; + char isid[6]; + u32 cmd_sn; + itt_t init_task_tag; + u32 initial_exp_statsn; + u32 rsp_length; + u16 cid; + u16 tsih; + char req[ISCSI_HDR_LEN]; + char rsp[ISCSI_HDR_LEN]; + char *req_buf; + char *rsp_buf; + struct iscsi_conn *conn; + struct iscsi_np *np; +} ____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; +}; + +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; + u32 demo_mode_discovery; + u32 default_erl; + u8 t10_pi; + 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; + bool enabled; + 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; + void *np_context; + struct iscsit_transport *np_transport; + struct list_head np_list; + struct iscsi_tpg_np *tpg_np; +} ____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 completion tpg_np_comp; + struct kref tpg_np_kref; +}; + +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 semaphore np_login_sem; + struct iscsi_tpg_attrib tpg_attrib; + struct iscsi_node_auth tpg_demo_auth; + /* 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; + +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; + +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 00000000000..e93d5a7a3f8 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_datain_values.c @@ -0,0 +1,526 @@ +/******************************************************************************* + * This file contains the iSCSI Target DataIN value generation functions. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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->cmd_datain_node); + + 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->cmd_datain_node, &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->cmd_datain_node); + 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, cmd_datain_node) { + list_del(&dr->cmd_datain_node); + kmem_cache_free(lio_dr_cache, dr); + } + spin_unlock(&cmd->datain_lock); +} + +struct iscsi_datain_req *iscsit_get_datain_req(struct iscsi_cmd *cmd) +{ + if (list_empty(&cmd->datain_list)) { + pr_err("cmd->datain_list is empty for ITT:" + " 0x%08x\n", cmd->init_task_tag); + return NULL; + } + + return list_first_entry(&cmd->datain_list, struct iscsi_datain_req, + cmd_datain_node); +} + +/* + * 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->se_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->se_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->se_cmd.data_length) { + datain->length = (cmd->se_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->se_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->se_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->se_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->se_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->se_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 00000000000..646429ac5a0 --- /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 00000000000..7087c736daa --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_device.c @@ -0,0 +1,66 @@ +/******************************************************************************* + * This file contains the iSCSI Virtual Device and Disk Transport + * agnostic related functions. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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_fabric.h> + +#include "iscsi_target_core.h" +#include "iscsi_target_device.h" +#include "iscsi_target_tpg.h" +#include "iscsi_target_util.h" + +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); +} +EXPORT_SYMBOL(iscsit_increment_maxcmdsn); diff --git a/drivers/target/iscsi/iscsi_target_device.h b/drivers/target/iscsi/iscsi_target_device.h new file mode 100644 index 00000000000..a0e2df9e809 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_device.h @@ -0,0 +1,7 @@ +#ifndef ISCSI_TARGET_DEVICE_H +#define ISCSI_TARGET_DEVICE_H + +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 00000000000..0d1e6ee3e99 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl0.c @@ -0,0 +1,1013 @@ +/****************************************************************************** + * This file contains error recovery level zero functions used by + * the iSCSI Target driver. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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_fabric.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->se_cmd.data_length > + conn->sess->sess_ops->FirstBurstLength) ? + conn->sess->sess_ops->FirstBurstLength : cmd->se_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->se_cmd.data_length > + conn->sess->sess_ops->MaxBurstLength) ? + (cmd->write_data_done + + conn->sess->sess_ops->MaxBurstLength) : cmd->se_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->se_cmd.data_length) ? cmd->se_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 != be32_to_cpu(hdr->offset)) + goto dump; + + cmd->cmd_flags &= ~ICF_WITHIN_COMMAND_RECOVERY; + } else { + struct iscsi_seq *seq; + + seq = iscsit_get_seq_holder(cmd, be32_to_cpu(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 != be32_to_cpu(hdr->offset) || + seq->data_sn != be32_to_cpu(hdr->datasn))) + goto dump; + } else { + if (seq->status == + DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY && + seq->data_sn != be32_to_cpu(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 ((be32_to_cpu(hdr->offset) < cmd->seq_start_offset) || + ((be32_to_cpu(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, + be32_to_cpu(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->se_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->se_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->se_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->se_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 ((be32_to_cpu(hdr->offset) < cmd->seq_start_offset) || + ((be32_to_cpu(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, + be32_to_cpu(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, be32_to_cpu(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->se_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->se_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 (be32_to_cpu(hdr->datasn) > data_sn) { + pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x" + " higher than expected 0x%08x.\n", cmd->init_task_tag, + be32_to_cpu(hdr->datasn), data_sn); + recovery = 1; + goto recover; + } else if (be32_to_cpu(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, be32_to_cpu(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 + * occurred 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 occurred and fail the connection. + */ + if (conn->sess->sess_ops->DataSequenceInOrder) { + if (be32_to_cpu(hdr->offset) != cmd->write_data_done) { + pr_err("Command ITT: 0x%08x, received offset" + " %u different than expected %u.\n", cmd->init_task_tag, + be32_to_cpu(hdr->offset), cmd->write_data_done); + recovery = 1; + goto recover; + } + } else { + struct iscsi_seq *seq = cmd->seq_ptr; + + if (be32_to_cpu(hdr->offset) > seq->offset) { + pr_err("Command ITT: 0x%08x, received offset" + " %u greater than expected %u.\n", cmd->init_task_tag, + be32_to_cpu(hdr->offset), seq->offset); + recovery = 1; + goto recover; + } else if (be32_to_cpu(hdr->offset) < seq->offset) { + pr_err("Command ITT: 0x%08x, received offset" + " %u less than expected %u, discarding payload.\n", + cmd->init_task_tag, be32_to_cpu(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, + be32_to_cpu(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, be32_to_cpu(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, + be32_to_cpu(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, be32_to_cpu(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, + be32_to_cpu(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, + be32_to_cpu(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, be32_to_cpu(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, + be32_to_cpu(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, be32_to_cpu(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; + + if (cmd->write_data_done == cmd->se_cmd.data_length) + return DATAOUT_SEND_TO_TRANSPORT; + else if (send_r2t) + return DATAOUT_SEND_R2T; + else + return 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, be32_to_cpu(hdr->offset), + payload_length); +} + +/* + * Called from iscsit_handle_data_out() before DataOUT Payload is received + * and CRC computed. + */ +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_reject_cmd(cmd, ISCSI_REASON_DATA_DIGEST_ERROR, + buf); + return DATAOUT_CANNOT_RECOVER; + } + + iscsit_reject_cmd(cmd, ISCSI_REASON_DATA_DIGEST_ERROR, buf); + 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 = sess->tpg; + 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++; + atomic_long_inc(&sess->conn_timeout_errors); + spin_unlock(&tiqn->sess_err_stats.lock); + } + } + + spin_unlock_bh(&se_tpg->session_lock); + target_put_session(sess->se_sess); +} + +void iscsit_start_time2retain_handler(struct iscsi_session *sess) +{ + int tpg_active; + /* + * Only start Time2Retain timer when the associated TPG is still in + * an ACTIVE (eg: not disabled or shutdown) state. + */ + spin_lock(&sess->tpg->tpg_state_lock); + tpg_active = (sess->tpg->tpg_state == TPG_STATE_ACTIVE); + spin_unlock(&sess->tpg->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 + */ +int iscsit_stop_time2retain_timer(struct iscsi_session *sess) +{ + struct iscsi_portal_group *tpg = sess->tpg; + 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(&se_tpg->session_lock); + + del_timer_sync(&sess->time2retain_timer); + + spin_lock(&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); +} +EXPORT_SYMBOL(iscsit_cause_connection_reinstatement); + +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); + } +} + +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 00000000000..21acc9a0637 --- /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 00000000000..cda4d80cfae --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl1.c @@ -0,0 +1,1294 @@ +/******************************************************************************* + * This file contains error recovery level one used by the iSCSI Target driver. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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_fabric.h> +#include <target/iscsi/iscsi_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; + + if (conn->sess->sess_ops->RDMAExtensions) + return 0; + + 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_reject_cmd(cmd, + ISCSI_REASON_PROTOCOL_ERROR, buf); + } + + 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_reject_cmd(cmd, + ISCSI_REASON_BOOKMARK_INVALID, buf); + } + 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) { + pr_err("Ignoring non-sent sequence 0x%08x ->" + " 0x%08x\n\n", seq->first_datasn, + seq->last_datasn); + 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)) { + pr_err("Pre BegRun sequence 0x%08x ->" + " 0x%08x\n", seq->first_datasn, + seq->last_datasn); + + 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)) { + pr_err("Found sequence begrun: 0x%08x in" + " 0x%08x -> 0x%08x\n", begrun, + seq->first_datasn, seq->last_datasn); + + 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)) { + pr_err("Post BegRun sequence 0x%08x -> 0x%08x\n", + seq->first_datasn, seq->last_datasn); + + 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 (!(se_cmd->transport_state & CMD_T_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_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, buf); + } + + /* + * 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_reject_cmd(cmd, ISCSI_REASON_BOOKMARK_INVALID, + buf); + } + + dr = iscsit_allocate_datain_req(); + if (!dr) + return iscsit_reject_cmd(cmd, ISCSI_REASON_BOOKMARK_NO_RESOURCES, + buf); + + 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, + itt_t 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, + itt_t init_task_tag, + u32 targ_xfer_tag, + u32 begrun, + u32 runlength) +{ + struct iscsi_cmd *cmd = NULL; + u32 last_statsn; + int found_cmd; + + if (!begrun) { + begrun = conn->exp_statsn; + } else 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_conn_node) { + 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 (iscsi_sna_lt(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) { + if (iscsi_sna_lt(ooo_tmp->cmdsn, ooo_cmdsn->cmdsn)) + continue; + + /* Insert before this entry */ + list_add(&ooo_cmdsn->ooo_list, + ooo_tmp->ooo_list.prev); + 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; + struct iscsi_conn *conn = cmd->conn; + 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. + */ + if (cmd->sense_reason) { + if (cmd->sense_reason == TCM_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, + cmd->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); + target_execute_cmd(&cmd->se_cmd); + return 0; + } + 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); + conn->conn_transport->iscsit_get_dataout(conn, cmd, false); + } + 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_unsoliticed_dataout(cmd); + } + 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 (cmd->se_cmd.se_tmr_req->response) { + 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 -ENOMEM; + + 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 -ENOMEM; + } + + return 0; +} + +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->se_cmd.data_length) ? + cmd->se_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->se_cmd.data_length) + pdu_length = (cmd->se_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 = 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 = 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); +} +EXPORT_SYMBOL(iscsit_stop_dataout_timer); diff --git a/drivers/target/iscsi/iscsi_target_erl1.h b/drivers/target/iscsi/iscsi_target_erl1.h new file mode 100644 index 00000000000..2a3ebf118a3 --- /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 *, + itt_t, u32, u32, u32); +extern int iscsit_handle_status_snack(struct iscsi_conn *, itt_t, 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 00000000000..4ca8fd2a70d --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_erl2.c @@ -0,0 +1,436 @@ +/******************************************************************************* + * This file contains error recovery level two functions used by + * the iSCSI Target driver. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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_fabric.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, + __be32 exp_data_sn) +{ + u32 data_sn = 0; + struct iscsi_conn *conn = cmd->conn; + + cmd->next_burst_len = 0; + cmd->read_data_done = 0; + + while (be32_to_cpu(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_conn_node) { + + list_del_init(&cmd->i_conn_node); + cmd->conn = NULL; + spin_unlock(&cr->conn_recovery_cmd_lock); + iscsit_free_cmd(cmd, true); + 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_conn_node) { + + list_del_init(&cmd->i_conn_node); + cmd->conn = NULL; + spin_unlock(&cr->conn_recovery_cmd_lock); + iscsit_free_cmd(cmd, true); + 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; +} + +static void 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); +} + +/* + * 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_init(&cmd->i_conn_node); + 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_conn_node) { + + 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); + iscsit_free_cmd(cmd, true); + 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_conn_node) { + if (!(cmd->cmd_flags & ICF_OOO_CMDSN)) + continue; + + list_del_init(&cmd->i_conn_node); + + spin_unlock_bh(&conn->cmd_lock); + iscsit_free_cmd(cmd, true); + 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_init(&cmd->i_conn_node); 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_conn_node) { + + 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_init(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); + iscsit_free_cmd(cmd, true); + 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 && + iscsi_sna_gte(cmd->cmd_sn, conn->sess->exp_cmd_sn)) { + list_del_init(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); + iscsit_free_cmd(cmd, true); + 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_init(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); + + iscsit_free_all_datain_reqs(cmd); + + transport_wait_for_tasks(&cmd->se_cmd); + /* + * Add the struct iscsi_cmd to the connection recovery cmd list + */ + spin_lock(&cr->conn_recovery_cmd_lock); + list_add_tail(&cmd->i_conn_node, &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->maxxmitdatasegmentlength = conn->conn_ops->MaxXmitDataSegmentLength; + 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 00000000000..63f2501f3fe --- /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 *, __be32); +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 00000000000..5e71ac60941 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_login.c @@ -0,0 +1,1457 @@ +/******************************************************************************* + * This file contains the login functions used by the iSCSI Target driver. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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/idr.h> +#include <scsi/iscsi_proto.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.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" + +#include <target/iscsi/iscsi_transport.h> + +static struct iscsi_login *iscsi_login_init_conn(struct iscsi_conn *conn) +{ + 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"); + return NULL; + } + conn->login = login; + login->conn = conn; + login->first_request = 1; + + login->req_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL); + if (!login->req_buf) { + pr_err("Unable to allocate memory for response buffer.\n"); + goto out_login; + } + + login->rsp_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL); + if (!login->rsp_buf) { + pr_err("Unable to allocate memory for request buffer.\n"); + goto out_req_buf; + } + + 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 out_rsp_buf; + } + + init_waitqueue_head(&conn->queues_wq); + 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"); + goto out_conn_ops; + } + conn->conn_login = login; + + return login; + +out_conn_ops: + kfree(conn->conn_ops); +out_rsp_buf: + kfree(login->rsp_buf); +out_req_buf: + kfree(login->req_buf); +out_login: + kfree(login); + return NULL; +} + +/* + * 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); + sessiontype_param = iscsi_find_param_from_key( + SESSIONTYPE, conn->param_list); + if (!initiatorname_param || !sessiontype_param) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_MISSING_FIELDS); + 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 = 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(sess_p->isid, conn->sess->isid, 6) && + (!strcmp(sess_p->sess_ops->InitiatorName, + 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); + target_put_session(sess->se_sess); + return 0; + } + spin_unlock_bh(&sess->conn_lock); + + iscsit_stop_session(sess, 1, 1); + iscsit_dec_session_usage_count(sess); + + target_put_session(sess->se_sess); + return 0; +} + +static void iscsi_login_set_conn_values( + struct iscsi_session *sess, + struct iscsi_conn *conn, + __be16 cid) +{ + conn->sess = sess; + conn->cid = be16_to_cpu(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); +} + +static __printf(2, 3) int iscsi_change_param_sprintf( + struct iscsi_conn *conn, + const char *fmt, ...) +{ + va_list args; + unsigned char buf[64]; + + memset(buf, 0, sizeof buf); + + va_start(args, fmt); + vsnprintf(buf, sizeof buf, fmt, args); + va_end(args); + + 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 0; +} + +/* + * 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; + enum target_prot_op sup_pro_ops; + int ret; + + 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 -ENOMEM; + } + + iscsi_login_set_conn_values(sess, conn, pdu->cid); + sess->init_task_tag = pdu->itt; + memcpy(&sess->isid, pdu->isid, 6); + sess->exp_cmd_sn = be32_to_cpu(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); + + idr_preload(GFP_KERNEL); + spin_lock_bh(&sess_idr_lock); + ret = idr_alloc(&sess_idr, NULL, 0, 0, GFP_NOWAIT); + if (ret >= 0) + sess->session_index = ret; + spin_unlock_bh(&sess_idr_lock); + idr_preload_end(); + + if (ret < 0) { + pr_err("idr_alloc() for sess_idr failed\n"); + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + kfree(sess); + return -ENOMEM; + } + + sess->creation_time = get_jiffies_64(); + /* + * 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 = be32_to_cpu(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"); + kfree(sess); + return -ENOMEM; + } + sup_pro_ops = conn->conn_transport->iscsit_get_sup_prot_ops(conn); + + sess->se_sess = transport_init_session(sup_pro_ops); + if (IS_ERR(sess->se_sess)) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + kfree(sess); + return -ENOMEM; + } + + return 0; +} + +static int iscsi_login_zero_tsih_s2( + struct iscsi_conn *conn) +{ + struct iscsi_node_attrib *na; + struct iscsi_session *sess = conn->sess; + bool iser = false; + + 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 = ++sess->tpg->ntsih; + if (!sess->tsih) + sess->tsih = ++sess->tpg->ntsih; + + /* + * Create the default params from user defined values.. + */ + if (iscsi_copy_param_list(&conn->param_list, + conn->tpg->param_list, 1) < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return -1; + } + + if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) + iser = true; + + iscsi_set_keys_to_negotiate(conn->param_list, iser); + + 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. + */ + if (iscsi_change_param_sprintf(conn, "TargetPortalGroupTag=%hu", sess->tpg->tpgt)) + return -1; + + /* + * Workaround for Initiators that have broken connection recovery logic. + * + * "We would really like to get rid of this." Linux-iSCSI.org team + */ + if (iscsi_change_param_sprintf(conn, "ErrorRecoveryLevel=%d", na->default_erl)) + return -1; + + if (iscsi_login_disable_FIM_keys(conn->param_list, conn) < 0) + return -1; + /* + * Set RDMAExtensions=Yes by default for iSER enabled network portals + */ + if (iser) { + struct iscsi_param *param; + unsigned long mrdsl, off; + int rc; + + if (iscsi_change_param_sprintf(conn, "RDMAExtensions=Yes")) + return -1; + + /* + * Make MaxRecvDataSegmentLength PAGE_SIZE aligned for + * Immediate Data + Unsolicitied Data-OUT if necessary.. + */ + param = iscsi_find_param_from_key("MaxRecvDataSegmentLength", + conn->param_list); + if (!param) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return -1; + } + rc = kstrtoul(param->value, 0, &mrdsl); + if (rc < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return -1; + } + off = mrdsl % PAGE_SIZE; + if (!off) + goto check_prot; + + if (mrdsl < PAGE_SIZE) + mrdsl = PAGE_SIZE; + else + mrdsl -= off; + + pr_warn("Aligning ISER MaxRecvDataSegmentLength: %lu down" + " to PAGE_SIZE\n", mrdsl); + + if (iscsi_change_param_sprintf(conn, "MaxRecvDataSegmentLength=%lu\n", mrdsl)) + return -1; + /* + * ISER currently requires that ImmediateData + Unsolicited + * Data be disabled when protection / signature MRs are enabled. + */ +check_prot: + if (sess->se_sess->sup_prot_ops & + (TARGET_PROT_DOUT_STRIP | TARGET_PROT_DOUT_PASS | + TARGET_PROT_DOUT_INSERT)) { + + if (iscsi_change_param_sprintf(conn, "ImmediateData=No")) + return -1; + + if (iscsi_change_param_sprintf(conn, "InitialR2T=Yes")) + return -1; + + pr_debug("Forcing ImmediateData=No + InitialR2T=Yes for" + " T10-PI enabled ISER session\n"); + } + } + + 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; + bool iser = false; + + 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(sess_p->isid, pdu->isid, 6) && + (sess_p->tsih == be16_to_cpu(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, + conn->tpg->param_list, 0) < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return -1; + } + + if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) + iser = true; + + iscsi_set_keys_to_negotiate(conn->param_list, iser); + /* + * 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. + */ + if (iscsi_change_param_sprintf(conn, "TargetPortalGroupTag=%hu", sess->tpg->tpgt)) + 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; + /* + * FIXME: Unsolicitied NopIN support for ISER + */ + if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) + return; + + if (!sess->sess_ops->SessionType) + iscsit_start_nopin_timer(conn); +} + +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 = sess->tpg; + 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, + conn->local_ip, conn->local_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, 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, conn->local_ip, conn->local_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 iscsit_setup_np( + struct iscsi_np *np, + struct __kernel_sockaddr_storage *sockaddr) +{ + struct socket *sock = NULL; + int backlog = ISCSIT_TCP_BACKLOG, 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; + default: + pr_err("Unsupported network_transport: %d\n", + np->np_network_transport); + return -EINVAL; + } + + np->np_ip_proto = IPPROTO_TCP; + np->np_sock_type = SOCK_STREAM; + + 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; + /* + * Setup the np->np_sockaddr from the passed sockaddr setup + * in iscsi_target_configfs.c code.. + */ + memcpy(&np->np_sockaddr, 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. + */ + /* FIXME: Someone please explain why this is endian-safe */ + 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; + } + } + + /* FIXME: Someone please explain why this is endian-safe */ + 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_setsockopt(sock, IPPROTO_IP, IP_FREEBIND, + (char *)&opt, sizeof(opt)); + if (ret < 0) { + pr_err("kernel_setsockopt() for IP_FREEBIND" + " 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) + sock_release(sock); + return ret; +} + +int iscsi_target_setup_login_socket( + struct iscsi_np *np, + struct __kernel_sockaddr_storage *sockaddr) +{ + struct iscsit_transport *t; + int rc; + + t = iscsit_get_transport(np->np_network_transport); + if (!t) + return -EINVAL; + + rc = t->iscsit_setup_np(np, sockaddr); + if (rc < 0) { + iscsit_put_transport(t); + return rc; + } + + np->np_transport = t; + np->enabled = true; + return 0; +} + +int iscsit_accept_np(struct iscsi_np *np, struct iscsi_conn *conn) +{ + struct socket *new_sock, *sock = np->np_socket; + struct sockaddr_in sock_in; + struct sockaddr_in6 sock_in6; + int rc, err; + + rc = kernel_accept(sock, &new_sock, 0); + if (rc < 0) + return rc; + + conn->sock = new_sock; + conn->login_family = np->np_sockaddr.ss_family; + + if (np->np_sockaddr.ss_family == AF_INET6) { + memset(&sock_in6, 0, sizeof(struct sockaddr_in6)); + + rc = conn->sock->ops->getname(conn->sock, + (struct sockaddr *)&sock_in6, &err, 1); + if (!rc) { + if (!ipv6_addr_v4mapped(&sock_in6.sin6_addr)) + snprintf(conn->login_ip, sizeof(conn->login_ip), "[%pI6c]", + &sock_in6.sin6_addr.in6_u); + else + snprintf(conn->login_ip, sizeof(conn->login_ip), "%pI4", + &sock_in6.sin6_addr.s6_addr32[3]); + conn->login_port = ntohs(sock_in6.sin6_port); + } + + rc = conn->sock->ops->getname(conn->sock, + (struct sockaddr *)&sock_in6, &err, 0); + if (!rc) { + if (!ipv6_addr_v4mapped(&sock_in6.sin6_addr)) + snprintf(conn->local_ip, sizeof(conn->local_ip), "[%pI6c]", + &sock_in6.sin6_addr.in6_u); + else + snprintf(conn->local_ip, sizeof(conn->local_ip), "%pI4", + &sock_in6.sin6_addr.s6_addr32[3]); + conn->local_port = ntohs(sock_in6.sin6_port); + } + } else { + memset(&sock_in, 0, sizeof(struct sockaddr_in)); + + rc = conn->sock->ops->getname(conn->sock, + (struct sockaddr *)&sock_in, &err, 1); + if (!rc) { + sprintf(conn->login_ip, "%pI4", + &sock_in.sin_addr.s_addr); + conn->login_port = ntohs(sock_in.sin_port); + } + + rc = conn->sock->ops->getname(conn->sock, + (struct sockaddr *)&sock_in, &err, 0); + if (!rc) { + sprintf(conn->local_ip, "%pI4", + &sock_in.sin_addr.s_addr); + conn->local_port = ntohs(sock_in.sin_port); + } + } + + return 0; +} + +int iscsit_get_login_rx(struct iscsi_conn *conn, struct iscsi_login *login) +{ + struct iscsi_login_req *login_req; + u32 padding = 0, payload_length; + + 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); + padding = ((-payload_length) & 3); + + 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); + /* + * Setup the initial iscsi_login values from the leading + * login request PDU. + */ + if (login->first_request) { + login_req = (struct iscsi_login_req *)login->req; + login->leading_connection = (!login_req->tsih) ? 1 : 0; + login->current_stage = ISCSI_LOGIN_CURRENT_STAGE(login_req->flags); + login->version_min = login_req->min_version; + login->version_max = login_req->max_version; + memcpy(login->isid, login_req->isid, 6); + login->cmd_sn = be32_to_cpu(login_req->cmdsn); + login->init_task_tag = login_req->itt; + login->initial_exp_statsn = be32_to_cpu(login_req->exp_statsn); + login->cid = be16_to_cpu(login_req->cid); + login->tsih = be16_to_cpu(login_req->tsih); + } + + if (iscsi_target_check_login_request(conn, login) < 0) + return -1; + + 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; +} + +int iscsit_put_login_tx(struct iscsi_conn *conn, struct iscsi_login *login, + u32 length) +{ + if (iscsi_login_tx_data(conn, login->rsp, login->rsp_buf, length) < 0) + return -1; + + return 0; +} + +static int +iscsit_conn_set_transport(struct iscsi_conn *conn, struct iscsit_transport *t) +{ + int rc; + + if (!t->owner) { + conn->conn_transport = t; + return 0; + } + + rc = try_module_get(t->owner); + if (!rc) { + pr_err("try_module_get() failed for %s\n", t->name); + return -EINVAL; + } + + conn->conn_transport = t; + return 0; +} + +void iscsi_target_login_sess_out(struct iscsi_conn *conn, + struct iscsi_np *np, bool zero_tsih, bool new_sess) +{ + if (!new_sess) + goto old_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); + } + kfree(conn->sess->sess_ops); + 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 = + &conn->tpg->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; + } + iscsi_target_nego_release(conn); + + if (conn->sock) { + sock_release(conn->sock); + conn->sock = NULL; + } + + if (conn->conn_transport->iscsit_free_conn) + conn->conn_transport->iscsit_free_conn(conn); + + iscsit_put_transport(conn->conn_transport); + kfree(conn); +} + +static int __iscsi_target_login_thread(struct iscsi_np *np) +{ + u8 *buffer, zero_tsih = 0; + int ret = 0, rc; + struct iscsi_conn *conn = NULL; + struct iscsi_login *login; + struct iscsi_portal_group *tpg = NULL; + struct iscsi_login_req *pdu; + struct iscsi_tpg_np *tpg_np; + bool new_sess = false; + + flush_signals(current); + + 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 if (np->np_thread_state == ISCSI_NP_THREAD_SHUTDOWN) { + spin_unlock_bh(&np->np_thread_lock); + goto exit; + } else { + np->np_thread_state = ISCSI_NP_THREAD_ACTIVE; + } + spin_unlock_bh(&np->np_thread_lock); + + conn = kzalloc(sizeof(struct iscsi_conn), GFP_KERNEL); + if (!conn) { + pr_err("Could not allocate memory for" + " new connection\n"); + /* Get another socket */ + return 1; + } + pr_debug("Moving to TARG_CONN_STATE_FREE.\n"); + conn->conn_state = TARG_CONN_STATE_FREE; + + if (iscsit_conn_set_transport(conn, np->np_transport) < 0) { + kfree(conn); + return 1; + } + + rc = np->np_transport->iscsit_accept_np(np, conn); + if (rc == -ENOSYS) { + complete(&np->np_restart_comp); + iscsit_put_transport(conn->conn_transport); + kfree(conn); + conn = NULL; + goto exit; + } else if (rc < 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); + iscsit_put_transport(conn->conn_transport); + kfree(conn); + conn = NULL; + if (ret == -ENODEV) + goto out; + /* Get another socket */ + return 1; + } + spin_unlock_bh(&np->np_thread_lock); + iscsit_put_transport(conn->conn_transport); + kfree(conn); + conn = NULL; + goto out; + } + /* + * Perform the remaining iSCSI connection initialization items.. + */ + login = iscsi_login_init_conn(conn); + if (!login) { + goto new_sess_out; + } + + iscsi_start_login_thread_timer(np); + + pr_debug("Moving to TARG_CONN_STATE_XPT_UP.\n"); + conn->conn_state = TARG_CONN_STATE_XPT_UP; + /* + * This will process the first login request + payload.. + */ + rc = np->np_transport->iscsit_get_login_rx(conn, login); + if (rc == 1) + return 1; + else if (rc < 0) + goto new_sess_out; + + buffer = &login->req[0]; + pdu = (struct iscsi_login_req *)buffer; + /* + * 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); + + 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, np->np_transport->name, + conn->local_ip, conn->local_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; + } + /* + * SessionType: Discovery + * + * Locates Default Portal + * + * SessionType: Normal + * + * Locates Target Portal from NP -> Target IQN + */ + rc = iscsi_target_locate_portal(np, conn, login); + if (rc < 0) { + tpg = conn->tpg; + goto new_sess_out; + } + login->zero_tsih = zero_tsih; + + 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) + goto new_sess_out; + } else { + if (iscsi_login_non_zero_tsih_s2(conn, buffer) < 0) + goto old_sess_out; + } + + ret = iscsi_target_start_negotiation(login, conn); + if (ret < 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; + + if (ret == 1) { + tpg_np = conn->tpg_np; + + ret = iscsi_post_login_handler(np, conn, zero_tsih); + if (ret < 0) + goto new_sess_out; + + iscsit_deaccess_np(np, tpg, tpg_np); + } + + tpg = NULL; + tpg_np = NULL; + /* Get another socket */ + return 1; + +new_sess_out: + new_sess = true; +old_sess_out: + tpg_np = conn->tpg_np; + iscsi_target_login_sess_out(conn, np, zero_tsih, new_sess); + new_sess = false; + + if (tpg) { + iscsit_deaccess_np(np, tpg, tpg_np); + tpg = NULL; + tpg_np = NULL; + } + +out: + return 1; + +exit: + 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 = arg; + int ret; + + allow_signal(SIGINT); + + while (1) { + 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 00000000000..29d098324b7 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_login.h @@ -0,0 +1,21 @@ +#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 iscsit_setup_np(struct iscsi_np *, + struct __kernel_sockaddr_storage *); +extern int iscsi_target_setup_login_socket(struct iscsi_np *, + struct __kernel_sockaddr_storage *); +extern int iscsit_accept_np(struct iscsi_np *, struct iscsi_conn *); +extern int iscsit_get_login_rx(struct iscsi_conn *, struct iscsi_login *); +extern int iscsit_put_login_tx(struct iscsi_conn *, struct iscsi_login *, u32); +extern void iscsit_free_conn(struct iscsi_np *, struct iscsi_conn *); +extern int iscsi_post_login_handler(struct iscsi_np *, struct iscsi_conn *, u8); +extern void iscsi_target_login_sess_out(struct iscsi_conn *, struct iscsi_np *, + bool, bool); +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 00000000000..62a095f36bf --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_nego.c @@ -0,0 +1,1254 @@ +/******************************************************************************* + * This file contains main functions related to iSCSI Parameter negotiation. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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_fabric.h> +#include <target/iscsi/iscsi_transport.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] = ';'; +} + +static 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 exceeds 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 iscsi_portal_group *iscsi_tpg; + 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; + } + + if (se_nacl->dynamic_node_acl) { + iscsi_tpg = container_of(se_nacl->se_tpg, + struct iscsi_portal_group, tpg_se_tpg); + + auth = &iscsi_tpg->tpg_demo_auth; + } else { + iscsi_nacl = container_of(se_nacl, struct iscsi_node_acl, + se_node_acl); + + auth = &iscsi_nacl->node_auth; + } + } 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); +} + +int iscsi_target_check_login_request( + struct iscsi_conn *conn, + struct iscsi_login *login) +{ + int req_csg, req_nsg; + u32 payload_length; + struct iscsi_login_req *login_req; + + login_req = (struct iscsi_login_req *) login->req; + 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 = ISCSI_LOGIN_CURRENT_STAGE(login_req->flags); + req_nsg = ISCSI_LOGIN_NEXT_STAGE(login_req->flags); + + 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 = 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, (__force u32)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 (conn->conn_transport->iscsit_put_login_tx(conn, login, + login->rsp_length + padding) < 0) + return -1; + + login->rsp_length = 0; + mutex_lock(&sess->cmdsn_mutex); + login_rsp->exp_cmdsn = cpu_to_be32(sess->exp_cmd_sn); + login_rsp->max_cmdsn = cpu_to_be32(sess->max_cmd_sn); + mutex_unlock(&sess->cmdsn_mutex); + + return 0; +} + +static void iscsi_target_sk_data_ready(struct sock *sk) +{ + struct iscsi_conn *conn = sk->sk_user_data; + bool rc; + + pr_debug("Entering iscsi_target_sk_data_ready: conn: %p\n", conn); + + write_lock_bh(&sk->sk_callback_lock); + if (!sk->sk_user_data) { + write_unlock_bh(&sk->sk_callback_lock); + return; + } + if (!test_bit(LOGIN_FLAGS_READY, &conn->login_flags)) { + write_unlock_bh(&sk->sk_callback_lock); + pr_debug("Got LOGIN_FLAGS_READY=0, conn: %p >>>>\n", conn); + return; + } + if (test_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)) { + write_unlock_bh(&sk->sk_callback_lock); + pr_debug("Got LOGIN_FLAGS_CLOSED=1, conn: %p >>>>\n", conn); + return; + } + if (test_and_set_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags)) { + write_unlock_bh(&sk->sk_callback_lock); + pr_debug("Got LOGIN_FLAGS_READ_ACTIVE=1, conn: %p >>>>\n", conn); + return; + } + + rc = schedule_delayed_work(&conn->login_work, 0); + if (!rc) { + pr_debug("iscsi_target_sk_data_ready, schedule_delayed_work" + " got false\n"); + } + write_unlock_bh(&sk->sk_callback_lock); +} + +static void iscsi_target_sk_state_change(struct sock *); + +static void iscsi_target_set_sock_callbacks(struct iscsi_conn *conn) +{ + struct sock *sk; + + if (!conn->sock) + return; + + sk = conn->sock->sk; + pr_debug("Entering iscsi_target_set_sock_callbacks: conn: %p\n", conn); + + write_lock_bh(&sk->sk_callback_lock); + sk->sk_user_data = conn; + conn->orig_data_ready = sk->sk_data_ready; + conn->orig_state_change = sk->sk_state_change; + sk->sk_data_ready = iscsi_target_sk_data_ready; + sk->sk_state_change = iscsi_target_sk_state_change; + write_unlock_bh(&sk->sk_callback_lock); + + sk->sk_sndtimeo = TA_LOGIN_TIMEOUT * HZ; + sk->sk_rcvtimeo = TA_LOGIN_TIMEOUT * HZ; +} + +static void iscsi_target_restore_sock_callbacks(struct iscsi_conn *conn) +{ + struct sock *sk; + + if (!conn->sock) + return; + + sk = conn->sock->sk; + pr_debug("Entering iscsi_target_restore_sock_callbacks: conn: %p\n", conn); + + write_lock_bh(&sk->sk_callback_lock); + if (!sk->sk_user_data) { + write_unlock_bh(&sk->sk_callback_lock); + return; + } + sk->sk_user_data = NULL; + sk->sk_data_ready = conn->orig_data_ready; + sk->sk_state_change = conn->orig_state_change; + write_unlock_bh(&sk->sk_callback_lock); + + sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT; + sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT; +} + +static int iscsi_target_do_login(struct iscsi_conn *, struct iscsi_login *); + +static bool iscsi_target_sk_state_check(struct sock *sk) +{ + if (sk->sk_state == TCP_CLOSE_WAIT || sk->sk_state == TCP_CLOSE) { + pr_debug("iscsi_target_sk_state_check: TCP_CLOSE_WAIT|TCP_CLOSE," + "returning FALSE\n"); + return false; + } + return true; +} + +static void iscsi_target_login_drop(struct iscsi_conn *conn, struct iscsi_login *login) +{ + struct iscsi_np *np = login->np; + bool zero_tsih = login->zero_tsih; + + iscsi_remove_failed_auth_entry(conn); + iscsi_target_nego_release(conn); + iscsi_target_login_sess_out(conn, np, zero_tsih, true); +} + +static void iscsi_target_login_timeout(unsigned long data) +{ + struct iscsi_conn *conn = (struct iscsi_conn *)data; + + pr_debug("Entering iscsi_target_login_timeout >>>>>>>>>>>>>>>>>>>\n"); + + if (conn->login_kworker) { + pr_debug("Sending SIGINT to conn->login_kworker %s/%d\n", + conn->login_kworker->comm, conn->login_kworker->pid); + send_sig(SIGINT, conn->login_kworker, 1); + } +} + +static void iscsi_target_do_login_rx(struct work_struct *work) +{ + struct iscsi_conn *conn = container_of(work, + struct iscsi_conn, login_work.work); + struct iscsi_login *login = conn->login; + struct iscsi_np *np = login->np; + struct iscsi_portal_group *tpg = conn->tpg; + struct iscsi_tpg_np *tpg_np = conn->tpg_np; + struct timer_list login_timer; + int rc, zero_tsih = login->zero_tsih; + bool state; + + pr_debug("entering iscsi_target_do_login_rx, conn: %p, %s:%d\n", + conn, current->comm, current->pid); + + spin_lock(&tpg->tpg_state_lock); + state = (tpg->tpg_state == TPG_STATE_ACTIVE); + spin_unlock(&tpg->tpg_state_lock); + + if (!state) { + pr_debug("iscsi_target_do_login_rx: tpg_state != TPG_STATE_ACTIVE\n"); + iscsi_target_restore_sock_callbacks(conn); + iscsi_target_login_drop(conn, login); + iscsit_deaccess_np(np, tpg, tpg_np); + return; + } + + if (conn->sock) { + struct sock *sk = conn->sock->sk; + + read_lock_bh(&sk->sk_callback_lock); + state = iscsi_target_sk_state_check(sk); + read_unlock_bh(&sk->sk_callback_lock); + + if (!state) { + pr_debug("iscsi_target_do_login_rx, TCP state CLOSE\n"); + iscsi_target_restore_sock_callbacks(conn); + iscsi_target_login_drop(conn, login); + iscsit_deaccess_np(np, tpg, tpg_np); + return; + } + } + + conn->login_kworker = current; + allow_signal(SIGINT); + + init_timer(&login_timer); + login_timer.expires = (get_jiffies_64() + TA_LOGIN_TIMEOUT * HZ); + login_timer.data = (unsigned long)conn; + login_timer.function = iscsi_target_login_timeout; + add_timer(&login_timer); + pr_debug("Starting login_timer for %s/%d\n", current->comm, current->pid); + + rc = conn->conn_transport->iscsit_get_login_rx(conn, login); + del_timer_sync(&login_timer); + flush_signals(current); + conn->login_kworker = NULL; + + if (rc < 0) { + iscsi_target_restore_sock_callbacks(conn); + iscsi_target_login_drop(conn, login); + iscsit_deaccess_np(np, tpg, tpg_np); + return; + } + + pr_debug("iscsi_target_do_login_rx after rx_login_io, %p, %s:%d\n", + conn, current->comm, current->pid); + + rc = iscsi_target_do_login(conn, login); + if (rc < 0) { + iscsi_target_restore_sock_callbacks(conn); + iscsi_target_login_drop(conn, login); + iscsit_deaccess_np(np, tpg, tpg_np); + } else if (!rc) { + if (conn->sock) { + struct sock *sk = conn->sock->sk; + + write_lock_bh(&sk->sk_callback_lock); + clear_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags); + write_unlock_bh(&sk->sk_callback_lock); + } + } else if (rc == 1) { + iscsi_target_nego_release(conn); + iscsi_post_login_handler(np, conn, zero_tsih); + iscsit_deaccess_np(np, tpg, tpg_np); + } +} + +static void iscsi_target_do_cleanup(struct work_struct *work) +{ + struct iscsi_conn *conn = container_of(work, + struct iscsi_conn, login_cleanup_work.work); + struct sock *sk = conn->sock->sk; + struct iscsi_login *login = conn->login; + struct iscsi_np *np = login->np; + struct iscsi_portal_group *tpg = conn->tpg; + struct iscsi_tpg_np *tpg_np = conn->tpg_np; + + pr_debug("Entering iscsi_target_do_cleanup\n"); + + cancel_delayed_work_sync(&conn->login_work); + conn->orig_state_change(sk); + + iscsi_target_restore_sock_callbacks(conn); + iscsi_target_login_drop(conn, login); + iscsit_deaccess_np(np, tpg, tpg_np); + + pr_debug("iscsi_target_do_cleanup done()\n"); +} + +static void iscsi_target_sk_state_change(struct sock *sk) +{ + struct iscsi_conn *conn; + void (*orig_state_change)(struct sock *); + bool state; + + pr_debug("Entering iscsi_target_sk_state_change\n"); + + write_lock_bh(&sk->sk_callback_lock); + conn = sk->sk_user_data; + if (!conn) { + write_unlock_bh(&sk->sk_callback_lock); + return; + } + orig_state_change = conn->orig_state_change; + + if (!test_bit(LOGIN_FLAGS_READY, &conn->login_flags)) { + pr_debug("Got LOGIN_FLAGS_READY=0 sk_state_change conn: %p\n", + conn); + write_unlock_bh(&sk->sk_callback_lock); + orig_state_change(sk); + return; + } + if (test_bit(LOGIN_FLAGS_READ_ACTIVE, &conn->login_flags)) { + pr_debug("Got LOGIN_FLAGS_READ_ACTIVE=1 sk_state_change" + " conn: %p\n", conn); + write_unlock_bh(&sk->sk_callback_lock); + orig_state_change(sk); + return; + } + if (test_and_set_bit(LOGIN_FLAGS_CLOSED, &conn->login_flags)) { + pr_debug("Got LOGIN_FLAGS_CLOSED=1 sk_state_change conn: %p\n", + conn); + write_unlock_bh(&sk->sk_callback_lock); + orig_state_change(sk); + return; + } + + state = iscsi_target_sk_state_check(sk); + write_unlock_bh(&sk->sk_callback_lock); + + pr_debug("iscsi_target_sk_state_change: state: %d\n", state); + + if (!state) { + pr_debug("iscsi_target_sk_state_change got failed state\n"); + schedule_delayed_work(&conn->login_cleanup_work, 0); + return; + } + orig_state_change(sk); +} + +/* + * 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 successfully.\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); + 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; + } else if (!payload_length) { + pr_err("Initiator sent zero length security payload," + " login failed\n"); + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_AUTH_FAILED); + return -1; + } + + 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 (conn->tpg->tpg_attrib.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 (conn->tpg->tpg_attrib.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); + if (ret < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_INIT_ERR); + 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) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_INIT_ERR); + return -1; + } + + if (!login->auth_complete && + conn->tpg->tpg_attrib.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 (ISCSI_LOGIN_CURRENT_STAGE(login_req->flags)) { + case 0: + login_rsp->flags &= ~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; + login->login_complete = 1; + iscsi_target_restore_sock_callbacks(conn); + if (iscsi_target_do_tx_login_io(conn, + login) < 0) + return -1; + return 1; + } + break; + default: + pr_err("Illegal CSG: %d received from" + " Initiator, protocol error.\n", + ISCSI_LOGIN_CURRENT_STAGE(login_req->flags)); + break; + } + + if (iscsi_target_do_tx_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; + } + break; + } + + if (conn->sock) { + struct sock *sk = conn->sock->sk; + bool state; + + read_lock_bh(&sk->sk_callback_lock); + state = iscsi_target_sk_state_check(sk); + read_unlock_bh(&sk->sk_callback_lock); + + if (!state) { + pr_debug("iscsi_target_do_login() failed state for" + " conn: %p\n", conn); + return -1; + } + } + + 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 = ¶m_buf[i]; + if (!isupper(*c)) + continue; + + *c = tolower(*c); + } +} + +/* + * Processes the first Login Request.. + */ +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_tpg_np *tpg_np = NULL; + struct iscsi_login_req *login_req; + struct se_node_acl *se_nacl; + u32 payload_length, queue_depth = 0; + int sessiontype = 0, ret = 0, tag_num, tag_size; + + INIT_DELAYED_WORK(&conn->login_work, iscsi_target_do_login_rx); + INIT_DELAYED_WORK(&conn->login_cleanup_work, iscsi_target_do_cleanup); + iscsi_target_set_sock_callbacks(conn); + + login->np = np; + + login_req = (struct iscsi_login_req *) login->req; + payload_length = ntoh24(login_req->dlength); + + 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 alloc_tags; + } + +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, &tpg_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; + } + conn->tpg_np = tpg_np; + 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"); + kref_put(&tpg_np->tpg_np_kref, iscsit_login_kref_put); + iscsit_put_tiqn_for_login(tiqn); + conn->tpg = NULL; + ret = -1; + goto out; + } + /* + * Serialize access across the struct iscsi_portal_group to + * process login attempt. + */ + if (iscsit_access_np(np, conn->tpg) < 0) { + kref_put(&tpg_np->tpg_np_kref, iscsit_login_kref_put); + iscsit_put_tiqn_for_login(tiqn); + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE); + conn->tpg = NULL; + ret = -1; + 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; + } + se_nacl = sess->se_sess->se_node_acl; + queue_depth = se_nacl->queue_depth; + /* + * Setup pre-allocated tags based upon allowed per NodeACL CmdSN + * depth for non immediate commands, plus extra tags for immediate + * commands. + * + * Also enforce a ISCSIT_MIN_TAGS to prevent unnecessary contention + * in per-cpu-ida tag allocation logic + small queue_depth. + */ +alloc_tags: + tag_num = max_t(u32, ISCSIT_MIN_TAGS, queue_depth); + tag_num = (tag_num * 2) + ISCSIT_EXTRA_TAGS; + tag_size = sizeof(struct iscsi_cmd) + conn->conn_transport->priv_size; + + ret = transport_alloc_session_tags(sess->se_sess, tag_num, tag_size); + if (ret < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + ret = -1; + } +out: + kfree(tmpbuf); + return ret; +} + +int iscsi_target_start_negotiation( + struct iscsi_login *login, + struct iscsi_conn *conn) +{ + int ret; + + ret = iscsi_target_do_login(conn, login); + if (!ret) { + if (conn->sock) { + struct sock *sk = conn->sock->sk; + + write_lock_bh(&sk->sk_callback_lock); + set_bit(LOGIN_FLAGS_READY, &conn->login_flags); + write_unlock_bh(&sk->sk_callback_lock); + } + } else if (ret < 0) { + cancel_delayed_work_sync(&conn->login_work); + cancel_delayed_work_sync(&conn->login_cleanup_work); + iscsi_target_restore_sock_callbacks(conn); + iscsi_remove_failed_auth_entry(conn); + } + if (ret != 0) + iscsi_target_nego_release(conn); + + return ret; +} + +void iscsi_target_nego_release(struct iscsi_conn *conn) +{ + struct iscsi_login *login = conn->conn_login; + + if (!login) + return; + + kfree(login->req_buf); + kfree(login->rsp_buf); + kfree(login); + + conn->conn_login = NULL; +} diff --git a/drivers/target/iscsi/iscsi_target_nego.h b/drivers/target/iscsi/iscsi_target_nego.h new file mode 100644 index 00000000000..f021cbd330e --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_nego.h @@ -0,0 +1,20 @@ +#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 int iscsi_target_check_login_request(struct iscsi_conn *, + struct iscsi_login *); +extern int iscsi_target_get_initial_payload(struct iscsi_conn *, + struct iscsi_login *); +extern int iscsi_target_locate_portal(struct iscsi_np *, struct iscsi_conn *, + struct iscsi_login *); +extern int iscsi_target_start_negotiation( + struct iscsi_login *, struct iscsi_conn *); +extern void iscsi_target_nego_release(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 00000000000..16454a922e2 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_nodeattrib.c @@ -0,0 +1,261 @@ +/******************************************************************************* + * This file contains the main functions related to Initiator Node Attributes. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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 "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_portal_group *tpg) +{ + 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 = tpg->tpg_attrib.default_erl; +} + +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; +} + +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; +} + +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 = 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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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 00000000000..0c69a46a62e --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_nodeattrib.h @@ -0,0 +1,15 @@ +#ifndef ISCSI_TARGET_NODEATTRIB_H +#define ISCSI_TARGET_NODEATTRIB_H + +extern void iscsit_set_default_node_attribues(struct iscsi_node_acl *, + struct iscsi_portal_group *); +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 00000000000..02f9de26f38 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_parameters.c @@ -0,0 +1,1982 @@ +/******************************************************************************* + * This file contains main functions related to iSCSI Parameter negotiation. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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, iov_cnt = 1; + 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; + + if (text_buf && text_length) { + iov[1].iov_len = text_length; + iov[1].iov_base = text_buf; + iov_cnt++; + } + + /* + * 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], iov_cnt, 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 = kstrdup(name, GFP_KERNEL); + if (!param->name) { + pr_err("Unable to allocate memory for parameter name.\n"); + goto out; + } + + param->value = kstrdup(value, GFP_KERNEL); + if (!param->value) { + pr_err("Unable to allocate memory for parameter value.\n"); + goto out; + } + + 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, MAXXMITDATASEGMENTLENGTH, + INITIAL_MAXXMITDATASEGMENTLENGTH, + PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH, + TYPERANGE_512_TO_16777215, USE_ALL); + 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; + /* + * Extra parameters for ISER from RFC-5046 + */ + param = iscsi_set_default_param(pl, RDMAEXTENSIONS, INITIAL_RDMAEXTENSIONS, + PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH, + TYPERANGE_BOOL_AND, USE_LEADING_ONLY); + if (!param) + goto out; + + param = iscsi_set_default_param(pl, INITIATORRECVDATASEGMENTLENGTH, + INITIAL_INITIATORRECVDATASEGMENTLENGTH, + PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH, + TYPERANGE_512_TO_16777215, USE_ALL); + if (!param) + goto out; + + param = iscsi_set_default_param(pl, TARGETRECVDATASEGMENTLENGTH, + INITIAL_TARGETRECVDATASEGMENTLENGTH, + PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH, + TYPERANGE_512_TO_16777215, USE_ALL); + if (!param) + goto out; + + *param_list_ptr = pl; + return 0; +out: + iscsi_release_param_list(pl); + return -1; +} + +int iscsi_set_keys_to_negotiate( + struct iscsi_param_list *param_list, + bool iser) +{ + struct iscsi_param *param; + + param_list->iser = iser; + + 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)) { + if (!iser) + SET_PSTATE_NEGOTIATE(param); + } else if (!strcmp(param->name, DATADIGEST)) { + if (!iser) + 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)) { + if (!iser) + SET_PSTATE_NEGOTIATE(param); + } else if (!strcmp(param->name, MAXXMITDATASEGMENTLENGTH)) { + continue; + } 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); + } else if (!strcmp(param->name, RDMAEXTENSIONS)) { + if (iser) + SET_PSTATE_NEGOTIATE(param); + } else if (!strcmp(param->name, INITIATORRECVDATASEGMENTLENGTH)) { + if (iser) + SET_PSTATE_NEGOTIATE(param); + } else if (!strcmp(param->name, TARGETRECVDATASEGMENTLENGTH)) { + if (iser) + 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; + else if (!strcmp(param->name, RDMAEXTENSIONS)) + param->state &= ~PSTATE_NEGOTIATE; + else if (!strcmp(param->name, INITIATORRECVDATASEGMENTLENGTH)) + param->state &= ~PSTATE_NEGOTIATE; + else if (!strcmp(param->name, TARGETRECVDATASEGMENTLENGTH)) + 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 *param = NULL; + struct iscsi_param *new_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->name = kstrdup(param->name, GFP_KERNEL); + new_param->value = kstrdup(param->value, GFP_KERNEL); + if (!new_param->value || !new_param->name) { + kfree(new_param->value); + kfree(new_param->name); + kfree(new_param); + pr_err("Unable to allocate memory for parameter name/value.\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; + + 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); + kfree(param->value); + kfree(param); + } + + 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 \"=\" separator 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 = kstrdup(value, GFP_KERNEL); + if (!param->value) { + pr_err("Unable to allocate memory for value.\n"); + return -ENOMEM; + } + + 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); + + strlcpy(extra_response->key, key, sizeof(extra_response->key)); + strlcpy(extra_response->value, NOTUNDERSTOOD, + sizeof(extra_response->value)); + + 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); + + 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; + u32 left_val, right_val, local_left_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, NULL, 0); + right_val = simple_strtoul(right_val_ptr, NULL, 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, NULL, 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 (!strcmp(acceptor_values, proposer_values)) { + if (tmp2) + *tmp2 = ','; + goto out; + } + if (tmp2) + *tmp2++ = ','; + + acceptor_values = tmp2; + } 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, + struct iscsi_conn *conn) +{ + 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[11]; + u32 acceptor_value = simple_strtoul(param->value, &tmpptr, 0); + u32 proposer_value = simple_strtoul(value, &tmpptr, 0); + + memset(buf, 0, sizeof(buf)); + + 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)) { + struct iscsi_param *param_mxdsl; + unsigned long long tmp; + int rc; + + rc = kstrtoull(param->value, 0, &tmp); + if (rc < 0) + return -1; + + conn->conn_ops->MaxRecvDataSegmentLength = tmp; + pr_debug("Saving op->MaxRecvDataSegmentLength from" + " original initiator received value: %u\n", + conn->conn_ops->MaxRecvDataSegmentLength); + + param_mxdsl = iscsi_find_param_from_key( + MAXXMITDATASEGMENTLENGTH, + conn->param_list); + if (!param_mxdsl) + return -1; + + rc = iscsi_update_param_value(param, + param_mxdsl->value); + if (rc < 0) + return -1; + + pr_debug("Updated %s to target MXDSL value: %s\n", + param->name, param->value); + } + + } 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; + + 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, NULL, 0); + right_val = simple_strtoul(right_val_ptr, NULL, 0); + recieved_value = simple_strtoul(value, NULL, 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 separator \",\", 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"); + break; + 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 = 1, OFMarkInt_Reject = 1; + 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[11]; + memset(tmpbuf, 0, sizeof(tmpbuf)); + 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_conn *conn) +{ + struct iscsi_param_list *param_list = conn->param_list; + char *tmpbuf, *start = NULL, *end = NULL; + + tmpbuf = kzalloc(length + 1, GFP_KERNEL); + if (!tmpbuf) { + pr_err("Unable to allocate %u + 1 bytes for tmpbuf.\n", length); + 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) { + 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, conn) < 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) { + /* + * Special case to set MAXXMITDATASEGMENTLENGTH from the + * target requested MaxRecvDataSegmentLength, even though + * this key is not sent over the wire. + */ + if (!strcmp(param->name, MAXXMITDATASEGMENTLENGTH)) { + ops->MaxXmitDataSegmentLength = + simple_strtoul(param->value, &tmpptr, 0); + pr_debug("MaxXmitDataSegmentLength: %s\n", + param->value); + } + + 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)) { + /* + * At this point iscsi_check_acceptor_state() will have + * set ops->MaxRecvDataSegmentLength from the original + * initiator provided value. + */ + pr_debug("MaxRecvDataSegmentLength: %u\n", + ops->MaxRecvDataSegmentLength); + } 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); + } else if (!strcmp(param->name, INITIATORRECVDATASEGMENTLENGTH)) { + ops->InitiatorRecvDataSegmentLength = + simple_strtoul(param->value, &tmpptr, 0); + pr_debug("InitiatorRecvDataSegmentLength: %s\n", + param->value); + ops->MaxRecvDataSegmentLength = + ops->InitiatorRecvDataSegmentLength; + pr_debug("Set MRDSL from InitiatorRecvDataSegmentLength\n"); + } else if (!strcmp(param->name, TARGETRECVDATASEGMENTLENGTH)) { + ops->TargetRecvDataSegmentLength = + simple_strtoul(param->value, &tmpptr, 0); + pr_debug("TargetRecvDataSegmentLength: %s\n", + param->value); + ops->MaxXmitDataSegmentLength = + ops->TargetRecvDataSegmentLength; + pr_debug("Set MXDSL from TargetRecvDataSegmentLength\n"); + } + } + 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); + } else if (!strcmp(param->name, RDMAEXTENSIONS)) { + ops->RDMAExtensions = !strcmp(param->value, YES); + pr_debug("RDMAExtensions: %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 00000000000..a47046a752a --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_parameters.h @@ -0,0 +1,290 @@ +#ifndef ISCSI_PARAMETERS_H +#define ISCSI_PARAMETERS_H + +#include <scsi/iscsi_proto.h> + +struct iscsi_extra_response { + char key[KEY_MAXLEN]; + 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(struct iscsi_param_list *, bool); +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_conn *); +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 MAXXMITDATASEGMENTLENGTH "MaxXmitDataSegmentLength" +#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" + +/* + * Parameter names of iSCSI Extentions for RDMA (iSER). See RFC-5046 + */ +#define RDMAEXTENSIONS "RDMAExtensions" +#define INITIATORRECVDATASEGMENTLENGTH "InitiatorRecvDataSegmentLength" +#define TARGETRECVDATASEGMENTLENGTH "TargetRecvDataSegmentLength" + +/* + * 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" +/* + * Match outgoing MXDSL default to incoming Open-iSCSI default + */ +#define INITIAL_MAXXMITDATASEGMENTLENGTH "262144" +#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" + +/* + * Initial values for iSER parameters following RFC-5046 Section 6 + */ +#define INITIAL_RDMAEXTENSIONS NO +#define INITIAL_INITIATORRECVDATASEGMENTLENGTH "262144" +#define INITIAL_TARGETRECVDATASEGMENTLENGTH "8192" + +/* + * 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 00000000000..ca41b583f2f --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_seq_pdu_list.c @@ -0,0 +1,700 @@ +/******************************************************************************* + * This file contains main functions related to iSCSI DataSequenceInOrder=No + * and DataPDUInOrder=No. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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_tpg.h" +#include "iscsi_target_seq_pdu_list.h" + +#define OFFLOAD_BUF_SIZE 32768 + +#ifdef DEBUG +static 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); + } +} + +static 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); + } +} +#else +static void iscsit_dump_seq_list(struct iscsi_cmd *cmd) {} +static void iscsit_dump_pdu_list(struct iscsi_cmd *cmd) {} +#endif + +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 = kcalloc(seq_count, sizeof(u32), GFP_KERNEL); + if (!array) { + pr_err("Unable to allocate memory" + " for random array.\n"); + return -ENOMEM; + } + 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 = kcalloc(seq_count, sizeof(u32), GFP_KERNEL); + if (!array) { + pr_err("Unable to allocate memory for" + " random array.\n"); + return -ENOMEM; + } + 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 = kcalloc(seq_count, sizeof(u32), GFP_KERNEL); + if (!array) { + pr_err("Unable to allocate memory for random array.\n"); + return -ENOMEM; + } + 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; + u32 mdsl; + struct iscsi_conn *conn = cmd->conn; + + if (cmd->se_cmd.data_direction == DMA_TO_DEVICE) + mdsl = cmd->conn->conn_ops->MaxXmitDataSegmentLength; + else + mdsl = cmd->conn->conn_ops->MaxRecvDataSegmentLength; + + 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 = min(cmd->se_cmd.data_length, + conn->sess->sess_ops->FirstBurstLength); + + while (offset < cmd->se_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 + mdsl) >= cmd->se_cmd.data_length) { + unsolicited_data_length -= + (cmd->se_cmd.data_length - offset); + offset += (cmd->se_cmd.data_length - offset); + continue; + } + if ((offset + mdsl) + >= 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 += mdsl; + unsolicited_data_length -= mdsl; + continue; + } + if ((offset + mdsl) >= cmd->se_cmd.data_length) { + offset += (cmd->se_cmd.data_length - offset); + continue; + } + if ((burstlength + mdsl) >= + conn->sess->sess_ops->MaxBurstLength) { + offset += (conn->sess->sess_ops->MaxBurstLength - + burstlength); + burstlength = 0; + *seq_count += 1; + continue; + } + + burstlength += mdsl; + offset += mdsl; + } +} + + +/* + * Builds PDU and/or Sequence list, called while DataSequenceInOrder=No + * or DataPDUInOrder=No. + */ +static int iscsit_do_build_pdu_and_seq_lists( + struct iscsi_cmd *cmd, + struct iscsi_build_list *bl) +{ + int check_immediate = 0, datapduinorder, datasequenceinorder; + u32 burstlength = 0, offset = 0, i = 0, mdsl; + 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; + + if (cmd->se_cmd.data_direction == DMA_TO_DEVICE) + mdsl = cmd->conn->conn_ops->MaxXmitDataSegmentLength; + else + mdsl = cmd->conn->conn_ops->MaxRecvDataSegmentLength; + + 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 = min(cmd->se_cmd.data_length, + conn->sess->sess_ops->FirstBurstLength); + + while (offset < cmd->se_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 + mdsl) >= cmd->se_cmd.data_length) { + if (!datapduinorder) { + pdu[i].type = PDUTYPE_UNSOLICITED; + pdu[i].length = + (cmd->se_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->se_cmd.data_length - offset)); + } + unsolicited_data_length -= + (cmd->se_cmd.data_length - offset); + offset += (cmd->se_cmd.data_length - offset); + continue; + } + if ((offset + mdsl) >= + 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 = mdsl; + } + burstlength += mdsl; + offset += mdsl; + unsolicited_data_length -= mdsl; + continue; + } + if ((offset + mdsl) >= cmd->se_cmd.data_length) { + if (!datapduinorder) { + pdu[i].type = PDUTYPE_NORMAL; + pdu[i].length = (cmd->se_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->se_cmd.data_length - offset)); + } + offset += (cmd->se_cmd.data_length - offset); + continue; + } + if ((burstlength + mdsl) >= + 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 = mdsl; + } + burstlength += mdsl; + offset += mdsl; + } + + 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); + } + + iscsit_dump_seq_list(cmd); + } + 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); + } + + iscsit_dump_pdu_list(cmd); + } + + return 0; +} + +int iscsit_build_pdu_and_seq_lists( + struct iscsi_cmd *cmd, + u32 immediate_data_length) +{ + 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; + + struct iscsi_session *sess = conn->sess; + struct iscsi_node_attrib *na; + + /* + * Do nothing if no OOO shenanigans + */ + 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; + } + + iscsit_determine_counts_for_list(cmd, &bl, &seq_count, &pdu_count); + + if (!conn->sess->sess_ops->DataSequenceInOrder) { + seq = kcalloc(seq_count, sizeof(struct iscsi_seq), GFP_ATOMIC); + if (!seq) { + pr_err("Unable to allocate struct iscsi_seq list\n"); + return -ENOMEM; + } + cmd->seq_list = seq; + cmd->seq_count = seq_count; + } + + if (!conn->sess->sess_ops->DataPDUInOrder) { + pdu = kcalloc(pdu_count, sizeof(struct iscsi_pdu), GFP_ATOMIC); + if (!pdu) { + pr_err("Unable to allocate struct iscsi_pdu list.\n"); + kfree(seq); + return -ENOMEM; + } + cmd->pdu_list = pdu; + cmd->pdu_count = pdu_count; + } + + return iscsit_do_build_pdu_and_seq_lists(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++) { + 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); + + 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; + } + + pr_debug("seq->pdu_start: %d, seq->pdu_count: %d," + " seq->seq_no: %d\n", seq->pdu_start, seq->pdu_count, + seq->seq_no); + + 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++) { + 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); + + 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 00000000000..d5b153751a8 --- /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_build_pdu_and_seq_lists(struct iscsi_cmd *, u32); +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 00000000000..10339551030 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_stat.c @@ -0,0 +1,949 @@ +/******************************************************************************* + * Modern ConfigFS group context specific iSCSI statistics based on original + * iscsi_target_mib.c code + * + * Copyright (c) 2011-2013 Datera, Inc. + * + * 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 <linux/export.h> +#include <scsi/iscsi_proto.h> +#include <target/target_core_base.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, "Datera, Inc. 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; + int ret; + + spin_lock(&lstat->lock); + if (lstat->last_intr_fail_ip_family == AF_INET6) + ret = snprintf(page, PAGE_SIZE, "ipv6\n"); + else + ret = snprintf(page, PAGE_SIZE, "ipv4\n"); + spin_unlock(&lstat->lock); + + return ret; +} +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; + int ret; + + spin_lock(&lstat->lock); + ret = snprintf(page, PAGE_SIZE, "%s\n", lstat->last_intr_fail_ip_addr); + spin_unlock(&lstat->lock); + + return ret; +} +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 = 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 = 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 = se_sess->fabric_sess_ptr; + if (sess) + ret = snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&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 = se_sess->fabric_sess_ptr; + if (sess) + ret = snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&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 = se_sess->fabric_sess_ptr; + if (sess) + ret = snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&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 = se_sess->fabric_sess_ptr; + if (sess) + ret = snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&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 = se_sess->fabric_sess_ptr; + if (sess) + ret = snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&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 = se_sess->fabric_sess_ptr; + if (sess) + ret = snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&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 00000000000..3ff76b4faad --- /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 00000000000..78404b1cc0b --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tmr.c @@ -0,0 +1,849 @@ +/******************************************************************************* + * This file contains the iSCSI Target specific Task Management functions. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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/scsi_device.h> +#include <scsi/iscsi_proto.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/iscsi/iscsi_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 (iscsi_sna_gte(be32_to_cpu(hdr->refcmdsn), conn->sess->exp_cmd_sn) && + iscsi_sna_lte(be32_to_cpu(hdr->refcmdsn), conn->sess->max_cmd_sn)) ? + ISCSI_TMF_RSP_COMPLETE : ISCSI_TMF_RSP_NO_TASK; + } + if (ref_cmd->cmd_sn != be32_to_cpu(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 = (__force u32)hdr->rtt; + tmr_req->ref_cmd = ref_cmd; + tmr_req->exp_data_sn = be32_to_cpu(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 (!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, ref_lun; + + 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 Max*DataSegmentLength. + */ + 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; + } + if (cr->maxxmitdatasegmentlength != + conn->conn_ops->MaxXmitDataSegmentLength) { + pr_err("Unable to perform connection recovery for" + " differing MaxXmitDataSegmentLength, rejecting" + " TMR TASK_REASSIGN.\n"); + return ISCSI_TMF_RSP_REJECTED; + } + + ref_lun = scsilun_to_int(&hdr->lun); + if (ref_lun != ref_cmd->se_cmd.orig_fe_lun) { + pr_err("Unable to perform connection recovery for" + " differing ref_lun: %d ref_cmd orig_fe_lun: %u\n", + ref_lun, ref_cmd->se_cmd.orig_fe_lun); + return ISCSI_TMF_RSP_REJECTED; + } + + se_tmr->ref_task_tag = (__force u32)hdr->rtt; + tmr_req->ref_cmd = ref_cmd; + tmr_req->exp_data_sn = be32_to_cpu(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 iscsi_cmd *cmd = tmr_req->ref_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_conn_node, &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 (!(cmd->se_cmd.transport_state & CMD_T_SENT)) { + pr_debug("WRITE ITT: 0x%08x: t_state: %d" + " never sent to transport\n", + cmd->init_task_tag, cmd->se_cmd.t_state); + target_execute_cmd(se_cmd); + return 0; + } + + 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->se_cmd.data_length) { + no_build_r2ts = 1; + length = (cmd->se_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 conn->conn_transport->iscsit_get_dataout(conn, cmd, true); +} + +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 (!(cmd->se_cmd.transport_state & CMD_T_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_handle_cdb_direct(se_cmd); + return 0; + } + + if (!(se_cmd->transport_state & CMD_T_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 iscsi_cmd *cmd = tmr_req->ref_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_conn_node, &conn->conn_cmd_list); + spin_unlock_bh(&conn->cmd_lock); + + if (cmd->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 iscsi_cmd *cmd; + int ret = 0; + + if (!tmr_req->ref_cmd) { + pr_err("TMR Request is missing a RefCmd struct iscsi_cmd.\n"); + return -1; + } + cmd = tmr_req->ref_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. + */ +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; +} +EXPORT_SYMBOL(iscsit_tmr_post_handler); + +/* + * Nothing to do here, but leave it for good measure. :-) + */ +static 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; + } + } + } +} + +static int iscsit_task_reassign_prepare_write( + struct iscsi_tmr_req *tmr_req, + struct iscsi_conn *conn) +{ + struct iscsi_cmd *cmd = tmr_req->ref_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 iscsi_cmd *ref_cmd = tmr_req->ref_cmd; + + if (ref_cmd->iscsi_opcode != ISCSI_OP_SCSI_CMD) + return 0; + + if (ref_cmd->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 00000000000..142e992cb09 --- /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 00000000000..c3cb5c15efd --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tpg.c @@ -0,0 +1,881 @@ +/******************************************************************************* + * This file contains iSCSI Target Portal Group related functions. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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_fabric.h> +#include <target/target_core_configfs.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" + +#include <target/iscsi/iscsi_transport.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); + sema_init(&tpg->np_login_sem, 1); + 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, 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_tpg_np **tpg_np_out) +{ + 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_ACTIVE) { + 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) { + *tpg_np_out = tpg_np; + kref_get(&tpg_np->tpg_np_kref); + 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, + bool shutdown) +{ + if (!tpg_np->tpg_np) { + pr_err("struct iscsi_tpg_np->tpg_np is NULL!\n"); + return; + } + + if (shutdown) + tpg_np->tpg_np->enabled = false; + iscsit_reset_np_thread(tpg_np->tpg_np, tpg_np, tpg, shutdown); +} + +static void iscsit_clear_tpg_np_login_threads( + struct iscsi_portal_group *tpg, + bool shutdown) +{ + 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, shutdown); + 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; + a->demo_mode_discovery = TA_DEMO_MODE_DISCOVERY; + a->default_erl = TA_DEFAULT_ERL; + a->t10_pi = TA_DEFAULT_T10_PI; +} + +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; + + tpg->tpg_attrib.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; + int ret; + + 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 -EINVAL; + } + + if (tpg->tpg_attrib.authentication) { + if (!strcmp(param->value, NONE)) { + ret = iscsi_update_param_value(param, CHAP); + if (ret) + goto err; + } + + ret = iscsit_ta_authentication(tpg, 1); + if (ret < 0) + goto err; + } + + 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; + +err: + spin_unlock(&tpg->tpg_state_lock); + return ret; +} + +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, false); + + 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; +} + +static bool iscsit_tpg_check_network_portal( + struct iscsi_tiqn *tiqn, + struct __kernel_sockaddr_storage *sockaddr, + int network_transport) +{ + struct iscsi_portal_group *tpg; + struct iscsi_tpg_np *tpg_np; + struct iscsi_np *np; + bool match = false; + + spin_lock(&tiqn->tiqn_tpg_lock); + list_for_each_entry(tpg, &tiqn->tiqn_tpg_list, tpg_list) { + + spin_lock(&tpg->tpg_np_lock); + list_for_each_entry(tpg_np, &tpg->tpg_gnp_list, tpg_np_list) { + np = tpg_np->tpg_np; + + match = iscsit_check_np_match(sockaddr, np, + network_transport); + if (match) + break; + } + spin_unlock(&tpg->tpg_np_lock); + } + spin_unlock(&tiqn->tiqn_tpg_lock); + + return match; +} + +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; + + if (!tpg_np_parent) { + if (iscsit_tpg_check_network_portal(tpg->tpg_tiqn, sockaddr, + network_transport)) { + pr_err("Network Portal: %s already exists on a" + " different TPG on %s\n", ip_str, + tpg->tpg_tiqn->tiqn); + return ERR_PTR(-EEXIST); + } + } + + 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); + init_completion(&tpg_np->tpg_np_comp); + kref_init(&tpg_np->tpg_np_kref); + tpg_np->tpg_np = np; + np->tpg_np = tpg_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_transport->name); + + 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, true); + + 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_transport->name); + + 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 -EINVAL; + } + + 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"); + + if (flag == 1 && a->cache_dynamic_acls == 0) { + pr_debug("Explicitly setting cache_dynamic_acls=1 when " + "generate_node_acls=1\n"); + a->cache_dynamic_acls = 1; + } + + 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; + } + + if (a->generate_node_acls == 1 && flag == 0) { + pr_debug("Skipping cache_dynamic_acls=0 when" + " generate_node_acls=1\n"); + return 0; + } + + 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; +} + +int iscsit_ta_demo_mode_discovery( + 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_discovery = flag; + pr_debug("iSCSI_TPG[%hu] - Demo Mode Discovery bit:" + " %s\n", tpg->tpgt, (a->demo_mode_discovery) ? + "ON" : "OFF"); + + return 0; +} + +int iscsit_ta_default_erl( + struct iscsi_portal_group *tpg, + u32 default_erl) +{ + struct iscsi_tpg_attrib *a = &tpg->tpg_attrib; + + if ((default_erl != 0) && (default_erl != 1) && (default_erl != 2)) { + pr_err("Illegal value for default_erl: %u\n", default_erl); + return -EINVAL; + } + + a->default_erl = default_erl; + pr_debug("iSCSI_TPG[%hu] - DefaultERL: %u\n", tpg->tpgt, a->default_erl); + + return 0; +} + +int iscsit_ta_t10_pi( + 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->t10_pi = flag; + pr_debug("iSCSI_TPG[%hu] - T10 Protection information bit:" + " %s\n", tpg->tpgt, (a->t10_pi) ? + "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 00000000000..e7265337bc4 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tpg.h @@ -0,0 +1,43 @@ +#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 *, struct iscsi_tpg_np **); +extern int iscsit_get_tpg(struct iscsi_portal_group *); +extern void iscsit_put_tpg(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); +extern int iscsit_ta_demo_mode_discovery(struct iscsi_portal_group *, u32); +extern int iscsit_ta_default_erl(struct iscsi_portal_group *, u32); +extern int iscsit_ta_t10_pi(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 00000000000..601e9cc61e9 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tq.c @@ -0,0 +1,513 @@ +/******************************************************************************* + * This file contains the iSCSI Login Thread and Thread Queue functions. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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); +} + +static 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; + } + + ts = list_first_entry(&inactive_ts_list, struct iscsi_thread_set, ts_list); + + list_del(&ts->ts_list); + iscsit_global->inactive_ts--; + spin_unlock(&inactive_ts_lock); + + return ts; +} + +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_restart_comp); + init_completion(&ts->tx_restart_comp); + init_completion(&ts->rx_start_comp); + init_completion(&ts->tx_start_comp); + sema_init(&ts->ts_activate_sem, 0); + + 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; +} + +static void iscsi_deallocate_thread_one(struct iscsi_thread_set *ts) +{ + spin_lock_bh(&ts->ts_state_lock); + ts->status = ISCSI_THREAD_SET_DIE; + + if (ts->rx_thread) { + complete(&ts->rx_start_comp); + spin_unlock_bh(&ts->ts_state_lock); + kthread_stop(ts->rx_thread); + spin_lock_bh(&ts->ts_state_lock); + } + if (ts->tx_thread) { + complete(&ts->tx_start_comp); + spin_unlock_bh(&ts->ts_state_lock); + kthread_stop(ts->tx_thread); + spin_lock_bh(&ts->ts_state_lock); + } + spin_unlock_bh(&ts->ts_state_lock); + /* + * 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); + + kfree(ts); +} + +void iscsi_deallocate_thread_sets(void) +{ + struct iscsi_thread_set *ts = NULL; + u32 released_count = 0; + + while ((ts = iscsi_get_ts_from_inactive_list())) { + + iscsi_deallocate_thread_one(ts); + released_count++; + } + + 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; + + iscsi_deallocate_thread_one(ts); + released_count++; + } + + 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; + ts->status = ISCSI_THREAD_SET_ACTIVE; + spin_unlock_bh(&ts->ts_state_lock); + + complete(&ts->rx_start_comp); + complete(&ts->tx_start_comp); + + down(&ts->ts_activate_sem); +} + +struct iscsi_thread_set *iscsi_get_thread_set(void) +{ + struct iscsi_thread_set *ts; + +get_set: + ts = iscsi_get_ts_from_inactive_list(); + if (!ts) { + iscsi_allocate_thread_sets(1); + 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); + sema_init(&ts->ts_activate_sem, 0); + + 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 || kthread_should_stop() || + 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; + } + + if (ts->status != ISCSI_THREAD_SET_DIE) + 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; + + iscsi_check_to_add_additional_sets(); + + spin_lock_bh(&ts->ts_state_lock); + if (!ts->conn) { + pr_err("struct iscsi_thread_set->conn is NULL for" + " RX thread_id: %s/%d\n", current->comm, current->pid); + spin_unlock_bh(&ts->ts_state_lock); + return NULL; + } + ts->thread_clear |= ISCSI_CLEAR_RX_THREAD; + spin_unlock_bh(&ts->ts_state_lock); + + up(&ts->ts_activate_sem); + + 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; + } + + if (ts->status != ISCSI_THREAD_SET_DIE) + 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; + + iscsi_check_to_add_additional_sets(); + + spin_lock_bh(&ts->ts_state_lock); + if (!ts->conn) { + pr_err("struct iscsi_thread_set->conn is NULL for" + " TX thread_id: %s/%d\n", current->comm, current->pid); + spin_unlock_bh(&ts->ts_state_lock); + return NULL; + } + ts->thread_clear |= ISCSI_CLEAR_TX_THREAD; + spin_unlock_bh(&ts->ts_state_lock); + + up(&ts->ts_activate_sem); + + 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; + } + + 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 00000000000..cc1eede5ab3 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_tq.h @@ -0,0 +1,84 @@ +#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 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 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; + struct semaphore ts_activate_sem; +}; + +#endif /*** ISCSI_THREAD_QUEUE_H ***/ diff --git a/drivers/target/iscsi/iscsi_target_transport.c b/drivers/target/iscsi/iscsi_target_transport.c new file mode 100644 index 00000000000..882728fac30 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_transport.c @@ -0,0 +1,55 @@ +#include <linux/spinlock.h> +#include <linux/list.h> +#include <target/iscsi/iscsi_transport.h> + +static LIST_HEAD(g_transport_list); +static DEFINE_MUTEX(transport_mutex); + +struct iscsit_transport *iscsit_get_transport(int type) +{ + struct iscsit_transport *t; + + mutex_lock(&transport_mutex); + list_for_each_entry(t, &g_transport_list, t_node) { + if (t->transport_type == type) { + if (t->owner && !try_module_get(t->owner)) { + t = NULL; + } + mutex_unlock(&transport_mutex); + return t; + } + } + mutex_unlock(&transport_mutex); + + return NULL; +} + +void iscsit_put_transport(struct iscsit_transport *t) +{ + if (t->owner) + module_put(t->owner); +} + +int iscsit_register_transport(struct iscsit_transport *t) +{ + INIT_LIST_HEAD(&t->t_node); + + mutex_lock(&transport_mutex); + list_add_tail(&t->t_node, &g_transport_list); + mutex_unlock(&transport_mutex); + + pr_debug("Registered iSCSI transport: %s\n", t->name); + + return 0; +} +EXPORT_SYMBOL(iscsit_register_transport); + +void iscsit_unregister_transport(struct iscsit_transport *t) +{ + mutex_lock(&transport_mutex); + list_del(&t->t_node); + mutex_unlock(&transport_mutex); + + pr_debug("Unregistered iSCSI transport: %s\n", t->name); +} +EXPORT_SYMBOL(iscsit_unregister_transport); diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c new file mode 100644 index 00000000000..fd90b28f1d9 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -0,0 +1,1510 @@ +/******************************************************************************* + * This file contains the iSCSI Target specific utility functions. + * + * (c) Copyright 2007-2013 Datera, Inc. + * + * 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 <linux/percpu_ida.h> +#include <scsi/scsi_tcq.h> +#include <scsi/iscsi_proto.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_configfs.h> +#include <target/iscsi/iscsi_transport.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, int state) +{ + struct iscsi_cmd *cmd; + struct se_session *se_sess = conn->sess->se_sess; + int size, tag; + + tag = percpu_ida_alloc(&se_sess->sess_tag_pool, state); + if (tag < 0) + return NULL; + + size = sizeof(struct iscsi_cmd) + conn->conn_transport->priv_size; + cmd = (struct iscsi_cmd *)(se_sess->sess_cmd_map + (tag * size)); + memset(cmd, 0, size); + + cmd->se_cmd.map_tag = tag; + cmd->conn = conn; + INIT_LIST_HEAD(&cmd->i_conn_node); + INIT_LIST_HEAD(&cmd->datain_list); + INIT_LIST_HEAD(&cmd->cmd_r2t_list); + 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; +} +EXPORT_SYMBOL(iscsit_allocate_cmd); + +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, ignoring.\n", cmdsn, + sess->max_cmd_sn); + ret = CMDSN_MAXCMDSN_OVERRUN; + + } 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, + unsigned char *buf, __be32 cmdsn) +{ + int ret, cmdsn_ret; + bool reject = false; + u8 reason = ISCSI_REASON_BOOKMARK_NO_RESOURCES; + + mutex_lock(&conn->sess->cmdsn_mutex); + + cmdsn_ret = iscsit_check_received_cmdsn(conn->sess, be32_to_cpu(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); + else if (ret < 0) { + reject = true; + ret = CMDSN_ERROR_CANNOT_RECOVER; + } + break; + case CMDSN_HIGHER_THAN_EXP: + ret = iscsit_handle_ooo_cmdsn(conn->sess, cmd, be32_to_cpu(cmdsn)); + if (ret < 0) { + reject = true; + ret = CMDSN_ERROR_CANNOT_RECOVER; + break; + } + ret = CMDSN_HIGHER_THAN_EXP; + break; + case CMDSN_LOWER_THAN_EXP: + case CMDSN_MAXCMDSN_OVERRUN: + default: + cmd->i_state = ISTATE_REMOVE; + iscsit_add_cmd_to_immediate_queue(cmd, conn, cmd->i_state); + /* + * Existing callers for iscsit_sequence_cmd() will silently + * ignore commands with CMDSN_LOWER_THAN_EXP, so force this + * return for CMDSN_MAXCMDSN_OVERRUN as well.. + */ + ret = CMDSN_LOWER_THAN_EXP; + break; + } + mutex_unlock(&conn->sess->cmdsn_mutex); + + if (reject) + iscsit_reject_cmd(cmd, reason, buf); + + return ret; +} +EXPORT_SYMBOL(iscsit_sequence_cmd); + +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->se_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->se_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, + itt_t init_task_tag) +{ + struct iscsi_cmd *cmd; + + spin_lock_bh(&conn->cmd_lock); + list_for_each_entry(cmd, &conn->conn_cmd_list, i_conn_node) { + 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, + itt_t 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_conn_node) { + 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_conn_node) { + 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, + itt_t 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_conn_node) { + 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_conn_node) { + 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(&conn->queues_wq); +} + +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; + } + qr = list_first_entry(&conn->immed_queue_list, + struct iscsi_queue_req, qr_list); + + 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(&conn->queues_wq); +} + +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; + } + + qr = list_first_entry(&conn->response_queue_list, + struct iscsi_queue_req, qr_list); + + 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)); + } +} + +bool iscsit_conn_all_queues_empty(struct iscsi_conn *conn) +{ + bool empty; + + spin_lock_bh(&conn->immed_queue_lock); + empty = list_empty(&conn->immed_queue_list); + spin_unlock_bh(&conn->immed_queue_lock); + + if (!empty) + return empty; + + spin_lock_bh(&conn->response_queue_lock); + empty = list_empty(&conn->response_queue_list); + spin_unlock_bh(&conn->response_queue_lock); + + return empty; +} + +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_session *sess; + struct se_cmd *se_cmd = &cmd->se_cmd; + + if (cmd->conn) + sess = cmd->conn->sess; + else + sess = cmd->sess; + + BUG_ON(!sess || !sess->se_sess); + + kfree(cmd->buf_ptr); + kfree(cmd->pdu_list); + kfree(cmd->seq_list); + kfree(cmd->tmr_req); + kfree(cmd->iov_data); + kfree(cmd->text_in_ptr); + + percpu_ida_free(&sess->se_sess->sess_tag_pool, se_cmd->map_tag); +} +EXPORT_SYMBOL(iscsit_release_cmd); + +void __iscsit_free_cmd(struct iscsi_cmd *cmd, bool scsi_cmd, + bool check_queues) +{ + struct iscsi_conn *conn = cmd->conn; + + if (scsi_cmd) { + if (cmd->data_direction == DMA_TO_DEVICE) { + iscsit_stop_dataout_timer(cmd); + iscsit_free_r2ts_from_list(cmd); + } + if (cmd->data_direction == DMA_FROM_DEVICE) + iscsit_free_all_datain_reqs(cmd); + } + + if (conn && check_queues) { + iscsit_remove_cmd_from_immediate_queue(cmd, conn); + iscsit_remove_cmd_from_response_queue(cmd, conn); + } +} + +void iscsit_free_cmd(struct iscsi_cmd *cmd, bool shutdown) +{ + struct se_cmd *se_cmd = NULL; + int rc; + /* + * Determine if a struct se_cmd is associated with + * this struct iscsi_cmd. + */ + switch (cmd->iscsi_opcode) { + case ISCSI_OP_SCSI_CMD: + se_cmd = &cmd->se_cmd; + __iscsit_free_cmd(cmd, true, shutdown); + /* + * Fallthrough + */ + case ISCSI_OP_SCSI_TMFUNC: + rc = transport_generic_free_cmd(&cmd->se_cmd, shutdown); + if (!rc && shutdown && se_cmd && se_cmd->se_sess) { + __iscsit_free_cmd(cmd, true, shutdown); + target_put_sess_cmd(se_cmd->se_sess, se_cmd); + } + break; + case ISCSI_OP_REJECT: + /* + * Handle special case for REJECT when iscsi_add_reject*() has + * overwritten the original iscsi_opcode assignment, and the + * associated cmd->se_cmd needs to be released. + */ + if (cmd->se_cmd.se_tfo != NULL) { + se_cmd = &cmd->se_cmd; + __iscsit_free_cmd(cmd, true, shutdown); + + rc = transport_generic_free_cmd(&cmd->se_cmd, shutdown); + if (!rc && shutdown && se_cmd->se_sess) { + __iscsit_free_cmd(cmd, true, shutdown); + target_put_sess_cmd(se_cmd->se_sess, se_cmd); + } + break; + } + /* Fall-through */ + default: + __iscsit_free_cmd(cmd, false, shutdown); + iscsit_release_cmd(cmd); + break; + } +} + +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); +} + +/* + * 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, TASK_RUNNING); + 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 = RESERVED_ITT; + 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_conn_node, &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, + 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++; + atomic_long_inc(&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, iov_off; + +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; + /* + * Set iov_off used by padding and data digest tx_data() calls below + * in order to determine proper offset into cmd->iov_data[] + */ + if (conn->conn_ops->DataDigest) { + data_len -= ISCSI_CRC_LEN; + if (cmd->padding) + iov_off = (cmd->iov_data_count - 2); + else + iov_off = (cmd->iov_data_count - 1); + } else { + iov_off = (cmd->iov_data_count - 1); + } + /* + * 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[iov_off++]; + + 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[iov_off]; + + 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) +{ + struct iscsi_login_rsp *hdr; + struct iscsi_login *login = conn->conn_login; + + login->login_failed = 1; + iscsit_collect_login_stats(conn, status_class, status_detail); + + memset(&login->rsp[0], 0, ISCSI_HDR_LEN); + + hdr = (struct iscsi_login_rsp *)&login->rsp[0]; + hdr->opcode = ISCSI_OP_LOGIN_RSP; + hdr->status_class = status_class; + hdr->status_detail = status_detail; + hdr->itt = conn->login_itt; + + return conn->conn_transport->iscsit_put_login_tx(conn, login, 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; + struct kvec *iov_p; + struct msghdr msg; + + if (!conn || !conn->sock || !conn->conn_ops) + return -1; + + memset(&msg, 0, sizeof(struct msghdr)); + + 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); + } + + 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; + struct kvec *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)); + + 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); + } + + 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; + + 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; + + 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->login_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 00000000000..a68508c4fec --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_util.h @@ -0,0 +1,62 @@ +#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_alloc_cmd(struct iscsi_conn *, gfp_t); +extern struct iscsi_cmd *iscsit_allocate_cmd(struct iscsi_conn *, int); +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); +extern int iscsit_sequence_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char * ,__be32 cmdsn); +extern int iscsit_check_unsolicited_dataout(struct iscsi_cmd *, unsigned char *); +extern struct iscsi_cmd *iscsit_find_cmd_from_itt(struct iscsi_conn *, itt_t); +extern struct iscsi_cmd *iscsit_find_cmd_from_itt_or_dump(struct iscsi_conn *, + itt_t, 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 **, itt_t); +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 bool iscsit_conn_all_queues_empty(struct iscsi_conn *); +extern void iscsit_free_queue_reqs_for_conn(struct iscsi_conn *); +extern void iscsit_release_cmd(struct iscsi_cmd *); +extern void __iscsit_free_cmd(struct iscsi_cmd *, bool, bool); +extern void iscsit_free_cmd(struct iscsi_cmd *, bool); +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/loopback/Kconfig b/drivers/target/loopback/Kconfig new file mode 100644 index 00000000000..abe8ecbcdf0 --- /dev/null +++ b/drivers/target/loopback/Kconfig @@ -0,0 +1,5 @@ +config LOOPBACK_TARGET + tristate "TCM Virtual SAS target and Linux/SCSI LDD fabric loopback module" + help + Say Y here to enable the TCM Virtual SAS target and Linux/SCSI LLD + fabric loopback module. diff --git a/drivers/target/loopback/Makefile b/drivers/target/loopback/Makefile new file mode 100644 index 00000000000..6abebdf9565 --- /dev/null +++ b/drivers/target/loopback/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_LOOPBACK_TARGET) += tcm_loop.o diff --git a/drivers/target/loopback/tcm_loop.c b/drivers/target/loopback/tcm_loop.c new file mode 100644 index 00000000000..8c64b8776a9 --- /dev/null +++ b/drivers/target/loopback/tcm_loop.c @@ -0,0 +1,1611 @@ +/******************************************************************************* + * + * This file contains the Linux/SCSI LLD virtual SCSI initiator driver + * for emulated SAS initiator ports + * + * © Copyright 2011-2013 Datera, Inc. + * + * Licensed to the Linux Foundation under the General Public License (GPL) version 2. + * + * Author: Nicholas A. Bellinger <nab@risingtidesystems.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + ****************************************************************************/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/configfs.h> +#include <scsi/scsi.h> +#include <scsi/scsi_tcq.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> + +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_fabric_configfs.h> +#include <target/target_core_configfs.h> + +#include "tcm_loop.h" + +#define to_tcm_loop_hba(hba) container_of(hba, struct tcm_loop_hba, dev) + +/* Local pointer to allocated TCM configfs fabric module */ +static struct target_fabric_configfs *tcm_loop_fabric_configfs; + +static struct workqueue_struct *tcm_loop_workqueue; +static struct kmem_cache *tcm_loop_cmd_cache; + +static int tcm_loop_hba_no_cnt; + +static int tcm_loop_queue_status(struct se_cmd *se_cmd); + +/* + * Called from struct target_core_fabric_ops->check_stop_free() + */ +static int tcm_loop_check_stop_free(struct se_cmd *se_cmd) +{ + /* + * Do not release struct se_cmd's containing a valid TMR + * pointer. These will be released directly in tcm_loop_device_reset() + * with transport_generic_free_cmd(). + */ + if (se_cmd->se_cmd_flags & SCF_SCSI_TMR_CDB) + return 0; + /* + * Release the struct se_cmd, which will make a callback to release + * struct tcm_loop_cmd * in tcm_loop_deallocate_core_cmd() + */ + transport_generic_free_cmd(se_cmd, 0); + return 1; +} + +static void tcm_loop_release_cmd(struct se_cmd *se_cmd) +{ + struct tcm_loop_cmd *tl_cmd = container_of(se_cmd, + struct tcm_loop_cmd, tl_se_cmd); + + kmem_cache_free(tcm_loop_cmd_cache, tl_cmd); +} + +static int tcm_loop_show_info(struct seq_file *m, struct Scsi_Host *host) +{ + seq_printf(m, "tcm_loop_proc_info()\n"); + return 0; +} + +static int tcm_loop_driver_probe(struct device *); +static int tcm_loop_driver_remove(struct device *); + +static int pseudo_lld_bus_match(struct device *dev, + struct device_driver *dev_driver) +{ + return 1; +} + +static struct bus_type tcm_loop_lld_bus = { + .name = "tcm_loop_bus", + .match = pseudo_lld_bus_match, + .probe = tcm_loop_driver_probe, + .remove = tcm_loop_driver_remove, +}; + +static struct device_driver tcm_loop_driverfs = { + .name = "tcm_loop", + .bus = &tcm_loop_lld_bus, +}; +/* + * Used with root_device_register() in tcm_loop_alloc_core_bus() below + */ +struct device *tcm_loop_primary; + +/* + * Copied from drivers/scsi/libfc/fc_fcp.c:fc_change_queue_depth() and + * drivers/scsi/libiscsi.c:iscsi_change_queue_depth() + */ +static int tcm_loop_change_queue_depth( + struct scsi_device *sdev, + int depth, + int reason) +{ + switch (reason) { + case SCSI_QDEPTH_DEFAULT: + scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), depth); + break; + case SCSI_QDEPTH_QFULL: + scsi_track_queue_full(sdev, depth); + break; + case SCSI_QDEPTH_RAMP_UP: + scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), depth); + break; + default: + return -EOPNOTSUPP; + } + return sdev->queue_depth; +} + +static int tcm_loop_change_queue_type(struct scsi_device *sdev, int tag) +{ + if (sdev->tagged_supported) { + scsi_set_tag_type(sdev, tag); + + if (tag) + scsi_activate_tcq(sdev, sdev->queue_depth); + else + scsi_deactivate_tcq(sdev, sdev->queue_depth); + } else + tag = 0; + + return tag; +} + +/* + * Locate the SAM Task Attr from struct scsi_cmnd * + */ +static int tcm_loop_sam_attr(struct scsi_cmnd *sc) +{ + if (sc->device->tagged_supported) { + switch (sc->tag) { + case HEAD_OF_QUEUE_TAG: + return MSG_HEAD_TAG; + case ORDERED_QUEUE_TAG: + return MSG_ORDERED_TAG; + default: + break; + } + } + + return MSG_SIMPLE_TAG; +} + +static void tcm_loop_submission_work(struct work_struct *work) +{ + struct tcm_loop_cmd *tl_cmd = + container_of(work, struct tcm_loop_cmd, work); + struct se_cmd *se_cmd = &tl_cmd->tl_se_cmd; + struct scsi_cmnd *sc = tl_cmd->sc; + struct tcm_loop_nexus *tl_nexus; + struct tcm_loop_hba *tl_hba; + struct tcm_loop_tpg *tl_tpg; + struct scatterlist *sgl_bidi = NULL; + u32 sgl_bidi_count = 0, transfer_length; + int rc; + + tl_hba = *(struct tcm_loop_hba **)shost_priv(sc->device->host); + tl_tpg = &tl_hba->tl_hba_tpgs[sc->device->id]; + + /* + * Ensure that this tl_tpg reference from the incoming sc->device->id + * has already been configured via tcm_loop_make_naa_tpg(). + */ + if (!tl_tpg->tl_hba) { + set_host_byte(sc, DID_NO_CONNECT); + goto out_done; + } + if (tl_tpg->tl_transport_status == TCM_TRANSPORT_OFFLINE) { + set_host_byte(sc, DID_TRANSPORT_DISRUPTED); + goto out_done; + } + tl_nexus = tl_hba->tl_nexus; + if (!tl_nexus) { + scmd_printk(KERN_ERR, sc, "TCM_Loop I_T Nexus" + " does not exist\n"); + set_host_byte(sc, DID_ERROR); + goto out_done; + } + if (scsi_bidi_cmnd(sc)) { + struct scsi_data_buffer *sdb = scsi_in(sc); + + sgl_bidi = sdb->table.sgl; + sgl_bidi_count = sdb->table.nents; + se_cmd->se_cmd_flags |= SCF_BIDI; + + } + + transfer_length = scsi_transfer_length(sc); + if (!scsi_prot_sg_count(sc) && + scsi_get_prot_op(sc) != SCSI_PROT_NORMAL) { + se_cmd->prot_pto = true; + /* + * loopback transport doesn't support + * WRITE_GENERATE, READ_STRIP protection + * information operations, go ahead unprotected. + */ + transfer_length = scsi_bufflen(sc); + } + + rc = target_submit_cmd_map_sgls(se_cmd, tl_nexus->se_sess, sc->cmnd, + &tl_cmd->tl_sense_buf[0], tl_cmd->sc->device->lun, + transfer_length, tcm_loop_sam_attr(sc), + sc->sc_data_direction, 0, + scsi_sglist(sc), scsi_sg_count(sc), + sgl_bidi, sgl_bidi_count, + scsi_prot_sglist(sc), scsi_prot_sg_count(sc)); + if (rc < 0) { + set_host_byte(sc, DID_NO_CONNECT); + goto out_done; + } + return; + +out_done: + kmem_cache_free(tcm_loop_cmd_cache, tl_cmd); + sc->scsi_done(sc); + return; +} + +/* + * ->queuecommand can be and usually is called from interrupt context, so + * defer the actual submission to a workqueue. + */ +static int tcm_loop_queuecommand(struct Scsi_Host *sh, struct scsi_cmnd *sc) +{ + struct tcm_loop_cmd *tl_cmd; + + pr_debug("tcm_loop_queuecommand() %d:%d:%d:%d got CDB: 0x%02x" + " scsi_buf_len: %u\n", sc->device->host->host_no, + sc->device->id, sc->device->channel, sc->device->lun, + sc->cmnd[0], scsi_bufflen(sc)); + + tl_cmd = kmem_cache_zalloc(tcm_loop_cmd_cache, GFP_ATOMIC); + if (!tl_cmd) { + pr_err("Unable to allocate struct tcm_loop_cmd\n"); + set_host_byte(sc, DID_ERROR); + sc->scsi_done(sc); + return 0; + } + + tl_cmd->sc = sc; + tl_cmd->sc_cmd_tag = sc->tag; + INIT_WORK(&tl_cmd->work, tcm_loop_submission_work); + queue_work(tcm_loop_workqueue, &tl_cmd->work); + return 0; +} + +/* + * Called from SCSI EH process context to issue a LUN_RESET TMR + * to struct scsi_device + */ +static int tcm_loop_issue_tmr(struct tcm_loop_tpg *tl_tpg, + struct tcm_loop_nexus *tl_nexus, + int lun, int task, enum tcm_tmreq_table tmr) +{ + struct se_cmd *se_cmd = NULL; + struct se_session *se_sess; + struct se_portal_group *se_tpg; + struct tcm_loop_cmd *tl_cmd = NULL; + struct tcm_loop_tmr *tl_tmr = NULL; + int ret = TMR_FUNCTION_FAILED, rc; + + tl_cmd = kmem_cache_zalloc(tcm_loop_cmd_cache, GFP_KERNEL); + if (!tl_cmd) { + pr_err("Unable to allocate memory for tl_cmd\n"); + return ret; + } + + tl_tmr = kzalloc(sizeof(struct tcm_loop_tmr), GFP_KERNEL); + if (!tl_tmr) { + pr_err("Unable to allocate memory for tl_tmr\n"); + goto release; + } + init_waitqueue_head(&tl_tmr->tl_tmr_wait); + + se_cmd = &tl_cmd->tl_se_cmd; + se_tpg = &tl_tpg->tl_se_tpg; + se_sess = tl_nexus->se_sess; + /* + * Initialize struct se_cmd descriptor from target_core_mod infrastructure + */ + transport_init_se_cmd(se_cmd, se_tpg->se_tpg_tfo, se_sess, 0, + DMA_NONE, MSG_SIMPLE_TAG, + &tl_cmd->tl_sense_buf[0]); + + rc = core_tmr_alloc_req(se_cmd, tl_tmr, tmr, GFP_KERNEL); + if (rc < 0) + goto release; + + if (tmr == TMR_ABORT_TASK) + se_cmd->se_tmr_req->ref_task_tag = task; + + /* + * Locate the underlying TCM struct se_lun + */ + if (transport_lookup_tmr_lun(se_cmd, lun) < 0) { + ret = TMR_LUN_DOES_NOT_EXIST; + goto release; + } + /* + * Queue the TMR to TCM Core and sleep waiting for + * tcm_loop_queue_tm_rsp() to wake us up. + */ + transport_generic_handle_tmr(se_cmd); + wait_event(tl_tmr->tl_tmr_wait, atomic_read(&tl_tmr->tmr_complete)); + /* + * The TMR LUN_RESET has completed, check the response status and + * then release allocations. + */ + ret = se_cmd->se_tmr_req->response; +release: + if (se_cmd) + transport_generic_free_cmd(se_cmd, 1); + else + kmem_cache_free(tcm_loop_cmd_cache, tl_cmd); + kfree(tl_tmr); + return ret; +} + +static int tcm_loop_abort_task(struct scsi_cmnd *sc) +{ + struct tcm_loop_hba *tl_hba; + struct tcm_loop_nexus *tl_nexus; + struct tcm_loop_tpg *tl_tpg; + int ret = FAILED; + + /* + * Locate the tcm_loop_hba_t pointer + */ + tl_hba = *(struct tcm_loop_hba **)shost_priv(sc->device->host); + /* + * Locate the tl_nexus and se_sess pointers + */ + tl_nexus = tl_hba->tl_nexus; + if (!tl_nexus) { + pr_err("Unable to perform device reset without" + " active I_T Nexus\n"); + return FAILED; + } + + /* + * Locate the tl_tpg pointer from TargetID in sc->device->id + */ + tl_tpg = &tl_hba->tl_hba_tpgs[sc->device->id]; + ret = tcm_loop_issue_tmr(tl_tpg, tl_nexus, sc->device->lun, + sc->tag, TMR_ABORT_TASK); + return (ret == TMR_FUNCTION_COMPLETE) ? SUCCESS : FAILED; +} + +/* + * Called from SCSI EH process context to issue a LUN_RESET TMR + * to struct scsi_device + */ +static int tcm_loop_device_reset(struct scsi_cmnd *sc) +{ + struct tcm_loop_hba *tl_hba; + struct tcm_loop_nexus *tl_nexus; + struct tcm_loop_tpg *tl_tpg; + int ret = FAILED; + + /* + * Locate the tcm_loop_hba_t pointer + */ + tl_hba = *(struct tcm_loop_hba **)shost_priv(sc->device->host); + /* + * Locate the tl_nexus and se_sess pointers + */ + tl_nexus = tl_hba->tl_nexus; + if (!tl_nexus) { + pr_err("Unable to perform device reset without" + " active I_T Nexus\n"); + return FAILED; + } + /* + * Locate the tl_tpg pointer from TargetID in sc->device->id + */ + tl_tpg = &tl_hba->tl_hba_tpgs[sc->device->id]; + ret = tcm_loop_issue_tmr(tl_tpg, tl_nexus, sc->device->lun, + 0, TMR_LUN_RESET); + return (ret == TMR_FUNCTION_COMPLETE) ? SUCCESS : FAILED; +} + +static int tcm_loop_target_reset(struct scsi_cmnd *sc) +{ + struct tcm_loop_hba *tl_hba; + struct tcm_loop_tpg *tl_tpg; + + /* + * Locate the tcm_loop_hba_t pointer + */ + tl_hba = *(struct tcm_loop_hba **)shost_priv(sc->device->host); + if (!tl_hba) { + pr_err("Unable to perform device reset without" + " active I_T Nexus\n"); + return FAILED; + } + /* + * Locate the tl_tpg pointer from TargetID in sc->device->id + */ + tl_tpg = &tl_hba->tl_hba_tpgs[sc->device->id]; + if (tl_tpg) { + tl_tpg->tl_transport_status = TCM_TRANSPORT_ONLINE; + return SUCCESS; + } + return FAILED; +} + +static int tcm_loop_slave_alloc(struct scsi_device *sd) +{ + set_bit(QUEUE_FLAG_BIDI, &sd->request_queue->queue_flags); + return 0; +} + +static int tcm_loop_slave_configure(struct scsi_device *sd) +{ + if (sd->tagged_supported) { + scsi_activate_tcq(sd, sd->queue_depth); + scsi_adjust_queue_depth(sd, MSG_SIMPLE_TAG, + sd->host->cmd_per_lun); + } else { + scsi_adjust_queue_depth(sd, 0, + sd->host->cmd_per_lun); + } + + return 0; +} + +static struct scsi_host_template tcm_loop_driver_template = { + .show_info = tcm_loop_show_info, + .proc_name = "tcm_loopback", + .name = "TCM_Loopback", + .queuecommand = tcm_loop_queuecommand, + .change_queue_depth = tcm_loop_change_queue_depth, + .change_queue_type = tcm_loop_change_queue_type, + .eh_abort_handler = tcm_loop_abort_task, + .eh_device_reset_handler = tcm_loop_device_reset, + .eh_target_reset_handler = tcm_loop_target_reset, + .can_queue = 1024, + .this_id = -1, + .sg_tablesize = 256, + .cmd_per_lun = 1024, + .max_sectors = 0xFFFF, + .use_clustering = DISABLE_CLUSTERING, + .slave_alloc = tcm_loop_slave_alloc, + .slave_configure = tcm_loop_slave_configure, + .module = THIS_MODULE, +}; + +static int tcm_loop_driver_probe(struct device *dev) +{ + struct tcm_loop_hba *tl_hba; + struct Scsi_Host *sh; + int error, host_prot; + + tl_hba = to_tcm_loop_hba(dev); + + sh = scsi_host_alloc(&tcm_loop_driver_template, + sizeof(struct tcm_loop_hba)); + if (!sh) { + pr_err("Unable to allocate struct scsi_host\n"); + return -ENODEV; + } + tl_hba->sh = sh; + + /* + * Assign the struct tcm_loop_hba pointer to struct Scsi_Host->hostdata + */ + *((struct tcm_loop_hba **)sh->hostdata) = tl_hba; + /* + * Setup single ID, Channel and LUN for now.. + */ + sh->max_id = 2; + sh->max_lun = 0; + sh->max_channel = 0; + sh->max_cmd_len = TL_SCSI_MAX_CMD_LEN; + + host_prot = SHOST_DIF_TYPE1_PROTECTION | SHOST_DIF_TYPE2_PROTECTION | + SHOST_DIF_TYPE3_PROTECTION | SHOST_DIX_TYPE1_PROTECTION | + SHOST_DIX_TYPE2_PROTECTION | SHOST_DIX_TYPE3_PROTECTION; + + scsi_host_set_prot(sh, host_prot); + scsi_host_set_guard(sh, SHOST_DIX_GUARD_CRC); + + error = scsi_add_host(sh, &tl_hba->dev); + if (error) { + pr_err("%s: scsi_add_host failed\n", __func__); + scsi_host_put(sh); + return -ENODEV; + } + return 0; +} + +static int tcm_loop_driver_remove(struct device *dev) +{ + struct tcm_loop_hba *tl_hba; + struct Scsi_Host *sh; + + tl_hba = to_tcm_loop_hba(dev); + sh = tl_hba->sh; + + scsi_remove_host(sh); + scsi_host_put(sh); + return 0; +} + +static void tcm_loop_release_adapter(struct device *dev) +{ + struct tcm_loop_hba *tl_hba = to_tcm_loop_hba(dev); + + kfree(tl_hba); +} + +/* + * Called from tcm_loop_make_scsi_hba() in tcm_loop_configfs.c + */ +static int tcm_loop_setup_hba_bus(struct tcm_loop_hba *tl_hba, int tcm_loop_host_id) +{ + int ret; + + tl_hba->dev.bus = &tcm_loop_lld_bus; + tl_hba->dev.parent = tcm_loop_primary; + tl_hba->dev.release = &tcm_loop_release_adapter; + dev_set_name(&tl_hba->dev, "tcm_loop_adapter_%d", tcm_loop_host_id); + + ret = device_register(&tl_hba->dev); + if (ret) { + pr_err("device_register() failed for" + " tl_hba->dev: %d\n", ret); + return -ENODEV; + } + + return 0; +} + +/* + * Called from tcm_loop_fabric_init() in tcl_loop_fabric.c to load the emulated + * tcm_loop SCSI bus. + */ +static int tcm_loop_alloc_core_bus(void) +{ + int ret; + + tcm_loop_primary = root_device_register("tcm_loop_0"); + if (IS_ERR(tcm_loop_primary)) { + pr_err("Unable to allocate tcm_loop_primary\n"); + return PTR_ERR(tcm_loop_primary); + } + + ret = bus_register(&tcm_loop_lld_bus); + if (ret) { + pr_err("bus_register() failed for tcm_loop_lld_bus\n"); + goto dev_unreg; + } + + ret = driver_register(&tcm_loop_driverfs); + if (ret) { + pr_err("driver_register() failed for" + "tcm_loop_driverfs\n"); + goto bus_unreg; + } + + pr_debug("Initialized TCM Loop Core Bus\n"); + return ret; + +bus_unreg: + bus_unregister(&tcm_loop_lld_bus); +dev_unreg: + root_device_unregister(tcm_loop_primary); + return ret; +} + +static void tcm_loop_release_core_bus(void) +{ + driver_unregister(&tcm_loop_driverfs); + bus_unregister(&tcm_loop_lld_bus); + root_device_unregister(tcm_loop_primary); + + pr_debug("Releasing TCM Loop Core BUS\n"); +} + +static char *tcm_loop_get_fabric_name(void) +{ + return "loopback"; +} + +static u8 tcm_loop_get_fabric_proto_ident(struct se_portal_group *se_tpg) +{ + struct tcm_loop_tpg *tl_tpg = se_tpg->se_tpg_fabric_ptr; + struct tcm_loop_hba *tl_hba = tl_tpg->tl_hba; + /* + * tl_proto_id is set at tcm_loop_configfs.c:tcm_loop_make_scsi_hba() + * time based on the protocol dependent prefix of the passed configfs group. + * + * Based upon tl_proto_id, TCM_Loop emulates the requested fabric + * ProtocolID using target_core_fabric_lib.c symbols. + */ + switch (tl_hba->tl_proto_id) { + case SCSI_PROTOCOL_SAS: + return sas_get_fabric_proto_ident(se_tpg); + case SCSI_PROTOCOL_FCP: + return fc_get_fabric_proto_ident(se_tpg); + case SCSI_PROTOCOL_ISCSI: + return iscsi_get_fabric_proto_ident(se_tpg); + default: + pr_err("Unknown tl_proto_id: 0x%02x, using" + " SAS emulation\n", tl_hba->tl_proto_id); + break; + } + + return sas_get_fabric_proto_ident(se_tpg); +} + +static char *tcm_loop_get_endpoint_wwn(struct se_portal_group *se_tpg) +{ + struct tcm_loop_tpg *tl_tpg = se_tpg->se_tpg_fabric_ptr; + /* + * Return the passed NAA identifier for the SAS Target Port + */ + return &tl_tpg->tl_hba->tl_wwn_address[0]; +} + +static u16 tcm_loop_get_tag(struct se_portal_group *se_tpg) +{ + struct tcm_loop_tpg *tl_tpg = se_tpg->se_tpg_fabric_ptr; + /* + * This Tag is used when forming SCSI Name identifier in EVPD=1 0x83 + * to represent the SCSI Target Port. + */ + return tl_tpg->tl_tpgt; +} + +static u32 tcm_loop_get_default_depth(struct se_portal_group *se_tpg) +{ + return 1; +} + +static u32 tcm_loop_get_pr_transport_id( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code, + unsigned char *buf) +{ + struct tcm_loop_tpg *tl_tpg = se_tpg->se_tpg_fabric_ptr; + struct tcm_loop_hba *tl_hba = tl_tpg->tl_hba; + + switch (tl_hba->tl_proto_id) { + case SCSI_PROTOCOL_SAS: + return sas_get_pr_transport_id(se_tpg, se_nacl, pr_reg, + format_code, buf); + case SCSI_PROTOCOL_FCP: + return fc_get_pr_transport_id(se_tpg, se_nacl, pr_reg, + format_code, buf); + case SCSI_PROTOCOL_ISCSI: + return iscsi_get_pr_transport_id(se_tpg, se_nacl, pr_reg, + format_code, buf); + default: + pr_err("Unknown tl_proto_id: 0x%02x, using" + " SAS emulation\n", tl_hba->tl_proto_id); + break; + } + + return sas_get_pr_transport_id(se_tpg, se_nacl, pr_reg, + format_code, buf); +} + +static u32 tcm_loop_get_pr_transport_id_len( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code) +{ + struct tcm_loop_tpg *tl_tpg = se_tpg->se_tpg_fabric_ptr; + struct tcm_loop_hba *tl_hba = tl_tpg->tl_hba; + + switch (tl_hba->tl_proto_id) { + case SCSI_PROTOCOL_SAS: + return sas_get_pr_transport_id_len(se_tpg, se_nacl, pr_reg, + format_code); + case SCSI_PROTOCOL_FCP: + return fc_get_pr_transport_id_len(se_tpg, se_nacl, pr_reg, + format_code); + case SCSI_PROTOCOL_ISCSI: + return iscsi_get_pr_transport_id_len(se_tpg, se_nacl, pr_reg, + format_code); + default: + pr_err("Unknown tl_proto_id: 0x%02x, using" + " SAS emulation\n", tl_hba->tl_proto_id); + break; + } + + return sas_get_pr_transport_id_len(se_tpg, se_nacl, pr_reg, + format_code); +} + +/* + * Used for handling SCSI fabric dependent TransportIDs in SPC-3 and above + * Persistent Reservation SPEC_I_PT=1 and PROUT REGISTER_AND_MOVE operations. + */ +static char *tcm_loop_parse_pr_out_transport_id( + struct se_portal_group *se_tpg, + const char *buf, + u32 *out_tid_len, + char **port_nexus_ptr) +{ + struct tcm_loop_tpg *tl_tpg = se_tpg->se_tpg_fabric_ptr; + struct tcm_loop_hba *tl_hba = tl_tpg->tl_hba; + + switch (tl_hba->tl_proto_id) { + case SCSI_PROTOCOL_SAS: + return sas_parse_pr_out_transport_id(se_tpg, buf, out_tid_len, + port_nexus_ptr); + case SCSI_PROTOCOL_FCP: + return fc_parse_pr_out_transport_id(se_tpg, buf, out_tid_len, + port_nexus_ptr); + case SCSI_PROTOCOL_ISCSI: + return iscsi_parse_pr_out_transport_id(se_tpg, buf, out_tid_len, + port_nexus_ptr); + default: + pr_err("Unknown tl_proto_id: 0x%02x, using" + " SAS emulation\n", tl_hba->tl_proto_id); + break; + } + + return sas_parse_pr_out_transport_id(se_tpg, buf, out_tid_len, + port_nexus_ptr); +} + +/* + * Returning (1) here allows for target_core_mod struct se_node_acl to be generated + * based upon the incoming fabric dependent SCSI Initiator Port + */ +static int tcm_loop_check_demo_mode(struct se_portal_group *se_tpg) +{ + return 1; +} + +static int tcm_loop_check_demo_mode_cache(struct se_portal_group *se_tpg) +{ + return 0; +} + +/* + * Allow I_T Nexus full READ-WRITE access without explict Initiator Node ACLs for + * local virtual Linux/SCSI LLD passthrough into VM hypervisor guest + */ +static int tcm_loop_check_demo_mode_write_protect(struct se_portal_group *se_tpg) +{ + return 0; +} + +/* + * Because TCM_Loop does not use explict ACLs and MappedLUNs, this will + * never be called for TCM_Loop by target_core_fabric_configfs.c code. + * It has been added here as a nop for target_fabric_tf_ops_check() + */ +static int tcm_loop_check_prod_mode_write_protect(struct se_portal_group *se_tpg) +{ + return 0; +} + +static struct se_node_acl *tcm_loop_tpg_alloc_fabric_acl( + struct se_portal_group *se_tpg) +{ + struct tcm_loop_nacl *tl_nacl; + + tl_nacl = kzalloc(sizeof(struct tcm_loop_nacl), GFP_KERNEL); + if (!tl_nacl) { + pr_err("Unable to allocate struct tcm_loop_nacl\n"); + return NULL; + } + + return &tl_nacl->se_node_acl; +} + +static void tcm_loop_tpg_release_fabric_acl( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl) +{ + struct tcm_loop_nacl *tl_nacl = container_of(se_nacl, + struct tcm_loop_nacl, se_node_acl); + + kfree(tl_nacl); +} + +static u32 tcm_loop_get_inst_index(struct se_portal_group *se_tpg) +{ + return 1; +} + +static u32 tcm_loop_sess_get_index(struct se_session *se_sess) +{ + return 1; +} + +static void tcm_loop_set_default_node_attributes(struct se_node_acl *se_acl) +{ + return; +} + +static u32 tcm_loop_get_task_tag(struct se_cmd *se_cmd) +{ + struct tcm_loop_cmd *tl_cmd = container_of(se_cmd, + struct tcm_loop_cmd, tl_se_cmd); + + return tl_cmd->sc_cmd_tag; +} + +static int tcm_loop_get_cmd_state(struct se_cmd *se_cmd) +{ + struct tcm_loop_cmd *tl_cmd = container_of(se_cmd, + struct tcm_loop_cmd, tl_se_cmd); + + return tl_cmd->sc_cmd_state; +} + +static int tcm_loop_shutdown_session(struct se_session *se_sess) +{ + return 0; +} + +static void tcm_loop_close_session(struct se_session *se_sess) +{ + return; +}; + +static int tcm_loop_write_pending(struct se_cmd *se_cmd) +{ + /* + * Since Linux/SCSI has already sent down a struct scsi_cmnd + * sc->sc_data_direction of DMA_TO_DEVICE with struct scatterlist array + * memory, and memory has already been mapped to struct se_cmd->t_mem_list + * format with transport_generic_map_mem_to_cmd(). + * + * We now tell TCM to add this WRITE CDB directly into the TCM storage + * object execution queue. + */ + target_execute_cmd(se_cmd); + return 0; +} + +static int tcm_loop_write_pending_status(struct se_cmd *se_cmd) +{ + return 0; +} + +static int tcm_loop_queue_data_in(struct se_cmd *se_cmd) +{ + struct tcm_loop_cmd *tl_cmd = container_of(se_cmd, + struct tcm_loop_cmd, tl_se_cmd); + struct scsi_cmnd *sc = tl_cmd->sc; + + pr_debug("tcm_loop_queue_data_in() called for scsi_cmnd: %p" + " cdb: 0x%02x\n", sc, sc->cmnd[0]); + + sc->result = SAM_STAT_GOOD; + set_host_byte(sc, DID_OK); + if ((se_cmd->se_cmd_flags & SCF_OVERFLOW_BIT) || + (se_cmd->se_cmd_flags & SCF_UNDERFLOW_BIT)) + scsi_set_resid(sc, se_cmd->residual_count); + sc->scsi_done(sc); + return 0; +} + +static int tcm_loop_queue_status(struct se_cmd *se_cmd) +{ + struct tcm_loop_cmd *tl_cmd = container_of(se_cmd, + struct tcm_loop_cmd, tl_se_cmd); + struct scsi_cmnd *sc = tl_cmd->sc; + + pr_debug("tcm_loop_queue_status() called for scsi_cmnd: %p" + " cdb: 0x%02x\n", sc, sc->cmnd[0]); + + if (se_cmd->sense_buffer && + ((se_cmd->se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) || + (se_cmd->se_cmd_flags & SCF_EMULATED_TASK_SENSE))) { + + memcpy(sc->sense_buffer, se_cmd->sense_buffer, + SCSI_SENSE_BUFFERSIZE); + sc->result = SAM_STAT_CHECK_CONDITION; + set_driver_byte(sc, DRIVER_SENSE); + } else + sc->result = se_cmd->scsi_status; + + set_host_byte(sc, DID_OK); + if ((se_cmd->se_cmd_flags & SCF_OVERFLOW_BIT) || + (se_cmd->se_cmd_flags & SCF_UNDERFLOW_BIT)) + scsi_set_resid(sc, se_cmd->residual_count); + sc->scsi_done(sc); + return 0; +} + +static void tcm_loop_queue_tm_rsp(struct se_cmd *se_cmd) +{ + struct se_tmr_req *se_tmr = se_cmd->se_tmr_req; + struct tcm_loop_tmr *tl_tmr = se_tmr->fabric_tmr_ptr; + /* + * The SCSI EH thread will be sleeping on se_tmr->tl_tmr_wait, go ahead + * and wake up the wait_queue_head_t in tcm_loop_device_reset() + */ + atomic_set(&tl_tmr->tmr_complete, 1); + wake_up(&tl_tmr->tl_tmr_wait); +} + +static void tcm_loop_aborted_task(struct se_cmd *se_cmd) +{ + return; +} + +static char *tcm_loop_dump_proto_id(struct tcm_loop_hba *tl_hba) +{ + switch (tl_hba->tl_proto_id) { + case SCSI_PROTOCOL_SAS: + return "SAS"; + case SCSI_PROTOCOL_FCP: + return "FCP"; + case SCSI_PROTOCOL_ISCSI: + return "iSCSI"; + default: + break; + } + + return "Unknown"; +} + +/* Start items for tcm_loop_port_cit */ + +static int tcm_loop_port_link( + struct se_portal_group *se_tpg, + struct se_lun *lun) +{ + struct tcm_loop_tpg *tl_tpg = container_of(se_tpg, + struct tcm_loop_tpg, tl_se_tpg); + struct tcm_loop_hba *tl_hba = tl_tpg->tl_hba; + + atomic_inc(&tl_tpg->tl_tpg_port_count); + smp_mb__after_atomic(); + /* + * Add Linux/SCSI struct scsi_device by HCTL + */ + scsi_add_device(tl_hba->sh, 0, tl_tpg->tl_tpgt, lun->unpacked_lun); + + pr_debug("TCM_Loop_ConfigFS: Port Link Successful\n"); + return 0; +} + +static void tcm_loop_port_unlink( + struct se_portal_group *se_tpg, + struct se_lun *se_lun) +{ + struct scsi_device *sd; + struct tcm_loop_hba *tl_hba; + struct tcm_loop_tpg *tl_tpg; + + tl_tpg = container_of(se_tpg, struct tcm_loop_tpg, tl_se_tpg); + tl_hba = tl_tpg->tl_hba; + + sd = scsi_device_lookup(tl_hba->sh, 0, tl_tpg->tl_tpgt, + se_lun->unpacked_lun); + if (!sd) { + pr_err("Unable to locate struct scsi_device for %d:%d:" + "%d\n", 0, tl_tpg->tl_tpgt, se_lun->unpacked_lun); + return; + } + /* + * Remove Linux/SCSI struct scsi_device by HCTL + */ + scsi_remove_device(sd); + scsi_device_put(sd); + + atomic_dec(&tl_tpg->tl_tpg_port_count); + smp_mb__after_atomic(); + + pr_debug("TCM_Loop_ConfigFS: Port Unlink Successful\n"); +} + +/* End items for tcm_loop_port_cit */ + +/* Start items for tcm_loop_nexus_cit */ + +static int tcm_loop_make_nexus( + struct tcm_loop_tpg *tl_tpg, + const char *name) +{ + struct se_portal_group *se_tpg; + struct tcm_loop_hba *tl_hba = tl_tpg->tl_hba; + struct tcm_loop_nexus *tl_nexus; + int ret = -ENOMEM; + + if (tl_tpg->tl_hba->tl_nexus) { + pr_debug("tl_tpg->tl_hba->tl_nexus already exists\n"); + return -EEXIST; + } + se_tpg = &tl_tpg->tl_se_tpg; + + tl_nexus = kzalloc(sizeof(struct tcm_loop_nexus), GFP_KERNEL); + if (!tl_nexus) { + pr_err("Unable to allocate struct tcm_loop_nexus\n"); + return -ENOMEM; + } + /* + * Initialize the struct se_session pointer + */ + tl_nexus->se_sess = transport_init_session(TARGET_PROT_ALL); + if (IS_ERR(tl_nexus->se_sess)) { + ret = PTR_ERR(tl_nexus->se_sess); + goto out; + } + /* + * Since we are running in 'demo mode' this call with generate a + * struct se_node_acl for the tcm_loop struct se_portal_group with the SCSI + * Initiator port name of the passed configfs group 'name'. + */ + tl_nexus->se_sess->se_node_acl = core_tpg_check_initiator_node_acl( + se_tpg, (unsigned char *)name); + if (!tl_nexus->se_sess->se_node_acl) { + transport_free_session(tl_nexus->se_sess); + goto out; + } + /* + * Now, register the SAS I_T Nexus as active with the call to + * transport_register_session() + */ + __transport_register_session(se_tpg, tl_nexus->se_sess->se_node_acl, + tl_nexus->se_sess, tl_nexus); + tl_tpg->tl_hba->tl_nexus = tl_nexus; + pr_debug("TCM_Loop_ConfigFS: Established I_T Nexus to emulated" + " %s Initiator Port: %s\n", tcm_loop_dump_proto_id(tl_hba), + name); + return 0; + +out: + kfree(tl_nexus); + return ret; +} + +static int tcm_loop_drop_nexus( + struct tcm_loop_tpg *tpg) +{ + struct se_session *se_sess; + struct tcm_loop_nexus *tl_nexus; + struct tcm_loop_hba *tl_hba = tpg->tl_hba; + + if (!tl_hba) + return -ENODEV; + + tl_nexus = tl_hba->tl_nexus; + if (!tl_nexus) + return -ENODEV; + + se_sess = tl_nexus->se_sess; + if (!se_sess) + return -ENODEV; + + if (atomic_read(&tpg->tl_tpg_port_count)) { + pr_err("Unable to remove TCM_Loop I_T Nexus with" + " active TPG port count: %d\n", + atomic_read(&tpg->tl_tpg_port_count)); + return -EPERM; + } + + pr_debug("TCM_Loop_ConfigFS: Removing I_T Nexus to emulated" + " %s Initiator Port: %s\n", tcm_loop_dump_proto_id(tl_hba), + tl_nexus->se_sess->se_node_acl->initiatorname); + /* + * Release the SCSI I_T Nexus to the emulated SAS Target Port + */ + transport_deregister_session(tl_nexus->se_sess); + tpg->tl_hba->tl_nexus = NULL; + kfree(tl_nexus); + return 0; +} + +/* End items for tcm_loop_nexus_cit */ + +static ssize_t tcm_loop_tpg_show_nexus( + struct se_portal_group *se_tpg, + char *page) +{ + struct tcm_loop_tpg *tl_tpg = container_of(se_tpg, + struct tcm_loop_tpg, tl_se_tpg); + struct tcm_loop_nexus *tl_nexus; + ssize_t ret; + + tl_nexus = tl_tpg->tl_hba->tl_nexus; + if (!tl_nexus) + return -ENODEV; + + ret = snprintf(page, PAGE_SIZE, "%s\n", + tl_nexus->se_sess->se_node_acl->initiatorname); + + return ret; +} + +static ssize_t tcm_loop_tpg_store_nexus( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct tcm_loop_tpg *tl_tpg = container_of(se_tpg, + struct tcm_loop_tpg, tl_se_tpg); + struct tcm_loop_hba *tl_hba = tl_tpg->tl_hba; + unsigned char i_port[TL_WWN_ADDR_LEN], *ptr, *port_ptr; + int ret; + /* + * Shutdown the active I_T nexus if 'NULL' is passed.. + */ + if (!strncmp(page, "NULL", 4)) { + ret = tcm_loop_drop_nexus(tl_tpg); + return (!ret) ? count : ret; + } + /* + * Otherwise make sure the passed virtual Initiator port WWN matches + * the fabric protocol_id set in tcm_loop_make_scsi_hba(), and call + * tcm_loop_make_nexus() + */ + if (strlen(page) >= TL_WWN_ADDR_LEN) { + pr_err("Emulated NAA Sas Address: %s, exceeds" + " max: %d\n", page, TL_WWN_ADDR_LEN); + return -EINVAL; + } + snprintf(&i_port[0], TL_WWN_ADDR_LEN, "%s", page); + + ptr = strstr(i_port, "naa."); + if (ptr) { + if (tl_hba->tl_proto_id != SCSI_PROTOCOL_SAS) { + pr_err("Passed SAS Initiator Port %s does not" + " match target port protoid: %s\n", i_port, + tcm_loop_dump_proto_id(tl_hba)); + return -EINVAL; + } + port_ptr = &i_port[0]; + goto check_newline; + } + ptr = strstr(i_port, "fc."); + if (ptr) { + if (tl_hba->tl_proto_id != SCSI_PROTOCOL_FCP) { + pr_err("Passed FCP Initiator Port %s does not" + " match target port protoid: %s\n", i_port, + tcm_loop_dump_proto_id(tl_hba)); + return -EINVAL; + } + port_ptr = &i_port[3]; /* Skip over "fc." */ + goto check_newline; + } + ptr = strstr(i_port, "iqn."); + if (ptr) { + if (tl_hba->tl_proto_id != SCSI_PROTOCOL_ISCSI) { + pr_err("Passed iSCSI Initiator Port %s does not" + " match target port protoid: %s\n", i_port, + tcm_loop_dump_proto_id(tl_hba)); + return -EINVAL; + } + port_ptr = &i_port[0]; + goto check_newline; + } + pr_err("Unable to locate prefix for emulated Initiator Port:" + " %s\n", i_port); + return -EINVAL; + /* + * Clear any trailing newline for the NAA WWN + */ +check_newline: + if (i_port[strlen(i_port)-1] == '\n') + i_port[strlen(i_port)-1] = '\0'; + + ret = tcm_loop_make_nexus(tl_tpg, port_ptr); + if (ret < 0) + return ret; + + return count; +} + +TF_TPG_BASE_ATTR(tcm_loop, nexus, S_IRUGO | S_IWUSR); + +static ssize_t tcm_loop_tpg_show_transport_status( + struct se_portal_group *se_tpg, + char *page) +{ + struct tcm_loop_tpg *tl_tpg = container_of(se_tpg, + struct tcm_loop_tpg, tl_se_tpg); + const char *status = NULL; + ssize_t ret = -EINVAL; + + switch (tl_tpg->tl_transport_status) { + case TCM_TRANSPORT_ONLINE: + status = "online"; + break; + case TCM_TRANSPORT_OFFLINE: + status = "offline"; + break; + default: + break; + } + + if (status) + ret = snprintf(page, PAGE_SIZE, "%s\n", status); + + return ret; +} + +static ssize_t tcm_loop_tpg_store_transport_status( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct tcm_loop_tpg *tl_tpg = container_of(se_tpg, + struct tcm_loop_tpg, tl_se_tpg); + + if (!strncmp(page, "online", 6)) { + tl_tpg->tl_transport_status = TCM_TRANSPORT_ONLINE; + return count; + } + if (!strncmp(page, "offline", 7)) { + tl_tpg->tl_transport_status = TCM_TRANSPORT_OFFLINE; + return count; + } + return -EINVAL; +} + +TF_TPG_BASE_ATTR(tcm_loop, transport_status, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *tcm_loop_tpg_attrs[] = { + &tcm_loop_tpg_nexus.attr, + &tcm_loop_tpg_transport_status.attr, + NULL, +}; + +/* Start items for tcm_loop_naa_cit */ + +static struct se_portal_group *tcm_loop_make_naa_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct tcm_loop_hba *tl_hba = container_of(wwn, + struct tcm_loop_hba, tl_hba_wwn); + struct tcm_loop_tpg *tl_tpg; + char *tpgt_str, *end_ptr; + int ret; + unsigned short int tpgt; + + tpgt_str = strstr(name, "tpgt_"); + if (!tpgt_str) { + pr_err("Unable to locate \"tpgt_#\" directory" + " group\n"); + return ERR_PTR(-EINVAL); + } + tpgt_str += 5; /* Skip ahead of "tpgt_" */ + tpgt = (unsigned short int) simple_strtoul(tpgt_str, &end_ptr, 0); + + if (tpgt >= TL_TPGS_PER_HBA) { + pr_err("Passed tpgt: %hu exceeds TL_TPGS_PER_HBA:" + " %u\n", tpgt, TL_TPGS_PER_HBA); + return ERR_PTR(-EINVAL); + } + tl_tpg = &tl_hba->tl_hba_tpgs[tpgt]; + tl_tpg->tl_hba = tl_hba; + tl_tpg->tl_tpgt = tpgt; + /* + * Register the tl_tpg as a emulated SAS TCM Target Endpoint + */ + ret = core_tpg_register(&tcm_loop_fabric_configfs->tf_ops, + wwn, &tl_tpg->tl_se_tpg, tl_tpg, + TRANSPORT_TPG_TYPE_NORMAL); + if (ret < 0) + return ERR_PTR(-ENOMEM); + + pr_debug("TCM_Loop_ConfigFS: Allocated Emulated %s" + " Target Port %s,t,0x%04x\n", tcm_loop_dump_proto_id(tl_hba), + config_item_name(&wwn->wwn_group.cg_item), tpgt); + + return &tl_tpg->tl_se_tpg; +} + +static void tcm_loop_drop_naa_tpg( + struct se_portal_group *se_tpg) +{ + struct se_wwn *wwn = se_tpg->se_tpg_wwn; + struct tcm_loop_tpg *tl_tpg = container_of(se_tpg, + struct tcm_loop_tpg, tl_se_tpg); + struct tcm_loop_hba *tl_hba; + unsigned short tpgt; + + tl_hba = tl_tpg->tl_hba; + tpgt = tl_tpg->tl_tpgt; + /* + * Release the I_T Nexus for the Virtual SAS link if present + */ + tcm_loop_drop_nexus(tl_tpg); + /* + * Deregister the tl_tpg as a emulated SAS TCM Target Endpoint + */ + core_tpg_deregister(se_tpg); + + tl_tpg->tl_hba = NULL; + tl_tpg->tl_tpgt = 0; + + pr_debug("TCM_Loop_ConfigFS: Deallocated Emulated %s" + " Target Port %s,t,0x%04x\n", tcm_loop_dump_proto_id(tl_hba), + config_item_name(&wwn->wwn_group.cg_item), tpgt); +} + +/* End items for tcm_loop_naa_cit */ + +/* Start items for tcm_loop_cit */ + +static struct se_wwn *tcm_loop_make_scsi_hba( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct tcm_loop_hba *tl_hba; + struct Scsi_Host *sh; + char *ptr; + int ret, off = 0; + + tl_hba = kzalloc(sizeof(struct tcm_loop_hba), GFP_KERNEL); + if (!tl_hba) { + pr_err("Unable to allocate struct tcm_loop_hba\n"); + return ERR_PTR(-ENOMEM); + } + /* + * Determine the emulated Protocol Identifier and Target Port Name + * based on the incoming configfs directory name. + */ + ptr = strstr(name, "naa."); + if (ptr) { + tl_hba->tl_proto_id = SCSI_PROTOCOL_SAS; + goto check_len; + } + ptr = strstr(name, "fc."); + if (ptr) { + tl_hba->tl_proto_id = SCSI_PROTOCOL_FCP; + off = 3; /* Skip over "fc." */ + goto check_len; + } + ptr = strstr(name, "iqn."); + if (!ptr) { + pr_err("Unable to locate prefix for emulated Target " + "Port: %s\n", name); + ret = -EINVAL; + goto out; + } + tl_hba->tl_proto_id = SCSI_PROTOCOL_ISCSI; + +check_len: + if (strlen(name) >= TL_WWN_ADDR_LEN) { + pr_err("Emulated NAA %s Address: %s, exceeds" + " max: %d\n", name, tcm_loop_dump_proto_id(tl_hba), + TL_WWN_ADDR_LEN); + ret = -EINVAL; + goto out; + } + snprintf(&tl_hba->tl_wwn_address[0], TL_WWN_ADDR_LEN, "%s", &name[off]); + + /* + * Call device_register(tl_hba->dev) to register the emulated + * Linux/SCSI LLD of type struct Scsi_Host at tl_hba->sh after + * device_register() callbacks in tcm_loop_driver_probe() + */ + ret = tcm_loop_setup_hba_bus(tl_hba, tcm_loop_hba_no_cnt); + if (ret) + goto out; + + sh = tl_hba->sh; + tcm_loop_hba_no_cnt++; + pr_debug("TCM_Loop_ConfigFS: Allocated emulated Target" + " %s Address: %s at Linux/SCSI Host ID: %d\n", + tcm_loop_dump_proto_id(tl_hba), name, sh->host_no); + + return &tl_hba->tl_hba_wwn; +out: + kfree(tl_hba); + return ERR_PTR(ret); +} + +static void tcm_loop_drop_scsi_hba( + struct se_wwn *wwn) +{ + struct tcm_loop_hba *tl_hba = container_of(wwn, + struct tcm_loop_hba, tl_hba_wwn); + + pr_debug("TCM_Loop_ConfigFS: Deallocating emulated Target" + " SAS Address: %s at Linux/SCSI Host ID: %d\n", + tl_hba->tl_wwn_address, tl_hba->sh->host_no); + /* + * Call device_unregister() on the original tl_hba->dev. + * tcm_loop_fabric_scsi.c:tcm_loop_release_adapter() will + * release *tl_hba; + */ + device_unregister(&tl_hba->dev); +} + +/* Start items for tcm_loop_cit */ +static ssize_t tcm_loop_wwn_show_attr_version( + struct target_fabric_configfs *tf, + char *page) +{ + return sprintf(page, "TCM Loopback Fabric module %s\n", TCM_LOOP_VERSION); +} + +TF_WWN_ATTR_RO(tcm_loop, version); + +static struct configfs_attribute *tcm_loop_wwn_attrs[] = { + &tcm_loop_wwn_version.attr, + NULL, +}; + +/* End items for tcm_loop_cit */ + +static int tcm_loop_register_configfs(void) +{ + struct target_fabric_configfs *fabric; + int ret; + /* + * Set the TCM Loop HBA counter to zero + */ + tcm_loop_hba_no_cnt = 0; + /* + * Register the top level struct config_item_type with TCM core + */ + fabric = target_fabric_configfs_init(THIS_MODULE, "loopback"); + if (IS_ERR(fabric)) { + pr_err("tcm_loop_register_configfs() failed!\n"); + return PTR_ERR(fabric); + } + /* + * Setup the fabric API of function pointers used by target_core_mod + */ + fabric->tf_ops.get_fabric_name = &tcm_loop_get_fabric_name; + fabric->tf_ops.get_fabric_proto_ident = &tcm_loop_get_fabric_proto_ident; + fabric->tf_ops.tpg_get_wwn = &tcm_loop_get_endpoint_wwn; + fabric->tf_ops.tpg_get_tag = &tcm_loop_get_tag; + fabric->tf_ops.tpg_get_default_depth = &tcm_loop_get_default_depth; + fabric->tf_ops.tpg_get_pr_transport_id = &tcm_loop_get_pr_transport_id; + fabric->tf_ops.tpg_get_pr_transport_id_len = + &tcm_loop_get_pr_transport_id_len; + fabric->tf_ops.tpg_parse_pr_out_transport_id = + &tcm_loop_parse_pr_out_transport_id; + fabric->tf_ops.tpg_check_demo_mode = &tcm_loop_check_demo_mode; + fabric->tf_ops.tpg_check_demo_mode_cache = + &tcm_loop_check_demo_mode_cache; + fabric->tf_ops.tpg_check_demo_mode_write_protect = + &tcm_loop_check_demo_mode_write_protect; + fabric->tf_ops.tpg_check_prod_mode_write_protect = + &tcm_loop_check_prod_mode_write_protect; + /* + * The TCM loopback fabric module runs in demo-mode to a local + * virtual SCSI device, so fabric dependent initator ACLs are + * not required. + */ + fabric->tf_ops.tpg_alloc_fabric_acl = &tcm_loop_tpg_alloc_fabric_acl; + fabric->tf_ops.tpg_release_fabric_acl = + &tcm_loop_tpg_release_fabric_acl; + fabric->tf_ops.tpg_get_inst_index = &tcm_loop_get_inst_index; + /* + * Used for setting up remaining TCM resources in process context + */ + fabric->tf_ops.check_stop_free = &tcm_loop_check_stop_free; + fabric->tf_ops.release_cmd = &tcm_loop_release_cmd; + fabric->tf_ops.shutdown_session = &tcm_loop_shutdown_session; + fabric->tf_ops.close_session = &tcm_loop_close_session; + fabric->tf_ops.sess_get_index = &tcm_loop_sess_get_index; + fabric->tf_ops.sess_get_initiator_sid = NULL; + fabric->tf_ops.write_pending = &tcm_loop_write_pending; + fabric->tf_ops.write_pending_status = &tcm_loop_write_pending_status; + /* + * Not used for TCM loopback + */ + fabric->tf_ops.set_default_node_attributes = + &tcm_loop_set_default_node_attributes; + fabric->tf_ops.get_task_tag = &tcm_loop_get_task_tag; + fabric->tf_ops.get_cmd_state = &tcm_loop_get_cmd_state; + fabric->tf_ops.queue_data_in = &tcm_loop_queue_data_in; + fabric->tf_ops.queue_status = &tcm_loop_queue_status; + fabric->tf_ops.queue_tm_rsp = &tcm_loop_queue_tm_rsp; + fabric->tf_ops.aborted_task = &tcm_loop_aborted_task; + + /* + * Setup function pointers for generic logic in target_core_fabric_configfs.c + */ + fabric->tf_ops.fabric_make_wwn = &tcm_loop_make_scsi_hba; + fabric->tf_ops.fabric_drop_wwn = &tcm_loop_drop_scsi_hba; + fabric->tf_ops.fabric_make_tpg = &tcm_loop_make_naa_tpg; + fabric->tf_ops.fabric_drop_tpg = &tcm_loop_drop_naa_tpg; + /* + * fabric_post_link() and fabric_pre_unlink() are used for + * registration and release of TCM Loop Virtual SCSI LUNs. + */ + fabric->tf_ops.fabric_post_link = &tcm_loop_port_link; + fabric->tf_ops.fabric_pre_unlink = &tcm_loop_port_unlink; + fabric->tf_ops.fabric_make_np = NULL; + fabric->tf_ops.fabric_drop_np = NULL; + /* + * Setup default attribute lists for various fabric->tf_cit_tmpl + */ + fabric->tf_cit_tmpl.tfc_wwn_cit.ct_attrs = tcm_loop_wwn_attrs; + fabric->tf_cit_tmpl.tfc_tpg_base_cit.ct_attrs = tcm_loop_tpg_attrs; + fabric->tf_cit_tmpl.tfc_tpg_attrib_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_param_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_np_base_cit.ct_attrs = NULL; + /* + * Once fabric->tf_ops has been setup, now register the fabric for + * use within TCM + */ + ret = target_fabric_configfs_register(fabric); + if (ret < 0) { + pr_err("target_fabric_configfs_register() for" + " TCM_Loop failed!\n"); + target_fabric_configfs_free(fabric); + return -1; + } + /* + * Setup our local pointer to *fabric. + */ + tcm_loop_fabric_configfs = fabric; + pr_debug("TCM_LOOP[0] - Set fabric ->" + " tcm_loop_fabric_configfs\n"); + return 0; +} + +static void tcm_loop_deregister_configfs(void) +{ + if (!tcm_loop_fabric_configfs) + return; + + target_fabric_configfs_deregister(tcm_loop_fabric_configfs); + tcm_loop_fabric_configfs = NULL; + pr_debug("TCM_LOOP[0] - Cleared" + " tcm_loop_fabric_configfs\n"); +} + +static int __init tcm_loop_fabric_init(void) +{ + int ret = -ENOMEM; + + tcm_loop_workqueue = alloc_workqueue("tcm_loop", 0, 0); + if (!tcm_loop_workqueue) + goto out; + + tcm_loop_cmd_cache = kmem_cache_create("tcm_loop_cmd_cache", + sizeof(struct tcm_loop_cmd), + __alignof__(struct tcm_loop_cmd), + 0, NULL); + if (!tcm_loop_cmd_cache) { + pr_debug("kmem_cache_create() for" + " tcm_loop_cmd_cache failed\n"); + goto out_destroy_workqueue; + } + + ret = tcm_loop_alloc_core_bus(); + if (ret) + goto out_destroy_cache; + + ret = tcm_loop_register_configfs(); + if (ret) + goto out_release_core_bus; + + return 0; + +out_release_core_bus: + tcm_loop_release_core_bus(); +out_destroy_cache: + kmem_cache_destroy(tcm_loop_cmd_cache); +out_destroy_workqueue: + destroy_workqueue(tcm_loop_workqueue); +out: + return ret; +} + +static void __exit tcm_loop_fabric_exit(void) +{ + tcm_loop_deregister_configfs(); + tcm_loop_release_core_bus(); + kmem_cache_destroy(tcm_loop_cmd_cache); + destroy_workqueue(tcm_loop_workqueue); +} + +MODULE_DESCRIPTION("TCM loopback virtual Linux/SCSI fabric module"); +MODULE_AUTHOR("Nicholas A. Bellinger <nab@risingtidesystems.com>"); +MODULE_LICENSE("GPL"); +module_init(tcm_loop_fabric_init); +module_exit(tcm_loop_fabric_exit); diff --git a/drivers/target/loopback/tcm_loop.h b/drivers/target/loopback/tcm_loop.h new file mode 100644 index 00000000000..54c59d0b660 --- /dev/null +++ b/drivers/target/loopback/tcm_loop.h @@ -0,0 +1,67 @@ +#define TCM_LOOP_VERSION "v2.1-rc2" +#define TL_WWN_ADDR_LEN 256 +#define TL_TPGS_PER_HBA 32 + +/* + * Used in tcm_loop_driver_probe() for struct Scsi_Host->max_cmd_len + */ +#define TL_SCSI_MAX_CMD_LEN 32 + +struct tcm_loop_cmd { + /* State of Linux/SCSI CDB+Data descriptor */ + u32 sc_cmd_state; + /* Tagged command queueing */ + u32 sc_cmd_tag; + /* Pointer to the CDB+Data descriptor from Linux/SCSI subsystem */ + struct scsi_cmnd *sc; + /* The TCM I/O descriptor that is accessed via container_of() */ + struct se_cmd tl_se_cmd; + struct work_struct work; + /* Sense buffer that will be mapped into outgoing status */ + unsigned char tl_sense_buf[TRANSPORT_SENSE_BUFFER]; +}; + +struct tcm_loop_tmr { + atomic_t tmr_complete; + wait_queue_head_t tl_tmr_wait; +}; + +struct tcm_loop_nexus { + int it_nexus_active; + /* + * Pointer to Linux/SCSI HBA from linux/include/scsi_host.h + */ + struct scsi_host *sh; + /* + * Pointer to TCM session for I_T Nexus + */ + struct se_session *se_sess; +}; + +struct tcm_loop_nacl { + struct se_node_acl se_node_acl; +}; + +#define TCM_TRANSPORT_ONLINE 0 +#define TCM_TRANSPORT_OFFLINE 1 + +struct tcm_loop_tpg { + unsigned short tl_tpgt; + unsigned short tl_transport_status; + atomic_t tl_tpg_port_count; + struct se_portal_group tl_se_tpg; + struct tcm_loop_hba *tl_hba; +}; + +struct tcm_loop_hba { + u8 tl_proto_id; + unsigned char tl_wwn_address[TL_WWN_ADDR_LEN]; + struct se_hba_s *se_hba; + struct se_lun *tl_hba_lun; + struct se_port *tl_hba_lun_sep; + struct tcm_loop_nexus *tl_nexus; + struct device dev; + struct Scsi_Host *sh; + struct tcm_loop_tpg tl_hba_tpgs[TL_TPGS_PER_HBA]; + struct se_wwn tl_hba_wwn; +}; diff --git a/drivers/target/sbp/Kconfig b/drivers/target/sbp/Kconfig new file mode 100644 index 00000000000..1614bc710d4 --- /dev/null +++ b/drivers/target/sbp/Kconfig @@ -0,0 +1,11 @@ +config SBP_TARGET + tristate "FireWire SBP-2 fabric module" + depends on FIREWIRE + help + Say Y or M here to enable SCSI target functionality over FireWire. + This enables you to expose SCSI devices to other nodes on the FireWire + bus, for example hard disks. Similar to FireWire Target Disk mode on + many Apple computers. + + To compile this driver as a module, say M here: The module will be + called sbp-target. diff --git a/drivers/target/sbp/Makefile b/drivers/target/sbp/Makefile new file mode 100644 index 00000000000..27747ad054c --- /dev/null +++ b/drivers/target/sbp/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SBP_TARGET) += sbp_target.o diff --git a/drivers/target/sbp/sbp_target.c b/drivers/target/sbp/sbp_target.c new file mode 100644 index 00000000000..e7e93727553 --- /dev/null +++ b/drivers/target/sbp/sbp_target.c @@ -0,0 +1,2614 @@ +/* + * SBP2 target driver (SCSI over IEEE1394 in target mode) + * + * Copyright (C) 2011 Chris Boot <bootc@bootc.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#define KMSG_COMPONENT "sbp_target" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <scsi/scsi.h> +#include <scsi/scsi_tcq.h> +#include <target/target_core_base.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> +#include <target/target_core_fabric_configfs.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> +#include <asm/unaligned.h> + +#include "sbp_target.h" + +/* Local pointer to allocated TCM configfs fabric module */ +static struct target_fabric_configfs *sbp_fabric_configfs; + +/* FireWire address region for management and command block address handlers */ +static const struct fw_address_region sbp_register_region = { + .start = CSR_REGISTER_BASE + 0x10000, + .end = 0x1000000000000ULL, +}; + +static const u32 sbp_unit_directory_template[] = { + 0x1200609e, /* unit_specifier_id: NCITS/T10 */ + 0x13010483, /* unit_sw_version: 1155D Rev 4 */ + 0x3800609e, /* command_set_specifier_id: NCITS/T10 */ + 0x390104d8, /* command_set: SPC-2 */ + 0x3b000000, /* command_set_revision: 0 */ + 0x3c000001, /* firmware_revision: 1 */ +}; + +#define SESSION_MAINTENANCE_INTERVAL HZ + +static atomic_t login_id = ATOMIC_INIT(0); + +static void session_maintenance_work(struct work_struct *); +static int sbp_run_transaction(struct fw_card *, int, int, int, int, + unsigned long long, void *, size_t); + +static int read_peer_guid(u64 *guid, const struct sbp_management_request *req) +{ + int ret; + __be32 high, low; + + ret = sbp_run_transaction(req->card, TCODE_READ_QUADLET_REQUEST, + req->node_addr, req->generation, req->speed, + (CSR_REGISTER_BASE | CSR_CONFIG_ROM) + 3 * 4, + &high, sizeof(high)); + if (ret != RCODE_COMPLETE) + return ret; + + ret = sbp_run_transaction(req->card, TCODE_READ_QUADLET_REQUEST, + req->node_addr, req->generation, req->speed, + (CSR_REGISTER_BASE | CSR_CONFIG_ROM) + 4 * 4, + &low, sizeof(low)); + if (ret != RCODE_COMPLETE) + return ret; + + *guid = (u64)be32_to_cpu(high) << 32 | be32_to_cpu(low); + + return RCODE_COMPLETE; +} + +static struct sbp_session *sbp_session_find_by_guid( + struct sbp_tpg *tpg, u64 guid) +{ + struct se_session *se_sess; + struct sbp_session *sess, *found = NULL; + + spin_lock_bh(&tpg->se_tpg.session_lock); + list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { + sess = se_sess->fabric_sess_ptr; + if (sess->guid == guid) + found = sess; + } + spin_unlock_bh(&tpg->se_tpg.session_lock); + + return found; +} + +static struct sbp_login_descriptor *sbp_login_find_by_lun( + struct sbp_session *session, struct se_lun *lun) +{ + struct sbp_login_descriptor *login, *found = NULL; + + spin_lock_bh(&session->lock); + list_for_each_entry(login, &session->login_list, link) { + if (login->lun == lun) + found = login; + } + spin_unlock_bh(&session->lock); + + return found; +} + +static int sbp_login_count_all_by_lun( + struct sbp_tpg *tpg, + struct se_lun *lun, + int exclusive) +{ + struct se_session *se_sess; + struct sbp_session *sess; + struct sbp_login_descriptor *login; + int count = 0; + + spin_lock_bh(&tpg->se_tpg.session_lock); + list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { + sess = se_sess->fabric_sess_ptr; + + spin_lock_bh(&sess->lock); + list_for_each_entry(login, &sess->login_list, link) { + if (login->lun != lun) + continue; + + if (!exclusive || login->exclusive) + count++; + } + spin_unlock_bh(&sess->lock); + } + spin_unlock_bh(&tpg->se_tpg.session_lock); + + return count; +} + +static struct sbp_login_descriptor *sbp_login_find_by_id( + struct sbp_tpg *tpg, int login_id) +{ + struct se_session *se_sess; + struct sbp_session *sess; + struct sbp_login_descriptor *login, *found = NULL; + + spin_lock_bh(&tpg->se_tpg.session_lock); + list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { + sess = se_sess->fabric_sess_ptr; + + spin_lock_bh(&sess->lock); + list_for_each_entry(login, &sess->login_list, link) { + if (login->login_id == login_id) + found = login; + } + spin_unlock_bh(&sess->lock); + } + spin_unlock_bh(&tpg->se_tpg.session_lock); + + return found; +} + +static struct se_lun *sbp_get_lun_from_tpg(struct sbp_tpg *tpg, int lun) +{ + struct se_portal_group *se_tpg = &tpg->se_tpg; + struct se_lun *se_lun; + + if (lun >= TRANSPORT_MAX_LUNS_PER_TPG) + return ERR_PTR(-EINVAL); + + spin_lock(&se_tpg->tpg_lun_lock); + se_lun = se_tpg->tpg_lun_list[lun]; + + if (se_lun->lun_status != TRANSPORT_LUN_STATUS_ACTIVE) + se_lun = ERR_PTR(-ENODEV); + + spin_unlock(&se_tpg->tpg_lun_lock); + + return se_lun; +} + +static struct sbp_session *sbp_session_create( + struct sbp_tpg *tpg, + u64 guid) +{ + struct sbp_session *sess; + int ret; + char guid_str[17]; + struct se_node_acl *se_nacl; + + sess = kmalloc(sizeof(*sess), GFP_KERNEL); + if (!sess) { + pr_err("failed to allocate session descriptor\n"); + return ERR_PTR(-ENOMEM); + } + + sess->se_sess = transport_init_session(TARGET_PROT_NORMAL); + if (IS_ERR(sess->se_sess)) { + pr_err("failed to init se_session\n"); + + ret = PTR_ERR(sess->se_sess); + kfree(sess); + return ERR_PTR(ret); + } + + snprintf(guid_str, sizeof(guid_str), "%016llx", guid); + + se_nacl = core_tpg_check_initiator_node_acl(&tpg->se_tpg, guid_str); + if (!se_nacl) { + pr_warn("Node ACL not found for %s\n", guid_str); + + transport_free_session(sess->se_sess); + kfree(sess); + + return ERR_PTR(-EPERM); + } + + sess->se_sess->se_node_acl = se_nacl; + + spin_lock_init(&sess->lock); + INIT_LIST_HEAD(&sess->login_list); + INIT_DELAYED_WORK(&sess->maint_work, session_maintenance_work); + + sess->guid = guid; + + transport_register_session(&tpg->se_tpg, se_nacl, sess->se_sess, sess); + + return sess; +} + +static void sbp_session_release(struct sbp_session *sess, bool cancel_work) +{ + spin_lock_bh(&sess->lock); + if (!list_empty(&sess->login_list)) { + spin_unlock_bh(&sess->lock); + return; + } + spin_unlock_bh(&sess->lock); + + if (cancel_work) + cancel_delayed_work_sync(&sess->maint_work); + + transport_deregister_session_configfs(sess->se_sess); + transport_deregister_session(sess->se_sess); + + if (sess->card) + fw_card_put(sess->card); + + kfree(sess); +} + +static void sbp_target_agent_unregister(struct sbp_target_agent *); + +static void sbp_login_release(struct sbp_login_descriptor *login, + bool cancel_work) +{ + struct sbp_session *sess = login->sess; + + /* FIXME: abort/wait on tasks */ + + sbp_target_agent_unregister(login->tgt_agt); + + if (sess) { + spin_lock_bh(&sess->lock); + list_del(&login->link); + spin_unlock_bh(&sess->lock); + + sbp_session_release(sess, cancel_work); + } + + kfree(login); +} + +static struct sbp_target_agent *sbp_target_agent_register( + struct sbp_login_descriptor *); + +static void sbp_management_request_login( + struct sbp_management_agent *agent, struct sbp_management_request *req, + int *status_data_size) +{ + struct sbp_tport *tport = agent->tport; + struct sbp_tpg *tpg = tport->tpg; + struct se_lun *se_lun; + int ret; + u64 guid; + struct sbp_session *sess; + struct sbp_login_descriptor *login; + struct sbp_login_response_block *response; + int login_response_len; + + se_lun = sbp_get_lun_from_tpg(tpg, + LOGIN_ORB_LUN(be32_to_cpu(req->orb.misc))); + if (IS_ERR(se_lun)) { + pr_notice("login to unknown LUN: %d\n", + LOGIN_ORB_LUN(be32_to_cpu(req->orb.misc))); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_LUN_NOTSUPP)); + return; + } + + ret = read_peer_guid(&guid, req); + if (ret != RCODE_COMPLETE) { + pr_warn("failed to read peer GUID: %d\n", ret); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + return; + } + + pr_notice("mgt_agent LOGIN to LUN %d from %016llx\n", + se_lun->unpacked_lun, guid); + + sess = sbp_session_find_by_guid(tpg, guid); + if (sess) { + login = sbp_login_find_by_lun(sess, se_lun); + if (login) { + pr_notice("initiator already logged-in\n"); + + /* + * SBP-2 R4 says we should return access denied, but + * that can confuse initiators. Instead we need to + * treat this like a reconnect, but send the login + * response block like a fresh login. + * + * This is required particularly in the case of Apple + * devices booting off the FireWire target, where + * the firmware has an active login to the target. When + * the OS takes control of the session it issues its own + * LOGIN rather than a RECONNECT. To avoid the machine + * waiting until the reconnect_hold expires, we can skip + * the ACCESS_DENIED errors to speed things up. + */ + + goto already_logged_in; + } + } + + /* + * check exclusive bit in login request + * reject with access_denied if any logins present + */ + if (LOGIN_ORB_EXCLUSIVE(be32_to_cpu(req->orb.misc)) && + sbp_login_count_all_by_lun(tpg, se_lun, 0)) { + pr_warn("refusing exclusive login with other active logins\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + /* + * check exclusive bit in any existing login descriptor + * reject with access_denied if any exclusive logins present + */ + if (sbp_login_count_all_by_lun(tpg, se_lun, 1)) { + pr_warn("refusing login while another exclusive login present\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + /* + * check we haven't exceeded the number of allowed logins + * reject with resources_unavailable if we have + */ + if (sbp_login_count_all_by_lun(tpg, se_lun, 0) >= + tport->max_logins_per_lun) { + pr_warn("max number of logins reached\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); + return; + } + + if (!sess) { + sess = sbp_session_create(tpg, guid); + if (IS_ERR(sess)) { + switch (PTR_ERR(sess)) { + case -EPERM: + ret = SBP_STATUS_ACCESS_DENIED; + break; + default: + ret = SBP_STATUS_RESOURCES_UNAVAIL; + break; + } + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP( + STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(ret)); + return; + } + + sess->node_id = req->node_addr; + sess->card = fw_card_get(req->card); + sess->generation = req->generation; + sess->speed = req->speed; + + schedule_delayed_work(&sess->maint_work, + SESSION_MAINTENANCE_INTERVAL); + } + + /* only take the latest reconnect_hold into account */ + sess->reconnect_hold = min( + 1 << LOGIN_ORB_RECONNECT(be32_to_cpu(req->orb.misc)), + tport->max_reconnect_timeout) - 1; + + login = kmalloc(sizeof(*login), GFP_KERNEL); + if (!login) { + pr_err("failed to allocate login descriptor\n"); + + sbp_session_release(sess, true); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); + return; + } + + login->sess = sess; + login->lun = se_lun; + login->status_fifo_addr = sbp2_pointer_to_addr(&req->orb.status_fifo); + login->exclusive = LOGIN_ORB_EXCLUSIVE(be32_to_cpu(req->orb.misc)); + login->login_id = atomic_inc_return(&login_id); + + login->tgt_agt = sbp_target_agent_register(login); + if (IS_ERR(login->tgt_agt)) { + ret = PTR_ERR(login->tgt_agt); + pr_err("failed to map command block handler: %d\n", ret); + + sbp_session_release(sess, true); + kfree(login); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); + return; + } + + spin_lock_bh(&sess->lock); + list_add_tail(&login->link, &sess->login_list); + spin_unlock_bh(&sess->lock); + +already_logged_in: + response = kzalloc(sizeof(*response), GFP_KERNEL); + if (!response) { + pr_err("failed to allocate login response block\n"); + + sbp_login_release(login, true); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); + return; + } + + login_response_len = clamp_val( + LOGIN_ORB_RESPONSE_LENGTH(be32_to_cpu(req->orb.length)), + 12, sizeof(*response)); + response->misc = cpu_to_be32( + ((login_response_len & 0xffff) << 16) | + (login->login_id & 0xffff)); + response->reconnect_hold = cpu_to_be32(sess->reconnect_hold & 0xffff); + addr_to_sbp2_pointer(login->tgt_agt->handler.offset, + &response->command_block_agent); + + ret = sbp_run_transaction(sess->card, TCODE_WRITE_BLOCK_REQUEST, + sess->node_id, sess->generation, sess->speed, + sbp2_pointer_to_addr(&req->orb.ptr2), response, + login_response_len); + if (ret != RCODE_COMPLETE) { + pr_debug("failed to write login response block: %x\n", ret); + + kfree(response); + sbp_login_release(login, true); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + return; + } + + kfree(response); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void sbp_management_request_query_logins( + struct sbp_management_agent *agent, struct sbp_management_request *req, + int *status_data_size) +{ + pr_notice("QUERY LOGINS not implemented\n"); + /* FIXME: implement */ + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); +} + +static void sbp_management_request_reconnect( + struct sbp_management_agent *agent, struct sbp_management_request *req, + int *status_data_size) +{ + struct sbp_tport *tport = agent->tport; + struct sbp_tpg *tpg = tport->tpg; + int ret; + u64 guid; + struct sbp_login_descriptor *login; + + ret = read_peer_guid(&guid, req); + if (ret != RCODE_COMPLETE) { + pr_warn("failed to read peer GUID: %d\n", ret); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + return; + } + + pr_notice("mgt_agent RECONNECT from %016llx\n", guid); + + login = sbp_login_find_by_id(tpg, + RECONNECT_ORB_LOGIN_ID(be32_to_cpu(req->orb.misc))); + + if (!login) { + pr_err("mgt_agent RECONNECT unknown login ID\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + if (login->sess->guid != guid) { + pr_err("mgt_agent RECONNECT login GUID doesn't match\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + spin_lock_bh(&login->sess->lock); + if (login->sess->card) + fw_card_put(login->sess->card); + + /* update the node details */ + login->sess->generation = req->generation; + login->sess->node_id = req->node_addr; + login->sess->card = fw_card_get(req->card); + login->sess->speed = req->speed; + spin_unlock_bh(&login->sess->lock); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void sbp_management_request_logout( + struct sbp_management_agent *agent, struct sbp_management_request *req, + int *status_data_size) +{ + struct sbp_tport *tport = agent->tport; + struct sbp_tpg *tpg = tport->tpg; + int id; + struct sbp_login_descriptor *login; + + id = LOGOUT_ORB_LOGIN_ID(be32_to_cpu(req->orb.misc)); + + login = sbp_login_find_by_id(tpg, id); + if (!login) { + pr_warn("cannot find login: %d\n", id); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_LOGIN_ID_UNKNOWN)); + return; + } + + pr_info("mgt_agent LOGOUT from LUN %d session %d\n", + login->lun->unpacked_lun, login->login_id); + + if (req->node_addr != login->sess->node_id) { + pr_warn("logout from different node ID\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + sbp_login_release(login, true); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void session_check_for_reset(struct sbp_session *sess) +{ + bool card_valid = false; + + spin_lock_bh(&sess->lock); + + if (sess->card) { + spin_lock_irq(&sess->card->lock); + card_valid = (sess->card->local_node != NULL); + spin_unlock_irq(&sess->card->lock); + + if (!card_valid) { + fw_card_put(sess->card); + sess->card = NULL; + } + } + + if (!card_valid || (sess->generation != sess->card->generation)) { + pr_info("Waiting for reconnect from node: %016llx\n", + sess->guid); + + sess->node_id = -1; + sess->reconnect_expires = get_jiffies_64() + + ((sess->reconnect_hold + 1) * HZ); + } + + spin_unlock_bh(&sess->lock); +} + +static void session_reconnect_expired(struct sbp_session *sess) +{ + struct sbp_login_descriptor *login, *temp; + LIST_HEAD(login_list); + + pr_info("Reconnect timer expired for node: %016llx\n", sess->guid); + + spin_lock_bh(&sess->lock); + list_for_each_entry_safe(login, temp, &sess->login_list, link) { + login->sess = NULL; + list_move_tail(&login->link, &login_list); + } + spin_unlock_bh(&sess->lock); + + list_for_each_entry_safe(login, temp, &login_list, link) { + list_del(&login->link); + sbp_login_release(login, false); + } + + sbp_session_release(sess, false); +} + +static void session_maintenance_work(struct work_struct *work) +{ + struct sbp_session *sess = container_of(work, struct sbp_session, + maint_work.work); + + /* could be called while tearing down the session */ + spin_lock_bh(&sess->lock); + if (list_empty(&sess->login_list)) { + spin_unlock_bh(&sess->lock); + return; + } + spin_unlock_bh(&sess->lock); + + if (sess->node_id != -1) { + /* check for bus reset and make node_id invalid */ + session_check_for_reset(sess); + + schedule_delayed_work(&sess->maint_work, + SESSION_MAINTENANCE_INTERVAL); + } else if (!time_after64(get_jiffies_64(), sess->reconnect_expires)) { + /* still waiting for reconnect */ + schedule_delayed_work(&sess->maint_work, + SESSION_MAINTENANCE_INTERVAL); + } else { + /* reconnect timeout has expired */ + session_reconnect_expired(sess); + } +} + +static int tgt_agent_rw_agent_state(struct fw_card *card, int tcode, void *data, + struct sbp_target_agent *agent) +{ + int state; + + switch (tcode) { + case TCODE_READ_QUADLET_REQUEST: + pr_debug("tgt_agent AGENT_STATE READ\n"); + + spin_lock_bh(&agent->lock); + state = agent->state; + spin_unlock_bh(&agent->lock); + + *(__be32 *)data = cpu_to_be32(state); + + return RCODE_COMPLETE; + + case TCODE_WRITE_QUADLET_REQUEST: + /* ignored */ + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static int tgt_agent_rw_agent_reset(struct fw_card *card, int tcode, void *data, + struct sbp_target_agent *agent) +{ + switch (tcode) { + case TCODE_WRITE_QUADLET_REQUEST: + pr_debug("tgt_agent AGENT_RESET\n"); + spin_lock_bh(&agent->lock); + agent->state = AGENT_STATE_RESET; + spin_unlock_bh(&agent->lock); + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static int tgt_agent_rw_orb_pointer(struct fw_card *card, int tcode, void *data, + struct sbp_target_agent *agent) +{ + struct sbp2_pointer *ptr = data; + + switch (tcode) { + case TCODE_WRITE_BLOCK_REQUEST: + spin_lock_bh(&agent->lock); + if (agent->state != AGENT_STATE_SUSPENDED && + agent->state != AGENT_STATE_RESET) { + spin_unlock_bh(&agent->lock); + pr_notice("Ignoring ORB_POINTER write while active.\n"); + return RCODE_CONFLICT_ERROR; + } + agent->state = AGENT_STATE_ACTIVE; + spin_unlock_bh(&agent->lock); + + agent->orb_pointer = sbp2_pointer_to_addr(ptr); + agent->doorbell = false; + + pr_debug("tgt_agent ORB_POINTER write: 0x%llx\n", + agent->orb_pointer); + + queue_work(system_unbound_wq, &agent->work); + + return RCODE_COMPLETE; + + case TCODE_READ_BLOCK_REQUEST: + pr_debug("tgt_agent ORB_POINTER READ\n"); + spin_lock_bh(&agent->lock); + addr_to_sbp2_pointer(agent->orb_pointer, ptr); + spin_unlock_bh(&agent->lock); + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static int tgt_agent_rw_doorbell(struct fw_card *card, int tcode, void *data, + struct sbp_target_agent *agent) +{ + switch (tcode) { + case TCODE_WRITE_QUADLET_REQUEST: + spin_lock_bh(&agent->lock); + if (agent->state != AGENT_STATE_SUSPENDED) { + spin_unlock_bh(&agent->lock); + pr_debug("Ignoring DOORBELL while active.\n"); + return RCODE_CONFLICT_ERROR; + } + agent->state = AGENT_STATE_ACTIVE; + spin_unlock_bh(&agent->lock); + + agent->doorbell = true; + + pr_debug("tgt_agent DOORBELL\n"); + + queue_work(system_unbound_wq, &agent->work); + + return RCODE_COMPLETE; + + case TCODE_READ_QUADLET_REQUEST: + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static int tgt_agent_rw_unsolicited_status_enable(struct fw_card *card, + int tcode, void *data, struct sbp_target_agent *agent) +{ + switch (tcode) { + case TCODE_WRITE_QUADLET_REQUEST: + pr_debug("tgt_agent UNSOLICITED_STATUS_ENABLE\n"); + /* ignored as we don't send unsolicited status */ + return RCODE_COMPLETE; + + case TCODE_READ_QUADLET_REQUEST: + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static void tgt_agent_rw(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, int generation, + unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct sbp_target_agent *agent = callback_data; + struct sbp_session *sess = agent->login->sess; + int sess_gen, sess_node, rcode; + + spin_lock_bh(&sess->lock); + sess_gen = sess->generation; + sess_node = sess->node_id; + spin_unlock_bh(&sess->lock); + + if (generation != sess_gen) { + pr_notice("ignoring request with wrong generation\n"); + rcode = RCODE_TYPE_ERROR; + goto out; + } + + if (source != sess_node) { + pr_notice("ignoring request from foreign node (%x != %x)\n", + source, sess_node); + rcode = RCODE_TYPE_ERROR; + goto out; + } + + /* turn offset into the offset from the start of the block */ + offset -= agent->handler.offset; + + if (offset == 0x00 && length == 4) { + /* AGENT_STATE */ + rcode = tgt_agent_rw_agent_state(card, tcode, data, agent); + } else if (offset == 0x04 && length == 4) { + /* AGENT_RESET */ + rcode = tgt_agent_rw_agent_reset(card, tcode, data, agent); + } else if (offset == 0x08 && length == 8) { + /* ORB_POINTER */ + rcode = tgt_agent_rw_orb_pointer(card, tcode, data, agent); + } else if (offset == 0x10 && length == 4) { + /* DOORBELL */ + rcode = tgt_agent_rw_doorbell(card, tcode, data, agent); + } else if (offset == 0x14 && length == 4) { + /* UNSOLICITED_STATUS_ENABLE */ + rcode = tgt_agent_rw_unsolicited_status_enable(card, tcode, + data, agent); + } else { + rcode = RCODE_ADDRESS_ERROR; + } + +out: + fw_send_response(card, request, rcode); +} + +static void sbp_handle_command(struct sbp_target_request *); +static int sbp_send_status(struct sbp_target_request *); +static void sbp_free_request(struct sbp_target_request *); + +static void tgt_agent_process_work(struct work_struct *work) +{ + struct sbp_target_request *req = + container_of(work, struct sbp_target_request, work); + + pr_debug("tgt_orb ptr:0x%llx next_ORB:0x%llx data_descriptor:0x%llx misc:0x%x\n", + req->orb_pointer, + sbp2_pointer_to_addr(&req->orb.next_orb), + sbp2_pointer_to_addr(&req->orb.data_descriptor), + be32_to_cpu(req->orb.misc)); + + if (req->orb_pointer >> 32) + pr_debug("ORB with high bits set\n"); + + switch (ORB_REQUEST_FORMAT(be32_to_cpu(req->orb.misc))) { + case 0:/* Format specified by this standard */ + sbp_handle_command(req); + return; + case 1: /* Reserved for future standardization */ + case 2: /* Vendor-dependent */ + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP( + STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS( + SBP_STATUS_REQ_TYPE_NOTSUPP)); + sbp_send_status(req); + sbp_free_request(req); + return; + case 3: /* Dummy ORB */ + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP( + STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS( + SBP_STATUS_DUMMY_ORB_COMPLETE)); + sbp_send_status(req); + sbp_free_request(req); + return; + default: + BUG(); + } +} + +/* used to double-check we haven't been issued an AGENT_RESET */ +static inline bool tgt_agent_check_active(struct sbp_target_agent *agent) +{ + bool active; + + spin_lock_bh(&agent->lock); + active = (agent->state == AGENT_STATE_ACTIVE); + spin_unlock_bh(&agent->lock); + + return active; +} + +static void tgt_agent_fetch_work(struct work_struct *work) +{ + struct sbp_target_agent *agent = + container_of(work, struct sbp_target_agent, work); + struct sbp_session *sess = agent->login->sess; + struct sbp_target_request *req; + int ret; + bool doorbell = agent->doorbell; + u64 next_orb = agent->orb_pointer; + + while (next_orb && tgt_agent_check_active(agent)) { + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) { + spin_lock_bh(&agent->lock); + agent->state = AGENT_STATE_DEAD; + spin_unlock_bh(&agent->lock); + return; + } + + req->login = agent->login; + req->orb_pointer = next_orb; + + req->status.status = cpu_to_be32(STATUS_BLOCK_ORB_OFFSET_HIGH( + req->orb_pointer >> 32)); + req->status.orb_low = cpu_to_be32( + req->orb_pointer & 0xfffffffc); + + /* read in the ORB */ + ret = sbp_run_transaction(sess->card, TCODE_READ_BLOCK_REQUEST, + sess->node_id, sess->generation, sess->speed, + req->orb_pointer, &req->orb, sizeof(req->orb)); + if (ret != RCODE_COMPLETE) { + pr_debug("tgt_orb fetch failed: %x\n", ret); + req->status.status |= cpu_to_be32( + STATUS_BLOCK_SRC( + STATUS_SRC_ORB_FINISHED) | + STATUS_BLOCK_RESP( + STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(1) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS( + SBP_STATUS_UNSPECIFIED_ERROR)); + spin_lock_bh(&agent->lock); + agent->state = AGENT_STATE_DEAD; + spin_unlock_bh(&agent->lock); + + sbp_send_status(req); + sbp_free_request(req); + return; + } + + /* check the next_ORB field */ + if (be32_to_cpu(req->orb.next_orb.high) & 0x80000000) { + next_orb = 0; + req->status.status |= cpu_to_be32(STATUS_BLOCK_SRC( + STATUS_SRC_ORB_FINISHED)); + } else { + next_orb = sbp2_pointer_to_addr(&req->orb.next_orb); + req->status.status |= cpu_to_be32(STATUS_BLOCK_SRC( + STATUS_SRC_ORB_CONTINUING)); + } + + if (tgt_agent_check_active(agent) && !doorbell) { + INIT_WORK(&req->work, tgt_agent_process_work); + queue_work(system_unbound_wq, &req->work); + } else { + /* don't process this request, just check next_ORB */ + sbp_free_request(req); + } + + spin_lock_bh(&agent->lock); + doorbell = agent->doorbell = false; + + /* check if we should carry on processing */ + if (next_orb) + agent->orb_pointer = next_orb; + else + agent->state = AGENT_STATE_SUSPENDED; + + spin_unlock_bh(&agent->lock); + }; +} + +static struct sbp_target_agent *sbp_target_agent_register( + struct sbp_login_descriptor *login) +{ + struct sbp_target_agent *agent; + int ret; + + agent = kmalloc(sizeof(*agent), GFP_KERNEL); + if (!agent) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&agent->lock); + + agent->handler.length = 0x20; + agent->handler.address_callback = tgt_agent_rw; + agent->handler.callback_data = agent; + + agent->login = login; + agent->state = AGENT_STATE_RESET; + INIT_WORK(&agent->work, tgt_agent_fetch_work); + agent->orb_pointer = 0; + agent->doorbell = false; + + ret = fw_core_add_address_handler(&agent->handler, + &sbp_register_region); + if (ret < 0) { + kfree(agent); + return ERR_PTR(ret); + } + + return agent; +} + +static void sbp_target_agent_unregister(struct sbp_target_agent *agent) +{ + fw_core_remove_address_handler(&agent->handler); + cancel_work_sync(&agent->work); + kfree(agent); +} + +/* + * Simple wrapper around fw_run_transaction that retries the transaction several + * times in case of failure, with an exponential backoff. + */ +static int sbp_run_transaction(struct fw_card *card, int tcode, int destination_id, + int generation, int speed, unsigned long long offset, + void *payload, size_t length) +{ + int attempt, ret, delay; + + for (attempt = 1; attempt <= 5; attempt++) { + ret = fw_run_transaction(card, tcode, destination_id, + generation, speed, offset, payload, length); + + switch (ret) { + case RCODE_COMPLETE: + case RCODE_TYPE_ERROR: + case RCODE_ADDRESS_ERROR: + case RCODE_GENERATION: + return ret; + + default: + delay = 5 * attempt * attempt; + usleep_range(delay, delay * 2); + } + } + + return ret; +} + +/* + * Wrapper around sbp_run_transaction that gets the card, destination, + * generation and speed out of the request's session. + */ +static int sbp_run_request_transaction(struct sbp_target_request *req, + int tcode, unsigned long long offset, void *payload, + size_t length) +{ + struct sbp_login_descriptor *login = req->login; + struct sbp_session *sess = login->sess; + struct fw_card *card; + int node_id, generation, speed, ret; + + spin_lock_bh(&sess->lock); + card = fw_card_get(sess->card); + node_id = sess->node_id; + generation = sess->generation; + speed = sess->speed; + spin_unlock_bh(&sess->lock); + + ret = sbp_run_transaction(card, tcode, node_id, generation, speed, + offset, payload, length); + + fw_card_put(card); + + return ret; +} + +static int sbp_fetch_command(struct sbp_target_request *req) +{ + int ret, cmd_len, copy_len; + + cmd_len = scsi_command_size(req->orb.command_block); + + req->cmd_buf = kmalloc(cmd_len, GFP_KERNEL); + if (!req->cmd_buf) + return -ENOMEM; + + memcpy(req->cmd_buf, req->orb.command_block, + min_t(int, cmd_len, sizeof(req->orb.command_block))); + + if (cmd_len > sizeof(req->orb.command_block)) { + pr_debug("sbp_fetch_command: filling in long command\n"); + copy_len = cmd_len - sizeof(req->orb.command_block); + + ret = sbp_run_request_transaction(req, + TCODE_READ_BLOCK_REQUEST, + req->orb_pointer + sizeof(req->orb), + req->cmd_buf + sizeof(req->orb.command_block), + copy_len); + if (ret != RCODE_COMPLETE) + return -EIO; + } + + return 0; +} + +static int sbp_fetch_page_table(struct sbp_target_request *req) +{ + int pg_tbl_sz, ret; + struct sbp_page_table_entry *pg_tbl; + + if (!CMDBLK_ORB_PG_TBL_PRESENT(be32_to_cpu(req->orb.misc))) + return 0; + + pg_tbl_sz = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)) * + sizeof(struct sbp_page_table_entry); + + pg_tbl = kmalloc(pg_tbl_sz, GFP_KERNEL); + if (!pg_tbl) + return -ENOMEM; + + ret = sbp_run_request_transaction(req, TCODE_READ_BLOCK_REQUEST, + sbp2_pointer_to_addr(&req->orb.data_descriptor), + pg_tbl, pg_tbl_sz); + if (ret != RCODE_COMPLETE) { + kfree(pg_tbl); + return -EIO; + } + + req->pg_tbl = pg_tbl; + return 0; +} + +static void sbp_calc_data_length_direction(struct sbp_target_request *req, + u32 *data_len, enum dma_data_direction *data_dir) +{ + int data_size, direction, idx; + + data_size = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)); + direction = CMDBLK_ORB_DIRECTION(be32_to_cpu(req->orb.misc)); + + if (!data_size) { + *data_len = 0; + *data_dir = DMA_NONE; + return; + } + + *data_dir = direction ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + if (req->pg_tbl) { + *data_len = 0; + for (idx = 0; idx < data_size; idx++) { + *data_len += be16_to_cpu( + req->pg_tbl[idx].segment_length); + } + } else { + *data_len = data_size; + } +} + +static void sbp_handle_command(struct sbp_target_request *req) +{ + struct sbp_login_descriptor *login = req->login; + struct sbp_session *sess = login->sess; + int ret, unpacked_lun; + u32 data_length; + enum dma_data_direction data_dir; + + ret = sbp_fetch_command(req); + if (ret) { + pr_debug("sbp_handle_command: fetch command failed: %d\n", ret); + goto err; + } + + ret = sbp_fetch_page_table(req); + if (ret) { + pr_debug("sbp_handle_command: fetch page table failed: %d\n", + ret); + goto err; + } + + unpacked_lun = req->login->lun->unpacked_lun; + sbp_calc_data_length_direction(req, &data_length, &data_dir); + + pr_debug("sbp_handle_command ORB:0x%llx unpacked_lun:%d data_len:%d data_dir:%d\n", + req->orb_pointer, unpacked_lun, data_length, data_dir); + + if (target_submit_cmd(&req->se_cmd, sess->se_sess, req->cmd_buf, + req->sense_buf, unpacked_lun, data_length, + MSG_SIMPLE_TAG, data_dir, 0)) + goto err; + + return; + +err: + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + sbp_send_status(req); + sbp_free_request(req); +} + +/* + * DMA_TO_DEVICE = read from initiator (SCSI WRITE) + * DMA_FROM_DEVICE = write to initiator (SCSI READ) + */ +static int sbp_rw_data(struct sbp_target_request *req) +{ + struct sbp_session *sess = req->login->sess; + int tcode, sg_miter_flags, max_payload, pg_size, speed, node_id, + generation, num_pte, length, tfr_length, + rcode = RCODE_COMPLETE; + struct sbp_page_table_entry *pte; + unsigned long long offset; + struct fw_card *card; + struct sg_mapping_iter iter; + + if (req->se_cmd.data_direction == DMA_FROM_DEVICE) { + tcode = TCODE_WRITE_BLOCK_REQUEST; + sg_miter_flags = SG_MITER_FROM_SG; + } else { + tcode = TCODE_READ_BLOCK_REQUEST; + sg_miter_flags = SG_MITER_TO_SG; + } + + max_payload = 4 << CMDBLK_ORB_MAX_PAYLOAD(be32_to_cpu(req->orb.misc)); + speed = CMDBLK_ORB_SPEED(be32_to_cpu(req->orb.misc)); + + pg_size = CMDBLK_ORB_PG_SIZE(be32_to_cpu(req->orb.misc)); + if (pg_size) { + pr_err("sbp_run_transaction: page size ignored\n"); + pg_size = 0x100 << pg_size; + } + + spin_lock_bh(&sess->lock); + card = fw_card_get(sess->card); + node_id = sess->node_id; + generation = sess->generation; + spin_unlock_bh(&sess->lock); + + if (req->pg_tbl) { + pte = req->pg_tbl; + num_pte = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)); + + offset = 0; + length = 0; + } else { + pte = NULL; + num_pte = 0; + + offset = sbp2_pointer_to_addr(&req->orb.data_descriptor); + length = req->se_cmd.data_length; + } + + sg_miter_start(&iter, req->se_cmd.t_data_sg, req->se_cmd.t_data_nents, + sg_miter_flags); + + while (length || num_pte) { + if (!length) { + offset = (u64)be16_to_cpu(pte->segment_base_hi) << 32 | + be32_to_cpu(pte->segment_base_lo); + length = be16_to_cpu(pte->segment_length); + + pte++; + num_pte--; + } + + sg_miter_next(&iter); + + tfr_length = min3(length, max_payload, (int)iter.length); + + /* FIXME: take page_size into account */ + + rcode = sbp_run_transaction(card, tcode, node_id, + generation, speed, + offset, iter.addr, tfr_length); + + if (rcode != RCODE_COMPLETE) + break; + + length -= tfr_length; + offset += tfr_length; + iter.consumed = tfr_length; + } + + sg_miter_stop(&iter); + fw_card_put(card); + + if (rcode == RCODE_COMPLETE) { + WARN_ON(length != 0); + return 0; + } else { + return -EIO; + } +} + +static int sbp_send_status(struct sbp_target_request *req) +{ + int ret, length; + struct sbp_login_descriptor *login = req->login; + + length = (((be32_to_cpu(req->status.status) >> 24) & 0x07) + 1) * 4; + + ret = sbp_run_request_transaction(req, TCODE_WRITE_BLOCK_REQUEST, + login->status_fifo_addr, &req->status, length); + if (ret != RCODE_COMPLETE) { + pr_debug("sbp_send_status: write failed: 0x%x\n", ret); + return -EIO; + } + + pr_debug("sbp_send_status: status write complete for ORB: 0x%llx\n", + req->orb_pointer); + + return 0; +} + +static void sbp_sense_mangle(struct sbp_target_request *req) +{ + struct se_cmd *se_cmd = &req->se_cmd; + u8 *sense = req->sense_buf; + u8 *status = req->status.data; + + WARN_ON(se_cmd->scsi_sense_length < 18); + + switch (sense[0] & 0x7f) { /* sfmt */ + case 0x70: /* current, fixed */ + status[0] = 0 << 6; + break; + case 0x71: /* deferred, fixed */ + status[0] = 1 << 6; + break; + case 0x72: /* current, descriptor */ + case 0x73: /* deferred, descriptor */ + default: + /* + * TODO: SBP-3 specifies what we should do with descriptor + * format sense data + */ + pr_err("sbp_send_sense: unknown sense format: 0x%x\n", + sense[0]); + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQUEST_ABORTED)); + return; + } + + status[0] |= se_cmd->scsi_status & 0x3f;/* status */ + status[1] = + (sense[0] & 0x80) | /* valid */ + ((sense[2] & 0xe0) >> 1) | /* mark, eom, ili */ + (sense[2] & 0x0f); /* sense_key */ + status[2] = se_cmd->scsi_asc; /* sense_code */ + status[3] = se_cmd->scsi_ascq; /* sense_qualifier */ + + /* information */ + status[4] = sense[3]; + status[5] = sense[4]; + status[6] = sense[5]; + status[7] = sense[6]; + + /* CDB-dependent */ + status[8] = sense[8]; + status[9] = sense[9]; + status[10] = sense[10]; + status[11] = sense[11]; + + /* fru */ + status[12] = sense[14]; + + /* sense_key-dependent */ + status[13] = sense[15]; + status[14] = sense[16]; + status[15] = sense[17]; + + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(5) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static int sbp_send_sense(struct sbp_target_request *req) +{ + struct se_cmd *se_cmd = &req->se_cmd; + + if (se_cmd->scsi_sense_length) { + sbp_sense_mangle(req); + } else { + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); + } + + return sbp_send_status(req); +} + +static void sbp_free_request(struct sbp_target_request *req) +{ + kfree(req->pg_tbl); + kfree(req->cmd_buf); + kfree(req); +} + +static void sbp_mgt_agent_process(struct work_struct *work) +{ + struct sbp_management_agent *agent = + container_of(work, struct sbp_management_agent, work); + struct sbp_management_request *req = agent->request; + int ret; + int status_data_len = 0; + + /* fetch the ORB from the initiator */ + ret = sbp_run_transaction(req->card, TCODE_READ_BLOCK_REQUEST, + req->node_addr, req->generation, req->speed, + agent->orb_offset, &req->orb, sizeof(req->orb)); + if (ret != RCODE_COMPLETE) { + pr_debug("mgt_orb fetch failed: %x\n", ret); + goto out; + } + + pr_debug("mgt_orb ptr1:0x%llx ptr2:0x%llx misc:0x%x len:0x%x status_fifo:0x%llx\n", + sbp2_pointer_to_addr(&req->orb.ptr1), + sbp2_pointer_to_addr(&req->orb.ptr2), + be32_to_cpu(req->orb.misc), be32_to_cpu(req->orb.length), + sbp2_pointer_to_addr(&req->orb.status_fifo)); + + if (!ORB_NOTIFY(be32_to_cpu(req->orb.misc)) || + ORB_REQUEST_FORMAT(be32_to_cpu(req->orb.misc)) != 0) { + pr_err("mgt_orb bad request\n"); + goto out; + } + + switch (MANAGEMENT_ORB_FUNCTION(be32_to_cpu(req->orb.misc))) { + case MANAGEMENT_ORB_FUNCTION_LOGIN: + sbp_management_request_login(agent, req, &status_data_len); + break; + + case MANAGEMENT_ORB_FUNCTION_QUERY_LOGINS: + sbp_management_request_query_logins(agent, req, + &status_data_len); + break; + + case MANAGEMENT_ORB_FUNCTION_RECONNECT: + sbp_management_request_reconnect(agent, req, &status_data_len); + break; + + case MANAGEMENT_ORB_FUNCTION_SET_PASSWORD: + pr_notice("SET PASSWORD not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + case MANAGEMENT_ORB_FUNCTION_LOGOUT: + sbp_management_request_logout(agent, req, &status_data_len); + break; + + case MANAGEMENT_ORB_FUNCTION_ABORT_TASK: + pr_notice("ABORT TASK not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + case MANAGEMENT_ORB_FUNCTION_ABORT_TASK_SET: + pr_notice("ABORT TASK SET not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + case MANAGEMENT_ORB_FUNCTION_LOGICAL_UNIT_RESET: + pr_notice("LOGICAL UNIT RESET not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + case MANAGEMENT_ORB_FUNCTION_TARGET_RESET: + pr_notice("TARGET RESET not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + default: + pr_notice("unknown management function 0x%x\n", + MANAGEMENT_ORB_FUNCTION(be32_to_cpu(req->orb.misc))); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + } + + req->status.status |= cpu_to_be32( + STATUS_BLOCK_SRC(1) | /* Response to ORB, next_ORB absent */ + STATUS_BLOCK_LEN(DIV_ROUND_UP(status_data_len, 4) + 1) | + STATUS_BLOCK_ORB_OFFSET_HIGH(agent->orb_offset >> 32)); + req->status.orb_low = cpu_to_be32(agent->orb_offset); + + /* write the status block back to the initiator */ + ret = sbp_run_transaction(req->card, TCODE_WRITE_BLOCK_REQUEST, + req->node_addr, req->generation, req->speed, + sbp2_pointer_to_addr(&req->orb.status_fifo), + &req->status, 8 + status_data_len); + if (ret != RCODE_COMPLETE) { + pr_debug("mgt_orb status write failed: %x\n", ret); + goto out; + } + +out: + fw_card_put(req->card); + kfree(req); + + spin_lock_bh(&agent->lock); + agent->state = MANAGEMENT_AGENT_STATE_IDLE; + spin_unlock_bh(&agent->lock); +} + +static void sbp_mgt_agent_rw(struct fw_card *card, + struct fw_request *request, int tcode, int destination, int source, + int generation, unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct sbp_management_agent *agent = callback_data; + struct sbp2_pointer *ptr = data; + int rcode = RCODE_ADDRESS_ERROR; + + if (!agent->tport->enable) + goto out; + + if ((offset != agent->handler.offset) || (length != 8)) + goto out; + + if (tcode == TCODE_WRITE_BLOCK_REQUEST) { + struct sbp_management_request *req; + int prev_state; + + spin_lock_bh(&agent->lock); + prev_state = agent->state; + agent->state = MANAGEMENT_AGENT_STATE_BUSY; + spin_unlock_bh(&agent->lock); + + if (prev_state == MANAGEMENT_AGENT_STATE_BUSY) { + pr_notice("ignoring management request while busy\n"); + rcode = RCODE_CONFLICT_ERROR; + goto out; + } + + req = kzalloc(sizeof(*req), GFP_ATOMIC); + if (!req) { + rcode = RCODE_CONFLICT_ERROR; + goto out; + } + + req->card = fw_card_get(card); + req->generation = generation; + req->node_addr = source; + req->speed = fw_get_request_speed(request); + + agent->orb_offset = sbp2_pointer_to_addr(ptr); + agent->request = req; + + queue_work(system_unbound_wq, &agent->work); + rcode = RCODE_COMPLETE; + } else if (tcode == TCODE_READ_BLOCK_REQUEST) { + addr_to_sbp2_pointer(agent->orb_offset, ptr); + rcode = RCODE_COMPLETE; + } else { + rcode = RCODE_TYPE_ERROR; + } + +out: + fw_send_response(card, request, rcode); +} + +static struct sbp_management_agent *sbp_management_agent_register( + struct sbp_tport *tport) +{ + int ret; + struct sbp_management_agent *agent; + + agent = kmalloc(sizeof(*agent), GFP_KERNEL); + if (!agent) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&agent->lock); + agent->tport = tport; + agent->handler.length = 0x08; + agent->handler.address_callback = sbp_mgt_agent_rw; + agent->handler.callback_data = agent; + agent->state = MANAGEMENT_AGENT_STATE_IDLE; + INIT_WORK(&agent->work, sbp_mgt_agent_process); + agent->orb_offset = 0; + agent->request = NULL; + + ret = fw_core_add_address_handler(&agent->handler, + &sbp_register_region); + if (ret < 0) { + kfree(agent); + return ERR_PTR(ret); + } + + return agent; +} + +static void sbp_management_agent_unregister(struct sbp_management_agent *agent) +{ + fw_core_remove_address_handler(&agent->handler); + cancel_work_sync(&agent->work); + kfree(agent); +} + +static int sbp_check_true(struct se_portal_group *se_tpg) +{ + return 1; +} + +static int sbp_check_false(struct se_portal_group *se_tpg) +{ + return 0; +} + +static char *sbp_get_fabric_name(void) +{ + return "sbp"; +} + +static char *sbp_get_fabric_wwn(struct se_portal_group *se_tpg) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + + return &tport->tport_name[0]; +} + +static u16 sbp_get_tag(struct se_portal_group *se_tpg) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + return tpg->tport_tpgt; +} + +static u32 sbp_get_default_depth(struct se_portal_group *se_tpg) +{ + return 1; +} + +static struct se_node_acl *sbp_alloc_fabric_acl(struct se_portal_group *se_tpg) +{ + struct sbp_nacl *nacl; + + nacl = kzalloc(sizeof(struct sbp_nacl), GFP_KERNEL); + if (!nacl) { + pr_err("Unable to allocate struct sbp_nacl\n"); + return NULL; + } + + return &nacl->se_node_acl; +} + +static void sbp_release_fabric_acl( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl) +{ + struct sbp_nacl *nacl = + container_of(se_nacl, struct sbp_nacl, se_node_acl); + kfree(nacl); +} + +static u32 sbp_tpg_get_inst_index(struct se_portal_group *se_tpg) +{ + return 1; +} + +static void sbp_release_cmd(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + + sbp_free_request(req); +} + +static int sbp_shutdown_session(struct se_session *se_sess) +{ + return 0; +} + +static void sbp_close_session(struct se_session *se_sess) +{ + return; +} + +static u32 sbp_sess_get_index(struct se_session *se_sess) +{ + return 0; +} + +static int sbp_write_pending(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + int ret; + + ret = sbp_rw_data(req); + if (ret) { + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP( + STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS( + SBP_STATUS_UNSPECIFIED_ERROR)); + sbp_send_status(req); + return ret; + } + + target_execute_cmd(se_cmd); + return 0; +} + +static int sbp_write_pending_status(struct se_cmd *se_cmd) +{ + return 0; +} + +static void sbp_set_default_node_attrs(struct se_node_acl *nacl) +{ + return; +} + +static u32 sbp_get_task_tag(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + + /* only used for printk until we do TMRs */ + return (u32)req->orb_pointer; +} + +static int sbp_get_cmd_state(struct se_cmd *se_cmd) +{ + return 0; +} + +static int sbp_queue_data_in(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + int ret; + + ret = sbp_rw_data(req); + if (ret) { + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + sbp_send_status(req); + return ret; + } + + return sbp_send_sense(req); +} + +/* + * Called after command (no data transfer) or after the write (to device) + * operation is completed + */ +static int sbp_queue_status(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + + return sbp_send_sense(req); +} + +static void sbp_queue_tm_rsp(struct se_cmd *se_cmd) +{ +} + +static void sbp_aborted_task(struct se_cmd *se_cmd) +{ + return; +} + +static int sbp_check_stop_free(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + + transport_generic_free_cmd(&req->se_cmd, 0); + return 1; +} + +/* + * Handlers for Serial Bus Protocol 2/3 (SBP-2 / SBP-3) + */ +static u8 sbp_get_fabric_proto_ident(struct se_portal_group *se_tpg) +{ + /* + * Return a IEEE 1394 SCSI Protocol identifier for loopback operations + * This is defined in section 7.5.1 Table 362 in spc4r17 + */ + return SCSI_PROTOCOL_SBP; +} + +static u32 sbp_get_pr_transport_id( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code, + unsigned char *buf) +{ + int ret; + + /* + * Set PROTOCOL IDENTIFIER to 3h for SBP + */ + buf[0] = SCSI_PROTOCOL_SBP; + /* + * From spc4r17, 7.5.4.4 TransportID for initiator ports using SCSI + * over IEEE 1394 + */ + ret = hex2bin(&buf[8], se_nacl->initiatorname, 8); + if (ret < 0) + pr_debug("sbp transport_id: invalid hex string\n"); + + /* + * The IEEE 1394 Transport ID is a hardcoded 24-byte length + */ + return 24; +} + +static u32 sbp_get_pr_transport_id_len( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code) +{ + *format_code = 0; + /* + * From spc4r17, 7.5.4.4 TransportID for initiator ports using SCSI + * over IEEE 1394 + * + * The SBP Transport ID is a hardcoded 24-byte length + */ + return 24; +} + +/* + * Used for handling SCSI fabric dependent TransportIDs in SPC-3 and above + * Persistent Reservation SPEC_I_PT=1 and PROUT REGISTER_AND_MOVE operations. + */ +static char *sbp_parse_pr_out_transport_id( + struct se_portal_group *se_tpg, + const char *buf, + u32 *out_tid_len, + char **port_nexus_ptr) +{ + /* + * Assume the FORMAT CODE 00b from spc4r17, 7.5.4.4 TransportID + * for initiator ports using SCSI over SBP Serial SCSI Protocol + * + * The TransportID for a IEEE 1394 Initiator Port is of fixed size of + * 24 bytes, and IEEE 1394 does not contain a I_T nexus identifier, + * so we return the **port_nexus_ptr set to NULL. + */ + *port_nexus_ptr = NULL; + *out_tid_len = 24; + + return (char *)&buf[8]; +} + +static int sbp_count_se_tpg_luns(struct se_portal_group *tpg) +{ + int i, count = 0; + + spin_lock(&tpg->tpg_lun_lock); + for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { + struct se_lun *se_lun = tpg->tpg_lun_list[i]; + + if (se_lun->lun_status == TRANSPORT_LUN_STATUS_FREE) + continue; + + count++; + } + spin_unlock(&tpg->tpg_lun_lock); + + return count; +} + +static int sbp_update_unit_directory(struct sbp_tport *tport) +{ + int num_luns, num_entries, idx = 0, mgt_agt_addr, ret, i; + u32 *data; + + if (tport->unit_directory.data) { + fw_core_remove_descriptor(&tport->unit_directory); + kfree(tport->unit_directory.data); + tport->unit_directory.data = NULL; + } + + if (!tport->enable || !tport->tpg) + return 0; + + num_luns = sbp_count_se_tpg_luns(&tport->tpg->se_tpg); + + /* + * Number of entries in the final unit directory: + * - all of those in the template + * - management_agent + * - unit_characteristics + * - reconnect_timeout + * - unit unique ID + * - one for each LUN + * + * MUST NOT include leaf or sub-directory entries + */ + num_entries = ARRAY_SIZE(sbp_unit_directory_template) + 4 + num_luns; + + if (tport->directory_id != -1) + num_entries++; + + /* allocate num_entries + 4 for the header and unique ID leaf */ + data = kcalloc((num_entries + 4), sizeof(u32), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* directory_length */ + data[idx++] = num_entries << 16; + + /* directory_id */ + if (tport->directory_id != -1) + data[idx++] = (CSR_DIRECTORY_ID << 24) | tport->directory_id; + + /* unit directory template */ + memcpy(&data[idx], sbp_unit_directory_template, + sizeof(sbp_unit_directory_template)); + idx += ARRAY_SIZE(sbp_unit_directory_template); + + /* management_agent */ + mgt_agt_addr = (tport->mgt_agt->handler.offset - CSR_REGISTER_BASE) / 4; + data[idx++] = 0x54000000 | (mgt_agt_addr & 0x00ffffff); + + /* unit_characteristics */ + data[idx++] = 0x3a000000 | + (((tport->mgt_orb_timeout * 2) << 8) & 0xff00) | + SBP_ORB_FETCH_SIZE; + + /* reconnect_timeout */ + data[idx++] = 0x3d000000 | (tport->max_reconnect_timeout & 0xffff); + + /* unit unique ID (leaf is just after LUNs) */ + data[idx++] = 0x8d000000 | (num_luns + 1); + + spin_lock(&tport->tpg->se_tpg.tpg_lun_lock); + for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { + struct se_lun *se_lun = tport->tpg->se_tpg.tpg_lun_list[i]; + struct se_device *dev; + int type; + + if (se_lun->lun_status == TRANSPORT_LUN_STATUS_FREE) + continue; + + spin_unlock(&tport->tpg->se_tpg.tpg_lun_lock); + + dev = se_lun->lun_se_dev; + type = dev->transport->get_device_type(dev); + + /* logical_unit_number */ + data[idx++] = 0x14000000 | + ((type << 16) & 0x1f0000) | + (se_lun->unpacked_lun & 0xffff); + + spin_lock(&tport->tpg->se_tpg.tpg_lun_lock); + } + spin_unlock(&tport->tpg->se_tpg.tpg_lun_lock); + + /* unit unique ID leaf */ + data[idx++] = 2 << 16; + data[idx++] = tport->guid >> 32; + data[idx++] = tport->guid; + + tport->unit_directory.length = idx; + tport->unit_directory.key = (CSR_DIRECTORY | CSR_UNIT) << 24; + tport->unit_directory.data = data; + + ret = fw_core_add_descriptor(&tport->unit_directory); + if (ret < 0) { + kfree(tport->unit_directory.data); + tport->unit_directory.data = NULL; + } + + return ret; +} + +static ssize_t sbp_parse_wwn(const char *name, u64 *wwn) +{ + const char *cp; + char c, nibble; + int pos = 0, err; + + *wwn = 0; + for (cp = name; cp < &name[SBP_NAMELEN - 1]; cp++) { + c = *cp; + if (c == '\n' && cp[1] == '\0') + continue; + if (c == '\0') { + err = 2; + if (pos != 16) + goto fail; + return cp - name; + } + err = 3; + if (isdigit(c)) + nibble = c - '0'; + else if (isxdigit(c)) + nibble = tolower(c) - 'a' + 10; + else + goto fail; + *wwn = (*wwn << 4) | nibble; + pos++; + } + err = 4; +fail: + printk(KERN_INFO "err %u len %zu pos %u\n", + err, cp - name, pos); + return -1; +} + +static ssize_t sbp_format_wwn(char *buf, size_t len, u64 wwn) +{ + return snprintf(buf, len, "%016llx", wwn); +} + +static struct se_node_acl *sbp_make_nodeacl( + struct se_portal_group *se_tpg, + struct config_group *group, + const char *name) +{ + struct se_node_acl *se_nacl, *se_nacl_new; + struct sbp_nacl *nacl; + u64 guid = 0; + u32 nexus_depth = 1; + + if (sbp_parse_wwn(name, &guid) < 0) + return ERR_PTR(-EINVAL); + + se_nacl_new = sbp_alloc_fabric_acl(se_tpg); + if (!se_nacl_new) + return ERR_PTR(-ENOMEM); + + /* + * se_nacl_new may be released by core_tpg_add_initiator_node_acl() + * when converting a NodeACL from demo mode -> explict + */ + se_nacl = core_tpg_add_initiator_node_acl(se_tpg, se_nacl_new, + name, nexus_depth); + if (IS_ERR(se_nacl)) { + sbp_release_fabric_acl(se_tpg, se_nacl_new); + return se_nacl; + } + + nacl = container_of(se_nacl, struct sbp_nacl, se_node_acl); + nacl->guid = guid; + sbp_format_wwn(nacl->iport_name, SBP_NAMELEN, guid); + + return se_nacl; +} + +static void sbp_drop_nodeacl(struct se_node_acl *se_acl) +{ + struct sbp_nacl *nacl = + container_of(se_acl, struct sbp_nacl, se_node_acl); + + core_tpg_del_initiator_node_acl(se_acl->se_tpg, se_acl, 1); + kfree(nacl); +} + +static int sbp_post_link_lun( + struct se_portal_group *se_tpg, + struct se_lun *se_lun) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + + return sbp_update_unit_directory(tpg->tport); +} + +static void sbp_pre_unlink_lun( + struct se_portal_group *se_tpg, + struct se_lun *se_lun) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + int ret; + + if (sbp_count_se_tpg_luns(&tpg->se_tpg) == 0) + tport->enable = 0; + + ret = sbp_update_unit_directory(tport); + if (ret < 0) + pr_err("unlink LUN: failed to update unit directory\n"); +} + +static struct se_portal_group *sbp_make_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct sbp_tport *tport = + container_of(wwn, struct sbp_tport, tport_wwn); + + struct sbp_tpg *tpg; + unsigned long tpgt; + int ret; + + if (strstr(name, "tpgt_") != name) + return ERR_PTR(-EINVAL); + if (kstrtoul(name + 5, 10, &tpgt) || tpgt > UINT_MAX) + return ERR_PTR(-EINVAL); + + if (tport->tpg) { + pr_err("Only one TPG per Unit is possible.\n"); + return ERR_PTR(-EBUSY); + } + + tpg = kzalloc(sizeof(*tpg), GFP_KERNEL); + if (!tpg) { + pr_err("Unable to allocate struct sbp_tpg\n"); + return ERR_PTR(-ENOMEM); + } + + tpg->tport = tport; + tpg->tport_tpgt = tpgt; + tport->tpg = tpg; + + /* default attribute values */ + tport->enable = 0; + tport->directory_id = -1; + tport->mgt_orb_timeout = 15; + tport->max_reconnect_timeout = 5; + tport->max_logins_per_lun = 1; + + tport->mgt_agt = sbp_management_agent_register(tport); + if (IS_ERR(tport->mgt_agt)) { + ret = PTR_ERR(tport->mgt_agt); + goto out_free_tpg; + } + + ret = core_tpg_register(&sbp_fabric_configfs->tf_ops, wwn, + &tpg->se_tpg, (void *)tpg, + TRANSPORT_TPG_TYPE_NORMAL); + if (ret < 0) + goto out_unreg_mgt_agt; + + return &tpg->se_tpg; + +out_unreg_mgt_agt: + sbp_management_agent_unregister(tport->mgt_agt); +out_free_tpg: + tport->tpg = NULL; + kfree(tpg); + return ERR_PTR(ret); +} + +static void sbp_drop_tpg(struct se_portal_group *se_tpg) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + + core_tpg_deregister(se_tpg); + sbp_management_agent_unregister(tport->mgt_agt); + tport->tpg = NULL; + kfree(tpg); +} + +static struct se_wwn *sbp_make_tport( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct sbp_tport *tport; + u64 guid = 0; + + if (sbp_parse_wwn(name, &guid) < 0) + return ERR_PTR(-EINVAL); + + tport = kzalloc(sizeof(*tport), GFP_KERNEL); + if (!tport) { + pr_err("Unable to allocate struct sbp_tport\n"); + return ERR_PTR(-ENOMEM); + } + + tport->guid = guid; + sbp_format_wwn(tport->tport_name, SBP_NAMELEN, guid); + + return &tport->tport_wwn; +} + +static void sbp_drop_tport(struct se_wwn *wwn) +{ + struct sbp_tport *tport = + container_of(wwn, struct sbp_tport, tport_wwn); + + kfree(tport); +} + +static ssize_t sbp_wwn_show_attr_version( + struct target_fabric_configfs *tf, + char *page) +{ + return sprintf(page, "FireWire SBP fabric module %s\n", SBP_VERSION); +} + +TF_WWN_ATTR_RO(sbp, version); + +static struct configfs_attribute *sbp_wwn_attrs[] = { + &sbp_wwn_version.attr, + NULL, +}; + +static ssize_t sbp_tpg_show_directory_id( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + + if (tport->directory_id == -1) + return sprintf(page, "implicit\n"); + else + return sprintf(page, "%06x\n", tport->directory_id); +} + +static ssize_t sbp_tpg_store_directory_id( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + + if (tport->enable) { + pr_err("Cannot change the directory_id on an active target.\n"); + return -EBUSY; + } + + if (strstr(page, "implicit") == page) { + tport->directory_id = -1; + } else { + if (kstrtoul(page, 16, &val) < 0) + return -EINVAL; + if (val > 0xffffff) + return -EINVAL; + + tport->directory_id = val; + } + + return count; +} + +static ssize_t sbp_tpg_show_enable( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + return sprintf(page, "%d\n", tport->enable); +} + +static ssize_t sbp_tpg_store_enable( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + int ret; + + if (kstrtoul(page, 0, &val) < 0) + return -EINVAL; + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (tport->enable == val) + return count; + + if (val) { + if (sbp_count_se_tpg_luns(&tpg->se_tpg) == 0) { + pr_err("Cannot enable a target with no LUNs!\n"); + return -EINVAL; + } + } else { + /* XXX: force-shutdown sessions instead? */ + spin_lock_bh(&se_tpg->session_lock); + if (!list_empty(&se_tpg->tpg_sess_list)) { + spin_unlock_bh(&se_tpg->session_lock); + return -EBUSY; + } + spin_unlock_bh(&se_tpg->session_lock); + } + + tport->enable = val; + + ret = sbp_update_unit_directory(tport); + if (ret < 0) { + pr_err("Could not update Config ROM\n"); + return ret; + } + + return count; +} + +TF_TPG_BASE_ATTR(sbp, directory_id, S_IRUGO | S_IWUSR); +TF_TPG_BASE_ATTR(sbp, enable, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *sbp_tpg_base_attrs[] = { + &sbp_tpg_directory_id.attr, + &sbp_tpg_enable.attr, + NULL, +}; + +static ssize_t sbp_tpg_attrib_show_mgt_orb_timeout( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + return sprintf(page, "%d\n", tport->mgt_orb_timeout); +} + +static ssize_t sbp_tpg_attrib_store_mgt_orb_timeout( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + int ret; + + if (kstrtoul(page, 0, &val) < 0) + return -EINVAL; + if ((val < 1) || (val > 127)) + return -EINVAL; + + if (tport->mgt_orb_timeout == val) + return count; + + tport->mgt_orb_timeout = val; + + ret = sbp_update_unit_directory(tport); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t sbp_tpg_attrib_show_max_reconnect_timeout( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + return sprintf(page, "%d\n", tport->max_reconnect_timeout); +} + +static ssize_t sbp_tpg_attrib_store_max_reconnect_timeout( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + int ret; + + if (kstrtoul(page, 0, &val) < 0) + return -EINVAL; + if ((val < 1) || (val > 32767)) + return -EINVAL; + + if (tport->max_reconnect_timeout == val) + return count; + + tport->max_reconnect_timeout = val; + + ret = sbp_update_unit_directory(tport); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t sbp_tpg_attrib_show_max_logins_per_lun( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + return sprintf(page, "%d\n", tport->max_logins_per_lun); +} + +static ssize_t sbp_tpg_attrib_store_max_logins_per_lun( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + + if (kstrtoul(page, 0, &val) < 0) + return -EINVAL; + if ((val < 1) || (val > 127)) + return -EINVAL; + + /* XXX: also check against current count? */ + + tport->max_logins_per_lun = val; + + return count; +} + +TF_TPG_ATTRIB_ATTR(sbp, mgt_orb_timeout, S_IRUGO | S_IWUSR); +TF_TPG_ATTRIB_ATTR(sbp, max_reconnect_timeout, S_IRUGO | S_IWUSR); +TF_TPG_ATTRIB_ATTR(sbp, max_logins_per_lun, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *sbp_tpg_attrib_attrs[] = { + &sbp_tpg_attrib_mgt_orb_timeout.attr, + &sbp_tpg_attrib_max_reconnect_timeout.attr, + &sbp_tpg_attrib_max_logins_per_lun.attr, + NULL, +}; + +static struct target_core_fabric_ops sbp_ops = { + .get_fabric_name = sbp_get_fabric_name, + .get_fabric_proto_ident = sbp_get_fabric_proto_ident, + .tpg_get_wwn = sbp_get_fabric_wwn, + .tpg_get_tag = sbp_get_tag, + .tpg_get_default_depth = sbp_get_default_depth, + .tpg_get_pr_transport_id = sbp_get_pr_transport_id, + .tpg_get_pr_transport_id_len = sbp_get_pr_transport_id_len, + .tpg_parse_pr_out_transport_id = sbp_parse_pr_out_transport_id, + .tpg_check_demo_mode = sbp_check_true, + .tpg_check_demo_mode_cache = sbp_check_true, + .tpg_check_demo_mode_write_protect = sbp_check_false, + .tpg_check_prod_mode_write_protect = sbp_check_false, + .tpg_alloc_fabric_acl = sbp_alloc_fabric_acl, + .tpg_release_fabric_acl = sbp_release_fabric_acl, + .tpg_get_inst_index = sbp_tpg_get_inst_index, + .release_cmd = sbp_release_cmd, + .shutdown_session = sbp_shutdown_session, + .close_session = sbp_close_session, + .sess_get_index = sbp_sess_get_index, + .write_pending = sbp_write_pending, + .write_pending_status = sbp_write_pending_status, + .set_default_node_attributes = sbp_set_default_node_attrs, + .get_task_tag = sbp_get_task_tag, + .get_cmd_state = sbp_get_cmd_state, + .queue_data_in = sbp_queue_data_in, + .queue_status = sbp_queue_status, + .queue_tm_rsp = sbp_queue_tm_rsp, + .aborted_task = sbp_aborted_task, + .check_stop_free = sbp_check_stop_free, + + .fabric_make_wwn = sbp_make_tport, + .fabric_drop_wwn = sbp_drop_tport, + .fabric_make_tpg = sbp_make_tpg, + .fabric_drop_tpg = sbp_drop_tpg, + .fabric_post_link = sbp_post_link_lun, + .fabric_pre_unlink = sbp_pre_unlink_lun, + .fabric_make_np = NULL, + .fabric_drop_np = NULL, + .fabric_make_nodeacl = sbp_make_nodeacl, + .fabric_drop_nodeacl = sbp_drop_nodeacl, +}; + +static int sbp_register_configfs(void) +{ + struct target_fabric_configfs *fabric; + int ret; + + fabric = target_fabric_configfs_init(THIS_MODULE, "sbp"); + if (IS_ERR(fabric)) { + pr_err("target_fabric_configfs_init() failed\n"); + return PTR_ERR(fabric); + } + + fabric->tf_ops = sbp_ops; + + /* + * Setup default attribute lists for various fabric->tf_cit_tmpl + */ + fabric->tf_cit_tmpl.tfc_wwn_cit.ct_attrs = sbp_wwn_attrs; + fabric->tf_cit_tmpl.tfc_tpg_base_cit.ct_attrs = sbp_tpg_base_attrs; + fabric->tf_cit_tmpl.tfc_tpg_attrib_cit.ct_attrs = sbp_tpg_attrib_attrs; + fabric->tf_cit_tmpl.tfc_tpg_param_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_np_base_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_nacl_base_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_nacl_auth_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_nacl_param_cit.ct_attrs = NULL; + + ret = target_fabric_configfs_register(fabric); + if (ret < 0) { + pr_err("target_fabric_configfs_register() failed for SBP\n"); + return ret; + } + + sbp_fabric_configfs = fabric; + + return 0; +}; + +static void sbp_deregister_configfs(void) +{ + if (!sbp_fabric_configfs) + return; + + target_fabric_configfs_deregister(sbp_fabric_configfs); + sbp_fabric_configfs = NULL; +}; + +static int __init sbp_init(void) +{ + int ret; + + ret = sbp_register_configfs(); + if (ret < 0) + return ret; + + return 0; +}; + +static void __exit sbp_exit(void) +{ + sbp_deregister_configfs(); +}; + +MODULE_DESCRIPTION("FireWire SBP fabric driver"); +MODULE_LICENSE("GPL"); +module_init(sbp_init); +module_exit(sbp_exit); diff --git a/drivers/target/sbp/sbp_target.h b/drivers/target/sbp/sbp_target.h new file mode 100644 index 00000000000..6d0d74a2c54 --- /dev/null +++ b/drivers/target/sbp/sbp_target.h @@ -0,0 +1,251 @@ +#ifndef _SBP_BASE_H +#define _SBP_BASE_H + +#include <linux/firewire.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <target/target_core_base.h> + +#define SBP_VERSION "v0.1" +#define SBP_NAMELEN 32 + +#define SBP_ORB_FETCH_SIZE 8 + +#define MANAGEMENT_AGENT_STATE_IDLE 0 +#define MANAGEMENT_AGENT_STATE_BUSY 1 + +#define ORB_NOTIFY(v) (((v) >> 31) & 0x01) +#define ORB_REQUEST_FORMAT(v) (((v) >> 29) & 0x03) + +#define MANAGEMENT_ORB_FUNCTION(v) (((v) >> 16) & 0x0f) + +#define MANAGEMENT_ORB_FUNCTION_LOGIN 0x0 +#define MANAGEMENT_ORB_FUNCTION_QUERY_LOGINS 0x1 +#define MANAGEMENT_ORB_FUNCTION_RECONNECT 0x3 +#define MANAGEMENT_ORB_FUNCTION_SET_PASSWORD 0x4 +#define MANAGEMENT_ORB_FUNCTION_LOGOUT 0x7 +#define MANAGEMENT_ORB_FUNCTION_ABORT_TASK 0xb +#define MANAGEMENT_ORB_FUNCTION_ABORT_TASK_SET 0xc +#define MANAGEMENT_ORB_FUNCTION_LOGICAL_UNIT_RESET 0xe +#define MANAGEMENT_ORB_FUNCTION_TARGET_RESET 0xf + +#define LOGIN_ORB_EXCLUSIVE(v) (((v) >> 28) & 0x01) +#define LOGIN_ORB_RESERVED(v) (((v) >> 24) & 0x0f) +#define LOGIN_ORB_RECONNECT(v) (((v) >> 20) & 0x0f) +#define LOGIN_ORB_LUN(v) (((v) >> 0) & 0xffff) +#define LOGIN_ORB_PASSWORD_LENGTH(v) (((v) >> 16) & 0xffff) +#define LOGIN_ORB_RESPONSE_LENGTH(v) (((v) >> 0) & 0xffff) + +#define RECONNECT_ORB_LOGIN_ID(v) (((v) >> 0) & 0xffff) +#define LOGOUT_ORB_LOGIN_ID(v) (((v) >> 0) & 0xffff) + +#define CMDBLK_ORB_DIRECTION(v) (((v) >> 27) & 0x01) +#define CMDBLK_ORB_SPEED(v) (((v) >> 24) & 0x07) +#define CMDBLK_ORB_MAX_PAYLOAD(v) (((v) >> 20) & 0x0f) +#define CMDBLK_ORB_PG_TBL_PRESENT(v) (((v) >> 19) & 0x01) +#define CMDBLK_ORB_PG_SIZE(v) (((v) >> 16) & 0x07) +#define CMDBLK_ORB_DATA_SIZE(v) (((v) >> 0) & 0xffff) + +#define STATUS_BLOCK_SRC(v) (((v) & 0x03) << 30) +#define STATUS_BLOCK_RESP(v) (((v) & 0x03) << 28) +#define STATUS_BLOCK_DEAD(v) (((v) ? 1 : 0) << 27) +#define STATUS_BLOCK_LEN(v) (((v) & 0x07) << 24) +#define STATUS_BLOCK_SBP_STATUS(v) (((v) & 0xff) << 16) +#define STATUS_BLOCK_ORB_OFFSET_HIGH(v) (((v) & 0xffff) << 0) + +#define STATUS_SRC_ORB_CONTINUING 0 +#define STATUS_SRC_ORB_FINISHED 1 +#define STATUS_SRC_UNSOLICITED 2 + +#define STATUS_RESP_REQUEST_COMPLETE 0 +#define STATUS_RESP_TRANSPORT_FAILURE 1 +#define STATUS_RESP_ILLEGAL_REQUEST 2 +#define STATUS_RESP_VENDOR_DEPENDENT 3 + +#define SBP_STATUS_OK 0 +#define SBP_STATUS_REQ_TYPE_NOTSUPP 1 +#define SBP_STATUS_SPEED_NOTSUPP 2 +#define SBP_STATUS_PAGE_SIZE_NOTSUPP 3 +#define SBP_STATUS_ACCESS_DENIED 4 +#define SBP_STATUS_LUN_NOTSUPP 5 +#define SBP_STATUS_PAYLOAD_TOO_SMALL 6 +/* 7 is reserved */ +#define SBP_STATUS_RESOURCES_UNAVAIL 8 +#define SBP_STATUS_FUNCTION_REJECTED 9 +#define SBP_STATUS_LOGIN_ID_UNKNOWN 10 +#define SBP_STATUS_DUMMY_ORB_COMPLETE 11 +#define SBP_STATUS_REQUEST_ABORTED 12 +#define SBP_STATUS_UNSPECIFIED_ERROR 0xff + +#define AGENT_STATE_RESET 0 +#define AGENT_STATE_ACTIVE 1 +#define AGENT_STATE_SUSPENDED 2 +#define AGENT_STATE_DEAD 3 + +struct sbp2_pointer { + __be32 high; + __be32 low; +}; + +struct sbp_command_block_orb { + struct sbp2_pointer next_orb; + struct sbp2_pointer data_descriptor; + __be32 misc; + u8 command_block[12]; +}; + +struct sbp_page_table_entry { + __be16 segment_length; + __be16 segment_base_hi; + __be32 segment_base_lo; +}; + +struct sbp_management_orb { + struct sbp2_pointer ptr1; + struct sbp2_pointer ptr2; + __be32 misc; + __be32 length; + struct sbp2_pointer status_fifo; +}; + +struct sbp_status_block { + __be32 status; + __be32 orb_low; + u8 data[24]; +}; + +struct sbp_login_response_block { + __be32 misc; + struct sbp2_pointer command_block_agent; + __be32 reconnect_hold; +}; + +struct sbp_login_descriptor { + struct sbp_session *sess; + struct list_head link; + + struct se_lun *lun; + + u64 status_fifo_addr; + int exclusive; + u16 login_id; + + struct sbp_target_agent *tgt_agt; +}; + +struct sbp_session { + spinlock_t lock; + struct se_session *se_sess; + struct list_head login_list; + struct delayed_work maint_work; + + u64 guid; /* login_owner_EUI_64 */ + int node_id; /* login_owner_ID */ + + struct fw_card *card; + int generation; + int speed; + + int reconnect_hold; + u64 reconnect_expires; +}; + +struct sbp_nacl { + /* Initiator EUI-64 */ + u64 guid; + /* ASCII formatted GUID for SBP Initiator port */ + char iport_name[SBP_NAMELEN]; + /* Returned by sbp_make_nodeacl() */ + struct se_node_acl se_node_acl; +}; + +struct sbp_tpg { + /* Target portal group tag for TCM */ + u16 tport_tpgt; + /* Pointer back to sbp_tport */ + struct sbp_tport *tport; + /* Returned by sbp_make_tpg() */ + struct se_portal_group se_tpg; +}; + +struct sbp_tport { + /* Target Unit Identifier (EUI-64) */ + u64 guid; + /* Target port name */ + char tport_name[SBP_NAMELEN]; + /* Returned by sbp_make_tport() */ + struct se_wwn tport_wwn; + + struct sbp_tpg *tpg; + + /* FireWire unit directory */ + struct fw_descriptor unit_directory; + + /* SBP Management Agent */ + struct sbp_management_agent *mgt_agt; + + /* Parameters */ + int enable; + s32 directory_id; + int mgt_orb_timeout; + int max_reconnect_timeout; + int max_logins_per_lun; +}; + +static inline u64 sbp2_pointer_to_addr(const struct sbp2_pointer *ptr) +{ + return (u64)(be32_to_cpu(ptr->high) & 0x0000ffff) << 32 | + (be32_to_cpu(ptr->low) & 0xfffffffc); +} + +static inline void addr_to_sbp2_pointer(u64 addr, struct sbp2_pointer *ptr) +{ + ptr->high = cpu_to_be32(addr >> 32); + ptr->low = cpu_to_be32(addr); +} + +struct sbp_target_agent { + spinlock_t lock; + struct fw_address_handler handler; + struct sbp_login_descriptor *login; + int state; + struct work_struct work; + u64 orb_pointer; + bool doorbell; +}; + +struct sbp_target_request { + struct sbp_login_descriptor *login; + u64 orb_pointer; + struct sbp_command_block_orb orb; + struct sbp_status_block status; + struct work_struct work; + + struct se_cmd se_cmd; + struct sbp_page_table_entry *pg_tbl; + void *cmd_buf; + + unsigned char sense_buf[TRANSPORT_SENSE_BUFFER]; +}; + +struct sbp_management_agent { + spinlock_t lock; + struct sbp_tport *tport; + struct fw_address_handler handler; + int state; + struct work_struct work; + u64 orb_offset; + struct sbp_management_request *request; +}; + +struct sbp_management_request { + struct sbp_management_orb orb; + struct sbp_status_block status; + struct fw_card *card; + int generation; + int node_addr; + int speed; +}; + +#endif diff --git a/drivers/target/target_core_alua.c b/drivers/target/target_core_alua.c index 2c5fcfed593..fbc5ebb5f76 100644 --- a/drivers/target/target_core_alua.c +++ b/drivers/target/target_core_alua.c @@ -3,8 +3,7 @@ * * This file contains SPC-3 compliant asymmetric logical unit assigntment (ALUA) * - * Copyright (c) 2009-2010 Rising Tide Systems - * Copyright (c) 2009-2010 Linux-iSCSI.org + * (c) Copyright 2009-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -24,47 +23,170 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/configfs.h> +#include <linux/export.h> +#include <linux/file.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> +#include <asm/unaligned.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> #include <target/target_core_configfs.h> +#include "target_core_internal.h" #include "target_core_alua.h" -#include "target_core_hba.h" #include "target_core_ua.h" -static int core_alua_check_transition(int state, int *primary); +static sense_reason_t core_alua_check_transition(int state, int valid, + int *primary); static int core_alua_set_tg_pt_secondary_state( struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem, - struct se_port *port, int explict, int offline); + struct se_port *port, int explicit, int offline); + +static char *core_alua_dump_state(int state); + +static u16 alua_lu_gps_counter; +static u32 alua_lu_gps_count; + +static DEFINE_SPINLOCK(lu_gps_lock); +static LIST_HEAD(lu_gps_list); + +struct t10_alua_lu_gp *default_lu_gp; + +/* + * REPORT REFERRALS + * + * See sbc3r35 section 5.23 + */ +sense_reason_t +target_emulate_report_referrals(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct t10_alua_lba_map *map; + struct t10_alua_lba_map_member *map_mem; + unsigned char *buf; + u32 rd_len = 0, off; + + if (cmd->data_length < 4) { + pr_warn("REPORT REFERRALS allocation length %u too" + " small\n", cmd->data_length); + return TCM_INVALID_CDB_FIELD; + } + + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + off = 4; + spin_lock(&dev->t10_alua.lba_map_lock); + if (list_empty(&dev->t10_alua.lba_map_list)) { + spin_unlock(&dev->t10_alua.lba_map_lock); + transport_kunmap_data_sg(cmd); + + return TCM_UNSUPPORTED_SCSI_OPCODE; + } + + list_for_each_entry(map, &dev->t10_alua.lba_map_list, + lba_map_list) { + int desc_num = off + 3; + int pg_num; + + off += 4; + if (cmd->data_length > off) + put_unaligned_be64(map->lba_map_first_lba, &buf[off]); + off += 8; + if (cmd->data_length > off) + put_unaligned_be64(map->lba_map_last_lba, &buf[off]); + off += 8; + rd_len += 20; + pg_num = 0; + list_for_each_entry(map_mem, &map->lba_map_mem_list, + lba_map_mem_list) { + int alua_state = map_mem->lba_map_mem_alua_state; + int alua_pg_id = map_mem->lba_map_mem_alua_pg_id; + + if (cmd->data_length > off) + buf[off] = alua_state & 0x0f; + off += 2; + if (cmd->data_length > off) + buf[off] = (alua_pg_id >> 8) & 0xff; + off++; + if (cmd->data_length > off) + buf[off] = (alua_pg_id & 0xff); + off++; + rd_len += 4; + pg_num++; + } + if (cmd->data_length > desc_num) + buf[desc_num] = pg_num; + } + spin_unlock(&dev->t10_alua.lba_map_lock); + + /* + * Set the RETURN DATA LENGTH set in the header of the DataIN Payload + */ + put_unaligned_be16(rd_len, &buf[2]); + + transport_kunmap_data_sg(cmd); + + target_complete_cmd(cmd, GOOD); + return 0; +} /* * REPORT_TARGET_PORT_GROUPS * * See spc4r17 section 6.27 */ -int core_emulate_report_target_port_groups(struct se_cmd *cmd) +sense_reason_t +target_emulate_report_target_port_groups(struct se_cmd *cmd) { - struct se_subsystem_dev *su_dev = SE_DEV(cmd)->se_sub_dev; + struct se_device *dev = cmd->se_dev; struct se_port *port; struct t10_alua_tg_pt_gp *tg_pt_gp; struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; - u32 rd_len = 0, off = 4; /* Skip over RESERVED area to first - Target port group descriptor */ + unsigned char *buf; + u32 rd_len = 0, off; + int ext_hdr = (cmd->t_task_cdb[1] & 0x20); + + /* + * Skip over RESERVED area to first Target port group descriptor + * depending on the PARAMETER DATA FORMAT type.. + */ + if (ext_hdr != 0) + off = 8; + else + off = 4; - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); - list_for_each_entry(tg_pt_gp, &T10_ALUA(su_dev)->tg_pt_gps_list, + if (cmd->data_length < off) { + pr_warn("REPORT TARGET PORT GROUPS allocation length %u too" + " small for %s header\n", cmd->data_length, + (ext_hdr) ? "extended" : "normal"); + return TCM_INVALID_CDB_FIELD; + } + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + spin_lock(&dev->t10_alua.tg_pt_gps_lock); + list_for_each_entry(tg_pt_gp, &dev->t10_alua.tg_pt_gps_list, tg_pt_gp_list) { /* + * Check if the Target port group and Target port descriptor list + * based on tg_pt_gp_members count will fit into the response payload. + * Otherwise, bump rd_len to let the initiator know we have exceeded + * the allocation length and the response is truncated. + */ + if ((off + 8 + (tg_pt_gp->tg_pt_gp_members * 4)) > + cmd->data_length) { + rd_len += 8 + (tg_pt_gp->tg_pt_gp_members * 4); + continue; + } + /* * PREF: Preferred target port bit, determine if this * bit should be set for port group. */ @@ -78,12 +200,7 @@ int core_emulate_report_target_port_groups(struct se_cmd *cmd) /* * Set supported ASYMMETRIC ACCESS State bits */ - buf[off] = 0x80; /* T_SUP */ - buf[off] |= 0x40; /* O_SUP */ - buf[off] |= 0x8; /* U_SUP */ - buf[off] |= 0x4; /* S_SUP */ - buf[off] |= 0x2; /* AN_SUP */ - buf[off++] |= 0x1; /* AO_SUP */ + buf[off++] |= tg_pt_gp->tg_pt_gp_alua_supported_states; /* * TARGET PORT GROUP */ @@ -124,73 +241,115 @@ int core_emulate_report_target_port_groups(struct se_cmd *cmd) } spin_unlock(&tg_pt_gp->tg_pt_gp_lock); } - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); /* * Set the RETURN DATA LENGTH set in the header of the DataIN Payload */ - buf[0] = ((rd_len >> 24) & 0xff); - buf[1] = ((rd_len >> 16) & 0xff); - buf[2] = ((rd_len >> 8) & 0xff); - buf[3] = (rd_len & 0xff); + put_unaligned_be32(rd_len, &buf[0]); + + /* + * Fill in the Extended header parameter data format if requested + */ + if (ext_hdr != 0) { + buf[4] = 0x10; + /* + * Set the implicit transition time (in seconds) for the application + * client to use as a base for it's transition timeout value. + * + * Use the current tg_pt_gp_mem -> tg_pt_gp membership from the LUN + * this CDB was received upon to determine this value individually + * for ALUA target port group. + */ + port = cmd->se_lun->lun_sep; + tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; + if (tg_pt_gp_mem) { + spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); + tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; + if (tg_pt_gp) + buf[5] = tg_pt_gp->tg_pt_gp_implicit_trans_secs; + spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); + } + } + transport_kunmap_data_sg(cmd); + target_complete_cmd(cmd, GOOD); return 0; } /* - * SET_TARGET_PORT_GROUPS for explict ALUA operation. + * SET_TARGET_PORT_GROUPS for explicit ALUA operation. * * See spc4r17 section 6.35 */ -int core_emulate_set_target_port_groups(struct se_cmd *cmd) +sense_reason_t +target_emulate_set_target_port_groups(struct se_cmd *cmd) { - struct se_device *dev = SE_DEV(cmd); - struct se_subsystem_dev *su_dev = SE_DEV(cmd)->se_sub_dev; - struct se_port *port, *l_port = SE_LUN(cmd)->lun_sep; - struct se_node_acl *nacl = SE_SESS(cmd)->se_node_acl; + struct se_device *dev = cmd->se_dev; + struct se_port *port, *l_port = cmd->se_lun->lun_sep; + struct se_node_acl *nacl = cmd->se_sess->se_node_acl; struct t10_alua_tg_pt_gp *tg_pt_gp = NULL, *l_tg_pt_gp; struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem, *l_tg_pt_gp_mem; - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; - unsigned char *ptr = &buf[4]; /* Skip over RESERVED area in header */ + unsigned char *buf; + unsigned char *ptr; + sense_reason_t rc = TCM_NO_SENSE; u32 len = 4; /* Skip over RESERVED area in header */ - int alua_access_state, primary = 0, rc; + int alua_access_state, primary = 0, valid_states; u16 tg_pt_id, rtpi; - if (!(l_port)) - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (!l_port) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + if (cmd->data_length < 4) { + pr_warn("SET TARGET PORT GROUPS parameter list length %u too" + " small\n", cmd->data_length); + return TCM_INVALID_PARAMETER_LIST; + } + + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + /* - * Determine if explict ALUA via SET_TARGET_PORT_GROUPS is allowed + * Determine if explicit ALUA via SET_TARGET_PORT_GROUPS is allowed * for the local tg_pt_gp. */ l_tg_pt_gp_mem = l_port->sep_alua_tg_pt_gp_mem; - if (!(l_tg_pt_gp_mem)) { - printk(KERN_ERR "Unable to access l_port->sep_alua_tg_pt_gp_mem\n"); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; + if (!l_tg_pt_gp_mem) { + pr_err("Unable to access l_port->sep_alua_tg_pt_gp_mem\n"); + rc = TCM_UNSUPPORTED_SCSI_OPCODE; + goto out; } spin_lock(&l_tg_pt_gp_mem->tg_pt_gp_mem_lock); l_tg_pt_gp = l_tg_pt_gp_mem->tg_pt_gp; - if (!(l_tg_pt_gp)) { + if (!l_tg_pt_gp) { spin_unlock(&l_tg_pt_gp_mem->tg_pt_gp_mem_lock); - printk(KERN_ERR "Unable to access *l_tg_pt_gp_mem->tg_pt_gp\n"); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; + pr_err("Unable to access *l_tg_pt_gp_mem->tg_pt_gp\n"); + rc = TCM_UNSUPPORTED_SCSI_OPCODE; + goto out; } - rc = (l_tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_EXPLICT_ALUA); spin_unlock(&l_tg_pt_gp_mem->tg_pt_gp_mem_lock); - if (!(rc)) { - printk(KERN_INFO "Unable to process SET_TARGET_PORT_GROUPS" - " while TPGS_EXPLICT_ALUA is disabled\n"); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; + if (!(l_tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_EXPLICIT_ALUA)) { + pr_debug("Unable to process SET_TARGET_PORT_GROUPS" + " while TPGS_EXPLICIT_ALUA is disabled\n"); + rc = TCM_UNSUPPORTED_SCSI_OPCODE; + goto out; } + valid_states = l_tg_pt_gp->tg_pt_gp_alua_supported_states; + + ptr = &buf[4]; /* Skip over RESERVED area in header */ while (len < cmd->data_length) { + bool found = false; alua_access_state = (ptr[0] & 0x0f); /* * Check the received ALUA access state, and determine if * the state is a primary or secondary target port asymmetric * access state. */ - rc = core_alua_check_transition(alua_access_state, &primary); - if (rc != 0) { + rc = core_alua_check_transition(alua_access_state, + valid_states, &primary); + if (rc) { /* * If the SET TARGET PORT GROUPS attempts to establish * an invalid combination of target port asymmetric @@ -201,9 +360,9 @@ int core_emulate_set_target_port_groups(struct se_cmd *cmd) * REQUEST, and the additional sense code set to INVALID * FIELD IN PARAMETER LIST. */ - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + goto out; } - rc = -1; + /* * If the ASYMMETRIC ACCESS STATE field (see table 267) * specifies a primary target port asymmetric access state, @@ -218,52 +377,46 @@ int core_emulate_set_target_port_groups(struct se_cmd *cmd) * changed. */ if (primary) { - tg_pt_id = ((ptr[2] << 8) & 0xff); - tg_pt_id |= (ptr[3] & 0xff); + tg_pt_id = get_unaligned_be16(ptr + 2); /* * Locate the matching target port group ID from * the global tg_pt_gp list */ - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); list_for_each_entry(tg_pt_gp, - &T10_ALUA(su_dev)->tg_pt_gps_list, + &dev->t10_alua.tg_pt_gps_list, tg_pt_gp_list) { - if (!(tg_pt_gp->tg_pt_gp_valid_id)) + if (!tg_pt_gp->tg_pt_gp_valid_id) continue; if (tg_pt_id != tg_pt_gp->tg_pt_gp_id) continue; atomic_inc(&tg_pt_gp->tg_pt_gp_ref_cnt); - smp_mb__after_atomic_inc(); - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + smp_mb__after_atomic(); + + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); - rc = core_alua_do_port_transition(tg_pt_gp, + if (!core_alua_do_port_transition(tg_pt_gp, dev, l_port, nacl, - alua_access_state, 1); + alua_access_state, 1)) + found = true; - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); atomic_dec(&tg_pt_gp->tg_pt_gp_ref_cnt); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); break; } - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); - /* - * If not matching target port group ID can be located - * throw an exception with ASCQ: INVALID_PARAMETER_LIST - */ - if (rc != 0) - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); } else { /* - * Extact the RELATIVE TARGET PORT IDENTIFIER to identify + * Extract the RELATIVE TARGET PORT IDENTIFIER to identify * the Target Port in question for the the incoming * SET_TARGET_PORT_GROUPS op. */ - rtpi = ((ptr[2] << 8) & 0xff); - rtpi |= (ptr[3] & 0xff); + rtpi = get_unaligned_be16(ptr + 2); /* - * Locate the matching relative target port identifer + * Locate the matching relative target port identifier * for the struct se_device storage object. */ spin_lock(&dev->se_port_lock); @@ -273,36 +426,55 @@ int core_emulate_set_target_port_groups(struct se_cmd *cmd) continue; tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; + spin_unlock(&dev->se_port_lock); - rc = core_alua_set_tg_pt_secondary_state( - tg_pt_gp_mem, port, 1, 1); + if (!core_alua_set_tg_pt_secondary_state( + tg_pt_gp_mem, port, 1, 1)) + found = true; spin_lock(&dev->se_port_lock); break; } spin_unlock(&dev->se_port_lock); - /* - * If not matching relative target port identifier can - * be located, throw an exception with ASCQ: - * INVALID_PARAMETER_LIST - */ - if (rc != 0) - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + } + + if (!found) { + rc = TCM_INVALID_PARAMETER_LIST; + goto out; } ptr += 4; len += 4; } - return 0; +out: + transport_kunmap_data_sg(cmd); + if (!rc) + target_complete_cmd(cmd, GOOD); + return rc; } -static inline int core_alua_state_nonoptimized( +static inline void set_ascq(struct se_cmd *cmd, u8 alua_ascq) +{ + /* + * Set SCSI additional sense code (ASC) to 'LUN Not Accessible'; + * The ALUA additional sense code qualifier (ASCQ) is determined + * by the ALUA primary or secondary access state.. + */ + pr_debug("[%s]: ALUA TG Port not available, " + "SenseKey: NOT_READY, ASC/ASCQ: " + "0x04/0x%02x\n", + cmd->se_tfo->get_fabric_name(), alua_ascq); + + cmd->scsi_asc = 0x04; + cmd->scsi_ascq = alua_ascq; +} + +static inline void core_alua_state_nonoptimized( struct se_cmd *cmd, unsigned char *cdb, - int nonop_delay_msecs, - u8 *alua_ascq) + int nonop_delay_msecs) { /* * Set SCF_ALUA_NON_OPTIMIZED here, this value will be checked @@ -311,13 +483,85 @@ static inline int core_alua_state_nonoptimized( */ cmd->se_cmd_flags |= SCF_ALUA_NON_OPTIMIZED; cmd->alua_nonop_delay = nonop_delay_msecs; +} + +static inline int core_alua_state_lba_dependent( + struct se_cmd *cmd, + struct t10_alua_tg_pt_gp *tg_pt_gp) +{ + struct se_device *dev = cmd->se_dev; + u64 segment_size, segment_mult, sectors, lba; + + /* Only need to check for cdb actually containing LBAs */ + if (!(cmd->se_cmd_flags & SCF_SCSI_DATA_CDB)) + return 0; + + spin_lock(&dev->t10_alua.lba_map_lock); + segment_size = dev->t10_alua.lba_map_segment_size; + segment_mult = dev->t10_alua.lba_map_segment_multiplier; + sectors = cmd->data_length / dev->dev_attrib.block_size; + + lba = cmd->t_task_lba; + while (lba < cmd->t_task_lba + sectors) { + struct t10_alua_lba_map *cur_map = NULL, *map; + struct t10_alua_lba_map_member *map_mem; + + list_for_each_entry(map, &dev->t10_alua.lba_map_list, + lba_map_list) { + u64 start_lba, last_lba; + u64 first_lba = map->lba_map_first_lba; + + if (segment_mult) { + u64 tmp = lba; + start_lba = do_div(tmp, segment_size * segment_mult); + + last_lba = first_lba + segment_size - 1; + if (start_lba >= first_lba && + start_lba <= last_lba) { + lba += segment_size; + cur_map = map; + break; + } + } else { + last_lba = map->lba_map_last_lba; + if (lba >= first_lba && lba <= last_lba) { + lba = last_lba + 1; + cur_map = map; + break; + } + } + } + if (!cur_map) { + spin_unlock(&dev->t10_alua.lba_map_lock); + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_UNAVAILABLE); + return 1; + } + list_for_each_entry(map_mem, &cur_map->lba_map_mem_list, + lba_map_mem_list) { + if (map_mem->lba_map_mem_alua_pg_id != + tg_pt_gp->tg_pt_gp_id) + continue; + switch(map_mem->lba_map_mem_alua_state) { + case ALUA_ACCESS_STATE_STANDBY: + spin_unlock(&dev->t10_alua.lba_map_lock); + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_STANDBY); + return 1; + case ALUA_ACCESS_STATE_UNAVAILABLE: + spin_unlock(&dev->t10_alua.lba_map_lock); + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_UNAVAILABLE); + return 1; + default: + break; + } + } + } + spin_unlock(&dev->t10_alua.lba_map_lock); return 0; } static inline int core_alua_state_standby( struct se_cmd *cmd, - unsigned char *cdb, - u8 *alua_ascq) + unsigned char *cdb) { /* * Allowed CDBs for ALUA_ACCESS_STATE_STANDBY as defined by @@ -332,12 +576,22 @@ static inline int core_alua_state_standby( case REPORT_LUNS: case RECEIVE_DIAGNOSTIC: case SEND_DIAGNOSTIC: + case READ_CAPACITY: + return 0; + case SERVICE_ACTION_IN: + switch (cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + return 0; + default: + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_STANDBY); + return 1; + } case MAINTENANCE_IN: - switch (cdb[1]) { + switch (cdb[1] & 0x1f) { case MI_REPORT_TARGET_PGS: return 0; default: - *alua_ascq = ASCQ_04H_ALUA_TG_PT_STANDBY; + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_STANDBY); return 1; } case MAINTENANCE_OUT: @@ -345,7 +599,7 @@ static inline int core_alua_state_standby( case MO_SET_TARGET_PGS: return 0; default: - *alua_ascq = ASCQ_04H_ALUA_TG_PT_STANDBY; + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_STANDBY); return 1; } case REQUEST_SENSE: @@ -355,7 +609,7 @@ static inline int core_alua_state_standby( case WRITE_BUFFER: return 0; default: - *alua_ascq = ASCQ_04H_ALUA_TG_PT_STANDBY; + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_STANDBY); return 1; } @@ -364,8 +618,7 @@ static inline int core_alua_state_standby( static inline int core_alua_state_unavailable( struct se_cmd *cmd, - unsigned char *cdb, - u8 *alua_ascq) + unsigned char *cdb) { /* * Allowed CDBs for ALUA_ACCESS_STATE_UNAVAILABLE as defined by @@ -374,12 +627,13 @@ static inline int core_alua_state_unavailable( switch (cdb[0]) { case INQUIRY: case REPORT_LUNS: + return 0; case MAINTENANCE_IN: - switch (cdb[1]) { + switch (cdb[1] & 0x1f) { case MI_REPORT_TARGET_PGS: return 0; default: - *alua_ascq = ASCQ_04H_ALUA_TG_PT_UNAVAILABLE; + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_UNAVAILABLE); return 1; } case MAINTENANCE_OUT: @@ -387,7 +641,7 @@ static inline int core_alua_state_unavailable( case MO_SET_TARGET_PGS: return 0; default: - *alua_ascq = ASCQ_04H_ALUA_TG_PT_UNAVAILABLE; + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_UNAVAILABLE); return 1; } case REQUEST_SENSE: @@ -395,7 +649,7 @@ static inline int core_alua_state_unavailable( case WRITE_BUFFER: return 0; default: - *alua_ascq = ASCQ_04H_ALUA_TG_PT_UNAVAILABLE; + set_ascq(cmd, ASCQ_04H_ALUA_TG_PT_UNAVAILABLE); return 1; } @@ -404,22 +658,22 @@ static inline int core_alua_state_unavailable( static inline int core_alua_state_transition( struct se_cmd *cmd, - unsigned char *cdb, - u8 *alua_ascq) + unsigned char *cdb) { /* - * Allowed CDBs for ALUA_ACCESS_STATE_TRANSITIO as defined by + * Allowed CDBs for ALUA_ACCESS_STATE_TRANSITION as defined by * spc4r17 section 5.9.2.5 */ switch (cdb[0]) { case INQUIRY: case REPORT_LUNS: + return 0; case MAINTENANCE_IN: - switch (cdb[1]) { + switch (cdb[1] & 0x1f) { case MI_REPORT_TARGET_PGS: return 0; default: - *alua_ascq = ASCQ_04H_ALUA_STATE_TRANSITION; + set_ascq(cmd, ASCQ_04H_ALUA_STATE_TRANSITION); return 1; } case REQUEST_SENSE: @@ -427,7 +681,7 @@ static inline int core_alua_state_transition( case WRITE_BUFFER: return 0; default: - *alua_ascq = ASCQ_04H_ALUA_STATE_TRANSITION; + set_ascq(cmd, ASCQ_04H_ALUA_STATE_TRANSITION); return 1; } @@ -435,53 +689,37 @@ static inline int core_alua_state_transition( } /* - * Used for alua_type SPC_ALUA_PASSTHROUGH and SPC2_ALUA_DISABLED - * in transport_cmd_sequencer(). This function is assigned to - * struct t10_alua *->state_check() in core_setup_alua() - */ -static int core_alua_state_check_nop( - struct se_cmd *cmd, - unsigned char *cdb, - u8 *alua_ascq) -{ - return 0; -} - -/* - * Used for alua_type SPC3_ALUA_EMULATED in transport_cmd_sequencer(). - * This function is assigned to struct t10_alua *->state_check() in - * core_setup_alua() - * - * Also, this function can return three different return codes to - * signal transport_generic_cmd_sequencer() - * - * return 1: Is used to signal LUN not accecsable, and check condition/not ready + * return 1: Is used to signal LUN not accessible, and check condition/not ready * return 0: Used to signal success - * reutrn -1: Used to signal failure, and invalid cdb field + * return -1: Used to signal failure, and invalid cdb field */ -static int core_alua_state_check( - struct se_cmd *cmd, - unsigned char *cdb, - u8 *alua_ascq) +sense_reason_t +target_alua_state_check(struct se_cmd *cmd) { - struct se_lun *lun = SE_LUN(cmd); + struct se_device *dev = cmd->se_dev; + unsigned char *cdb = cmd->t_task_cdb; + struct se_lun *lun = cmd->se_lun; struct se_port *port = lun->lun_sep; struct t10_alua_tg_pt_gp *tg_pt_gp; struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; int out_alua_state, nonop_delay_msecs; - if (!(port)) + if (dev->se_hba->hba_flags & HBA_FLAGS_INTERNAL_USE) + return 0; + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) + return 0; + + if (!port) return 0; /* * First, check for a struct se_port specific secondary ALUA target port * access state: OFFLINE */ if (atomic_read(&port->sep_tg_pt_secondary_offline)) { - *alua_ascq = ASCQ_04H_ALUA_OFFLINE; - printk(KERN_INFO "ALUA: Got secondary offline status for local" + pr_debug("ALUA: Got secondary offline status for local" " target port\n"); - *alua_ascq = ASCQ_04H_ALUA_OFFLINE; - return 1; + set_ascq(cmd, ASCQ_04H_ALUA_OFFLINE); + return TCM_CHECK_CONDITION_NOT_READY; } /* * Second, obtain the struct t10_alua_tg_pt_gp_member pointer to the @@ -490,58 +728,91 @@ static int core_alua_state_check( * a ALUA logical unit group. */ tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; + if (!tg_pt_gp_mem) + return 0; + spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; out_alua_state = atomic_read(&tg_pt_gp->tg_pt_gp_alua_access_state); nonop_delay_msecs = tg_pt_gp->tg_pt_gp_nonop_delay_msecs; spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); /* - * Process ALUA_ACCESS_STATE_ACTIVE_OPTMIZED in a seperate conditional - * statement so the complier knows explictly to check this case first. + * Process ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED in a separate conditional + * statement so the compiler knows explicitly to check this case first. * For the Optimized ALUA access state case, we want to process the * incoming fabric cmd ASAP.. */ - if (out_alua_state == ALUA_ACCESS_STATE_ACTIVE_OPTMIZED) + if (out_alua_state == ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED) return 0; switch (out_alua_state) { case ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED: - return core_alua_state_nonoptimized(cmd, cdb, - nonop_delay_msecs, alua_ascq); + core_alua_state_nonoptimized(cmd, cdb, nonop_delay_msecs); + break; case ALUA_ACCESS_STATE_STANDBY: - return core_alua_state_standby(cmd, cdb, alua_ascq); + if (core_alua_state_standby(cmd, cdb)) + return TCM_CHECK_CONDITION_NOT_READY; + break; case ALUA_ACCESS_STATE_UNAVAILABLE: - return core_alua_state_unavailable(cmd, cdb, alua_ascq); + if (core_alua_state_unavailable(cmd, cdb)) + return TCM_CHECK_CONDITION_NOT_READY; + break; case ALUA_ACCESS_STATE_TRANSITION: - return core_alua_state_transition(cmd, cdb, alua_ascq); + if (core_alua_state_transition(cmd, cdb)) + return TCM_CHECK_CONDITION_NOT_READY; + break; + case ALUA_ACCESS_STATE_LBA_DEPENDENT: + if (core_alua_state_lba_dependent(cmd, tg_pt_gp)) + return TCM_CHECK_CONDITION_NOT_READY; + break; /* * OFFLINE is a secondary ALUA target port group access state, that is * handled above with struct se_port->sep_tg_pt_secondary_offline=1 */ case ALUA_ACCESS_STATE_OFFLINE: default: - printk(KERN_ERR "Unknown ALUA access state: 0x%02x\n", + pr_err("Unknown ALUA access state: 0x%02x\n", out_alua_state); - return -1; + return TCM_INVALID_CDB_FIELD; } return 0; } /* - * Check implict and explict ALUA state change request. + * Check implicit and explicit ALUA state change request. */ -static int core_alua_check_transition(int state, int *primary) +static sense_reason_t +core_alua_check_transition(int state, int valid, int *primary) { + /* + * OPTIMIZED, NON-OPTIMIZED, STANDBY and UNAVAILABLE are + * defined as primary target port asymmetric access states. + */ switch (state) { - case ALUA_ACCESS_STATE_ACTIVE_OPTMIZED: + case ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED: + if (!(valid & ALUA_AO_SUP)) + goto not_supported; + *primary = 1; + break; case ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED: + if (!(valid & ALUA_AN_SUP)) + goto not_supported; + *primary = 1; + break; case ALUA_ACCESS_STATE_STANDBY: + if (!(valid & ALUA_S_SUP)) + goto not_supported; + *primary = 1; + break; case ALUA_ACCESS_STATE_UNAVAILABLE: - /* - * OPTIMIZED, NON-OPTIMIZED, STANDBY and UNAVAILABLE are - * defined as primary target port asymmetric access states. - */ + if (!(valid & ALUA_U_SUP)) + goto not_supported; + *primary = 1; + break; + case ALUA_ACCESS_STATE_LBA_DEPENDENT: + if (!(valid & ALUA_LBD_SUP)) + goto not_supported; *primary = 1; break; case ALUA_ACCESS_STATE_OFFLINE: @@ -549,29 +820,46 @@ static int core_alua_check_transition(int state, int *primary) * OFFLINE state is defined as a secondary target port * asymmetric access state. */ + if (!(valid & ALUA_O_SUP)) + goto not_supported; *primary = 0; break; + case ALUA_ACCESS_STATE_TRANSITION: + /* + * Transitioning is set internally, and + * cannot be selected manually. + */ + goto not_supported; default: - printk(KERN_ERR "Unknown ALUA access state: 0x%02x\n", state); - return -1; + pr_err("Unknown ALUA access state: 0x%02x\n", state); + return TCM_INVALID_PARAMETER_LIST; } return 0; + +not_supported: + pr_err("ALUA access state %s not supported", + core_alua_dump_state(state)); + return TCM_INVALID_PARAMETER_LIST; } static char *core_alua_dump_state(int state) { switch (state) { - case ALUA_ACCESS_STATE_ACTIVE_OPTMIZED: + case ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED: return "Active/Optimized"; case ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED: return "Active/NonOptimized"; + case ALUA_ACCESS_STATE_LBA_DEPENDENT: + return "LBA Dependent"; case ALUA_ACCESS_STATE_STANDBY: return "Standby"; case ALUA_ACCESS_STATE_UNAVAILABLE: return "Unavailable"; case ALUA_ACCESS_STATE_OFFLINE: return "Offline"; + case ALUA_ACCESS_STATE_TRANSITION: + return "Transitioning"; default: return "Unknown"; } @@ -584,10 +872,10 @@ char *core_alua_dump_status(int status) switch (status) { case ALUA_STATUS_NONE: return "None"; - case ALUA_STATUS_ALTERED_BY_EXPLICT_STPG: - return "Altered by Explict STPG"; - case ALUA_STATUS_ALTERED_BY_IMPLICT_ALUA: - return "Altered by Implict ALUA"; + case ALUA_STATUS_ALTERED_BY_EXPLICIT_STPG: + return "Altered by Explicit STPG"; + case ALUA_STATUS_ALTERED_BY_IMPLICIT_ALUA: + return "Altered by Implicit ALUA"; default: return "Unknown"; } @@ -610,7 +898,7 @@ int core_alua_check_nonop_delay( * The ALUA Active/NonOptimized access state delay can be disabled * in via configfs with a value of zero */ - if (!(cmd->alua_nonop_delay)) + if (!cmd->alua_nonop_delay) return 0; /* * struct se_cmd->alua_nonop_delay gets set by a target port group @@ -630,95 +918,67 @@ static int core_alua_write_tpg_metadata( unsigned char *md_buf, u32 md_buf_len) { - mm_segment_t old_fs; - struct file *file; - struct iovec iov[1]; - int flags = O_RDWR | O_CREAT | O_TRUNC, ret; - - memset(iov, 0, sizeof(struct iovec)); + struct file *file = filp_open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); + int ret; - file = filp_open(path, flags, 0600); - if (IS_ERR(file) || !file || !file->f_dentry) { - printk(KERN_ERR "filp_open(%s) for ALUA metadata failed\n", - path); + if (IS_ERR(file)) { + pr_err("filp_open(%s) for ALUA metadata failed\n", path); return -ENODEV; } - - iov[0].iov_base = &md_buf[0]; - iov[0].iov_len = md_buf_len; - - old_fs = get_fs(); - set_fs(get_ds()); - ret = vfs_writev(file, &iov[0], 1, &file->f_pos); - set_fs(old_fs); - - if (ret < 0) { - printk(KERN_ERR "Error writing ALUA metadata file: %s\n", path); - filp_close(file, NULL); - return -EIO; - } - filp_close(file, NULL); - - return 0; + ret = kernel_write(file, md_buf, md_buf_len, 0); + if (ret < 0) + pr_err("Error writing ALUA metadata file: %s\n", path); + fput(file); + return (ret < 0) ? -EIO : 0; } /* * Called with tg_pt_gp->tg_pt_gp_md_mutex held */ static int core_alua_update_tpg_primary_metadata( - struct t10_alua_tg_pt_gp *tg_pt_gp, - int primary_state, - unsigned char *md_buf) + struct t10_alua_tg_pt_gp *tg_pt_gp) { - struct se_subsystem_dev *su_dev = tg_pt_gp->tg_pt_gp_su_dev; - struct t10_wwn *wwn = &su_dev->t10_wwn; + unsigned char *md_buf; + struct t10_wwn *wwn = &tg_pt_gp->tg_pt_gp_dev->t10_wwn; char path[ALUA_METADATA_PATH_LEN]; - int len; + int len, rc; + + md_buf = kzalloc(ALUA_MD_BUF_LEN, GFP_KERNEL); + if (!md_buf) { + pr_err("Unable to allocate buf for ALUA metadata\n"); + return -ENOMEM; + } memset(path, 0, ALUA_METADATA_PATH_LEN); - len = snprintf(md_buf, tg_pt_gp->tg_pt_gp_md_buf_len, + len = snprintf(md_buf, ALUA_MD_BUF_LEN, "tg_pt_gp_id=%hu\n" "alua_access_state=0x%02x\n" "alua_access_status=0x%02x\n", - tg_pt_gp->tg_pt_gp_id, primary_state, + tg_pt_gp->tg_pt_gp_id, + tg_pt_gp->tg_pt_gp_alua_pending_state, tg_pt_gp->tg_pt_gp_alua_access_status); snprintf(path, ALUA_METADATA_PATH_LEN, "/var/target/alua/tpgs_%s/%s", &wwn->unit_serial[0], config_item_name(&tg_pt_gp->tg_pt_gp_group.cg_item)); - return core_alua_write_tpg_metadata(path, md_buf, len); + rc = core_alua_write_tpg_metadata(path, md_buf, len); + kfree(md_buf); + return rc; } -static int core_alua_do_transition_tg_pt( - struct t10_alua_tg_pt_gp *tg_pt_gp, - struct se_port *l_port, - struct se_node_acl *nacl, - unsigned char *md_buf, - int new_state, - int explict) +static void core_alua_do_transition_tg_pt_work(struct work_struct *work) { + struct t10_alua_tg_pt_gp *tg_pt_gp = container_of(work, + struct t10_alua_tg_pt_gp, tg_pt_gp_transition_work.work); + struct se_device *dev = tg_pt_gp->tg_pt_gp_dev; struct se_dev_entry *se_deve; struct se_lun_acl *lacl; struct se_port *port; struct t10_alua_tg_pt_gp_member *mem; - int old_state = 0; - /* - * Save the old primary ALUA access state, and set the current state - * to ALUA_ACCESS_STATE_TRANSITION. - */ - old_state = atomic_read(&tg_pt_gp->tg_pt_gp_alua_access_state); - atomic_set(&tg_pt_gp->tg_pt_gp_alua_access_state, - ALUA_ACCESS_STATE_TRANSITION); - tg_pt_gp->tg_pt_gp_alua_access_status = (explict) ? - ALUA_STATUS_ALTERED_BY_EXPLICT_STPG : - ALUA_STATUS_ALTERED_BY_IMPLICT_ALUA; - /* - * Check for the optional ALUA primary state transition delay - */ - if (tg_pt_gp->tg_pt_gp_trans_delay_msecs != 0) - msleep_interruptible(tg_pt_gp->tg_pt_gp_trans_delay_msecs); + bool explicit = (tg_pt_gp->tg_pt_gp_alua_access_status == + ALUA_STATUS_ALTERED_BY_EXPLICIT_STPG); spin_lock(&tg_pt_gp->tg_pt_gp_lock); list_for_each_entry(mem, &tg_pt_gp->tg_pt_gp_mem_list, @@ -729,7 +989,7 @@ static int core_alua_do_transition_tg_pt( * change, a device server shall establish a unit attention * condition for the initiator port associated with every I_T * nexus with the additional sense code set to ASYMMETRIC - * ACCESS STATE CHAGED. + * ACCESS STATE CHANGED. * * After an explicit target port asymmetric access state * change, a device server shall establish a unit attention @@ -739,7 +999,7 @@ static int core_alua_do_transition_tg_pt( * TARGET PORT GROUPS command */ atomic_inc(&mem->tg_pt_gp_mem_ref_cnt); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&tg_pt_gp->tg_pt_gp_lock); spin_lock_bh(&port->sep_alua_lock); @@ -748,14 +1008,17 @@ static int core_alua_do_transition_tg_pt( lacl = se_deve->se_lun_acl; /* * se_deve->se_lun_acl pointer may be NULL for a - * entry created without explict Node+MappedLUN ACLs + * entry created without explicit Node+MappedLUN ACLs */ - if (!(lacl)) + if (!lacl) continue; - if (explict && - (nacl != NULL) && (nacl == lacl->se_lun_nacl) && - (l_port != NULL) && (l_port == port)) + if ((tg_pt_gp->tg_pt_gp_alua_access_status == + ALUA_STATUS_ALTERED_BY_EXPLICIT_STPG) && + (tg_pt_gp->tg_pt_gp_alua_nacl != NULL) && + (tg_pt_gp->tg_pt_gp_alua_nacl == lacl->se_lun_nacl) && + (tg_pt_gp->tg_pt_gp_alua_port != NULL) && + (tg_pt_gp->tg_pt_gp_alua_port == port)) continue; core_scsi3_ua_allocate(lacl->se_lun_nacl, @@ -766,7 +1029,7 @@ static int core_alua_do_transition_tg_pt( spin_lock(&tg_pt_gp->tg_pt_gp_lock); atomic_dec(&mem->tg_pt_gp_mem_ref_cnt); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } spin_unlock(&tg_pt_gp->tg_pt_gp_lock); /* @@ -783,20 +1046,102 @@ static int core_alua_do_transition_tg_pt( */ if (tg_pt_gp->tg_pt_gp_write_metadata) { mutex_lock(&tg_pt_gp->tg_pt_gp_md_mutex); - core_alua_update_tpg_primary_metadata(tg_pt_gp, - new_state, md_buf); + core_alua_update_tpg_primary_metadata(tg_pt_gp); mutex_unlock(&tg_pt_gp->tg_pt_gp_md_mutex); } /* * Set the current primary ALUA access state to the requested new state */ - atomic_set(&tg_pt_gp->tg_pt_gp_alua_access_state, new_state); + atomic_set(&tg_pt_gp->tg_pt_gp_alua_access_state, + tg_pt_gp->tg_pt_gp_alua_pending_state); + + pr_debug("Successful %s ALUA transition TG PT Group: %s ID: %hu" + " from primary access state %s to %s\n", (explicit) ? "explicit" : + "implicit", config_item_name(&tg_pt_gp->tg_pt_gp_group.cg_item), + tg_pt_gp->tg_pt_gp_id, + core_alua_dump_state(tg_pt_gp->tg_pt_gp_alua_previous_state), + core_alua_dump_state(tg_pt_gp->tg_pt_gp_alua_pending_state)); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); + atomic_dec(&tg_pt_gp->tg_pt_gp_ref_cnt); + smp_mb__after_atomic(); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); + + if (tg_pt_gp->tg_pt_gp_transition_complete) + complete(tg_pt_gp->tg_pt_gp_transition_complete); +} + +static int core_alua_do_transition_tg_pt( + struct t10_alua_tg_pt_gp *tg_pt_gp, + int new_state, + int explicit) +{ + struct se_device *dev = tg_pt_gp->tg_pt_gp_dev; + DECLARE_COMPLETION_ONSTACK(wait); + + /* Nothing to be done here */ + if (atomic_read(&tg_pt_gp->tg_pt_gp_alua_access_state) == new_state) + return 0; + + if (new_state == ALUA_ACCESS_STATE_TRANSITION) + return -EAGAIN; + + /* + * Flush any pending transitions + */ + if (!explicit && tg_pt_gp->tg_pt_gp_implicit_trans_secs && + atomic_read(&tg_pt_gp->tg_pt_gp_alua_access_state) == + ALUA_ACCESS_STATE_TRANSITION) { + /* Just in case */ + tg_pt_gp->tg_pt_gp_alua_pending_state = new_state; + tg_pt_gp->tg_pt_gp_transition_complete = &wait; + flush_delayed_work(&tg_pt_gp->tg_pt_gp_transition_work); + wait_for_completion(&wait); + tg_pt_gp->tg_pt_gp_transition_complete = NULL; + return 0; + } + + /* + * Save the old primary ALUA access state, and set the current state + * to ALUA_ACCESS_STATE_TRANSITION. + */ + tg_pt_gp->tg_pt_gp_alua_previous_state = + atomic_read(&tg_pt_gp->tg_pt_gp_alua_access_state); + tg_pt_gp->tg_pt_gp_alua_pending_state = new_state; + + atomic_set(&tg_pt_gp->tg_pt_gp_alua_access_state, + ALUA_ACCESS_STATE_TRANSITION); + tg_pt_gp->tg_pt_gp_alua_access_status = (explicit) ? + ALUA_STATUS_ALTERED_BY_EXPLICIT_STPG : + ALUA_STATUS_ALTERED_BY_IMPLICIT_ALUA; + + /* + * Check for the optional ALUA primary state transition delay + */ + if (tg_pt_gp->tg_pt_gp_trans_delay_msecs != 0) + msleep_interruptible(tg_pt_gp->tg_pt_gp_trans_delay_msecs); - printk(KERN_INFO "Successful %s ALUA transition TG PT Group: %s ID: %hu" - " from primary access state %s to %s\n", (explict) ? "explict" : - "implict", config_item_name(&tg_pt_gp->tg_pt_gp_group.cg_item), - tg_pt_gp->tg_pt_gp_id, core_alua_dump_state(old_state), - core_alua_dump_state(new_state)); + /* + * Take a reference for workqueue item + */ + spin_lock(&dev->t10_alua.tg_pt_gps_lock); + atomic_inc(&tg_pt_gp->tg_pt_gp_ref_cnt); + smp_mb__after_atomic(); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); + + if (!explicit && tg_pt_gp->tg_pt_gp_implicit_trans_secs) { + unsigned long transition_tmo; + + transition_tmo = tg_pt_gp->tg_pt_gp_implicit_trans_secs * HZ; + queue_delayed_work(tg_pt_gp->tg_pt_gp_dev->tmr_wq, + &tg_pt_gp->tg_pt_gp_transition_work, + transition_tmo); + } else { + tg_pt_gp->tg_pt_gp_transition_complete = &wait; + queue_delayed_work(tg_pt_gp->tg_pt_gp_dev->tmr_wq, + &tg_pt_gp->tg_pt_gp_transition_work, 0); + wait_for_completion(&wait); + tg_pt_gp->tg_pt_gp_transition_complete = NULL; + } return 0; } @@ -807,49 +1152,41 @@ int core_alua_do_port_transition( struct se_port *l_port, struct se_node_acl *l_nacl, int new_state, - int explict) + int explicit) { struct se_device *dev; - struct se_port *port; - struct se_subsystem_dev *su_dev; - struct se_node_acl *nacl; struct t10_alua_lu_gp *lu_gp; struct t10_alua_lu_gp_member *lu_gp_mem, *local_lu_gp_mem; struct t10_alua_tg_pt_gp *tg_pt_gp; - unsigned char *md_buf; - int primary; + int primary, valid_states, rc = 0; - if (core_alua_check_transition(new_state, &primary) != 0) + valid_states = l_tg_pt_gp->tg_pt_gp_alua_supported_states; + if (core_alua_check_transition(new_state, valid_states, &primary) != 0) return -EINVAL; - md_buf = kzalloc(l_tg_pt_gp->tg_pt_gp_md_buf_len, GFP_KERNEL); - if (!(md_buf)) { - printk("Unable to allocate buf for ALUA metadata\n"); - return -ENOMEM; - } - local_lu_gp_mem = l_dev->dev_alua_lu_gp_mem; spin_lock(&local_lu_gp_mem->lu_gp_mem_lock); lu_gp = local_lu_gp_mem->lu_gp; atomic_inc(&lu_gp->lu_gp_ref_cnt); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&local_lu_gp_mem->lu_gp_mem_lock); /* * For storage objects that are members of the 'default_lu_gp', * we only do transition on the passed *l_tp_pt_gp, and not * on all of the matching target port groups IDs in default_lu_gp. */ - if (!(lu_gp->lu_gp_id)) { + if (!lu_gp->lu_gp_id) { /* * core_alua_do_transition_tg_pt() will always return * success. */ - core_alua_do_transition_tg_pt(l_tg_pt_gp, l_port, l_nacl, - md_buf, new_state, explict); + l_tg_pt_gp->tg_pt_gp_alua_port = l_port; + l_tg_pt_gp->tg_pt_gp_alua_nacl = l_nacl; + rc = core_alua_do_transition_tg_pt(l_tg_pt_gp, + new_state, explicit); atomic_dec(&lu_gp->lu_gp_ref_cnt); - smp_mb__after_atomic_dec(); - kfree(md_buf); - return 0; + smp_mb__after_atomic(); + return rc; } /* * For all other LU groups aside from 'default_lu_gp', walk all of @@ -861,21 +1198,20 @@ int core_alua_do_port_transition( lu_gp_mem_list) { dev = lu_gp_mem->lu_gp_mem_dev; - su_dev = dev->se_sub_dev; atomic_inc(&lu_gp_mem->lu_gp_mem_ref_cnt); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&lu_gp->lu_gp_lock); - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); list_for_each_entry(tg_pt_gp, - &T10_ALUA(su_dev)->tg_pt_gps_list, + &dev->t10_alua.tg_pt_gps_list, tg_pt_gp_list) { - if (!(tg_pt_gp->tg_pt_gp_valid_id)) + if (!tg_pt_gp->tg_pt_gp_valid_id) continue; /* * If the target behavior port asymmetric access state - * is changed for any target port group accessiable via + * is changed for any target port group accessible via * a logical unit within a LU group, the target port * behavior group asymmetric access states for the same * target port group accessible via other logical units @@ -885,44 +1221,48 @@ int core_alua_do_port_transition( continue; if (l_tg_pt_gp == tg_pt_gp) { - port = l_port; - nacl = l_nacl; + tg_pt_gp->tg_pt_gp_alua_port = l_port; + tg_pt_gp->tg_pt_gp_alua_nacl = l_nacl; } else { - port = NULL; - nacl = NULL; + tg_pt_gp->tg_pt_gp_alua_port = NULL; + tg_pt_gp->tg_pt_gp_alua_nacl = NULL; } atomic_inc(&tg_pt_gp->tg_pt_gp_ref_cnt); - smp_mb__after_atomic_inc(); - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + smp_mb__after_atomic(); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); /* * core_alua_do_transition_tg_pt() will always return * success. */ - core_alua_do_transition_tg_pt(tg_pt_gp, port, - nacl, md_buf, new_state, explict); + rc = core_alua_do_transition_tg_pt(tg_pt_gp, + new_state, explicit); - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); atomic_dec(&tg_pt_gp->tg_pt_gp_ref_cnt); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); + if (rc) + break; } - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); spin_lock(&lu_gp->lu_gp_lock); atomic_dec(&lu_gp_mem->lu_gp_mem_ref_cnt); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } spin_unlock(&lu_gp->lu_gp_lock); - printk(KERN_INFO "Successfully processed LU Group: %s all ALUA TG PT" - " Group IDs: %hu %s transition to primary state: %s\n", - config_item_name(&lu_gp->lu_gp_group.cg_item), - l_tg_pt_gp->tg_pt_gp_id, (explict) ? "explict" : "implict", - core_alua_dump_state(new_state)); + if (!rc) { + pr_debug("Successfully processed LU Group: %s all ALUA TG PT" + " Group IDs: %hu %s transition to primary state: %s\n", + config_item_name(&lu_gp->lu_gp_group.cg_item), + l_tg_pt_gp->tg_pt_gp_id, + (explicit) ? "explicit" : "implicit", + core_alua_dump_state(new_state)); + } atomic_dec(&lu_gp->lu_gp_ref_cnt); - smp_mb__after_atomic_dec(); - kfree(md_buf); - return 0; + smp_mb__after_atomic(); + return rc; } /* @@ -930,54 +1270,60 @@ int core_alua_do_port_transition( */ static int core_alua_update_tpg_secondary_metadata( struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem, - struct se_port *port, - unsigned char *md_buf, - u32 md_buf_len) + struct se_port *port) { + unsigned char *md_buf; struct se_portal_group *se_tpg = port->sep_tpg; char path[ALUA_METADATA_PATH_LEN], wwn[ALUA_SECONDARY_METADATA_WWN_LEN]; - int len; + int len, rc; + + md_buf = kzalloc(ALUA_MD_BUF_LEN, GFP_KERNEL); + if (!md_buf) { + pr_err("Unable to allocate buf for ALUA metadata\n"); + return -ENOMEM; + } memset(path, 0, ALUA_METADATA_PATH_LEN); memset(wwn, 0, ALUA_SECONDARY_METADATA_WWN_LEN); len = snprintf(wwn, ALUA_SECONDARY_METADATA_WWN_LEN, "%s", - TPG_TFO(se_tpg)->tpg_get_wwn(se_tpg)); + se_tpg->se_tpg_tfo->tpg_get_wwn(se_tpg)); - if (TPG_TFO(se_tpg)->tpg_get_tag != NULL) + if (se_tpg->se_tpg_tfo->tpg_get_tag != NULL) snprintf(wwn+len, ALUA_SECONDARY_METADATA_WWN_LEN-len, "+%hu", - TPG_TFO(se_tpg)->tpg_get_tag(se_tpg)); + se_tpg->se_tpg_tfo->tpg_get_tag(se_tpg)); - len = snprintf(md_buf, md_buf_len, "alua_tg_pt_offline=%d\n" + len = snprintf(md_buf, ALUA_MD_BUF_LEN, "alua_tg_pt_offline=%d\n" "alua_tg_pt_status=0x%02x\n", atomic_read(&port->sep_tg_pt_secondary_offline), port->sep_tg_pt_secondary_stat); snprintf(path, ALUA_METADATA_PATH_LEN, "/var/target/alua/%s/%s/lun_%u", - TPG_TFO(se_tpg)->get_fabric_name(), wwn, + se_tpg->se_tpg_tfo->get_fabric_name(), wwn, port->sep_lun->unpacked_lun); - return core_alua_write_tpg_metadata(path, md_buf, len); + rc = core_alua_write_tpg_metadata(path, md_buf, len); + kfree(md_buf); + + return rc; } static int core_alua_set_tg_pt_secondary_state( struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem, struct se_port *port, - int explict, + int explicit, int offline) { struct t10_alua_tg_pt_gp *tg_pt_gp; - unsigned char *md_buf; - u32 md_buf_len; int trans_delay_msecs; spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; - if (!(tg_pt_gp)) { + if (!tg_pt_gp) { spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); - printk(KERN_ERR "Unable to complete secondary state" + pr_err("Unable to complete secondary state" " transition\n"); - return -1; + return -EINVAL; } trans_delay_msecs = tg_pt_gp->tg_pt_gp_trans_delay_msecs; /* @@ -989,14 +1335,13 @@ static int core_alua_set_tg_pt_secondary_state( else atomic_set(&port->sep_tg_pt_secondary_offline, 0); - md_buf_len = tg_pt_gp->tg_pt_gp_md_buf_len; - port->sep_tg_pt_secondary_stat = (explict) ? - ALUA_STATUS_ALTERED_BY_EXPLICT_STPG : - ALUA_STATUS_ALTERED_BY_IMPLICT_ALUA; + port->sep_tg_pt_secondary_stat = (explicit) ? + ALUA_STATUS_ALTERED_BY_EXPLICIT_STPG : + ALUA_STATUS_ALTERED_BY_IMPLICIT_ALUA; - printk(KERN_INFO "Successful %s ALUA transition TG PT Group: %s ID: %hu" - " to secondary access state: %s\n", (explict) ? "explict" : - "implict", config_item_name(&tg_pt_gp->tg_pt_gp_group.cg_item), + pr_debug("Successful %s ALUA transition TG PT Group: %s ID: %hu" + " to secondary access state: %s\n", (explicit) ? "explicit" : + "implicit", config_item_name(&tg_pt_gp->tg_pt_gp_group.cg_item), tg_pt_gp->tg_pt_gp_id, (offline) ? "OFFLINE" : "ONLINE"); spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); @@ -1011,42 +1356,134 @@ static int core_alua_set_tg_pt_secondary_state( * secondary state and status */ if (port->sep_tg_pt_secondary_write_md) { - md_buf = kzalloc(md_buf_len, GFP_KERNEL); - if (!(md_buf)) { - printk(KERN_ERR "Unable to allocate md_buf for" - " secondary ALUA access metadata\n"); - return -1; - } mutex_lock(&port->sep_tg_pt_md_mutex); - core_alua_update_tpg_secondary_metadata(tg_pt_gp_mem, port, - md_buf, md_buf_len); + core_alua_update_tpg_secondary_metadata(tg_pt_gp_mem, port); mutex_unlock(&port->sep_tg_pt_md_mutex); + } - kfree(md_buf); + return 0; +} + +struct t10_alua_lba_map * +core_alua_allocate_lba_map(struct list_head *list, + u64 first_lba, u64 last_lba) +{ + struct t10_alua_lba_map *lba_map; + + lba_map = kmem_cache_zalloc(t10_alua_lba_map_cache, GFP_KERNEL); + if (!lba_map) { + pr_err("Unable to allocate struct t10_alua_lba_map\n"); + return ERR_PTR(-ENOMEM); } + INIT_LIST_HEAD(&lba_map->lba_map_mem_list); + lba_map->lba_map_first_lba = first_lba; + lba_map->lba_map_last_lba = last_lba; + + list_add_tail(&lba_map->lba_map_list, list); + return lba_map; +} + +int +core_alua_allocate_lba_map_mem(struct t10_alua_lba_map *lba_map, + int pg_id, int state) +{ + struct t10_alua_lba_map_member *lba_map_mem; + list_for_each_entry(lba_map_mem, &lba_map->lba_map_mem_list, + lba_map_mem_list) { + if (lba_map_mem->lba_map_mem_alua_pg_id == pg_id) { + pr_err("Duplicate pg_id %d in lba_map\n", pg_id); + return -EINVAL; + } + } + + lba_map_mem = kmem_cache_zalloc(t10_alua_lba_map_mem_cache, GFP_KERNEL); + if (!lba_map_mem) { + pr_err("Unable to allocate struct t10_alua_lba_map_mem\n"); + return -ENOMEM; + } + lba_map_mem->lba_map_mem_alua_state = state; + lba_map_mem->lba_map_mem_alua_pg_id = pg_id; + + list_add_tail(&lba_map_mem->lba_map_mem_list, + &lba_map->lba_map_mem_list); return 0; } +void +core_alua_free_lba_map(struct list_head *lba_list) +{ + struct t10_alua_lba_map *lba_map, *lba_map_tmp; + struct t10_alua_lba_map_member *lba_map_mem, *lba_map_mem_tmp; + + list_for_each_entry_safe(lba_map, lba_map_tmp, lba_list, + lba_map_list) { + list_for_each_entry_safe(lba_map_mem, lba_map_mem_tmp, + &lba_map->lba_map_mem_list, + lba_map_mem_list) { + list_del(&lba_map_mem->lba_map_mem_list); + kmem_cache_free(t10_alua_lba_map_mem_cache, + lba_map_mem); + } + list_del(&lba_map->lba_map_list); + kmem_cache_free(t10_alua_lba_map_cache, lba_map); + } +} + +void +core_alua_set_lba_map(struct se_device *dev, struct list_head *lba_map_list, + int segment_size, int segment_mult) +{ + struct list_head old_lba_map_list; + struct t10_alua_tg_pt_gp *tg_pt_gp; + int activate = 0, supported; + + INIT_LIST_HEAD(&old_lba_map_list); + spin_lock(&dev->t10_alua.lba_map_lock); + dev->t10_alua.lba_map_segment_size = segment_size; + dev->t10_alua.lba_map_segment_multiplier = segment_mult; + list_splice_init(&dev->t10_alua.lba_map_list, &old_lba_map_list); + if (lba_map_list) { + list_splice_init(lba_map_list, &dev->t10_alua.lba_map_list); + activate = 1; + } + spin_unlock(&dev->t10_alua.lba_map_lock); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); + list_for_each_entry(tg_pt_gp, &dev->t10_alua.tg_pt_gps_list, + tg_pt_gp_list) { + + if (!tg_pt_gp->tg_pt_gp_valid_id) + continue; + supported = tg_pt_gp->tg_pt_gp_alua_supported_states; + if (activate) + supported |= ALUA_LBD_SUP; + else + supported &= ~ALUA_LBD_SUP; + tg_pt_gp->tg_pt_gp_alua_supported_states = supported; + } + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); + core_alua_free_lba_map(&old_lba_map_list); +} + struct t10_alua_lu_gp * core_alua_allocate_lu_gp(const char *name, int def_group) { struct t10_alua_lu_gp *lu_gp; lu_gp = kmem_cache_zalloc(t10_alua_lu_gp_cache, GFP_KERNEL); - if (!(lu_gp)) { - printk(KERN_ERR "Unable to allocate struct t10_alua_lu_gp\n"); - return ERR_PTR(-ENOMEM);; + if (!lu_gp) { + pr_err("Unable to allocate struct t10_alua_lu_gp\n"); + return ERR_PTR(-ENOMEM); } - INIT_LIST_HEAD(&lu_gp->lu_gp_list); + INIT_LIST_HEAD(&lu_gp->lu_gp_node); INIT_LIST_HEAD(&lu_gp->lu_gp_mem_list); spin_lock_init(&lu_gp->lu_gp_lock); atomic_set(&lu_gp->lu_gp_ref_cnt, 0); if (def_group) { - lu_gp->lu_gp_id = se_global->alua_lu_gps_counter++;; + lu_gp->lu_gp_id = alua_lu_gps_counter++; lu_gp->lu_gp_valid_id = 1; - se_global->alua_lu_gps_count++; + alua_lu_gps_count++; } return lu_gp; @@ -1060,41 +1497,41 @@ int core_alua_set_lu_gp_id(struct t10_alua_lu_gp *lu_gp, u16 lu_gp_id) * The lu_gp->lu_gp_id may only be set once.. */ if (lu_gp->lu_gp_valid_id) { - printk(KERN_WARNING "ALUA LU Group already has a valid ID," + pr_warn("ALUA LU Group already has a valid ID," " ignoring request\n"); - return -1; + return -EINVAL; } - spin_lock(&se_global->lu_gps_lock); - if (se_global->alua_lu_gps_count == 0x0000ffff) { - printk(KERN_ERR "Maximum ALUA se_global->alua_lu_gps_count:" + spin_lock(&lu_gps_lock); + if (alua_lu_gps_count == 0x0000ffff) { + pr_err("Maximum ALUA alua_lu_gps_count:" " 0x0000ffff reached\n"); - spin_unlock(&se_global->lu_gps_lock); + spin_unlock(&lu_gps_lock); kmem_cache_free(t10_alua_lu_gp_cache, lu_gp); - return -1; + return -ENOSPC; } again: lu_gp_id_tmp = (lu_gp_id != 0) ? lu_gp_id : - se_global->alua_lu_gps_counter++; + alua_lu_gps_counter++; - list_for_each_entry(lu_gp_tmp, &se_global->g_lu_gps_list, lu_gp_list) { + list_for_each_entry(lu_gp_tmp, &lu_gps_list, lu_gp_node) { if (lu_gp_tmp->lu_gp_id == lu_gp_id_tmp) { - if (!(lu_gp_id)) + if (!lu_gp_id) goto again; - printk(KERN_WARNING "ALUA Logical Unit Group ID: %hu" + pr_warn("ALUA Logical Unit Group ID: %hu" " already exists, ignoring request\n", lu_gp_id); - spin_unlock(&se_global->lu_gps_lock); - return -1; + spin_unlock(&lu_gps_lock); + return -EINVAL; } } lu_gp->lu_gp_id = lu_gp_id_tmp; lu_gp->lu_gp_valid_id = 1; - list_add_tail(&lu_gp->lu_gp_list, &se_global->g_lu_gps_list); - se_global->alua_lu_gps_count++; - spin_unlock(&se_global->lu_gps_lock); + list_add_tail(&lu_gp->lu_gp_node, &lu_gps_list); + alua_lu_gps_count++; + spin_unlock(&lu_gps_lock); return 0; } @@ -1105,8 +1542,8 @@ core_alua_allocate_lu_gp_mem(struct se_device *dev) struct t10_alua_lu_gp_member *lu_gp_mem; lu_gp_mem = kmem_cache_zalloc(t10_alua_lu_gp_mem_cache, GFP_KERNEL); - if (!(lu_gp_mem)) { - printk(KERN_ERR "Unable to allocate struct t10_alua_lu_gp_member\n"); + if (!lu_gp_mem) { + pr_err("Unable to allocate struct t10_alua_lu_gp_member\n"); return ERR_PTR(-ENOMEM); } INIT_LIST_HEAD(&lu_gp_mem->lu_gp_mem_list); @@ -1130,11 +1567,10 @@ void core_alua_free_lu_gp(struct t10_alua_lu_gp *lu_gp) * no associations can be made while we are releasing * struct t10_alua_lu_gp. */ - spin_lock(&se_global->lu_gps_lock); - atomic_set(&lu_gp->lu_gp_shutdown, 1); - list_del(&lu_gp->lu_gp_list); - se_global->alua_lu_gps_count--; - spin_unlock(&se_global->lu_gps_lock); + spin_lock(&lu_gps_lock); + list_del(&lu_gp->lu_gp_node); + alua_lu_gps_count--; + spin_unlock(&lu_gps_lock); /* * Allow struct t10_alua_lu_gp * referenced by core_alua_get_lu_gp_by_name() * in target_core_configfs.c:target_core_store_alua_lu_gp() to be @@ -1157,17 +1593,17 @@ void core_alua_free_lu_gp(struct t10_alua_lu_gp *lu_gp) spin_unlock(&lu_gp->lu_gp_lock); /* * - * lu_gp_mem is assoicated with a single + * lu_gp_mem is associated with a single * struct se_device->dev_alua_lu_gp_mem, and is released when * struct se_device is released via core_alua_free_lu_gp_mem(). * * If the passed lu_gp does NOT match the default_lu_gp, assume - * we want to re-assocate a given lu_gp_mem with default_lu_gp. + * we want to re-associate a given lu_gp_mem with default_lu_gp. */ spin_lock(&lu_gp_mem->lu_gp_mem_lock); - if (lu_gp != se_global->default_lu_gp) + if (lu_gp != default_lu_gp) __core_alua_attach_lu_gp_mem(lu_gp_mem, - se_global->default_lu_gp); + default_lu_gp); else lu_gp_mem->lu_gp = NULL; spin_unlock(&lu_gp_mem->lu_gp_mem_lock); @@ -1181,16 +1617,11 @@ void core_alua_free_lu_gp(struct t10_alua_lu_gp *lu_gp) void core_alua_free_lu_gp_mem(struct se_device *dev) { - struct se_subsystem_dev *su_dev = dev->se_sub_dev; - struct t10_alua *alua = T10_ALUA(su_dev); struct t10_alua_lu_gp *lu_gp; struct t10_alua_lu_gp_member *lu_gp_mem; - if (alua->alua_type != SPC3_ALUA_EMULATED) - return; - lu_gp_mem = dev->dev_alua_lu_gp_mem; - if (!(lu_gp_mem)) + if (!lu_gp_mem) return; while (atomic_read(&lu_gp_mem->lu_gp_mem_ref_cnt)) @@ -1198,7 +1629,7 @@ void core_alua_free_lu_gp_mem(struct se_device *dev) spin_lock(&lu_gp_mem->lu_gp_mem_lock); lu_gp = lu_gp_mem->lu_gp; - if ((lu_gp)) { + if (lu_gp) { spin_lock(&lu_gp->lu_gp_lock); if (lu_gp_mem->lu_gp_assoc) { list_del(&lu_gp_mem->lu_gp_mem_list); @@ -1218,27 +1649,27 @@ struct t10_alua_lu_gp *core_alua_get_lu_gp_by_name(const char *name) struct t10_alua_lu_gp *lu_gp; struct config_item *ci; - spin_lock(&se_global->lu_gps_lock); - list_for_each_entry(lu_gp, &se_global->g_lu_gps_list, lu_gp_list) { - if (!(lu_gp->lu_gp_valid_id)) + spin_lock(&lu_gps_lock); + list_for_each_entry(lu_gp, &lu_gps_list, lu_gp_node) { + if (!lu_gp->lu_gp_valid_id) continue; ci = &lu_gp->lu_gp_group.cg_item; - if (!(strcmp(config_item_name(ci), name))) { + if (!strcmp(config_item_name(ci), name)) { atomic_inc(&lu_gp->lu_gp_ref_cnt); - spin_unlock(&se_global->lu_gps_lock); + spin_unlock(&lu_gps_lock); return lu_gp; } } - spin_unlock(&se_global->lu_gps_lock); + spin_unlock(&lu_gps_lock); return NULL; } void core_alua_put_lu_gp_from_name(struct t10_alua_lu_gp *lu_gp) { - spin_lock(&se_global->lu_gps_lock); + spin_lock(&lu_gps_lock); atomic_dec(&lu_gp->lu_gp_ref_cnt); - spin_unlock(&se_global->lu_gps_lock); + spin_unlock(&lu_gps_lock); } /* @@ -1271,16 +1702,14 @@ void __core_alua_drop_lu_gp_mem( spin_unlock(&lu_gp->lu_gp_lock); } -struct t10_alua_tg_pt_gp *core_alua_allocate_tg_pt_gp( - struct se_subsystem_dev *su_dev, - const char *name, - int def_group) +struct t10_alua_tg_pt_gp *core_alua_allocate_tg_pt_gp(struct se_device *dev, + const char *name, int def_group) { struct t10_alua_tg_pt_gp *tg_pt_gp; tg_pt_gp = kmem_cache_zalloc(t10_alua_tg_pt_gp_cache, GFP_KERNEL); - if (!(tg_pt_gp)) { - printk(KERN_ERR "Unable to allocate struct t10_alua_tg_pt_gp\n"); + if (!tg_pt_gp) { + pr_err("Unable to allocate struct t10_alua_tg_pt_gp\n"); return NULL; } INIT_LIST_HEAD(&tg_pt_gp->tg_pt_gp_list); @@ -1288,30 +1717,39 @@ struct t10_alua_tg_pt_gp *core_alua_allocate_tg_pt_gp( mutex_init(&tg_pt_gp->tg_pt_gp_md_mutex); spin_lock_init(&tg_pt_gp->tg_pt_gp_lock); atomic_set(&tg_pt_gp->tg_pt_gp_ref_cnt, 0); - tg_pt_gp->tg_pt_gp_su_dev = su_dev; - tg_pt_gp->tg_pt_gp_md_buf_len = ALUA_MD_BUF_LEN; + INIT_DELAYED_WORK(&tg_pt_gp->tg_pt_gp_transition_work, + core_alua_do_transition_tg_pt_work); + tg_pt_gp->tg_pt_gp_dev = dev; atomic_set(&tg_pt_gp->tg_pt_gp_alua_access_state, - ALUA_ACCESS_STATE_ACTIVE_OPTMIZED); + ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED); /* - * Enable both explict and implict ALUA support by default + * Enable both explicit and implicit ALUA support by default */ tg_pt_gp->tg_pt_gp_alua_access_type = - TPGS_EXPLICT_ALUA | TPGS_IMPLICT_ALUA; + TPGS_EXPLICIT_ALUA | TPGS_IMPLICIT_ALUA; /* * Set the default Active/NonOptimized Delay in milliseconds */ tg_pt_gp->tg_pt_gp_nonop_delay_msecs = ALUA_DEFAULT_NONOP_DELAY_MSECS; tg_pt_gp->tg_pt_gp_trans_delay_msecs = ALUA_DEFAULT_TRANS_DELAY_MSECS; + tg_pt_gp->tg_pt_gp_implicit_trans_secs = ALUA_DEFAULT_IMPLICIT_TRANS_SECS; + + /* + * Enable all supported states + */ + tg_pt_gp->tg_pt_gp_alua_supported_states = + ALUA_T_SUP | ALUA_O_SUP | + ALUA_U_SUP | ALUA_S_SUP | ALUA_AN_SUP | ALUA_AO_SUP; if (def_group) { - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); tg_pt_gp->tg_pt_gp_id = - T10_ALUA(su_dev)->alua_tg_pt_gps_counter++; + dev->t10_alua.alua_tg_pt_gps_counter++; tg_pt_gp->tg_pt_gp_valid_id = 1; - T10_ALUA(su_dev)->alua_tg_pt_gps_count++; + dev->t10_alua.alua_tg_pt_gps_count++; list_add_tail(&tg_pt_gp->tg_pt_gp_list, - &T10_ALUA(su_dev)->tg_pt_gps_list); - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + &dev->t10_alua.tg_pt_gps_list); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); } return tg_pt_gp; @@ -1321,49 +1759,50 @@ int core_alua_set_tg_pt_gp_id( struct t10_alua_tg_pt_gp *tg_pt_gp, u16 tg_pt_gp_id) { - struct se_subsystem_dev *su_dev = tg_pt_gp->tg_pt_gp_su_dev; + struct se_device *dev = tg_pt_gp->tg_pt_gp_dev; struct t10_alua_tg_pt_gp *tg_pt_gp_tmp; u16 tg_pt_gp_id_tmp; + /* * The tg_pt_gp->tg_pt_gp_id may only be set once.. */ if (tg_pt_gp->tg_pt_gp_valid_id) { - printk(KERN_WARNING "ALUA TG PT Group already has a valid ID," + pr_warn("ALUA TG PT Group already has a valid ID," " ignoring request\n"); - return -1; + return -EINVAL; } - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); - if (T10_ALUA(su_dev)->alua_tg_pt_gps_count == 0x0000ffff) { - printk(KERN_ERR "Maximum ALUA alua_tg_pt_gps_count:" + spin_lock(&dev->t10_alua.tg_pt_gps_lock); + if (dev->t10_alua.alua_tg_pt_gps_count == 0x0000ffff) { + pr_err("Maximum ALUA alua_tg_pt_gps_count:" " 0x0000ffff reached\n"); - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); kmem_cache_free(t10_alua_tg_pt_gp_cache, tg_pt_gp); - return -1; + return -ENOSPC; } again: tg_pt_gp_id_tmp = (tg_pt_gp_id != 0) ? tg_pt_gp_id : - T10_ALUA(su_dev)->alua_tg_pt_gps_counter++; + dev->t10_alua.alua_tg_pt_gps_counter++; - list_for_each_entry(tg_pt_gp_tmp, &T10_ALUA(su_dev)->tg_pt_gps_list, + list_for_each_entry(tg_pt_gp_tmp, &dev->t10_alua.tg_pt_gps_list, tg_pt_gp_list) { if (tg_pt_gp_tmp->tg_pt_gp_id == tg_pt_gp_id_tmp) { - if (!(tg_pt_gp_id)) + if (!tg_pt_gp_id) goto again; - printk(KERN_ERR "ALUA Target Port Group ID: %hu already" + pr_err("ALUA Target Port Group ID: %hu already" " exists, ignoring request\n", tg_pt_gp_id); - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); - return -1; + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); + return -EINVAL; } } tg_pt_gp->tg_pt_gp_id = tg_pt_gp_id_tmp; tg_pt_gp->tg_pt_gp_valid_id = 1; list_add_tail(&tg_pt_gp->tg_pt_gp_list, - &T10_ALUA(su_dev)->tg_pt_gps_list); - T10_ALUA(su_dev)->alua_tg_pt_gps_count++; - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + &dev->t10_alua.tg_pt_gps_list); + dev->t10_alua.alua_tg_pt_gps_count++; + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); return 0; } @@ -1375,8 +1814,8 @@ struct t10_alua_tg_pt_gp_member *core_alua_allocate_tg_pt_gp_mem( tg_pt_gp_mem = kmem_cache_zalloc(t10_alua_tg_pt_gp_mem_cache, GFP_KERNEL); - if (!(tg_pt_gp_mem)) { - printk(KERN_ERR "Unable to allocate struct t10_alua_tg_pt_gp_member\n"); + if (!tg_pt_gp_mem) { + pr_err("Unable to allocate struct t10_alua_tg_pt_gp_member\n"); return ERR_PTR(-ENOMEM); } INIT_LIST_HEAD(&tg_pt_gp_mem->tg_pt_gp_mem_list); @@ -1385,7 +1824,6 @@ struct t10_alua_tg_pt_gp_member *core_alua_allocate_tg_pt_gp_mem( tg_pt_gp_mem->tg_pt = port; port->sep_alua_tg_pt_gp_mem = tg_pt_gp_mem; - atomic_set(&port->sep_tg_pt_gp_active, 1); return tg_pt_gp_mem; } @@ -1393,20 +1831,24 @@ struct t10_alua_tg_pt_gp_member *core_alua_allocate_tg_pt_gp_mem( void core_alua_free_tg_pt_gp( struct t10_alua_tg_pt_gp *tg_pt_gp) { - struct se_subsystem_dev *su_dev = tg_pt_gp->tg_pt_gp_su_dev; + struct se_device *dev = tg_pt_gp->tg_pt_gp_dev; struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem, *tg_pt_gp_mem_tmp; + /* * Once we have reached this point, config_item_put() has already * been called from target_core_alua_drop_tg_pt_gp(). * * Here we remove *tg_pt_gp from the global list so that - * no assications *OR* explict ALUA via SET_TARGET_PORT_GROUPS + * no associations *OR* explicit ALUA via SET_TARGET_PORT_GROUPS * can be made while we are releasing struct t10_alua_tg_pt_gp. */ - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); list_del(&tg_pt_gp->tg_pt_gp_list); - T10_ALUA(su_dev)->alua_tg_pt_gps_counter--; - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + dev->t10_alua.alua_tg_pt_gps_counter--; + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); + + flush_delayed_work(&tg_pt_gp->tg_pt_gp_transition_work); + /* * Allow a struct t10_alua_tg_pt_gp_member * referenced by * core_alua_get_tg_pt_gp_by_name() in @@ -1415,6 +1857,7 @@ void core_alua_free_tg_pt_gp( */ while (atomic_read(&tg_pt_gp->tg_pt_gp_ref_cnt)) cpu_relax(); + /* * Release reference to struct t10_alua_tg_pt_gp from all associated * struct se_port. @@ -1429,18 +1872,18 @@ void core_alua_free_tg_pt_gp( } spin_unlock(&tg_pt_gp->tg_pt_gp_lock); /* - * tg_pt_gp_mem is assoicated with a single + * tg_pt_gp_mem is associated with a single * se_port->sep_alua_tg_pt_gp_mem, and is released via * core_alua_free_tg_pt_gp_mem(). * * If the passed tg_pt_gp does NOT match the default_tg_pt_gp, - * assume we want to re-assocate a given tg_pt_gp_mem with + * assume we want to re-associate a given tg_pt_gp_mem with * default_tg_pt_gp. */ spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); - if (tg_pt_gp != T10_ALUA(su_dev)->default_tg_pt_gp) { + if (tg_pt_gp != dev->t10_alua.default_tg_pt_gp) { __core_alua_attach_tg_pt_gp_mem(tg_pt_gp_mem, - T10_ALUA(su_dev)->default_tg_pt_gp); + dev->t10_alua.default_tg_pt_gp); } else tg_pt_gp_mem->tg_pt_gp = NULL; spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); @@ -1454,16 +1897,11 @@ void core_alua_free_tg_pt_gp( void core_alua_free_tg_pt_gp_mem(struct se_port *port) { - struct se_subsystem_dev *su_dev = port->sep_lun->lun_se_dev->se_sub_dev; - struct t10_alua *alua = T10_ALUA(su_dev); struct t10_alua_tg_pt_gp *tg_pt_gp; struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; - if (alua->alua_type != SPC3_ALUA_EMULATED) - return; - tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; - if (!(tg_pt_gp_mem)) + if (!tg_pt_gp_mem) return; while (atomic_read(&tg_pt_gp_mem->tg_pt_gp_mem_ref_cnt)) @@ -1471,7 +1909,7 @@ void core_alua_free_tg_pt_gp_mem(struct se_port *port) spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; - if ((tg_pt_gp)) { + if (tg_pt_gp) { spin_lock(&tg_pt_gp->tg_pt_gp_lock); if (tg_pt_gp_mem->tg_pt_gp_assoc) { list_del(&tg_pt_gp_mem->tg_pt_gp_mem_list); @@ -1487,25 +1925,24 @@ void core_alua_free_tg_pt_gp_mem(struct se_port *port) } static struct t10_alua_tg_pt_gp *core_alua_get_tg_pt_gp_by_name( - struct se_subsystem_dev *su_dev, - const char *name) + struct se_device *dev, const char *name) { struct t10_alua_tg_pt_gp *tg_pt_gp; struct config_item *ci; - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); - list_for_each_entry(tg_pt_gp, &T10_ALUA(su_dev)->tg_pt_gps_list, + spin_lock(&dev->t10_alua.tg_pt_gps_lock); + list_for_each_entry(tg_pt_gp, &dev->t10_alua.tg_pt_gps_list, tg_pt_gp_list) { - if (!(tg_pt_gp->tg_pt_gp_valid_id)) + if (!tg_pt_gp->tg_pt_gp_valid_id) continue; ci = &tg_pt_gp->tg_pt_gp_group.cg_item; - if (!(strcmp(config_item_name(ci), name))) { + if (!strcmp(config_item_name(ci), name)) { atomic_inc(&tg_pt_gp->tg_pt_gp_ref_cnt); - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); return tg_pt_gp; } } - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); return NULL; } @@ -1513,11 +1950,11 @@ static struct t10_alua_tg_pt_gp *core_alua_get_tg_pt_gp_by_name( static void core_alua_put_tg_pt_gp_from_name( struct t10_alua_tg_pt_gp *tg_pt_gp) { - struct se_subsystem_dev *su_dev = tg_pt_gp->tg_pt_gp_su_dev; + struct se_device *dev = tg_pt_gp->tg_pt_gp_dev; - spin_lock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_lock(&dev->t10_alua.tg_pt_gps_lock); atomic_dec(&tg_pt_gp->tg_pt_gp_ref_cnt); - spin_unlock(&T10_ALUA(su_dev)->tg_pt_gps_lock); + spin_unlock(&dev->t10_alua.tg_pt_gps_lock); } /* @@ -1553,23 +1990,18 @@ static void __core_alua_drop_tg_pt_gp_mem( ssize_t core_alua_show_tg_pt_gp_info(struct se_port *port, char *page) { - struct se_subsystem_dev *su_dev = port->sep_lun->lun_se_dev->se_sub_dev; struct config_item *tg_pt_ci; - struct t10_alua *alua = T10_ALUA(su_dev); struct t10_alua_tg_pt_gp *tg_pt_gp; struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; ssize_t len = 0; - if (alua->alua_type != SPC3_ALUA_EMULATED) - return len; - tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; - if (!(tg_pt_gp_mem)) + if (!tg_pt_gp_mem) return len; spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; - if ((tg_pt_gp)) { + if (tg_pt_gp) { tg_pt_ci = &tg_pt_gp->tg_pt_gp_group.cg_item; len += sprintf(page, "TG Port Alias: %s\nTG Port Group ID:" " %hu\nTG Port Primary Access State: %s\nTG Port " @@ -1596,7 +2028,7 @@ ssize_t core_alua_store_tg_pt_gp_info( { struct se_portal_group *tpg; struct se_lun *lun; - struct se_subsystem_dev *su_dev = port->sep_lun->lun_se_dev->se_sub_dev; + struct se_device *dev = port->sep_lun->lun_se_dev; struct t10_alua_tg_pt_gp *tg_pt_gp = NULL, *tg_pt_gp_new = NULL; struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; unsigned char buf[TG_PT_GROUP_NAME_BUF]; @@ -1605,16 +2037,12 @@ ssize_t core_alua_store_tg_pt_gp_info( tpg = port->sep_tpg; lun = port->sep_lun; - if (T10_ALUA(su_dev)->alua_type != SPC3_ALUA_EMULATED) { - printk(KERN_WARNING "SPC3_ALUA_EMULATED not enabled for" - " %s/tpgt_%hu/%s\n", TPG_TFO(tpg)->tpg_get_wwn(tpg), - TPG_TFO(tpg)->tpg_get_tag(tpg), - config_item_name(&lun->lun_group.cg_item)); - return -EINVAL; - } + tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; + if (!tg_pt_gp_mem) + return 0; if (count > TG_PT_GROUP_NAME_BUF) { - printk(KERN_ERR "ALUA Target Port Group alias too large!\n"); + pr_err("ALUA Target Port Group alias too large!\n"); return -EINVAL; } memset(buf, 0, TG_PT_GROUP_NAME_BUF); @@ -1629,33 +2057,26 @@ ssize_t core_alua_store_tg_pt_gp_info( * struct t10_alua_tg_pt_gp. This reference is released with * core_alua_put_tg_pt_gp_from_name() below. */ - tg_pt_gp_new = core_alua_get_tg_pt_gp_by_name(su_dev, + tg_pt_gp_new = core_alua_get_tg_pt_gp_by_name(dev, strstrip(buf)); - if (!(tg_pt_gp_new)) + if (!tg_pt_gp_new) return -ENODEV; } - tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; - if (!(tg_pt_gp_mem)) { - if (tg_pt_gp_new) - core_alua_put_tg_pt_gp_from_name(tg_pt_gp_new); - printk(KERN_ERR "NULL struct se_port->sep_alua_tg_pt_gp_mem pointer\n"); - return -EINVAL; - } spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; - if ((tg_pt_gp)) { + if (tg_pt_gp) { /* * Clearing an existing tg_pt_gp association, and replacing * with the default_tg_pt_gp. */ - if (!(tg_pt_gp_new)) { - printk(KERN_INFO "Target_Core_ConfigFS: Moving" + if (!tg_pt_gp_new) { + pr_debug("Target_Core_ConfigFS: Moving" " %s/tpgt_%hu/%s from ALUA Target Port Group:" " alua/%s, ID: %hu back to" " default_tg_pt_gp\n", - TPG_TFO(tpg)->tpg_get_wwn(tpg), - TPG_TFO(tpg)->tpg_get_tag(tpg), + tpg->se_tpg_tfo->tpg_get_wwn(tpg), + tpg->se_tpg_tfo->tpg_get_tag(tpg), config_item_name(&lun->lun_group.cg_item), config_item_name( &tg_pt_gp->tg_pt_gp_group.cg_item), @@ -1663,7 +2084,7 @@ ssize_t core_alua_store_tg_pt_gp_info( __core_alua_drop_tg_pt_gp_mem(tg_pt_gp_mem, tg_pt_gp); __core_alua_attach_tg_pt_gp_mem(tg_pt_gp_mem, - T10_ALUA(su_dev)->default_tg_pt_gp); + dev->t10_alua.default_tg_pt_gp); spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); return count; @@ -1679,10 +2100,10 @@ ssize_t core_alua_store_tg_pt_gp_info( */ __core_alua_attach_tg_pt_gp_mem(tg_pt_gp_mem, tg_pt_gp_new); spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); - printk(KERN_INFO "Target_Core_ConfigFS: %s %s/tpgt_%hu/%s to ALUA" + pr_debug("Target_Core_ConfigFS: %s %s/tpgt_%hu/%s to ALUA" " Target Port Group: alua/%s, ID: %hu\n", (move) ? - "Moving" : "Adding", TPG_TFO(tpg)->tpg_get_wwn(tpg), - TPG_TFO(tpg)->tpg_get_tag(tpg), + "Moving" : "Adding", tpg->se_tpg_tfo->tpg_get_wwn(tpg), + tpg->se_tpg_tfo->tpg_get_tag(tpg), config_item_name(&lun->lun_group.cg_item), config_item_name(&tg_pt_gp_new->tg_pt_gp_group.cg_item), tg_pt_gp_new->tg_pt_gp_id); @@ -1695,13 +2116,13 @@ ssize_t core_alua_show_access_type( struct t10_alua_tg_pt_gp *tg_pt_gp, char *page) { - if ((tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_EXPLICT_ALUA) && - (tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_IMPLICT_ALUA)) - return sprintf(page, "Implict and Explict\n"); - else if (tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_IMPLICT_ALUA) - return sprintf(page, "Implict\n"); - else if (tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_EXPLICT_ALUA) - return sprintf(page, "Explict\n"); + if ((tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_EXPLICIT_ALUA) && + (tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_IMPLICIT_ALUA)) + return sprintf(page, "Implicit and Explicit\n"); + else if (tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_IMPLICIT_ALUA) + return sprintf(page, "Implicit\n"); + else if (tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_EXPLICIT_ALUA) + return sprintf(page, "Explicit\n"); else return sprintf(page, "None\n"); } @@ -1714,23 +2135,23 @@ ssize_t core_alua_store_access_type( unsigned long tmp; int ret; - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract alua_access_type\n"); - return -EINVAL; + pr_err("Unable to extract alua_access_type\n"); + return ret; } if ((tmp != 0) && (tmp != 1) && (tmp != 2) && (tmp != 3)) { - printk(KERN_ERR "Illegal value for alua_access_type:" + pr_err("Illegal value for alua_access_type:" " %lu\n", tmp); return -EINVAL; } if (tmp == 3) tg_pt_gp->tg_pt_gp_alua_access_type = - TPGS_IMPLICT_ALUA | TPGS_EXPLICT_ALUA; + TPGS_IMPLICIT_ALUA | TPGS_EXPLICIT_ALUA; else if (tmp == 2) - tg_pt_gp->tg_pt_gp_alua_access_type = TPGS_EXPLICT_ALUA; + tg_pt_gp->tg_pt_gp_alua_access_type = TPGS_EXPLICIT_ALUA; else if (tmp == 1) - tg_pt_gp->tg_pt_gp_alua_access_type = TPGS_IMPLICT_ALUA; + tg_pt_gp->tg_pt_gp_alua_access_type = TPGS_IMPLICIT_ALUA; else tg_pt_gp->tg_pt_gp_alua_access_type = 0; @@ -1752,13 +2173,13 @@ ssize_t core_alua_store_nonop_delay_msecs( unsigned long tmp; int ret; - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract nonop_delay_msecs\n"); - return -EINVAL; + pr_err("Unable to extract nonop_delay_msecs\n"); + return ret; } if (tmp > ALUA_MAX_NONOP_DELAY_MSECS) { - printk(KERN_ERR "Passed nonop_delay_msecs: %lu, exceeds" + pr_err("Passed nonop_delay_msecs: %lu, exceeds" " ALUA_MAX_NONOP_DELAY_MSECS: %d\n", tmp, ALUA_MAX_NONOP_DELAY_MSECS); return -EINVAL; @@ -1783,13 +2204,13 @@ ssize_t core_alua_store_trans_delay_msecs( unsigned long tmp; int ret; - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract trans_delay_msecs\n"); - return -EINVAL; + pr_err("Unable to extract trans_delay_msecs\n"); + return ret; } if (tmp > ALUA_MAX_TRANS_DELAY_MSECS) { - printk(KERN_ERR "Passed trans_delay_msecs: %lu, exceeds" + pr_err("Passed trans_delay_msecs: %lu, exceeds" " ALUA_MAX_TRANS_DELAY_MSECS: %d\n", tmp, ALUA_MAX_TRANS_DELAY_MSECS); return -EINVAL; @@ -1799,6 +2220,37 @@ ssize_t core_alua_store_trans_delay_msecs( return count; } +ssize_t core_alua_show_implicit_trans_secs( + struct t10_alua_tg_pt_gp *tg_pt_gp, + char *page) +{ + return sprintf(page, "%d\n", tg_pt_gp->tg_pt_gp_implicit_trans_secs); +} + +ssize_t core_alua_store_implicit_trans_secs( + struct t10_alua_tg_pt_gp *tg_pt_gp, + const char *page, + size_t count) +{ + unsigned long tmp; + int ret; + + ret = kstrtoul(page, 0, &tmp); + if (ret < 0) { + pr_err("Unable to extract implicit_trans_secs\n"); + return ret; + } + if (tmp > ALUA_MAX_IMPLICIT_TRANS_SECS) { + pr_err("Passed implicit_trans_secs: %lu, exceeds" + " ALUA_MAX_IMPLICIT_TRANS_SECS: %d\n", tmp, + ALUA_MAX_IMPLICIT_TRANS_SECS); + return -EINVAL; + } + tg_pt_gp->tg_pt_gp_implicit_trans_secs = (int)tmp; + + return count; +} + ssize_t core_alua_show_preferred_bit( struct t10_alua_tg_pt_gp *tg_pt_gp, char *page) @@ -1814,13 +2266,13 @@ ssize_t core_alua_store_preferred_bit( unsigned long tmp; int ret; - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract preferred ALUA value\n"); - return -EINVAL; + pr_err("Unable to extract preferred ALUA value\n"); + return ret; } if ((tmp != 0) && (tmp != 1)) { - printk(KERN_ERR "Illegal value for preferred ALUA: %lu\n", tmp); + pr_err("Illegal value for preferred ALUA: %lu\n", tmp); return -EINVAL; } tg_pt_gp->tg_pt_gp_pref = (int)tmp; @@ -1830,7 +2282,7 @@ ssize_t core_alua_store_preferred_bit( ssize_t core_alua_show_offline_bit(struct se_lun *lun, char *page) { - if (!(lun->lun_sep)) + if (!lun->lun_sep) return -ENODEV; return sprintf(page, "%d\n", @@ -1846,22 +2298,22 @@ ssize_t core_alua_store_offline_bit( unsigned long tmp; int ret; - if (!(lun->lun_sep)) + if (!lun->lun_sep) return -ENODEV; - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract alua_tg_pt_offline value\n"); - return -EINVAL; + pr_err("Unable to extract alua_tg_pt_offline value\n"); + return ret; } if ((tmp != 0) && (tmp != 1)) { - printk(KERN_ERR "Illegal value for alua_tg_pt_offline: %lu\n", + pr_err("Illegal value for alua_tg_pt_offline: %lu\n", tmp); return -EINVAL; } tg_pt_gp_mem = lun->lun_sep->sep_alua_tg_pt_gp_mem; - if (!(tg_pt_gp_mem)) { - printk(KERN_ERR "Unable to locate *tg_pt_gp_mem\n"); + if (!tg_pt_gp_mem) { + pr_err("Unable to locate *tg_pt_gp_mem\n"); return -EINVAL; } @@ -1888,15 +2340,15 @@ ssize_t core_alua_store_secondary_status( unsigned long tmp; int ret; - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract alua_tg_pt_status\n"); - return -EINVAL; + pr_err("Unable to extract alua_tg_pt_status\n"); + return ret; } if ((tmp != ALUA_STATUS_NONE) && - (tmp != ALUA_STATUS_ALTERED_BY_EXPLICT_STPG) && - (tmp != ALUA_STATUS_ALTERED_BY_IMPLICT_ALUA)) { - printk(KERN_ERR "Illegal value for alua_tg_pt_status: %lu\n", + (tmp != ALUA_STATUS_ALTERED_BY_EXPLICIT_STPG) && + (tmp != ALUA_STATUS_ALTERED_BY_IMPLICIT_ALUA)) { + pr_err("Illegal value for alua_tg_pt_status: %lu\n", tmp); return -EINVAL; } @@ -1921,13 +2373,13 @@ ssize_t core_alua_store_secondary_write_metadata( unsigned long tmp; int ret; - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract alua_tg_pt_write_md\n"); - return -EINVAL; + pr_err("Unable to extract alua_tg_pt_write_md\n"); + return ret; } if ((tmp != 0) && (tmp != 1)) { - printk(KERN_ERR "Illegal value for alua_tg_pt_write_md:" + pr_err("Illegal value for alua_tg_pt_write_md:" " %lu\n", tmp); return -EINVAL; } @@ -1936,55 +2388,28 @@ ssize_t core_alua_store_secondary_write_metadata( return count; } -int core_setup_alua(struct se_device *dev, int force_pt) +int core_setup_alua(struct se_device *dev) { - struct se_subsystem_dev *su_dev = dev->se_sub_dev; - struct t10_alua *alua = T10_ALUA(su_dev); - struct t10_alua_lu_gp_member *lu_gp_mem; - /* - * If this device is from Target_Core_Mod/pSCSI, use the ALUA logic - * of the Underlying SCSI hardware. In Linux/SCSI terms, this can - * cause a problem because libata and some SATA RAID HBAs appear - * under Linux/SCSI, but emulate SCSI logic themselves. - */ - if (((TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) && - !(DEV_ATTRIB(dev)->emulate_alua)) || force_pt) { - alua->alua_type = SPC_ALUA_PASSTHROUGH; - alua->alua_state_check = &core_alua_state_check_nop; - printk(KERN_INFO "%s: Using SPC_ALUA_PASSTHROUGH, no ALUA" - " emulation\n", TRANSPORT(dev)->name); - return 0; - } - /* - * If SPC-3 or above is reported by real or emulated struct se_device, - * use emulated ALUA. - */ - if (TRANSPORT(dev)->get_device_rev(dev) >= SCSI_3) { - printk(KERN_INFO "%s: Enabling ALUA Emulation for SPC-3" - " device\n", TRANSPORT(dev)->name); + if (dev->transport->transport_type != TRANSPORT_PLUGIN_PHBA_PDEV && + !(dev->se_hba->hba_flags & HBA_FLAGS_INTERNAL_USE)) { + struct t10_alua_lu_gp_member *lu_gp_mem; + /* - * Assoicate this struct se_device with the default ALUA + * Associate this struct se_device with the default ALUA * LUN Group. */ lu_gp_mem = core_alua_allocate_lu_gp_mem(dev); - if (IS_ERR(lu_gp_mem) || !lu_gp_mem) - return -1; + if (IS_ERR(lu_gp_mem)) + return PTR_ERR(lu_gp_mem); - alua->alua_type = SPC3_ALUA_EMULATED; - alua->alua_state_check = &core_alua_state_check; spin_lock(&lu_gp_mem->lu_gp_mem_lock); __core_alua_attach_lu_gp_mem(lu_gp_mem, - se_global->default_lu_gp); + default_lu_gp); spin_unlock(&lu_gp_mem->lu_gp_mem_lock); - printk(KERN_INFO "%s: Adding to default ALUA LU Group:" + pr_debug("%s: Adding to default ALUA LU Group:" " core/alua/lu_gps/default_lu_gp\n", - TRANSPORT(dev)->name); - } else { - alua->alua_type = SPC2_ALUA_DISABLED; - alua->alua_state_check = &core_alua_state_check_nop; - printk(KERN_INFO "%s: Disabling ALUA Emulation for SPC-2" - " device\n", TRANSPORT(dev)->name); + dev->transport->name); } return 0; diff --git a/drivers/target/target_core_alua.h b/drivers/target/target_core_alua.h index c86f97a081e..0a7d65e8040 100644 --- a/drivers/target/target_core_alua.h +++ b/drivers/target/target_core_alua.h @@ -7,29 +7,41 @@ * from spc4r17 section 6.4.2 Table 135 */ #define TPGS_NO_ALUA 0x00 -#define TPGS_IMPLICT_ALUA 0x10 -#define TPGS_EXPLICT_ALUA 0x20 +#define TPGS_IMPLICIT_ALUA 0x10 +#define TPGS_EXPLICIT_ALUA 0x20 /* * ASYMMETRIC ACCESS STATE field * - * from spc4r17 section 6.27 Table 245 + * from spc4r36j section 6.37 Table 307 */ -#define ALUA_ACCESS_STATE_ACTIVE_OPTMIZED 0x0 +#define ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED 0x0 #define ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED 0x1 #define ALUA_ACCESS_STATE_STANDBY 0x2 #define ALUA_ACCESS_STATE_UNAVAILABLE 0x3 +#define ALUA_ACCESS_STATE_LBA_DEPENDENT 0x4 #define ALUA_ACCESS_STATE_OFFLINE 0xe #define ALUA_ACCESS_STATE_TRANSITION 0xf /* + * from spc4r36j section 6.37 Table 306 + */ +#define ALUA_T_SUP 0x80 +#define ALUA_O_SUP 0x40 +#define ALUA_LBD_SUP 0x10 +#define ALUA_U_SUP 0x08 +#define ALUA_S_SUP 0x04 +#define ALUA_AN_SUP 0x02 +#define ALUA_AO_SUP 0x01 + +/* * REPORT_TARGET_PORT_GROUP STATUS CODE * * from spc4r17 section 6.27 Table 246 */ #define ALUA_STATUS_NONE 0x00 -#define ALUA_STATUS_ALTERED_BY_EXPLICT_STPG 0x01 -#define ALUA_STATUS_ALTERED_BY_IMPLICT_ALUA 0x02 +#define ALUA_STATUS_ALTERED_BY_EXPLICIT_STPG 0x01 +#define ALUA_STATUS_ALTERED_BY_IMPLICIT_ALUA 0x02 /* * From spc4r17, Table D.1: ASC and ASCQ Assignement @@ -46,12 +58,18 @@ #define ALUA_DEFAULT_NONOP_DELAY_MSECS 100 #define ALUA_MAX_NONOP_DELAY_MSECS 10000 /* 10 seconds */ /* - * Used for implict and explict ALUA transitional delay, that is disabled + * Used for implicit and explicit ALUA transitional delay, that is disabled * by default, and is intended to be used for debugging client side ALUA code. */ #define ALUA_DEFAULT_TRANS_DELAY_MSECS 0 #define ALUA_MAX_TRANS_DELAY_MSECS 30000 /* 30 seconds */ /* + * Used for the recommended application client implicit transition timeout + * in seconds, returned by the REPORT_TARGET_PORT_GROUPS w/ extended header. + */ +#define ALUA_DEFAULT_IMPLICIT_TRANS_SECS 0 +#define ALUA_MAX_IMPLICIT_TRANS_SECS 255 +/* * Used by core_alua_update_tpg_primary_metadata() and * core_alua_update_tpg_secondary_metadata() */ @@ -61,18 +79,30 @@ */ #define ALUA_SECONDARY_METADATA_WWN_LEN 256 +/* Used by core_alua_update_tpg_(primary,secondary)_metadata */ +#define ALUA_MD_BUF_LEN 1024 + extern struct kmem_cache *t10_alua_lu_gp_cache; extern struct kmem_cache *t10_alua_lu_gp_mem_cache; extern struct kmem_cache *t10_alua_tg_pt_gp_cache; extern struct kmem_cache *t10_alua_tg_pt_gp_mem_cache; +extern struct kmem_cache *t10_alua_lba_map_cache; +extern struct kmem_cache *t10_alua_lba_map_mem_cache; -extern int core_emulate_report_target_port_groups(struct se_cmd *); -extern int core_emulate_set_target_port_groups(struct se_cmd *); +extern sense_reason_t target_emulate_report_target_port_groups(struct se_cmd *); +extern sense_reason_t target_emulate_set_target_port_groups(struct se_cmd *); +extern sense_reason_t target_emulate_report_referrals(struct se_cmd *); extern int core_alua_check_nonop_delay(struct se_cmd *); extern int core_alua_do_port_transition(struct t10_alua_tg_pt_gp *, struct se_device *, struct se_port *, struct se_node_acl *, int, int); extern char *core_alua_dump_status(int); +extern struct t10_alua_lba_map *core_alua_allocate_lba_map( + struct list_head *, u64, u64); +extern int core_alua_allocate_lba_map_mem(struct t10_alua_lba_map *, int, int); +extern void core_alua_free_lba_map(struct list_head *); +extern void core_alua_set_lba_map(struct se_device *, struct list_head *, + int, int); extern struct t10_alua_lu_gp *core_alua_allocate_lu_gp(const char *, int); extern int core_alua_set_lu_gp_id(struct t10_alua_lu_gp *, u16); extern void core_alua_free_lu_gp(struct t10_alua_lu_gp *); @@ -85,7 +115,7 @@ extern void __core_alua_drop_lu_gp_mem(struct t10_alua_lu_gp_member *, struct t10_alua_lu_gp *); extern void core_alua_drop_lu_gp_dev(struct se_device *); extern struct t10_alua_tg_pt_gp *core_alua_allocate_tg_pt_gp( - struct se_subsystem_dev *, const char *, int); + struct se_device *, const char *, int); extern int core_alua_set_tg_pt_gp_id(struct t10_alua_tg_pt_gp *, u16); extern struct t10_alua_tg_pt_gp_member *core_alua_allocate_tg_pt_gp_mem( struct se_port *); @@ -107,6 +137,10 @@ extern ssize_t core_alua_show_trans_delay_msecs(struct t10_alua_tg_pt_gp *, char *); extern ssize_t core_alua_store_trans_delay_msecs(struct t10_alua_tg_pt_gp *, const char *, size_t); +extern ssize_t core_alua_show_implicit_trans_secs(struct t10_alua_tg_pt_gp *, + char *); +extern ssize_t core_alua_store_implicit_trans_secs(struct t10_alua_tg_pt_gp *, + const char *, size_t); extern ssize_t core_alua_show_preferred_bit(struct t10_alua_tg_pt_gp *, char *); extern ssize_t core_alua_store_preferred_bit(struct t10_alua_tg_pt_gp *, @@ -121,6 +155,7 @@ extern ssize_t core_alua_show_secondary_write_metadata(struct se_lun *, char *); extern ssize_t core_alua_store_secondary_write_metadata(struct se_lun *, const char *, size_t); -extern int core_setup_alua(struct se_device *, int); +extern int core_setup_alua(struct se_device *); +extern sense_reason_t target_alua_state_check(struct se_cmd *cmd); #endif /* TARGET_CORE_ALUA_H */ diff --git a/drivers/target/target_core_cdb.c b/drivers/target/target_core_cdb.c deleted file mode 100644 index 366080baf47..00000000000 --- a/drivers/target/target_core_cdb.c +++ /dev/null @@ -1,1131 +0,0 @@ -/* - * CDB emulation for non-READ/WRITE commands. - * - * Copyright (c) 2002, 2003, 2004, 2005 PyX Technologies, Inc. - * Copyright (c) 2005, 2006, 2007 SBE, Inc. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org - * - * Nicholas A. Bellinger <nab@kernel.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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include <asm/unaligned.h> -#include <scsi/scsi.h> - -#include <target/target_core_base.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> -#include "target_core_ua.h" - -static void -target_fill_alua_data(struct se_port *port, unsigned char *buf) -{ - struct t10_alua_tg_pt_gp *tg_pt_gp; - struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; - - /* - * Set SCCS for MAINTENANCE_IN + REPORT_TARGET_PORT_GROUPS. - */ - buf[5] = 0x80; - - /* - * Set TPGS field for explict and/or implict ALUA access type - * and opteration. - * - * See spc4r17 section 6.4.2 Table 135 - */ - if (!port) - return; - tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; - if (!tg_pt_gp_mem) - return; - - spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); - tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; - if (tg_pt_gp) - buf[5] |= tg_pt_gp->tg_pt_gp_alua_access_type; - spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); -} - -static int -target_emulate_inquiry_std(struct se_cmd *cmd) -{ - struct se_lun *lun = SE_LUN(cmd); - struct se_device *dev = SE_DEV(cmd); - unsigned char *buf = cmd->t_task->t_task_buf; - - /* - * Make sure we at least have 6 bytes of INQUIRY response - * payload going back for EVPD=0 - */ - if (cmd->data_length < 6) { - printk(KERN_ERR "SCSI Inquiry payload length: %u" - " too small for EVPD=0\n", cmd->data_length); - return -1; - } - - buf[0] = dev->transport->get_device_type(dev); - if (buf[0] == TYPE_TAPE) - buf[1] = 0x80; - buf[2] = dev->transport->get_device_rev(dev); - - /* - * Enable SCCS and TPGS fields for Emulated ALUA - */ - if (T10_ALUA(dev->se_sub_dev)->alua_type == SPC3_ALUA_EMULATED) - target_fill_alua_data(lun->lun_sep, buf); - - if (cmd->data_length < 8) { - buf[4] = 1; /* Set additional length to 1 */ - return 0; - } - - buf[7] = 0x32; /* Sync=1 and CmdQue=1 */ - - /* - * Do not include vendor, product, reversion info in INQUIRY - * response payload for cdbs with a small allocation length. - */ - if (cmd->data_length < 36) { - buf[4] = 3; /* Set additional length to 3 */ - return 0; - } - - snprintf((unsigned char *)&buf[8], 8, "LIO-ORG"); - snprintf((unsigned char *)&buf[16], 16, "%s", - &DEV_T10_WWN(dev)->model[0]); - snprintf((unsigned char *)&buf[32], 4, "%s", - &DEV_T10_WWN(dev)->revision[0]); - buf[4] = 31; /* Set additional length to 31 */ - return 0; -} - -/* supported vital product data pages */ -static int -target_emulate_evpd_00(struct se_cmd *cmd, unsigned char *buf) -{ - buf[1] = 0x00; - if (cmd->data_length < 8) - return 0; - - buf[4] = 0x0; - /* - * Only report the INQUIRY EVPD=1 pages after a valid NAA - * Registered Extended LUN WWN has been set via ConfigFS - * during device creation/restart. - */ - if (SE_DEV(cmd)->se_sub_dev->su_dev_flags & - SDF_EMULATED_VPD_UNIT_SERIAL) { - buf[3] = 3; - buf[5] = 0x80; - buf[6] = 0x83; - buf[7] = 0x86; - } - - return 0; -} - -/* unit serial number */ -static int -target_emulate_evpd_80(struct se_cmd *cmd, unsigned char *buf) -{ - struct se_device *dev = SE_DEV(cmd); - u16 len = 0; - - buf[1] = 0x80; - if (dev->se_sub_dev->su_dev_flags & - SDF_EMULATED_VPD_UNIT_SERIAL) { - u32 unit_serial_len; - - unit_serial_len = - strlen(&DEV_T10_WWN(dev)->unit_serial[0]); - unit_serial_len++; /* For NULL Terminator */ - - if (((len + 4) + unit_serial_len) > cmd->data_length) { - len += unit_serial_len; - buf[2] = ((len >> 8) & 0xff); - buf[3] = (len & 0xff); - return 0; - } - len += sprintf((unsigned char *)&buf[4], "%s", - &DEV_T10_WWN(dev)->unit_serial[0]); - len++; /* Extra Byte for NULL Terminator */ - buf[3] = len; - } - return 0; -} - -/* - * Device identification VPD, for a complete list of - * DESIGNATOR TYPEs see spc4r17 Table 459. - */ -static int -target_emulate_evpd_83(struct se_cmd *cmd, unsigned char *buf) -{ - struct se_device *dev = SE_DEV(cmd); - struct se_lun *lun = SE_LUN(cmd); - struct se_port *port = NULL; - struct se_portal_group *tpg = NULL; - struct t10_alua_lu_gp_member *lu_gp_mem; - struct t10_alua_tg_pt_gp *tg_pt_gp; - struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; - unsigned char binary, binary_new; - unsigned char *prod = &DEV_T10_WWN(dev)->model[0]; - u32 prod_len; - u32 unit_serial_len, off = 0; - int i; - u16 len = 0, id_len; - - buf[1] = 0x83; - off = 4; - - /* - * NAA IEEE Registered Extended Assigned designator format, see - * spc4r17 section 7.7.3.6.5 - * - * We depend upon a target_core_mod/ConfigFS provided - * /sys/kernel/config/target/core/$HBA/$DEV/wwn/vpd_unit_serial - * value in order to return the NAA id. - */ - if (!(dev->se_sub_dev->su_dev_flags & SDF_EMULATED_VPD_UNIT_SERIAL)) - goto check_t10_vend_desc; - - if (off + 20 > cmd->data_length) - goto check_t10_vend_desc; - - /* CODE SET == Binary */ - buf[off++] = 0x1; - - /* Set ASSOICATION == addressed logical unit: 0)b */ - buf[off] = 0x00; - - /* Identifier/Designator type == NAA identifier */ - buf[off++] = 0x3; - off++; - - /* Identifier/Designator length */ - buf[off++] = 0x10; - - /* - * Start NAA IEEE Registered Extended Identifier/Designator - */ - buf[off++] = (0x6 << 4); - - /* - * Use OpenFabrics IEEE Company ID: 00 14 05 - */ - buf[off++] = 0x01; - buf[off++] = 0x40; - buf[off] = (0x5 << 4); - - /* - * Return ConfigFS Unit Serial Number information for - * VENDOR_SPECIFIC_IDENTIFIER and - * VENDOR_SPECIFIC_IDENTIFIER_EXTENTION - */ - binary = transport_asciihex_to_binaryhex( - &DEV_T10_WWN(dev)->unit_serial[0]); - buf[off++] |= (binary & 0xf0) >> 4; - for (i = 0; i < 24; i += 2) { - binary_new = transport_asciihex_to_binaryhex( - &DEV_T10_WWN(dev)->unit_serial[i+2]); - buf[off] = (binary & 0x0f) << 4; - buf[off++] |= (binary_new & 0xf0) >> 4; - binary = binary_new; - } - len = 20; - off = (len + 4); - -check_t10_vend_desc: - /* - * T10 Vendor Identifier Page, see spc4r17 section 7.7.3.4 - */ - id_len = 8; /* For Vendor field */ - prod_len = 4; /* For VPD Header */ - prod_len += 8; /* For Vendor field */ - prod_len += strlen(prod); - prod_len++; /* For : */ - - if (dev->se_sub_dev->su_dev_flags & - SDF_EMULATED_VPD_UNIT_SERIAL) { - unit_serial_len = - strlen(&DEV_T10_WWN(dev)->unit_serial[0]); - unit_serial_len++; /* For NULL Terminator */ - - if ((len + (id_len + 4) + - (prod_len + unit_serial_len)) > - cmd->data_length) { - len += (prod_len + unit_serial_len); - goto check_port; - } - id_len += sprintf((unsigned char *)&buf[off+12], - "%s:%s", prod, - &DEV_T10_WWN(dev)->unit_serial[0]); - } - buf[off] = 0x2; /* ASCII */ - buf[off+1] = 0x1; /* T10 Vendor ID */ - buf[off+2] = 0x0; - memcpy((unsigned char *)&buf[off+4], "LIO-ORG", 8); - /* Extra Byte for NULL Terminator */ - id_len++; - /* Identifier Length */ - buf[off+3] = id_len; - /* Header size for Designation descriptor */ - len += (id_len + 4); - off += (id_len + 4); - /* - * struct se_port is only set for INQUIRY VPD=1 through $FABRIC_MOD - */ -check_port: - port = lun->lun_sep; - if (port) { - struct t10_alua_lu_gp *lu_gp; - u32 padding, scsi_name_len; - u16 lu_gp_id = 0; - u16 tg_pt_gp_id = 0; - u16 tpgt; - - tpg = port->sep_tpg; - /* - * Relative target port identifer, see spc4r17 - * section 7.7.3.7 - * - * Get the PROTOCOL IDENTIFIER as defined by spc4r17 - * section 7.5.1 Table 362 - */ - if (((len + 4) + 8) > cmd->data_length) { - len += 8; - goto check_tpgi; - } - buf[off] = - (TPG_TFO(tpg)->get_fabric_proto_ident(tpg) << 4); - buf[off++] |= 0x1; /* CODE SET == Binary */ - buf[off] = 0x80; /* Set PIV=1 */ - /* Set ASSOICATION == target port: 01b */ - buf[off] |= 0x10; - /* DESIGNATOR TYPE == Relative target port identifer */ - buf[off++] |= 0x4; - off++; /* Skip over Reserved */ - buf[off++] = 4; /* DESIGNATOR LENGTH */ - /* Skip over Obsolete field in RTPI payload - * in Table 472 */ - off += 2; - buf[off++] = ((port->sep_rtpi >> 8) & 0xff); - buf[off++] = (port->sep_rtpi & 0xff); - len += 8; /* Header size + Designation descriptor */ - /* - * Target port group identifier, see spc4r17 - * section 7.7.3.8 - * - * Get the PROTOCOL IDENTIFIER as defined by spc4r17 - * section 7.5.1 Table 362 - */ -check_tpgi: - if (T10_ALUA(dev->se_sub_dev)->alua_type != - SPC3_ALUA_EMULATED) - goto check_scsi_name; - - if (((len + 4) + 8) > cmd->data_length) { - len += 8; - goto check_lu_gp; - } - tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; - if (!tg_pt_gp_mem) - goto check_lu_gp; - - spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); - tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; - if (!(tg_pt_gp)) { - spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); - goto check_lu_gp; - } - tg_pt_gp_id = tg_pt_gp->tg_pt_gp_id; - spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); - - buf[off] = - (TPG_TFO(tpg)->get_fabric_proto_ident(tpg) << 4); - buf[off++] |= 0x1; /* CODE SET == Binary */ - buf[off] = 0x80; /* Set PIV=1 */ - /* Set ASSOICATION == target port: 01b */ - buf[off] |= 0x10; - /* DESIGNATOR TYPE == Target port group identifier */ - buf[off++] |= 0x5; - off++; /* Skip over Reserved */ - buf[off++] = 4; /* DESIGNATOR LENGTH */ - off += 2; /* Skip over Reserved Field */ - buf[off++] = ((tg_pt_gp_id >> 8) & 0xff); - buf[off++] = (tg_pt_gp_id & 0xff); - len += 8; /* Header size + Designation descriptor */ - /* - * Logical Unit Group identifier, see spc4r17 - * section 7.7.3.8 - */ -check_lu_gp: - if (((len + 4) + 8) > cmd->data_length) { - len += 8; - goto check_scsi_name; - } - lu_gp_mem = dev->dev_alua_lu_gp_mem; - if (!(lu_gp_mem)) - goto check_scsi_name; - - spin_lock(&lu_gp_mem->lu_gp_mem_lock); - lu_gp = lu_gp_mem->lu_gp; - if (!(lu_gp)) { - spin_unlock(&lu_gp_mem->lu_gp_mem_lock); - goto check_scsi_name; - } - lu_gp_id = lu_gp->lu_gp_id; - spin_unlock(&lu_gp_mem->lu_gp_mem_lock); - - buf[off++] |= 0x1; /* CODE SET == Binary */ - /* DESIGNATOR TYPE == Logical Unit Group identifier */ - buf[off++] |= 0x6; - off++; /* Skip over Reserved */ - buf[off++] = 4; /* DESIGNATOR LENGTH */ - off += 2; /* Skip over Reserved Field */ - buf[off++] = ((lu_gp_id >> 8) & 0xff); - buf[off++] = (lu_gp_id & 0xff); - len += 8; /* Header size + Designation descriptor */ - /* - * SCSI name string designator, see spc4r17 - * section 7.7.3.11 - * - * Get the PROTOCOL IDENTIFIER as defined by spc4r17 - * section 7.5.1 Table 362 - */ -check_scsi_name: - scsi_name_len = strlen(TPG_TFO(tpg)->tpg_get_wwn(tpg)); - /* UTF-8 ",t,0x<16-bit TPGT>" + NULL Terminator */ - scsi_name_len += 10; - /* Check for 4-byte padding */ - padding = ((-scsi_name_len) & 3); - if (padding != 0) - scsi_name_len += padding; - /* Header size + Designation descriptor */ - scsi_name_len += 4; - - if (((len + 4) + scsi_name_len) > cmd->data_length) { - len += scsi_name_len; - goto set_len; - } - buf[off] = - (TPG_TFO(tpg)->get_fabric_proto_ident(tpg) << 4); - buf[off++] |= 0x3; /* CODE SET == UTF-8 */ - buf[off] = 0x80; /* Set PIV=1 */ - /* Set ASSOICATION == target port: 01b */ - buf[off] |= 0x10; - /* DESIGNATOR TYPE == SCSI name string */ - buf[off++] |= 0x8; - off += 2; /* Skip over Reserved and length */ - /* - * SCSI name string identifer containing, $FABRIC_MOD - * dependent information. For LIO-Target and iSCSI - * Target Port, this means "<iSCSI name>,t,0x<TPGT> in - * UTF-8 encoding. - */ - tpgt = TPG_TFO(tpg)->tpg_get_tag(tpg); - scsi_name_len = sprintf(&buf[off], "%s,t,0x%04x", - TPG_TFO(tpg)->tpg_get_wwn(tpg), tpgt); - scsi_name_len += 1 /* Include NULL terminator */; - /* - * The null-terminated, null-padded (see 4.4.2) SCSI - * NAME STRING field contains a UTF-8 format string. - * The number of bytes in the SCSI NAME STRING field - * (i.e., the value in the DESIGNATOR LENGTH field) - * shall be no larger than 256 and shall be a multiple - * of four. - */ - if (padding) - scsi_name_len += padding; - - buf[off-1] = scsi_name_len; - off += scsi_name_len; - /* Header size + Designation descriptor */ - len += (scsi_name_len + 4); - } -set_len: - buf[2] = ((len >> 8) & 0xff); - buf[3] = (len & 0xff); /* Page Length for VPD 0x83 */ - return 0; -} - -/* Extended INQUIRY Data VPD Page */ -static int -target_emulate_evpd_86(struct se_cmd *cmd, unsigned char *buf) -{ - if (cmd->data_length < 60) - return 0; - - buf[1] = 0x86; - buf[2] = 0x3c; - /* Set HEADSUP, ORDSUP, SIMPSUP */ - buf[5] = 0x07; - - /* If WriteCache emulation is enabled, set V_SUP */ - if (DEV_ATTRIB(SE_DEV(cmd))->emulate_write_cache > 0) - buf[6] = 0x01; - return 0; -} - -/* Block Limits VPD page */ -static int -target_emulate_evpd_b0(struct se_cmd *cmd, unsigned char *buf) -{ - struct se_device *dev = SE_DEV(cmd); - int have_tp = 0; - - /* - * Following sbc3r22 section 6.5.3 Block Limits VPD page, when - * emulate_tpu=1 or emulate_tpws=1 we will be expect a - * different page length for Thin Provisioning. - */ - if (DEV_ATTRIB(dev)->emulate_tpu || DEV_ATTRIB(dev)->emulate_tpws) - have_tp = 1; - - if (cmd->data_length < (0x10 + 4)) { - printk(KERN_INFO "Received data_length: %u" - " too small for EVPD 0xb0\n", - cmd->data_length); - return -1; - } - - if (have_tp && cmd->data_length < (0x3c + 4)) { - printk(KERN_INFO "Received data_length: %u" - " too small for TPE=1 EVPD 0xb0\n", - cmd->data_length); - have_tp = 0; - } - - buf[0] = dev->transport->get_device_type(dev); - buf[1] = 0xb0; - buf[3] = have_tp ? 0x3c : 0x10; - - /* - * Set OPTIMAL TRANSFER LENGTH GRANULARITY - */ - put_unaligned_be16(1, &buf[6]); - - /* - * Set MAXIMUM TRANSFER LENGTH - */ - put_unaligned_be32(DEV_ATTRIB(dev)->max_sectors, &buf[8]); - - /* - * Set OPTIMAL TRANSFER LENGTH - */ - put_unaligned_be32(DEV_ATTRIB(dev)->optimal_sectors, &buf[12]); - - /* - * Exit now if we don't support TP or the initiator sent a too - * short buffer. - */ - if (!have_tp || cmd->data_length < (0x3c + 4)) - return 0; - - /* - * Set MAXIMUM UNMAP LBA COUNT - */ - put_unaligned_be32(DEV_ATTRIB(dev)->max_unmap_lba_count, &buf[20]); - - /* - * Set MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT - */ - put_unaligned_be32(DEV_ATTRIB(dev)->max_unmap_block_desc_count, - &buf[24]); - - /* - * Set OPTIMAL UNMAP GRANULARITY - */ - put_unaligned_be32(DEV_ATTRIB(dev)->unmap_granularity, &buf[28]); - - /* - * UNMAP GRANULARITY ALIGNMENT - */ - put_unaligned_be32(DEV_ATTRIB(dev)->unmap_granularity_alignment, - &buf[32]); - if (DEV_ATTRIB(dev)->unmap_granularity_alignment != 0) - buf[32] |= 0x80; /* Set the UGAVALID bit */ - - return 0; -} - -/* Thin Provisioning VPD */ -static int -target_emulate_evpd_b2(struct se_cmd *cmd, unsigned char *buf) -{ - struct se_device *dev = SE_DEV(cmd); - - /* - * From sbc3r22 section 6.5.4 Thin Provisioning VPD page: - * - * The PAGE LENGTH field is defined in SPC-4. If the DP bit is set to - * zero, then the page length shall be set to 0004h. If the DP bit - * is set to one, then the page length shall be set to the value - * defined in table 162. - */ - buf[0] = dev->transport->get_device_type(dev); - buf[1] = 0xb2; - - /* - * Set Hardcoded length mentioned above for DP=0 - */ - put_unaligned_be16(0x0004, &buf[2]); - - /* - * The THRESHOLD EXPONENT field indicates the threshold set size in - * LBAs as a power of 2 (i.e., the threshold set size is equal to - * 2(threshold exponent)). - * - * Note that this is currently set to 0x00 as mkp says it will be - * changing again. We can enable this once it has settled in T10 - * and is actually used by Linux/SCSI ML code. - */ - buf[4] = 0x00; - - /* - * A TPU bit set to one indicates that the device server supports - * the UNMAP command (see 5.25). A TPU bit set to zero indicates - * that the device server does not support the UNMAP command. - */ - if (DEV_ATTRIB(dev)->emulate_tpu != 0) - buf[5] = 0x80; - - /* - * A TPWS bit set to one indicates that the device server supports - * the use of the WRITE SAME (16) command (see 5.42) to unmap LBAs. - * A TPWS bit set to zero indicates that the device server does not - * support the use of the WRITE SAME (16) command to unmap LBAs. - */ - if (DEV_ATTRIB(dev)->emulate_tpws != 0) - buf[5] |= 0x40; - - return 0; -} - -static int -target_emulate_inquiry(struct se_cmd *cmd) -{ - struct se_device *dev = SE_DEV(cmd); - unsigned char *buf = cmd->t_task->t_task_buf; - unsigned char *cdb = cmd->t_task->t_task_cdb; - - if (!(cdb[1] & 0x1)) - return target_emulate_inquiry_std(cmd); - - /* - * Make sure we at least have 4 bytes of INQUIRY response - * payload for 0x00 going back for EVPD=1. Note that 0x80 - * and 0x83 will check for enough payload data length and - * jump to set_len: label when there is not enough inquiry EVPD - * payload length left for the next outgoing EVPD metadata - */ - if (cmd->data_length < 4) { - printk(KERN_ERR "SCSI Inquiry payload length: %u" - " too small for EVPD=1\n", cmd->data_length); - return -1; - } - buf[0] = dev->transport->get_device_type(dev); - - switch (cdb[2]) { - case 0x00: - return target_emulate_evpd_00(cmd, buf); - case 0x80: - return target_emulate_evpd_80(cmd, buf); - case 0x83: - return target_emulate_evpd_83(cmd, buf); - case 0x86: - return target_emulate_evpd_86(cmd, buf); - case 0xb0: - return target_emulate_evpd_b0(cmd, buf); - case 0xb2: - return target_emulate_evpd_b2(cmd, buf); - default: - printk(KERN_ERR "Unknown VPD Code: 0x%02x\n", cdb[2]); - return -1; - } - - return 0; -} - -static int -target_emulate_readcapacity(struct se_cmd *cmd) -{ - struct se_device *dev = SE_DEV(cmd); - unsigned char *buf = cmd->t_task->t_task_buf; - u32 blocks = dev->transport->get_blocks(dev); - - buf[0] = (blocks >> 24) & 0xff; - buf[1] = (blocks >> 16) & 0xff; - buf[2] = (blocks >> 8) & 0xff; - buf[3] = blocks & 0xff; - buf[4] = (DEV_ATTRIB(dev)->block_size >> 24) & 0xff; - buf[5] = (DEV_ATTRIB(dev)->block_size >> 16) & 0xff; - buf[6] = (DEV_ATTRIB(dev)->block_size >> 8) & 0xff; - buf[7] = DEV_ATTRIB(dev)->block_size & 0xff; - /* - * Set max 32-bit blocks to signal SERVICE ACTION READ_CAPACITY_16 - */ - if (DEV_ATTRIB(dev)->emulate_tpu || DEV_ATTRIB(dev)->emulate_tpws) - put_unaligned_be32(0xFFFFFFFF, &buf[0]); - - return 0; -} - -static int -target_emulate_readcapacity_16(struct se_cmd *cmd) -{ - struct se_device *dev = SE_DEV(cmd); - unsigned char *buf = cmd->t_task->t_task_buf; - unsigned long long blocks = dev->transport->get_blocks(dev); - - buf[0] = (blocks >> 56) & 0xff; - buf[1] = (blocks >> 48) & 0xff; - buf[2] = (blocks >> 40) & 0xff; - buf[3] = (blocks >> 32) & 0xff; - buf[4] = (blocks >> 24) & 0xff; - buf[5] = (blocks >> 16) & 0xff; - buf[6] = (blocks >> 8) & 0xff; - buf[7] = blocks & 0xff; - buf[8] = (DEV_ATTRIB(dev)->block_size >> 24) & 0xff; - buf[9] = (DEV_ATTRIB(dev)->block_size >> 16) & 0xff; - buf[10] = (DEV_ATTRIB(dev)->block_size >> 8) & 0xff; - buf[11] = DEV_ATTRIB(dev)->block_size & 0xff; - /* - * Set Thin Provisioning Enable bit following sbc3r22 in section - * READ CAPACITY (16) byte 14 if emulate_tpu or emulate_tpws is enabled. - */ - if (DEV_ATTRIB(dev)->emulate_tpu || DEV_ATTRIB(dev)->emulate_tpws) - buf[14] = 0x80; - - return 0; -} - -static int -target_modesense_rwrecovery(unsigned char *p) -{ - p[0] = 0x01; - p[1] = 0x0a; - - return 12; -} - -static int -target_modesense_control(struct se_device *dev, unsigned char *p) -{ - p[0] = 0x0a; - p[1] = 0x0a; - p[2] = 2; - /* - * From spc4r17, section 7.4.6 Control mode Page - * - * Unit Attention interlocks control (UN_INTLCK_CTRL) to code 00b - * - * 00b: The logical unit shall clear any unit attention condition - * reported in the same I_T_L_Q nexus transaction as a CHECK CONDITION - * status and shall not establish a unit attention condition when a com- - * mand is completed with BUSY, TASK SET FULL, or RESERVATION CONFLICT - * status. - * - * 10b: The logical unit shall not clear any unit attention condition - * reported in the same I_T_L_Q nexus transaction as a CHECK CONDITION - * status and shall not establish a unit attention condition when - * a command is completed with BUSY, TASK SET FULL, or RESERVATION - * CONFLICT status. - * - * 11b a The logical unit shall not clear any unit attention condition - * reported in the same I_T_L_Q nexus transaction as a CHECK CONDITION - * status and shall establish a unit attention condition for the - * initiator port associated with the I_T nexus on which the BUSY, - * TASK SET FULL, or RESERVATION CONFLICT status is being returned. - * Depending on the status, the additional sense code shall be set to - * PREVIOUS BUSY STATUS, PREVIOUS TASK SET FULL STATUS, or PREVIOUS - * RESERVATION CONFLICT STATUS. Until it is cleared by a REQUEST SENSE - * command, a unit attention condition shall be established only once - * for a BUSY, TASK SET FULL, or RESERVATION CONFLICT status regardless - * to the number of commands completed with one of those status codes. - */ - p[4] = (DEV_ATTRIB(dev)->emulate_ua_intlck_ctrl == 2) ? 0x30 : - (DEV_ATTRIB(dev)->emulate_ua_intlck_ctrl == 1) ? 0x20 : 0x00; - /* - * From spc4r17, section 7.4.6 Control mode Page - * - * Task Aborted Status (TAS) bit set to zero. - * - * A task aborted status (TAS) bit set to zero specifies that aborted - * tasks shall be terminated by the device server without any response - * to the application client. A TAS bit set to one specifies that tasks - * aborted by the actions of an I_T nexus other than the I_T nexus on - * which the command was received shall be completed with TASK ABORTED - * status (see SAM-4). - */ - p[5] = (DEV_ATTRIB(dev)->emulate_tas) ? 0x40 : 0x00; - p[8] = 0xff; - p[9] = 0xff; - p[11] = 30; - - return 12; -} - -static int -target_modesense_caching(struct se_device *dev, unsigned char *p) -{ - p[0] = 0x08; - p[1] = 0x12; - if (DEV_ATTRIB(dev)->emulate_write_cache > 0) - p[2] = 0x04; /* Write Cache Enable */ - p[12] = 0x20; /* Disabled Read Ahead */ - - return 20; -} - -static void -target_modesense_write_protect(unsigned char *buf, int type) -{ - /* - * I believe that the WP bit (bit 7) in the mode header is the same for - * all device types.. - */ - switch (type) { - case TYPE_DISK: - case TYPE_TAPE: - default: - buf[0] |= 0x80; /* WP bit */ - break; - } -} - -static void -target_modesense_dpofua(unsigned char *buf, int type) -{ - switch (type) { - case TYPE_DISK: - buf[0] |= 0x10; /* DPOFUA bit */ - break; - default: - break; - } -} - -static int -target_emulate_modesense(struct se_cmd *cmd, int ten) -{ - struct se_device *dev = SE_DEV(cmd); - char *cdb = cmd->t_task->t_task_cdb; - unsigned char *rbuf = cmd->t_task->t_task_buf; - int type = dev->transport->get_device_type(dev); - int offset = (ten) ? 8 : 4; - int length = 0; - unsigned char buf[SE_MODE_PAGE_BUF]; - - memset(buf, 0, SE_MODE_PAGE_BUF); - - switch (cdb[2] & 0x3f) { - case 0x01: - length = target_modesense_rwrecovery(&buf[offset]); - break; - case 0x08: - length = target_modesense_caching(dev, &buf[offset]); - break; - case 0x0a: - length = target_modesense_control(dev, &buf[offset]); - break; - case 0x3f: - length = target_modesense_rwrecovery(&buf[offset]); - length += target_modesense_caching(dev, &buf[offset+length]); - length += target_modesense_control(dev, &buf[offset+length]); - break; - default: - printk(KERN_ERR "Got Unknown Mode Page: 0x%02x\n", - cdb[2] & 0x3f); - return PYX_TRANSPORT_UNKNOWN_MODE_PAGE; - } - offset += length; - - if (ten) { - offset -= 2; - buf[0] = (offset >> 8) & 0xff; - buf[1] = offset & 0xff; - - if ((SE_LUN(cmd)->lun_access & TRANSPORT_LUNFLAGS_READ_ONLY) || - (cmd->se_deve && - (cmd->se_deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY))) - target_modesense_write_protect(&buf[3], type); - - if ((DEV_ATTRIB(dev)->emulate_write_cache > 0) && - (DEV_ATTRIB(dev)->emulate_fua_write > 0)) - target_modesense_dpofua(&buf[3], type); - - if ((offset + 2) > cmd->data_length) - offset = cmd->data_length; - - } else { - offset -= 1; - buf[0] = offset & 0xff; - - if ((SE_LUN(cmd)->lun_access & TRANSPORT_LUNFLAGS_READ_ONLY) || - (cmd->se_deve && - (cmd->se_deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY))) - target_modesense_write_protect(&buf[2], type); - - if ((DEV_ATTRIB(dev)->emulate_write_cache > 0) && - (DEV_ATTRIB(dev)->emulate_fua_write > 0)) - target_modesense_dpofua(&buf[2], type); - - if ((offset + 1) > cmd->data_length) - offset = cmd->data_length; - } - memcpy(rbuf, buf, offset); - - return 0; -} - -static int -target_emulate_request_sense(struct se_cmd *cmd) -{ - unsigned char *cdb = cmd->t_task->t_task_cdb; - unsigned char *buf = cmd->t_task->t_task_buf; - u8 ua_asc = 0, ua_ascq = 0; - - if (cdb[1] & 0x01) { - printk(KERN_ERR "REQUEST_SENSE description emulation not" - " supported\n"); - return PYX_TRANSPORT_INVALID_CDB_FIELD; - } - if (!(core_scsi3_ua_clear_for_request_sense(cmd, &ua_asc, &ua_ascq))) { - /* - * CURRENT ERROR, UNIT ATTENTION - */ - buf[0] = 0x70; - buf[SPC_SENSE_KEY_OFFSET] = UNIT_ATTENTION; - /* - * Make sure request data length is enough for additional - * sense data. - */ - if (cmd->data_length <= 18) { - buf[7] = 0x00; - return 0; - } - /* - * The Additional Sense Code (ASC) from the UNIT ATTENTION - */ - buf[SPC_ASC_KEY_OFFSET] = ua_asc; - buf[SPC_ASCQ_KEY_OFFSET] = ua_ascq; - buf[7] = 0x0A; - } else { - /* - * CURRENT ERROR, NO SENSE - */ - buf[0] = 0x70; - buf[SPC_SENSE_KEY_OFFSET] = NO_SENSE; - /* - * Make sure request data length is enough for additional - * sense data. - */ - if (cmd->data_length <= 18) { - buf[7] = 0x00; - return 0; - } - /* - * NO ADDITIONAL SENSE INFORMATION - */ - buf[SPC_ASC_KEY_OFFSET] = 0x00; - buf[7] = 0x0A; - } - - return 0; -} - -/* - * Used for TCM/IBLOCK and TCM/FILEIO for block/blk-lib.c level discard support. - * Note this is not used for TCM/pSCSI passthrough - */ -static int -target_emulate_unmap(struct se_task *task) -{ - struct se_cmd *cmd = TASK_CMD(task); - struct se_device *dev = SE_DEV(cmd); - unsigned char *buf = cmd->t_task->t_task_buf, *ptr = NULL; - unsigned char *cdb = &cmd->t_task->t_task_cdb[0]; - sector_t lba; - unsigned int size = cmd->data_length, range; - int ret, offset; - unsigned short dl, bd_dl; - - /* First UNMAP block descriptor starts at 8 byte offset */ - offset = 8; - size -= 8; - dl = get_unaligned_be16(&cdb[0]); - bd_dl = get_unaligned_be16(&cdb[2]); - ptr = &buf[offset]; - printk(KERN_INFO "UNMAP: Sub: %s Using dl: %hu bd_dl: %hu size: %hu" - " ptr: %p\n", dev->transport->name, dl, bd_dl, size, ptr); - - while (size) { - lba = get_unaligned_be64(&ptr[0]); - range = get_unaligned_be32(&ptr[8]); - printk(KERN_INFO "UNMAP: Using lba: %llu and range: %u\n", - (unsigned long long)lba, range); - - ret = dev->transport->do_discard(dev, lba, range); - if (ret < 0) { - printk(KERN_ERR "blkdev_issue_discard() failed: %d\n", - ret); - return -1; - } - - ptr += 16; - size -= 16; - } - - task->task_scsi_status = GOOD; - transport_complete_task(task, 1); - return 0; -} - -/* - * Used for TCM/IBLOCK and TCM/FILEIO for block/blk-lib.c level discard support. - * Note this is not used for TCM/pSCSI passthrough - */ -static int -target_emulate_write_same(struct se_task *task) -{ - struct se_cmd *cmd = TASK_CMD(task); - struct se_device *dev = SE_DEV(cmd); - sector_t lba = cmd->t_task->t_task_lba; - unsigned int range; - int ret; - - range = (cmd->data_length / DEV_ATTRIB(dev)->block_size); - - printk(KERN_INFO "WRITE_SAME UNMAP: LBA: %llu Range: %u\n", - (unsigned long long)lba, range); - - ret = dev->transport->do_discard(dev, lba, range); - if (ret < 0) { - printk(KERN_INFO "blkdev_issue_discard() failed for WRITE_SAME\n"); - return -1; - } - - task->task_scsi_status = GOOD; - transport_complete_task(task, 1); - return 0; -} - -int -transport_emulate_control_cdb(struct se_task *task) -{ - struct se_cmd *cmd = TASK_CMD(task); - struct se_device *dev = SE_DEV(cmd); - unsigned short service_action; - int ret = 0; - - switch (cmd->t_task->t_task_cdb[0]) { - case INQUIRY: - ret = target_emulate_inquiry(cmd); - break; - case READ_CAPACITY: - ret = target_emulate_readcapacity(cmd); - break; - case MODE_SENSE: - ret = target_emulate_modesense(cmd, 0); - break; - case MODE_SENSE_10: - ret = target_emulate_modesense(cmd, 1); - break; - case SERVICE_ACTION_IN: - switch (cmd->t_task->t_task_cdb[1] & 0x1f) { - case SAI_READ_CAPACITY_16: - ret = target_emulate_readcapacity_16(cmd); - break; - default: - printk(KERN_ERR "Unsupported SA: 0x%02x\n", - cmd->t_task->t_task_cdb[1] & 0x1f); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - } - break; - case REQUEST_SENSE: - ret = target_emulate_request_sense(cmd); - break; - case UNMAP: - if (!dev->transport->do_discard) { - printk(KERN_ERR "UNMAP emulation not supported for: %s\n", - dev->transport->name); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - } - ret = target_emulate_unmap(task); - break; - case WRITE_SAME_16: - if (!dev->transport->do_discard) { - printk(KERN_ERR "WRITE_SAME_16 emulation not supported" - " for: %s\n", dev->transport->name); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - } - ret = target_emulate_write_same(task); - break; - case VARIABLE_LENGTH_CMD: - service_action = - get_unaligned_be16(&cmd->t_task->t_task_cdb[8]); - switch (service_action) { - case WRITE_SAME_32: - if (!dev->transport->do_discard) { - printk(KERN_ERR "WRITE_SAME_32 SA emulation not" - " supported for: %s\n", - dev->transport->name); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - } - ret = target_emulate_write_same(task); - break; - default: - printk(KERN_ERR "Unsupported VARIABLE_LENGTH_CMD SA:" - " 0x%02x\n", service_action); - break; - } - break; - case SYNCHRONIZE_CACHE: - case 0x91: /* SYNCHRONIZE_CACHE_16: */ - if (!dev->transport->do_sync_cache) { - printk(KERN_ERR - "SYNCHRONIZE_CACHE emulation not supported" - " for: %s\n", dev->transport->name); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - } - dev->transport->do_sync_cache(task); - break; - case ALLOW_MEDIUM_REMOVAL: - case ERASE: - case REZERO_UNIT: - case SEEK_10: - case SPACE: - case START_STOP: - case TEST_UNIT_READY: - case VERIFY: - case WRITE_FILEMARKS: - break; - default: - printk(KERN_ERR "Unsupported SCSI Opcode: 0x%02x for %s\n", - cmd->t_task->t_task_cdb[0], dev->transport->name); - return PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - } - - if (ret < 0) - return ret; - task->task_scsi_status = GOOD; - transport_complete_task(task, 1); - - return PYX_TRANSPORT_SENT_TO_TRANSPORT; -} diff --git a/drivers/target/target_core_configfs.c b/drivers/target/target_core_configfs.c index caf8dc18ee0..bf55c5a04cf 100644 --- a/drivers/target/target_core_configfs.c +++ b/drivers/target/target_core_configfs.c @@ -3,8 +3,7 @@ * * This file contains ConfigFS logic for the Generic Target Engine project. * - * Copyright (c) 2008-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2008-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -23,7 +22,6 @@ #include <linux/module.h> #include <linux/moduleparam.h> -#include <linux/version.h> #include <generated/utsrelease.h> #include <linux/utsname.h> #include <linux/init.h> @@ -37,22 +35,25 @@ #include <linux/parser.h> #include <linux/syscalls.h> #include <linux/configfs.h> +#include <linux/spinlock.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> #include <target/target_core_fabric_configfs.h> #include <target/target_core_configfs.h> #include <target/configfs_macros.h> +#include "target_core_internal.h" #include "target_core_alua.h" -#include "target_core_hba.h" #include "target_core_pr.h" #include "target_core_rd.h" +#include "target_core_xcopy.h" -static struct list_head g_tf_list; -static struct mutex g_tf_lock; +extern struct t10_alua_lu_gp *default_lu_gp; + +static LIST_HEAD(g_tf_list); +static DEFINE_MUTEX(g_tf_lock); struct target_core_configfs_attribute { struct configfs_attribute attr; @@ -60,6 +61,10 @@ struct target_core_configfs_attribute { ssize_t (*store)(void *, const char *, size_t); }; +static struct config_group target_core_hbagroup; +static struct config_group alua_group; +static struct config_group alua_lu_gps_group; + static inline struct se_hba * item_to_hba(struct config_item *item) { @@ -93,12 +98,12 @@ static struct target_fabric_configfs *target_core_get_fabric( { struct target_fabric_configfs *tf; - if (!(name)) + if (!name) return NULL; mutex_lock(&g_tf_lock); list_for_each_entry(tf, &g_tf_list, tf_list) { - if (!(strcmp(tf->tf_name, name))) { + if (!strcmp(tf->tf_name, name)) { atomic_inc(&tf->tf_access_cnt); mutex_unlock(&g_tf_lock); return tf; @@ -119,17 +124,9 @@ static struct config_group *target_core_register_fabric( struct target_fabric_configfs *tf; int ret; - printk(KERN_INFO "Target_Core_ConfigFS: REGISTER -> group: %p name:" + pr_debug("Target_Core_ConfigFS: REGISTER -> group: %p name:" " %s\n", group, name); /* - * Ensure that TCM subsystem plugins are loaded at this point for - * using the RAMDISK_DR virtual LUN 0 and all other struct se_port - * LUN symlinks. - */ - if (transport_subsystem_check_init() < 0) - return ERR_PTR(-EINVAL); - - /* * Below are some hardcoded request_module() calls to automatically * local fabric modules when the following is called: * @@ -139,7 +136,7 @@ static struct config_group *target_core_register_fabric( * registered, but simply provids auto loading logic for modules with * mkdir(2) system calls with known TCM fabric modules. */ - if (!(strncmp(name, "iscsi", 5))) { + if (!strncmp(name, "iscsi", 5)) { /* * Automatically load the LIO Target fabric module when the * following is called: @@ -148,11 +145,11 @@ static struct config_group *target_core_register_fabric( */ ret = request_module("iscsi_target_mod"); if (ret < 0) { - printk(KERN_ERR "request_module() failed for" + pr_err("request_module() failed for" " iscsi_target_mod.ko: %d\n", ret); return ERR_PTR(-EINVAL); } - } else if (!(strncmp(name, "loopback", 8))) { + } else if (!strncmp(name, "loopback", 8)) { /* * Automatically load the tcm_loop fabric module when the * following is called: @@ -161,44 +158,44 @@ static struct config_group *target_core_register_fabric( */ ret = request_module("tcm_loop"); if (ret < 0) { - printk(KERN_ERR "request_module() failed for" + pr_err("request_module() failed for" " tcm_loop.ko: %d\n", ret); return ERR_PTR(-EINVAL); } } tf = target_core_get_fabric(name); - if (!(tf)) { - printk(KERN_ERR "target_core_get_fabric() failed for %s\n", + if (!tf) { + pr_err("target_core_get_fabric() failed for %s\n", name); return ERR_PTR(-EINVAL); } - printk(KERN_INFO "Target_Core_ConfigFS: REGISTER -> Located fabric:" + pr_debug("Target_Core_ConfigFS: REGISTER -> Located fabric:" " %s\n", tf->tf_name); /* * On a successful target_core_get_fabric() look, the returned * struct target_fabric_configfs *tf will contain a usage reference. */ - printk(KERN_INFO "Target_Core_ConfigFS: REGISTER tfc_wwn_cit -> %p\n", - &TF_CIT_TMPL(tf)->tfc_wwn_cit); + pr_debug("Target_Core_ConfigFS: REGISTER tfc_wwn_cit -> %p\n", + &tf->tf_cit_tmpl.tfc_wwn_cit); tf->tf_group.default_groups = tf->tf_default_groups; tf->tf_group.default_groups[0] = &tf->tf_disc_group; tf->tf_group.default_groups[1] = NULL; config_group_init_type_name(&tf->tf_group, name, - &TF_CIT_TMPL(tf)->tfc_wwn_cit); + &tf->tf_cit_tmpl.tfc_wwn_cit); config_group_init_type_name(&tf->tf_disc_group, "discovery_auth", - &TF_CIT_TMPL(tf)->tfc_discovery_cit); + &tf->tf_cit_tmpl.tfc_discovery_cit); - printk(KERN_INFO "Target_Core_ConfigFS: REGISTER -> Allocated Fabric:" + pr_debug("Target_Core_ConfigFS: REGISTER -> Allocated Fabric:" " %s\n", tf->tf_group.cg_item.ci_name); /* * Setup tf_ops.tf_subsys pointer for usage with configfs_depend_item() */ tf->tf_ops.tf_subsys = tf->tf_subsys; tf->tf_fabric = &tf->tf_group.cg_item; - printk(KERN_INFO "Target_Core_ConfigFS: REGISTER -> Set tf->tf_fabric" + pr_debug("Target_Core_ConfigFS: REGISTER -> Set tf->tf_fabric" " for %s\n", name); return &tf->tf_group; @@ -217,18 +214,18 @@ static void target_core_deregister_fabric( struct config_item *df_item; int i; - printk(KERN_INFO "Target_Core_ConfigFS: DEREGISTER -> Looking up %s in" + pr_debug("Target_Core_ConfigFS: DEREGISTER -> Looking up %s in" " tf list\n", config_item_name(item)); - printk(KERN_INFO "Target_Core_ConfigFS: DEREGISTER -> located fabric:" + pr_debug("Target_Core_ConfigFS: DEREGISTER -> located fabric:" " %s\n", tf->tf_name); atomic_dec(&tf->tf_access_cnt); - printk(KERN_INFO "Target_Core_ConfigFS: DEREGISTER -> Releasing" + pr_debug("Target_Core_ConfigFS: DEREGISTER -> Releasing" " tf->tf_fabric for %s\n", tf->tf_name); tf->tf_fabric = NULL; - printk(KERN_INFO "Target_Core_ConfigFS: DEREGISTER -> Releasing ci" + pr_debug("Target_Core_ConfigFS: DEREGISTER -> Releasing ci" " %s\n", config_item_name(item)); tf_group = &tf->tf_group; @@ -272,7 +269,7 @@ static struct configfs_subsystem target_core_fabrics = { }, }; -static struct configfs_subsystem *target_core_subsystem[] = { +struct configfs_subsystem *target_core_subsystem[] = { &target_core_fabrics, NULL, }; @@ -295,22 +292,18 @@ struct target_fabric_configfs *target_fabric_configfs_init( { struct target_fabric_configfs *tf; - if (!(fabric_mod)) { - printk(KERN_ERR "Missing struct module *fabric_mod pointer\n"); - return NULL; - } if (!(name)) { - printk(KERN_ERR "Unable to locate passed fabric name\n"); - return NULL; + pr_err("Unable to locate passed fabric name\n"); + return ERR_PTR(-EINVAL); } - if (strlen(name) > TARGET_FABRIC_NAME_SIZE) { - printk(KERN_ERR "Passed name: %s exceeds TARGET_FABRIC" + if (strlen(name) >= TARGET_FABRIC_NAME_SIZE) { + pr_err("Passed name: %s exceeds TARGET_FABRIC" "_NAME_SIZE\n", name); - return NULL; + return ERR_PTR(-EINVAL); } tf = kzalloc(sizeof(struct target_fabric_configfs), GFP_KERNEL); - if (!(tf)) + if (!tf) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&tf->tf_list); @@ -329,9 +322,9 @@ struct target_fabric_configfs *target_fabric_configfs_init( list_add_tail(&tf->tf_list, &g_tf_list); mutex_unlock(&g_tf_lock); - printk(KERN_INFO "<<<<<<<<<<<<<<<<<<<<<< BEGIN FABRIC API >>>>>>>>" + pr_debug("<<<<<<<<<<<<<<<<<<<<<< BEGIN FABRIC API >>>>>>>>" ">>>>>>>>>>>>>>\n"); - printk(KERN_INFO "Initialized struct target_fabric_configfs: %p for" + pr_debug("Initialized struct target_fabric_configfs: %p for" " %s\n", tf, tf->tf_name); return tf; } @@ -360,144 +353,112 @@ static int target_fabric_tf_ops_check( { struct target_core_fabric_ops *tfo = &tf->tf_ops; - if (!(tfo->get_fabric_name)) { - printk(KERN_ERR "Missing tfo->get_fabric_name()\n"); + if (!tfo->get_fabric_name) { + pr_err("Missing tfo->get_fabric_name()\n"); return -EINVAL; } - if (!(tfo->get_fabric_proto_ident)) { - printk(KERN_ERR "Missing tfo->get_fabric_proto_ident()\n"); + if (!tfo->get_fabric_proto_ident) { + pr_err("Missing tfo->get_fabric_proto_ident()\n"); return -EINVAL; } - if (!(tfo->tpg_get_wwn)) { - printk(KERN_ERR "Missing tfo->tpg_get_wwn()\n"); + if (!tfo->tpg_get_wwn) { + pr_err("Missing tfo->tpg_get_wwn()\n"); return -EINVAL; } - if (!(tfo->tpg_get_tag)) { - printk(KERN_ERR "Missing tfo->tpg_get_tag()\n"); + if (!tfo->tpg_get_tag) { + pr_err("Missing tfo->tpg_get_tag()\n"); return -EINVAL; } - if (!(tfo->tpg_get_default_depth)) { - printk(KERN_ERR "Missing tfo->tpg_get_default_depth()\n"); + if (!tfo->tpg_get_default_depth) { + pr_err("Missing tfo->tpg_get_default_depth()\n"); return -EINVAL; } - if (!(tfo->tpg_get_pr_transport_id)) { - printk(KERN_ERR "Missing tfo->tpg_get_pr_transport_id()\n"); + if (!tfo->tpg_get_pr_transport_id) { + pr_err("Missing tfo->tpg_get_pr_transport_id()\n"); return -EINVAL; } - if (!(tfo->tpg_get_pr_transport_id_len)) { - printk(KERN_ERR "Missing tfo->tpg_get_pr_transport_id_len()\n"); + if (!tfo->tpg_get_pr_transport_id_len) { + pr_err("Missing tfo->tpg_get_pr_transport_id_len()\n"); return -EINVAL; } - if (!(tfo->tpg_check_demo_mode)) { - printk(KERN_ERR "Missing tfo->tpg_check_demo_mode()\n"); + if (!tfo->tpg_check_demo_mode) { + pr_err("Missing tfo->tpg_check_demo_mode()\n"); return -EINVAL; } - if (!(tfo->tpg_check_demo_mode_cache)) { - printk(KERN_ERR "Missing tfo->tpg_check_demo_mode_cache()\n"); + if (!tfo->tpg_check_demo_mode_cache) { + pr_err("Missing tfo->tpg_check_demo_mode_cache()\n"); return -EINVAL; } - if (!(tfo->tpg_check_demo_mode_write_protect)) { - printk(KERN_ERR "Missing tfo->tpg_check_demo_mode_write_protect()\n"); + if (!tfo->tpg_check_demo_mode_write_protect) { + pr_err("Missing tfo->tpg_check_demo_mode_write_protect()\n"); return -EINVAL; } - if (!(tfo->tpg_check_prod_mode_write_protect)) { - printk(KERN_ERR "Missing tfo->tpg_check_prod_mode_write_protect()\n"); + if (!tfo->tpg_check_prod_mode_write_protect) { + pr_err("Missing tfo->tpg_check_prod_mode_write_protect()\n"); return -EINVAL; } - if (!(tfo->tpg_alloc_fabric_acl)) { - printk(KERN_ERR "Missing tfo->tpg_alloc_fabric_acl()\n"); + if (!tfo->tpg_alloc_fabric_acl) { + pr_err("Missing tfo->tpg_alloc_fabric_acl()\n"); return -EINVAL; } - if (!(tfo->tpg_release_fabric_acl)) { - printk(KERN_ERR "Missing tfo->tpg_release_fabric_acl()\n"); + if (!tfo->tpg_release_fabric_acl) { + pr_err("Missing tfo->tpg_release_fabric_acl()\n"); return -EINVAL; } - if (!(tfo->tpg_get_inst_index)) { - printk(KERN_ERR "Missing tfo->tpg_get_inst_index()\n"); + if (!tfo->tpg_get_inst_index) { + pr_err("Missing tfo->tpg_get_inst_index()\n"); return -EINVAL; } - if (!(tfo->release_cmd_to_pool)) { - printk(KERN_ERR "Missing tfo->release_cmd_to_pool()\n"); + if (!tfo->release_cmd) { + pr_err("Missing tfo->release_cmd()\n"); return -EINVAL; } - if (!(tfo->release_cmd_direct)) { - printk(KERN_ERR "Missing tfo->release_cmd_direct()\n"); + if (!tfo->shutdown_session) { + pr_err("Missing tfo->shutdown_session()\n"); return -EINVAL; } - if (!(tfo->shutdown_session)) { - printk(KERN_ERR "Missing tfo->shutdown_session()\n"); + if (!tfo->close_session) { + pr_err("Missing tfo->close_session()\n"); return -EINVAL; } - if (!(tfo->close_session)) { - printk(KERN_ERR "Missing tfo->close_session()\n"); + if (!tfo->sess_get_index) { + pr_err("Missing tfo->sess_get_index()\n"); return -EINVAL; } - if (!(tfo->stop_session)) { - printk(KERN_ERR "Missing tfo->stop_session()\n"); + if (!tfo->write_pending) { + pr_err("Missing tfo->write_pending()\n"); return -EINVAL; } - if (!(tfo->fall_back_to_erl0)) { - printk(KERN_ERR "Missing tfo->fall_back_to_erl0()\n"); + if (!tfo->write_pending_status) { + pr_err("Missing tfo->write_pending_status()\n"); return -EINVAL; } - if (!(tfo->sess_logged_in)) { - printk(KERN_ERR "Missing tfo->sess_logged_in()\n"); + if (!tfo->set_default_node_attributes) { + pr_err("Missing tfo->set_default_node_attributes()\n"); return -EINVAL; } - if (!(tfo->sess_get_index)) { - printk(KERN_ERR "Missing tfo->sess_get_index()\n"); + if (!tfo->get_task_tag) { + pr_err("Missing tfo->get_task_tag()\n"); return -EINVAL; } - if (!(tfo->write_pending)) { - printk(KERN_ERR "Missing tfo->write_pending()\n"); + if (!tfo->get_cmd_state) { + pr_err("Missing tfo->get_cmd_state()\n"); return -EINVAL; } - if (!(tfo->write_pending_status)) { - printk(KERN_ERR "Missing tfo->write_pending_status()\n"); + if (!tfo->queue_data_in) { + pr_err("Missing tfo->queue_data_in()\n"); return -EINVAL; } - if (!(tfo->set_default_node_attributes)) { - printk(KERN_ERR "Missing tfo->set_default_node_attributes()\n"); + if (!tfo->queue_status) { + pr_err("Missing tfo->queue_status()\n"); return -EINVAL; } - if (!(tfo->get_task_tag)) { - printk(KERN_ERR "Missing tfo->get_task_tag()\n"); + if (!tfo->queue_tm_rsp) { + pr_err("Missing tfo->queue_tm_rsp()\n"); return -EINVAL; } - if (!(tfo->get_cmd_state)) { - printk(KERN_ERR "Missing tfo->get_cmd_state()\n"); - return -EINVAL; - } - if (!(tfo->new_cmd_failure)) { - printk(KERN_ERR "Missing tfo->new_cmd_failure()\n"); - return -EINVAL; - } - if (!(tfo->queue_data_in)) { - printk(KERN_ERR "Missing tfo->queue_data_in()\n"); - return -EINVAL; - } - if (!(tfo->queue_status)) { - printk(KERN_ERR "Missing tfo->queue_status()\n"); - return -EINVAL; - } - if (!(tfo->queue_tm_rsp)) { - printk(KERN_ERR "Missing tfo->queue_tm_rsp()\n"); - return -EINVAL; - } - if (!(tfo->set_fabric_sense_len)) { - printk(KERN_ERR "Missing tfo->set_fabric_sense_len()\n"); - return -EINVAL; - } - if (!(tfo->get_fabric_sense_len)) { - printk(KERN_ERR "Missing tfo->get_fabric_sense_len()\n"); - return -EINVAL; - } - if (!(tfo->is_state_remove)) { - printk(KERN_ERR "Missing tfo->is_state_remove()\n"); - return -EINVAL; - } - if (!(tfo->pack_lun)) { - printk(KERN_ERR "Missing tfo->pack_lun()\n"); + if (!tfo->aborted_task) { + pr_err("Missing tfo->aborted_task()\n"); return -EINVAL; } /* @@ -505,20 +466,20 @@ static int target_fabric_tf_ops_check( * tfo->fabric_make_tpg() and tfo->fabric_drop_tpg() in * target_core_fabric_configfs.c WWN+TPG group context code. */ - if (!(tfo->fabric_make_wwn)) { - printk(KERN_ERR "Missing tfo->fabric_make_wwn()\n"); + if (!tfo->fabric_make_wwn) { + pr_err("Missing tfo->fabric_make_wwn()\n"); return -EINVAL; } - if (!(tfo->fabric_drop_wwn)) { - printk(KERN_ERR "Missing tfo->fabric_drop_wwn()\n"); + if (!tfo->fabric_drop_wwn) { + pr_err("Missing tfo->fabric_drop_wwn()\n"); return -EINVAL; } - if (!(tfo->fabric_make_tpg)) { - printk(KERN_ERR "Missing tfo->fabric_make_tpg()\n"); + if (!tfo->fabric_make_tpg) { + pr_err("Missing tfo->fabric_make_tpg()\n"); return -EINVAL; } - if (!(tfo->fabric_drop_tpg)) { - printk(KERN_ERR "Missing tfo->fabric_drop_tpg()\n"); + if (!tfo->fabric_drop_tpg) { + pr_err("Missing tfo->fabric_drop_tpg()\n"); return -EINVAL; } @@ -536,22 +497,15 @@ static int target_fabric_tf_ops_check( int target_fabric_configfs_register( struct target_fabric_configfs *tf) { - struct config_group *su_group; int ret; - if (!(tf)) { - printk(KERN_ERR "Unable to locate target_fabric_configfs" - " pointer\n"); - return -EINVAL; - } - if (!(tf->tf_subsys)) { - printk(KERN_ERR "Unable to target struct config_subsystem" + if (!tf) { + pr_err("Unable to locate target_fabric_configfs" " pointer\n"); return -EINVAL; } - su_group = &tf->tf_subsys->su_group; - if (!(su_group)) { - printk(KERN_ERR "Unable to locate target struct config_group" + if (!tf->tf_subsys) { + pr_err("Unable to target struct config_subsystem" " pointer\n"); return -EINVAL; } @@ -559,7 +513,7 @@ int target_fabric_configfs_register( if (ret < 0) return ret; - printk(KERN_INFO "<<<<<<<<<<<<<<<<<<<<<< END FABRIC API >>>>>>>>>>>>" + pr_debug("<<<<<<<<<<<<<<<<<<<<<< END FABRIC API >>>>>>>>>>>>" ">>>>>>>>>>\n"); return 0; } @@ -568,48 +522,39 @@ EXPORT_SYMBOL(target_fabric_configfs_register); void target_fabric_configfs_deregister( struct target_fabric_configfs *tf) { - struct config_group *su_group; struct configfs_subsystem *su; - if (!(tf)) { - printk(KERN_ERR "Unable to locate passed target_fabric_" + if (!tf) { + pr_err("Unable to locate passed target_fabric_" "configfs\n"); return; } su = tf->tf_subsys; - if (!(su)) { - printk(KERN_ERR "Unable to locate passed tf->tf_subsys" + if (!su) { + pr_err("Unable to locate passed tf->tf_subsys" " pointer\n"); return; } - su_group = &tf->tf_subsys->su_group; - if (!(su_group)) { - printk(KERN_ERR "Unable to locate target struct config_group" - " pointer\n"); - return; - } - - printk(KERN_INFO "<<<<<<<<<<<<<<<<<<<<<< BEGIN FABRIC API >>>>>>>>>>" + pr_debug("<<<<<<<<<<<<<<<<<<<<<< BEGIN FABRIC API >>>>>>>>>>" ">>>>>>>>>>>>\n"); mutex_lock(&g_tf_lock); if (atomic_read(&tf->tf_access_cnt)) { mutex_unlock(&g_tf_lock); - printk(KERN_ERR "Non zero tf->tf_access_cnt for fabric %s\n", + pr_err("Non zero tf->tf_access_cnt for fabric %s\n", tf->tf_name); BUG(); } list_del(&tf->tf_list); mutex_unlock(&g_tf_lock); - printk(KERN_INFO "Target_Core_ConfigFS: DEREGISTER -> Releasing tf:" + pr_debug("Target_Core_ConfigFS: DEREGISTER -> Releasing tf:" " %s\n", tf->tf_name); tf->tf_module = NULL; tf->tf_subsys = NULL; kfree(tf); - printk("<<<<<<<<<<<<<<<<<<<<<< END FABRIC API >>>>>>>>>>>>>>>>>" + pr_debug("<<<<<<<<<<<<<<<<<<<<<< END FABRIC API >>>>>>>>>>>>>>>>>" ">>>>>\n"); - return; } EXPORT_SYMBOL(target_fabric_configfs_deregister); @@ -624,20 +569,8 @@ static ssize_t target_core_dev_show_attr_##_name( \ struct se_dev_attrib *da, \ char *page) \ { \ - struct se_device *dev; \ - struct se_subsystem_dev *se_dev = da->da_sub_dev; \ - ssize_t rb; \ - \ - spin_lock(&se_dev->se_dev_lock); \ - dev = se_dev->se_dev_ptr; \ - if (!(dev)) { \ - spin_unlock(&se_dev->se_dev_lock); \ - return -ENODEV; \ - } \ - rb = snprintf(page, PAGE_SIZE, "%u\n", (u32)DEV_ATTRIB(dev)->_name); \ - spin_unlock(&se_dev->se_dev_lock); \ - \ - return rb; \ + return snprintf(page, PAGE_SIZE, "%u\n", \ + (u32)da->da_dev->dev_attrib._name); \ } #define DEF_DEV_ATTRIB_STORE(_name) \ @@ -646,26 +579,16 @@ static ssize_t target_core_dev_store_attr_##_name( \ const char *page, \ size_t count) \ { \ - struct se_device *dev; \ - struct se_subsystem_dev *se_dev = da->da_sub_dev; \ unsigned long val; \ int ret; \ \ - spin_lock(&se_dev->se_dev_lock); \ - dev = se_dev->se_dev_ptr; \ - if (!(dev)) { \ - spin_unlock(&se_dev->se_dev_lock); \ - return -ENODEV; \ - } \ - ret = strict_strtoul(page, 0, &val); \ + ret = kstrtoul(page, 0, &val); \ if (ret < 0) { \ - spin_unlock(&se_dev->se_dev_lock); \ - printk(KERN_ERR "strict_strtoul() failed with" \ + pr_err("kstrtoul() failed with" \ " ret: %d\n", ret); \ return -EINVAL; \ } \ - ret = se_dev_set_##_name(dev, (u32)val); \ - spin_unlock(&se_dev->se_dev_lock); \ + ret = se_dev_set_##_name(da->da_dev, (u32)val); \ \ return (!ret) ? count : -EINVAL; \ } @@ -691,6 +614,9 @@ static struct target_core_dev_attrib_attribute \ __CONFIGFS_EATTR_RO(_name, \ target_core_dev_show_attr_##_name); +DEF_DEV_ATTRIB(emulate_model_alias); +SE_DEV_ATTR(emulate_model_alias, S_IRUGO | S_IWUSR); + DEF_DEV_ATTRIB(emulate_dpo); SE_DEV_ATTR(emulate_dpo, S_IRUGO | S_IWUSR); @@ -715,9 +641,30 @@ SE_DEV_ATTR(emulate_tpu, S_IRUGO | S_IWUSR); DEF_DEV_ATTRIB(emulate_tpws); SE_DEV_ATTR(emulate_tpws, S_IRUGO | S_IWUSR); +DEF_DEV_ATTRIB(emulate_caw); +SE_DEV_ATTR(emulate_caw, S_IRUGO | S_IWUSR); + +DEF_DEV_ATTRIB(emulate_3pc); +SE_DEV_ATTR(emulate_3pc, S_IRUGO | S_IWUSR); + +DEF_DEV_ATTRIB(pi_prot_type); +SE_DEV_ATTR(pi_prot_type, S_IRUGO | S_IWUSR); + +DEF_DEV_ATTRIB_RO(hw_pi_prot_type); +SE_DEV_ATTR_RO(hw_pi_prot_type); + +DEF_DEV_ATTRIB(pi_prot_format); +SE_DEV_ATTR(pi_prot_format, S_IRUGO | S_IWUSR); + DEF_DEV_ATTRIB(enforce_pr_isids); SE_DEV_ATTR(enforce_pr_isids, S_IRUGO | S_IWUSR); +DEF_DEV_ATTRIB(is_nonrot); +SE_DEV_ATTR(is_nonrot, S_IRUGO | S_IWUSR); + +DEF_DEV_ATTRIB(emulate_rest_reord); +SE_DEV_ATTR(emulate_rest_reord, S_IRUGO | S_IWUSR); + DEF_DEV_ATTRIB_RO(hw_block_size); SE_DEV_ATTR_RO(hw_block_size); @@ -727,8 +674,8 @@ SE_DEV_ATTR(block_size, S_IRUGO | S_IWUSR); DEF_DEV_ATTRIB_RO(hw_max_sectors); SE_DEV_ATTR_RO(hw_max_sectors); -DEF_DEV_ATTRIB(max_sectors); -SE_DEV_ATTR(max_sectors, S_IRUGO | S_IWUSR); +DEF_DEV_ATTRIB(fabric_max_sectors); +SE_DEV_ATTR(fabric_max_sectors, S_IRUGO | S_IWUSR); DEF_DEV_ATTRIB(optimal_sectors); SE_DEV_ATTR(optimal_sectors, S_IRUGO | S_IWUSR); @@ -739,9 +686,6 @@ SE_DEV_ATTR_RO(hw_queue_depth); DEF_DEV_ATTRIB(queue_depth); SE_DEV_ATTR(queue_depth, S_IRUGO | S_IWUSR); -DEF_DEV_ATTRIB(task_timeout); -SE_DEV_ATTR(task_timeout, S_IRUGO | S_IWUSR); - DEF_DEV_ATTRIB(max_unmap_lba_count); SE_DEV_ATTR(max_unmap_lba_count, S_IRUGO | S_IWUSR); @@ -754,9 +698,13 @@ SE_DEV_ATTR(unmap_granularity, S_IRUGO | S_IWUSR); DEF_DEV_ATTRIB(unmap_granularity_alignment); SE_DEV_ATTR(unmap_granularity_alignment, S_IRUGO | S_IWUSR); +DEF_DEV_ATTRIB(max_write_same_len); +SE_DEV_ATTR(max_write_same_len, S_IRUGO | S_IWUSR); + CONFIGFS_EATTR_OPS(target_core_dev_attrib, se_dev_attrib, da_group); static struct configfs_attribute *target_core_dev_attrib_attrs[] = { + &target_core_dev_attrib_emulate_model_alias.attr, &target_core_dev_attrib_emulate_dpo.attr, &target_core_dev_attrib_emulate_fua_write.attr, &target_core_dev_attrib_emulate_fua_read.attr, @@ -765,19 +713,26 @@ static struct configfs_attribute *target_core_dev_attrib_attrs[] = { &target_core_dev_attrib_emulate_tas.attr, &target_core_dev_attrib_emulate_tpu.attr, &target_core_dev_attrib_emulate_tpws.attr, + &target_core_dev_attrib_emulate_caw.attr, + &target_core_dev_attrib_emulate_3pc.attr, + &target_core_dev_attrib_pi_prot_type.attr, + &target_core_dev_attrib_hw_pi_prot_type.attr, + &target_core_dev_attrib_pi_prot_format.attr, &target_core_dev_attrib_enforce_pr_isids.attr, + &target_core_dev_attrib_is_nonrot.attr, + &target_core_dev_attrib_emulate_rest_reord.attr, &target_core_dev_attrib_hw_block_size.attr, &target_core_dev_attrib_block_size.attr, &target_core_dev_attrib_hw_max_sectors.attr, - &target_core_dev_attrib_max_sectors.attr, + &target_core_dev_attrib_fabric_max_sectors.attr, &target_core_dev_attrib_optimal_sectors.attr, &target_core_dev_attrib_hw_queue_depth.attr, &target_core_dev_attrib_queue_depth.attr, - &target_core_dev_attrib_task_timeout.attr, &target_core_dev_attrib_max_unmap_lba_count.attr, &target_core_dev_attrib_max_unmap_block_desc_count.attr, &target_core_dev_attrib_unmap_granularity.attr, &target_core_dev_attrib_unmap_granularity_alignment.attr, + &target_core_dev_attrib_max_write_same_len.attr, NULL, }; @@ -818,13 +773,6 @@ static ssize_t target_core_dev_wwn_show_attr_vpd_unit_serial( struct t10_wwn *t10_wwn, char *page) { - struct se_subsystem_dev *se_dev = t10_wwn->t10_sub_dev; - struct se_device *dev; - - dev = se_dev->se_dev_ptr; - if (!(dev)) - return -ENODEV; - return sprintf(page, "T10 VPD Unit Serial Number: %s\n", &t10_wwn->unit_serial[0]); } @@ -834,8 +782,7 @@ static ssize_t target_core_dev_wwn_store_attr_vpd_unit_serial( const char *page, size_t count) { - struct se_subsystem_dev *su_dev = t10_wwn->t10_sub_dev; - struct se_device *dev; + struct se_device *dev = t10_wwn->t10_dev; unsigned char buf[INQUIRY_VPD_SERIAL_LEN]; /* @@ -848,14 +795,14 @@ static ssize_t target_core_dev_wwn_store_attr_vpd_unit_serial( * it is doing 'the right thing' wrt a world wide unique * VPD Unit Serial Number that OS dependent multipath can depend on. */ - if (su_dev->su_dev_flags & SDF_FIRMWARE_VPD_UNIT_SERIAL) { - printk(KERN_ERR "Underlying SCSI device firmware provided VPD" + if (dev->dev_flags & DF_FIRMWARE_VPD_UNIT_SERIAL) { + pr_err("Underlying SCSI device firmware provided VPD" " Unit Serial, ignoring request\n"); return -EOPNOTSUPP; } - if ((strlen(page) + 1) > INQUIRY_VPD_SERIAL_LEN) { - printk(KERN_ERR "Emulated VPD Unit Serial exceeds" + if (strlen(page) >= INQUIRY_VPD_SERIAL_LEN) { + pr_err("Emulated VPD Unit Serial exceeds" " INQUIRY_VPD_SERIAL_LEN: %d\n", INQUIRY_VPD_SERIAL_LEN); return -EOVERFLOW; } @@ -865,15 +812,13 @@ static ssize_t target_core_dev_wwn_store_attr_vpd_unit_serial( * (underneath the initiator side OS dependent multipath code) * could cause negative effects. */ - dev = su_dev->se_dev_ptr; - if ((dev)) { - if (atomic_read(&dev->dev_export_obj.obj_access_count)) { - printk(KERN_ERR "Unable to set VPD Unit Serial while" - " active %d $FABRIC_MOD exports exist\n", - atomic_read(&dev->dev_export_obj.obj_access_count)); - return -EINVAL; - } + if (dev->export_count) { + pr_err("Unable to set VPD Unit Serial while" + " active %d $FABRIC_MOD exports exist\n", + dev->export_count); + return -EINVAL; } + /* * This currently assumes ASCII encoding for emulated VPD Unit Serial. * @@ -882,12 +827,12 @@ static ssize_t target_core_dev_wwn_store_attr_vpd_unit_serial( */ memset(buf, 0, INQUIRY_VPD_SERIAL_LEN); snprintf(buf, INQUIRY_VPD_SERIAL_LEN, "%s", page); - snprintf(su_dev->t10_wwn.unit_serial, INQUIRY_VPD_SERIAL_LEN, + snprintf(dev->t10_wwn.unit_serial, INQUIRY_VPD_SERIAL_LEN, "%s", strstrip(buf)); - su_dev->su_dev_flags |= SDF_EMULATED_VPD_UNIT_SERIAL; + dev->dev_flags |= DF_EMULATED_VPD_UNIT_SERIAL; - printk(KERN_INFO "Target_Core_ConfigFS: Set emulated VPD Unit Serial:" - " %s\n", su_dev->t10_wwn.unit_serial); + pr_debug("Target_Core_ConfigFS: Set emulated VPD Unit Serial:" + " %s\n", dev->t10_wwn.unit_serial); return count; } @@ -901,26 +846,20 @@ static ssize_t target_core_dev_wwn_show_attr_vpd_protocol_identifier( struct t10_wwn *t10_wwn, char *page) { - struct se_subsystem_dev *se_dev = t10_wwn->t10_sub_dev; - struct se_device *dev; struct t10_vpd *vpd; unsigned char buf[VPD_TMP_BUF_SIZE]; ssize_t len = 0; - dev = se_dev->se_dev_ptr; - if (!(dev)) - return -ENODEV; - memset(buf, 0, VPD_TMP_BUF_SIZE); spin_lock(&t10_wwn->t10_vpd_lock); list_for_each_entry(vpd, &t10_wwn->t10_vpd_list, vpd_list) { - if (!(vpd->protocol_identifier_set)) + if (!vpd->protocol_identifier_set) continue; transport_dump_vpd_proto_id(vpd, buf, VPD_TMP_BUF_SIZE); - if ((len + strlen(buf) > PAGE_SIZE)) + if (len + strlen(buf) >= PAGE_SIZE) break; len += sprintf(page+len, "%s", buf); @@ -948,16 +887,10 @@ static ssize_t target_core_dev_wwn_show_attr_##_name( \ struct t10_wwn *t10_wwn, \ char *page) \ { \ - struct se_subsystem_dev *se_dev = t10_wwn->t10_sub_dev; \ - struct se_device *dev; \ struct t10_vpd *vpd; \ unsigned char buf[VPD_TMP_BUF_SIZE]; \ ssize_t len = 0; \ \ - dev = se_dev->se_dev_ptr; \ - if (!(dev)) \ - return -ENODEV; \ - \ spin_lock(&t10_wwn->t10_vpd_lock); \ list_for_each_entry(vpd, &t10_wwn->t10_vpd_list, vpd_list) { \ if (vpd->association != _assoc) \ @@ -965,19 +898,19 @@ static ssize_t target_core_dev_wwn_show_attr_##_name( \ \ memset(buf, 0, VPD_TMP_BUF_SIZE); \ transport_dump_vpd_assoc(vpd, buf, VPD_TMP_BUF_SIZE); \ - if ((len + strlen(buf) > PAGE_SIZE)) \ + if (len + strlen(buf) >= PAGE_SIZE) \ break; \ len += sprintf(page+len, "%s", buf); \ \ memset(buf, 0, VPD_TMP_BUF_SIZE); \ transport_dump_vpd_ident_type(vpd, buf, VPD_TMP_BUF_SIZE); \ - if ((len + strlen(buf) > PAGE_SIZE)) \ + if (len + strlen(buf) >= PAGE_SIZE) \ break; \ len += sprintf(page+len, "%s", buf); \ \ memset(buf, 0, VPD_TMP_BUF_SIZE); \ transport_dump_vpd_ident(vpd, buf, VPD_TMP_BUF_SIZE); \ - if ((len + strlen(buf) > PAGE_SIZE)) \ + if (len + strlen(buf) >= PAGE_SIZE) \ break; \ len += sprintf(page+len, "%s", buf); \ } \ @@ -987,7 +920,7 @@ static ssize_t target_core_dev_wwn_show_attr_##_name( \ } /* - * VPD page 0x83 Assoication: Logical Unit + * VPD page 0x83 Association: Logical Unit */ DEF_DEV_WWN_ASSOC_SHOW(vpd_assoc_logical_unit, 0x00); @@ -1057,7 +990,7 @@ static struct config_item_type target_core_dev_wwn_cit = { /* Start functions for struct config_item_type target_core_dev_pr_cit */ -CONFIGFS_EATTR_STRUCT(target_core_dev_pr, se_subsystem_dev); +CONFIGFS_EATTR_STRUCT(target_core_dev_pr, se_device); #define SE_DEV_PR_ATTR(_name, _mode) \ static struct target_core_dev_pr_attribute target_core_dev_pr_##_name = \ __CONFIGFS_EATTR(_name, _mode, \ @@ -1069,149 +1002,90 @@ static struct target_core_dev_pr_attribute target_core_dev_pr_##_name = \ __CONFIGFS_EATTR_RO(_name, \ target_core_dev_pr_show_attr_##_name); -/* - * res_holder - */ -static ssize_t target_core_dev_pr_show_spc3_res( - struct se_device *dev, - char *page, - ssize_t *len) +static ssize_t target_core_dev_pr_show_spc3_res(struct se_device *dev, + char *page) { struct se_node_acl *se_nacl; struct t10_pr_registration *pr_reg; char i_buf[PR_REG_ISID_ID_LEN]; - int prf_isid; memset(i_buf, 0, PR_REG_ISID_ID_LEN); - spin_lock(&dev->dev_reservation_lock); pr_reg = dev->dev_pr_res_holder; - if (!(pr_reg)) { - *len += sprintf(page + *len, "No SPC-3 Reservation holder\n"); - spin_unlock(&dev->dev_reservation_lock); - return *len; - } - se_nacl = pr_reg->pr_reg_nacl; - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + if (!pr_reg) + return sprintf(page, "No SPC-3 Reservation holder\n"); - *len += sprintf(page + *len, "SPC-3 Reservation: %s Initiator: %s%s\n", - TPG_TFO(se_nacl->se_tpg)->get_fabric_name(), - se_nacl->initiatorname, (prf_isid) ? &i_buf[0] : ""); - spin_unlock(&dev->dev_reservation_lock); + se_nacl = pr_reg->pr_reg_nacl; + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); - return *len; + return sprintf(page, "SPC-3 Reservation: %s Initiator: %s%s\n", + se_nacl->se_tpg->se_tpg_tfo->get_fabric_name(), + se_nacl->initiatorname, i_buf); } -static ssize_t target_core_dev_pr_show_spc2_res( - struct se_device *dev, - char *page, - ssize_t *len) +static ssize_t target_core_dev_pr_show_spc2_res(struct se_device *dev, + char *page) { struct se_node_acl *se_nacl; + ssize_t len; - spin_lock(&dev->dev_reservation_lock); se_nacl = dev->dev_reserved_node_acl; - if (!(se_nacl)) { - *len += sprintf(page + *len, "No SPC-2 Reservation holder\n"); - spin_unlock(&dev->dev_reservation_lock); - return *len; - } - *len += sprintf(page + *len, "SPC-2 Reservation: %s Initiator: %s\n", - TPG_TFO(se_nacl->se_tpg)->get_fabric_name(), - se_nacl->initiatorname); - spin_unlock(&dev->dev_reservation_lock); - - return *len; + if (se_nacl) { + len = sprintf(page, + "SPC-2 Reservation: %s Initiator: %s\n", + se_nacl->se_tpg->se_tpg_tfo->get_fabric_name(), + se_nacl->initiatorname); + } else { + len = sprintf(page, "No SPC-2 Reservation holder\n"); + } + return len; } -static ssize_t target_core_dev_pr_show_attr_res_holder( - struct se_subsystem_dev *su_dev, - char *page) +static ssize_t target_core_dev_pr_show_attr_res_holder(struct se_device *dev, + char *page) { - ssize_t len = 0; - - if (!(su_dev->se_dev_ptr)) - return -ENODEV; + int ret; - switch (T10_RES(su_dev)->res_type) { - case SPC3_PERSISTENT_RESERVATIONS: - target_core_dev_pr_show_spc3_res(su_dev->se_dev_ptr, - page, &len); - break; - case SPC2_RESERVATIONS: - target_core_dev_pr_show_spc2_res(su_dev->se_dev_ptr, - page, &len); - break; - case SPC_PASSTHROUGH: - len += sprintf(page+len, "Passthrough\n"); - break; - default: - len += sprintf(page+len, "Unknown\n"); - break; - } + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) + return sprintf(page, "Passthrough\n"); - return len; + spin_lock(&dev->dev_reservation_lock); + if (dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS) + ret = target_core_dev_pr_show_spc2_res(dev, page); + else + ret = target_core_dev_pr_show_spc3_res(dev, page); + spin_unlock(&dev->dev_reservation_lock); + return ret; } SE_DEV_PR_ATTR_RO(res_holder); -/* - * res_pr_all_tgt_pts - */ static ssize_t target_core_dev_pr_show_attr_res_pr_all_tgt_pts( - struct se_subsystem_dev *su_dev, - char *page) + struct se_device *dev, char *page) { - struct se_device *dev; - struct t10_pr_registration *pr_reg; ssize_t len = 0; - dev = su_dev->se_dev_ptr; - if (!(dev)) - return -ENODEV; - - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) - return len; - spin_lock(&dev->dev_reservation_lock); - pr_reg = dev->dev_pr_res_holder; - if (!(pr_reg)) { + if (!dev->dev_pr_res_holder) { len = sprintf(page, "No SPC-3 Reservation holder\n"); - spin_unlock(&dev->dev_reservation_lock); - return len; - } - /* - * See All Target Ports (ALL_TG_PT) bit in spcr17, section 6.14.3 - * Basic PERSISTENT RESERVER OUT parameter list, page 290 - */ - if (pr_reg->pr_reg_all_tg_pt) + } else if (dev->dev_pr_res_holder->pr_reg_all_tg_pt) { len = sprintf(page, "SPC-3 Reservation: All Target" " Ports registration\n"); - else + } else { len = sprintf(page, "SPC-3 Reservation: Single" " Target Port registration\n"); - spin_unlock(&dev->dev_reservation_lock); + } + spin_unlock(&dev->dev_reservation_lock); return len; } SE_DEV_PR_ATTR_RO(res_pr_all_tgt_pts); -/* - * res_pr_generation - */ static ssize_t target_core_dev_pr_show_attr_res_pr_generation( - struct se_subsystem_dev *su_dev, - char *page) + struct se_device *dev, char *page) { - if (!(su_dev->se_dev_ptr)) - return -ENODEV; - - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) - return 0; - - return sprintf(page, "0x%08x\n", T10_RES(su_dev)->pr_generation); + return sprintf(page, "0x%08x\n", dev->t10_pr.pr_generation); } SE_DEV_PR_ATTR_RO(res_pr_generation); @@ -1220,10 +1094,8 @@ SE_DEV_PR_ATTR_RO(res_pr_generation); * res_pr_holder_tg_port */ static ssize_t target_core_dev_pr_show_attr_res_pr_holder_tg_port( - struct se_subsystem_dev *su_dev, - char *page) + struct se_device *dev, char *page) { - struct se_device *dev; struct se_node_acl *se_nacl; struct se_lun *lun; struct se_portal_group *se_tpg; @@ -1231,86 +1103,69 @@ static ssize_t target_core_dev_pr_show_attr_res_pr_holder_tg_port( struct target_core_fabric_ops *tfo; ssize_t len = 0; - dev = su_dev->se_dev_ptr; - if (!(dev)) - return -ENODEV; - - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) - return len; - spin_lock(&dev->dev_reservation_lock); pr_reg = dev->dev_pr_res_holder; - if (!(pr_reg)) { + if (!pr_reg) { len = sprintf(page, "No SPC-3 Reservation holder\n"); - spin_unlock(&dev->dev_reservation_lock); - return len; + goto out_unlock; } + se_nacl = pr_reg->pr_reg_nacl; se_tpg = se_nacl->se_tpg; lun = pr_reg->pr_reg_tg_pt_lun; - tfo = TPG_TFO(se_tpg); + tfo = se_tpg->se_tpg_tfo; len += sprintf(page+len, "SPC-3 Reservation: %s" " Target Node Endpoint: %s\n", tfo->get_fabric_name(), tfo->tpg_get_wwn(se_tpg)); len += sprintf(page+len, "SPC-3 Reservation: Relative Port" - " Identifer Tag: %hu %s Portal Group Tag: %hu" + " Identifier Tag: %hu %s Portal Group Tag: %hu" " %s Logical Unit: %u\n", lun->lun_sep->sep_rtpi, tfo->get_fabric_name(), tfo->tpg_get_tag(se_tpg), tfo->get_fabric_name(), lun->unpacked_lun); - spin_unlock(&dev->dev_reservation_lock); +out_unlock: + spin_unlock(&dev->dev_reservation_lock); return len; } SE_DEV_PR_ATTR_RO(res_pr_holder_tg_port); -/* - * res_pr_registered_i_pts - */ static ssize_t target_core_dev_pr_show_attr_res_pr_registered_i_pts( - struct se_subsystem_dev *su_dev, - char *page) + struct se_device *dev, char *page) { struct target_core_fabric_ops *tfo; struct t10_pr_registration *pr_reg; unsigned char buf[384]; char i_buf[PR_REG_ISID_ID_LEN]; ssize_t len = 0; - int reg_count = 0, prf_isid; - - if (!(su_dev->se_dev_ptr)) - return -ENODEV; - - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) - return len; + int reg_count = 0; len += sprintf(page+len, "SPC-3 PR Registrations:\n"); - spin_lock(&T10_RES(su_dev)->registration_lock); - list_for_each_entry(pr_reg, &T10_RES(su_dev)->registration_list, + spin_lock(&dev->t10_pr.registration_lock); + list_for_each_entry(pr_reg, &dev->t10_pr.registration_list, pr_reg_list) { memset(buf, 0, 384); memset(i_buf, 0, PR_REG_ISID_ID_LEN); tfo = pr_reg->pr_reg_nacl->se_tpg->se_tpg_tfo; - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); sprintf(buf, "%s Node: %s%s Key: 0x%016Lx PRgen: 0x%08x\n", tfo->get_fabric_name(), - pr_reg->pr_reg_nacl->initiatorname, (prf_isid) ? - &i_buf[0] : "", pr_reg->pr_res_key, + pr_reg->pr_reg_nacl->initiatorname, i_buf, pr_reg->pr_res_key, pr_reg->pr_res_generation); - if ((len + strlen(buf) > PAGE_SIZE)) + if (len + strlen(buf) >= PAGE_SIZE) break; len += sprintf(page+len, "%s", buf); reg_count++; } - spin_unlock(&T10_RES(su_dev)->registration_lock); + spin_unlock(&dev->t10_pr.registration_lock); - if (!(reg_count)) + if (!reg_count) len += sprintf(page+len, "None\n"); return len; @@ -1318,88 +1173,48 @@ static ssize_t target_core_dev_pr_show_attr_res_pr_registered_i_pts( SE_DEV_PR_ATTR_RO(res_pr_registered_i_pts); -/* - * res_pr_type - */ static ssize_t target_core_dev_pr_show_attr_res_pr_type( - struct se_subsystem_dev *su_dev, - char *page) + struct se_device *dev, char *page) { - struct se_device *dev; struct t10_pr_registration *pr_reg; ssize_t len = 0; - dev = su_dev->se_dev_ptr; - if (!(dev)) - return -ENODEV; - - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) - return len; - spin_lock(&dev->dev_reservation_lock); pr_reg = dev->dev_pr_res_holder; - if (!(pr_reg)) { + if (pr_reg) { + len = sprintf(page, "SPC-3 Reservation Type: %s\n", + core_scsi3_pr_dump_type(pr_reg->pr_res_type)); + } else { len = sprintf(page, "No SPC-3 Reservation holder\n"); - spin_unlock(&dev->dev_reservation_lock); - return len; } - len = sprintf(page, "SPC-3 Reservation Type: %s\n", - core_scsi3_pr_dump_type(pr_reg->pr_res_type)); - spin_unlock(&dev->dev_reservation_lock); + spin_unlock(&dev->dev_reservation_lock); return len; } SE_DEV_PR_ATTR_RO(res_pr_type); -/* - * res_type - */ static ssize_t target_core_dev_pr_show_attr_res_type( - struct se_subsystem_dev *su_dev, - char *page) + struct se_device *dev, char *page) { - ssize_t len = 0; - - if (!(su_dev->se_dev_ptr)) - return -ENODEV; - - switch (T10_RES(su_dev)->res_type) { - case SPC3_PERSISTENT_RESERVATIONS: - len = sprintf(page, "SPC3_PERSISTENT_RESERVATIONS\n"); - break; - case SPC2_RESERVATIONS: - len = sprintf(page, "SPC2_RESERVATIONS\n"); - break; - case SPC_PASSTHROUGH: - len = sprintf(page, "SPC_PASSTHROUGH\n"); - break; - default: - len = sprintf(page, "UNKNOWN\n"); - break; - } - - return len; + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) + return sprintf(page, "SPC_PASSTHROUGH\n"); + else if (dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS) + return sprintf(page, "SPC2_RESERVATIONS\n"); + else + return sprintf(page, "SPC3_PERSISTENT_RESERVATIONS\n"); } SE_DEV_PR_ATTR_RO(res_type); -/* - * res_aptpl_active - */ - static ssize_t target_core_dev_pr_show_attr_res_aptpl_active( - struct se_subsystem_dev *su_dev, - char *page) + struct se_device *dev, char *page) { - if (!(su_dev->se_dev_ptr)) - return -ENODEV; - - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) return 0; return sprintf(page, "APTPL Bit Status: %s\n", - (T10_RES(su_dev)->pr_aptpl_active) ? "Activated" : "Disabled"); + (dev->t10_pr.pr_aptpl_active) ? "Activated" : "Disabled"); } SE_DEV_PR_ATTR_RO(res_aptpl_active); @@ -1408,13 +1223,9 @@ SE_DEV_PR_ATTR_RO(res_aptpl_active); * res_aptpl_metadata */ static ssize_t target_core_dev_pr_show_attr_res_aptpl_metadata( - struct se_subsystem_dev *su_dev, - char *page) + struct se_device *dev, char *page) { - if (!(su_dev->se_dev_ptr)) - return -ENODEV; - - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) return 0; return sprintf(page, "Ready to process PR APTPL metadata..\n"); @@ -1446,13 +1257,12 @@ static match_table_t tokens = { }; static ssize_t target_core_dev_pr_store_attr_res_aptpl_metadata( - struct se_subsystem_dev *su_dev, + struct se_device *dev, const char *page, size_t count) { - struct se_device *dev; - unsigned char *i_fabric, *t_fabric, *i_port = NULL, *t_port = NULL; - unsigned char *isid = NULL; + unsigned char *i_fabric = NULL, *i_port = NULL, *isid = NULL; + unsigned char *t_fabric = NULL, *t_port = NULL; char *orig, *ptr, *arg_p, *opts; substring_t args[MAX_OPT_ARGS]; unsigned long long tmp_ll; @@ -1462,15 +1272,13 @@ static ssize_t target_core_dev_pr_store_attr_res_aptpl_metadata( u16 port_rpti = 0, tpgt = 0; u8 type = 0, scope; - dev = su_dev->se_dev_ptr; - if (!(dev)) - return -ENODEV; - - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) + return 0; + if (dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS) return 0; - if (atomic_read(&dev->dev_export_obj.obj_access_count)) { - printk(KERN_INFO "Unable to process APTPL metadata while" + if (dev->export_count) { + pr_debug("Unable to process APTPL metadata while" " active fabric exports exist\n"); return -EINVAL; } @@ -1480,7 +1288,7 @@ static ssize_t target_core_dev_pr_store_attr_res_aptpl_metadata( return -ENOMEM; orig = opts; - while ((ptr = strsep(&opts, ",")) != NULL) { + while ((ptr = strsep(&opts, ",\n")) != NULL) { if (!*ptr) continue; @@ -1488,11 +1296,19 @@ static ssize_t target_core_dev_pr_store_attr_res_aptpl_metadata( switch (token) { case Opt_initiator_fabric: i_fabric = match_strdup(&args[0]); + if (!i_fabric) { + ret = -ENOMEM; + goto out; + } break; case Opt_initiator_node: i_port = match_strdup(&args[0]); - if (strlen(i_port) > PR_APTPL_MAX_IPORT_LEN) { - printk(KERN_ERR "APTPL metadata initiator_node=" + if (!i_port) { + ret = -ENOMEM; + goto out; + } + if (strlen(i_port) >= PR_APTPL_MAX_IPORT_LEN) { + pr_err("APTPL metadata initiator_node=" " exceeds PR_APTPL_MAX_IPORT_LEN: %d\n", PR_APTPL_MAX_IPORT_LEN); ret = -EINVAL; @@ -1501,8 +1317,12 @@ static ssize_t target_core_dev_pr_store_attr_res_aptpl_metadata( break; case Opt_initiator_sid: isid = match_strdup(&args[0]); - if (strlen(isid) > PR_REG_ISID_LEN) { - printk(KERN_ERR "APTPL metadata initiator_isid" + if (!isid) { + ret = -ENOMEM; + goto out; + } + if (strlen(isid) >= PR_REG_ISID_LEN) { + pr_err("APTPL metadata initiator_isid" "= exceeds PR_REG_ISID_LEN: %d\n", PR_REG_ISID_LEN); ret = -EINVAL; @@ -1511,9 +1331,13 @@ static ssize_t target_core_dev_pr_store_attr_res_aptpl_metadata( break; case Opt_sa_res_key: arg_p = match_strdup(&args[0]); - ret = strict_strtoull(arg_p, 0, &tmp_ll); + if (!arg_p) { + ret = -ENOMEM; + goto out; + } + ret = kstrtoull(arg_p, 0, &tmp_ll); if (ret < 0) { - printk(KERN_ERR "strict_strtoull() failed for" + pr_err("kstrtoull() failed for" " sa_res_key=\n"); goto out; } @@ -1547,11 +1371,19 @@ static ssize_t target_core_dev_pr_store_attr_res_aptpl_metadata( */ case Opt_target_fabric: t_fabric = match_strdup(&args[0]); + if (!t_fabric) { + ret = -ENOMEM; + goto out; + } break; case Opt_target_node: t_port = match_strdup(&args[0]); - if (strlen(t_port) > PR_APTPL_MAX_TPORT_LEN) { - printk(KERN_ERR "APTPL metadata target_node=" + if (!t_port) { + ret = -ENOMEM; + goto out; + } + if (strlen(t_port) >= PR_APTPL_MAX_TPORT_LEN) { + pr_err("APTPL metadata target_node=" " exceeds PR_APTPL_MAX_TPORT_LEN: %d\n", PR_APTPL_MAX_TPORT_LEN); ret = -EINVAL; @@ -1575,30 +1407,35 @@ static ssize_t target_core_dev_pr_store_attr_res_aptpl_metadata( } } - if (!(i_port) || !(t_port) || !(sa_res_key)) { - printk(KERN_ERR "Illegal parameters for APTPL registration\n"); + if (!i_port || !t_port || !sa_res_key) { + pr_err("Illegal parameters for APTPL registration\n"); ret = -EINVAL; goto out; } if (res_holder && !(type)) { - printk(KERN_ERR "Illegal PR type: 0x%02x for reservation" + pr_err("Illegal PR type: 0x%02x for reservation" " holder\n", type); ret = -EINVAL; goto out; } - ret = core_scsi3_alloc_aptpl_registration(T10_RES(su_dev), sa_res_key, + ret = core_scsi3_alloc_aptpl_registration(&dev->t10_pr, sa_res_key, i_port, isid, mapped_lun, t_port, tpgt, target_lun, res_holder, all_tg_pt, type); out: + kfree(i_fabric); + kfree(i_port); + kfree(isid); + kfree(t_fabric); + kfree(t_port); kfree(orig); return (ret == 0) ? count : ret; } SE_DEV_PR_ATTR(res_aptpl_metadata, S_IRUGO | S_IWUSR); -CONFIGFS_EATTR_OPS(target_core_dev_pr, se_subsystem_dev, se_dev_pr_group); +CONFIGFS_EATTR_OPS(target_core_dev_pr, se_device, dev_pr_group); static struct configfs_attribute *target_core_dev_pr_attrs[] = { &target_core_dev_pr_res_holder.attr, @@ -1630,18 +1467,14 @@ static struct config_item_type target_core_dev_pr_cit = { static ssize_t target_core_show_dev_info(void *p, char *page) { - struct se_subsystem_dev *se_dev = (struct se_subsystem_dev *)p; - struct se_hba *hba = se_dev->se_dev_hba; - struct se_subsystem_api *t = hba->transport; + struct se_device *dev = p; + struct se_subsystem_api *t = dev->transport; int bl = 0; ssize_t read_bytes = 0; - if (!(se_dev->se_dev_ptr)) - return -ENODEV; - - transport_dump_dev_state(se_dev->se_dev_ptr, page, &bl); + transport_dump_dev_state(dev, page, &bl); read_bytes += bl; - read_bytes += t->show_configfs_dev_params(hba, se_dev, page+read_bytes); + read_bytes += t->show_configfs_dev_params(dev, page+read_bytes); return read_bytes; } @@ -1658,17 +1491,10 @@ static ssize_t target_core_store_dev_control( const char *page, size_t count) { - struct se_subsystem_dev *se_dev = (struct se_subsystem_dev *)p; - struct se_hba *hba = se_dev->se_dev_hba; - struct se_subsystem_api *t = hba->transport; - - if (!(se_dev->se_dev_su_ptr)) { - printk(KERN_ERR "Unable to locate struct se_subsystem_dev>se" - "_dev_su_ptr\n"); - return -EINVAL; - } + struct se_device *dev = p; + struct se_subsystem_api *t = dev->transport; - return t->set_configfs_dev_params(hba, se_dev, page, count); + return t->set_configfs_dev_params(dev, page, count); } static struct target_core_configfs_attribute target_core_attr_dev_control = { @@ -1681,12 +1507,12 @@ static struct target_core_configfs_attribute target_core_attr_dev_control = { static ssize_t target_core_show_dev_alias(void *p, char *page) { - struct se_subsystem_dev *se_dev = (struct se_subsystem_dev *)p; + struct se_device *dev = p; - if (!(se_dev->su_dev_flags & SDF_USING_ALIAS)) + if (!(dev->dev_flags & DF_USING_ALIAS)) return 0; - return snprintf(page, PAGE_SIZE, "%s\n", se_dev->se_dev_alias); + return snprintf(page, PAGE_SIZE, "%s\n", dev->dev_alias); } static ssize_t target_core_store_dev_alias( @@ -1694,25 +1520,29 @@ static ssize_t target_core_store_dev_alias( const char *page, size_t count) { - struct se_subsystem_dev *se_dev = (struct se_subsystem_dev *)p; - struct se_hba *hba = se_dev->se_dev_hba; + struct se_device *dev = p; + struct se_hba *hba = dev->se_hba; ssize_t read_bytes; if (count > (SE_DEV_ALIAS_LEN-1)) { - printk(KERN_ERR "alias count: %d exceeds" + pr_err("alias count: %d exceeds" " SE_DEV_ALIAS_LEN-1: %u\n", (int)count, SE_DEV_ALIAS_LEN-1); return -EINVAL; } - se_dev->su_dev_flags |= SDF_USING_ALIAS; - read_bytes = snprintf(&se_dev->se_dev_alias[0], SE_DEV_ALIAS_LEN, - "%s", page); + read_bytes = snprintf(&dev->dev_alias[0], SE_DEV_ALIAS_LEN, "%s", page); + if (!read_bytes) + return -EINVAL; + if (dev->dev_alias[read_bytes - 1] == '\n') + dev->dev_alias[read_bytes - 1] = '\0'; - printk(KERN_INFO "Target_Core_ConfigFS: %s/%s set alias: %s\n", + dev->dev_flags |= DF_USING_ALIAS; + + pr_debug("Target_Core_ConfigFS: %s/%s set alias: %s\n", config_item_name(&hba->hba_group.cg_item), - config_item_name(&se_dev->se_dev_group.cg_item), - se_dev->se_dev_alias); + config_item_name(&dev->dev_group.cg_item), + dev->dev_alias); return read_bytes; } @@ -1727,12 +1557,12 @@ static struct target_core_configfs_attribute target_core_attr_dev_alias = { static ssize_t target_core_show_dev_udev_path(void *p, char *page) { - struct se_subsystem_dev *se_dev = (struct se_subsystem_dev *)p; + struct se_device *dev = p; - if (!(se_dev->su_dev_flags & SDF_USING_UDEV_PATH)) + if (!(dev->dev_flags & DF_USING_UDEV_PATH)) return 0; - return snprintf(page, PAGE_SIZE, "%s\n", se_dev->se_dev_udev_path); + return snprintf(page, PAGE_SIZE, "%s\n", dev->udev_path); } static ssize_t target_core_store_dev_udev_path( @@ -1740,25 +1570,30 @@ static ssize_t target_core_store_dev_udev_path( const char *page, size_t count) { - struct se_subsystem_dev *se_dev = (struct se_subsystem_dev *)p; - struct se_hba *hba = se_dev->se_dev_hba; + struct se_device *dev = p; + struct se_hba *hba = dev->se_hba; ssize_t read_bytes; if (count > (SE_UDEV_PATH_LEN-1)) { - printk(KERN_ERR "udev_path count: %d exceeds" + pr_err("udev_path count: %d exceeds" " SE_UDEV_PATH_LEN-1: %u\n", (int)count, SE_UDEV_PATH_LEN-1); return -EINVAL; } - se_dev->su_dev_flags |= SDF_USING_UDEV_PATH; - read_bytes = snprintf(&se_dev->se_dev_udev_path[0], SE_UDEV_PATH_LEN, + read_bytes = snprintf(&dev->udev_path[0], SE_UDEV_PATH_LEN, "%s", page); + if (!read_bytes) + return -EINVAL; + if (dev->udev_path[read_bytes - 1] == '\n') + dev->udev_path[read_bytes - 1] = '\0'; + + dev->dev_flags |= DF_USING_UDEV_PATH; - printk(KERN_INFO "Target_Core_ConfigFS: %s/%s set udev_path: %s\n", + pr_debug("Target_Core_ConfigFS: %s/%s set udev_path: %s\n", config_item_name(&hba->hba_group.cg_item), - config_item_name(&se_dev->se_dev_group.cg_item), - se_dev->se_dev_udev_path); + config_item_name(&dev->dev_group.cg_item), + dev->udev_path); return read_bytes; } @@ -1771,77 +1606,58 @@ static struct target_core_configfs_attribute target_core_attr_dev_udev_path = { .store = target_core_store_dev_udev_path, }; +static ssize_t target_core_show_dev_enable(void *p, char *page) +{ + struct se_device *dev = p; + + return snprintf(page, PAGE_SIZE, "%d\n", !!(dev->dev_flags & DF_CONFIGURED)); +} + static ssize_t target_core_store_dev_enable( void *p, const char *page, size_t count) { - struct se_subsystem_dev *se_dev = (struct se_subsystem_dev *)p; - struct se_device *dev; - struct se_hba *hba = se_dev->se_dev_hba; - struct se_subsystem_api *t = hba->transport; + struct se_device *dev = p; char *ptr; + int ret; ptr = strstr(page, "1"); - if (!(ptr)) { - printk(KERN_ERR "For dev_enable ops, only valid value" + if (!ptr) { + pr_err("For dev_enable ops, only valid value" " is \"1\"\n"); return -EINVAL; } - if ((se_dev->se_dev_ptr)) { - printk(KERN_ERR "se_dev->se_dev_ptr already set for storage" - " object\n"); - return -EEXIST; - } - - if (t->check_configfs_dev_params(hba, se_dev) < 0) - return -EINVAL; - - dev = t->create_virtdevice(hba, se_dev, se_dev->se_dev_su_ptr); - if (!(dev) || IS_ERR(dev)) - return -EINVAL; - - se_dev->se_dev_ptr = dev; - printk(KERN_INFO "Target_Core_ConfigFS: Registered se_dev->se_dev_ptr:" - " %p\n", se_dev->se_dev_ptr); + ret = target_configure_device(dev); + if (ret) + return ret; return count; } static struct target_core_configfs_attribute target_core_attr_dev_enable = { .attr = { .ca_owner = THIS_MODULE, .ca_name = "enable", - .ca_mode = S_IWUSR }, - .show = NULL, + .ca_mode = S_IRUGO | S_IWUSR }, + .show = target_core_show_dev_enable, .store = target_core_store_dev_enable, }; static ssize_t target_core_show_alua_lu_gp(void *p, char *page) { - struct se_device *dev; - struct se_subsystem_dev *su_dev = (struct se_subsystem_dev *)p; + struct se_device *dev = p; struct config_item *lu_ci; struct t10_alua_lu_gp *lu_gp; struct t10_alua_lu_gp_member *lu_gp_mem; ssize_t len = 0; - dev = su_dev->se_dev_ptr; - if (!(dev)) - return -ENODEV; - - if (T10_ALUA(su_dev)->alua_type != SPC3_ALUA_EMULATED) - return len; - lu_gp_mem = dev->dev_alua_lu_gp_mem; - if (!(lu_gp_mem)) { - printk(KERN_ERR "NULL struct se_device->dev_alua_lu_gp_mem" - " pointer\n"); - return -EINVAL; - } + if (!lu_gp_mem) + return 0; spin_lock(&lu_gp_mem->lu_gp_mem_lock); lu_gp = lu_gp_mem->lu_gp; - if ((lu_gp)) { + if (lu_gp) { lu_ci = &lu_gp->lu_gp_group.cg_item; len += sprintf(page, "LU Group Alias: %s\nLU Group ID: %hu\n", config_item_name(lu_ci), lu_gp->lu_gp_id); @@ -1856,26 +1672,19 @@ static ssize_t target_core_store_alua_lu_gp( const char *page, size_t count) { - struct se_device *dev; - struct se_subsystem_dev *su_dev = (struct se_subsystem_dev *)p; - struct se_hba *hba = su_dev->se_dev_hba; + struct se_device *dev = p; + struct se_hba *hba = dev->se_hba; struct t10_alua_lu_gp *lu_gp = NULL, *lu_gp_new = NULL; struct t10_alua_lu_gp_member *lu_gp_mem; unsigned char buf[LU_GROUP_NAME_BUF]; int move = 0; - dev = su_dev->se_dev_ptr; - if (!(dev)) - return -ENODEV; + lu_gp_mem = dev->dev_alua_lu_gp_mem; + if (!lu_gp_mem) + return 0; - if (T10_ALUA(su_dev)->alua_type != SPC3_ALUA_EMULATED) { - printk(KERN_WARNING "SPC3_ALUA_EMULATED not enabled for %s/%s\n", - config_item_name(&hba->hba_group.cg_item), - config_item_name(&su_dev->se_dev_group.cg_item)); - return -EINVAL; - } if (count > LU_GROUP_NAME_BUF) { - printk(KERN_ERR "ALUA LU Group Alias too large!\n"); + pr_err("ALUA LU Group Alias too large!\n"); return -EINVAL; } memset(buf, 0, LU_GROUP_NAME_BUF); @@ -1891,31 +1700,23 @@ static ssize_t target_core_store_alua_lu_gp( * core_alua_get_lu_gp_by_name below(). */ lu_gp_new = core_alua_get_lu_gp_by_name(strstrip(buf)); - if (!(lu_gp_new)) + if (!lu_gp_new) return -ENODEV; } - lu_gp_mem = dev->dev_alua_lu_gp_mem; - if (!(lu_gp_mem)) { - if (lu_gp_new) - core_alua_put_lu_gp_from_name(lu_gp_new); - printk(KERN_ERR "NULL struct se_device->dev_alua_lu_gp_mem" - " pointer\n"); - return -EINVAL; - } spin_lock(&lu_gp_mem->lu_gp_mem_lock); lu_gp = lu_gp_mem->lu_gp; - if ((lu_gp)) { + if (lu_gp) { /* * Clearing an existing lu_gp association, and replacing * with NULL */ - if (!(lu_gp_new)) { - printk(KERN_INFO "Target_Core_ConfigFS: Releasing %s/%s" + if (!lu_gp_new) { + pr_debug("Target_Core_ConfigFS: Releasing %s/%s" " from ALUA LU Group: core/alua/lu_gps/%s, ID:" " %hu\n", config_item_name(&hba->hba_group.cg_item), - config_item_name(&su_dev->se_dev_group.cg_item), + config_item_name(&dev->dev_group.cg_item), config_item_name(&lu_gp->lu_gp_group.cg_item), lu_gp->lu_gp_id); @@ -1936,11 +1737,11 @@ static ssize_t target_core_store_alua_lu_gp( __core_alua_attach_lu_gp_mem(lu_gp_mem, lu_gp_new); spin_unlock(&lu_gp_mem->lu_gp_mem_lock); - printk(KERN_INFO "Target_Core_ConfigFS: %s %s/%s to ALUA LU Group:" + pr_debug("Target_Core_ConfigFS: %s %s/%s to ALUA LU Group:" " core/alua/lu_gps/%s, ID: %hu\n", (move) ? "Moving" : "Adding", config_item_name(&hba->hba_group.cg_item), - config_item_name(&su_dev->se_dev_group.cg_item), + config_item_name(&dev->dev_group.cg_item), config_item_name(&lu_gp_new->lu_gp_group.cg_item), lu_gp_new->lu_gp_id); @@ -1956,6 +1757,176 @@ static struct target_core_configfs_attribute target_core_attr_dev_alua_lu_gp = { .store = target_core_store_alua_lu_gp, }; +static ssize_t target_core_show_dev_lba_map(void *p, char *page) +{ + struct se_device *dev = p; + struct t10_alua_lba_map *map; + struct t10_alua_lba_map_member *mem; + char *b = page; + int bl = 0; + char state; + + spin_lock(&dev->t10_alua.lba_map_lock); + if (!list_empty(&dev->t10_alua.lba_map_list)) + bl += sprintf(b + bl, "%u %u\n", + dev->t10_alua.lba_map_segment_size, + dev->t10_alua.lba_map_segment_multiplier); + list_for_each_entry(map, &dev->t10_alua.lba_map_list, lba_map_list) { + bl += sprintf(b + bl, "%llu %llu", + map->lba_map_first_lba, map->lba_map_last_lba); + list_for_each_entry(mem, &map->lba_map_mem_list, + lba_map_mem_list) { + switch (mem->lba_map_mem_alua_state) { + case ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED: + state = 'O'; + break; + case ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED: + state = 'A'; + break; + case ALUA_ACCESS_STATE_STANDBY: + state = 'S'; + break; + case ALUA_ACCESS_STATE_UNAVAILABLE: + state = 'U'; + break; + default: + state = '.'; + break; + } + bl += sprintf(b + bl, " %d:%c", + mem->lba_map_mem_alua_pg_id, state); + } + bl += sprintf(b + bl, "\n"); + } + spin_unlock(&dev->t10_alua.lba_map_lock); + return bl; +} + +static ssize_t target_core_store_dev_lba_map( + void *p, + const char *page, + size_t count) +{ + struct se_device *dev = p; + struct t10_alua_lba_map *lba_map = NULL; + struct list_head lba_list; + char *map_entries, *ptr; + char state; + int pg_num = -1, pg; + int ret = 0, num = 0, pg_id, alua_state; + unsigned long start_lba = -1, end_lba = -1; + unsigned long segment_size = -1, segment_mult = -1; + + map_entries = kstrdup(page, GFP_KERNEL); + if (!map_entries) + return -ENOMEM; + + INIT_LIST_HEAD(&lba_list); + while ((ptr = strsep(&map_entries, "\n")) != NULL) { + if (!*ptr) + continue; + + if (num == 0) { + if (sscanf(ptr, "%lu %lu\n", + &segment_size, &segment_mult) != 2) { + pr_err("Invalid line %d\n", num); + ret = -EINVAL; + break; + } + num++; + continue; + } + if (sscanf(ptr, "%lu %lu", &start_lba, &end_lba) != 2) { + pr_err("Invalid line %d\n", num); + ret = -EINVAL; + break; + } + ptr = strchr(ptr, ' '); + if (!ptr) { + pr_err("Invalid line %d, missing end lba\n", num); + ret = -EINVAL; + break; + } + ptr++; + ptr = strchr(ptr, ' '); + if (!ptr) { + pr_err("Invalid line %d, missing state definitions\n", + num); + ret = -EINVAL; + break; + } + ptr++; + lba_map = core_alua_allocate_lba_map(&lba_list, + start_lba, end_lba); + if (IS_ERR(lba_map)) { + ret = PTR_ERR(lba_map); + break; + } + pg = 0; + while (sscanf(ptr, "%d:%c", &pg_id, &state) == 2) { + switch (state) { + case 'O': + alua_state = ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED; + break; + case 'A': + alua_state = ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED; + break; + case 'S': + alua_state = ALUA_ACCESS_STATE_STANDBY; + break; + case 'U': + alua_state = ALUA_ACCESS_STATE_UNAVAILABLE; + break; + default: + pr_err("Invalid ALUA state '%c'\n", state); + ret = -EINVAL; + goto out; + } + + ret = core_alua_allocate_lba_map_mem(lba_map, + pg_id, alua_state); + if (ret) { + pr_err("Invalid target descriptor %d:%c " + "at line %d\n", + pg_id, state, num); + break; + } + pg++; + ptr = strchr(ptr, ' '); + if (ptr) + ptr++; + else + break; + } + if (pg_num == -1) + pg_num = pg; + else if (pg != pg_num) { + pr_err("Only %d from %d port groups definitions " + "at line %d\n", pg, pg_num, num); + ret = -EINVAL; + break; + } + num++; + } +out: + if (ret) { + core_alua_free_lba_map(&lba_list); + count = ret; + } else + core_alua_set_lba_map(dev, &lba_list, + segment_size, segment_mult); + kfree(map_entries); + return count; +} + +static struct target_core_configfs_attribute target_core_attr_dev_lba_map = { + .attr = { .ca_owner = THIS_MODULE, + .ca_name = "lba_map", + .ca_mode = S_IRUGO | S_IWUSR }, + .show = target_core_show_dev_lba_map, + .store = target_core_store_dev_lba_map, +}; + static struct configfs_attribute *lio_core_dev_attrs[] = { &target_core_attr_dev_info.attr, &target_core_attr_dev_control.attr, @@ -1963,74 +1934,50 @@ static struct configfs_attribute *lio_core_dev_attrs[] = { &target_core_attr_dev_udev_path.attr, &target_core_attr_dev_enable.attr, &target_core_attr_dev_alua_lu_gp.attr, + &target_core_attr_dev_lba_map.attr, NULL, }; static void target_core_dev_release(struct config_item *item) { - struct se_subsystem_dev *se_dev = container_of(to_config_group(item), - struct se_subsystem_dev, se_dev_group); - struct se_hba *hba = item_to_hba(&se_dev->se_dev_hba->hba_group.cg_item); - struct se_subsystem_api *t = hba->transport; - struct config_group *dev_cg = &se_dev->se_dev_group; + struct config_group *dev_cg = to_config_group(item); + struct se_device *dev = + container_of(dev_cg, struct se_device, dev_group); kfree(dev_cg->default_groups); - /* - * This pointer will set when the storage is enabled with: - *`echo 1 > $CONFIGFS/core/$HBA/$DEV/dev_enable` - */ - if (se_dev->se_dev_ptr) { - printk(KERN_INFO "Target_Core_ConfigFS: Calling se_free_" - "virtual_device() for se_dev_ptr: %p\n", - se_dev->se_dev_ptr); - - se_free_virtual_device(se_dev->se_dev_ptr, hba); - } else { - /* - * Release struct se_subsystem_dev->se_dev_su_ptr.. - */ - printk(KERN_INFO "Target_Core_ConfigFS: Calling t->free_" - "device() for se_dev_su_ptr: %p\n", - se_dev->se_dev_su_ptr); - - t->free_device(se_dev->se_dev_su_ptr); - } - - printk(KERN_INFO "Target_Core_ConfigFS: Deallocating se_subsystem" - "_dev_t: %p\n", se_dev); - kfree(se_dev); + target_free_device(dev); } static ssize_t target_core_dev_show(struct config_item *item, struct configfs_attribute *attr, char *page) { - struct se_subsystem_dev *se_dev = container_of( - to_config_group(item), struct se_subsystem_dev, - se_dev_group); + struct config_group *dev_cg = to_config_group(item); + struct se_device *dev = + container_of(dev_cg, struct se_device, dev_group); struct target_core_configfs_attribute *tc_attr = container_of( attr, struct target_core_configfs_attribute, attr); - if (!(tc_attr->show)) + if (!tc_attr->show) return -EINVAL; - return tc_attr->show((void *)se_dev, page); + return tc_attr->show(dev, page); } static ssize_t target_core_dev_store(struct config_item *item, struct configfs_attribute *attr, const char *page, size_t count) { - struct se_subsystem_dev *se_dev = container_of( - to_config_group(item), struct se_subsystem_dev, - se_dev_group); + struct config_group *dev_cg = to_config_group(item); + struct se_device *dev = + container_of(dev_cg, struct se_device, dev_group); struct target_core_configfs_attribute *tc_attr = container_of( attr, struct target_core_configfs_attribute, attr); - if (!(tc_attr->store)) + if (!tc_attr->store) return -EINVAL; - return tc_attr->store((void *)se_dev, page, count); + return tc_attr->store(dev, page, count); } static struct configfs_item_operations target_core_dev_item_ops = { @@ -2070,7 +2017,7 @@ static ssize_t target_core_alua_lu_gp_show_attr_lu_gp_id( struct t10_alua_lu_gp *lu_gp, char *page) { - if (!(lu_gp->lu_gp_valid_id)) + if (!lu_gp->lu_gp_valid_id) return 0; return sprintf(page, "%hu\n", lu_gp->lu_gp_id); @@ -2085,14 +2032,14 @@ static ssize_t target_core_alua_lu_gp_store_attr_lu_gp_id( unsigned long lu_gp_id; int ret; - ret = strict_strtoul(page, 0, &lu_gp_id); + ret = kstrtoul(page, 0, &lu_gp_id); if (ret < 0) { - printk(KERN_ERR "strict_strtoul() returned %d for" + pr_err("kstrtoul() returned %d for" " lu_gp_id\n", ret); - return -EINVAL; + return ret; } if (lu_gp_id > 0x0000ffff) { - printk(KERN_ERR "ALUA lu_gp_id: %lu exceeds maximum:" + pr_err("ALUA lu_gp_id: %lu exceeds maximum:" " 0x0000ffff\n", lu_gp_id); return -EINVAL; } @@ -2101,7 +2048,7 @@ static ssize_t target_core_alua_lu_gp_store_attr_lu_gp_id( if (ret < 0) return -EINVAL; - printk(KERN_INFO "Target_Core_ConfigFS: Set ALUA Logical Unit" + pr_debug("Target_Core_ConfigFS: Set ALUA Logical Unit" " Group: core/alua/lu_gps/%s to ID: %hu\n", config_item_name(&alua_lu_gp_cg->cg_item), lu_gp->lu_gp_id); @@ -2120,7 +2067,6 @@ static ssize_t target_core_alua_lu_gp_show_attr_members( { struct se_device *dev; struct se_hba *hba; - struct se_subsystem_dev *su_dev; struct t10_alua_lu_gp_member *lu_gp_mem; ssize_t len = 0, cur_len; unsigned char buf[LU_GROUP_NAME_BUF]; @@ -2130,16 +2076,15 @@ static ssize_t target_core_alua_lu_gp_show_attr_members( spin_lock(&lu_gp->lu_gp_lock); list_for_each_entry(lu_gp_mem, &lu_gp->lu_gp_mem_list, lu_gp_mem_list) { dev = lu_gp_mem->lu_gp_mem_dev; - su_dev = dev->se_sub_dev; - hba = su_dev->se_dev_hba; + hba = dev->se_hba; cur_len = snprintf(buf, LU_GROUP_NAME_BUF, "%s/%s\n", config_item_name(&hba->hba_group.cg_item), - config_item_name(&su_dev->se_dev_group.cg_item)); + config_item_name(&dev->dev_group.cg_item)); cur_len++; /* Extra byte for NULL terminator */ if ((cur_len + len) > PAGE_SIZE) { - printk(KERN_WARNING "Ran out of lu_gp_show_attr" + pr_warn("Ran out of lu_gp_show_attr" "_members buffer\n"); break; } @@ -2203,7 +2148,7 @@ static struct config_group *target_core_alua_create_lu_gp( config_group_init_type_name(alua_lu_gp_cg, name, &target_core_alua_lu_gp_cit); - printk(KERN_INFO "Target_Core_ConfigFS: Allocated ALUA Logical Unit" + pr_debug("Target_Core_ConfigFS: Allocated ALUA Logical Unit" " Group: core/alua/lu_gps/%s\n", config_item_name(alua_lu_gp_ci)); @@ -2218,7 +2163,7 @@ static void target_core_alua_drop_lu_gp( struct t10_alua_lu_gp *lu_gp = container_of(to_config_group(item), struct t10_alua_lu_gp, lu_gp_group); - printk(KERN_INFO "Target_Core_ConfigFS: Releasing ALUA Logical Unit" + pr_debug("Target_Core_ConfigFS: Releasing ALUA Logical Unit" " Group: core/alua/lu_gps/%s, ID: %hu\n", config_item_name(item), lu_gp->lu_gp_id); /* @@ -2273,31 +2218,43 @@ static ssize_t target_core_alua_tg_pt_gp_store_attr_alua_access_state( const char *page, size_t count) { - struct se_subsystem_dev *su_dev = tg_pt_gp->tg_pt_gp_su_dev; + struct se_device *dev = tg_pt_gp->tg_pt_gp_dev; unsigned long tmp; int new_state, ret; - if (!(tg_pt_gp->tg_pt_gp_valid_id)) { - printk(KERN_ERR "Unable to do implict ALUA on non valid" + if (!tg_pt_gp->tg_pt_gp_valid_id) { + pr_err("Unable to do implicit ALUA on non valid" " tg_pt_gp ID: %hu\n", tg_pt_gp->tg_pt_gp_valid_id); return -EINVAL; } + if (!(dev->dev_flags & DF_CONFIGURED)) { + pr_err("Unable to set alua_access_state while device is" + " not configured\n"); + return -ENODEV; + } - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk("Unable to extract new ALUA access state from" + pr_err("Unable to extract new ALUA access state from" " %s\n", page); - return -EINVAL; + return ret; } new_state = (int)tmp; - if (!(tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_IMPLICT_ALUA)) { - printk(KERN_ERR "Unable to process implict configfs ALUA" - " transition while TPGS_IMPLICT_ALUA is diabled\n"); + if (!(tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_IMPLICIT_ALUA)) { + pr_err("Unable to process implicit configfs ALUA" + " transition while TPGS_IMPLICIT_ALUA is disabled\n"); + return -EINVAL; + } + if (tg_pt_gp->tg_pt_gp_alua_access_type & TPGS_EXPLICIT_ALUA && + new_state == ALUA_ACCESS_STATE_LBA_DEPENDENT) { + /* LBA DEPENDENT is only allowed with implicit ALUA */ + pr_err("Unable to process implicit configfs ALUA transition" + " while explicit ALUA management is enabled\n"); return -EINVAL; } - ret = core_alua_do_port_transition(tg_pt_gp, su_dev->se_dev_ptr, + ret = core_alua_do_port_transition(tg_pt_gp, dev, NULL, NULL, new_state, 0); return (!ret) ? count : -EINVAL; } @@ -2323,25 +2280,25 @@ static ssize_t target_core_alua_tg_pt_gp_store_attr_alua_access_status( unsigned long tmp; int new_status, ret; - if (!(tg_pt_gp->tg_pt_gp_valid_id)) { - printk(KERN_ERR "Unable to do set ALUA access status on non" + if (!tg_pt_gp->tg_pt_gp_valid_id) { + pr_err("Unable to do set ALUA access status on non" " valid tg_pt_gp ID: %hu\n", tg_pt_gp->tg_pt_gp_valid_id); return -EINVAL; } - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract new ALUA access status" + pr_err("Unable to extract new ALUA access status" " from %s\n", page); - return -EINVAL; + return ret; } new_status = (int)tmp; if ((new_status != ALUA_STATUS_NONE) && - (new_status != ALUA_STATUS_ALTERED_BY_EXPLICT_STPG) && - (new_status != ALUA_STATUS_ALTERED_BY_IMPLICT_ALUA)) { - printk(KERN_ERR "Illegal ALUA access status: 0x%02x\n", + (new_status != ALUA_STATUS_ALTERED_BY_EXPLICIT_STPG) && + (new_status != ALUA_STATUS_ALTERED_BY_IMPLICIT_ALUA)) { + pr_err("Illegal ALUA access status: 0x%02x\n", new_status); return -EINVAL; } @@ -2373,6 +2330,90 @@ static ssize_t target_core_alua_tg_pt_gp_store_attr_alua_access_type( SE_DEV_ALUA_TG_PT_ATTR(alua_access_type, S_IRUGO | S_IWUSR); /* + * alua_supported_states + */ + +#define SE_DEV_ALUA_SUPPORT_STATE_SHOW(_name, _var, _bit) \ +static ssize_t target_core_alua_tg_pt_gp_show_attr_alua_support_##_name( \ + struct t10_alua_tg_pt_gp *t, char *p) \ +{ \ + return sprintf(p, "%d\n", !!(t->_var & _bit)); \ +} + +#define SE_DEV_ALUA_SUPPORT_STATE_STORE(_name, _var, _bit) \ +static ssize_t target_core_alua_tg_pt_gp_store_attr_alua_support_##_name(\ + struct t10_alua_tg_pt_gp *t, const char *p, size_t c) \ +{ \ + unsigned long tmp; \ + int ret; \ + \ + if (!t->tg_pt_gp_valid_id) { \ + pr_err("Unable to do set ##_name ALUA state on non" \ + " valid tg_pt_gp ID: %hu\n", \ + t->tg_pt_gp_valid_id); \ + return -EINVAL; \ + } \ + \ + ret = kstrtoul(p, 0, &tmp); \ + if (ret < 0) { \ + pr_err("Invalid value '%s', must be '0' or '1'\n", p); \ + return -EINVAL; \ + } \ + if (tmp > 1) { \ + pr_err("Invalid value '%ld', must be '0' or '1'\n", tmp); \ + return -EINVAL; \ + } \ + if (!tmp) \ + t->_var |= _bit; \ + else \ + t->_var &= ~_bit; \ + \ + return c; \ +} + +SE_DEV_ALUA_SUPPORT_STATE_SHOW(transitioning, + tg_pt_gp_alua_supported_states, ALUA_T_SUP); +SE_DEV_ALUA_SUPPORT_STATE_STORE(transitioning, + tg_pt_gp_alua_supported_states, ALUA_T_SUP); +SE_DEV_ALUA_TG_PT_ATTR(alua_support_transitioning, S_IRUGO | S_IWUSR); + +SE_DEV_ALUA_SUPPORT_STATE_SHOW(offline, + tg_pt_gp_alua_supported_states, ALUA_O_SUP); +SE_DEV_ALUA_SUPPORT_STATE_STORE(offline, + tg_pt_gp_alua_supported_states, ALUA_O_SUP); +SE_DEV_ALUA_TG_PT_ATTR(alua_support_offline, S_IRUGO | S_IWUSR); + +SE_DEV_ALUA_SUPPORT_STATE_SHOW(lba_dependent, + tg_pt_gp_alua_supported_states, ALUA_LBD_SUP); +SE_DEV_ALUA_SUPPORT_STATE_STORE(lba_dependent, + tg_pt_gp_alua_supported_states, ALUA_LBD_SUP); +SE_DEV_ALUA_TG_PT_ATTR(alua_support_lba_dependent, S_IRUGO); + +SE_DEV_ALUA_SUPPORT_STATE_SHOW(unavailable, + tg_pt_gp_alua_supported_states, ALUA_U_SUP); +SE_DEV_ALUA_SUPPORT_STATE_STORE(unavailable, + tg_pt_gp_alua_supported_states, ALUA_U_SUP); +SE_DEV_ALUA_TG_PT_ATTR(alua_support_unavailable, S_IRUGO | S_IWUSR); + +SE_DEV_ALUA_SUPPORT_STATE_SHOW(standby, + tg_pt_gp_alua_supported_states, ALUA_S_SUP); +SE_DEV_ALUA_SUPPORT_STATE_STORE(standby, + tg_pt_gp_alua_supported_states, ALUA_S_SUP); +SE_DEV_ALUA_TG_PT_ATTR(alua_support_standby, S_IRUGO | S_IWUSR); + +SE_DEV_ALUA_SUPPORT_STATE_SHOW(active_optimized, + tg_pt_gp_alua_supported_states, ALUA_AO_SUP); +SE_DEV_ALUA_SUPPORT_STATE_STORE(active_optimized, + tg_pt_gp_alua_supported_states, ALUA_AO_SUP); +SE_DEV_ALUA_TG_PT_ATTR(alua_support_active_optimized, S_IRUGO | S_IWUSR); + +SE_DEV_ALUA_SUPPORT_STATE_SHOW(active_nonoptimized, + tg_pt_gp_alua_supported_states, ALUA_AN_SUP); +SE_DEV_ALUA_SUPPORT_STATE_STORE(active_nonoptimized, + tg_pt_gp_alua_supported_states, ALUA_AN_SUP); +SE_DEV_ALUA_TG_PT_ATTR(alua_support_active_nonoptimized, S_IRUGO | S_IWUSR); + +/* * alua_write_metadata */ static ssize_t target_core_alua_tg_pt_gp_show_attr_alua_write_metadata( @@ -2390,14 +2431,14 @@ static ssize_t target_core_alua_tg_pt_gp_store_attr_alua_write_metadata( unsigned long tmp; int ret; - ret = strict_strtoul(page, 0, &tmp); + ret = kstrtoul(page, 0, &tmp); if (ret < 0) { - printk(KERN_ERR "Unable to extract alua_write_metadata\n"); - return -EINVAL; + pr_err("Unable to extract alua_write_metadata\n"); + return ret; } if ((tmp != 0) && (tmp != 1)) { - printk(KERN_ERR "Illegal value for alua_write_metadata:" + pr_err("Illegal value for alua_write_metadata:" " %lu\n", tmp); return -EINVAL; } @@ -2452,6 +2493,26 @@ static ssize_t target_core_alua_tg_pt_gp_store_attr_trans_delay_msecs( SE_DEV_ALUA_TG_PT_ATTR(trans_delay_msecs, S_IRUGO | S_IWUSR); /* + * implicit_trans_secs + */ +static ssize_t target_core_alua_tg_pt_gp_show_attr_implicit_trans_secs( + struct t10_alua_tg_pt_gp *tg_pt_gp, + char *page) +{ + return core_alua_show_implicit_trans_secs(tg_pt_gp, page); +} + +static ssize_t target_core_alua_tg_pt_gp_store_attr_implicit_trans_secs( + struct t10_alua_tg_pt_gp *tg_pt_gp, + const char *page, + size_t count) +{ + return core_alua_store_implicit_trans_secs(tg_pt_gp, page, count); +} + +SE_DEV_ALUA_TG_PT_ATTR(implicit_trans_secs, S_IRUGO | S_IWUSR); + +/* * preferred */ @@ -2479,7 +2540,7 @@ static ssize_t target_core_alua_tg_pt_gp_show_attr_tg_pt_gp_id( struct t10_alua_tg_pt_gp *tg_pt_gp, char *page) { - if (!(tg_pt_gp->tg_pt_gp_valid_id)) + if (!tg_pt_gp->tg_pt_gp_valid_id) return 0; return sprintf(page, "%hu\n", tg_pt_gp->tg_pt_gp_id); @@ -2494,14 +2555,14 @@ static ssize_t target_core_alua_tg_pt_gp_store_attr_tg_pt_gp_id( unsigned long tg_pt_gp_id; int ret; - ret = strict_strtoul(page, 0, &tg_pt_gp_id); + ret = kstrtoul(page, 0, &tg_pt_gp_id); if (ret < 0) { - printk(KERN_ERR "strict_strtoul() returned %d for" + pr_err("kstrtoul() returned %d for" " tg_pt_gp_id\n", ret); - return -EINVAL; + return ret; } if (tg_pt_gp_id > 0x0000ffff) { - printk(KERN_ERR "ALUA tg_pt_gp_id: %lu exceeds maximum:" + pr_err("ALUA tg_pt_gp_id: %lu exceeds maximum:" " 0x0000ffff\n", tg_pt_gp_id); return -EINVAL; } @@ -2510,7 +2571,7 @@ static ssize_t target_core_alua_tg_pt_gp_store_attr_tg_pt_gp_id( if (ret < 0) return -EINVAL; - printk(KERN_INFO "Target_Core_ConfigFS: Set ALUA Target Port Group: " + pr_debug("Target_Core_ConfigFS: Set ALUA Target Port Group: " "core/alua/tg_pt_gps/%s to ID: %hu\n", config_item_name(&alua_tg_pt_gp_cg->cg_item), tg_pt_gp->tg_pt_gp_id); @@ -2544,14 +2605,14 @@ static ssize_t target_core_alua_tg_pt_gp_show_attr_members( lun = port->sep_lun; cur_len = snprintf(buf, TG_PT_GROUP_NAME_BUF, "%s/%s/tpgt_%hu" - "/%s\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_wwn(tpg), - TPG_TFO(tpg)->tpg_get_tag(tpg), + "/%s\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_wwn(tpg), + tpg->se_tpg_tfo->tpg_get_tag(tpg), config_item_name(&lun->lun_group.cg_item)); cur_len++; /* Extra byte for NULL terminator */ if ((cur_len + len) > PAGE_SIZE) { - printk(KERN_WARNING "Ran out of lu_gp_show_attr" + pr_warn("Ran out of lu_gp_show_attr" "_members buffer\n"); break; } @@ -2572,9 +2633,17 @@ static struct configfs_attribute *target_core_alua_tg_pt_gp_attrs[] = { &target_core_alua_tg_pt_gp_alua_access_state.attr, &target_core_alua_tg_pt_gp_alua_access_status.attr, &target_core_alua_tg_pt_gp_alua_access_type.attr, + &target_core_alua_tg_pt_gp_alua_support_transitioning.attr, + &target_core_alua_tg_pt_gp_alua_support_offline.attr, + &target_core_alua_tg_pt_gp_alua_support_lba_dependent.attr, + &target_core_alua_tg_pt_gp_alua_support_unavailable.attr, + &target_core_alua_tg_pt_gp_alua_support_standby.attr, + &target_core_alua_tg_pt_gp_alua_support_active_nonoptimized.attr, + &target_core_alua_tg_pt_gp_alua_support_active_optimized.attr, &target_core_alua_tg_pt_gp_alua_write_metadata.attr, &target_core_alua_tg_pt_gp_nonop_delay_msecs.attr, &target_core_alua_tg_pt_gp_trans_delay_msecs.attr, + &target_core_alua_tg_pt_gp_implicit_trans_secs.attr, &target_core_alua_tg_pt_gp_preferred.attr, &target_core_alua_tg_pt_gp_tg_pt_gp_id.attr, &target_core_alua_tg_pt_gp_members.attr, @@ -2612,12 +2681,11 @@ static struct config_group *target_core_alua_create_tg_pt_gp( struct t10_alua *alua = container_of(group, struct t10_alua, alua_tg_pt_gps_group); struct t10_alua_tg_pt_gp *tg_pt_gp; - struct se_subsystem_dev *su_dev = alua->t10_sub_dev; struct config_group *alua_tg_pt_gp_cg = NULL; struct config_item *alua_tg_pt_gp_ci = NULL; - tg_pt_gp = core_alua_allocate_tg_pt_gp(su_dev, name, 0); - if (!(tg_pt_gp)) + tg_pt_gp = core_alua_allocate_tg_pt_gp(alua->t10_dev, name, 0); + if (!tg_pt_gp) return NULL; alua_tg_pt_gp_cg = &tg_pt_gp->tg_pt_gp_group; @@ -2626,7 +2694,7 @@ static struct config_group *target_core_alua_create_tg_pt_gp( config_group_init_type_name(alua_tg_pt_gp_cg, name, &target_core_alua_tg_pt_gp_cit); - printk(KERN_INFO "Target_Core_ConfigFS: Allocated ALUA Target Port" + pr_debug("Target_Core_ConfigFS: Allocated ALUA Target Port" " Group: alua/tg_pt_gps/%s\n", config_item_name(alua_tg_pt_gp_ci)); @@ -2640,7 +2708,7 @@ static void target_core_alua_drop_tg_pt_gp( struct t10_alua_tg_pt_gp *tg_pt_gp = container_of(to_config_group(item), struct t10_alua_tg_pt_gp, tg_pt_gp_group); - printk(KERN_INFO "Target_Core_ConfigFS: Releasing ALUA Target Port" + pr_debug("Target_Core_ConfigFS: Releasing ALUA Target Port" " Group: alua/tg_pt_gps/%s, ID: %hu\n", config_item_name(item), tg_pt_gp->tg_pt_gp_id); /* @@ -2678,6 +2746,34 @@ static struct config_item_type target_core_alua_cit = { /* End functions for struct config_item_type target_core_alua_cit */ +/* Start functions for struct config_item_type target_core_stat_cit */ + +static struct config_group *target_core_stat_mkdir( + struct config_group *group, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} + +static void target_core_stat_rmdir( + struct config_group *group, + struct config_item *item) +{ + return; +} + +static struct configfs_group_operations target_core_stat_group_ops = { + .make_group = &target_core_stat_mkdir, + .drop_item = &target_core_stat_rmdir, +}; + +static struct config_item_type target_core_stat_cit = { + .ct_group_ops = &target_core_stat_group_ops, + .ct_owner = THIS_MODULE, +}; + +/* End functions for struct config_item_type target_core_stat_cit */ + /* Start functions for struct config_item_type target_core_hba_cit */ static struct config_group *target_core_make_subdev( @@ -2685,147 +2781,125 @@ static struct config_group *target_core_make_subdev( const char *name) { struct t10_alua_tg_pt_gp *tg_pt_gp; - struct se_subsystem_dev *se_dev; struct se_subsystem_api *t; struct config_item *hba_ci = &group->cg_item; struct se_hba *hba = item_to_hba(hba_ci); + struct se_device *dev; struct config_group *dev_cg = NULL, *tg_pt_gp_cg = NULL; + struct config_group *dev_stat_grp = NULL; + int errno = -ENOMEM, ret; - if (mutex_lock_interruptible(&hba->hba_access_mutex)) - return NULL; - + ret = mutex_lock_interruptible(&hba->hba_access_mutex); + if (ret) + return ERR_PTR(ret); /* * Locate the struct se_subsystem_api from parent's struct se_hba. */ t = hba->transport; - se_dev = kzalloc(sizeof(struct se_subsystem_dev), GFP_KERNEL); - if (!se_dev) { - printk(KERN_ERR "Unable to allocate memory for" - " struct se_subsystem_dev\n"); - goto unlock; - } - INIT_LIST_HEAD(&se_dev->g_se_dev_list); - INIT_LIST_HEAD(&se_dev->t10_wwn.t10_vpd_list); - spin_lock_init(&se_dev->t10_wwn.t10_vpd_lock); - INIT_LIST_HEAD(&se_dev->t10_reservation.registration_list); - INIT_LIST_HEAD(&se_dev->t10_reservation.aptpl_reg_list); - spin_lock_init(&se_dev->t10_reservation.registration_lock); - spin_lock_init(&se_dev->t10_reservation.aptpl_reg_lock); - INIT_LIST_HEAD(&se_dev->t10_alua.tg_pt_gps_list); - spin_lock_init(&se_dev->t10_alua.tg_pt_gps_lock); - spin_lock_init(&se_dev->se_dev_lock); - se_dev->t10_reservation.pr_aptpl_buf_len = PR_APTPL_BUF_LEN; - se_dev->t10_wwn.t10_sub_dev = se_dev; - se_dev->t10_alua.t10_sub_dev = se_dev; - se_dev->se_dev_attrib.da_sub_dev = se_dev; - - se_dev->se_dev_hba = hba; - dev_cg = &se_dev->se_dev_group; - - dev_cg->default_groups = kzalloc(sizeof(struct config_group) * 6, + dev = target_alloc_device(hba, name); + if (!dev) + goto out_unlock; + + dev_cg = &dev->dev_group; + + dev_cg->default_groups = kmalloc(sizeof(struct config_group *) * 6, GFP_KERNEL); - if (!(dev_cg->default_groups)) - goto out; - /* - * Set se_dev_su_ptr from struct se_subsystem_api returned void ptr - * for ->allocate_virtdevice() - * - * se_dev->se_dev_ptr will be set after ->create_virtdev() - * has been called successfully in the next level up in the - * configfs tree for device object's struct config_group. - */ - se_dev->se_dev_su_ptr = t->allocate_virtdevice(hba, name); - if (!(se_dev->se_dev_su_ptr)) { - printk(KERN_ERR "Unable to locate subsystem dependent pointer" - " from allocate_virtdevice()\n"); - goto out; - } - spin_lock(&se_global->g_device_lock); - list_add_tail(&se_dev->g_se_dev_list, &se_global->g_se_dev_list); - spin_unlock(&se_global->g_device_lock); + if (!dev_cg->default_groups) + goto out_free_device; - config_group_init_type_name(&se_dev->se_dev_group, name, - &target_core_dev_cit); - config_group_init_type_name(&se_dev->se_dev_attrib.da_group, "attrib", + config_group_init_type_name(dev_cg, name, &target_core_dev_cit); + config_group_init_type_name(&dev->dev_attrib.da_group, "attrib", &target_core_dev_attrib_cit); - config_group_init_type_name(&se_dev->se_dev_pr_group, "pr", + config_group_init_type_name(&dev->dev_pr_group, "pr", &target_core_dev_pr_cit); - config_group_init_type_name(&se_dev->t10_wwn.t10_wwn_group, "wwn", + config_group_init_type_name(&dev->t10_wwn.t10_wwn_group, "wwn", &target_core_dev_wwn_cit); - config_group_init_type_name(&se_dev->t10_alua.alua_tg_pt_gps_group, + config_group_init_type_name(&dev->t10_alua.alua_tg_pt_gps_group, "alua", &target_core_alua_tg_pt_gps_cit); - dev_cg->default_groups[0] = &se_dev->se_dev_attrib.da_group; - dev_cg->default_groups[1] = &se_dev->se_dev_pr_group; - dev_cg->default_groups[2] = &se_dev->t10_wwn.t10_wwn_group; - dev_cg->default_groups[3] = &se_dev->t10_alua.alua_tg_pt_gps_group; - dev_cg->default_groups[4] = NULL; + config_group_init_type_name(&dev->dev_stat_grps.stat_group, + "statistics", &target_core_stat_cit); + + dev_cg->default_groups[0] = &dev->dev_attrib.da_group; + dev_cg->default_groups[1] = &dev->dev_pr_group; + dev_cg->default_groups[2] = &dev->t10_wwn.t10_wwn_group; + dev_cg->default_groups[3] = &dev->t10_alua.alua_tg_pt_gps_group; + dev_cg->default_groups[4] = &dev->dev_stat_grps.stat_group; + dev_cg->default_groups[5] = NULL; /* - * Add core/$HBA/$DEV/alua/tg_pt_gps/default_tg_pt_gp + * Add core/$HBA/$DEV/alua/default_tg_pt_gp */ - tg_pt_gp = core_alua_allocate_tg_pt_gp(se_dev, "default_tg_pt_gp", 1); - if (!(tg_pt_gp)) - goto out; + tg_pt_gp = core_alua_allocate_tg_pt_gp(dev, "default_tg_pt_gp", 1); + if (!tg_pt_gp) + goto out_free_dev_cg_default_groups; + dev->t10_alua.default_tg_pt_gp = tg_pt_gp; - tg_pt_gp_cg = &T10_ALUA(se_dev)->alua_tg_pt_gps_group; - tg_pt_gp_cg->default_groups = kzalloc(sizeof(struct config_group) * 2, + tg_pt_gp_cg = &dev->t10_alua.alua_tg_pt_gps_group; + tg_pt_gp_cg->default_groups = kmalloc(sizeof(struct config_group *) * 2, GFP_KERNEL); - if (!(tg_pt_gp_cg->default_groups)) { - printk(KERN_ERR "Unable to allocate tg_pt_gp_cg->" + if (!tg_pt_gp_cg->default_groups) { + pr_err("Unable to allocate tg_pt_gp_cg->" "default_groups\n"); - goto out; + goto out_free_tg_pt_gp; } config_group_init_type_name(&tg_pt_gp->tg_pt_gp_group, "default_tg_pt_gp", &target_core_alua_tg_pt_gp_cit); tg_pt_gp_cg->default_groups[0] = &tg_pt_gp->tg_pt_gp_group; tg_pt_gp_cg->default_groups[1] = NULL; - T10_ALUA(se_dev)->default_tg_pt_gp = tg_pt_gp; - - printk(KERN_INFO "Target_Core_ConfigFS: Allocated struct se_subsystem_dev:" - " %p se_dev_su_ptr: %p\n", se_dev, se_dev->se_dev_su_ptr); + /* + * Add core/$HBA/$DEV/statistics/ default groups + */ + dev_stat_grp = &dev->dev_stat_grps.stat_group; + dev_stat_grp->default_groups = kmalloc(sizeof(struct config_group *) * 4, + GFP_KERNEL); + if (!dev_stat_grp->default_groups) { + pr_err("Unable to allocate dev_stat_grp->default_groups\n"); + goto out_free_tg_pt_gp_cg_default_groups; + } + target_stat_setup_dev_default_groups(dev); mutex_unlock(&hba->hba_access_mutex); - return &se_dev->se_dev_group; -out: - if (T10_ALUA(se_dev)->default_tg_pt_gp) { - core_alua_free_tg_pt_gp(T10_ALUA(se_dev)->default_tg_pt_gp); - T10_ALUA(se_dev)->default_tg_pt_gp = NULL; - } - if (tg_pt_gp_cg) - kfree(tg_pt_gp_cg->default_groups); - if (dev_cg) - kfree(dev_cg->default_groups); - if (se_dev->se_dev_su_ptr) - t->free_device(se_dev->se_dev_su_ptr); - kfree(se_dev); -unlock: + return dev_cg; + +out_free_tg_pt_gp_cg_default_groups: + kfree(tg_pt_gp_cg->default_groups); +out_free_tg_pt_gp: + core_alua_free_tg_pt_gp(tg_pt_gp); +out_free_dev_cg_default_groups: + kfree(dev_cg->default_groups); +out_free_device: + target_free_device(dev); +out_unlock: mutex_unlock(&hba->hba_access_mutex); - return NULL; + return ERR_PTR(errno); } static void target_core_drop_subdev( struct config_group *group, struct config_item *item) { - struct se_subsystem_dev *se_dev = container_of(to_config_group(item), - struct se_subsystem_dev, se_dev_group); + struct config_group *dev_cg = to_config_group(item); + struct se_device *dev = + container_of(dev_cg, struct se_device, dev_group); struct se_hba *hba; - struct se_subsystem_api *t; struct config_item *df_item; - struct config_group *dev_cg, *tg_pt_gp_cg; + struct config_group *tg_pt_gp_cg, *dev_stat_grp; int i; - hba = item_to_hba(&se_dev->se_dev_hba->hba_group.cg_item); + hba = item_to_hba(&dev->se_hba->hba_group.cg_item); mutex_lock(&hba->hba_access_mutex); - t = hba->transport; - spin_lock(&se_global->g_device_lock); - list_del(&se_dev->g_se_dev_list); - spin_unlock(&se_global->g_device_lock); + dev_stat_grp = &dev->dev_stat_grps.stat_group; + for (i = 0; dev_stat_grp->default_groups[i]; i++) { + df_item = &dev_stat_grp->default_groups[i]->cg_item; + dev_stat_grp->default_groups[i] = NULL; + config_item_put(df_item); + } + kfree(dev_stat_grp->default_groups); - tg_pt_gp_cg = &T10_ALUA(se_dev)->alua_tg_pt_gps_group; + tg_pt_gp_cg = &dev->t10_alua.alua_tg_pt_gps_group; for (i = 0; tg_pt_gp_cg->default_groups[i]; i++) { df_item = &tg_pt_gp_cg->default_groups[i]->cg_item; tg_pt_gp_cg->default_groups[i] = NULL; @@ -2836,17 +2910,15 @@ static void target_core_drop_subdev( * core_alua_free_tg_pt_gp() is called from ->default_tg_pt_gp * directly from target_core_alua_tg_pt_gp_release(). */ - T10_ALUA(se_dev)->default_tg_pt_gp = NULL; + dev->t10_alua.default_tg_pt_gp = NULL; - dev_cg = &se_dev->se_dev_group; for (i = 0; dev_cg->default_groups[i]; i++) { df_item = &dev_cg->default_groups[i]->cg_item; dev_cg->default_groups[i] = NULL; config_item_put(df_item); } /* - * The releasing of se_dev and associated se_dev->se_dev_ptr is done - * from target_core_dev_item_ops->release() ->target_core_dev_release(). + * se_dev is released from target_core_dev_item_ops->release() */ config_item_put(item); mutex_unlock(&hba->hba_access_mutex); @@ -2903,19 +2975,16 @@ static ssize_t target_core_hba_store_attr_hba_mode(struct se_hba *hba, if (transport->pmode_enable_hba == NULL) return -EINVAL; - ret = strict_strtoul(page, 0, &mode_flag); + ret = kstrtoul(page, 0, &mode_flag); if (ret < 0) { - printk(KERN_ERR "Unable to extract hba mode flag: %d\n", ret); - return -EINVAL; + pr_err("Unable to extract hba mode flag: %d\n", ret); + return ret; } - spin_lock(&hba->device_lock); - if (!(list_empty(&hba->hba_dev_list))) { - printk(KERN_ERR "Unable to set hba_mode with active devices\n"); - spin_unlock(&hba->device_lock); + if (hba->dev_count) { + pr_err("Unable to set hba_mode with active devices\n"); return -EINVAL; } - spin_unlock(&hba->device_lock); ret = transport->pmode_enable_hba(hba, mode_flag); if (ret < 0) @@ -2969,8 +3038,8 @@ static struct config_group *target_core_call_addhbatotarget( int ret; memset(buf, 0, TARGET_CORE_NAME_MAX_LEN); - if (strlen(name) > TARGET_CORE_NAME_MAX_LEN) { - printk(KERN_ERR "Passed *name strlen(): %d exceeds" + if (strlen(name) >= TARGET_CORE_NAME_MAX_LEN) { + pr_err("Passed *name strlen(): %d exceeds" " TARGET_CORE_NAME_MAX_LEN: %d\n", (int)strlen(name), TARGET_CORE_NAME_MAX_LEN); return ERR_PTR(-ENAMETOOLONG); @@ -2978,8 +3047,8 @@ static struct config_group *target_core_call_addhbatotarget( snprintf(buf, TARGET_CORE_NAME_MAX_LEN, "%s", name); str = strstr(buf, "_"); - if (!(str)) { - printk(KERN_ERR "Unable to locate \"_\" for $SUBSYSTEM_PLUGIN_$HOST_ID\n"); + if (!str) { + pr_err("Unable to locate \"_\" for $SUBSYSTEM_PLUGIN_$HOST_ID\n"); return ERR_PTR(-EINVAL); } se_plugin_str = buf; @@ -2988,7 +3057,7 @@ static struct config_group *target_core_call_addhbatotarget( * Namely rd_direct and rd_mcp.. */ str2 = strstr(str+1, "_"); - if ((str2)) { + if (str2) { *str2 = '\0'; /* Terminate for *se_plugin_str */ str2++; /* Skip to start of plugin dependent ID */ str = str2; @@ -2997,17 +3066,16 @@ static struct config_group *target_core_call_addhbatotarget( str++; /* Skip to start of plugin dependent ID */ } - ret = strict_strtoul(str, 0, &plugin_dep_id); + ret = kstrtoul(str, 0, &plugin_dep_id); if (ret < 0) { - printk(KERN_ERR "strict_strtoul() returned %d for" + pr_err("kstrtoul() returned %d for" " plugin_dep_id\n", ret); - return ERR_PTR(-EINVAL); + return ERR_PTR(ret); } /* * Load up TCM subsystem plugins if they have not already been loaded. */ - if (transport_subsystem_check_init() < 0) - return ERR_PTR(-EINVAL); + transport_subsystem_check_init(); hba = core_alloc_hba(se_plugin_str, plugin_dep_id, 0); if (IS_ERR(hba)) @@ -3044,7 +3112,7 @@ static struct config_item_type target_core_cit = { /* Stop functions for struct config_item_type target_core_hba_cit */ -static int target_core_init_configfs(void) +static int __init target_core_init_configfs(void) { struct config_group *target_cg, *hba_cg = NULL, *alua_cg = NULL; struct config_group *lu_gp_cg = NULL; @@ -3052,7 +3120,7 @@ static int target_core_init_configfs(void) struct t10_alua_lu_gp *lu_gp; int ret; - printk(KERN_INFO "TARGET_CORE[0]: Loading Generic Kernel Storage" + pr_debug("TARGET_CORE[0]: Loading Generic Kernel Storage" " Engine: %s on %s/%s on "UTS_RELEASE"\n", TARGET_CORE_VERSION, utsname()->sysname, utsname()->machine); @@ -3060,70 +3128,73 @@ static int target_core_init_configfs(void) config_group_init(&subsys->su_group); mutex_init(&subsys->su_mutex); - INIT_LIST_HEAD(&g_tf_list); - mutex_init(&g_tf_lock); - init_scsi_index_table(); - ret = init_se_global(); + ret = init_se_kmem_caches(); if (ret < 0) - return -1; + return ret; /* * Create $CONFIGFS/target/core default group for HBA <-> Storage Object * and ALUA Logical Unit Group and Target Port Group infrastructure. */ target_cg = &subsys->su_group; - target_cg->default_groups = kzalloc(sizeof(struct config_group) * 2, + target_cg->default_groups = kmalloc(sizeof(struct config_group *) * 2, GFP_KERNEL); - if (!(target_cg->default_groups)) { - printk(KERN_ERR "Unable to allocate target_cg->default_groups\n"); + if (!target_cg->default_groups) { + pr_err("Unable to allocate target_cg->default_groups\n"); + ret = -ENOMEM; goto out_global; } - config_group_init_type_name(&se_global->target_core_hbagroup, + config_group_init_type_name(&target_core_hbagroup, "core", &target_core_cit); - target_cg->default_groups[0] = &se_global->target_core_hbagroup; + target_cg->default_groups[0] = &target_core_hbagroup; target_cg->default_groups[1] = NULL; /* * Create ALUA infrastructure under /sys/kernel/config/target/core/alua/ */ - hba_cg = &se_global->target_core_hbagroup; - hba_cg->default_groups = kzalloc(sizeof(struct config_group) * 2, + hba_cg = &target_core_hbagroup; + hba_cg->default_groups = kmalloc(sizeof(struct config_group *) * 2, GFP_KERNEL); - if (!(hba_cg->default_groups)) { - printk(KERN_ERR "Unable to allocate hba_cg->default_groups\n"); + if (!hba_cg->default_groups) { + pr_err("Unable to allocate hba_cg->default_groups\n"); + ret = -ENOMEM; goto out_global; } - config_group_init_type_name(&se_global->alua_group, + config_group_init_type_name(&alua_group, "alua", &target_core_alua_cit); - hba_cg->default_groups[0] = &se_global->alua_group; + hba_cg->default_groups[0] = &alua_group; hba_cg->default_groups[1] = NULL; /* * Add ALUA Logical Unit Group and Target Port Group ConfigFS * groups under /sys/kernel/config/target/core/alua/ */ - alua_cg = &se_global->alua_group; - alua_cg->default_groups = kzalloc(sizeof(struct config_group) * 2, + alua_cg = &alua_group; + alua_cg->default_groups = kmalloc(sizeof(struct config_group *) * 2, GFP_KERNEL); - if (!(alua_cg->default_groups)) { - printk(KERN_ERR "Unable to allocate alua_cg->default_groups\n"); + if (!alua_cg->default_groups) { + pr_err("Unable to allocate alua_cg->default_groups\n"); + ret = -ENOMEM; goto out_global; } - config_group_init_type_name(&se_global->alua_lu_gps_group, + config_group_init_type_name(&alua_lu_gps_group, "lu_gps", &target_core_alua_lu_gps_cit); - alua_cg->default_groups[0] = &se_global->alua_lu_gps_group; + alua_cg->default_groups[0] = &alua_lu_gps_group; alua_cg->default_groups[1] = NULL; /* * Add core/alua/lu_gps/default_lu_gp */ lu_gp = core_alua_allocate_lu_gp("default_lu_gp", 1); - if (IS_ERR(lu_gp)) + if (IS_ERR(lu_gp)) { + ret = -ENOMEM; goto out_global; + } - lu_gp_cg = &se_global->alua_lu_gps_group; - lu_gp_cg->default_groups = kzalloc(sizeof(struct config_group) * 2, + lu_gp_cg = &alua_lu_gps_group; + lu_gp_cg->default_groups = kmalloc(sizeof(struct config_group *) * 2, GFP_KERNEL); - if (!(lu_gp_cg->default_groups)) { - printk(KERN_ERR "Unable to allocate lu_gp_cg->default_groups\n"); + if (!lu_gp_cg->default_groups) { + pr_err("Unable to allocate lu_gp_cg->default_groups\n"); + ret = -ENOMEM; goto out_global; } @@ -3131,17 +3202,17 @@ static int target_core_init_configfs(void) &target_core_alua_lu_gp_cit); lu_gp_cg->default_groups[0] = &lu_gp->lu_gp_group; lu_gp_cg->default_groups[1] = NULL; - se_global->default_lu_gp = lu_gp; + default_lu_gp = lu_gp; /* * Register the target_core_mod subsystem with configfs. */ ret = configfs_register_subsystem(subsys); if (ret < 0) { - printk(KERN_ERR "Error %d while registering subsystem %s\n", + pr_err("Error %d while registering subsystem %s\n", ret, subsys->su_group.cg_item.ci_namebuf); goto out_global; } - printk(KERN_INFO "TARGET_CORE[0]: Initialized ConfigFS Fabric" + pr_debug("TARGET_CORE[0]: Initialized ConfigFS Fabric" " Infrastructure: "TARGET_CORE_CONFIGFS_VERSION" on %s/%s" " on "UTS_RELEASE"\n", utsname()->sysname, utsname()->machine); /* @@ -3151,7 +3222,12 @@ static int target_core_init_configfs(void) if (ret < 0) goto out; - if (core_dev_setup_virtual_lun0() < 0) + ret = core_dev_setup_virtual_lun0(); + if (ret < 0) + goto out; + + ret = target_xcopy_setup_pt(); + if (ret < 0) goto out; return 0; @@ -3161,9 +3237,9 @@ out: core_dev_release_virtual_lun0(); rd_module_exit(); out_global: - if (se_global->default_lu_gp) { - core_alua_free_lu_gp(se_global->default_lu_gp); - se_global->default_lu_gp = NULL; + if (default_lu_gp) { + core_alua_free_lu_gp(default_lu_gp); + default_lu_gp = NULL; } if (lu_gp_cg) kfree(lu_gp_cg->default_groups); @@ -3172,21 +3248,20 @@ out_global: if (hba_cg) kfree(hba_cg->default_groups); kfree(target_cg->default_groups); - release_se_global(); - return -1; + release_se_kmem_caches(); + return ret; } -static void target_core_exit_configfs(void) +static void __exit target_core_exit_configfs(void) { struct configfs_subsystem *subsys; struct config_group *hba_cg, *alua_cg, *lu_gp_cg; struct config_item *item; int i; - se_global->in_shutdown = 1; subsys = target_core_subsystem[0]; - lu_gp_cg = &se_global->alua_lu_gps_group; + lu_gp_cg = &alua_lu_gps_group; for (i = 0; lu_gp_cg->default_groups[i]; i++) { item = &lu_gp_cg->default_groups[i]->cg_item; lu_gp_cg->default_groups[i] = NULL; @@ -3195,7 +3270,7 @@ static void target_core_exit_configfs(void) kfree(lu_gp_cg->default_groups); lu_gp_cg->default_groups = NULL; - alua_cg = &se_global->alua_group; + alua_cg = &alua_group; for (i = 0; alua_cg->default_groups[i]; i++) { item = &alua_cg->default_groups[i]->cg_item; alua_cg->default_groups[i] = NULL; @@ -3204,7 +3279,7 @@ static void target_core_exit_configfs(void) kfree(alua_cg->default_groups); alua_cg->default_groups = NULL; - hba_cg = &se_global->target_core_hbagroup; + hba_cg = &target_core_hbagroup; for (i = 0; hba_cg->default_groups[i]; i++) { item = &hba_cg->default_groups[i]->cg_item; hba_cg->default_groups[i] = NULL; @@ -3219,17 +3294,16 @@ static void target_core_exit_configfs(void) configfs_unregister_subsystem(subsys); kfree(subsys->su_group.default_groups); - core_alua_free_lu_gp(se_global->default_lu_gp); - se_global->default_lu_gp = NULL; + core_alua_free_lu_gp(default_lu_gp); + default_lu_gp = NULL; - printk(KERN_INFO "TARGET_CORE[0]: Released ConfigFS Fabric" + pr_debug("TARGET_CORE[0]: Released ConfigFS Fabric" " Infrastructure\n"); core_dev_release_virtual_lun0(); rd_module_exit(); - release_se_global(); - - return; + target_xcopy_release_pt(); + release_se_kmem_caches(); } MODULE_DESCRIPTION("Target_Core_Mod/ConfigFS"); diff --git a/drivers/target/target_core_device.c b/drivers/target/target_core_device.c index 5da051a07fa..98da9016715 100644 --- a/drivers/target/target_core_device.c +++ b/drivers/target/target_core_device.c @@ -1,13 +1,10 @@ /******************************************************************************* * Filename: target_core_device.c (based on iscsi_target_device.c) * - * This file contains the iSCSI Virtual Device and Disk Transport + * This file contains the TCM Virtual Device and Disk Transport * agnostic related functions. * - * Copyright (c) 2003, 2004, 2005 PyX Technologies, Inc. - * Copyright (c) 2005-2006 SBE, Inc. All Rights Reserved. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2003-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -33,197 +30,160 @@ #include <linux/timer.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> #include <linux/kthread.h> #include <linux/in.h> +#include <linux/export.h> #include <net/sock.h> #include <net/tcp.h> #include <scsi/scsi.h> +#include <scsi/scsi_device.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_tpg.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> +#include "target_core_internal.h" #include "target_core_alua.h" -#include "target_core_hba.h" #include "target_core_pr.h" #include "target_core_ua.h" -static void se_dev_start(struct se_device *dev); -static void se_dev_stop(struct se_device *dev); +DEFINE_MUTEX(g_device_mutex); +LIST_HEAD(g_device_list); -int transport_get_lun_for_cmd( - struct se_cmd *se_cmd, - unsigned char *cdb, - u32 unpacked_lun) +static struct se_hba *lun0_hba; +/* not static, needed by tpg.c */ +struct se_device *g_lun0_dev; + +sense_reason_t +transport_lookup_cmd_lun(struct se_cmd *se_cmd, u32 unpacked_lun) { - struct se_dev_entry *deve; struct se_lun *se_lun = NULL; - struct se_session *se_sess = SE_SESS(se_cmd); + struct se_session *se_sess = se_cmd->se_sess; + struct se_device *dev; unsigned long flags; - int read_only = 0; - spin_lock_irq(&SE_NODE_ACL(se_sess)->device_list_lock); - deve = se_cmd->se_deve = - &SE_NODE_ACL(se_sess)->device_list[unpacked_lun]; - if (deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS) { - if (se_cmd) { - deve->total_cmds++; - deve->total_bytes += se_cmd->data_length; - - if (se_cmd->data_direction == DMA_TO_DEVICE) { - if (deve->lun_flags & - TRANSPORT_LUNFLAGS_READ_ONLY) { - read_only = 1; - goto out; - } - deve->write_bytes += se_cmd->data_length; - } else if (se_cmd->data_direction == - DMA_FROM_DEVICE) { - deve->read_bytes += se_cmd->data_length; - } + if (unpacked_lun >= TRANSPORT_MAX_LUNS_PER_TPG) + return TCM_NON_EXISTENT_LUN; + + spin_lock_irqsave(&se_sess->se_node_acl->device_list_lock, flags); + se_cmd->se_deve = se_sess->se_node_acl->device_list[unpacked_lun]; + if (se_cmd->se_deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS) { + struct se_dev_entry *deve = se_cmd->se_deve; + + deve->total_cmds++; + + if ((se_cmd->data_direction == DMA_TO_DEVICE) && + (deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY)) { + pr_err("TARGET_CORE[%s]: Detected WRITE_PROTECTED LUN" + " Access for 0x%08x\n", + se_cmd->se_tfo->get_fabric_name(), + unpacked_lun); + spin_unlock_irqrestore(&se_sess->se_node_acl->device_list_lock, flags); + return TCM_WRITE_PROTECTED; } - deve->deve_cmds++; - se_lun = se_cmd->se_lun = deve->se_lun; + if (se_cmd->data_direction == DMA_TO_DEVICE) + deve->write_bytes += se_cmd->data_length; + else if (se_cmd->data_direction == DMA_FROM_DEVICE) + deve->read_bytes += se_cmd->data_length; + + se_lun = deve->se_lun; + se_cmd->se_lun = deve->se_lun; se_cmd->pr_res_key = deve->pr_res_key; se_cmd->orig_fe_lun = unpacked_lun; - se_cmd->se_orig_obj_ptr = SE_LUN(se_cmd)->lun_se_dev; se_cmd->se_cmd_flags |= SCF_SE_LUN_CMD; + + percpu_ref_get(&se_lun->lun_ref); + se_cmd->lun_ref_active = true; } -out: - spin_unlock_irq(&SE_NODE_ACL(se_sess)->device_list_lock); + spin_unlock_irqrestore(&se_sess->se_node_acl->device_list_lock, flags); if (!se_lun) { - if (read_only) { - se_cmd->scsi_sense_reason = TCM_WRITE_PROTECTED; - se_cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - printk("TARGET_CORE[%s]: Detected WRITE_PROTECTED LUN" + /* + * Use the se_portal_group->tpg_virt_lun0 to allow for + * REPORT_LUNS, et al to be returned when no active + * MappedLUN=0 exists for this Initiator Port. + */ + if (unpacked_lun != 0) { + pr_err("TARGET_CORE[%s]: Detected NON_EXISTENT_LUN" " Access for 0x%08x\n", - CMD_TFO(se_cmd)->get_fabric_name(), + se_cmd->se_tfo->get_fabric_name(), unpacked_lun); - return -1; - } else { - /* - * Use the se_portal_group->tpg_virt_lun0 to allow for - * REPORT_LUNS, et al to be returned when no active - * MappedLUN=0 exists for this Initiator Port. - */ - if (unpacked_lun != 0) { - se_cmd->scsi_sense_reason = TCM_NON_EXISTENT_LUN; - se_cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - printk("TARGET_CORE[%s]: Detected NON_EXISTENT_LUN" - " Access for 0x%08x\n", - CMD_TFO(se_cmd)->get_fabric_name(), - unpacked_lun); - return -1; - } - /* - * Force WRITE PROTECT for virtual LUN 0 - */ - if ((se_cmd->data_direction != DMA_FROM_DEVICE) && - (se_cmd->data_direction != DMA_NONE)) { - se_cmd->scsi_sense_reason = TCM_WRITE_PROTECTED; - se_cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - return -1; - } -#if 0 - printk("TARGET_CORE[%s]: Using virtual LUN0! :-)\n", - CMD_TFO(se_cmd)->get_fabric_name()); -#endif - se_lun = se_cmd->se_lun = &se_sess->se_tpg->tpg_virt_lun0; - se_cmd->orig_fe_lun = 0; - se_cmd->se_orig_obj_ptr = SE_LUN(se_cmd)->lun_se_dev; - se_cmd->se_cmd_flags |= SCF_SE_LUN_CMD; + return TCM_NON_EXISTENT_LUN; } - } - /* - * Determine if the struct se_lun is online. - */ -/* #warning FIXME: Check for LUN_RESET + UNIT Attention */ - if (se_dev_check_online(se_lun->lun_se_dev) != 0) { - se_cmd->scsi_sense_reason = TCM_NON_EXISTENT_LUN; - se_cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - return -1; + /* + * Force WRITE PROTECT for virtual LUN 0 + */ + if ((se_cmd->data_direction != DMA_FROM_DEVICE) && + (se_cmd->data_direction != DMA_NONE)) + return TCM_WRITE_PROTECTED; + + se_lun = &se_sess->se_tpg->tpg_virt_lun0; + se_cmd->se_lun = &se_sess->se_tpg->tpg_virt_lun0; + se_cmd->orig_fe_lun = 0; + se_cmd->se_cmd_flags |= SCF_SE_LUN_CMD; + + percpu_ref_get(&se_lun->lun_ref); + se_cmd->lun_ref_active = true; } - { - struct se_device *dev = se_lun->lun_se_dev; - spin_lock(&dev->stats_lock); - dev->num_cmds++; + /* Directly associate cmd with se_dev */ + se_cmd->se_dev = se_lun->lun_se_dev; + + dev = se_lun->lun_se_dev; + atomic_long_inc(&dev->num_cmds); if (se_cmd->data_direction == DMA_TO_DEVICE) - dev->write_bytes += se_cmd->data_length; + atomic_long_add(se_cmd->data_length, &dev->write_bytes); else if (se_cmd->data_direction == DMA_FROM_DEVICE) - dev->read_bytes += se_cmd->data_length; - spin_unlock(&dev->stats_lock); - } - - /* - * Add the iscsi_cmd_t to the struct se_lun's cmd list. This list is used - * for tracking state of struct se_cmds during LUN shutdown events. - */ - spin_lock_irqsave(&se_lun->lun_cmd_lock, flags); - list_add_tail(&se_cmd->se_lun_list, &se_lun->lun_cmd_list); - atomic_set(&T_TASK(se_cmd)->transport_lun_active, 1); -#if 0 - printk(KERN_INFO "Adding ITT: 0x%08x to LUN LIST[%d]\n", - CMD_TFO(se_cmd)->get_task_tag(se_cmd), se_lun->unpacked_lun); -#endif - spin_unlock_irqrestore(&se_lun->lun_cmd_lock, flags); + atomic_long_add(se_cmd->data_length, &dev->read_bytes); return 0; } -EXPORT_SYMBOL(transport_get_lun_for_cmd); +EXPORT_SYMBOL(transport_lookup_cmd_lun); -int transport_get_lun_for_tmr( - struct se_cmd *se_cmd, - u32 unpacked_lun) +int transport_lookup_tmr_lun(struct se_cmd *se_cmd, u32 unpacked_lun) { - struct se_device *dev = NULL; struct se_dev_entry *deve; struct se_lun *se_lun = NULL; - struct se_session *se_sess = SE_SESS(se_cmd); + struct se_session *se_sess = se_cmd->se_sess; struct se_tmr_req *se_tmr = se_cmd->se_tmr_req; + unsigned long flags; + + if (unpacked_lun >= TRANSPORT_MAX_LUNS_PER_TPG) + return -ENODEV; + + spin_lock_irqsave(&se_sess->se_node_acl->device_list_lock, flags); + se_cmd->se_deve = se_sess->se_node_acl->device_list[unpacked_lun]; + deve = se_cmd->se_deve; - spin_lock_irq(&SE_NODE_ACL(se_sess)->device_list_lock); - deve = se_cmd->se_deve = - &SE_NODE_ACL(se_sess)->device_list[unpacked_lun]; if (deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS) { - se_lun = se_cmd->se_lun = se_tmr->tmr_lun = deve->se_lun; - dev = se_tmr->tmr_dev = se_lun->lun_se_dev; + se_tmr->tmr_lun = deve->se_lun; + se_cmd->se_lun = deve->se_lun; + se_lun = deve->se_lun; se_cmd->pr_res_key = deve->pr_res_key; se_cmd->orig_fe_lun = unpacked_lun; - se_cmd->se_orig_obj_ptr = SE_LUN(se_cmd)->lun_se_dev; -/* se_cmd->se_cmd_flags |= SCF_SE_LUN_CMD; */ } - spin_unlock_irq(&SE_NODE_ACL(se_sess)->device_list_lock); + spin_unlock_irqrestore(&se_sess->se_node_acl->device_list_lock, flags); if (!se_lun) { - printk(KERN_INFO "TARGET_CORE[%s]: Detected NON_EXISTENT_LUN" + pr_debug("TARGET_CORE[%s]: Detected NON_EXISTENT_LUN" " Access for 0x%08x\n", - CMD_TFO(se_cmd)->get_fabric_name(), + se_cmd->se_tfo->get_fabric_name(), unpacked_lun); - se_cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - return -1; - } - /* - * Determine if the struct se_lun is online. - */ -/* #warning FIXME: Check for LUN_RESET + UNIT Attention */ - if (se_dev_check_online(se_lun->lun_se_dev) != 0) { - se_cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - return -1; + return -ENODEV; } - spin_lock(&dev->se_tmr_lock); - list_add_tail(&se_tmr->tmr_list, &dev->dev_tmr_list); - spin_unlock(&dev->se_tmr_lock); + /* Directly associate cmd with se_dev */ + se_cmd->se_dev = se_lun->lun_se_dev; + se_tmr->tmr_dev = se_lun->lun_se_dev; + + spin_lock_irqsave(&se_tmr->tmr_dev->se_tmr_lock, flags); + list_add_tail(&se_tmr->tmr_list, &se_tmr->tmr_dev->dev_tmr_list); + spin_unlock_irqrestore(&se_tmr->tmr_dev->se_tmr_lock, flags); return 0; } -EXPORT_SYMBOL(transport_get_lun_for_tmr); +EXPORT_SYMBOL(transport_lookup_tmr_lun); /* * This function is called from core_scsi3_emulate_pro_register_and_move() @@ -242,30 +202,30 @@ struct se_dev_entry *core_get_se_deve_from_rtpi( spin_lock_irq(&nacl->device_list_lock); for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - deve = &nacl->device_list[i]; + deve = nacl->device_list[i]; if (!(deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS)) continue; lun = deve->se_lun; - if (!(lun)) { - printk(KERN_ERR "%s device entries device pointer is" + if (!lun) { + pr_err("%s device entries device pointer is" " NULL, but Initiator has access.\n", - TPG_TFO(tpg)->get_fabric_name()); + tpg->se_tpg_tfo->get_fabric_name()); continue; } port = lun->lun_sep; - if (!(port)) { - printk(KERN_ERR "%s device entries device pointer is" + if (!port) { + pr_err("%s device entries device pointer is" " NULL, but Initiator has access.\n", - TPG_TFO(tpg)->get_fabric_name()); + tpg->se_tpg_tfo->get_fabric_name()); continue; } if (port->sep_rtpi != rtpi) continue; atomic_inc(&deve->pr_ref_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock_irq(&nacl->device_list_lock); return deve; @@ -288,44 +248,32 @@ int core_free_device_list_for_node( spin_lock_irq(&nacl->device_list_lock); for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - deve = &nacl->device_list[i]; + deve = nacl->device_list[i]; if (!(deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS)) continue; if (!deve->se_lun) { - printk(KERN_ERR "%s device entries device pointer is" + pr_err("%s device entries device pointer is" " NULL, but Initiator has access.\n", - TPG_TFO(tpg)->get_fabric_name()); + tpg->se_tpg_tfo->get_fabric_name()); continue; } lun = deve->se_lun; spin_unlock_irq(&nacl->device_list_lock); - core_update_device_list_for_node(lun, NULL, deve->mapped_lun, - TRANSPORT_LUNFLAGS_NO_ACCESS, nacl, tpg, 0); + core_disable_device_list_for_node(lun, NULL, deve->mapped_lun, + TRANSPORT_LUNFLAGS_NO_ACCESS, nacl, tpg); spin_lock_irq(&nacl->device_list_lock); } spin_unlock_irq(&nacl->device_list_lock); - kfree(nacl->device_list); + array_free(nacl->device_list, TRANSPORT_MAX_LUNS_PER_TPG); nacl->device_list = NULL; return 0; } -void core_dec_lacl_count(struct se_node_acl *se_nacl, struct se_cmd *se_cmd) -{ - struct se_dev_entry *deve; - - spin_lock_irq(&se_nacl->device_list_lock); - deve = &se_nacl->device_list[se_cmd->orig_fe_lun]; - deve->deve_cmds--; - spin_unlock_irq(&se_nacl->device_list_lock); - - return; -} - void core_update_device_list_access( u32 mapped_lun, u32 lun_access, @@ -334,7 +282,7 @@ void core_update_device_list_access( struct se_dev_entry *deve; spin_lock_irq(&nacl->device_list_lock); - deve = &nacl->device_list[mapped_lun]; + deve = nacl->device_list[mapped_lun]; if (lun_access & TRANSPORT_LUNFLAGS_READ_WRITE) { deve->lun_flags &= ~TRANSPORT_LUNFLAGS_READ_ONLY; deve->lun_flags |= TRANSPORT_LUNFLAGS_READ_WRITE; @@ -343,76 +291,48 @@ void core_update_device_list_access( deve->lun_flags |= TRANSPORT_LUNFLAGS_READ_ONLY; } spin_unlock_irq(&nacl->device_list_lock); - - return; } -/* core_update_device_list_for_node(): +/* core_enable_device_list_for_node(): * * */ -int core_update_device_list_for_node( +int core_enable_device_list_for_node( struct se_lun *lun, struct se_lun_acl *lun_acl, u32 mapped_lun, u32 lun_access, struct se_node_acl *nacl, - struct se_portal_group *tpg, - int enable) + struct se_portal_group *tpg) { struct se_port *port = lun->lun_sep; - struct se_dev_entry *deve = &nacl->device_list[mapped_lun]; - int trans = 0; - /* - * If the MappedLUN entry is being disabled, the entry in - * port->sep_alua_list must be removed now before clearing the - * struct se_dev_entry pointers below as logic in - * core_alua_do_transition_tg_pt() depends on these being present. - */ - if (!(enable)) { - /* - * deve->se_lun_acl will be NULL for demo-mode created LUNs - * that have not been explictly concerted to MappedLUNs -> - * struct se_lun_acl, but we remove deve->alua_port_list from - * port->sep_alua_list. This also means that active UAs and - * NodeACL context specific PR metadata for demo-mode - * MappedLUN *deve will be released below.. - */ - spin_lock_bh(&port->sep_alua_lock); - list_del(&deve->alua_port_list); - spin_unlock_bh(&port->sep_alua_lock); - } + struct se_dev_entry *deve; spin_lock_irq(&nacl->device_list_lock); - if (enable) { - /* - * Check if the call is handling demo mode -> explict LUN ACL - * transition. This transition must be for the same struct se_lun - * + mapped_lun that was setup in demo mode.. - */ - if (deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS) { - if (deve->se_lun_acl != NULL) { - printk(KERN_ERR "struct se_dev_entry->se_lun_acl" - " already set for demo mode -> explict" - " LUN ACL transition\n"); - spin_unlock_irq(&nacl->device_list_lock); - return -1; - } - if (deve->se_lun != lun) { - printk(KERN_ERR "struct se_dev_entry->se_lun does" - " match passed struct se_lun for demo mode" - " -> explict LUN ACL transition\n"); - spin_unlock_irq(&nacl->device_list_lock); - return -1; - } - deve->se_lun_acl = lun_acl; - trans = 1; - } else { - deve->se_lun = lun; - deve->se_lun_acl = lun_acl; - deve->mapped_lun = mapped_lun; - deve->lun_flags |= TRANSPORT_LUNFLAGS_INITIATOR_ACCESS; + + deve = nacl->device_list[mapped_lun]; + + /* + * Check if the call is handling demo mode -> explicit LUN ACL + * transition. This transition must be for the same struct se_lun + * + mapped_lun that was setup in demo mode.. + */ + if (deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS) { + if (deve->se_lun_acl != NULL) { + pr_err("struct se_dev_entry->se_lun_acl" + " already set for demo mode -> explicit" + " LUN ACL transition\n"); + spin_unlock_irq(&nacl->device_list_lock); + return -EINVAL; + } + if (deve->se_lun != lun) { + pr_err("struct se_dev_entry->se_lun does" + " match passed struct se_lun for demo mode" + " -> explicit LUN ACL transition\n"); + spin_unlock_irq(&nacl->device_list_lock); + return -EINVAL; } + deve->se_lun_acl = lun_acl; if (lun_access & TRANSPORT_LUNFLAGS_READ_WRITE) { deve->lun_flags &= ~TRANSPORT_LUNFLAGS_READ_ONLY; @@ -422,27 +342,72 @@ int core_update_device_list_for_node( deve->lun_flags |= TRANSPORT_LUNFLAGS_READ_ONLY; } - if (trans) { - spin_unlock_irq(&nacl->device_list_lock); - return 0; - } - deve->creation_time = get_jiffies_64(); - deve->attach_count++; spin_unlock_irq(&nacl->device_list_lock); + return 0; + } - spin_lock_bh(&port->sep_alua_lock); - list_add_tail(&deve->alua_port_list, &port->sep_alua_list); - spin_unlock_bh(&port->sep_alua_lock); + deve->se_lun = lun; + deve->se_lun_acl = lun_acl; + deve->mapped_lun = mapped_lun; + deve->lun_flags |= TRANSPORT_LUNFLAGS_INITIATOR_ACCESS; - return 0; + if (lun_access & TRANSPORT_LUNFLAGS_READ_WRITE) { + deve->lun_flags &= ~TRANSPORT_LUNFLAGS_READ_ONLY; + deve->lun_flags |= TRANSPORT_LUNFLAGS_READ_WRITE; + } else { + deve->lun_flags &= ~TRANSPORT_LUNFLAGS_READ_WRITE; + deve->lun_flags |= TRANSPORT_LUNFLAGS_READ_ONLY; } + + deve->creation_time = get_jiffies_64(); + deve->attach_count++; + spin_unlock_irq(&nacl->device_list_lock); + + spin_lock_bh(&port->sep_alua_lock); + list_add_tail(&deve->alua_port_list, &port->sep_alua_list); + spin_unlock_bh(&port->sep_alua_lock); + + return 0; +} + +/* core_disable_device_list_for_node(): + * + * + */ +int core_disable_device_list_for_node( + struct se_lun *lun, + struct se_lun_acl *lun_acl, + u32 mapped_lun, + u32 lun_access, + struct se_node_acl *nacl, + struct se_portal_group *tpg) +{ + struct se_port *port = lun->lun_sep; + struct se_dev_entry *deve = nacl->device_list[mapped_lun]; + + /* + * If the MappedLUN entry is being disabled, the entry in + * port->sep_alua_list must be removed now before clearing the + * struct se_dev_entry pointers below as logic in + * core_alua_do_transition_tg_pt() depends on these being present. + * + * deve->se_lun_acl will be NULL for demo-mode created LUNs + * that have not been explicitly converted to MappedLUNs -> + * struct se_lun_acl, but we remove deve->alua_port_list from + * port->sep_alua_list. This also means that active UAs and + * NodeACL context specific PR metadata for demo-mode + * MappedLUN *deve will be released below.. + */ + spin_lock_bh(&port->sep_alua_lock); + list_del(&deve->alua_port_list); + spin_unlock_bh(&port->sep_alua_lock); /* * Wait for any in process SPEC_I_PT=1 or REGISTER_AND_MOVE * PR operation to complete. */ - spin_unlock_irq(&nacl->device_list_lock); while (atomic_read(&deve->pr_ref_count) != 0) cpu_relax(); + spin_lock_irq(&nacl->device_list_lock); /* * Disable struct se_dev_entry LUN ACL mapping @@ -469,30 +434,28 @@ void core_clear_lun_from_tpg(struct se_lun *lun, struct se_portal_group *tpg) struct se_dev_entry *deve; u32 i; - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); list_for_each_entry(nacl, &tpg->acl_node_list, acl_list) { - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); spin_lock_irq(&nacl->device_list_lock); for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - deve = &nacl->device_list[i]; + deve = nacl->device_list[i]; if (lun != deve->se_lun) continue; spin_unlock_irq(&nacl->device_list_lock); - core_update_device_list_for_node(lun, NULL, + core_disable_device_list_for_node(lun, NULL, deve->mapped_lun, TRANSPORT_LUNFLAGS_NO_ACCESS, - nacl, tpg, 0); + nacl, tpg); spin_lock_irq(&nacl->device_list_lock); } spin_unlock_irq(&nacl->device_list_lock); - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); } - spin_unlock_bh(&tpg->acl_node_lock); - - return; + spin_unlock_irq(&tpg->acl_node_lock); } static struct se_port *core_alloc_port(struct se_device *dev) @@ -500,9 +463,9 @@ static struct se_port *core_alloc_port(struct se_device *dev) struct se_port *port, *port_tmp; port = kzalloc(sizeof(struct se_port), GFP_KERNEL); - if (!(port)) { - printk(KERN_ERR "Unable to allocate struct se_port\n"); - return NULL; + if (!port) { + pr_err("Unable to allocate struct se_port\n"); + return ERR_PTR(-ENOMEM); } INIT_LIST_HEAD(&port->sep_alua_list); INIT_LIST_HEAD(&port->sep_list); @@ -512,14 +475,14 @@ static struct se_port *core_alloc_port(struct se_device *dev) spin_lock(&dev->se_port_lock); if (dev->dev_port_count == 0x0000ffff) { - printk(KERN_WARNING "Reached dev->dev_port_count ==" + pr_warn("Reached dev->dev_port_count ==" " 0x0000ffff\n"); spin_unlock(&dev->se_port_lock); - return NULL; + return ERR_PTR(-ENOSPC); } again: /* - * Allocate the next RELATIVE TARGET PORT IDENTIFER for this struct se_device + * Allocate the next RELATIVE TARGET PORT IDENTIFIER for this struct se_device * Here is the table from spc4r17 section 7.7.3.8. * * Table 473 -- RELATIVE TARGET PORT IDENTIFIER field @@ -531,12 +494,12 @@ again: * 3h to FFFFh Relative port 3 through 65 535 */ port->sep_rtpi = dev->dev_rpti_counter++; - if (!(port->sep_rtpi)) + if (!port->sep_rtpi) goto again; list_for_each_entry(port_tmp, &dev->dev_sep_list, sep_list) { /* - * Make sure RELATIVE TARGET PORT IDENTIFER is unique + * Make sure RELATIVE TARGET PORT IDENTIFIER is unique * for 16-bit wrap.. */ if (port->sep_rtpi == port_tmp->sep_rtpi) @@ -553,7 +516,6 @@ static void core_export_port( struct se_port *port, struct se_lun *lun) { - struct se_subsystem_dev *su_dev = SU_DEV(dev); struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem = NULL; spin_lock(&dev->se_port_lock); @@ -566,30 +528,32 @@ static void core_export_port( list_add_tail(&port->sep_list, &dev->dev_sep_list); spin_unlock(&dev->se_port_lock); - if (T10_ALUA(su_dev)->alua_type == SPC3_ALUA_EMULATED) { + if (dev->transport->transport_type != TRANSPORT_PLUGIN_PHBA_PDEV && + !(dev->se_hba->hba_flags & HBA_FLAGS_INTERNAL_USE)) { tg_pt_gp_mem = core_alua_allocate_tg_pt_gp_mem(port); if (IS_ERR(tg_pt_gp_mem) || !tg_pt_gp_mem) { - printk(KERN_ERR "Unable to allocate t10_alua_tg_pt" + pr_err("Unable to allocate t10_alua_tg_pt" "_gp_member_t\n"); return; } spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); __core_alua_attach_tg_pt_gp_mem(tg_pt_gp_mem, - T10_ALUA(su_dev)->default_tg_pt_gp); + dev->t10_alua.default_tg_pt_gp); spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); - printk(KERN_INFO "%s/%s: Adding to default ALUA Target Port" + pr_debug("%s/%s: Adding to default ALUA Target Port" " Group: alua/default_tg_pt_gp\n", - TRANSPORT(dev)->name, TPG_TFO(tpg)->get_fabric_name()); + dev->transport->name, tpg->se_tpg_tfo->get_fabric_name()); } dev->dev_port_count++; - port->sep_index = port->sep_rtpi; /* RELATIVE TARGET PORT IDENTIFER */ + port->sep_index = port->sep_rtpi; /* RELATIVE TARGET PORT IDENTIFIER */ } /* * Called with struct se_device->se_port_lock spinlock held. */ static void core_release_port(struct se_device *dev, struct se_port *port) + __releases(&dev->se_port_lock) __acquires(&dev->se_port_lock) { /* * Wait for any port reference for PR ALL_TG_PT=1 operation @@ -605,8 +569,6 @@ static void core_release_port(struct se_device *dev, struct se_port *port) list_del(&port->sep_list); dev->dev_port_count--; kfree(port); - - return; } int core_dev_export( @@ -614,16 +576,19 @@ int core_dev_export( struct se_portal_group *tpg, struct se_lun *lun) { + struct se_hba *hba = dev->se_hba; struct se_port *port; port = core_alloc_port(dev); - if (!(port)) - return -1; + if (IS_ERR(port)) + return PTR_ERR(port); lun->lun_se_dev = dev; - se_dev_start(dev); - atomic_inc(&dev->dev_export_obj.obj_access_count); + spin_lock(&hba->device_lock); + dev->export_count++; + spin_unlock(&hba->device_lock); + core_export_port(dev, tpg, port, lun); return 0; } @@ -633,6 +598,7 @@ void core_dev_unexport( struct se_portal_group *tpg, struct se_lun *lun) { + struct se_hba *hba = dev->se_hba; struct se_port *port = lun->lun_sep; spin_lock(&lun->lun_sep_lock); @@ -643,327 +609,55 @@ void core_dev_unexport( spin_unlock(&lun->lun_sep_lock); spin_lock(&dev->se_port_lock); - atomic_dec(&dev->dev_export_obj.obj_access_count); core_release_port(dev, port); spin_unlock(&dev->se_port_lock); - se_dev_stop(dev); - lun->lun_se_dev = NULL; -} - -int transport_core_report_lun_response(struct se_cmd *se_cmd) -{ - struct se_dev_entry *deve; - struct se_lun *se_lun; - struct se_session *se_sess = SE_SESS(se_cmd); - struct se_task *se_task; - unsigned char *buf = (unsigned char *)T_TASK(se_cmd)->t_task_buf; - u32 cdb_offset = 0, lun_count = 0, offset = 8; - u64 i, lun; - - list_for_each_entry(se_task, &T_TASK(se_cmd)->t_task_list, t_list) - break; - - if (!(se_task)) { - printk(KERN_ERR "Unable to locate struct se_task for struct se_cmd\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; - } - - /* - * If no struct se_session pointer is present, this struct se_cmd is - * coming via a target_core_mod PASSTHROUGH op, and not through - * a $FABRIC_MOD. In that case, report LUN=0 only. - */ - if (!(se_sess)) { - lun = 0; - buf[offset++] = ((lun >> 56) & 0xff); - buf[offset++] = ((lun >> 48) & 0xff); - buf[offset++] = ((lun >> 40) & 0xff); - buf[offset++] = ((lun >> 32) & 0xff); - buf[offset++] = ((lun >> 24) & 0xff); - buf[offset++] = ((lun >> 16) & 0xff); - buf[offset++] = ((lun >> 8) & 0xff); - buf[offset++] = (lun & 0xff); - lun_count = 1; - goto done; - } - - spin_lock_irq(&SE_NODE_ACL(se_sess)->device_list_lock); - for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - deve = &SE_NODE_ACL(se_sess)->device_list[i]; - if (!(deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS)) - continue; - se_lun = deve->se_lun; - /* - * We determine the correct LUN LIST LENGTH even once we - * have reached the initial allocation length. - * See SPC2-R20 7.19. - */ - lun_count++; - if ((cdb_offset + 8) >= se_cmd->data_length) - continue; - - lun = cpu_to_be64(CMD_TFO(se_cmd)->pack_lun(deve->mapped_lun)); - buf[offset++] = ((lun >> 56) & 0xff); - buf[offset++] = ((lun >> 48) & 0xff); - buf[offset++] = ((lun >> 40) & 0xff); - buf[offset++] = ((lun >> 32) & 0xff); - buf[offset++] = ((lun >> 24) & 0xff); - buf[offset++] = ((lun >> 16) & 0xff); - buf[offset++] = ((lun >> 8) & 0xff); - buf[offset++] = (lun & 0xff); - cdb_offset += 8; - } - spin_unlock_irq(&SE_NODE_ACL(se_sess)->device_list_lock); - - /* - * See SPC3 r07, page 159. - */ -done: - lun_count *= 8; - buf[0] = ((lun_count >> 24) & 0xff); - buf[1] = ((lun_count >> 16) & 0xff); - buf[2] = ((lun_count >> 8) & 0xff); - buf[3] = (lun_count & 0xff); - - return PYX_TRANSPORT_SENT_TO_TRANSPORT; -} - -/* se_release_device_for_hba(): - * - * - */ -void se_release_device_for_hba(struct se_device *dev) -{ - struct se_hba *hba = dev->se_hba; - - if ((dev->dev_status & TRANSPORT_DEVICE_ACTIVATED) || - (dev->dev_status & TRANSPORT_DEVICE_DEACTIVATED) || - (dev->dev_status & TRANSPORT_DEVICE_SHUTDOWN) || - (dev->dev_status & TRANSPORT_DEVICE_OFFLINE_ACTIVATED) || - (dev->dev_status & TRANSPORT_DEVICE_OFFLINE_DEACTIVATED)) - se_dev_stop(dev); - - if (dev->dev_ptr) { - kthread_stop(dev->process_thread); - if (dev->transport->free_device) - dev->transport->free_device(dev->dev_ptr); - } - spin_lock(&hba->device_lock); - list_del(&dev->dev_list); - hba->dev_count--; + dev->export_count--; spin_unlock(&hba->device_lock); - core_scsi3_free_all_registrations(dev); - se_release_vpd_for_dev(dev); - - kfree(dev->dev_status_queue_obj); - kfree(dev->dev_queue_obj); - kfree(dev); - - return; + lun->lun_sep = NULL; + lun->lun_se_dev = NULL; } -void se_release_vpd_for_dev(struct se_device *dev) +static void se_release_vpd_for_dev(struct se_device *dev) { struct t10_vpd *vpd, *vpd_tmp; - spin_lock(&DEV_T10_WWN(dev)->t10_vpd_lock); + spin_lock(&dev->t10_wwn.t10_vpd_lock); list_for_each_entry_safe(vpd, vpd_tmp, - &DEV_T10_WWN(dev)->t10_vpd_list, vpd_list) { + &dev->t10_wwn.t10_vpd_list, vpd_list) { list_del(&vpd->vpd_list); kfree(vpd); } - spin_unlock(&DEV_T10_WWN(dev)->t10_vpd_lock); - - return; + spin_unlock(&dev->t10_wwn.t10_vpd_lock); } -/* - * Called with struct se_hba->device_lock held. - */ -void se_clear_dev_ports(struct se_device *dev) +static u32 se_dev_align_max_sectors(u32 max_sectors, u32 block_size) { - struct se_hba *hba = dev->se_hba; - struct se_lun *lun; - struct se_portal_group *tpg; - struct se_port *sep, *sep_tmp; - - spin_lock(&dev->se_port_lock); - list_for_each_entry_safe(sep, sep_tmp, &dev->dev_sep_list, sep_list) { - spin_unlock(&dev->se_port_lock); - spin_unlock(&hba->device_lock); - - lun = sep->sep_lun; - tpg = sep->sep_tpg; - spin_lock(&lun->lun_sep_lock); - if (lun->lun_se_dev == NULL) { - spin_unlock(&lun->lun_sep_lock); - continue; - } - spin_unlock(&lun->lun_sep_lock); - - core_dev_del_lun(tpg, lun->unpacked_lun); - - spin_lock(&hba->device_lock); - spin_lock(&dev->se_port_lock); - } - spin_unlock(&dev->se_port_lock); - - return; -} - -/* se_free_virtual_device(): - * - * Used for IBLOCK, RAMDISK, and FILEIO Transport Drivers. - */ -int se_free_virtual_device(struct se_device *dev, struct se_hba *hba) -{ - spin_lock(&hba->device_lock); - se_clear_dev_ports(dev); - spin_unlock(&hba->device_lock); - - core_alua_free_lu_gp_mem(dev); - se_release_device_for_hba(dev); - - return 0; -} - -static void se_dev_start(struct se_device *dev) -{ - struct se_hba *hba = dev->se_hba; - - spin_lock(&hba->device_lock); - atomic_inc(&dev->dev_obj.obj_access_count); - if (atomic_read(&dev->dev_obj.obj_access_count) == 1) { - if (dev->dev_status & TRANSPORT_DEVICE_DEACTIVATED) { - dev->dev_status &= ~TRANSPORT_DEVICE_DEACTIVATED; - dev->dev_status |= TRANSPORT_DEVICE_ACTIVATED; - } else if (dev->dev_status & - TRANSPORT_DEVICE_OFFLINE_DEACTIVATED) { - dev->dev_status &= - ~TRANSPORT_DEVICE_OFFLINE_DEACTIVATED; - dev->dev_status |= TRANSPORT_DEVICE_OFFLINE_ACTIVATED; - } - } - spin_unlock(&hba->device_lock); -} - -static void se_dev_stop(struct se_device *dev) -{ - struct se_hba *hba = dev->se_hba; - - spin_lock(&hba->device_lock); - atomic_dec(&dev->dev_obj.obj_access_count); - if (atomic_read(&dev->dev_obj.obj_access_count) == 0) { - if (dev->dev_status & TRANSPORT_DEVICE_ACTIVATED) { - dev->dev_status &= ~TRANSPORT_DEVICE_ACTIVATED; - dev->dev_status |= TRANSPORT_DEVICE_DEACTIVATED; - } else if (dev->dev_status & - TRANSPORT_DEVICE_OFFLINE_ACTIVATED) { - dev->dev_status &= ~TRANSPORT_DEVICE_OFFLINE_ACTIVATED; - dev->dev_status |= TRANSPORT_DEVICE_OFFLINE_DEACTIVATED; - } - } - spin_unlock(&hba->device_lock); -} - -int se_dev_check_online(struct se_device *dev) -{ - int ret; - - spin_lock_irq(&dev->dev_status_lock); - ret = ((dev->dev_status & TRANSPORT_DEVICE_ACTIVATED) || - (dev->dev_status & TRANSPORT_DEVICE_DEACTIVATED)) ? 0 : 1; - spin_unlock_irq(&dev->dev_status_lock); - - return ret; -} - -int se_dev_check_shutdown(struct se_device *dev) -{ - int ret; - - spin_lock_irq(&dev->dev_status_lock); - ret = (dev->dev_status & TRANSPORT_DEVICE_SHUTDOWN); - spin_unlock_irq(&dev->dev_status_lock); - - return ret; -} - -void se_dev_set_default_attribs( - struct se_device *dev, - struct se_dev_limits *dev_limits) -{ - struct queue_limits *limits = &dev_limits->limits; - - DEV_ATTRIB(dev)->emulate_dpo = DA_EMULATE_DPO; - DEV_ATTRIB(dev)->emulate_fua_write = DA_EMULATE_FUA_WRITE; - DEV_ATTRIB(dev)->emulate_fua_read = DA_EMULATE_FUA_READ; - DEV_ATTRIB(dev)->emulate_write_cache = DA_EMULATE_WRITE_CACHE; - DEV_ATTRIB(dev)->emulate_ua_intlck_ctrl = DA_EMULATE_UA_INTLLCK_CTRL; - DEV_ATTRIB(dev)->emulate_tas = DA_EMULATE_TAS; - DEV_ATTRIB(dev)->emulate_tpu = DA_EMULATE_TPU; - DEV_ATTRIB(dev)->emulate_tpws = DA_EMULATE_TPWS; - DEV_ATTRIB(dev)->emulate_reservations = DA_EMULATE_RESERVATIONS; - DEV_ATTRIB(dev)->emulate_alua = DA_EMULATE_ALUA; - DEV_ATTRIB(dev)->enforce_pr_isids = DA_ENFORCE_PR_ISIDS; + u32 aligned_max_sectors; + u32 alignment; /* - * The TPU=1 and TPWS=1 settings will be set in TCM/IBLOCK - * iblock_create_virtdevice() from struct queue_limits values - * if blk_queue_discard()==1 + * Limit max_sectors to a PAGE_SIZE aligned value for modern + * transport_allocate_data_tasks() operation. */ - DEV_ATTRIB(dev)->max_unmap_lba_count = DA_MAX_UNMAP_LBA_COUNT; - DEV_ATTRIB(dev)->max_unmap_block_desc_count = - DA_MAX_UNMAP_BLOCK_DESC_COUNT; - DEV_ATTRIB(dev)->unmap_granularity = DA_UNMAP_GRANULARITY_DEFAULT; - DEV_ATTRIB(dev)->unmap_granularity_alignment = - DA_UNMAP_GRANULARITY_ALIGNMENT_DEFAULT; - /* - * block_size is based on subsystem plugin dependent requirements. - */ - DEV_ATTRIB(dev)->hw_block_size = limits->logical_block_size; - DEV_ATTRIB(dev)->block_size = limits->logical_block_size; - /* - * max_sectors is based on subsystem plugin dependent requirements. - */ - DEV_ATTRIB(dev)->hw_max_sectors = limits->max_hw_sectors; - DEV_ATTRIB(dev)->max_sectors = limits->max_sectors; - /* - * Set optimal_sectors from max_sectors, which can be lowered via - * configfs. - */ - DEV_ATTRIB(dev)->optimal_sectors = limits->max_sectors; - /* - * queue_depth is based on subsystem plugin dependent requirements. - */ - DEV_ATTRIB(dev)->hw_queue_depth = dev_limits->hw_queue_depth; - DEV_ATTRIB(dev)->queue_depth = dev_limits->queue_depth; -} + alignment = max(1ul, PAGE_SIZE / block_size); + aligned_max_sectors = rounddown(max_sectors, alignment); -int se_dev_set_task_timeout(struct se_device *dev, u32 task_timeout) -{ - if (task_timeout > DA_TASK_TIMEOUT_MAX) { - printk(KERN_ERR "dev[%p]: Passed task_timeout: %u larger then" - " DA_TASK_TIMEOUT_MAX\n", dev, task_timeout); - return -1; - } else { - DEV_ATTRIB(dev)->task_timeout = task_timeout; - printk(KERN_INFO "dev[%p]: Set SE Device task_timeout: %u\n", - dev, task_timeout); - } + if (max_sectors != aligned_max_sectors) + pr_info("Rounding down aligned max_sectors from %u to %u\n", + max_sectors, aligned_max_sectors); - return 0; + return aligned_max_sectors; } int se_dev_set_max_unmap_lba_count( struct se_device *dev, u32 max_unmap_lba_count) { - DEV_ATTRIB(dev)->max_unmap_lba_count = max_unmap_lba_count; - printk(KERN_INFO "dev[%p]: Set max_unmap_lba_count: %u\n", - dev, DEV_ATTRIB(dev)->max_unmap_lba_count); + dev->dev_attrib.max_unmap_lba_count = max_unmap_lba_count; + pr_debug("dev[%p]: Set max_unmap_lba_count: %u\n", + dev, dev->dev_attrib.max_unmap_lba_count); return 0; } @@ -971,9 +665,10 @@ int se_dev_set_max_unmap_block_desc_count( struct se_device *dev, u32 max_unmap_block_desc_count) { - DEV_ATTRIB(dev)->max_unmap_block_desc_count = max_unmap_block_desc_count; - printk(KERN_INFO "dev[%p]: Set max_unmap_block_desc_count: %u\n", - dev, DEV_ATTRIB(dev)->max_unmap_block_desc_count); + dev->dev_attrib.max_unmap_block_desc_count = + max_unmap_block_desc_count; + pr_debug("dev[%p]: Set max_unmap_block_desc_count: %u\n", + dev, dev->dev_attrib.max_unmap_block_desc_count); return 0; } @@ -981,9 +676,9 @@ int se_dev_set_unmap_granularity( struct se_device *dev, u32 unmap_granularity) { - DEV_ATTRIB(dev)->unmap_granularity = unmap_granularity; - printk(KERN_INFO "dev[%p]: Set unmap_granularity: %u\n", - dev, DEV_ATTRIB(dev)->unmap_granularity); + dev->dev_attrib.unmap_granularity = unmap_granularity; + pr_debug("dev[%p]: Set unmap_granularity: %u\n", + dev, dev->dev_attrib.unmap_granularity); return 0; } @@ -991,109 +686,147 @@ int se_dev_set_unmap_granularity_alignment( struct se_device *dev, u32 unmap_granularity_alignment) { - DEV_ATTRIB(dev)->unmap_granularity_alignment = unmap_granularity_alignment; - printk(KERN_INFO "dev[%p]: Set unmap_granularity_alignment: %u\n", - dev, DEV_ATTRIB(dev)->unmap_granularity_alignment); + dev->dev_attrib.unmap_granularity_alignment = unmap_granularity_alignment; + pr_debug("dev[%p]: Set unmap_granularity_alignment: %u\n", + dev, dev->dev_attrib.unmap_granularity_alignment); return 0; } -int se_dev_set_emulate_dpo(struct se_device *dev, int flag) +int se_dev_set_max_write_same_len( + struct se_device *dev, + u32 max_write_same_len) { - if ((flag != 0) && (flag != 1)) { - printk(KERN_ERR "Illegal value %d\n", flag); - return -1; + dev->dev_attrib.max_write_same_len = max_write_same_len; + pr_debug("dev[%p]: Set max_write_same_len: %u\n", + dev, dev->dev_attrib.max_write_same_len); + return 0; +} + +static void dev_set_t10_wwn_model_alias(struct se_device *dev) +{ + const char *configname; + + configname = config_item_name(&dev->dev_group.cg_item); + if (strlen(configname) >= 16) { + pr_warn("dev[%p]: Backstore name '%s' is too long for " + "INQUIRY_MODEL, truncating to 16 bytes\n", dev, + configname); } - if (TRANSPORT(dev)->dpo_emulated == NULL) { - printk(KERN_ERR "TRANSPORT(dev)->dpo_emulated is NULL\n"); - return -1; + snprintf(&dev->t10_wwn.model[0], 16, "%s", configname); +} + +int se_dev_set_emulate_model_alias(struct se_device *dev, int flag) +{ + if (dev->export_count) { + pr_err("dev[%p]: Unable to change model alias" + " while export_count is %d\n", + dev, dev->export_count); + return -EINVAL; + } + + if (flag != 0 && flag != 1) { + pr_err("Illegal value %d\n", flag); + return -EINVAL; } - if (TRANSPORT(dev)->dpo_emulated(dev) == 0) { - printk(KERN_ERR "TRANSPORT(dev)->dpo_emulated not supported\n"); - return -1; + + if (flag) { + dev_set_t10_wwn_model_alias(dev); + } else { + strncpy(&dev->t10_wwn.model[0], + dev->transport->inquiry_prod, 16); } - DEV_ATTRIB(dev)->emulate_dpo = flag; - printk(KERN_INFO "dev[%p]: SE Device Page Out (DPO) Emulation" - " bit: %d\n", dev, DEV_ATTRIB(dev)->emulate_dpo); + dev->dev_attrib.emulate_model_alias = flag; + return 0; } -int se_dev_set_emulate_fua_write(struct se_device *dev, int flag) +int se_dev_set_emulate_dpo(struct se_device *dev, int flag) { - if ((flag != 0) && (flag != 1)) { - printk(KERN_ERR "Illegal value %d\n", flag); - return -1; + if (flag != 0 && flag != 1) { + pr_err("Illegal value %d\n", flag); + return -EINVAL; } - if (TRANSPORT(dev)->fua_write_emulated == NULL) { - printk(KERN_ERR "TRANSPORT(dev)->fua_write_emulated is NULL\n"); - return -1; + + if (flag) { + pr_err("dpo_emulated not supported\n"); + return -EINVAL; } - if (TRANSPORT(dev)->fua_write_emulated(dev) == 0) { - printk(KERN_ERR "TRANSPORT(dev)->fua_write_emulated not supported\n"); - return -1; + + return 0; +} + +int se_dev_set_emulate_fua_write(struct se_device *dev, int flag) +{ + if (flag != 0 && flag != 1) { + pr_err("Illegal value %d\n", flag); + return -EINVAL; } - DEV_ATTRIB(dev)->emulate_fua_write = flag; - printk(KERN_INFO "dev[%p]: SE Device Forced Unit Access WRITEs: %d\n", - dev, DEV_ATTRIB(dev)->emulate_fua_write); + + if (flag && + dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { + pr_err("emulate_fua_write not supported for pSCSI\n"); + return -EINVAL; + } + dev->dev_attrib.emulate_fua_write = flag; + pr_debug("dev[%p]: SE Device Forced Unit Access WRITEs: %d\n", + dev, dev->dev_attrib.emulate_fua_write); return 0; } int se_dev_set_emulate_fua_read(struct se_device *dev, int flag) { - if ((flag != 0) && (flag != 1)) { - printk(KERN_ERR "Illegal value %d\n", flag); - return -1; - } - if (TRANSPORT(dev)->fua_read_emulated == NULL) { - printk(KERN_ERR "TRANSPORT(dev)->fua_read_emulated is NULL\n"); - return -1; + if (flag != 0 && flag != 1) { + pr_err("Illegal value %d\n", flag); + return -EINVAL; } - if (TRANSPORT(dev)->fua_read_emulated(dev) == 0) { - printk(KERN_ERR "TRANSPORT(dev)->fua_read_emulated not supported\n"); - return -1; + + if (flag) { + pr_err("ua read emulated not supported\n"); + return -EINVAL; } - DEV_ATTRIB(dev)->emulate_fua_read = flag; - printk(KERN_INFO "dev[%p]: SE Device Forced Unit Access READs: %d\n", - dev, DEV_ATTRIB(dev)->emulate_fua_read); + return 0; } int se_dev_set_emulate_write_cache(struct se_device *dev, int flag) { - if ((flag != 0) && (flag != 1)) { - printk(KERN_ERR "Illegal value %d\n", flag); - return -1; + if (flag != 0 && flag != 1) { + pr_err("Illegal value %d\n", flag); + return -EINVAL; } - if (TRANSPORT(dev)->write_cache_emulated == NULL) { - printk(KERN_ERR "TRANSPORT(dev)->write_cache_emulated is NULL\n"); - return -1; + if (flag && + dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { + pr_err("emulate_write_cache not supported for pSCSI\n"); + return -EINVAL; } - if (TRANSPORT(dev)->write_cache_emulated(dev) == 0) { - printk(KERN_ERR "TRANSPORT(dev)->write_cache_emulated not supported\n"); - return -1; + if (flag && + dev->transport->get_write_cache) { + pr_err("emulate_write_cache not supported for this device\n"); + return -EINVAL; } - DEV_ATTRIB(dev)->emulate_write_cache = flag; - printk(KERN_INFO "dev[%p]: SE Device WRITE_CACHE_EMULATION flag: %d\n", - dev, DEV_ATTRIB(dev)->emulate_write_cache); + + dev->dev_attrib.emulate_write_cache = flag; + pr_debug("dev[%p]: SE Device WRITE_CACHE_EMULATION flag: %d\n", + dev, dev->dev_attrib.emulate_write_cache); return 0; } int se_dev_set_emulate_ua_intlck_ctrl(struct se_device *dev, int flag) { if ((flag != 0) && (flag != 1) && (flag != 2)) { - printk(KERN_ERR "Illegal value %d\n", flag); - return -1; + pr_err("Illegal value %d\n", flag); + return -EINVAL; } - if (atomic_read(&dev->dev_export_obj.obj_access_count)) { - printk(KERN_ERR "dev[%p]: Unable to change SE Device" - " UA_INTRLCK_CTRL while dev_export_obj: %d count" - " exists\n", dev, - atomic_read(&dev->dev_export_obj.obj_access_count)); - return -1; + if (dev->export_count) { + pr_err("dev[%p]: Unable to change SE Device" + " UA_INTRLCK_CTRL while export_count is %d\n", + dev, dev->export_count); + return -EINVAL; } - DEV_ATTRIB(dev)->emulate_ua_intlck_ctrl = flag; - printk(KERN_INFO "dev[%p]: SE Device UA_INTRLCK_CTRL flag: %d\n", - dev, DEV_ATTRIB(dev)->emulate_ua_intlck_ctrl); + dev->dev_attrib.emulate_ua_intlck_ctrl = flag; + pr_debug("dev[%p]: SE Device UA_INTRLCK_CTRL flag: %d\n", + dev, dev->dev_attrib.emulate_ua_intlck_ctrl); return 0; } @@ -1101,19 +834,19 @@ int se_dev_set_emulate_ua_intlck_ctrl(struct se_device *dev, int flag) int se_dev_set_emulate_tas(struct se_device *dev, int flag) { if ((flag != 0) && (flag != 1)) { - printk(KERN_ERR "Illegal value %d\n", flag); - return -1; + pr_err("Illegal value %d\n", flag); + return -EINVAL; } - if (atomic_read(&dev->dev_export_obj.obj_access_count)) { - printk(KERN_ERR "dev[%p]: Unable to change SE Device TAS while" - " dev_export_obj: %d count exists\n", dev, - atomic_read(&dev->dev_export_obj.obj_access_count)); - return -1; + if (dev->export_count) { + pr_err("dev[%p]: Unable to change SE Device TAS while" + " export_count is %d\n", + dev, dev->export_count); + return -EINVAL; } - DEV_ATTRIB(dev)->emulate_tas = flag; - printk(KERN_INFO "dev[%p]: SE Device TASK_ABORTED status bit: %s\n", - dev, (DEV_ATTRIB(dev)->emulate_tas) ? "Enabled" : "Disabled"); + dev->dev_attrib.emulate_tas = flag; + pr_debug("dev[%p]: SE Device TASK_ABORTED status bit: %s\n", + dev, (dev->dev_attrib.emulate_tas) ? "Enabled" : "Disabled"); return 0; } @@ -1121,20 +854,20 @@ int se_dev_set_emulate_tas(struct se_device *dev, int flag) int se_dev_set_emulate_tpu(struct se_device *dev, int flag) { if ((flag != 0) && (flag != 1)) { - printk(KERN_ERR "Illegal value %d\n", flag); - return -1; + pr_err("Illegal value %d\n", flag); + return -EINVAL; } /* * We expect this value to be non-zero when generic Block Layer * Discard supported is detected iblock_create_virtdevice(). */ - if (!(DEV_ATTRIB(dev)->max_unmap_block_desc_count)) { - printk(KERN_ERR "Generic Block Discard not supported\n"); + if (flag && !dev->dev_attrib.max_unmap_block_desc_count) { + pr_err("Generic Block Discard not supported\n"); return -ENOSYS; } - DEV_ATTRIB(dev)->emulate_tpu = flag; - printk(KERN_INFO "dev[%p]: SE Device Thin Provisioning UNMAP bit: %d\n", + dev->dev_attrib.emulate_tpu = flag; + pr_debug("dev[%p]: SE Device Thin Provisioning UNMAP bit: %d\n", dev, flag); return 0; } @@ -1142,33 +875,171 @@ int se_dev_set_emulate_tpu(struct se_device *dev, int flag) int se_dev_set_emulate_tpws(struct se_device *dev, int flag) { if ((flag != 0) && (flag != 1)) { - printk(KERN_ERR "Illegal value %d\n", flag); - return -1; + pr_err("Illegal value %d\n", flag); + return -EINVAL; } /* * We expect this value to be non-zero when generic Block Layer * Discard supported is detected iblock_create_virtdevice(). */ - if (!(DEV_ATTRIB(dev)->max_unmap_block_desc_count)) { - printk(KERN_ERR "Generic Block Discard not supported\n"); + if (flag && !dev->dev_attrib.max_unmap_block_desc_count) { + pr_err("Generic Block Discard not supported\n"); return -ENOSYS; } - DEV_ATTRIB(dev)->emulate_tpws = flag; - printk(KERN_INFO "dev[%p]: SE Device Thin Provisioning WRITE_SAME: %d\n", + dev->dev_attrib.emulate_tpws = flag; + pr_debug("dev[%p]: SE Device Thin Provisioning WRITE_SAME: %d\n", dev, flag); return 0; } +int se_dev_set_emulate_caw(struct se_device *dev, int flag) +{ + if (flag != 0 && flag != 1) { + pr_err("Illegal value %d\n", flag); + return -EINVAL; + } + dev->dev_attrib.emulate_caw = flag; + pr_debug("dev[%p]: SE Device CompareAndWrite (AtomicTestandSet): %d\n", + dev, flag); + + return 0; +} + +int se_dev_set_emulate_3pc(struct se_device *dev, int flag) +{ + if (flag != 0 && flag != 1) { + pr_err("Illegal value %d\n", flag); + return -EINVAL; + } + dev->dev_attrib.emulate_3pc = flag; + pr_debug("dev[%p]: SE Device 3rd Party Copy (EXTENDED_COPY): %d\n", + dev, flag); + + return 0; +} + +int se_dev_set_pi_prot_type(struct se_device *dev, int flag) +{ + int rc, old_prot = dev->dev_attrib.pi_prot_type; + + if (flag != 0 && flag != 1 && flag != 2 && flag != 3) { + pr_err("Illegal value %d for pi_prot_type\n", flag); + return -EINVAL; + } + if (flag == 2) { + pr_err("DIF TYPE2 protection currently not supported\n"); + return -ENOSYS; + } + if (dev->dev_attrib.hw_pi_prot_type) { + pr_warn("DIF protection enabled on underlying hardware," + " ignoring\n"); + return 0; + } + if (!dev->transport->init_prot || !dev->transport->free_prot) { + /* 0 is only allowed value for non-supporting backends */ + if (flag == 0) + return 0; + + pr_err("DIF protection not supported by backend: %s\n", + dev->transport->name); + return -ENOSYS; + } + if (!(dev->dev_flags & DF_CONFIGURED)) { + pr_err("DIF protection requires device to be configured\n"); + return -ENODEV; + } + if (dev->export_count) { + pr_err("dev[%p]: Unable to change SE Device PROT type while" + " export_count is %d\n", dev, dev->export_count); + return -EINVAL; + } + + dev->dev_attrib.pi_prot_type = flag; + + if (flag && !old_prot) { + rc = dev->transport->init_prot(dev); + if (rc) { + dev->dev_attrib.pi_prot_type = old_prot; + return rc; + } + + } else if (!flag && old_prot) { + dev->transport->free_prot(dev); + } + pr_debug("dev[%p]: SE Device Protection Type: %d\n", dev, flag); + + return 0; +} + +int se_dev_set_pi_prot_format(struct se_device *dev, int flag) +{ + int rc; + + if (!flag) + return 0; + + if (flag != 1) { + pr_err("Illegal value %d for pi_prot_format\n", flag); + return -EINVAL; + } + if (!dev->transport->format_prot) { + pr_err("DIF protection format not supported by backend %s\n", + dev->transport->name); + return -ENOSYS; + } + if (!(dev->dev_flags & DF_CONFIGURED)) { + pr_err("DIF protection format requires device to be configured\n"); + return -ENODEV; + } + if (dev->export_count) { + pr_err("dev[%p]: Unable to format SE Device PROT type while" + " export_count is %d\n", dev, dev->export_count); + return -EINVAL; + } + + rc = dev->transport->format_prot(dev); + if (rc) + return rc; + + pr_debug("dev[%p]: SE Device Protection Format complete\n", dev); + + return 0; +} + int se_dev_set_enforce_pr_isids(struct se_device *dev, int flag) { if ((flag != 0) && (flag != 1)) { + pr_err("Illegal value %d\n", flag); + return -EINVAL; + } + dev->dev_attrib.enforce_pr_isids = flag; + pr_debug("dev[%p]: SE Device enforce_pr_isids bit: %s\n", dev, + (dev->dev_attrib.enforce_pr_isids) ? "Enabled" : "Disabled"); + return 0; +} + +int se_dev_set_is_nonrot(struct se_device *dev, int flag) +{ + if ((flag != 0) && (flag != 1)) { printk(KERN_ERR "Illegal value %d\n", flag); - return -1; + return -EINVAL; + } + dev->dev_attrib.is_nonrot = flag; + pr_debug("dev[%p]: SE Device is_nonrot bit: %d\n", + dev, flag); + return 0; +} + +int se_dev_set_emulate_rest_reord(struct se_device *dev, int flag) +{ + if (flag != 0) { + printk(KERN_ERR "dev[%p]: SE Device emulatation of restricted" + " reordering not implemented\n", dev); + return -ENOSYS; } - DEV_ATTRIB(dev)->enforce_pr_isids = flag; - printk(KERN_INFO "dev[%p]: SE Device enforce_pr_isids bit: %s\n", dev, - (DEV_ATTRIB(dev)->enforce_pr_isids) ? "Enabled" : "Disabled"); + dev->dev_attrib.emulate_rest_reord = flag; + pr_debug("dev[%p]: SE Device emulate_rest_reord: %d\n", dev, flag); return 0; } @@ -1177,211 +1048,202 @@ int se_dev_set_enforce_pr_isids(struct se_device *dev, int flag) */ int se_dev_set_queue_depth(struct se_device *dev, u32 queue_depth) { - u32 orig_queue_depth = dev->queue_depth; - - if (atomic_read(&dev->dev_export_obj.obj_access_count)) { - printk(KERN_ERR "dev[%p]: Unable to change SE Device TCQ while" - " dev_export_obj: %d count exists\n", dev, - atomic_read(&dev->dev_export_obj.obj_access_count)); - return -1; + if (dev->export_count) { + pr_err("dev[%p]: Unable to change SE Device TCQ while" + " export_count is %d\n", + dev, dev->export_count); + return -EINVAL; } - if (!(queue_depth)) { - printk(KERN_ERR "dev[%p]: Illegal ZERO value for queue" + if (!queue_depth) { + pr_err("dev[%p]: Illegal ZERO value for queue" "_depth\n", dev); - return -1; + return -EINVAL; } - if (TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { - if (queue_depth > DEV_ATTRIB(dev)->hw_queue_depth) { - printk(KERN_ERR "dev[%p]: Passed queue_depth: %u" + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { + if (queue_depth > dev->dev_attrib.hw_queue_depth) { + pr_err("dev[%p]: Passed queue_depth: %u" " exceeds TCM/SE_Device TCQ: %u\n", dev, queue_depth, - DEV_ATTRIB(dev)->hw_queue_depth); - return -1; + dev->dev_attrib.hw_queue_depth); + return -EINVAL; } } else { - if (queue_depth > DEV_ATTRIB(dev)->queue_depth) { - if (queue_depth > DEV_ATTRIB(dev)->hw_queue_depth) { - printk(KERN_ERR "dev[%p]: Passed queue_depth:" + if (queue_depth > dev->dev_attrib.queue_depth) { + if (queue_depth > dev->dev_attrib.hw_queue_depth) { + pr_err("dev[%p]: Passed queue_depth:" " %u exceeds TCM/SE_Device MAX" " TCQ: %u\n", dev, queue_depth, - DEV_ATTRIB(dev)->hw_queue_depth); - return -1; + dev->dev_attrib.hw_queue_depth); + return -EINVAL; } } } - DEV_ATTRIB(dev)->queue_depth = dev->queue_depth = queue_depth; - if (queue_depth > orig_queue_depth) - atomic_add(queue_depth - orig_queue_depth, &dev->depth_left); - else if (queue_depth < orig_queue_depth) - atomic_sub(orig_queue_depth - queue_depth, &dev->depth_left); - - printk(KERN_INFO "dev[%p]: SE Device TCQ Depth changed to: %u\n", + dev->dev_attrib.queue_depth = dev->queue_depth = queue_depth; + pr_debug("dev[%p]: SE Device TCQ Depth changed to: %u\n", dev, queue_depth); return 0; } -int se_dev_set_max_sectors(struct se_device *dev, u32 max_sectors) +int se_dev_set_fabric_max_sectors(struct se_device *dev, u32 fabric_max_sectors) { - int force = 0; /* Force setting for VDEVS */ - - if (atomic_read(&dev->dev_export_obj.obj_access_count)) { - printk(KERN_ERR "dev[%p]: Unable to change SE Device" - " max_sectors while dev_export_obj: %d count exists\n", - dev, atomic_read(&dev->dev_export_obj.obj_access_count)); - return -1; - } - if (!(max_sectors)) { - printk(KERN_ERR "dev[%p]: Illegal ZERO value for" - " max_sectors\n", dev); - return -1; - } - if (max_sectors < DA_STATUS_MAX_SECTORS_MIN) { - printk(KERN_ERR "dev[%p]: Passed max_sectors: %u less than" - " DA_STATUS_MAX_SECTORS_MIN: %u\n", dev, max_sectors, + int block_size = dev->dev_attrib.block_size; + + if (dev->export_count) { + pr_err("dev[%p]: Unable to change SE Device" + " fabric_max_sectors while export_count is %d\n", + dev, dev->export_count); + return -EINVAL; + } + if (!fabric_max_sectors) { + pr_err("dev[%p]: Illegal ZERO value for" + " fabric_max_sectors\n", dev); + return -EINVAL; + } + if (fabric_max_sectors < DA_STATUS_MAX_SECTORS_MIN) { + pr_err("dev[%p]: Passed fabric_max_sectors: %u less than" + " DA_STATUS_MAX_SECTORS_MIN: %u\n", dev, fabric_max_sectors, DA_STATUS_MAX_SECTORS_MIN); - return -1; + return -EINVAL; } - if (TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { - if (max_sectors > DEV_ATTRIB(dev)->hw_max_sectors) { - printk(KERN_ERR "dev[%p]: Passed max_sectors: %u" + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { + if (fabric_max_sectors > dev->dev_attrib.hw_max_sectors) { + pr_err("dev[%p]: Passed fabric_max_sectors: %u" " greater than TCM/SE_Device max_sectors:" - " %u\n", dev, max_sectors, - DEV_ATTRIB(dev)->hw_max_sectors); - return -1; + " %u\n", dev, fabric_max_sectors, + dev->dev_attrib.hw_max_sectors); + return -EINVAL; } } else { - if (!(force) && (max_sectors > - DEV_ATTRIB(dev)->hw_max_sectors)) { - printk(KERN_ERR "dev[%p]: Passed max_sectors: %u" - " greater than TCM/SE_Device max_sectors" - ": %u, use force=1 to override.\n", dev, - max_sectors, DEV_ATTRIB(dev)->hw_max_sectors); - return -1; - } - if (max_sectors > DA_STATUS_MAX_SECTORS_MAX) { - printk(KERN_ERR "dev[%p]: Passed max_sectors: %u" + if (fabric_max_sectors > DA_STATUS_MAX_SECTORS_MAX) { + pr_err("dev[%p]: Passed fabric_max_sectors: %u" " greater than DA_STATUS_MAX_SECTORS_MAX:" - " %u\n", dev, max_sectors, + " %u\n", dev, fabric_max_sectors, DA_STATUS_MAX_SECTORS_MAX); - return -1; + return -EINVAL; } } + /* + * Align max_sectors down to PAGE_SIZE to follow transport_allocate_data_tasks() + */ + if (!block_size) { + block_size = 512; + pr_warn("Defaulting to 512 for zero block_size\n"); + } + fabric_max_sectors = se_dev_align_max_sectors(fabric_max_sectors, + block_size); - DEV_ATTRIB(dev)->max_sectors = max_sectors; - printk("dev[%p]: SE Device max_sectors changed to %u\n", - dev, max_sectors); + dev->dev_attrib.fabric_max_sectors = fabric_max_sectors; + pr_debug("dev[%p]: SE Device max_sectors changed to %u\n", + dev, fabric_max_sectors); return 0; } int se_dev_set_optimal_sectors(struct se_device *dev, u32 optimal_sectors) { - if (atomic_read(&dev->dev_export_obj.obj_access_count)) { - printk(KERN_ERR "dev[%p]: Unable to change SE Device" - " optimal_sectors while dev_export_obj: %d count exists\n", - dev, atomic_read(&dev->dev_export_obj.obj_access_count)); + if (dev->export_count) { + pr_err("dev[%p]: Unable to change SE Device" + " optimal_sectors while export_count is %d\n", + dev, dev->export_count); return -EINVAL; } - if (TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { - printk(KERN_ERR "dev[%p]: Passed optimal_sectors cannot be" + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { + pr_err("dev[%p]: Passed optimal_sectors cannot be" " changed for TCM/pSCSI\n", dev); return -EINVAL; } - if (optimal_sectors > DEV_ATTRIB(dev)->max_sectors) { - printk(KERN_ERR "dev[%p]: Passed optimal_sectors %u cannot be" - " greater than max_sectors: %u\n", dev, - optimal_sectors, DEV_ATTRIB(dev)->max_sectors); + if (optimal_sectors > dev->dev_attrib.fabric_max_sectors) { + pr_err("dev[%p]: Passed optimal_sectors %u cannot be" + " greater than fabric_max_sectors: %u\n", dev, + optimal_sectors, dev->dev_attrib.fabric_max_sectors); return -EINVAL; } - DEV_ATTRIB(dev)->optimal_sectors = optimal_sectors; - printk(KERN_INFO "dev[%p]: SE Device optimal_sectors changed to %u\n", + dev->dev_attrib.optimal_sectors = optimal_sectors; + pr_debug("dev[%p]: SE Device optimal_sectors changed to %u\n", dev, optimal_sectors); return 0; } int se_dev_set_block_size(struct se_device *dev, u32 block_size) { - if (atomic_read(&dev->dev_export_obj.obj_access_count)) { - printk(KERN_ERR "dev[%p]: Unable to change SE Device block_size" - " while dev_export_obj: %d count exists\n", dev, - atomic_read(&dev->dev_export_obj.obj_access_count)); - return -1; + if (dev->export_count) { + pr_err("dev[%p]: Unable to change SE Device block_size" + " while export_count is %d\n", + dev, dev->export_count); + return -EINVAL; } if ((block_size != 512) && (block_size != 1024) && (block_size != 2048) && (block_size != 4096)) { - printk(KERN_ERR "dev[%p]: Illegal value for block_device: %u" + pr_err("dev[%p]: Illegal value for block_device: %u" " for SE device, must be 512, 1024, 2048 or 4096\n", dev, block_size); - return -1; + return -EINVAL; } - if (TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { - printk(KERN_ERR "dev[%p]: Not allowed to change block_size for" + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { + pr_err("dev[%p]: Not allowed to change block_size for" " Physical Device, use for Linux/SCSI to change" " block_size for underlying hardware\n", dev); - return -1; + return -EINVAL; } - DEV_ATTRIB(dev)->block_size = block_size; - printk(KERN_INFO "dev[%p]: SE Device block_size changed to %u\n", + dev->dev_attrib.block_size = block_size; + pr_debug("dev[%p]: SE Device block_size changed to %u\n", dev, block_size); + + if (dev->dev_attrib.max_bytes_per_io) + dev->dev_attrib.hw_max_sectors = + dev->dev_attrib.max_bytes_per_io / block_size; + return 0; } struct se_lun *core_dev_add_lun( struct se_portal_group *tpg, - struct se_hba *hba, struct se_device *dev, - u32 lun) + u32 unpacked_lun) { - struct se_lun *lun_p; - u32 lun_access = 0; - - if (atomic_read(&dev->dev_access_obj.obj_access_count) != 0) { - printk(KERN_ERR "Unable to export struct se_device while dev_access_obj: %d\n", - atomic_read(&dev->dev_access_obj.obj_access_count)); - return NULL; - } + struct se_lun *lun; + int rc; - lun_p = core_tpg_pre_addlun(tpg, lun); - if ((IS_ERR(lun_p)) || !(lun_p)) - return NULL; + lun = core_tpg_alloc_lun(tpg, unpacked_lun); + if (IS_ERR(lun)) + return lun; - if (dev->dev_flags & DF_READ_ONLY) - lun_access = TRANSPORT_LUNFLAGS_READ_ONLY; - else - lun_access = TRANSPORT_LUNFLAGS_READ_WRITE; + rc = core_tpg_add_lun(tpg, lun, + TRANSPORT_LUNFLAGS_READ_WRITE, dev); + if (rc < 0) + return ERR_PTR(rc); - if (core_tpg_post_addlun(tpg, lun_p, lun_access, dev) < 0) - return NULL; - - printk(KERN_INFO "%s_TPG[%u]_LUN[%u] - Activated %s Logical Unit from" - " CORE HBA: %u\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), lun_p->unpacked_lun, - TPG_TFO(tpg)->get_fabric_name(), hba->hba_id); + pr_debug("%s_TPG[%u]_LUN[%u] - Activated %s Logical Unit from" + " CORE HBA: %u\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), lun->unpacked_lun, + tpg->se_tpg_tfo->get_fabric_name(), dev->se_hba->hba_id); /* * Update LUN maps for dynamically added initiators when * generate_node_acl is enabled. */ - if (TPG_TFO(tpg)->tpg_check_demo_mode(tpg)) { + if (tpg->se_tpg_tfo->tpg_check_demo_mode(tpg)) { struct se_node_acl *acl; - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); list_for_each_entry(acl, &tpg->acl_node_list, acl_list) { - if (acl->dynamic_node_acl) { - spin_unlock_bh(&tpg->acl_node_lock); + if (acl->dynamic_node_acl && + (!tpg->se_tpg_tfo->tpg_check_demo_mode_login_only || + !tpg->se_tpg_tfo->tpg_check_demo_mode_login_only(tpg))) { + spin_unlock_irq(&tpg->acl_node_lock); core_tpg_add_node_to_devs(acl, tpg); - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); } } - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); } - return lun_p; + return lun; } /* core_dev_del_lun(): @@ -1393,18 +1255,17 @@ int core_dev_del_lun( u32 unpacked_lun) { struct se_lun *lun; - int ret = 0; - lun = core_tpg_pre_dellun(tpg, unpacked_lun, &ret); - if (!(lun)) - return ret; + lun = core_tpg_pre_dellun(tpg, unpacked_lun); + if (IS_ERR(lun)) + return PTR_ERR(lun); core_tpg_post_dellun(tpg, lun); - printk(KERN_INFO "%s_TPG[%u]_LUN[%u] - Deactivated %s Logical Unit from" - " device object\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), unpacked_lun, - TPG_TFO(tpg)->get_fabric_name()); + pr_debug("%s_TPG[%u]_LUN[%u] - Deactivated %s Logical Unit from" + " device object\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), unpacked_lun, + tpg->se_tpg_tfo->get_fabric_name()); return 0; } @@ -1415,21 +1276,21 @@ struct se_lun *core_get_lun_from_tpg(struct se_portal_group *tpg, u32 unpacked_l spin_lock(&tpg->tpg_lun_lock); if (unpacked_lun > (TRANSPORT_MAX_LUNS_PER_TPG-1)) { - printk(KERN_ERR "%s LUN: %u exceeds TRANSPORT_MAX_LUNS" + pr_err("%s LUN: %u exceeds TRANSPORT_MAX_LUNS" "_PER_TPG-1: %u for Target Portal Group: %hu\n", - TPG_TFO(tpg)->get_fabric_name(), unpacked_lun, + tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, TRANSPORT_MAX_LUNS_PER_TPG-1, - TPG_TFO(tpg)->tpg_get_tag(tpg)); + tpg->se_tpg_tfo->tpg_get_tag(tpg)); spin_unlock(&tpg->tpg_lun_lock); return NULL; } - lun = &tpg->tpg_lun_list[unpacked_lun]; + lun = tpg->tpg_lun_list[unpacked_lun]; if (lun->lun_status != TRANSPORT_LUN_STATUS_FREE) { - printk(KERN_ERR "%s Logical Unit Number: %u is not free on" + pr_err("%s Logical Unit Number: %u is not free on" " Target Portal Group: %hu, ignoring request.\n", - TPG_TFO(tpg)->get_fabric_name(), unpacked_lun, - TPG_TFO(tpg)->tpg_get_tag(tpg)); + tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, + tpg->se_tpg_tfo->tpg_get_tag(tpg)); spin_unlock(&tpg->tpg_lun_lock); return NULL; } @@ -1448,21 +1309,21 @@ static struct se_lun *core_dev_get_lun(struct se_portal_group *tpg, u32 unpacked spin_lock(&tpg->tpg_lun_lock); if (unpacked_lun > (TRANSPORT_MAX_LUNS_PER_TPG-1)) { - printk(KERN_ERR "%s LUN: %u exceeds TRANSPORT_MAX_LUNS_PER" + pr_err("%s LUN: %u exceeds TRANSPORT_MAX_LUNS_PER" "_TPG-1: %u for Target Portal Group: %hu\n", - TPG_TFO(tpg)->get_fabric_name(), unpacked_lun, + tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, TRANSPORT_MAX_LUNS_PER_TPG-1, - TPG_TFO(tpg)->tpg_get_tag(tpg)); + tpg->se_tpg_tfo->tpg_get_tag(tpg)); spin_unlock(&tpg->tpg_lun_lock); return NULL; } - lun = &tpg->tpg_lun_list[unpacked_lun]; + lun = tpg->tpg_lun_list[unpacked_lun]; if (lun->lun_status != TRANSPORT_LUN_STATUS_ACTIVE) { - printk(KERN_ERR "%s Logical Unit Number: %u is not active on" + pr_err("%s Logical Unit Number: %u is not active on" " Target Portal Group: %hu, ignoring request.\n", - TPG_TFO(tpg)->get_fabric_name(), unpacked_lun, - TPG_TFO(tpg)->tpg_get_tag(tpg)); + tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, + tpg->se_tpg_tfo->tpg_get_tag(tpg)); spin_unlock(&tpg->tpg_lun_lock); return NULL; } @@ -1473,27 +1334,21 @@ static struct se_lun *core_dev_get_lun(struct se_portal_group *tpg, u32 unpacked struct se_lun_acl *core_dev_init_initiator_node_lun_acl( struct se_portal_group *tpg, + struct se_node_acl *nacl, u32 mapped_lun, - char *initiatorname, int *ret) { struct se_lun_acl *lacl; - struct se_node_acl *nacl; - if (strlen(initiatorname) > TRANSPORT_IQN_LEN) { - printk(KERN_ERR "%s InitiatorName exceeds maximum size.\n", - TPG_TFO(tpg)->get_fabric_name()); + if (strlen(nacl->initiatorname) >= TRANSPORT_IQN_LEN) { + pr_err("%s InitiatorName exceeds maximum size.\n", + tpg->se_tpg_tfo->get_fabric_name()); *ret = -EOVERFLOW; return NULL; } - nacl = core_tpg_get_initiator_node_acl(tpg, initiatorname); - if (!(nacl)) { - *ret = -EINVAL; - return NULL; - } lacl = kzalloc(sizeof(struct se_lun_acl), GFP_KERNEL); - if (!(lacl)) { - printk(KERN_ERR "Unable to allocate memory for struct se_lun_acl.\n"); + if (!lacl) { + pr_err("Unable to allocate memory for struct se_lun_acl.\n"); *ret = -ENOMEM; return NULL; } @@ -1501,7 +1356,8 @@ struct se_lun_acl *core_dev_init_initiator_node_lun_acl( INIT_LIST_HEAD(&lacl->lacl_list); lacl->mapped_lun = mapped_lun; lacl->se_lun_nacl = nacl; - snprintf(lacl->initiatorname, TRANSPORT_IQN_LEN, "%s", initiatorname); + snprintf(lacl->initiatorname, TRANSPORT_IQN_LEN, "%s", + nacl->initiatorname); return lacl; } @@ -1516,16 +1372,16 @@ int core_dev_add_initiator_node_lun_acl( struct se_node_acl *nacl; lun = core_dev_get_lun(tpg, unpacked_lun); - if (!(lun)) { - printk(KERN_ERR "%s Logical Unit Number: %u is not active on" + if (!lun) { + pr_err("%s Logical Unit Number: %u is not active on" " Target Portal Group: %hu, ignoring request.\n", - TPG_TFO(tpg)->get_fabric_name(), unpacked_lun, - TPG_TFO(tpg)->tpg_get_tag(tpg)); + tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, + tpg->se_tpg_tfo->tpg_get_tag(tpg)); return -EINVAL; } nacl = lacl->se_lun_nacl; - if (!(nacl)) + if (!nacl) return -EINVAL; if ((lun->lun_access & TRANSPORT_LUNFLAGS_READ_ONLY) && @@ -1534,19 +1390,19 @@ int core_dev_add_initiator_node_lun_acl( lacl->se_lun = lun; - if (core_update_device_list_for_node(lun, lacl, lacl->mapped_lun, - lun_access, nacl, tpg, 1) < 0) + if (core_enable_device_list_for_node(lun, lacl, lacl->mapped_lun, + lun_access, nacl, tpg) < 0) return -EINVAL; spin_lock(&lun->lun_acl_lock); list_add_tail(&lacl->lacl_list, &lun->lun_acl_list); atomic_inc(&lun->lun_acl_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&lun->lun_acl_lock); - printk(KERN_INFO "%s_TPG[%hu]_LUN[%u->%u] - Added %s ACL for " - " InitiatorNode: %s\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), unpacked_lun, lacl->mapped_lun, + pr_debug("%s_TPG[%hu]_LUN[%u->%u] - Added %s ACL for " + " InitiatorNode: %s\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), unpacked_lun, lacl->mapped_lun, (lun_access & TRANSPORT_LUNFLAGS_READ_WRITE) ? "RW" : "RO", lacl->initiatorname); /* @@ -1569,24 +1425,24 @@ int core_dev_del_initiator_node_lun_acl( struct se_node_acl *nacl; nacl = lacl->se_lun_nacl; - if (!(nacl)) + if (!nacl) return -EINVAL; spin_lock(&lun->lun_acl_lock); list_del(&lacl->lacl_list); atomic_dec(&lun->lun_acl_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); spin_unlock(&lun->lun_acl_lock); - core_update_device_list_for_node(lun, NULL, lacl->mapped_lun, - TRANSPORT_LUNFLAGS_NO_ACCESS, nacl, tpg, 0); + core_disable_device_list_for_node(lun, NULL, lacl->mapped_lun, + TRANSPORT_LUNFLAGS_NO_ACCESS, nacl, tpg); lacl->se_lun = NULL; - printk(KERN_INFO "%s_TPG[%hu]_LUN[%u] - Removed ACL for" + pr_debug("%s_TPG[%hu]_LUN[%u] - Removed ACL for" " InitiatorNode: %s Mapped LUN: %u\n", - TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), lun->unpacked_lun, + tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), lun->unpacked_lun, lacl->initiatorname, lacl->mapped_lun); return 0; @@ -1596,98 +1452,288 @@ void core_dev_free_initiator_node_lun_acl( struct se_portal_group *tpg, struct se_lun_acl *lacl) { - printk("%s_TPG[%hu] - Freeing ACL for %s InitiatorNode: %s" - " Mapped LUN: %u\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), - TPG_TFO(tpg)->get_fabric_name(), + pr_debug("%s_TPG[%hu] - Freeing ACL for %s InitiatorNode: %s" + " Mapped LUN: %u\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), + tpg->se_tpg_tfo->get_fabric_name(), lacl->initiatorname, lacl->mapped_lun); kfree(lacl); } -int core_dev_setup_virtual_lun0(void) +static void scsi_dump_inquiry(struct se_device *dev) +{ + struct t10_wwn *wwn = &dev->t10_wwn; + char buf[17]; + int i, device_type; + /* + * Print Linux/SCSI style INQUIRY formatting to the kernel ring buffer + */ + for (i = 0; i < 8; i++) + if (wwn->vendor[i] >= 0x20) + buf[i] = wwn->vendor[i]; + else + buf[i] = ' '; + buf[i] = '\0'; + pr_debug(" Vendor: %s\n", buf); + + for (i = 0; i < 16; i++) + if (wwn->model[i] >= 0x20) + buf[i] = wwn->model[i]; + else + buf[i] = ' '; + buf[i] = '\0'; + pr_debug(" Model: %s\n", buf); + + for (i = 0; i < 4; i++) + if (wwn->revision[i] >= 0x20) + buf[i] = wwn->revision[i]; + else + buf[i] = ' '; + buf[i] = '\0'; + pr_debug(" Revision: %s\n", buf); + + device_type = dev->transport->get_device_type(dev); + pr_debug(" Type: %s ", scsi_device_type(device_type)); +} + +struct se_device *target_alloc_device(struct se_hba *hba, const char *name) { - struct se_hba *hba; struct se_device *dev; - struct se_subsystem_dev *se_dev = NULL; - struct se_subsystem_api *t; - char buf[16]; + struct se_lun *xcopy_lun; + + dev = hba->transport->alloc_device(hba, name); + if (!dev) + return NULL; + + dev->dev_link_magic = SE_DEV_LINK_MAGIC; + dev->se_hba = hba; + dev->transport = hba->transport; + dev->prot_length = sizeof(struct se_dif_v1_tuple); + + INIT_LIST_HEAD(&dev->dev_list); + INIT_LIST_HEAD(&dev->dev_sep_list); + INIT_LIST_HEAD(&dev->dev_tmr_list); + INIT_LIST_HEAD(&dev->delayed_cmd_list); + INIT_LIST_HEAD(&dev->state_list); + INIT_LIST_HEAD(&dev->qf_cmd_list); + INIT_LIST_HEAD(&dev->g_dev_node); + spin_lock_init(&dev->execute_task_lock); + spin_lock_init(&dev->delayed_cmd_lock); + spin_lock_init(&dev->dev_reservation_lock); + spin_lock_init(&dev->se_port_lock); + spin_lock_init(&dev->se_tmr_lock); + spin_lock_init(&dev->qf_cmd_lock); + sema_init(&dev->caw_sem, 1); + atomic_set(&dev->dev_ordered_id, 0); + INIT_LIST_HEAD(&dev->t10_wwn.t10_vpd_list); + spin_lock_init(&dev->t10_wwn.t10_vpd_lock); + INIT_LIST_HEAD(&dev->t10_pr.registration_list); + INIT_LIST_HEAD(&dev->t10_pr.aptpl_reg_list); + spin_lock_init(&dev->t10_pr.registration_lock); + spin_lock_init(&dev->t10_pr.aptpl_reg_lock); + INIT_LIST_HEAD(&dev->t10_alua.tg_pt_gps_list); + spin_lock_init(&dev->t10_alua.tg_pt_gps_lock); + INIT_LIST_HEAD(&dev->t10_alua.lba_map_list); + spin_lock_init(&dev->t10_alua.lba_map_lock); + + dev->t10_wwn.t10_dev = dev; + dev->t10_alua.t10_dev = dev; + + dev->dev_attrib.da_dev = dev; + dev->dev_attrib.emulate_model_alias = DA_EMULATE_MODEL_ALIAS; + dev->dev_attrib.emulate_dpo = DA_EMULATE_DPO; + dev->dev_attrib.emulate_fua_write = DA_EMULATE_FUA_WRITE; + dev->dev_attrib.emulate_fua_read = DA_EMULATE_FUA_READ; + dev->dev_attrib.emulate_write_cache = DA_EMULATE_WRITE_CACHE; + dev->dev_attrib.emulate_ua_intlck_ctrl = DA_EMULATE_UA_INTLLCK_CTRL; + dev->dev_attrib.emulate_tas = DA_EMULATE_TAS; + dev->dev_attrib.emulate_tpu = DA_EMULATE_TPU; + dev->dev_attrib.emulate_tpws = DA_EMULATE_TPWS; + dev->dev_attrib.emulate_caw = DA_EMULATE_CAW; + dev->dev_attrib.emulate_3pc = DA_EMULATE_3PC; + dev->dev_attrib.pi_prot_type = TARGET_DIF_TYPE0_PROT; + dev->dev_attrib.enforce_pr_isids = DA_ENFORCE_PR_ISIDS; + dev->dev_attrib.is_nonrot = DA_IS_NONROT; + dev->dev_attrib.emulate_rest_reord = DA_EMULATE_REST_REORD; + dev->dev_attrib.max_unmap_lba_count = DA_MAX_UNMAP_LBA_COUNT; + dev->dev_attrib.max_unmap_block_desc_count = + DA_MAX_UNMAP_BLOCK_DESC_COUNT; + dev->dev_attrib.unmap_granularity = DA_UNMAP_GRANULARITY_DEFAULT; + dev->dev_attrib.unmap_granularity_alignment = + DA_UNMAP_GRANULARITY_ALIGNMENT_DEFAULT; + dev->dev_attrib.max_write_same_len = DA_MAX_WRITE_SAME_LEN; + dev->dev_attrib.fabric_max_sectors = DA_FABRIC_MAX_SECTORS; + dev->dev_attrib.optimal_sectors = DA_FABRIC_MAX_SECTORS; + + xcopy_lun = &dev->xcopy_lun; + xcopy_lun->lun_se_dev = dev; + init_completion(&xcopy_lun->lun_shutdown_comp); + INIT_LIST_HEAD(&xcopy_lun->lun_acl_list); + spin_lock_init(&xcopy_lun->lun_acl_lock); + spin_lock_init(&xcopy_lun->lun_sep_lock); + init_completion(&xcopy_lun->lun_ref_comp); + + return dev; +} + +int target_configure_device(struct se_device *dev) +{ + struct se_hba *hba = dev->se_hba; int ret; - hba = core_alloc_hba("rd_dr", 0, HBA_FLAGS_INTERNAL_USE); - if (IS_ERR(hba)) - return PTR_ERR(hba); + if (dev->dev_flags & DF_CONFIGURED) { + pr_err("se_dev->se_dev_ptr already set for storage" + " object\n"); + return -EEXIST; + } - se_global->g_lun0_hba = hba; - t = hba->transport; + ret = dev->transport->configure_device(dev); + if (ret) + goto out; + dev->dev_flags |= DF_CONFIGURED; - se_dev = kzalloc(sizeof(struct se_subsystem_dev), GFP_KERNEL); - if (!(se_dev)) { - printk(KERN_ERR "Unable to allocate memory for" - " struct se_subsystem_dev\n"); - ret = -ENOMEM; + /* + * XXX: there is not much point to have two different values here.. + */ + dev->dev_attrib.block_size = dev->dev_attrib.hw_block_size; + dev->dev_attrib.queue_depth = dev->dev_attrib.hw_queue_depth; + + /* + * Align max_hw_sectors down to PAGE_SIZE I/O transfers + */ + dev->dev_attrib.hw_max_sectors = + se_dev_align_max_sectors(dev->dev_attrib.hw_max_sectors, + dev->dev_attrib.hw_block_size); + + dev->dev_index = scsi_get_new_index(SCSI_DEVICE_INDEX); + dev->creation_time = get_jiffies_64(); + + ret = core_setup_alua(dev); + if (ret) goto out; - } - INIT_LIST_HEAD(&se_dev->g_se_dev_list); - INIT_LIST_HEAD(&se_dev->t10_wwn.t10_vpd_list); - spin_lock_init(&se_dev->t10_wwn.t10_vpd_lock); - INIT_LIST_HEAD(&se_dev->t10_reservation.registration_list); - INIT_LIST_HEAD(&se_dev->t10_reservation.aptpl_reg_list); - spin_lock_init(&se_dev->t10_reservation.registration_lock); - spin_lock_init(&se_dev->t10_reservation.aptpl_reg_lock); - INIT_LIST_HEAD(&se_dev->t10_alua.tg_pt_gps_list); - spin_lock_init(&se_dev->t10_alua.tg_pt_gps_lock); - spin_lock_init(&se_dev->se_dev_lock); - se_dev->t10_reservation.pr_aptpl_buf_len = PR_APTPL_BUF_LEN; - se_dev->t10_wwn.t10_sub_dev = se_dev; - se_dev->t10_alua.t10_sub_dev = se_dev; - se_dev->se_dev_attrib.da_sub_dev = se_dev; - se_dev->se_dev_hba = hba; - - se_dev->se_dev_su_ptr = t->allocate_virtdevice(hba, "virt_lun0"); - if (!(se_dev->se_dev_su_ptr)) { - printk(KERN_ERR "Unable to locate subsystem dependent pointer" - " from allocate_virtdevice()\n"); + + /* + * Startup the struct se_device processing thread + */ + dev->tmr_wq = alloc_workqueue("tmr-%s", WQ_MEM_RECLAIM | WQ_UNBOUND, 1, + dev->transport->name); + if (!dev->tmr_wq) { + pr_err("Unable to create tmr workqueue for %s\n", + dev->transport->name); ret = -ENOMEM; - goto out; + goto out_free_alua; } - se_global->g_lun0_su_dev = se_dev; - memset(buf, 0, 16); - sprintf(buf, "rd_pages=8"); - t->set_configfs_dev_params(hba, se_dev, buf, sizeof(buf)); + /* + * Setup work_queue for QUEUE_FULL + */ + INIT_WORK(&dev->qf_work_queue, target_qf_do_work); - dev = t->create_virtdevice(hba, se_dev, se_dev->se_dev_su_ptr); - if (!(dev) || IS_ERR(dev)) { - ret = -ENOMEM; - goto out; + /* + * Preload the initial INQUIRY const values if we are doing + * anything virtual (IBLOCK, FILEIO, RAMDISK), but not for TCM/pSCSI + * passthrough because this is being provided by the backend LLD. + */ + if (dev->transport->transport_type != TRANSPORT_PLUGIN_PHBA_PDEV) { + strncpy(&dev->t10_wwn.vendor[0], "LIO-ORG", 8); + strncpy(&dev->t10_wwn.model[0], + dev->transport->inquiry_prod, 16); + strncpy(&dev->t10_wwn.revision[0], + dev->transport->inquiry_rev, 4); } - se_dev->se_dev_ptr = dev; - se_global->g_lun0_dev = dev; + + scsi_dump_inquiry(dev); + + spin_lock(&hba->device_lock); + hba->dev_count++; + spin_unlock(&hba->device_lock); + + mutex_lock(&g_device_mutex); + list_add_tail(&dev->g_dev_node, &g_device_list); + mutex_unlock(&g_device_mutex); return 0; + +out_free_alua: + core_alua_free_lu_gp_mem(dev); out: - se_global->g_lun0_su_dev = NULL; - kfree(se_dev); - if (se_global->g_lun0_hba) { - core_delete_hba(se_global->g_lun0_hba); - se_global->g_lun0_hba = NULL; + se_release_vpd_for_dev(dev); + return ret; +} + +void target_free_device(struct se_device *dev) +{ + struct se_hba *hba = dev->se_hba; + + WARN_ON(!list_empty(&dev->dev_sep_list)); + + if (dev->dev_flags & DF_CONFIGURED) { + destroy_workqueue(dev->tmr_wq); + + mutex_lock(&g_device_mutex); + list_del(&dev->g_dev_node); + mutex_unlock(&g_device_mutex); + + spin_lock(&hba->device_lock); + hba->dev_count--; + spin_unlock(&hba->device_lock); + } + + core_alua_free_lu_gp_mem(dev); + core_alua_set_lba_map(dev, NULL, 0, 0); + core_scsi3_free_all_registrations(dev); + se_release_vpd_for_dev(dev); + + if (dev->transport->free_prot) + dev->transport->free_prot(dev); + + dev->transport->free_device(dev); +} + +int core_dev_setup_virtual_lun0(void) +{ + struct se_hba *hba; + struct se_device *dev; + char buf[] = "rd_pages=8,rd_nullio=1"; + int ret; + + hba = core_alloc_hba("rd_mcp", 0, HBA_FLAGS_INTERNAL_USE); + if (IS_ERR(hba)) + return PTR_ERR(hba); + + dev = target_alloc_device(hba, "virt_lun0"); + if (!dev) { + ret = -ENOMEM; + goto out_free_hba; } + + hba->transport->set_configfs_dev_params(dev, buf, sizeof(buf)); + + ret = target_configure_device(dev); + if (ret) + goto out_free_se_dev; + + lun0_hba = hba; + g_lun0_dev = dev; + return 0; + +out_free_se_dev: + target_free_device(dev); +out_free_hba: + core_delete_hba(hba); return ret; } void core_dev_release_virtual_lun0(void) { - struct se_hba *hba = se_global->g_lun0_hba; - struct se_subsystem_dev *su_dev = se_global->g_lun0_su_dev; + struct se_hba *hba = lun0_hba; - if (!(hba)) + if (!hba) return; - if (se_global->g_lun0_dev) - se_free_virtual_device(se_global->g_lun0_dev, hba); - - kfree(su_dev); + if (g_lun0_dev) + target_free_device(g_lun0_dev); core_delete_hba(hba); } diff --git a/drivers/target/target_core_fabric_configfs.c b/drivers/target/target_core_fabric_configfs.c index b65d1c8e774..7de9f0475d0 100644 --- a/drivers/target/target_core_fabric_configfs.c +++ b/drivers/target/target_core_fabric_configfs.c @@ -4,10 +4,9 @@ * This file contains generic fabric module configfs infrastructure for * TCM v4.x code * - * Copyright (c) 2010 Rising Tide Systems - * Copyright (c) 2010 Linux-iSCSI.org + * (c) Copyright 2010-2013 Datera, Inc. * - * Copyright (c) 2010 Nicholas A. Bellinger <nab@linux-iscsi.org> + * 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 @@ -22,8 +21,6 @@ #include <linux/module.h> #include <linux/moduleparam.h> -#include <linux/version.h> -#include <generated/utsrelease.h> #include <linux/utsname.h> #include <linux/init.h> #include <linux/fs.h> @@ -37,16 +34,13 @@ #include <linux/configfs.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_tpg.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_fabric.h> #include <target/target_core_fabric_configfs.h> #include <target/target_core_configfs.h> #include <target/configfs_macros.h> +#include "target_core_internal.h" #include "target_core_alua.h" -#include "target_core_hba.h" #include "target_core_pr.h" #define TF_CIT_SETUP(_name, _item_ops, _group_ops, _attrs) \ @@ -59,7 +53,7 @@ static void target_fabric_setup_##_name##_cit(struct target_fabric_configfs *tf) cit->ct_group_ops = _group_ops; \ cit->ct_attrs = _attrs; \ cit->ct_owner = tf->tf_module; \ - printk("Setup generic %s\n", __stringify(_name)); \ + pr_debug("Setup generic %s\n", __stringify(_name)); \ } /* Start of tfc_tpg_mappedlun_cit */ @@ -76,11 +70,17 @@ static int target_fabric_mappedlun_link( struct se_portal_group *se_tpg; struct config_item *nacl_ci, *tpg_ci, *tpg_ci_s, *wwn_ci, *wwn_ci_s; int ret = 0, lun_access; + + if (lun->lun_link_magic != SE_LUN_LINK_MAGIC) { + pr_err("Bad lun->lun_link_magic, not a valid lun_ci pointer:" + " %p to struct lun: %p\n", lun_ci, lun); + return -EFAULT; + } /* * Ensure that the source port exists */ - if (!(lun->lun_sep) || !(lun->lun_sep->sep_tpg)) { - printk(KERN_ERR "Source se_lun->lun_sep or lun->lun_sep->sep" + if (!lun->lun_sep || !lun->lun_sep->sep_tpg) { + pr_err("Source se_lun->lun_sep or lun->lun_sep->sep" "_tpg does not exist\n"); return -EINVAL; } @@ -95,12 +95,12 @@ static int target_fabric_mappedlun_link( * Make sure the SymLink is going to the same $FABRIC/$WWN/tpgt_$TPGT */ if (strcmp(config_item_name(wwn_ci), config_item_name(wwn_ci_s))) { - printk(KERN_ERR "Illegal Initiator ACL SymLink outside of %s\n", + pr_err("Illegal Initiator ACL SymLink outside of %s\n", config_item_name(wwn_ci)); return -EINVAL; } if (strcmp(config_item_name(tpg_ci), config_item_name(tpg_ci_s))) { - printk(KERN_ERR "Illegal Initiator ACL Symlink outside of %s" + pr_err("Illegal Initiator ACL Symlink outside of %s" " TPGT: %s\n", config_item_name(wwn_ci), config_item_name(tpg_ci)); return -EINVAL; @@ -112,12 +112,12 @@ static int target_fabric_mappedlun_link( * tpg_1/attrib/demo_mode_write_protect=1 */ spin_lock_irq(&lacl->se_lun_nacl->device_list_lock); - deve = &lacl->se_lun_nacl->device_list[lacl->mapped_lun]; + deve = lacl->se_lun_nacl->device_list[lacl->mapped_lun]; if (deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS) lun_access = deve->lun_flags; else lun_access = - (TPG_TFO(se_tpg)->tpg_check_prod_mode_write_protect( + (se_tpg->se_tpg_tfo->tpg_check_prod_mode_write_protect( se_tpg)) ? TRANSPORT_LUNFLAGS_READ_ONLY : TRANSPORT_LUNFLAGS_READ_WRITE; spin_unlock_irq(&lacl->se_lun_nacl->device_list_lock); @@ -141,12 +141,12 @@ static int target_fabric_mappedlun_unlink( struct se_lun_acl *lacl = container_of(to_config_group(lun_acl_ci), struct se_lun_acl, se_lun_group); struct se_node_acl *nacl = lacl->se_lun_nacl; - struct se_dev_entry *deve = &nacl->device_list[lacl->mapped_lun]; + struct se_dev_entry *deve = nacl->device_list[lacl->mapped_lun]; struct se_portal_group *se_tpg; /* * Determine if the underlying MappedLUN has already been released.. */ - if (!(deve->se_lun)) + if (!deve->se_lun) return 0; lun = container_of(to_config_group(lun_ci), struct se_lun, lun_group); @@ -172,7 +172,7 @@ static ssize_t target_fabric_mappedlun_show_write_protect( ssize_t len; spin_lock_irq(&se_nacl->device_list_lock); - deve = &se_nacl->device_list[lacl->mapped_lun]; + deve = se_nacl->device_list[lacl->mapped_lun]; len = sprintf(page, "%d\n", (deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY) ? 1 : 0); @@ -189,9 +189,11 @@ static ssize_t target_fabric_mappedlun_store_write_protect( struct se_node_acl *se_nacl = lacl->se_lun_nacl; struct se_portal_group *se_tpg = se_nacl->se_tpg; unsigned long op; + int ret; - if (strict_strtoul(page, 0, &op)) - return -EINVAL; + ret = kstrtoul(page, 0, &op); + if (ret) + return ret; if ((op != 1) && (op != 0)) return -EINVAL; @@ -201,9 +203,9 @@ static ssize_t target_fabric_mappedlun_store_write_protect( TRANSPORT_LUNFLAGS_READ_WRITE, lacl->se_lun_nacl); - printk(KERN_INFO "%s_ConfigFS: Changed Initiator ACL: %s" + pr_debug("%s_ConfigFS: Changed Initiator ACL: %s" " Mapped LUN: %u Write Protect bit to %s\n", - TPG_TFO(se_tpg)->get_fabric_name(), + se_tpg->se_tpg_tfo->get_fabric_name(), lacl->initiatorname, lacl->mapped_lun, (op) ? "ON" : "OFF"); return count; @@ -241,6 +243,32 @@ TF_CIT_SETUP(tpg_mappedlun, &target_fabric_mappedlun_item_ops, NULL, /* End of tfc_tpg_mappedlun_cit */ +/* Start of tfc_tpg_mappedlun_port_cit */ + +static struct config_group *target_core_mappedlun_stat_mkdir( + struct config_group *group, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} + +static void target_core_mappedlun_stat_rmdir( + struct config_group *group, + struct config_item *item) +{ + return; +} + +static struct configfs_group_operations target_fabric_mappedlun_stat_group_ops = { + .make_group = target_core_mappedlun_stat_mkdir, + .drop_item = target_core_mappedlun_stat_rmdir, +}; + +TF_CIT_SETUP(tpg_mappedlun_stat, NULL, &target_fabric_mappedlun_stat_group_ops, + NULL); + +/* End of tfc_tpg_mappedlun_port_cit */ + /* Start of tfc_tpg_nacl_attrib_cit */ CONFIGFS_EATTR_OPS(target_fabric_nacl_attrib, se_node_acl, acl_attrib_group); @@ -294,19 +322,20 @@ static struct config_group *target_fabric_make_mappedlun( struct target_fabric_configfs *tf = se_tpg->se_tpg_wwn->wwn_tf; struct se_lun_acl *lacl; struct config_item *acl_ci; + struct config_group *lacl_cg = NULL, *ml_stat_grp = NULL; char *buf; unsigned long mapped_lun; int ret = 0; acl_ci = &group->cg_item; - if (!(acl_ci)) { - printk(KERN_ERR "Unable to locatel acl_ci\n"); + if (!acl_ci) { + pr_err("Unable to locatel acl_ci\n"); return NULL; } buf = kzalloc(strlen(name) + 1, GFP_KERNEL); - if (!(buf)) { - printk(KERN_ERR "Unable to allocate memory for name buf\n"); + if (!buf) { + pr_err("Unable to allocate memory for name buf\n"); return ERR_PTR(-ENOMEM); } snprintf(buf, strlen(name) + 1, "%s", name); @@ -314,7 +343,7 @@ static struct config_group *target_fabric_make_mappedlun( * Make sure user is creating iscsi/$IQN/$TPGT/acls/$INITIATOR/lun_$ID. */ if (strstr(buf, "lun_") != buf) { - printk(KERN_ERR "Unable to locate \"lun_\" from buf: %s" + pr_err("Unable to locate \"lun_\" from buf: %s" " name: %s\n", buf, name); ret = -EINVAL; goto out; @@ -323,22 +352,60 @@ static struct config_group *target_fabric_make_mappedlun( * Determine the Mapped LUN value. This is what the SCSI Initiator * Port will actually see. */ - if (strict_strtoul(buf + 4, 0, &mapped_lun) || mapped_lun > UINT_MAX) { + ret = kstrtoul(buf + 4, 0, &mapped_lun); + if (ret) + goto out; + if (mapped_lun > UINT_MAX) { + ret = -EINVAL; + goto out; + } + if (mapped_lun > (TRANSPORT_MAX_LUNS_PER_TPG-1)) { + pr_err("Mapped LUN: %lu exceeds TRANSPORT_MAX_LUNS_PER_TPG" + "-1: %u for Target Portal Group: %u\n", mapped_lun, + TRANSPORT_MAX_LUNS_PER_TPG-1, + se_tpg->se_tpg_tfo->tpg_get_tag(se_tpg)); ret = -EINVAL; goto out; } - lacl = core_dev_init_initiator_node_lun_acl(se_tpg, mapped_lun, - config_item_name(acl_ci), &ret); - if (!(lacl)) + lacl = core_dev_init_initiator_node_lun_acl(se_tpg, se_nacl, + mapped_lun, &ret); + if (!lacl) { + ret = -EINVAL; goto out; + } + + lacl_cg = &lacl->se_lun_group; + lacl_cg->default_groups = kmalloc(sizeof(struct config_group *) * 2, + GFP_KERNEL); + if (!lacl_cg->default_groups) { + pr_err("Unable to allocate lacl_cg->default_groups\n"); + ret = -ENOMEM; + goto out; + } config_group_init_type_name(&lacl->se_lun_group, name, - &TF_CIT_TMPL(tf)->tfc_tpg_mappedlun_cit); + &tf->tf_cit_tmpl.tfc_tpg_mappedlun_cit); + config_group_init_type_name(&lacl->ml_stat_grps.stat_group, + "statistics", &tf->tf_cit_tmpl.tfc_tpg_mappedlun_stat_cit); + lacl_cg->default_groups[0] = &lacl->ml_stat_grps.stat_group; + lacl_cg->default_groups[1] = NULL; + + ml_stat_grp = &lacl->ml_stat_grps.stat_group; + ml_stat_grp->default_groups = kmalloc(sizeof(struct config_group *) * 3, + GFP_KERNEL); + if (!ml_stat_grp->default_groups) { + pr_err("Unable to allocate ml_stat_grp->default_groups\n"); + ret = -ENOMEM; + goto out; + } + target_stat_setup_mappedlun_default_groups(lacl); kfree(buf); return &lacl->se_lun_group; out: + if (lacl_cg) + kfree(lacl_cg->default_groups); kfree(buf); return ERR_PTR(ret); } @@ -347,6 +414,28 @@ static void target_fabric_drop_mappedlun( struct config_group *group, struct config_item *item) { + struct se_lun_acl *lacl = container_of(to_config_group(item), + struct se_lun_acl, se_lun_group); + struct config_item *df_item; + struct config_group *lacl_cg = NULL, *ml_stat_grp = NULL; + int i; + + ml_stat_grp = &lacl->ml_stat_grps.stat_group; + for (i = 0; ml_stat_grp->default_groups[i]; i++) { + df_item = &ml_stat_grp->default_groups[i]->cg_item; + ml_stat_grp->default_groups[i] = NULL; + config_item_put(df_item); + } + kfree(ml_stat_grp->default_groups); + + lacl_cg = &lacl->se_lun_group; + for (i = 0; lacl_cg->default_groups[i]; i++) { + df_item = &lacl_cg->default_groups[i]->cg_item; + lacl_cg->default_groups[i] = NULL; + config_item_put(df_item); + } + kfree(lacl_cg->default_groups); + config_item_put(item); } @@ -376,6 +465,15 @@ TF_CIT_SETUP(tpg_nacl_base, &target_fabric_nacl_base_item_ops, /* End of tfc_tpg_nacl_base_cit */ +/* Start of tfc_node_fabric_stats_cit */ +/* + * This is used as a placeholder for struct se_node_acl->acl_fabric_stat_group + * to allow fabrics access to ->acl_fabric_stat_group->default_groups[] + */ +TF_CIT_SETUP(tpg_nacl_stat, NULL, NULL, NULL); + +/* End of tfc_wwn_fabric_stats_cit */ + /* Start of tfc_tpg_nacl_cit */ static struct config_group *target_fabric_make_nodeacl( @@ -388,30 +486,34 @@ static struct config_group *target_fabric_make_nodeacl( struct se_node_acl *se_nacl; struct config_group *nacl_cg; - if (!(tf->tf_ops.fabric_make_nodeacl)) { - printk(KERN_ERR "tf->tf_ops.fabric_make_nodeacl is NULL\n"); + if (!tf->tf_ops.fabric_make_nodeacl) { + pr_err("tf->tf_ops.fabric_make_nodeacl is NULL\n"); return ERR_PTR(-ENOSYS); } se_nacl = tf->tf_ops.fabric_make_nodeacl(se_tpg, group, name); if (IS_ERR(se_nacl)) - return ERR_PTR(PTR_ERR(se_nacl)); + return ERR_CAST(se_nacl); nacl_cg = &se_nacl->acl_group; nacl_cg->default_groups = se_nacl->acl_default_groups; nacl_cg->default_groups[0] = &se_nacl->acl_attrib_group; nacl_cg->default_groups[1] = &se_nacl->acl_auth_group; nacl_cg->default_groups[2] = &se_nacl->acl_param_group; - nacl_cg->default_groups[3] = NULL; + nacl_cg->default_groups[3] = &se_nacl->acl_fabric_stat_group; + nacl_cg->default_groups[4] = NULL; config_group_init_type_name(&se_nacl->acl_group, name, - &TF_CIT_TMPL(tf)->tfc_tpg_nacl_base_cit); + &tf->tf_cit_tmpl.tfc_tpg_nacl_base_cit); config_group_init_type_name(&se_nacl->acl_attrib_group, "attrib", - &TF_CIT_TMPL(tf)->tfc_tpg_nacl_attrib_cit); + &tf->tf_cit_tmpl.tfc_tpg_nacl_attrib_cit); config_group_init_type_name(&se_nacl->acl_auth_group, "auth", - &TF_CIT_TMPL(tf)->tfc_tpg_nacl_auth_cit); + &tf->tf_cit_tmpl.tfc_tpg_nacl_auth_cit); config_group_init_type_name(&se_nacl->acl_param_group, "param", - &TF_CIT_TMPL(tf)->tfc_tpg_nacl_param_cit); + &tf->tf_cit_tmpl.tfc_tpg_nacl_param_cit); + config_group_init_type_name(&se_nacl->acl_fabric_stat_group, + "fabric_statistics", + &tf->tf_cit_tmpl.tfc_tpg_nacl_stat_cit); return &se_nacl->acl_group; } @@ -482,18 +584,18 @@ static struct config_group *target_fabric_make_np( struct target_fabric_configfs *tf = se_tpg->se_tpg_wwn->wwn_tf; struct se_tpg_np *se_tpg_np; - if (!(tf->tf_ops.fabric_make_np)) { - printk(KERN_ERR "tf->tf_ops.fabric_make_np is NULL\n"); + if (!tf->tf_ops.fabric_make_np) { + pr_err("tf->tf_ops.fabric_make_np is NULL\n"); return ERR_PTR(-ENOSYS); } se_tpg_np = tf->tf_ops.fabric_make_np(se_tpg, group, name); - if (!(se_tpg_np) || IS_ERR(se_tpg_np)) + if (!se_tpg_np || IS_ERR(se_tpg_np)) return ERR_PTR(-EINVAL); se_tpg_np->tpg_np_parent = se_tpg; config_group_init_type_name(&se_tpg_np->tpg_np_group, name, - &TF_CIT_TMPL(tf)->tfc_tpg_np_base_cit); + &tf->tf_cit_tmpl.tfc_tpg_np_base_cit); return &se_tpg_np->tpg_np_group; } @@ -537,10 +639,7 @@ static ssize_t target_fabric_port_show_attr_alua_tg_pt_gp( struct se_lun *lun, char *page) { - if (!(lun)) - return -ENODEV; - - if (!(lun->lun_sep)) + if (!lun || !lun->lun_sep) return -ENODEV; return core_alua_show_tg_pt_gp_info(lun->lun_sep, page); @@ -551,10 +650,7 @@ static ssize_t target_fabric_port_store_attr_alua_tg_pt_gp( const char *page, size_t count) { - if (!(lun)) - return -ENODEV; - - if (!(lun->lun_sep)) + if (!lun || !lun->lun_sep) return -ENODEV; return core_alua_store_tg_pt_gp_info(lun->lun_sep, page, count); @@ -569,10 +665,7 @@ static ssize_t target_fabric_port_show_attr_alua_tg_pt_offline( struct se_lun *lun, char *page) { - if (!(lun)) - return -ENODEV; - - if (!(lun->lun_sep)) + if (!lun || !lun->lun_sep) return -ENODEV; return core_alua_show_offline_bit(lun, page); @@ -583,10 +676,7 @@ static ssize_t target_fabric_port_store_attr_alua_tg_pt_offline( const char *page, size_t count) { - if (!(lun)) - return -ENODEV; - - if (!(lun->lun_sep)) + if (!lun || !lun->lun_sep) return -ENODEV; return core_alua_store_offline_bit(lun, page, count); @@ -601,10 +691,7 @@ static ssize_t target_fabric_port_show_attr_alua_tg_pt_status( struct se_lun *lun, char *page) { - if (!(lun)) - return -ENODEV; - - if (!(lun->lun_sep)) + if (!lun || !lun->lun_sep) return -ENODEV; return core_alua_show_secondary_status(lun, page); @@ -615,10 +702,7 @@ static ssize_t target_fabric_port_store_attr_alua_tg_pt_status( const char *page, size_t count) { - if (!(lun)) - return -ENODEV; - - if (!(lun->lun_sep)) + if (!lun || !lun->lun_sep) return -ENODEV; return core_alua_store_secondary_status(lun, page, count); @@ -633,10 +717,7 @@ static ssize_t target_fabric_port_show_attr_alua_tg_pt_write_md( struct se_lun *lun, char *page) { - if (!(lun)) - return -ENODEV; - - if (!(lun->lun_sep)) + if (!lun || !lun->lun_sep) return -ENODEV; return core_alua_show_secondary_write_metadata(lun, page); @@ -647,10 +728,7 @@ static ssize_t target_fabric_port_store_attr_alua_tg_pt_write_md( const char *page, size_t count) { - if (!(lun)) - return -ENODEV; - - if (!(lun->lun_sep)) + if (!lun || !lun->lun_sep) return -ENODEV; return core_alua_store_secondary_write_metadata(lun, page, count); @@ -674,40 +752,40 @@ static int target_fabric_port_link( struct config_item *se_dev_ci) { struct config_item *tpg_ci; - struct se_device *dev; struct se_lun *lun = container_of(to_config_group(lun_ci), struct se_lun, lun_group); struct se_lun *lun_p; struct se_portal_group *se_tpg; - struct se_subsystem_dev *se_dev = container_of( - to_config_group(se_dev_ci), struct se_subsystem_dev, - se_dev_group); + struct se_device *dev = + container_of(to_config_group(se_dev_ci), struct se_device, dev_group); struct target_fabric_configfs *tf; int ret; + if (dev->dev_link_magic != SE_DEV_LINK_MAGIC) { + pr_err("Bad dev->dev_link_magic, not a valid se_dev_ci pointer:" + " %p to struct se_device: %p\n", se_dev_ci, dev); + return -EFAULT; + } + + if (!(dev->dev_flags & DF_CONFIGURED)) { + pr_err("se_device not configured yet, cannot port link\n"); + return -ENODEV; + } + tpg_ci = &lun_ci->ci_parent->ci_group->cg_item; se_tpg = container_of(to_config_group(tpg_ci), struct se_portal_group, tpg_group); tf = se_tpg->se_tpg_wwn->wwn_tf; if (lun->lun_se_dev != NULL) { - printk(KERN_ERR "Port Symlink already exists\n"); + pr_err("Port Symlink already exists\n"); return -EEXIST; } - dev = se_dev->se_dev_ptr; - if (!(dev)) { - printk(KERN_ERR "Unable to locate struct se_device pointer from" - " %s\n", config_item_name(se_dev_ci)); - ret = -ENODEV; - goto out; - } - - lun_p = core_dev_add_lun(se_tpg, dev->se_hba, dev, - lun->unpacked_lun); - if ((IS_ERR(lun_p)) || !(lun_p)) { - printk(KERN_ERR "core_dev_add_lun() failed\n"); - ret = -EINVAL; + lun_p = core_dev_add_lun(se_tpg, dev, lun->unpacked_lun); + if (IS_ERR(lun_p)) { + pr_err("core_dev_add_lun() failed\n"); + ret = PTR_ERR(lun_p); goto out; } @@ -758,6 +836,31 @@ TF_CIT_SETUP(tpg_port, &target_fabric_port_item_ops, NULL, target_fabric_port_at /* End of tfc_tpg_port_cit */ +/* Start of tfc_tpg_port_stat_cit */ + +static struct config_group *target_core_port_stat_mkdir( + struct config_group *group, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} + +static void target_core_port_stat_rmdir( + struct config_group *group, + struct config_item *item) +{ + return; +} + +static struct configfs_group_operations target_fabric_port_stat_group_ops = { + .make_group = target_core_port_stat_mkdir, + .drop_item = target_core_port_stat_rmdir, +}; + +TF_CIT_SETUP(tpg_port_stat, NULL, &target_fabric_port_stat_group_ops, NULL); + +/* End of tfc_tpg_port_stat_cit */ + /* Start of tfc_tpg_lun_cit */ static struct config_group *target_fabric_make_lun( @@ -768,30 +871,83 @@ static struct config_group *target_fabric_make_lun( struct se_portal_group *se_tpg = container_of(group, struct se_portal_group, tpg_lun_group); struct target_fabric_configfs *tf = se_tpg->se_tpg_wwn->wwn_tf; + struct config_group *lun_cg = NULL, *port_stat_grp = NULL; unsigned long unpacked_lun; + int errno; if (strstr(name, "lun_") != name) { - printk(KERN_ERR "Unable to locate \'_\" in" + pr_err("Unable to locate \'_\" in" " \"lun_$LUN_NUMBER\"\n"); return ERR_PTR(-EINVAL); } - if (strict_strtoul(name + 4, 0, &unpacked_lun) || unpacked_lun > UINT_MAX) + errno = kstrtoul(name + 4, 0, &unpacked_lun); + if (errno) + return ERR_PTR(errno); + if (unpacked_lun > UINT_MAX) return ERR_PTR(-EINVAL); lun = core_get_lun_from_tpg(se_tpg, unpacked_lun); - if (!(lun)) + if (!lun) return ERR_PTR(-EINVAL); + lun_cg = &lun->lun_group; + lun_cg->default_groups = kmalloc(sizeof(struct config_group *) * 2, + GFP_KERNEL); + if (!lun_cg->default_groups) { + pr_err("Unable to allocate lun_cg->default_groups\n"); + return ERR_PTR(-ENOMEM); + } + config_group_init_type_name(&lun->lun_group, name, - &TF_CIT_TMPL(tf)->tfc_tpg_port_cit); + &tf->tf_cit_tmpl.tfc_tpg_port_cit); + config_group_init_type_name(&lun->port_stat_grps.stat_group, + "statistics", &tf->tf_cit_tmpl.tfc_tpg_port_stat_cit); + lun_cg->default_groups[0] = &lun->port_stat_grps.stat_group; + lun_cg->default_groups[1] = NULL; + + port_stat_grp = &lun->port_stat_grps.stat_group; + port_stat_grp->default_groups = kzalloc(sizeof(struct config_group *) * 4, + GFP_KERNEL); + if (!port_stat_grp->default_groups) { + pr_err("Unable to allocate port_stat_grp->default_groups\n"); + errno = -ENOMEM; + goto out; + } + target_stat_setup_port_default_groups(lun); return &lun->lun_group; +out: + if (lun_cg) + kfree(lun_cg->default_groups); + return ERR_PTR(errno); } static void target_fabric_drop_lun( struct config_group *group, struct config_item *item) { + struct se_lun *lun = container_of(to_config_group(item), + struct se_lun, lun_group); + struct config_item *df_item; + struct config_group *lun_cg, *port_stat_grp; + int i; + + port_stat_grp = &lun->port_stat_grps.stat_group; + for (i = 0; port_stat_grp->default_groups[i]; i++) { + df_item = &port_stat_grp->default_groups[i]->cg_item; + port_stat_grp->default_groups[i] = NULL; + config_item_put(df_item); + } + kfree(port_stat_grp->default_groups); + + lun_cg = &lun->lun_group; + for (i = 0; lun_cg->default_groups[i]; i++) { + df_item = &lun_cg->default_groups[i]->cg_item; + lun_cg->default_groups[i] = NULL; + config_item_put(df_item); + } + kfree(lun_cg->default_groups); + config_item_put(item); } @@ -817,6 +973,19 @@ TF_CIT_SETUP(tpg_attrib, &target_fabric_tpg_attrib_item_ops, NULL, NULL); /* End of tfc_tpg_attrib_cit */ +/* Start of tfc_tpg_auth_cit */ + +CONFIGFS_EATTR_OPS(target_fabric_tpg_auth, se_portal_group, tpg_auth_group); + +static struct configfs_item_operations target_fabric_tpg_auth_item_ops = { + .show_attribute = target_fabric_tpg_auth_attr_show, + .store_attribute = target_fabric_tpg_auth_attr_store, +}; + +TF_CIT_SETUP(tpg_auth, &target_fabric_tpg_auth_item_ops, NULL, NULL); + +/* End of tfc_tpg_attrib_cit */ + /* Start of tfc_tpg_param_cit */ CONFIGFS_EATTR_OPS(target_fabric_tpg_param, se_portal_group, tpg_param_group); @@ -866,13 +1035,13 @@ static struct config_group *target_fabric_make_tpg( struct target_fabric_configfs *tf = wwn->wwn_tf; struct se_portal_group *se_tpg; - if (!(tf->tf_ops.fabric_make_tpg)) { - printk(KERN_ERR "tf->tf_ops.fabric_make_tpg is NULL\n"); + if (!tf->tf_ops.fabric_make_tpg) { + pr_err("tf->tf_ops.fabric_make_tpg is NULL\n"); return ERR_PTR(-ENOSYS); } se_tpg = tf->tf_ops.fabric_make_tpg(wwn, group, name); - if (!(se_tpg) || IS_ERR(se_tpg)) + if (!se_tpg || IS_ERR(se_tpg)) return ERR_PTR(-EINVAL); /* * Setup default groups from pre-allocated se_tpg->tpg_default_groups @@ -882,21 +1051,24 @@ static struct config_group *target_fabric_make_tpg( se_tpg->tpg_group.default_groups[1] = &se_tpg->tpg_np_group; se_tpg->tpg_group.default_groups[2] = &se_tpg->tpg_acl_group; se_tpg->tpg_group.default_groups[3] = &se_tpg->tpg_attrib_group; - se_tpg->tpg_group.default_groups[4] = &se_tpg->tpg_param_group; - se_tpg->tpg_group.default_groups[5] = NULL; + se_tpg->tpg_group.default_groups[4] = &se_tpg->tpg_auth_group; + se_tpg->tpg_group.default_groups[5] = &se_tpg->tpg_param_group; + se_tpg->tpg_group.default_groups[6] = NULL; config_group_init_type_name(&se_tpg->tpg_group, name, - &TF_CIT_TMPL(tf)->tfc_tpg_base_cit); + &tf->tf_cit_tmpl.tfc_tpg_base_cit); config_group_init_type_name(&se_tpg->tpg_lun_group, "lun", - &TF_CIT_TMPL(tf)->tfc_tpg_lun_cit); + &tf->tf_cit_tmpl.tfc_tpg_lun_cit); config_group_init_type_name(&se_tpg->tpg_np_group, "np", - &TF_CIT_TMPL(tf)->tfc_tpg_np_cit); + &tf->tf_cit_tmpl.tfc_tpg_np_cit); config_group_init_type_name(&se_tpg->tpg_acl_group, "acls", - &TF_CIT_TMPL(tf)->tfc_tpg_nacl_cit); + &tf->tf_cit_tmpl.tfc_tpg_nacl_cit); config_group_init_type_name(&se_tpg->tpg_attrib_group, "attrib", - &TF_CIT_TMPL(tf)->tfc_tpg_attrib_cit); + &tf->tf_cit_tmpl.tfc_tpg_attrib_cit); + config_group_init_type_name(&se_tpg->tpg_auth_group, "auth", + &tf->tf_cit_tmpl.tfc_tpg_auth_cit); config_group_init_type_name(&se_tpg->tpg_param_group, "param", - &TF_CIT_TMPL(tf)->tfc_tpg_param_cit); + &tf->tf_cit_tmpl.tfc_tpg_param_cit); return &se_tpg->tpg_group; } @@ -946,6 +1118,15 @@ TF_CIT_SETUP(tpg, &target_fabric_tpg_item_ops, &target_fabric_tpg_group_ops, /* End of tfc_tpg_cit */ +/* Start of tfc_wwn_fabric_stats_cit */ +/* + * This is used as a placeholder for struct se_wwn->fabric_stat_group + * to allow fabrics access to ->fabric_stat_group->default_groups[] + */ +TF_CIT_SETUP(wwn_fabric_stats, NULL, NULL, NULL); + +/* End of tfc_wwn_fabric_stats_cit */ + /* Start of tfc_wwn_cit */ static struct config_group *target_fabric_make_wwn( @@ -956,18 +1137,27 @@ static struct config_group *target_fabric_make_wwn( struct target_fabric_configfs, tf_group); struct se_wwn *wwn; - if (!(tf->tf_ops.fabric_make_wwn)) { - printk(KERN_ERR "tf->tf_ops.fabric_make_wwn is NULL\n"); + if (!tf->tf_ops.fabric_make_wwn) { + pr_err("tf->tf_ops.fabric_make_wwn is NULL\n"); return ERR_PTR(-ENOSYS); } wwn = tf->tf_ops.fabric_make_wwn(tf, group, name); - if (!(wwn) || IS_ERR(wwn)) + if (!wwn || IS_ERR(wwn)) return ERR_PTR(-EINVAL); wwn->wwn_tf = tf; + /* + * Setup default groups from pre-allocated wwn->wwn_default_groups + */ + wwn->wwn_group.default_groups = wwn->wwn_default_groups; + wwn->wwn_group.default_groups[0] = &wwn->fabric_stat_group; + wwn->wwn_group.default_groups[1] = NULL; + config_group_init_type_name(&wwn->wwn_group, name, - &TF_CIT_TMPL(tf)->tfc_tpg_cit); + &tf->tf_cit_tmpl.tfc_tpg_cit); + config_group_init_type_name(&wwn->fabric_stat_group, "fabric_statistics", + &tf->tf_cit_tmpl.tfc_wwn_fabric_stats_cit); return &wwn->wwn_group; } @@ -976,6 +1166,18 @@ static void target_fabric_drop_wwn( struct config_group *group, struct config_item *item) { + struct se_wwn *wwn = container_of(to_config_group(item), + struct se_wwn, wwn_group); + struct config_item *df_item; + struct config_group *cg = &wwn->wwn_group; + int i; + + for (i = 0; cg->default_groups[i]; i++) { + df_item = &cg->default_groups[i]->cg_item; + cg->default_groups[i] = NULL; + config_item_put(df_item); + } + config_item_put(item); } @@ -1015,20 +1217,25 @@ int target_fabric_setup_cits(struct target_fabric_configfs *tf) { target_fabric_setup_discovery_cit(tf); target_fabric_setup_wwn_cit(tf); + target_fabric_setup_wwn_fabric_stats_cit(tf); target_fabric_setup_tpg_cit(tf); target_fabric_setup_tpg_base_cit(tf); target_fabric_setup_tpg_port_cit(tf); + target_fabric_setup_tpg_port_stat_cit(tf); target_fabric_setup_tpg_lun_cit(tf); target_fabric_setup_tpg_np_cit(tf); target_fabric_setup_tpg_np_base_cit(tf); target_fabric_setup_tpg_attrib_cit(tf); + target_fabric_setup_tpg_auth_cit(tf); target_fabric_setup_tpg_param_cit(tf); target_fabric_setup_tpg_nacl_cit(tf); target_fabric_setup_tpg_nacl_base_cit(tf); target_fabric_setup_tpg_nacl_attrib_cit(tf); target_fabric_setup_tpg_nacl_auth_cit(tf); target_fabric_setup_tpg_nacl_param_cit(tf); + target_fabric_setup_tpg_nacl_stat_cit(tf); target_fabric_setup_tpg_mappedlun_cit(tf); + target_fabric_setup_tpg_mappedlun_stat_cit(tf); return 0; } diff --git a/drivers/target/target_core_fabric_lib.c b/drivers/target/target_core_fabric_lib.c index 26285644e4d..0d1cf8b4f49 100644 --- a/drivers/target/target_core_fabric_lib.c +++ b/drivers/target/target_core_fabric_lib.c @@ -4,8 +4,7 @@ * This file contains generic high level protocol identifier and PR * handlers for TCM fabric modules * - * Copyright (c) 2010 Rising Tide Systems, Inc. - * Copyright (c) 2010 Linux-iSCSI.org + * (c) Copyright 2010-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@linux-iscsi.org> * @@ -25,20 +24,19 @@ * ******************************************************************************/ +#include <linux/kernel.h> #include <linux/string.h> #include <linux/ctype.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> +#include <linux/export.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_fabric.h> #include <target/target_core_configfs.h> -#include "target_core_hba.h" +#include "target_core_internal.h" #include "target_core_pr.h" /* @@ -61,9 +59,9 @@ u32 sas_get_pr_transport_id( int *format_code, unsigned char *buf) { - unsigned char binary, *ptr; - int i; - u32 off = 4; + unsigned char *ptr; + int ret; + /* * Set PROTOCOL IDENTIFIER to 6h for SAS */ @@ -74,10 +72,10 @@ u32 sas_get_pr_transport_id( */ ptr = &se_nacl->initiatorname[4]; /* Skip over 'naa. prefix */ - for (i = 0; i < 16; i += 2) { - binary = transport_asciihex_to_binaryhex(&ptr[i]); - buf[off++] = binary; - } + ret = hex2bin(&buf[4], ptr, 8); + if (ret < 0) + pr_debug("sas transport_id: invalid hex string\n"); + /* * The SAS Transport ID is a hardcoded 24-byte length */ @@ -157,9 +155,10 @@ u32 fc_get_pr_transport_id( int *format_code, unsigned char *buf) { - unsigned char binary, *ptr; - int i; + unsigned char *ptr; + int i, ret; u32 off = 8; + /* * PROTOCOL IDENTIFIER is 0h for FCP-2 * @@ -172,12 +171,13 @@ u32 fc_get_pr_transport_id( ptr = &se_nacl->initiatorname[0]; for (i = 0; i < 24; ) { - if (!(strncmp(&ptr[i], ":", 1))) { + if (!strncmp(&ptr[i], ":", 1)) { i++; continue; } - binary = transport_asciihex_to_binaryhex(&ptr[i]); - buf[off++] = binary; + ret = hex2bin(&buf[off++], &ptr[i], 1); + if (ret < 0) + pr_debug("fc transport_id: invalid hex string\n"); i += 2; } /* @@ -337,7 +337,7 @@ u32 iscsi_get_pr_transport_id_len( * 00b: iSCSI Initiator device TransportID format */ if (pr_reg->isid_present_at_reg) { - len += 5; /* For ",i,0x" ASCII seperator */ + len += 5; /* For ",i,0x" ASCII separator */ len += 7; /* For iSCSI Initiator Session ID + Null terminator */ *format_code = 1; } else @@ -386,7 +386,7 @@ char *iscsi_parse_pr_out_transport_id( * Reserved */ if ((format_code != 0x00) && (format_code != 0x40)) { - printk(KERN_ERR "Illegal format code: 0x%02x for iSCSI" + pr_err("Illegal format code: 0x%02x for iSCSI" " Initiator Transport ID\n", format_code); return NULL; } @@ -398,7 +398,7 @@ char *iscsi_parse_pr_out_transport_id( add_len = ((buf[2] >> 8) & 0xff); add_len |= (buf[3] & 0xff); - tid_len = strlen((char *)&buf[4]); + tid_len = strlen(&buf[4]); tid_len += 4; /* Add four bytes for iSCSI Transport ID header */ tid_len += 1; /* Add one byte for NULL terminator */ padding = ((-tid_len) & 3); @@ -406,7 +406,7 @@ char *iscsi_parse_pr_out_transport_id( tid_len += padding; if ((add_len + 4) != tid_len) { - printk(KERN_INFO "LIO-Target Extracted add_len: %hu " + pr_debug("LIO-Target Extracted add_len: %hu " "does not match calculated tid_len: %u," " using tid_len instead\n", add_len+4, tid_len); *out_tid_len = tid_len; @@ -414,26 +414,26 @@ char *iscsi_parse_pr_out_transport_id( *out_tid_len = (add_len + 4); } /* - * Check for ',i,0x' seperator between iSCSI Name and iSCSI Initiator + * Check for ',i,0x' separator between iSCSI Name and iSCSI Initiator * Session ID as defined in Table 390 - iSCSI initiator port TransportID * format. */ if (format_code == 0x40) { - p = strstr((char *)&buf[4], ",i,0x"); - if (!(p)) { - printk(KERN_ERR "Unable to locate \",i,0x\" seperator" + p = strstr(&buf[4], ",i,0x"); + if (!p) { + pr_err("Unable to locate \",i,0x\" separator" " for Initiator port identifier: %s\n", - (char *)&buf[4]); + &buf[4]); return NULL; } *p = '\0'; /* Terminate iSCSI Name */ - p += 5; /* Skip over ",i,0x" seperator */ + p += 5; /* Skip over ",i,0x" separator */ *port_nexus_ptr = p; /* * Go ahead and do the lower case conversion of the received * 12 ASCII characters representing the ISID in the TransportID - * for comparision against the running iSCSI session's ISID from + * for comparison against the running iSCSI session's ISID from * iscsi_target.c:lio_sess_get_initiator_sid() */ for (i = 0; i < 12; i++) { diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c index 0aaca885668..7d6cddaec52 100644 --- a/drivers/target/target_core_file.c +++ b/drivers/target/target_core_file.c @@ -3,10 +3,7 @@ * * This file contains the Storage Engine <-> FILEIO transport specific functions * - * Copyright (c) 2005 PyX Technologies, Inc. - * Copyright (c) 2005-2006 SBE, Inc. All Rights Reserved. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2005-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -26,36 +23,27 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/string.h> #include <linux/parser.h> #include <linux/timer.h> #include <linux/blkdev.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> +#include <linux/module.h> +#include <linux/falloc.h> #include <scsi/scsi.h> #include <scsi/scsi_host.h> +#include <asm/unaligned.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_transport.h> +#include <target/target_core_backend.h> #include "target_core_file.h" -#if 1 -#define DEBUG_FD_CACHE(x...) printk(x) -#else -#define DEBUG_FD_CACHE(x...) -#endif - -#if 1 -#define DEBUG_FD_FUA(x...) printk(x) -#else -#define DEBUG_FD_FUA(x...) -#endif - -static struct se_subsystem_api fileio_template; +static inline struct fd_dev *FD_DEV(struct se_device *dev) +{ + return container_of(dev, struct fd_dev, dev); +} /* fd_attach_hba(): (Part of se_subsystem_api_t template) * @@ -66,24 +54,20 @@ static int fd_attach_hba(struct se_hba *hba, u32 host_id) struct fd_host *fd_host; fd_host = kzalloc(sizeof(struct fd_host), GFP_KERNEL); - if (!(fd_host)) { - printk(KERN_ERR "Unable to allocate memory for struct fd_host\n"); - return -1; + if (!fd_host) { + pr_err("Unable to allocate memory for struct fd_host\n"); + return -ENOMEM; } fd_host->fd_host_id = host_id; - atomic_set(&hba->left_queue_depth, FD_HBA_QUEUE_DEPTH); - atomic_set(&hba->max_queue_depth, FD_HBA_QUEUE_DEPTH); - hba->hba_ptr = (void *) fd_host; + hba->hba_ptr = fd_host; - printk(KERN_INFO "CORE_HBA[%d] - TCM FILEIO HBA Driver %s on Generic" + pr_debug("CORE_HBA[%d] - TCM FILEIO HBA Driver %s on Generic" " Target Core Stack %s\n", hba->hba_id, FD_VERSION, TARGET_CORE_MOD_VERSION); - printk(KERN_INFO "CORE_HBA[%d] - Attached FILEIO HBA: %u to Generic" - " Target Core with TCQ Depth: %d MaxSectors: %u\n", - hba->hba_id, fd_host->fd_host_id, - atomic_read(&hba->max_queue_depth), FD_MAX_SECTORS); + pr_debug("CORE_HBA[%d] - Attached FILEIO HBA: %u to Generic\n", + hba->hba_id, fd_host->fd_host_id); return 0; } @@ -92,83 +76,68 @@ static void fd_detach_hba(struct se_hba *hba) { struct fd_host *fd_host = hba->hba_ptr; - printk(KERN_INFO "CORE_HBA[%d] - Detached FILEIO HBA: %u from Generic" + pr_debug("CORE_HBA[%d] - Detached FILEIO HBA: %u from Generic" " Target Core\n", hba->hba_id, fd_host->fd_host_id); kfree(fd_host); hba->hba_ptr = NULL; } -static void *fd_allocate_virtdevice(struct se_hba *hba, const char *name) +static struct se_device *fd_alloc_device(struct se_hba *hba, const char *name) { struct fd_dev *fd_dev; - struct fd_host *fd_host = (struct fd_host *) hba->hba_ptr; + struct fd_host *fd_host = hba->hba_ptr; fd_dev = kzalloc(sizeof(struct fd_dev), GFP_KERNEL); - if (!(fd_dev)) { - printk(KERN_ERR "Unable to allocate memory for struct fd_dev\n"); + if (!fd_dev) { + pr_err("Unable to allocate memory for struct fd_dev\n"); return NULL; } fd_dev->fd_host = fd_host; - printk(KERN_INFO "FILEIO: Allocated fd_dev for %p\n", name); + pr_debug("FILEIO: Allocated fd_dev for %p\n", name); - return fd_dev; + return &fd_dev->dev; } -/* fd_create_virtdevice(): (Part of se_subsystem_api_t template) - * - * - */ -static struct se_device *fd_create_virtdevice( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - void *p) +static int fd_configure_device(struct se_device *dev) { - char *dev_p = NULL; - struct se_device *dev; - struct se_dev_limits dev_limits; - struct queue_limits *limits; - struct fd_dev *fd_dev = (struct fd_dev *) p; - struct fd_host *fd_host = (struct fd_host *) hba->hba_ptr; - mm_segment_t old_fs; + struct fd_dev *fd_dev = FD_DEV(dev); + struct fd_host *fd_host = dev->se_hba->hba_ptr; struct file *file; struct inode *inode = NULL; - int dev_flags = 0, flags; - - memset(&dev_limits, 0, sizeof(struct se_dev_limits)); - - old_fs = get_fs(); - set_fs(get_ds()); - dev_p = getname(fd_dev->fd_dev_name); - set_fs(old_fs); + int flags, ret = -EINVAL; - if (IS_ERR(dev_p)) { - printk(KERN_ERR "getname(%s) failed: %lu\n", - fd_dev->fd_dev_name, IS_ERR(dev_p)); - goto fail; + if (!(fd_dev->fbd_flags & FBDF_HAS_PATH)) { + pr_err("Missing fd_dev_name=\n"); + return -EINVAL; } -#if 0 - if (di->no_create_file) - flags = O_RDWR | O_LARGEFILE; - else - flags = O_RDWR | O_CREAT | O_LARGEFILE; -#else - flags = O_RDWR | O_CREAT | O_LARGEFILE; -#endif -/* flags |= O_DIRECT; */ + /* - * If fd_buffered_io=1 has not been set explictly (the default), - * use O_SYNC to force FILEIO writes to disk. + * Use O_DSYNC by default instead of O_SYNC to forgo syncing + * of pure timestamp updates. */ - if (!(fd_dev->fbd_flags & FDBD_USE_BUFFERED_IO)) - flags |= O_SYNC; + flags = O_RDWR | O_CREAT | O_LARGEFILE | O_DSYNC; - file = filp_open(dev_p, flags, 0600); + /* + * Optionally allow fd_buffered_io=1 to be enabled for people + * who want use the fs buffer cache as an WriteCache mechanism. + * + * This means that in event of a hard failure, there is a risk + * of silent data-loss if the SCSI client has *not* performed a + * forced unit access (FUA) write, or issued SYNCHRONIZE_CACHE + * to write-out the entire device cache. + */ + if (fd_dev->fbd_flags & FDBD_HAS_BUFFERED_IO_WCE) { + pr_debug("FILEIO: Disabling O_DSYNC, using buffered FILEIO\n"); + flags &= ~O_DSYNC; + } - if (IS_ERR(file) || !file || !file->f_dentry) { - printk(KERN_ERR "filp_open(%s) failed\n", dev_p); + file = filp_open(fd_dev->fd_dev_name, flags, 0600); + if (IS_ERR(file)) { + pr_err("filp_open(%s) failed\n", fd_dev->fd_dev_name); + ret = PTR_ERR(file); goto fail; } fd_dev->fd_file = file; @@ -180,78 +149,105 @@ static struct se_device *fd_create_virtdevice( */ inode = file->f_mapping->host; if (S_ISBLK(inode->i_mode)) { - struct request_queue *q; - /* - * Setup the local scope queue_limits from struct request_queue->limits - * to pass into transport_add_device_to_core_hba() as struct se_dev_limits. - */ - q = bdev_get_queue(inode->i_bdev); - limits = &dev_limits.limits; - limits->logical_block_size = bdev_logical_block_size(inode->i_bdev); - limits->max_hw_sectors = queue_max_hw_sectors(q); - limits->max_sectors = queue_max_sectors(q); + struct request_queue *q = bdev_get_queue(inode->i_bdev); + unsigned long long dev_size; + + fd_dev->fd_block_size = bdev_logical_block_size(inode->i_bdev); /* * Determine the number of bytes from i_size_read() minus * one (1) logical sector from underlying struct block_device */ - fd_dev->fd_block_size = bdev_logical_block_size(inode->i_bdev); - fd_dev->fd_dev_size = (i_size_read(file->f_mapping->host) - + dev_size = (i_size_read(file->f_mapping->host) - fd_dev->fd_block_size); - printk(KERN_INFO "FILEIO: Using size: %llu bytes from struct" + pr_debug("FILEIO: Using size: %llu bytes from struct" " block_device blocks: %llu logical_block_size: %d\n", - fd_dev->fd_dev_size, - div_u64(fd_dev->fd_dev_size, fd_dev->fd_block_size), + dev_size, div_u64(dev_size, fd_dev->fd_block_size), fd_dev->fd_block_size); + /* + * Check if the underlying struct block_device request_queue supports + * the QUEUE_FLAG_DISCARD bit for UNMAP/WRITE_SAME in SCSI + TRIM + * in ATA and we need to set TPE=1 + */ + if (blk_queue_discard(q)) { + dev->dev_attrib.max_unmap_lba_count = + q->limits.max_discard_sectors; + /* + * Currently hardcoded to 1 in Linux/SCSI code.. + */ + dev->dev_attrib.max_unmap_block_desc_count = 1; + dev->dev_attrib.unmap_granularity = + q->limits.discard_granularity >> 9; + dev->dev_attrib.unmap_granularity_alignment = + q->limits.discard_alignment; + pr_debug("IFILE: BLOCK Discard support available," + " disabled by default\n"); + } + /* + * Enable write same emulation for IBLOCK and use 0xFFFF as + * the smaller WRITE_SAME(10) only has a two-byte block count. + */ + dev->dev_attrib.max_write_same_len = 0xFFFF; + + if (blk_queue_nonrot(q)) + dev->dev_attrib.is_nonrot = 1; } else { if (!(fd_dev->fbd_flags & FBDF_HAS_SIZE)) { - printk(KERN_ERR "FILEIO: Missing fd_dev_size=" + pr_err("FILEIO: Missing fd_dev_size=" " parameter, and no backing struct" " block_device\n"); goto fail; } - limits = &dev_limits.limits; - limits->logical_block_size = FD_BLOCKSIZE; - limits->max_hw_sectors = FD_MAX_SECTORS; - limits->max_sectors = FD_MAX_SECTORS; fd_dev->fd_block_size = FD_BLOCKSIZE; + /* + * Limit UNMAP emulation to 8k Number of LBAs (NoLB) + */ + dev->dev_attrib.max_unmap_lba_count = 0x2000; + /* + * Currently hardcoded to 1 in Linux/SCSI code.. + */ + dev->dev_attrib.max_unmap_block_desc_count = 1; + dev->dev_attrib.unmap_granularity = 1; + dev->dev_attrib.unmap_granularity_alignment = 0; + + /* + * Limit WRITE_SAME w/ UNMAP=0 emulation to 8k Number of LBAs (NoLB) + * based upon struct iovec limit for vfs_writev() + */ + dev->dev_attrib.max_write_same_len = 0x1000; } - dev_limits.hw_queue_depth = FD_MAX_DEVICE_QUEUE_DEPTH; - dev_limits.queue_depth = FD_DEVICE_QUEUE_DEPTH; + dev->dev_attrib.hw_block_size = fd_dev->fd_block_size; + dev->dev_attrib.max_bytes_per_io = FD_MAX_BYTES; + dev->dev_attrib.hw_max_sectors = FD_MAX_BYTES / fd_dev->fd_block_size; + dev->dev_attrib.hw_queue_depth = FD_MAX_DEVICE_QUEUE_DEPTH; - dev = transport_add_device_to_core_hba(hba, &fileio_template, - se_dev, dev_flags, (void *)fd_dev, - &dev_limits, "FILEIO", FD_VERSION); - if (!(dev)) - goto fail; + if (fd_dev->fbd_flags & FDBD_HAS_BUFFERED_IO_WCE) { + pr_debug("FILEIO: Forcing setting of emulate_write_cache=1" + " with FDBD_HAS_BUFFERED_IO_WCE\n"); + dev->dev_attrib.emulate_write_cache = 1; + } fd_dev->fd_dev_id = fd_host->fd_host_dev_id_count++; fd_dev->fd_queue_depth = dev->queue_depth; - printk(KERN_INFO "CORE_FILE[%u] - Added TCM FILEIO Device ID: %u at %s," + pr_debug("CORE_FILE[%u] - Added TCM FILEIO Device ID: %u at %s," " %llu total bytes\n", fd_host->fd_host_id, fd_dev->fd_dev_id, fd_dev->fd_dev_name, fd_dev->fd_dev_size); - putname(dev_p); - return dev; + return 0; fail: if (fd_dev->fd_file) { filp_close(fd_dev->fd_file, NULL); fd_dev->fd_file = NULL; } - putname(dev_p); - return NULL; + return ret; } -/* fd_free_device(): (Part of se_subsystem_api_t template) - * - * - */ -static void fd_free_device(void *p) +static void fd_free_device(struct se_device *dev) { - struct fd_dev *fd_dev = (struct fd_dev *) p; + struct fd_dev *fd_dev = FD_DEV(dev); if (fd_dev->fd_file) { filp_close(fd_dev->fd_file, NULL); @@ -261,120 +257,145 @@ static void fd_free_device(void *p) kfree(fd_dev); } -static inline struct fd_request *FILE_REQ(struct se_task *task) -{ - return container_of(task, struct fd_request, fd_task); -} - - -static struct se_task * -fd_alloc_task(struct se_cmd *cmd) +static int fd_do_prot_rw(struct se_cmd *cmd, struct fd_prot *fd_prot, + int is_write) { - struct fd_request *fd_req; - - fd_req = kzalloc(sizeof(struct fd_request), GFP_KERNEL); - if (!(fd_req)) { - printk(KERN_ERR "Unable to allocate struct fd_request\n"); - return NULL; - } - - fd_req->fd_dev = SE_DEV(cmd)->dev_ptr; - - return &fd_req->fd_task; -} - -static int fd_do_readv(struct se_task *task) -{ - struct fd_request *req = FILE_REQ(task); - struct file *fd = req->fd_dev->fd_file; - struct scatterlist *sg = task->task_sg; - struct iovec *iov; - mm_segment_t old_fs; - loff_t pos = (task->task_lba * DEV_ATTRIB(task->se_dev)->block_size); - int ret = 0, i; + struct se_device *se_dev = cmd->se_dev; + struct fd_dev *dev = FD_DEV(se_dev); + struct file *prot_fd = dev->fd_prot_file; + struct scatterlist *sg; + loff_t pos = (cmd->t_task_lba * se_dev->prot_length); + unsigned char *buf; + u32 prot_size, len, size; + int rc, ret = 1, i; + + prot_size = (cmd->data_length / se_dev->dev_attrib.block_size) * + se_dev->prot_length; + + if (!is_write) { + fd_prot->prot_buf = vzalloc(prot_size); + if (!fd_prot->prot_buf) { + pr_err("Unable to allocate fd_prot->prot_buf\n"); + return -ENOMEM; + } + buf = fd_prot->prot_buf; + + fd_prot->prot_sg_nents = cmd->t_prot_nents; + fd_prot->prot_sg = kzalloc(sizeof(struct scatterlist) * + fd_prot->prot_sg_nents, GFP_KERNEL); + if (!fd_prot->prot_sg) { + pr_err("Unable to allocate fd_prot->prot_sg\n"); + vfree(fd_prot->prot_buf); + return -ENOMEM; + } + size = prot_size; - iov = kzalloc(sizeof(struct iovec) * task->task_sg_num, GFP_KERNEL); - if (!(iov)) { - printk(KERN_ERR "Unable to allocate fd_do_readv iov[]\n"); - return -1; - } + for_each_sg(fd_prot->prot_sg, sg, fd_prot->prot_sg_nents, i) { - for (i = 0; i < task->task_sg_num; i++) { - iov[i].iov_len = sg[i].length; - iov[i].iov_base = sg_virt(&sg[i]); + len = min_t(u32, PAGE_SIZE, size); + sg_set_buf(sg, buf, len); + size -= len; + buf += len; + } } - old_fs = get_fs(); - set_fs(get_ds()); - ret = vfs_readv(fd, &iov[0], task->task_sg_num, &pos); - set_fs(old_fs); - - kfree(iov); - /* - * Return zeros and GOOD status even if the READ did not return - * the expected virt_size for struct file w/o a backing struct - * block_device. - */ - if (S_ISBLK(fd->f_dentry->d_inode->i_mode)) { - if (ret < 0 || ret != task->task_size) { - printk(KERN_ERR "vfs_readv() returned %d," - " expecting %d for S_ISBLK\n", ret, - (int)task->task_size); - return -1; + if (is_write) { + rc = kernel_write(prot_fd, fd_prot->prot_buf, prot_size, pos); + if (rc < 0 || prot_size != rc) { + pr_err("kernel_write() for fd_do_prot_rw failed:" + " %d\n", rc); + ret = -EINVAL; } } else { - if (ret < 0) { - printk(KERN_ERR "vfs_readv() returned %d for non" - " S_ISBLK\n", ret); - return -1; + rc = kernel_read(prot_fd, pos, fd_prot->prot_buf, prot_size); + if (rc < 0) { + pr_err("kernel_read() for fd_do_prot_rw failed:" + " %d\n", rc); + ret = -EINVAL; } } - return 1; + if (is_write || ret < 0) { + kfree(fd_prot->prot_sg); + vfree(fd_prot->prot_buf); + } + + return ret; } -static int fd_do_writev(struct se_task *task) +static int fd_do_rw(struct se_cmd *cmd, struct scatterlist *sgl, + u32 sgl_nents, int is_write) { - struct fd_request *req = FILE_REQ(task); - struct file *fd = req->fd_dev->fd_file; - struct scatterlist *sg = task->task_sg; + struct se_device *se_dev = cmd->se_dev; + struct fd_dev *dev = FD_DEV(se_dev); + struct file *fd = dev->fd_file; + struct scatterlist *sg; struct iovec *iov; mm_segment_t old_fs; - loff_t pos = (task->task_lba * DEV_ATTRIB(task->se_dev)->block_size); - int ret, i = 0; + loff_t pos = (cmd->t_task_lba * se_dev->dev_attrib.block_size); + int ret = 0, i; - iov = kzalloc(sizeof(struct iovec) * task->task_sg_num, GFP_KERNEL); - if (!(iov)) { - printk(KERN_ERR "Unable to allocate fd_do_writev iov[]\n"); - return -1; + iov = kzalloc(sizeof(struct iovec) * sgl_nents, GFP_KERNEL); + if (!iov) { + pr_err("Unable to allocate fd_do_readv iov[]\n"); + return -ENOMEM; } - for (i = 0; i < task->task_sg_num; i++) { - iov[i].iov_len = sg[i].length; - iov[i].iov_base = sg_virt(&sg[i]); + for_each_sg(sgl, sg, sgl_nents, i) { + iov[i].iov_len = sg->length; + iov[i].iov_base = kmap(sg_page(sg)) + sg->offset; } old_fs = get_fs(); set_fs(get_ds()); - ret = vfs_writev(fd, &iov[0], task->task_sg_num, &pos); + + if (is_write) + ret = vfs_writev(fd, &iov[0], sgl_nents, &pos); + else + ret = vfs_readv(fd, &iov[0], sgl_nents, &pos); + set_fs(old_fs); + for_each_sg(sgl, sg, sgl_nents, i) + kunmap(sg_page(sg)); + kfree(iov); - if (ret < 0 || ret != task->task_size) { - printk(KERN_ERR "vfs_writev() returned %d\n", ret); - return -1; + if (is_write) { + if (ret < 0 || ret != cmd->data_length) { + pr_err("%s() write returned %d\n", __func__, ret); + return (ret < 0 ? ret : -EINVAL); + } + } else { + /* + * Return zeros and GOOD status even if the READ did not return + * the expected virt_size for struct file w/o a backing struct + * block_device. + */ + if (S_ISBLK(file_inode(fd)->i_mode)) { + if (ret < 0 || ret != cmd->data_length) { + pr_err("%s() returned %d, expecting %u for " + "S_ISBLK\n", __func__, ret, + cmd->data_length); + return (ret < 0 ? ret : -EINVAL); + } + } else { + if (ret < 0) { + pr_err("%s() returned %d for non S_ISBLK\n", + __func__, ret); + return ret; + } + } } - return 1; } -static void fd_emulate_sync_cache(struct se_task *task) +static sense_reason_t +fd_execute_sync_cache(struct se_cmd *cmd) { - struct se_cmd *cmd = TASK_CMD(task); struct se_device *dev = cmd->se_dev; - struct fd_dev *fd_dev = dev->dev_ptr; - int immed = (cmd->t_task->t_task_cdb[1] & 0x2); + struct fd_dev *fd_dev = FD_DEV(dev); + int immed = (cmd->t_task_cdb[1] & 0x2); loff_t start, end; int ret; @@ -383,16 +404,16 @@ static void fd_emulate_sync_cache(struct se_task *task) * for this SYNCHRONIZE_CACHE op */ if (immed) - transport_complete_sync_cache(cmd, 1); + target_complete_cmd(cmd, SAM_STAT_GOOD); /* * Determine if we will be flushing the entire device. */ - if (cmd->t_task->t_task_lba == 0 && cmd->data_length == 0) { + if (cmd->t_task_lba == 0 && cmd->data_length == 0) { start = 0; end = LLONG_MAX; } else { - start = cmd->t_task->t_task_lba * DEV_ATTRIB(dev)->block_size; + start = cmd->t_task_lba * dev->dev_attrib.block_size; if (cmd->data_length) end = start + cmd->data_length; else @@ -401,106 +422,285 @@ static void fd_emulate_sync_cache(struct se_task *task) ret = vfs_fsync_range(fd_dev->fd_file, start, end, 1); if (ret != 0) - printk(KERN_ERR "FILEIO: vfs_fsync_range() failed: %d\n", ret); + pr_err("FILEIO: vfs_fsync_range() failed: %d\n", ret); + + if (immed) + return 0; + + if (ret) + target_complete_cmd(cmd, SAM_STAT_CHECK_CONDITION); + else + target_complete_cmd(cmd, SAM_STAT_GOOD); - if (!immed) - transport_complete_sync_cache(cmd, ret == 0); + return 0; } -/* - * Tell TCM Core that we are capable of WriteCache emulation for - * an underlying struct se_device. - */ -static int fd_emulated_write_cache(struct se_device *dev) +static unsigned char * +fd_setup_write_same_buf(struct se_cmd *cmd, struct scatterlist *sg, + unsigned int len) { - return 1; + struct se_device *se_dev = cmd->se_dev; + unsigned int block_size = se_dev->dev_attrib.block_size; + unsigned int i = 0, end; + unsigned char *buf, *p, *kmap_buf; + + buf = kzalloc(min_t(unsigned int, len, PAGE_SIZE), GFP_KERNEL); + if (!buf) { + pr_err("Unable to allocate fd_execute_write_same buf\n"); + return NULL; + } + + kmap_buf = kmap(sg_page(sg)) + sg->offset; + if (!kmap_buf) { + pr_err("kmap() failed in fd_setup_write_same\n"); + kfree(buf); + return NULL; + } + /* + * Fill local *buf to contain multiple WRITE_SAME blocks up to + * min(len, PAGE_SIZE) + */ + p = buf; + end = min_t(unsigned int, len, PAGE_SIZE); + + while (i < end) { + memcpy(p, kmap_buf, block_size); + + i += block_size; + p += block_size; + } + kunmap(sg_page(sg)); + + return buf; } -static int fd_emulated_dpo(struct se_device *dev) +static sense_reason_t +fd_execute_write_same(struct se_cmd *cmd) { + struct se_device *se_dev = cmd->se_dev; + struct fd_dev *fd_dev = FD_DEV(se_dev); + struct file *f = fd_dev->fd_file; + struct scatterlist *sg; + struct iovec *iov; + mm_segment_t old_fs; + sector_t nolb = sbc_get_write_same_sectors(cmd); + loff_t pos = cmd->t_task_lba * se_dev->dev_attrib.block_size; + unsigned int len, len_tmp, iov_num; + int i, rc; + unsigned char *buf; + + if (!nolb) { + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; + } + sg = &cmd->t_data_sg[0]; + + if (cmd->t_data_nents > 1 || + sg->length != cmd->se_dev->dev_attrib.block_size) { + pr_err("WRITE_SAME: Illegal SGL t_data_nents: %u length: %u" + " block_size: %u\n", cmd->t_data_nents, sg->length, + cmd->se_dev->dev_attrib.block_size); + return TCM_INVALID_CDB_FIELD; + } + + len = len_tmp = nolb * se_dev->dev_attrib.block_size; + iov_num = DIV_ROUND_UP(len, PAGE_SIZE); + + buf = fd_setup_write_same_buf(cmd, sg, len); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + iov = vzalloc(sizeof(struct iovec) * iov_num); + if (!iov) { + pr_err("Unable to allocate fd_execute_write_same iovecs\n"); + kfree(buf); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + /* + * Map the single fabric received scatterlist block now populated + * in *buf into each iovec for I/O submission. + */ + for (i = 0; i < iov_num; i++) { + iov[i].iov_base = buf; + iov[i].iov_len = min_t(unsigned int, len_tmp, PAGE_SIZE); + len_tmp -= iov[i].iov_len; + } + + old_fs = get_fs(); + set_fs(get_ds()); + rc = vfs_writev(f, &iov[0], iov_num, &pos); + set_fs(old_fs); + + vfree(iov); + kfree(buf); + + if (rc < 0 || rc != len) { + pr_err("vfs_writev() returned %d for write same\n", rc); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + + target_complete_cmd(cmd, SAM_STAT_GOOD); return 0; } -/* - * Tell TCM Core that we will be emulating Forced Unit Access (FUA) for WRITEs - * for TYPE_DISK. - */ -static int fd_emulated_fua_write(struct se_device *dev) -{ - return 1; -} -static int fd_emulated_fua_read(struct se_device *dev) +static sense_reason_t +fd_do_unmap(struct se_cmd *cmd, void *priv, sector_t lba, sector_t nolb) { + struct file *file = priv; + struct inode *inode = file->f_mapping->host; + int ret; + + if (S_ISBLK(inode->i_mode)) { + /* The backend is block device, use discard */ + struct block_device *bdev = inode->i_bdev; + + ret = blkdev_issue_discard(bdev, lba, + nolb, GFP_KERNEL, 0); + if (ret < 0) { + pr_warn("FILEIO: blkdev_issue_discard() failed: %d\n", + ret); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + } else { + /* The backend is normal file, use fallocate */ + struct se_device *se_dev = cmd->se_dev; + loff_t pos = lba * se_dev->dev_attrib.block_size; + unsigned int len = nolb * se_dev->dev_attrib.block_size; + int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; + + if (!file->f_op->fallocate) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + ret = file->f_op->fallocate(file, mode, pos, len); + if (ret < 0) { + pr_warn("FILEIO: fallocate() failed: %d\n", ret); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + } + return 0; } -/* - * WRITE Force Unit Access (FUA) emulation on a per struct se_task - * LBA range basis.. - */ -static void fd_emulate_write_fua(struct se_cmd *cmd, struct se_task *task) +static sense_reason_t +fd_execute_write_same_unmap(struct se_cmd *cmd) { - struct se_device *dev = cmd->se_dev; - struct fd_dev *fd_dev = dev->dev_ptr; - loff_t start = task->task_lba * DEV_ATTRIB(dev)->block_size; - loff_t end = start + task->task_size; + struct se_device *se_dev = cmd->se_dev; + struct fd_dev *fd_dev = FD_DEV(se_dev); + struct file *file = fd_dev->fd_file; + sector_t lba = cmd->t_task_lba; + sector_t nolb = sbc_get_write_same_sectors(cmd); int ret; - DEBUG_FD_CACHE("FILEIO: FUA WRITE LBA: %llu, bytes: %u\n", - task->task_lba, task->task_size); + if (!nolb) { + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; + } - ret = vfs_fsync_range(fd_dev->fd_file, start, end, 1); - if (ret != 0) - printk(KERN_ERR "FILEIO: vfs_fsync_range() failed: %d\n", ret); + ret = fd_do_unmap(cmd, file, lba, nolb); + if (ret) + return ret; + + target_complete_cmd(cmd, GOOD); + return 0; } -static int fd_do_task(struct se_task *task) +static sense_reason_t +fd_execute_unmap(struct se_cmd *cmd) +{ + struct file *file = FD_DEV(cmd->se_dev)->fd_file; + + return sbc_execute_unmap(cmd, fd_do_unmap, file); +} + +static sense_reason_t +fd_execute_rw(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents, + enum dma_data_direction data_direction) { - struct se_cmd *cmd = task->task_se_cmd; struct se_device *dev = cmd->se_dev; + struct fd_prot fd_prot; + sense_reason_t rc; int ret = 0; /* * Call vectorized fileio functions to map struct scatterlist * physical memory addresses to struct iovec virtual memory. */ - if (task->task_data_direction == DMA_FROM_DEVICE) { - ret = fd_do_readv(task); + if (data_direction == DMA_FROM_DEVICE) { + memset(&fd_prot, 0, sizeof(struct fd_prot)); + + if (cmd->prot_type) { + ret = fd_do_prot_rw(cmd, &fd_prot, false); + if (ret < 0) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + + ret = fd_do_rw(cmd, sgl, sgl_nents, 0); + + if (ret > 0 && cmd->prot_type) { + u32 sectors = cmd->data_length / dev->dev_attrib.block_size; + + rc = sbc_dif_verify_read(cmd, cmd->t_task_lba, sectors, + 0, fd_prot.prot_sg, 0); + if (rc) { + kfree(fd_prot.prot_sg); + vfree(fd_prot.prot_buf); + return rc; + } + kfree(fd_prot.prot_sg); + vfree(fd_prot.prot_buf); + } } else { - ret = fd_do_writev(task); + memset(&fd_prot, 0, sizeof(struct fd_prot)); + + if (cmd->prot_type) { + u32 sectors = cmd->data_length / dev->dev_attrib.block_size; + + ret = fd_do_prot_rw(cmd, &fd_prot, false); + if (ret < 0) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + rc = sbc_dif_verify_write(cmd, cmd->t_task_lba, sectors, + 0, fd_prot.prot_sg, 0); + if (rc) { + kfree(fd_prot.prot_sg); + vfree(fd_prot.prot_buf); + return rc; + } + } + ret = fd_do_rw(cmd, sgl, sgl_nents, 1); + /* + * Perform implicit vfs_fsync_range() for fd_do_writev() ops + * for SCSI WRITEs with Forced Unit Access (FUA) set. + * Allow this to happen independent of WCE=0 setting. + */ if (ret > 0 && - DEV_ATTRIB(dev)->emulate_write_cache > 0 && - DEV_ATTRIB(dev)->emulate_fua_write > 0 && - T_TASK(cmd)->t_tasks_fua) { - /* - * We might need to be a bit smarter here - * and return some sense data to let the initiator - * know the FUA WRITE cache sync failed..? - */ - fd_emulate_write_fua(cmd, task); + dev->dev_attrib.emulate_fua_write > 0 && + (cmd->se_cmd_flags & SCF_FUA)) { + struct fd_dev *fd_dev = FD_DEV(dev); + loff_t start = cmd->t_task_lba * + dev->dev_attrib.block_size; + loff_t end = start + cmd->data_length; + + vfs_fsync_range(fd_dev->fd_file, start, end, 1); } + if (ret > 0 && cmd->prot_type) { + ret = fd_do_prot_rw(cmd, &fd_prot, true); + if (ret < 0) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } } - if (ret < 0) - return ret; - if (ret) { - task->task_scsi_status = GOOD; - transport_complete_task(task, 1); + if (ret < 0) { + kfree(fd_prot.prot_sg); + vfree(fd_prot.prot_buf); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } - return PYX_TRANSPORT_SENT_TO_TRANSPORT; -} - -/* fd_free_task(): (Part of se_subsystem_api_t template) - * - * - */ -static void fd_free_task(struct se_task *task) -{ - struct fd_request *req = FILE_REQ(task); - kfree(req); + if (ret) + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; } enum { @@ -510,16 +710,14 @@ enum { static match_table_t tokens = { {Opt_fd_dev_name, "fd_dev_name=%s"}, {Opt_fd_dev_size, "fd_dev_size=%s"}, - {Opt_fd_buffered_io, "fd_buffered_id=%d"}, + {Opt_fd_buffered_io, "fd_buffered_io=%d"}, {Opt_err, NULL} }; -static ssize_t fd_set_configfs_dev_params( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - const char *page, ssize_t count) +static ssize_t fd_set_configfs_dev_params(struct se_device *dev, + const char *page, ssize_t count) { - struct fd_dev *fd_dev = se_dev->se_dev_su_ptr; + struct fd_dev *fd_dev = FD_DEV(dev); char *orig, *ptr, *arg_p, *opts; substring_t args[MAX_OPT_ARGS]; int ret = 0, arg, token; @@ -530,43 +728,51 @@ static ssize_t fd_set_configfs_dev_params( orig = opts; - while ((ptr = strsep(&opts, ",")) != NULL) { + while ((ptr = strsep(&opts, ",\n")) != NULL) { if (!*ptr) continue; token = match_token(ptr, tokens, args); switch (token) { case Opt_fd_dev_name: - snprintf(fd_dev->fd_dev_name, FD_MAX_DEV_NAME, - "%s", match_strdup(&args[0])); - printk(KERN_INFO "FILEIO: Referencing Path: %s\n", + if (match_strlcpy(fd_dev->fd_dev_name, &args[0], + FD_MAX_DEV_NAME) == 0) { + ret = -EINVAL; + break; + } + pr_debug("FILEIO: Referencing Path: %s\n", fd_dev->fd_dev_name); fd_dev->fbd_flags |= FBDF_HAS_PATH; break; case Opt_fd_dev_size: arg_p = match_strdup(&args[0]); - ret = strict_strtoull(arg_p, 0, &fd_dev->fd_dev_size); + if (!arg_p) { + ret = -ENOMEM; + break; + } + ret = kstrtoull(arg_p, 0, &fd_dev->fd_dev_size); + kfree(arg_p); if (ret < 0) { - printk(KERN_ERR "strict_strtoull() failed for" + pr_err("kstrtoull() failed for" " fd_dev_size=\n"); goto out; } - printk(KERN_INFO "FILEIO: Referencing Size: %llu" + pr_debug("FILEIO: Referencing Size: %llu" " bytes\n", fd_dev->fd_dev_size); fd_dev->fbd_flags |= FBDF_HAS_SIZE; break; case Opt_fd_buffered_io: match_int(args, &arg); if (arg != 1) { - printk(KERN_ERR "bogus fd_buffered_io=%d value\n", arg); + pr_err("bogus fd_buffered_io=%d value\n", arg); ret = -EINVAL; goto out; } - printk(KERN_INFO "FILEIO: Using buffered I/O" + pr_debug("FILEIO: Using buffered I/O" " operations for struct fd_dev\n"); - fd_dev->fbd_flags |= FDBD_USE_BUFFERED_IO; + fd_dev->fbd_flags |= FDBD_HAS_BUFFERED_IO_WCE; break; default: break; @@ -578,96 +784,168 @@ out: return (!ret) ? count : ret; } -static ssize_t fd_check_configfs_dev_params(struct se_hba *hba, struct se_subsystem_dev *se_dev) -{ - struct fd_dev *fd_dev = (struct fd_dev *) se_dev->se_dev_su_ptr; - - if (!(fd_dev->fbd_flags & FBDF_HAS_PATH)) { - printk(KERN_ERR "Missing fd_dev_name=\n"); - return -1; - } - - return 0; -} - -static ssize_t fd_show_configfs_dev_params( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - char *b) +static ssize_t fd_show_configfs_dev_params(struct se_device *dev, char *b) { - struct fd_dev *fd_dev = se_dev->se_dev_su_ptr; + struct fd_dev *fd_dev = FD_DEV(dev); ssize_t bl = 0; bl = sprintf(b + bl, "TCM FILEIO ID: %u", fd_dev->fd_dev_id); bl += sprintf(b + bl, " File: %s Size: %llu Mode: %s\n", fd_dev->fd_dev_name, fd_dev->fd_dev_size, - (fd_dev->fbd_flags & FDBD_USE_BUFFERED_IO) ? - "Buffered" : "Synchronous"); + (fd_dev->fbd_flags & FDBD_HAS_BUFFERED_IO_WCE) ? + "Buffered-WCE" : "O_DSYNC"); return bl; } -/* fd_get_cdb(): (Part of se_subsystem_api_t template) - * - * - */ -static unsigned char *fd_get_cdb(struct se_task *task) +static sector_t fd_get_blocks(struct se_device *dev) { - struct fd_request *req = FILE_REQ(task); + struct fd_dev *fd_dev = FD_DEV(dev); + struct file *f = fd_dev->fd_file; + struct inode *i = f->f_mapping->host; + unsigned long long dev_size; + /* + * When using a file that references an underlying struct block_device, + * ensure dev_size is always based on the current inode size in order + * to handle underlying block_device resize operations. + */ + if (S_ISBLK(i->i_mode)) + dev_size = i_size_read(i); + else + dev_size = fd_dev->fd_dev_size; - return req->fd_scsi_cdb; + return div_u64(dev_size - dev->dev_attrib.block_size, + dev->dev_attrib.block_size); } -/* fd_get_device_rev(): (Part of se_subsystem_api_t template) - * - * - */ -static u32 fd_get_device_rev(struct se_device *dev) +static int fd_init_prot(struct se_device *dev) { - return SCSI_SPC_2; /* Returns SPC-3 in Initiator Data */ + struct fd_dev *fd_dev = FD_DEV(dev); + struct file *prot_file, *file = fd_dev->fd_file; + struct inode *inode; + int ret, flags = O_RDWR | O_CREAT | O_LARGEFILE | O_DSYNC; + char buf[FD_MAX_DEV_PROT_NAME]; + + if (!file) { + pr_err("Unable to locate fd_dev->fd_file\n"); + return -ENODEV; + } + + inode = file->f_mapping->host; + if (S_ISBLK(inode->i_mode)) { + pr_err("FILEIO Protection emulation only supported on" + " !S_ISBLK\n"); + return -ENOSYS; + } + + if (fd_dev->fbd_flags & FDBD_HAS_BUFFERED_IO_WCE) + flags &= ~O_DSYNC; + + snprintf(buf, FD_MAX_DEV_PROT_NAME, "%s.protection", + fd_dev->fd_dev_name); + + prot_file = filp_open(buf, flags, 0600); + if (IS_ERR(prot_file)) { + pr_err("filp_open(%s) failed\n", buf); + ret = PTR_ERR(prot_file); + return ret; + } + fd_dev->fd_prot_file = prot_file; + + return 0; } -/* fd_get_device_type(): (Part of se_subsystem_api_t template) - * - * - */ -static u32 fd_get_device_type(struct se_device *dev) +static int fd_format_prot(struct se_device *dev) { - return TYPE_DISK; + struct fd_dev *fd_dev = FD_DEV(dev); + struct file *prot_fd = fd_dev->fd_prot_file; + sector_t prot_length, prot; + unsigned char *buf; + loff_t pos = 0; + int unit_size = FDBD_FORMAT_UNIT_SIZE * dev->dev_attrib.block_size; + int rc, ret = 0, size, len; + + if (!dev->dev_attrib.pi_prot_type) { + pr_err("Unable to format_prot while pi_prot_type == 0\n"); + return -ENODEV; + } + if (!prot_fd) { + pr_err("Unable to locate fd_dev->fd_prot_file\n"); + return -ENODEV; + } + + buf = vzalloc(unit_size); + if (!buf) { + pr_err("Unable to allocate FILEIO prot buf\n"); + return -ENOMEM; + } + prot_length = (dev->transport->get_blocks(dev) + 1) * dev->prot_length; + size = prot_length; + + pr_debug("Using FILEIO prot_length: %llu\n", + (unsigned long long)prot_length); + + memset(buf, 0xff, unit_size); + for (prot = 0; prot < prot_length; prot += unit_size) { + len = min(unit_size, size); + rc = kernel_write(prot_fd, buf, len, pos); + if (rc != len) { + pr_err("vfs_write to prot file failed: %d\n", rc); + ret = -ENODEV; + goto out; + } + pos += len; + size -= len; + } + +out: + vfree(buf); + return ret; } -static sector_t fd_get_blocks(struct se_device *dev) +static void fd_free_prot(struct se_device *dev) { - struct fd_dev *fd_dev = dev->dev_ptr; - unsigned long long blocks_long = div_u64(fd_dev->fd_dev_size, - DEV_ATTRIB(dev)->block_size); + struct fd_dev *fd_dev = FD_DEV(dev); + + if (!fd_dev->fd_prot_file) + return; + + filp_close(fd_dev->fd_prot_file, NULL); + fd_dev->fd_prot_file = NULL; +} - return blocks_long; +static struct sbc_ops fd_sbc_ops = { + .execute_rw = fd_execute_rw, + .execute_sync_cache = fd_execute_sync_cache, + .execute_write_same = fd_execute_write_same, + .execute_write_same_unmap = fd_execute_write_same_unmap, + .execute_unmap = fd_execute_unmap, +}; + +static sense_reason_t +fd_parse_cdb(struct se_cmd *cmd) +{ + return sbc_parse_cdb(cmd, &fd_sbc_ops); } static struct se_subsystem_api fileio_template = { .name = "fileio", + .inquiry_prod = "FILEIO", + .inquiry_rev = FD_VERSION, .owner = THIS_MODULE, .transport_type = TRANSPORT_PLUGIN_VHBA_PDEV, .attach_hba = fd_attach_hba, .detach_hba = fd_detach_hba, - .allocate_virtdevice = fd_allocate_virtdevice, - .create_virtdevice = fd_create_virtdevice, + .alloc_device = fd_alloc_device, + .configure_device = fd_configure_device, .free_device = fd_free_device, - .dpo_emulated = fd_emulated_dpo, - .fua_write_emulated = fd_emulated_fua_write, - .fua_read_emulated = fd_emulated_fua_read, - .write_cache_emulated = fd_emulated_write_cache, - .alloc_task = fd_alloc_task, - .do_task = fd_do_task, - .do_sync_cache = fd_emulate_sync_cache, - .free_task = fd_free_task, - .check_configfs_dev_params = fd_check_configfs_dev_params, + .parse_cdb = fd_parse_cdb, .set_configfs_dev_params = fd_set_configfs_dev_params, .show_configfs_dev_params = fd_show_configfs_dev_params, - .get_cdb = fd_get_cdb, - .get_device_rev = fd_get_device_rev, - .get_device_type = fd_get_device_type, + .get_device_type = sbc_get_device_type, .get_blocks = fd_get_blocks, + .init_prot = fd_init_prot, + .format_prot = fd_format_prot, + .free_prot = fd_free_prot, }; static int __init fileio_module_init(void) @@ -675,7 +953,7 @@ static int __init fileio_module_init(void) return transport_subsystem_register(&fileio_template); } -static void fileio_module_exit(void) +static void __exit fileio_module_exit(void) { transport_subsystem_release(&fileio_template); } diff --git a/drivers/target/target_core_file.h b/drivers/target/target_core_file.h index ef4de2b4bd4..182cbb29503 100644 --- a/drivers/target/target_core_file.h +++ b/drivers/target/target_core_file.h @@ -4,29 +4,32 @@ #define FD_VERSION "4.0" #define FD_MAX_DEV_NAME 256 -/* Maximum queuedepth for the FILEIO HBA */ -#define FD_HBA_QUEUE_DEPTH 256 +#define FD_MAX_DEV_PROT_NAME FD_MAX_DEV_NAME + 16 #define FD_DEVICE_QUEUE_DEPTH 32 #define FD_MAX_DEVICE_QUEUE_DEPTH 128 #define FD_BLOCKSIZE 512 -#define FD_MAX_SECTORS 1024 +/* + * Limited by the number of iovecs (2048) per vfs_[writev,readv] call + */ +#define FD_MAX_BYTES 8388608 #define RRF_EMULATE_CDB 0x01 #define RRF_GOT_LBA 0x02 -struct fd_request { - struct se_task fd_task; - /* SCSI CDB from iSCSI Command PDU */ - unsigned char fd_scsi_cdb[TCM_MAX_COMMAND_SIZE]; - /* FILEIO device */ - struct fd_dev *fd_dev; -} ____cacheline_aligned; - #define FBDF_HAS_PATH 0x01 #define FBDF_HAS_SIZE 0x02 -#define FDBD_USE_BUFFERED_IO 0x04 +#define FDBD_HAS_BUFFERED_IO_WCE 0x04 +#define FDBD_FORMAT_UNIT_SIZE 2048 + +struct fd_prot { + unsigned char *prot_buf; + struct scatterlist *prot_sg; + u32 prot_sg_nents; +}; struct fd_dev { + struct se_device dev; + u32 fbd_flags; unsigned char fd_dev_name[FD_MAX_DEV_NAME]; /* Unique Ramdisk Device ID in Ramdisk HBA */ @@ -37,6 +40,7 @@ struct fd_dev { u32 fd_block_size; unsigned long long fd_dev_size; struct file *fd_file; + struct file *fd_prot_file; /* FILEIO HBA device is connected to */ struct fd_host *fd_host; } ____cacheline_aligned; diff --git a/drivers/target/target_core_hba.c b/drivers/target/target_core_hba.c index 4bbe8208b24..a25051a37dd 100644 --- a/drivers/target/target_core_hba.c +++ b/drivers/target/target_core_hba.c @@ -1,12 +1,9 @@ /******************************************************************************* * Filename: target_core_hba.c * - * This file copntains the iSCSI HBA Transport related functions. + * This file contains the TCM HBA Transport related functions. * - * Copyright (c) 2003, 2004, 2005 PyX Technologies, Inc. - * Copyright (c) 2005, 2006, 2007 SBE, Inc. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2003-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -31,22 +28,25 @@ #include <linux/timer.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> #include <linux/in.h> +#include <linux/module.h> #include <net/sock.h> #include <net/tcp.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_device.h> -#include <target/target_core_tpg.h> -#include <target/target_core_transport.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> -#include "target_core_hba.h" +#include "target_core_internal.h" static LIST_HEAD(subsystem_list); static DEFINE_MUTEX(subsystem_mutex); +static u32 hba_id_counter; + +static DEFINE_SPINLOCK(hba_lock); +static LIST_HEAD(hba_list); + int transport_subsystem_register(struct se_subsystem_api *sub_api) { struct se_subsystem_api *s; @@ -55,8 +55,8 @@ int transport_subsystem_register(struct se_subsystem_api *sub_api) mutex_lock(&subsystem_mutex); list_for_each_entry(s, &subsystem_list, sub_api_list) { - if (!(strcmp(s->name, sub_api->name))) { - printk(KERN_ERR "%p is already registered with" + if (!strcmp(s->name, sub_api->name)) { + pr_err("%p is already registered with" " duplicate name %s, unable to process" " request\n", s, s->name); mutex_unlock(&subsystem_mutex); @@ -66,7 +66,7 @@ int transport_subsystem_register(struct se_subsystem_api *sub_api) list_add_tail(&sub_api->sub_api_list, &subsystem_list); mutex_unlock(&subsystem_mutex); - printk(KERN_INFO "TCM: Registered subsystem plugin: %s struct module:" + pr_debug("TCM: Registered subsystem plugin: %s struct module:" " %p\n", sub_api->name, sub_api->owner); return 0; } @@ -106,21 +106,16 @@ core_alloc_hba(const char *plugin_name, u32 plugin_dep_id, u32 hba_flags) hba = kzalloc(sizeof(*hba), GFP_KERNEL); if (!hba) { - printk(KERN_ERR "Unable to allocate struct se_hba\n"); + pr_err("Unable to allocate struct se_hba\n"); return ERR_PTR(-ENOMEM); } - INIT_LIST_HEAD(&hba->hba_dev_list); spin_lock_init(&hba->device_lock); - spin_lock_init(&hba->hba_queue_lock); mutex_init(&hba->hba_access_mutex); hba->hba_index = scsi_get_new_index(SCSI_INST_INDEX); hba->hba_flags |= hba_flags; - atomic_set(&hba->max_queue_depth, 0); - atomic_set(&hba->left_queue_depth, 0); - hba->transport = core_get_backend(plugin_name); if (!hba->transport) { ret = -EINVAL; @@ -131,12 +126,12 @@ core_alloc_hba(const char *plugin_name, u32 plugin_dep_id, u32 hba_flags) if (ret < 0) goto out_module_put; - spin_lock(&se_global->hba_lock); - hba->hba_id = se_global->g_hba_id_counter++; - list_add_tail(&hba->hba_list, &se_global->g_hba_list); - spin_unlock(&se_global->hba_lock); + spin_lock(&hba_lock); + hba->hba_id = hba_id_counter++; + list_add_tail(&hba->hba_node, &hba_list); + spin_unlock(&hba_lock); - printk(KERN_INFO "CORE_HBA[%d] - Attached HBA to Generic Target" + pr_debug("CORE_HBA[%d] - Attached HBA to Generic Target" " Core\n", hba->hba_id); return hba; @@ -153,27 +148,15 @@ out_free_hba: int core_delete_hba(struct se_hba *hba) { - struct se_device *dev, *dev_tmp; - - spin_lock(&hba->device_lock); - list_for_each_entry_safe(dev, dev_tmp, &hba->hba_dev_list, dev_list) { - - se_clear_dev_ports(dev); - spin_unlock(&hba->device_lock); - - se_release_device_for_hba(dev); - - spin_lock(&hba->device_lock); - } - spin_unlock(&hba->device_lock); + WARN_ON(hba->dev_count); hba->transport->detach_hba(hba); - spin_lock(&se_global->hba_lock); - list_del(&hba->hba_list); - spin_unlock(&se_global->hba_lock); + spin_lock(&hba_lock); + list_del(&hba->hba_node); + spin_unlock(&hba_lock); - printk(KERN_INFO "CORE_HBA[%d] - Detached HBA from Generic Target" + pr_debug("CORE_HBA[%d] - Detached HBA from Generic Target" " Core\n", hba->hba_id); if (hba->transport->owner) diff --git a/drivers/target/target_core_hba.h b/drivers/target/target_core_hba.h deleted file mode 100644 index bb0fea5f730..00000000000 --- a/drivers/target/target_core_hba.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef TARGET_CORE_HBA_H -#define TARGET_CORE_HBA_H - -extern struct se_hba *core_alloc_hba(const char *, u32, u32); -extern int core_delete_hba(struct se_hba *); - -#endif /* TARGET_CORE_HBA_H */ diff --git a/drivers/target/target_core_iblock.c b/drivers/target/target_core_iblock.c index 67f0c09983c..7e6b857c6b3 100644 --- a/drivers/target/target_core_iblock.c +++ b/drivers/target/target_core_iblock.c @@ -4,10 +4,7 @@ * This file contains the Storage Engine <-> Linux BlockIO transport * specific functions. * - * Copyright (c) 2003, 2004, 2005 PyX Technologies, Inc. - * Copyright (c) 2005, 2006, 2007 SBE, Inc. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2003-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -27,7 +24,6 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/string.h> #include <linux/parser.h> #include <linux/timer.h> @@ -35,28 +31,29 @@ #include <linux/blkdev.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> #include <linux/bio.h> #include <linux/genhd.h> #include <linux/file.h> +#include <linux/module.h> #include <scsi/scsi.h> #include <scsi/scsi_host.h> +#include <asm/unaligned.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_transport.h> +#include <target/target_core_backend.h> #include "target_core_iblock.h" -#if 0 -#define DEBUG_IBLOCK(x...) printk(x) -#else -#define DEBUG_IBLOCK(x...) -#endif +#define IBLOCK_MAX_BIO_PER_TASK 32 /* max # of bios to submit at a time */ +#define IBLOCK_BIO_POOL_SIZE 128 -static struct se_subsystem_api iblock_template; +static inline struct iblock_dev *IBLOCK_DEV(struct se_device *dev) +{ + return container_of(dev, struct iblock_dev, dev); +} -static void iblock_bio_done(struct bio *, int); + +static struct se_subsystem_api iblock_template; /* iblock_attach_hba(): (Part of se_subsystem_api_t template) * @@ -64,188 +61,152 @@ static void iblock_bio_done(struct bio *, int); */ static int iblock_attach_hba(struct se_hba *hba, u32 host_id) { - struct iblock_hba *ib_host; - - ib_host = kzalloc(sizeof(struct iblock_hba), GFP_KERNEL); - if (!(ib_host)) { - printk(KERN_ERR "Unable to allocate memory for" - " struct iblock_hba\n"); - return -ENOMEM; - } - - ib_host->iblock_host_id = host_id; - - atomic_set(&hba->left_queue_depth, IBLOCK_HBA_QUEUE_DEPTH); - atomic_set(&hba->max_queue_depth, IBLOCK_HBA_QUEUE_DEPTH); - hba->hba_ptr = (void *) ib_host; - - printk(KERN_INFO "CORE_HBA[%d] - TCM iBlock HBA Driver %s on" + pr_debug("CORE_HBA[%d] - TCM iBlock HBA Driver %s on" " Generic Target Core Stack %s\n", hba->hba_id, IBLOCK_VERSION, TARGET_CORE_MOD_VERSION); - - printk(KERN_INFO "CORE_HBA[%d] - Attached iBlock HBA: %u to Generic" - " Target Core TCQ Depth: %d\n", hba->hba_id, - ib_host->iblock_host_id, atomic_read(&hba->max_queue_depth)); - return 0; } static void iblock_detach_hba(struct se_hba *hba) { - struct iblock_hba *ib_host = hba->hba_ptr; - - printk(KERN_INFO "CORE_HBA[%d] - Detached iBlock HBA: %u from Generic" - " Target Core\n", hba->hba_id, ib_host->iblock_host_id); - - kfree(ib_host); - hba->hba_ptr = NULL; } -static void *iblock_allocate_virtdevice(struct se_hba *hba, const char *name) +static struct se_device *iblock_alloc_device(struct se_hba *hba, const char *name) { struct iblock_dev *ib_dev = NULL; - struct iblock_hba *ib_host = hba->hba_ptr; ib_dev = kzalloc(sizeof(struct iblock_dev), GFP_KERNEL); - if (!(ib_dev)) { - printk(KERN_ERR "Unable to allocate struct iblock_dev\n"); + if (!ib_dev) { + pr_err("Unable to allocate struct iblock_dev\n"); return NULL; } - ib_dev->ibd_host = ib_host; - printk(KERN_INFO "IBLOCK: Allocated ib_dev for %s\n", name); + pr_debug( "IBLOCK: Allocated ib_dev for %s\n", name); - return ib_dev; + return &ib_dev->dev; } -static struct se_device *iblock_create_virtdevice( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - void *p) +static int iblock_configure_device(struct se_device *dev) { - struct iblock_dev *ib_dev = p; - struct se_device *dev; - struct se_dev_limits dev_limits; - struct block_device *bd = NULL; + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); struct request_queue *q; - struct queue_limits *limits; - u32 dev_flags = 0; + struct block_device *bd = NULL; + struct blk_integrity *bi; + fmode_t mode; + int ret = -ENOMEM; - if (!(ib_dev)) { - printk(KERN_ERR "Unable to locate struct iblock_dev parameter\n"); - return 0; + if (!(ib_dev->ibd_flags & IBDF_HAS_UDEV_PATH)) { + pr_err("Missing udev_path= parameters for IBLOCK\n"); + return -EINVAL; } - memset(&dev_limits, 0, sizeof(struct se_dev_limits)); - /* - * These settings need to be made tunable.. - */ - ib_dev->ibd_bio_set = bioset_create(32, 64); - if (!(ib_dev->ibd_bio_set)) { - printk(KERN_ERR "IBLOCK: Unable to create bioset()\n"); - return 0; + + ib_dev->ibd_bio_set = bioset_create(IBLOCK_BIO_POOL_SIZE, 0); + if (!ib_dev->ibd_bio_set) { + pr_err("IBLOCK: Unable to create bioset\n"); + goto out; } - printk(KERN_INFO "IBLOCK: Created bio_set()\n"); - /* - * iblock_check_configfs_dev_params() ensures that ib_dev->ibd_udev_path - * must already have been set in order for echo 1 > $HBA/$DEV/enable to run. - */ - printk(KERN_INFO "IBLOCK: Claiming struct block_device: %s\n", + + pr_debug( "IBLOCK: Claiming struct block_device: %s\n", ib_dev->ibd_udev_path); - bd = blkdev_get_by_path(ib_dev->ibd_udev_path, - FMODE_WRITE|FMODE_READ|FMODE_EXCL, ib_dev); - if (IS_ERR(bd)) - goto failed; - /* - * Setup the local scope queue_limits from struct request_queue->limits - * to pass into transport_add_device_to_core_hba() as struct se_dev_limits. - */ - q = bdev_get_queue(bd); - limits = &dev_limits.limits; - limits->logical_block_size = bdev_logical_block_size(bd); - limits->max_hw_sectors = queue_max_hw_sectors(q); - limits->max_sectors = queue_max_sectors(q); - dev_limits.hw_queue_depth = IBLOCK_MAX_DEVICE_QUEUE_DEPTH; - dev_limits.queue_depth = IBLOCK_DEVICE_QUEUE_DEPTH; - - ib_dev->ibd_major = MAJOR(bd->bd_dev); - ib_dev->ibd_minor = MINOR(bd->bd_dev); + mode = FMODE_READ|FMODE_EXCL; + if (!ib_dev->ibd_readonly) + mode |= FMODE_WRITE; + + bd = blkdev_get_by_path(ib_dev->ibd_udev_path, mode, ib_dev); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto out_free_bioset; + } ib_dev->ibd_bd = bd; - dev = transport_add_device_to_core_hba(hba, - &iblock_template, se_dev, dev_flags, (void *)ib_dev, - &dev_limits, "IBLOCK", IBLOCK_VERSION); - if (!(dev)) - goto failed; + q = bdev_get_queue(bd); - ib_dev->ibd_depth = dev->queue_depth; + dev->dev_attrib.hw_block_size = bdev_logical_block_size(bd); + dev->dev_attrib.hw_max_sectors = UINT_MAX; + dev->dev_attrib.hw_queue_depth = q->nr_requests; /* * Check if the underlying struct block_device request_queue supports * the QUEUE_FLAG_DISCARD bit for UNMAP/WRITE_SAME in SCSI + TRIM * in ATA and we need to set TPE=1 */ - if (blk_queue_discard(bdev_get_queue(bd))) { - struct request_queue *q = bdev_get_queue(bd); - - DEV_ATTRIB(dev)->max_unmap_lba_count = + if (blk_queue_discard(q)) { + dev->dev_attrib.max_unmap_lba_count = q->limits.max_discard_sectors; + /* * Currently hardcoded to 1 in Linux/SCSI code.. */ - DEV_ATTRIB(dev)->max_unmap_block_desc_count = 1; - DEV_ATTRIB(dev)->unmap_granularity = - q->limits.discard_granularity; - DEV_ATTRIB(dev)->unmap_granularity_alignment = + dev->dev_attrib.max_unmap_block_desc_count = 1; + dev->dev_attrib.unmap_granularity = + q->limits.discard_granularity >> 9; + dev->dev_attrib.unmap_granularity_alignment = q->limits.discard_alignment; - printk(KERN_INFO "IBLOCK: BLOCK Discard support available," + pr_debug("IBLOCK: BLOCK Discard support available," " disabled by default\n"); } + /* + * Enable write same emulation for IBLOCK and use 0xFFFF as + * the smaller WRITE_SAME(10) only has a two-byte block count. + */ + dev->dev_attrib.max_write_same_len = 0xFFFF; - return dev; + if (blk_queue_nonrot(q)) + dev->dev_attrib.is_nonrot = 1; -failed: - if (ib_dev->ibd_bio_set) { - bioset_free(ib_dev->ibd_bio_set); - ib_dev->ibd_bio_set = NULL; + bi = bdev_get_integrity(bd); + if (bi) { + struct bio_set *bs = ib_dev->ibd_bio_set; + + if (!strcmp(bi->name, "T10-DIF-TYPE3-IP") || + !strcmp(bi->name, "T10-DIF-TYPE1-IP")) { + pr_err("IBLOCK export of blk_integrity: %s not" + " supported\n", bi->name); + ret = -ENOSYS; + goto out_blkdev_put; + } + + if (!strcmp(bi->name, "T10-DIF-TYPE3-CRC")) { + dev->dev_attrib.pi_prot_type = TARGET_DIF_TYPE3_PROT; + } else if (!strcmp(bi->name, "T10-DIF-TYPE1-CRC")) { + dev->dev_attrib.pi_prot_type = TARGET_DIF_TYPE1_PROT; + } + + if (dev->dev_attrib.pi_prot_type) { + if (bioset_integrity_create(bs, IBLOCK_BIO_POOL_SIZE) < 0) { + pr_err("Unable to allocate bioset for PI\n"); + ret = -ENOMEM; + goto out_blkdev_put; + } + pr_debug("IBLOCK setup BIP bs->bio_integrity_pool: %p\n", + bs->bio_integrity_pool); + } + dev->dev_attrib.hw_pi_prot_type = dev->dev_attrib.pi_prot_type; } - ib_dev->ibd_bd = NULL; - ib_dev->ibd_major = 0; - ib_dev->ibd_minor = 0; - return NULL; + + return 0; + +out_blkdev_put: + blkdev_put(ib_dev->ibd_bd, FMODE_WRITE|FMODE_READ|FMODE_EXCL); +out_free_bioset: + bioset_free(ib_dev->ibd_bio_set); + ib_dev->ibd_bio_set = NULL; +out: + return ret; } -static void iblock_free_device(void *p) +static void iblock_free_device(struct se_device *dev) { - struct iblock_dev *ib_dev = p; + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); if (ib_dev->ibd_bd != NULL) blkdev_put(ib_dev->ibd_bd, FMODE_WRITE|FMODE_READ|FMODE_EXCL); if (ib_dev->ibd_bio_set != NULL) bioset_free(ib_dev->ibd_bio_set); - kfree(ib_dev); -} -static inline struct iblock_req *IBLOCK_REQ(struct se_task *task) -{ - return container_of(task, struct iblock_req, ib_task); -} - -static struct se_task * -iblock_alloc_task(struct se_cmd *cmd) -{ - struct iblock_req *ib_req; - - ib_req = kzalloc(sizeof(struct iblock_req), GFP_KERNEL); - if (!(ib_req)) { - printk(KERN_ERR "Unable to allocate memory for struct iblock_req\n"); - return NULL; - } - - ib_req->ib_dev = SE_DEV(cmd)->dev_ptr; - atomic_set(&ib_req->ib_bio_cnt, 0); - return &ib_req->ib_task; + kfree(ib_dev); } static unsigned long long iblock_emulate_read_cap_with_block_size( @@ -257,12 +218,12 @@ static unsigned long long iblock_emulate_read_cap_with_block_size( bdev_logical_block_size(bd)) - 1); u32 block_size = bdev_logical_block_size(bd); - if (block_size == DEV_ATTRIB(dev)->block_size) + if (block_size == dev->dev_attrib.block_size) return blocks_long; switch (block_size) { case 4096: - switch (DEV_ATTRIB(dev)->block_size) { + switch (dev->dev_attrib.block_size) { case 2048: blocks_long <<= 1; break; @@ -276,7 +237,7 @@ static unsigned long long iblock_emulate_read_cap_with_block_size( } break; case 2048: - switch (DEV_ATTRIB(dev)->block_size) { + switch (dev->dev_attrib.block_size) { case 4096: blocks_long >>= 1; break; @@ -291,7 +252,7 @@ static unsigned long long iblock_emulate_read_cap_with_block_size( } break; case 1024: - switch (DEV_ATTRIB(dev)->block_size) { + switch (dev->dev_attrib.block_size) { case 4096: blocks_long >>= 2; break; @@ -306,7 +267,7 @@ static unsigned long long iblock_emulate_read_cap_with_block_size( } break; case 512: - switch (DEV_ATTRIB(dev)->block_size) { + switch (dev->dev_attrib.block_size) { case 4096: blocks_long >>= 3; break; @@ -327,151 +288,253 @@ static unsigned long long iblock_emulate_read_cap_with_block_size( return blocks_long; } -/* - * Emulate SYCHRONIZE_CACHE_* - */ -static void iblock_emulate_sync_cache(struct se_task *task) +static void iblock_complete_cmd(struct se_cmd *cmd) { - struct se_cmd *cmd = TASK_CMD(task); - struct iblock_dev *ib_dev = cmd->se_dev->dev_ptr; - int immed = (T_TASK(cmd)->t_task_cdb[1] & 0x2); - sector_t error_sector; - int ret; + struct iblock_req *ibr = cmd->priv; + u8 status; + + if (!atomic_dec_and_test(&ibr->pending)) + return; + + if (atomic_read(&ibr->ib_bio_err_cnt)) + status = SAM_STAT_CHECK_CONDITION; + else + status = SAM_STAT_GOOD; + + target_complete_cmd(cmd, status); + kfree(ibr); +} + +static void iblock_bio_done(struct bio *bio, int err) +{ + struct se_cmd *cmd = bio->bi_private; + struct iblock_req *ibr = cmd->priv; /* - * If the Immediate bit is set, queue up the GOOD response - * for this SYNCHRONIZE_CACHE op + * Set -EIO if !BIO_UPTODATE and the passed is still err=0 */ - if (immed) - transport_complete_sync_cache(cmd, 1); + if (!test_bit(BIO_UPTODATE, &bio->bi_flags) && !err) + err = -EIO; + + if (err != 0) { + pr_err("test_bit(BIO_UPTODATE) failed for bio: %p," + " err: %d\n", bio, err); + /* + * Bump the ib_bio_err_cnt and release bio. + */ + atomic_inc(&ibr->ib_bio_err_cnt); + smp_mb__after_atomic(); + } + + bio_put(bio); + + iblock_complete_cmd(cmd); +} + +static struct bio * +iblock_get_bio(struct se_cmd *cmd, sector_t lba, u32 sg_num) +{ + struct iblock_dev *ib_dev = IBLOCK_DEV(cmd->se_dev); + struct bio *bio; /* - * blkdev_issue_flush() does not support a specifying a range, so - * we have to flush the entire cache. + * Only allocate as many vector entries as the bio code allows us to, + * we'll loop later on until we have handled the whole request. */ - ret = blkdev_issue_flush(ib_dev->ibd_bd, GFP_KERNEL, &error_sector); - if (ret != 0) { - printk(KERN_ERR "IBLOCK: block_issue_flush() failed: %d " - " error_sector: %llu\n", ret, - (unsigned long long)error_sector); + if (sg_num > BIO_MAX_PAGES) + sg_num = BIO_MAX_PAGES; + + bio = bio_alloc_bioset(GFP_NOIO, sg_num, ib_dev->ibd_bio_set); + if (!bio) { + pr_err("Unable to allocate memory for bio\n"); + return NULL; } - if (!immed) - transport_complete_sync_cache(cmd, ret == 0); + bio->bi_bdev = ib_dev->ibd_bd; + bio->bi_private = cmd; + bio->bi_end_io = &iblock_bio_done; + bio->bi_iter.bi_sector = lba; + + return bio; } -/* - * Tell TCM Core that we are capable of WriteCache emulation for - * an underlying struct se_device. - */ -static int iblock_emulated_write_cache(struct se_device *dev) +static void iblock_submit_bios(struct bio_list *list, int rw) { - return 1; + struct blk_plug plug; + struct bio *bio; + + blk_start_plug(&plug); + while ((bio = bio_list_pop(list))) + submit_bio(rw, bio); + blk_finish_plug(&plug); } -static int iblock_emulated_dpo(struct se_device *dev) +static void iblock_end_io_flush(struct bio *bio, int err) { - return 0; + struct se_cmd *cmd = bio->bi_private; + + if (err) + pr_err("IBLOCK: cache flush failed: %d\n", err); + + if (cmd) { + if (err) + target_complete_cmd(cmd, SAM_STAT_CHECK_CONDITION); + else + target_complete_cmd(cmd, SAM_STAT_GOOD); + } + + bio_put(bio); } /* - * Tell TCM Core that we will be emulating Forced Unit Access (FUA) for WRITEs - * for TYPE_DISK. + * Implement SYCHRONIZE CACHE. Note that we can't handle lba ranges and must + * always flush the whole cache. */ -static int iblock_emulated_fua_write(struct se_device *dev) +static sense_reason_t +iblock_execute_sync_cache(struct se_cmd *cmd) { - return 1; -} + struct iblock_dev *ib_dev = IBLOCK_DEV(cmd->se_dev); + int immed = (cmd->t_task_cdb[1] & 0x2); + struct bio *bio; -static int iblock_emulated_fua_read(struct se_device *dev) -{ + /* + * If the Immediate bit is set, queue up the GOOD response + * for this SYNCHRONIZE_CACHE op. + */ + if (immed) + target_complete_cmd(cmd, SAM_STAT_GOOD); + + bio = bio_alloc(GFP_KERNEL, 0); + bio->bi_end_io = iblock_end_io_flush; + bio->bi_bdev = ib_dev->ibd_bd; + if (!immed) + bio->bi_private = cmd; + submit_bio(WRITE_FLUSH, bio); return 0; } -static int iblock_do_task(struct se_task *task) +static sense_reason_t +iblock_do_unmap(struct se_cmd *cmd, void *priv, + sector_t lba, sector_t nolb) { - struct se_device *dev = task->task_se_cmd->se_dev; - struct iblock_req *req = IBLOCK_REQ(task); - struct iblock_dev *ibd = (struct iblock_dev *)req->ib_dev; - struct request_queue *q = bdev_get_queue(ibd->ibd_bd); - struct bio *bio = req->ib_bio, *nbio = NULL; - int rw; - - if (task->task_data_direction == DMA_TO_DEVICE) { - /* - * Force data to disk if we pretend to not have a volatile - * write cache, or the initiator set the Force Unit Access bit. - */ - if (DEV_ATTRIB(dev)->emulate_write_cache == 0 || - (DEV_ATTRIB(dev)->emulate_fua_write > 0 && - T_TASK(task->task_se_cmd)->t_tasks_fua)) - rw = WRITE_FUA; - else - rw = WRITE; - } else { - rw = READ; + struct block_device *bdev = priv; + int ret; + + ret = blkdev_issue_discard(bdev, lba, nolb, GFP_KERNEL, 0); + if (ret < 0) { + pr_err("blkdev_issue_discard() failed: %d\n", ret); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } - while (bio) { - nbio = bio->bi_next; - bio->bi_next = NULL; - DEBUG_IBLOCK("Calling submit_bio() task: %p bio: %p" - " bio->bi_sector: %llu\n", task, bio, bio->bi_sector); + return 0; +} - submit_bio(rw, bio); - bio = nbio; - } +static sense_reason_t +iblock_execute_unmap(struct se_cmd *cmd) +{ + struct block_device *bdev = IBLOCK_DEV(cmd->se_dev)->ibd_bd; - if (q->unplug_fn) - q->unplug_fn(q); - return PYX_TRANSPORT_SENT_TO_TRANSPORT; + return sbc_execute_unmap(cmd, iblock_do_unmap, bdev); } -static int iblock_do_discard(struct se_device *dev, sector_t lba, u32 range) +static sense_reason_t +iblock_execute_write_same_unmap(struct se_cmd *cmd) { - struct iblock_dev *ibd = dev->dev_ptr; - struct block_device *bd = ibd->ibd_bd; - int barrier = 0; + struct block_device *bdev = IBLOCK_DEV(cmd->se_dev)->ibd_bd; + sector_t lba = cmd->t_task_lba; + sector_t nolb = sbc_get_write_same_sectors(cmd); + int ret; + + ret = iblock_do_unmap(cmd, bdev, lba, nolb); + if (ret) + return ret; - return blkdev_issue_discard(bd, lba, range, GFP_KERNEL, barrier); + target_complete_cmd(cmd, GOOD); + return 0; } -static void iblock_free_task(struct se_task *task) +static sense_reason_t +iblock_execute_write_same(struct se_cmd *cmd) { - struct iblock_req *req = IBLOCK_REQ(task); - struct bio *bio, *hbio = req->ib_bio; - /* - * We only release the bio(s) here if iblock_bio_done() has not called - * bio_put() -> iblock_bio_destructor(). - */ - while (hbio != NULL) { - bio = hbio; - hbio = hbio->bi_next; - bio->bi_next = NULL; - bio_put(bio); + struct iblock_req *ibr; + struct scatterlist *sg; + struct bio *bio; + struct bio_list list; + sector_t block_lba = cmd->t_task_lba; + sector_t sectors = sbc_get_write_same_sectors(cmd); + + sg = &cmd->t_data_sg[0]; + + if (cmd->t_data_nents > 1 || + sg->length != cmd->se_dev->dev_attrib.block_size) { + pr_err("WRITE_SAME: Illegal SGL t_data_nents: %u length: %u" + " block_size: %u\n", cmd->t_data_nents, sg->length, + cmd->se_dev->dev_attrib.block_size); + return TCM_INVALID_CDB_FIELD; } - kfree(req); + ibr = kzalloc(sizeof(struct iblock_req), GFP_KERNEL); + if (!ibr) + goto fail; + cmd->priv = ibr; + + bio = iblock_get_bio(cmd, block_lba, 1); + if (!bio) + goto fail_free_ibr; + + bio_list_init(&list); + bio_list_add(&list, bio); + + atomic_set(&ibr->pending, 1); + + while (sectors) { + while (bio_add_page(bio, sg_page(sg), sg->length, sg->offset) + != sg->length) { + + bio = iblock_get_bio(cmd, block_lba, 1); + if (!bio) + goto fail_put_bios; + + atomic_inc(&ibr->pending); + bio_list_add(&list, bio); + } + + /* Always in 512 byte units for Linux/Block */ + block_lba += sg->length >> IBLOCK_LBA_SHIFT; + sectors -= 1; + } + + iblock_submit_bios(&list, WRITE); + return 0; + +fail_put_bios: + while ((bio = bio_list_pop(&list))) + bio_put(bio); +fail_free_ibr: + kfree(ibr); +fail: + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } enum { - Opt_udev_path, Opt_force, Opt_err + Opt_udev_path, Opt_readonly, Opt_force, Opt_err }; static match_table_t tokens = { {Opt_udev_path, "udev_path=%s"}, + {Opt_readonly, "readonly=%d"}, {Opt_force, "force=%d"}, {Opt_err, NULL} }; -static ssize_t iblock_set_configfs_dev_params(struct se_hba *hba, - struct se_subsystem_dev *se_dev, - const char *page, ssize_t count) +static ssize_t iblock_set_configfs_dev_params(struct se_device *dev, + const char *page, ssize_t count) { - struct iblock_dev *ib_dev = se_dev->se_dev_su_ptr; - char *orig, *ptr, *opts; + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + char *orig, *ptr, *arg_p, *opts; substring_t args[MAX_OPT_ARGS]; - int ret = 0, arg, token; + int ret = 0, token; + unsigned long tmp_readonly; opts = kstrdup(page, GFP_KERNEL); if (!opts) @@ -479,7 +542,7 @@ static ssize_t iblock_set_configfs_dev_params(struct se_hba *hba, orig = opts; - while ((ptr = strsep(&opts, ",")) != NULL) { + while ((ptr = strsep(&opts, ",\n")) != NULL) { if (!*ptr) continue; @@ -487,23 +550,37 @@ static ssize_t iblock_set_configfs_dev_params(struct se_hba *hba, switch (token) { case Opt_udev_path: if (ib_dev->ibd_bd) { - printk(KERN_ERR "Unable to set udev_path= while" + pr_err("Unable to set udev_path= while" " ib_dev->ibd_bd exists\n"); ret = -EEXIST; goto out; } - - ret = snprintf(ib_dev->ibd_udev_path, SE_UDEV_PATH_LEN, - "%s", match_strdup(&args[0])); - printk(KERN_INFO "IBLOCK: Referencing UDEV path: %s\n", + if (match_strlcpy(ib_dev->ibd_udev_path, &args[0], + SE_UDEV_PATH_LEN) == 0) { + ret = -EINVAL; + break; + } + pr_debug("IBLOCK: Referencing UDEV path: %s\n", ib_dev->ibd_udev_path); ib_dev->ibd_flags |= IBDF_HAS_UDEV_PATH; break; + case Opt_readonly: + arg_p = match_strdup(&args[0]); + if (!arg_p) { + ret = -ENOMEM; + break; + } + ret = kstrtoul(arg_p, 0, &tmp_readonly); + kfree(arg_p); + if (ret < 0) { + pr_err("kstrtoul() failed for" + " readonly=\n"); + goto out; + } + ib_dev->ibd_readonly = tmp_readonly; + pr_debug("IBLOCK: readonly: %d\n", ib_dev->ibd_readonly); + break; case Opt_force: - match_int(args, &arg); - ib_dev->ibd_force = arg; - printk(KERN_INFO "IBLOCK: Set force=%d\n", - ib_dev->ibd_force); break; default: break; @@ -515,281 +592,293 @@ out: return (!ret) ? count : ret; } -static ssize_t iblock_check_configfs_dev_params( - struct se_hba *hba, - struct se_subsystem_dev *se_dev) -{ - struct iblock_dev *ibd = se_dev->se_dev_su_ptr; - - if (!(ibd->ibd_flags & IBDF_HAS_UDEV_PATH)) { - printk(KERN_ERR "Missing udev_path= parameters for IBLOCK\n"); - return -1; - } - - return 0; -} - -static ssize_t iblock_show_configfs_dev_params( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - char *b) +static ssize_t iblock_show_configfs_dev_params(struct se_device *dev, char *b) { - struct iblock_dev *ibd = se_dev->se_dev_su_ptr; - struct block_device *bd = ibd->ibd_bd; + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct block_device *bd = ib_dev->ibd_bd; char buf[BDEVNAME_SIZE]; ssize_t bl = 0; if (bd) bl += sprintf(b + bl, "iBlock device: %s", bdevname(bd, buf)); - if (ibd->ibd_flags & IBDF_HAS_UDEV_PATH) { - bl += sprintf(b + bl, " UDEV PATH: %s\n", - ibd->ibd_udev_path); - } else - bl += sprintf(b + bl, "\n"); + if (ib_dev->ibd_flags & IBDF_HAS_UDEV_PATH) + bl += sprintf(b + bl, " UDEV PATH: %s", + ib_dev->ibd_udev_path); + bl += sprintf(b + bl, " readonly: %d\n", ib_dev->ibd_readonly); bl += sprintf(b + bl, " "); if (bd) { bl += sprintf(b + bl, "Major: %d Minor: %d %s\n", - ibd->ibd_major, ibd->ibd_minor, (!bd->bd_contains) ? - "" : (bd->bd_holder == (struct iblock_dev *)ibd) ? + MAJOR(bd->bd_dev), MINOR(bd->bd_dev), (!bd->bd_contains) ? + "" : (bd->bd_holder == ib_dev) ? "CLAIMED: IBLOCK" : "CLAIMED: OS"); } else { - bl += sprintf(b + bl, "Major: %d Minor: %d\n", - ibd->ibd_major, ibd->ibd_minor); + bl += sprintf(b + bl, "Major: 0 Minor: 0\n"); } return bl; } -static void iblock_bio_destructor(struct bio *bio) +static int +iblock_alloc_bip(struct se_cmd *cmd, struct bio *bio) { - struct se_task *task = bio->bi_private; - struct iblock_dev *ib_dev = task->se_dev->dev_ptr; - - bio_free(bio, ib_dev->ibd_bio_set); -} + struct se_device *dev = cmd->se_dev; + struct blk_integrity *bi; + struct bio_integrity_payload *bip; + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct scatterlist *sg; + int i, rc; -static struct bio *iblock_get_bio( - struct se_task *task, - struct iblock_req *ib_req, - struct iblock_dev *ib_dev, - int *ret, - sector_t lba, - u32 sg_num) -{ - struct bio *bio; + bi = bdev_get_integrity(ib_dev->ibd_bd); + if (!bi) { + pr_err("Unable to locate bio_integrity\n"); + return -ENODEV; + } - bio = bio_alloc_bioset(GFP_NOIO, sg_num, ib_dev->ibd_bio_set); - if (!(bio)) { - printk(KERN_ERR "Unable to allocate memory for bio\n"); - *ret = PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES; - return NULL; + bip = bio_integrity_alloc(bio, GFP_NOIO, cmd->t_prot_nents); + if (!bip) { + pr_err("Unable to allocate bio_integrity_payload\n"); + return -ENOMEM; } - DEBUG_IBLOCK("Allocated bio: %p task_sg_num: %u using ibd_bio_set:" - " %p\n", bio, task->task_sg_num, ib_dev->ibd_bio_set); - DEBUG_IBLOCK("Allocated bio: %p task_size: %u\n", bio, task->task_size); + bip->bip_iter.bi_size = (cmd->data_length / dev->dev_attrib.block_size) * + dev->prot_length; + bip->bip_iter.bi_sector = bio->bi_iter.bi_sector; - bio->bi_bdev = ib_dev->ibd_bd; - bio->bi_private = (void *) task; - bio->bi_destructor = iblock_bio_destructor; - bio->bi_end_io = &iblock_bio_done; - bio->bi_sector = lba; - atomic_inc(&ib_req->ib_bio_cnt); + pr_debug("IBLOCK BIP Size: %u Sector: %llu\n", bip->bip_iter.bi_size, + (unsigned long long)bip->bip_iter.bi_sector); - DEBUG_IBLOCK("Set bio->bi_sector: %llu\n", bio->bi_sector); - DEBUG_IBLOCK("Set ib_req->ib_bio_cnt: %d\n", - atomic_read(&ib_req->ib_bio_cnt)); - return bio; + for_each_sg(cmd->t_prot_sg, sg, cmd->t_prot_nents, i) { + + rc = bio_integrity_add_page(bio, sg_page(sg), sg->length, + sg->offset); + if (rc != sg->length) { + pr_err("bio_integrity_add_page() failed; %d\n", rc); + return -ENOMEM; + } + + pr_debug("Added bio integrity page: %p length: %d offset; %d\n", + sg_page(sg), sg->length, sg->offset); + } + + return 0; } -static int iblock_map_task_SG(struct se_task *task) +static sense_reason_t +iblock_execute_rw(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents, + enum dma_data_direction data_direction) { - struct se_cmd *cmd = task->task_se_cmd; - struct se_device *dev = SE_DEV(cmd); - struct iblock_dev *ib_dev = task->se_dev->dev_ptr; - struct iblock_req *ib_req = IBLOCK_REQ(task); - struct bio *bio = NULL, *hbio = NULL, *tbio = NULL; + struct se_device *dev = cmd->se_dev; + struct iblock_req *ibr; + struct bio *bio, *bio_start; + struct bio_list list; struct scatterlist *sg; - int ret = 0; - u32 i, sg_num = task->task_sg_num; + u32 sg_num = sgl_nents; sector_t block_lba; + unsigned bio_cnt; + int rw = 0; + int i; + + if (data_direction == DMA_TO_DEVICE) { + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct request_queue *q = bdev_get_queue(ib_dev->ibd_bd); + /* + * Force writethrough using WRITE_FUA if a volatile write cache + * is not enabled, or if initiator set the Force Unit Access bit. + */ + if (q->flush_flags & REQ_FUA) { + if (cmd->se_cmd_flags & SCF_FUA) + rw = WRITE_FUA; + else if (!(q->flush_flags & REQ_FLUSH)) + rw = WRITE_FUA; + else + rw = WRITE; + } else { + rw = WRITE; + } + } else { + rw = READ; + } + /* - * Do starting conversion up from non 512-byte blocksize with - * struct se_task SCSI blocksize into Linux/Block 512 units for BIO. + * Convert the blocksize advertised to the initiator to the 512 byte + * units unconditionally used by the Linux block layer. */ - if (DEV_ATTRIB(dev)->block_size == 4096) - block_lba = (task->task_lba << 3); - else if (DEV_ATTRIB(dev)->block_size == 2048) - block_lba = (task->task_lba << 2); - else if (DEV_ATTRIB(dev)->block_size == 1024) - block_lba = (task->task_lba << 1); - else if (DEV_ATTRIB(dev)->block_size == 512) - block_lba = task->task_lba; + if (dev->dev_attrib.block_size == 4096) + block_lba = (cmd->t_task_lba << 3); + else if (dev->dev_attrib.block_size == 2048) + block_lba = (cmd->t_task_lba << 2); + else if (dev->dev_attrib.block_size == 1024) + block_lba = (cmd->t_task_lba << 1); + else if (dev->dev_attrib.block_size == 512) + block_lba = cmd->t_task_lba; else { - printk(KERN_ERR "Unsupported SCSI -> BLOCK LBA conversion:" - " %u\n", DEV_ATTRIB(dev)->block_size); - return PYX_TRANSPORT_LU_COMM_FAILURE; + pr_err("Unsupported SCSI -> BLOCK LBA conversion:" + " %u\n", dev->dev_attrib.block_size); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } - bio = iblock_get_bio(task, ib_req, ib_dev, &ret, block_lba, sg_num); - if (!(bio)) - return ret; + ibr = kzalloc(sizeof(struct iblock_req), GFP_KERNEL); + if (!ibr) + goto fail; + cmd->priv = ibr; - ib_req->ib_bio = bio; - hbio = tbio = bio; - /* - * Use fs/bio.c:bio_add_pages() to setup the bio_vec maplist - * from TCM struct se_mem -> task->task_sg -> struct scatterlist memory. - */ - for_each_sg(task->task_sg, sg, task->task_sg_num, i) { - DEBUG_IBLOCK("task: %p bio: %p Calling bio_add_page(): page:" - " %p len: %u offset: %u\n", task, bio, sg_page(sg), - sg->length, sg->offset); -again: - ret = bio_add_page(bio, sg_page(sg), sg->length, sg->offset); - if (ret != sg->length) { - - DEBUG_IBLOCK("*** Set bio->bi_sector: %llu\n", - bio->bi_sector); - DEBUG_IBLOCK("** task->task_size: %u\n", - task->task_size); - DEBUG_IBLOCK("*** bio->bi_max_vecs: %u\n", - bio->bi_max_vecs); - DEBUG_IBLOCK("*** bio->bi_vcnt: %u\n", - bio->bi_vcnt); - - bio = iblock_get_bio(task, ib_req, ib_dev, &ret, - block_lba, sg_num); - if (!(bio)) - goto fail; - - tbio = tbio->bi_next = bio; - DEBUG_IBLOCK("-----------------> Added +1 bio: %p to" - " list, Going to again\n", bio); - goto again; + if (!sgl_nents) { + atomic_set(&ibr->pending, 1); + iblock_complete_cmd(cmd); + return 0; + } + + bio = iblock_get_bio(cmd, block_lba, sgl_nents); + if (!bio) + goto fail_free_ibr; + + bio_start = bio; + bio_list_init(&list); + bio_list_add(&list, bio); + + atomic_set(&ibr->pending, 2); + bio_cnt = 1; + + for_each_sg(sgl, sg, sgl_nents, i) { + /* + * XXX: if the length the device accepts is shorter than the + * length of the S/G list entry this will cause and + * endless loop. Better hope no driver uses huge pages. + */ + while (bio_add_page(bio, sg_page(sg), sg->length, sg->offset) + != sg->length) { + if (bio_cnt >= IBLOCK_MAX_BIO_PER_TASK) { + iblock_submit_bios(&list, rw); + bio_cnt = 0; + } + + bio = iblock_get_bio(cmd, block_lba, sg_num); + if (!bio) + goto fail_put_bios; + + atomic_inc(&ibr->pending); + bio_list_add(&list, bio); + bio_cnt++; } + /* Always in 512 byte units for Linux/Block */ block_lba += sg->length >> IBLOCK_LBA_SHIFT; sg_num--; - DEBUG_IBLOCK("task: %p bio-add_page() passed!, decremented" - " sg_num to %u\n", task, sg_num); - DEBUG_IBLOCK("task: %p bio_add_page() passed!, increased lba" - " to %llu\n", task, block_lba); - DEBUG_IBLOCK("task: %p bio_add_page() passed!, bio->bi_vcnt:" - " %u\n", task, bio->bi_vcnt); } + if (cmd->prot_type) { + int rc = iblock_alloc_bip(cmd, bio_start); + if (rc) + goto fail_put_bios; + } + + iblock_submit_bios(&list, rw); + iblock_complete_cmd(cmd); return 0; -fail: - while (hbio) { - bio = hbio; - hbio = hbio->bi_next; - bio->bi_next = NULL; + +fail_put_bios: + while ((bio = bio_list_pop(&list))) bio_put(bio); - } - return ret; +fail_free_ibr: + kfree(ibr); +fail: + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } -static unsigned char *iblock_get_cdb(struct se_task *task) +static sector_t iblock_get_blocks(struct se_device *dev) { - return IBLOCK_REQ(task)->ib_scsi_cdb; + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct block_device *bd = ib_dev->ibd_bd; + struct request_queue *q = bdev_get_queue(bd); + + return iblock_emulate_read_cap_with_block_size(dev, bd, q); } -static u32 iblock_get_device_rev(struct se_device *dev) +static sector_t iblock_get_alignment_offset_lbas(struct se_device *dev) { - return SCSI_SPC_2; /* Returns SPC-3 in Initiator Data */ + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct block_device *bd = ib_dev->ibd_bd; + int ret; + + ret = bdev_alignment_offset(bd); + if (ret == -1) + return 0; + + /* convert offset-bytes to offset-lbas */ + return ret / bdev_logical_block_size(bd); } -static u32 iblock_get_device_type(struct se_device *dev) +static unsigned int iblock_get_lbppbe(struct se_device *dev) { - return TYPE_DISK; + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct block_device *bd = ib_dev->ibd_bd; + int logs_per_phys = bdev_physical_block_size(bd) / bdev_logical_block_size(bd); + + return ilog2(logs_per_phys); } -static sector_t iblock_get_blocks(struct se_device *dev) +static unsigned int iblock_get_io_min(struct se_device *dev) { - struct iblock_dev *ibd = dev->dev_ptr; - struct block_device *bd = ibd->ibd_bd; - struct request_queue *q = bdev_get_queue(bd); + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct block_device *bd = ib_dev->ibd_bd; - return iblock_emulate_read_cap_with_block_size(dev, bd, q); + return bdev_io_min(bd); } -static void iblock_bio_done(struct bio *bio, int err) +static unsigned int iblock_get_io_opt(struct se_device *dev) { - struct se_task *task = bio->bi_private; - struct iblock_req *ibr = IBLOCK_REQ(task); - /* - * Set -EIO if !BIO_UPTODATE and the passed is still err=0 - */ - if (!(test_bit(BIO_UPTODATE, &bio->bi_flags)) && !(err)) - err = -EIO; + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct block_device *bd = ib_dev->ibd_bd; - if (err != 0) { - printk(KERN_ERR "test_bit(BIO_UPTODATE) failed for bio: %p," - " err: %d\n", bio, err); - /* - * Bump the ib_bio_err_cnt and release bio. - */ - atomic_inc(&ibr->ib_bio_err_cnt); - smp_mb__after_atomic_inc(); - bio_put(bio); - /* - * Wait to complete the task until the last bio as completed. - */ - if (!(atomic_dec_and_test(&ibr->ib_bio_cnt))) - return; + return bdev_io_opt(bd); +} - ibr->ib_bio = NULL; - transport_complete_task(task, 0); - return; - } - DEBUG_IBLOCK("done[%p] bio: %p task_lba: %llu bio_lba: %llu err=%d\n", - task, bio, task->task_lba, bio->bi_sector, err); - /* - * bio_put() will call iblock_bio_destructor() to release the bio back - * to ibr->ib_bio_set. - */ - bio_put(bio); - /* - * Wait to complete the task until the last bio as completed. - */ - if (!(atomic_dec_and_test(&ibr->ib_bio_cnt))) - return; - /* - * Return GOOD status for task if zero ib_bio_err_cnt exists. - */ - ibr->ib_bio = NULL; - transport_complete_task(task, (!atomic_read(&ibr->ib_bio_err_cnt))); +static struct sbc_ops iblock_sbc_ops = { + .execute_rw = iblock_execute_rw, + .execute_sync_cache = iblock_execute_sync_cache, + .execute_write_same = iblock_execute_write_same, + .execute_write_same_unmap = iblock_execute_write_same_unmap, + .execute_unmap = iblock_execute_unmap, +}; + +static sense_reason_t +iblock_parse_cdb(struct se_cmd *cmd) +{ + return sbc_parse_cdb(cmd, &iblock_sbc_ops); +} + +static bool iblock_get_write_cache(struct se_device *dev) +{ + struct iblock_dev *ib_dev = IBLOCK_DEV(dev); + struct block_device *bd = ib_dev->ibd_bd; + struct request_queue *q = bdev_get_queue(bd); + + return q->flush_flags & REQ_FLUSH; } static struct se_subsystem_api iblock_template = { .name = "iblock", + .inquiry_prod = "IBLOCK", + .inquiry_rev = IBLOCK_VERSION, .owner = THIS_MODULE, .transport_type = TRANSPORT_PLUGIN_VHBA_PDEV, - .map_task_SG = iblock_map_task_SG, .attach_hba = iblock_attach_hba, .detach_hba = iblock_detach_hba, - .allocate_virtdevice = iblock_allocate_virtdevice, - .create_virtdevice = iblock_create_virtdevice, + .alloc_device = iblock_alloc_device, + .configure_device = iblock_configure_device, .free_device = iblock_free_device, - .dpo_emulated = iblock_emulated_dpo, - .fua_write_emulated = iblock_emulated_fua_write, - .fua_read_emulated = iblock_emulated_fua_read, - .write_cache_emulated = iblock_emulated_write_cache, - .alloc_task = iblock_alloc_task, - .do_task = iblock_do_task, - .do_discard = iblock_do_discard, - .do_sync_cache = iblock_emulate_sync_cache, - .free_task = iblock_free_task, - .check_configfs_dev_params = iblock_check_configfs_dev_params, + .parse_cdb = iblock_parse_cdb, .set_configfs_dev_params = iblock_set_configfs_dev_params, .show_configfs_dev_params = iblock_show_configfs_dev_params, - .get_cdb = iblock_get_cdb, - .get_device_rev = iblock_get_device_rev, - .get_device_type = iblock_get_device_type, + .get_device_type = sbc_get_device_type, .get_blocks = iblock_get_blocks, + .get_alignment_offset_lbas = iblock_get_alignment_offset_lbas, + .get_lbppbe = iblock_get_lbppbe, + .get_io_min = iblock_get_io_min, + .get_io_opt = iblock_get_io_opt, + .get_write_cache = iblock_get_write_cache, }; static int __init iblock_module_init(void) @@ -797,7 +886,7 @@ static int __init iblock_module_init(void) return transport_subsystem_register(&iblock_template); } -static void iblock_module_exit(void) +static void __exit iblock_module_exit(void) { transport_subsystem_release(&iblock_template); } diff --git a/drivers/target/target_core_iblock.h b/drivers/target/target_core_iblock.h index 64c1f4d69f7..01c2afd8150 100644 --- a/drivers/target/target_core_iblock.h +++ b/drivers/target/target_core_iblock.h @@ -3,38 +3,23 @@ #define IBLOCK_VERSION "4.0" -#define IBLOCK_HBA_QUEUE_DEPTH 512 -#define IBLOCK_DEVICE_QUEUE_DEPTH 32 -#define IBLOCK_MAX_DEVICE_QUEUE_DEPTH 128 #define IBLOCK_MAX_CDBS 16 #define IBLOCK_LBA_SHIFT 9 struct iblock_req { - struct se_task ib_task; - unsigned char ib_scsi_cdb[TCM_MAX_COMMAND_SIZE]; - atomic_t ib_bio_cnt; + atomic_t pending; atomic_t ib_bio_err_cnt; - struct bio *ib_bio; - struct iblock_dev *ib_dev; } ____cacheline_aligned; #define IBDF_HAS_UDEV_PATH 0x01 -#define IBDF_HAS_FORCE 0x02 struct iblock_dev { + struct se_device dev; unsigned char ibd_udev_path[SE_UDEV_PATH_LEN]; - int ibd_force; - int ibd_major; - int ibd_minor; - u32 ibd_depth; u32 ibd_flags; struct bio_set *ibd_bio_set; struct block_device *ibd_bd; - struct iblock_hba *ibd_host; -} ____cacheline_aligned; - -struct iblock_hba { - int iblock_host_id; + bool ibd_readonly; } ____cacheline_aligned; #endif /* TARGET_CORE_IBLOCK_H */ diff --git a/drivers/target/target_core_internal.h b/drivers/target/target_core_internal.h new file mode 100644 index 00000000000..de9cab708f4 --- /dev/null +++ b/drivers/target/target_core_internal.h @@ -0,0 +1,115 @@ +#ifndef TARGET_CORE_INTERNAL_H +#define TARGET_CORE_INTERNAL_H + +/* target_core_alua.c */ +extern struct t10_alua_lu_gp *default_lu_gp; + +/* target_core_device.c */ +struct se_dev_entry *core_get_se_deve_from_rtpi(struct se_node_acl *, u16); +int core_free_device_list_for_node(struct se_node_acl *, + struct se_portal_group *); +void core_update_device_list_access(u32, u32, struct se_node_acl *); +int core_enable_device_list_for_node(struct se_lun *, struct se_lun_acl *, + u32, u32, struct se_node_acl *, struct se_portal_group *); +int core_disable_device_list_for_node(struct se_lun *, struct se_lun_acl *, + u32, u32, struct se_node_acl *, struct se_portal_group *); +void core_clear_lun_from_tpg(struct se_lun *, struct se_portal_group *); +int core_dev_export(struct se_device *, struct se_portal_group *, + struct se_lun *); +void core_dev_unexport(struct se_device *, struct se_portal_group *, + struct se_lun *); +int se_dev_set_task_timeout(struct se_device *, u32); +int se_dev_set_max_unmap_lba_count(struct se_device *, u32); +int se_dev_set_max_unmap_block_desc_count(struct se_device *, u32); +int se_dev_set_unmap_granularity(struct se_device *, u32); +int se_dev_set_unmap_granularity_alignment(struct se_device *, u32); +int se_dev_set_max_write_same_len(struct se_device *, u32); +int se_dev_set_emulate_model_alias(struct se_device *, int); +int se_dev_set_emulate_dpo(struct se_device *, int); +int se_dev_set_emulate_fua_write(struct se_device *, int); +int se_dev_set_emulate_fua_read(struct se_device *, int); +int se_dev_set_emulate_write_cache(struct se_device *, int); +int se_dev_set_emulate_ua_intlck_ctrl(struct se_device *, int); +int se_dev_set_emulate_tas(struct se_device *, int); +int se_dev_set_emulate_tpu(struct se_device *, int); +int se_dev_set_emulate_tpws(struct se_device *, int); +int se_dev_set_emulate_caw(struct se_device *, int); +int se_dev_set_emulate_3pc(struct se_device *, int); +int se_dev_set_pi_prot_type(struct se_device *, int); +int se_dev_set_pi_prot_format(struct se_device *, int); +int se_dev_set_enforce_pr_isids(struct se_device *, int); +int se_dev_set_is_nonrot(struct se_device *, int); +int se_dev_set_emulate_rest_reord(struct se_device *dev, int); +int se_dev_set_queue_depth(struct se_device *, u32); +int se_dev_set_max_sectors(struct se_device *, u32); +int se_dev_set_fabric_max_sectors(struct se_device *, u32); +int se_dev_set_optimal_sectors(struct se_device *, u32); +int se_dev_set_block_size(struct se_device *, u32); +struct se_lun *core_dev_add_lun(struct se_portal_group *, struct se_device *, u32); +int core_dev_del_lun(struct se_portal_group *, u32); +struct se_lun *core_get_lun_from_tpg(struct se_portal_group *, u32); +struct se_lun_acl *core_dev_init_initiator_node_lun_acl(struct se_portal_group *, + struct se_node_acl *, u32, int *); +int core_dev_add_initiator_node_lun_acl(struct se_portal_group *, + struct se_lun_acl *, u32, u32); +int core_dev_del_initiator_node_lun_acl(struct se_portal_group *, + struct se_lun *, struct se_lun_acl *); +void core_dev_free_initiator_node_lun_acl(struct se_portal_group *, + struct se_lun_acl *lacl); +int core_dev_setup_virtual_lun0(void); +void core_dev_release_virtual_lun0(void); +struct se_device *target_alloc_device(struct se_hba *hba, const char *name); +int target_configure_device(struct se_device *dev); +void target_free_device(struct se_device *); + +/* target_core_hba.c */ +struct se_hba *core_alloc_hba(const char *, u32, u32); +int core_delete_hba(struct se_hba *); + +/* target_core_tmr.c */ +void core_tmr_abort_task(struct se_device *, struct se_tmr_req *, + struct se_session *); +int core_tmr_lun_reset(struct se_device *, struct se_tmr_req *, + struct list_head *, struct se_cmd *); + +/* target_core_tpg.c */ +extern struct se_device *g_lun0_dev; + +struct se_node_acl *__core_tpg_get_initiator_node_acl(struct se_portal_group *tpg, + const char *); +void core_tpg_add_node_to_devs(struct se_node_acl *, struct se_portal_group *); +void core_tpg_wait_for_nacl_pr_ref(struct se_node_acl *); +struct se_lun *core_tpg_alloc_lun(struct se_portal_group *, u32); +int core_tpg_add_lun(struct se_portal_group *, struct se_lun *, + u32, struct se_device *); +struct se_lun *core_tpg_pre_dellun(struct se_portal_group *, u32 unpacked_lun); +int core_tpg_post_dellun(struct se_portal_group *, struct se_lun *); + +/* target_core_transport.c */ +extern struct kmem_cache *se_tmr_req_cache; + +int init_se_kmem_caches(void); +void release_se_kmem_caches(void); +u32 scsi_get_new_index(scsi_index_t); +void transport_subsystem_check_init(void); +void transport_cmd_finish_abort(struct se_cmd *, int); +unsigned char *transport_dump_cmd_direction(struct se_cmd *); +void transport_dump_dev_state(struct se_device *, char *, int *); +void transport_dump_dev_info(struct se_device *, struct se_lun *, + unsigned long long, char *, int *); +void transport_dump_vpd_proto_id(struct t10_vpd *, unsigned char *, int); +int transport_dump_vpd_assoc(struct t10_vpd *, unsigned char *, int); +int transport_dump_vpd_ident_type(struct t10_vpd *, unsigned char *, int); +int transport_dump_vpd_ident(struct t10_vpd *, unsigned char *, int); +bool target_stop_cmd(struct se_cmd *cmd, unsigned long *flags); +int transport_clear_lun_ref(struct se_lun *); +void transport_send_task_abort(struct se_cmd *); +sense_reason_t target_cmd_size_check(struct se_cmd *cmd, unsigned int size); +void target_qf_do_work(struct work_struct *work); + +/* target_core_stat.c */ +void target_stat_setup_dev_default_groups(struct se_device *); +void target_stat_setup_port_default_groups(struct se_lun *); +void target_stat_setup_mappedlun_default_groups(struct se_lun_acl *); + +#endif /* TARGET_CORE_INTERNAL_H */ diff --git a/drivers/target/target_core_pr.c b/drivers/target/target_core_pr.c index 2521f75362c..df357862286 100644 --- a/drivers/target/target_core_pr.c +++ b/drivers/target/target_core_pr.c @@ -4,8 +4,7 @@ * This file contains SPC-3 compliant persistent reservations and * legacy SPC-2 reservations with compatible reservation handling (CRH=1) * - * Copyright (c) 2009, 2010 Rising Tide Systems - * Copyright (c) 2009, 2010 Linux-iSCSI.org + * (c) Copyright 2009-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -25,23 +24,20 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/list.h> +#include <linux/file.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> #include <asm/unaligned.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_tmr.h> -#include <target/target_core_tpg.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> #include <target/target_core_configfs.h> -#include "target_core_hba.h" +#include "target_core_internal.h" #include "target_core_pr.h" #include "target_core_ua.h" @@ -57,146 +53,56 @@ struct pr_transport_id_holder { struct list_head dest_list; }; -int core_pr_dump_initiator_port( +void core_pr_dump_initiator_port( struct t10_pr_registration *pr_reg, char *buf, u32 size) { - if (!(pr_reg->isid_present_at_reg)) - return 0; + if (!pr_reg->isid_present_at_reg) + buf[0] = '\0'; - snprintf(buf, size, ",i,0x%s", &pr_reg->pr_reg_isid[0]); - return 1; + snprintf(buf, size, ",i,0x%s", pr_reg->pr_reg_isid); } +enum register_type { + REGISTER, + REGISTER_AND_IGNORE_EXISTING_KEY, + REGISTER_AND_MOVE, +}; + +enum preempt_type { + PREEMPT, + PREEMPT_AND_ABORT, +}; + static void __core_scsi3_complete_pro_release(struct se_device *, struct se_node_acl *, struct t10_pr_registration *, int); -static int core_scsi2_reservation_seq_non_holder( - struct se_cmd *cmd, - unsigned char *cdb, - u32 pr_reg_type) +static sense_reason_t +target_scsi2_reservation_check(struct se_cmd *cmd) { - switch (cdb[0]) { + struct se_device *dev = cmd->se_dev; + struct se_session *sess = cmd->se_sess; + + switch (cmd->t_task_cdb[0]) { case INQUIRY: case RELEASE: case RELEASE_10: return 0; default: - return 1; - } - - return 1; -} - -static int core_scsi2_reservation_check(struct se_cmd *cmd, u32 *pr_reg_type) -{ - struct se_device *dev = cmd->se_dev; - struct se_session *sess = cmd->se_sess; - int ret; - - if (!(sess)) - return 0; - - spin_lock(&dev->dev_reservation_lock); - if (!dev->dev_reserved_node_acl || !sess) { - spin_unlock(&dev->dev_reservation_lock); - return 0; - } - if (dev->dev_reserved_node_acl != sess->se_node_acl) { - spin_unlock(&dev->dev_reservation_lock); - return -1; - } - if (!(dev->dev_flags & DF_SPC2_RESERVATIONS_WITH_ISID)) { - spin_unlock(&dev->dev_reservation_lock); - return 0; + break; } - ret = (dev->dev_res_bin_isid == sess->sess_bin_isid) ? 0 : -1; - spin_unlock(&dev->dev_reservation_lock); - - return ret; -} - -static int core_scsi2_reservation_release(struct se_cmd *cmd) -{ - struct se_device *dev = cmd->se_dev; - struct se_session *sess = cmd->se_sess; - struct se_portal_group *tpg = sess->se_tpg; - if (!(sess) || !(tpg)) + if (!dev->dev_reserved_node_acl || !sess) return 0; - spin_lock(&dev->dev_reservation_lock); - if (!dev->dev_reserved_node_acl || !sess) { - spin_unlock(&dev->dev_reservation_lock); - return 0; - } + if (dev->dev_reserved_node_acl != sess->se_node_acl) + return TCM_RESERVATION_CONFLICT; - if (dev->dev_reserved_node_acl != sess->se_node_acl) { - spin_unlock(&dev->dev_reservation_lock); - return 0; + if (dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS_WITH_ISID) { + if (dev->dev_res_bin_isid != sess->sess_bin_isid) + return TCM_RESERVATION_CONFLICT; } - dev->dev_reserved_node_acl = NULL; - dev->dev_flags &= ~DF_SPC2_RESERVATIONS; - if (dev->dev_flags & DF_SPC2_RESERVATIONS_WITH_ISID) { - dev->dev_res_bin_isid = 0; - dev->dev_flags &= ~DF_SPC2_RESERVATIONS_WITH_ISID; - } - printk(KERN_INFO "SCSI-2 Released reservation for %s LUN: %u ->" - " MAPPED LUN: %u for %s\n", TPG_TFO(tpg)->get_fabric_name(), - SE_LUN(cmd)->unpacked_lun, cmd->se_deve->mapped_lun, - sess->se_node_acl->initiatorname); - spin_unlock(&dev->dev_reservation_lock); - - return 0; -} - -static int core_scsi2_reservation_reserve(struct se_cmd *cmd) -{ - struct se_device *dev = cmd->se_dev; - struct se_session *sess = cmd->se_sess; - struct se_portal_group *tpg = sess->se_tpg; - - if ((T_TASK(cmd)->t_task_cdb[1] & 0x01) && - (T_TASK(cmd)->t_task_cdb[1] & 0x02)) { - printk(KERN_ERR "LongIO and Obselete Bits set, returning" - " ILLEGAL_REQUEST\n"); - return PYX_TRANSPORT_ILLEGAL_REQUEST; - } - /* - * This is currently the case for target_core_mod passthrough struct se_cmd - * ops - */ - if (!(sess) || !(tpg)) - return 0; - - spin_lock(&dev->dev_reservation_lock); - if (dev->dev_reserved_node_acl && - (dev->dev_reserved_node_acl != sess->se_node_acl)) { - printk(KERN_ERR "SCSI-2 RESERVATION CONFLIFT for %s fabric\n", - TPG_TFO(tpg)->get_fabric_name()); - printk(KERN_ERR "Original reserver LUN: %u %s\n", - SE_LUN(cmd)->unpacked_lun, - dev->dev_reserved_node_acl->initiatorname); - printk(KERN_ERR "Current attempt - LUN: %u -> MAPPED LUN: %u" - " from %s \n", SE_LUN(cmd)->unpacked_lun, - cmd->se_deve->mapped_lun, - sess->se_node_acl->initiatorname); - spin_unlock(&dev->dev_reservation_lock); - return PYX_TRANSPORT_RESERVATION_CONFLICT; - } - - dev->dev_reserved_node_acl = sess->se_node_acl; - dev->dev_flags |= DF_SPC2_RESERVATIONS; - if (sess->sess_bin_isid != 0) { - dev->dev_res_bin_isid = sess->sess_bin_isid; - dev->dev_flags |= DF_SPC2_RESERVATIONS_WITH_ISID; - } - printk(KERN_INFO "SCSI-2 Reserved %s LUN: %u -> MAPPED LUN: %u" - " for %s\n", TPG_TFO(tpg)->get_fabric_name(), - SE_LUN(cmd)->unpacked_lun, cmd->se_deve->mapped_lun, - sess->se_node_acl->initiatorname); - spin_unlock(&dev->dev_reservation_lock); return 0; } @@ -205,27 +111,14 @@ static struct t10_pr_registration *core_scsi3_locate_pr_reg(struct se_device *, struct se_node_acl *, struct se_session *); static void core_scsi3_put_pr_reg(struct t10_pr_registration *); -/* - * Setup in target_core_transport.c:transport_generic_cmd_sequencer() - * and called via struct se_cmd->transport_emulate_cdb() in TCM processing - * thread context. - */ -int core_scsi2_emulate_crh(struct se_cmd *cmd) +static int target_check_scsi2_reservation_conflict(struct se_cmd *cmd) { struct se_session *se_sess = cmd->se_sess; - struct se_subsystem_dev *su_dev = cmd->se_dev->se_sub_dev; + struct se_device *dev = cmd->se_dev; struct t10_pr_registration *pr_reg; - struct t10_reservation_template *pr_tmpl = &su_dev->t10_reservation; - unsigned char *cdb = &T_TASK(cmd)->t_task_cdb[0]; - int crh = (T10_RES(su_dev)->res_type == SPC3_PERSISTENT_RESERVATIONS); + struct t10_reservation *pr_tmpl = &dev->t10_pr; int conflict = 0; - if (!(se_sess)) - return 0; - - if (!(crh)) - goto after_crh; - pr_reg = core_scsi3_locate_pr_reg(cmd->se_dev, se_sess->se_node_acl, se_sess); if (pr_reg) { @@ -252,14 +145,14 @@ int core_scsi2_emulate_crh(struct se_cmd *cmd) */ if (pr_reg->pr_res_holder) { core_scsi3_put_pr_reg(pr_reg); - return 0; + return 1; } if ((pr_reg->pr_res_type == PR_TYPE_WRITE_EXCLUSIVE_REGONLY) || (pr_reg->pr_res_type == PR_TYPE_EXCLUSIVE_ACCESS_REGONLY) || (pr_reg->pr_res_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG) || (pr_reg->pr_res_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG)) { core_scsi3_put_pr_reg(pr_reg); - return 0; + return 1; } core_scsi3_put_pr_reg(pr_reg); conflict = 1; @@ -280,21 +173,125 @@ int core_scsi2_emulate_crh(struct se_cmd *cmd) } if (conflict) { - printk(KERN_ERR "Received legacy SPC-2 RESERVE/RELEASE" + pr_err("Received legacy SPC-2 RESERVE/RELEASE" " while active SPC-3 registrations exist," " returning RESERVATION_CONFLICT\n"); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + return -EBUSY; } -after_crh: - if ((cdb[0] == RESERVE) || (cdb[0] == RESERVE_10)) - return core_scsi2_reservation_reserve(cmd); - else if ((cdb[0] == RELEASE) || (cdb[0] == RELEASE_10)) - return core_scsi2_reservation_release(cmd); - else - return PYX_TRANSPORT_INVALID_CDB_FIELD; + return 0; +} + +sense_reason_t +target_scsi2_reservation_release(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct se_session *sess = cmd->se_sess; + struct se_portal_group *tpg; + int rc; + + if (!sess || !sess->se_tpg) + goto out; + rc = target_check_scsi2_reservation_conflict(cmd); + if (rc == 1) + goto out; + if (rc < 0) + return TCM_RESERVATION_CONFLICT; + + spin_lock(&dev->dev_reservation_lock); + if (!dev->dev_reserved_node_acl || !sess) + goto out_unlock; + + if (dev->dev_reserved_node_acl != sess->se_node_acl) + goto out_unlock; + + if (dev->dev_res_bin_isid != sess->sess_bin_isid) + goto out_unlock; + + dev->dev_reserved_node_acl = NULL; + dev->dev_reservation_flags &= ~DRF_SPC2_RESERVATIONS; + if (dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS_WITH_ISID) { + dev->dev_res_bin_isid = 0; + dev->dev_reservation_flags &= ~DRF_SPC2_RESERVATIONS_WITH_ISID; + } + tpg = sess->se_tpg; + pr_debug("SCSI-2 Released reservation for %s LUN: %u ->" + " MAPPED LUN: %u for %s\n", tpg->se_tpg_tfo->get_fabric_name(), + cmd->se_lun->unpacked_lun, cmd->se_deve->mapped_lun, + sess->se_node_acl->initiatorname); + +out_unlock: + spin_unlock(&dev->dev_reservation_lock); +out: + target_complete_cmd(cmd, GOOD); + return 0; +} + +sense_reason_t +target_scsi2_reservation_reserve(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct se_session *sess = cmd->se_sess; + struct se_portal_group *tpg; + sense_reason_t ret = 0; + int rc; + + if ((cmd->t_task_cdb[1] & 0x01) && + (cmd->t_task_cdb[1] & 0x02)) { + pr_err("LongIO and Obselete Bits set, returning" + " ILLEGAL_REQUEST\n"); + return TCM_UNSUPPORTED_SCSI_OPCODE; + } + /* + * This is currently the case for target_core_mod passthrough struct se_cmd + * ops + */ + if (!sess || !sess->se_tpg) + goto out; + rc = target_check_scsi2_reservation_conflict(cmd); + if (rc == 1) + goto out; + + if (rc < 0) + return TCM_RESERVATION_CONFLICT; + + tpg = sess->se_tpg; + spin_lock(&dev->dev_reservation_lock); + if (dev->dev_reserved_node_acl && + (dev->dev_reserved_node_acl != sess->se_node_acl)) { + pr_err("SCSI-2 RESERVATION CONFLIFT for %s fabric\n", + tpg->se_tpg_tfo->get_fabric_name()); + pr_err("Original reserver LUN: %u %s\n", + cmd->se_lun->unpacked_lun, + dev->dev_reserved_node_acl->initiatorname); + pr_err("Current attempt - LUN: %u -> MAPPED LUN: %u" + " from %s \n", cmd->se_lun->unpacked_lun, + cmd->se_deve->mapped_lun, + sess->se_node_acl->initiatorname); + ret = TCM_RESERVATION_CONFLICT; + goto out_unlock; + } + + dev->dev_reserved_node_acl = sess->se_node_acl; + dev->dev_reservation_flags |= DRF_SPC2_RESERVATIONS; + if (sess->sess_bin_isid != 0) { + dev->dev_res_bin_isid = sess->sess_bin_isid; + dev->dev_reservation_flags |= DRF_SPC2_RESERVATIONS_WITH_ISID; + } + pr_debug("SCSI-2 Reserved %s LUN: %u -> MAPPED LUN: %u" + " for %s\n", tpg->se_tpg_tfo->get_fabric_name(), + cmd->se_lun->unpacked_lun, cmd->se_deve->mapped_lun, + sess->se_node_acl->initiatorname); + +out_unlock: + spin_unlock(&dev->dev_reservation_lock); +out: + if (!ret) + target_complete_cmd(cmd, GOOD); + return ret; } + /* * Begin SPC-3/SPC-4 Persistent Reservations emulation support * @@ -303,28 +300,22 @@ after_crh: */ static int core_scsi3_pr_seq_non_holder( struct se_cmd *cmd, - unsigned char *cdb, u32 pr_reg_type) { + unsigned char *cdb = cmd->t_task_cdb; struct se_dev_entry *se_deve; - struct se_session *se_sess = SE_SESS(cmd); + struct se_session *se_sess = cmd->se_sess; int other_cdb = 0, ignore_reg; int registered_nexus = 0, ret = 1; /* Conflict by default */ int all_reg = 0, reg_only = 0; /* ALL_REG, REG_ONLY */ int we = 0; /* Write Exclusive */ int legacy = 0; /* Act like a legacy device and return * RESERVATION CONFLICT on some CDBs */ - /* - * A legacy SPC-2 reservation is being held. - */ - if (cmd->se_dev->dev_flags & DF_SPC2_RESERVATIONS) - return core_scsi2_reservation_seq_non_holder(cmd, - cdb, pr_reg_type); - se_deve = &se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; + se_deve = se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; /* * Determine if the registration should be ignored due to - * non-matching ISIDs in core_scsi3_pr_reservation_check(). + * non-matching ISIDs in target_scsi3_pr_reservation_check(). */ ignore_reg = (pr_reg_type & 0x80000000); if (ignore_reg) @@ -362,7 +353,7 @@ static int core_scsi3_pr_seq_non_holder( registered_nexus = 1; break; default: - return -1; + return -EINVAL; } /* * Referenced from spc4r17 table 45 for *NON* PR holder access @@ -412,19 +403,19 @@ static int core_scsi3_pr_seq_non_holder( ret = (registered_nexus) ? 0 : 1; break; default: - printk(KERN_ERR "Unknown PERSISTENT_RESERVE_OUT service" + pr_err("Unknown PERSISTENT_RESERVE_OUT service" " action: 0x%02x\n", cdb[1] & 0x1f); - return -1; + return -EINVAL; } break; case RELEASE: case RELEASE_10: - /* Handled by CRH=1 in core_scsi2_emulate_crh() */ + /* Handled by CRH=1 in target_scsi2_reservation_release() */ ret = 0; break; case RESERVE: case RESERVE_10: - /* Handled by CRH=1 in core_scsi2_emulate_crh() */ + /* Handled by CRH=1 in target_scsi2_reservation_reserve() */ ret = 0; break; case TEST_UNIT_READY: @@ -459,9 +450,9 @@ static int core_scsi3_pr_seq_non_holder( ret = 0; /* Allowed */ break; default: - printk(KERN_ERR "Unknown MI Service Action: 0x%02x\n", + pr_err("Unknown MI Service Action: 0x%02x\n", (cdb[1] & 0x1f)); - return -1; + return -EINVAL; } break; case ACCESS_CONTROL_IN: @@ -471,6 +462,7 @@ static int core_scsi3_pr_seq_non_holder( case READ_MEDIA_SERIAL_NUMBER: case REPORT_LUNS: case REQUEST_SENSE: + case PERSISTENT_RESERVE_IN: ret = 0; /*/ Allowed CDBs */ break; default: @@ -478,27 +470,26 @@ static int core_scsi3_pr_seq_non_holder( break; } /* - * Case where the CDB is explictly allowed in the above switch + * Case where the CDB is explicitly allowed in the above switch * statement. */ - if (!(ret) && !(other_cdb)) { -#if 0 - printk(KERN_INFO "Allowing explict CDB: 0x%02x for %s" + if (!ret && !other_cdb) { + pr_debug("Allowing explicit CDB: 0x%02x for %s" " reservation holder\n", cdb[0], core_scsi3_pr_dump_type(pr_reg_type)); -#endif + return ret; } /* * Check if write exclusive initiator ports *NOT* holding the * WRITE_EXCLUSIVE_* reservation. */ - if ((we) && !(registered_nexus)) { + if (we && !registered_nexus) { if (cmd->data_direction == DMA_TO_DEVICE) { /* * Conflict for write exclusive */ - printk(KERN_INFO "%s Conflict for unregistered nexus" + pr_debug("%s Conflict for unregistered nexus" " %s CDB: 0x%02x to %s reservation\n", transport_dump_cmd_direction(cmd), se_sess->se_node_acl->initiatorname, cdb[0], @@ -514,14 +505,14 @@ static int core_scsi3_pr_seq_non_holder( * as we expect registered non-reservation holding * nexuses to issue CDBs. */ -#if 0 - if (!(registered_nexus)) { - printk(KERN_INFO "Allowing implict CDB: 0x%02x" + + if (!registered_nexus) { + pr_debug("Allowing implicit CDB: 0x%02x" " for %s reservation on unregistered" " nexus\n", cdb[0], core_scsi3_pr_dump_type(pr_reg_type)); } -#endif + return 0; } } else if ((reg_only) || (all_reg)) { @@ -530,15 +521,15 @@ static int core_scsi3_pr_seq_non_holder( * For PR_*_REG_ONLY and PR_*_ALL_REG reservations, * allow commands from registered nexuses. */ -#if 0 - printk(KERN_INFO "Allowing implict CDB: 0x%02x for %s" + + pr_debug("Allowing implicit CDB: 0x%02x for %s" " reservation\n", cdb[0], core_scsi3_pr_dump_type(pr_reg_type)); -#endif + return 0; } } - printk(KERN_INFO "%s Conflict for %sregistered nexus %s CDB: 0x%2x" + pr_debug("%s Conflict for %sregistered nexus %s CDB: 0x%2x" " for %s reservation\n", transport_dump_cmd_direction(cmd), (registered_nexus) ? "" : "un", se_sess->se_node_acl->initiatorname, cdb[0], @@ -547,10 +538,41 @@ static int core_scsi3_pr_seq_non_holder( return 1; /* Conflict by default */ } +static sense_reason_t +target_scsi3_pr_reservation_check(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct se_session *sess = cmd->se_sess; + u32 pr_reg_type; + + if (!dev->dev_pr_res_holder) + return 0; + + pr_reg_type = dev->dev_pr_res_holder->pr_res_type; + cmd->pr_res_key = dev->dev_pr_res_holder->pr_res_key; + if (dev->dev_pr_res_holder->pr_reg_nacl != sess->se_node_acl) + goto check_nonholder; + + if (dev->dev_pr_res_holder->isid_present_at_reg) { + if (dev->dev_pr_res_holder->pr_reg_bin_isid != + sess->sess_bin_isid) { + pr_reg_type |= 0x80000000; + goto check_nonholder; + } + } + + return 0; + +check_nonholder: + if (core_scsi3_pr_seq_non_holder(cmd, pr_reg_type)) + return TCM_RESERVATION_CONFLICT; + return 0; +} + static u32 core_scsi3_pr_generation(struct se_device *dev) { - struct se_subsystem_dev *su_dev = SU_DEV(dev); u32 prg; + /* * PRGeneration field shall contain the value of a 32-bit wrapping * counter mainted by the device server. @@ -561,56 +583,12 @@ static u32 core_scsi3_pr_generation(struct se_device *dev) * See spc4r17 section 6.3.12 READ_KEYS service action */ spin_lock(&dev->dev_reservation_lock); - prg = T10_RES(su_dev)->pr_generation++; + prg = dev->t10_pr.pr_generation++; spin_unlock(&dev->dev_reservation_lock); return prg; } -static int core_scsi3_pr_reservation_check( - struct se_cmd *cmd, - u32 *pr_reg_type) -{ - struct se_device *dev = cmd->se_dev; - struct se_session *sess = cmd->se_sess; - int ret; - - if (!(sess)) - return 0; - /* - * A legacy SPC-2 reservation is being held. - */ - if (dev->dev_flags & DF_SPC2_RESERVATIONS) - return core_scsi2_reservation_check(cmd, pr_reg_type); - - spin_lock(&dev->dev_reservation_lock); - if (!(dev->dev_pr_res_holder)) { - spin_unlock(&dev->dev_reservation_lock); - return 0; - } - *pr_reg_type = dev->dev_pr_res_holder->pr_res_type; - cmd->pr_res_key = dev->dev_pr_res_holder->pr_res_key; - if (dev->dev_pr_res_holder->pr_reg_nacl != sess->se_node_acl) { - spin_unlock(&dev->dev_reservation_lock); - return -1; - } - if (!(dev->dev_pr_res_holder->isid_present_at_reg)) { - spin_unlock(&dev->dev_reservation_lock); - return 0; - } - ret = (dev->dev_pr_res_holder->pr_reg_bin_isid == - sess->sess_bin_isid) ? 0 : -1; - /* - * Use bit in *pr_reg_type to notify ISID mismatch in - * core_scsi3_pr_seq_non_holder(). - */ - if (ret != 0) - *pr_reg_type |= 0x80000000; - spin_unlock(&dev->dev_reservation_lock); - - return ret; -} - static struct t10_pr_registration *__core_scsi3_do_alloc_registration( struct se_device *dev, struct se_node_acl *nacl, @@ -620,20 +598,11 @@ static struct t10_pr_registration *__core_scsi3_do_alloc_registration( int all_tg_pt, int aptpl) { - struct se_subsystem_dev *su_dev = SU_DEV(dev); struct t10_pr_registration *pr_reg; pr_reg = kmem_cache_zalloc(t10_pr_reg_cache, GFP_ATOMIC); - if (!(pr_reg)) { - printk(KERN_ERR "Unable to allocate struct t10_pr_registration\n"); - return NULL; - } - - pr_reg->pr_aptpl_buf = kzalloc(T10_RES(su_dev)->pr_aptpl_buf_len, - GFP_ATOMIC); - if (!(pr_reg->pr_aptpl_buf)) { - printk(KERN_ERR "Unable to allocate pr_reg->pr_aptpl_buf\n"); - kmem_cache_free(t10_pr_reg_cache, pr_reg); + if (!pr_reg) { + pr_err("Unable to allocate struct t10_pr_registration\n"); return NULL; } @@ -692,12 +661,12 @@ static struct t10_pr_registration *__core_scsi3_alloc_registration( */ pr_reg = __core_scsi3_do_alloc_registration(dev, nacl, deve, isid, sa_res_key, all_tg_pt, aptpl); - if (!(pr_reg)) + if (!pr_reg) return NULL; /* * Return pointer to pr_reg for ALL_TG_PT=0 */ - if (!(all_tg_pt)) + if (!all_tg_pt) return pr_reg; /* * Create list of matching SCSI Initiator Port registrations @@ -706,7 +675,7 @@ static struct t10_pr_registration *__core_scsi3_alloc_registration( spin_lock(&dev->se_port_lock); list_for_each_entry_safe(port, port_tmp, &dev->dev_sep_list, sep_list) { atomic_inc(&port->sep_tg_pt_ref_cnt); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&dev->se_port_lock); spin_lock_bh(&port->sep_alua_lock); @@ -714,10 +683,10 @@ static struct t10_pr_registration *__core_scsi3_alloc_registration( alua_port_list) { /* * This pointer will be NULL for demo mode MappedLUNs - * that have not been make explict via a ConfigFS + * that have not been make explicit via a ConfigFS * MappedLUN group for the SCSI Initiator Node ACL. */ - if (!(deve_tmp->se_lun_acl)) + if (!deve_tmp->se_lun_acl) continue; nacl_tmp = deve_tmp->se_lun_acl->se_lun_nacl; @@ -741,7 +710,7 @@ static struct t10_pr_registration *__core_scsi3_alloc_registration( continue; atomic_inc(&deve_tmp->pr_ref_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock_bh(&port->sep_alua_lock); /* * Grab a configfs group dependency that is released @@ -751,12 +720,12 @@ static struct t10_pr_registration *__core_scsi3_alloc_registration( */ ret = core_scsi3_lunacl_depend_item(deve_tmp); if (ret < 0) { - printk(KERN_ERR "core_scsi3_lunacl_depend" + pr_err("core_scsi3_lunacl_depend" "_item() failed\n"); atomic_dec(&port->sep_tg_pt_ref_cnt); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); atomic_dec(&deve_tmp->pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); goto out; } /* @@ -769,11 +738,11 @@ static struct t10_pr_registration *__core_scsi3_alloc_registration( pr_reg_atp = __core_scsi3_do_alloc_registration(dev, nacl_tmp, deve_tmp, NULL, sa_res_key, all_tg_pt, aptpl); - if (!(pr_reg_atp)) { + if (!pr_reg_atp) { atomic_dec(&port->sep_tg_pt_ref_cnt); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); atomic_dec(&deve_tmp->pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); core_scsi3_lunacl_undepend_item(deve_tmp); goto out; } @@ -786,7 +755,7 @@ static struct t10_pr_registration *__core_scsi3_alloc_registration( spin_lock(&dev->se_port_lock); atomic_dec(&port->sep_tg_pt_ref_cnt); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } spin_unlock(&dev->se_port_lock); @@ -803,7 +772,7 @@ out: } int core_scsi3_alloc_aptpl_registration( - struct t10_reservation_template *pr_tmpl, + struct t10_reservation *pr_tmpl, u64 sa_res_key, unsigned char *i_port, unsigned char *isid, @@ -817,17 +786,16 @@ int core_scsi3_alloc_aptpl_registration( { struct t10_pr_registration *pr_reg; - if (!(i_port) || !(t_port) || !(sa_res_key)) { - printk(KERN_ERR "Illegal parameters for APTPL registration\n"); - return -1; + if (!i_port || !t_port || !sa_res_key) { + pr_err("Illegal parameters for APTPL registration\n"); + return -EINVAL; } pr_reg = kmem_cache_zalloc(t10_pr_reg_cache, GFP_KERNEL); - if (!(pr_reg)) { - printk(KERN_ERR "Unable to allocate struct t10_pr_registration\n"); - return -1; + if (!pr_reg) { + pr_err("Unable to allocate struct t10_pr_registration\n"); + return -ENOMEM; } - pr_reg->pr_aptpl_buf = kzalloc(pr_tmpl->pr_aptpl_buf_len, GFP_KERNEL); INIT_LIST_HEAD(&pr_reg->pr_reg_list); INIT_LIST_HEAD(&pr_reg->pr_reg_abort_list); @@ -869,7 +837,7 @@ int core_scsi3_alloc_aptpl_registration( pr_reg->pr_res_holder = res_holder; list_add_tail(&pr_reg->pr_reg_aptpl_list, &pr_tmpl->aptpl_reg_list); - printk(KERN_INFO "SPC-3 PR APTPL Successfully added registration%s from" + pr_debug("SPC-3 PR APTPL Successfully added registration%s from" " metadata\n", (res_holder) ? "+reservation" : ""); return 0; } @@ -881,28 +849,26 @@ static void core_scsi3_aptpl_reserve( struct t10_pr_registration *pr_reg) { char i_buf[PR_REG_ISID_ID_LEN]; - int prf_isid; memset(i_buf, 0, PR_REG_ISID_ID_LEN); - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); spin_lock(&dev->dev_reservation_lock); dev->dev_pr_res_holder = pr_reg; spin_unlock(&dev->dev_reservation_lock); - printk(KERN_INFO "SPC-3 PR [%s] Service Action: APTPL RESERVE created" + pr_debug("SPC-3 PR [%s] Service Action: APTPL RESERVE created" " new reservation holder TYPE: %s ALL_TG_PT: %d\n", - TPG_TFO(tpg)->get_fabric_name(), + tpg->se_tpg_tfo->get_fabric_name(), core_scsi3_pr_dump_type(pr_reg->pr_res_type), (pr_reg->pr_reg_all_tg_pt) ? 1 : 0); - printk(KERN_INFO "SPC-3 PR [%s] RESERVE Node: %s%s\n", - TPG_TFO(tpg)->get_fabric_name(), node_acl->initiatorname, - (prf_isid) ? &i_buf[0] : ""); + pr_debug("SPC-3 PR [%s] RESERVE Node: %s%s\n", + tpg->se_tpg_tfo->get_fabric_name(), node_acl->initiatorname, + i_buf); } static void __core_scsi3_add_registration(struct se_device *, struct se_node_acl *, - struct t10_pr_registration *, int, int); + struct t10_pr_registration *, enum register_type, int); static int __core_scsi3_check_aptpl_registration( struct se_device *dev, @@ -913,7 +879,7 @@ static int __core_scsi3_check_aptpl_registration( struct se_dev_entry *deve) { struct t10_pr_registration *pr_reg, *pr_reg_tmp; - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct t10_reservation *pr_tmpl = &dev->t10_pr; unsigned char i_port[PR_APTPL_MAX_IPORT_LEN]; unsigned char t_port[PR_APTPL_MAX_TPORT_LEN]; u16 tpgt; @@ -925,8 +891,8 @@ static int __core_scsi3_check_aptpl_registration( */ snprintf(i_port, PR_APTPL_MAX_IPORT_LEN, "%s", nacl->initiatorname); snprintf(t_port, PR_APTPL_MAX_TPORT_LEN, "%s", - TPG_TFO(tpg)->tpg_get_wwn(tpg)); - tpgt = TPG_TFO(tpg)->tpg_get_tag(tpg); + tpg->se_tpg_tfo->tpg_get_wwn(tpg)); + tpgt = tpg->se_tpg_tfo->tpg_get_tag(tpg); /* * Look for the matching registrations+reservation from those * created from APTPL metadata. Note that multiple registrations @@ -936,7 +902,7 @@ static int __core_scsi3_check_aptpl_registration( spin_lock(&pr_tmpl->aptpl_reg_lock); list_for_each_entry_safe(pr_reg, pr_reg_tmp, &pr_tmpl->aptpl_reg_list, pr_reg_aptpl_list) { - if (!(strcmp(pr_reg->pr_iport, i_port)) && + if (!strcmp(pr_reg->pr_iport, i_port) && (pr_reg->pr_res_mapped_lun == deve->mapped_lun) && !(strcmp(pr_reg->pr_tport, t_port)) && (pr_reg->pr_reg_tpgt == tpgt) && @@ -980,11 +946,10 @@ int core_scsi3_check_aptpl_registration( struct se_lun *lun, struct se_lun_acl *lun_acl) { - struct se_subsystem_dev *su_dev = SU_DEV(dev); struct se_node_acl *nacl = lun_acl->se_lun_nacl; - struct se_dev_entry *deve = &nacl->device_list[lun_acl->mapped_lun]; + struct se_dev_entry *deve = nacl->device_list[lun_acl->mapped_lun]; - if (T10_RES(su_dev)->res_type != SPC3_PERSISTENT_RESERVATIONS) + if (dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS) return 0; return __core_scsi3_check_aptpl_registration(dev, tpg, lun, @@ -996,29 +961,27 @@ static void __core_scsi3_dump_registration( struct se_device *dev, struct se_node_acl *nacl, struct t10_pr_registration *pr_reg, - int register_type) + enum register_type register_type) { struct se_portal_group *se_tpg = nacl->se_tpg; char i_buf[PR_REG_ISID_ID_LEN]; - int prf_isid; memset(&i_buf[0], 0, PR_REG_ISID_ID_LEN); - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); - printk(KERN_INFO "SPC-3 PR [%s] Service Action: REGISTER%s Initiator" - " Node: %s%s\n", tfo->get_fabric_name(), (register_type == 2) ? - "_AND_MOVE" : (register_type == 1) ? + pr_debug("SPC-3 PR [%s] Service Action: REGISTER%s Initiator" + " Node: %s%s\n", tfo->get_fabric_name(), (register_type == REGISTER_AND_MOVE) ? + "_AND_MOVE" : (register_type == REGISTER_AND_IGNORE_EXISTING_KEY) ? "_AND_IGNORE_EXISTING_KEY" : "", nacl->initiatorname, - (prf_isid) ? i_buf : ""); - printk(KERN_INFO "SPC-3 PR [%s] registration on Target Port: %s,0x%04x\n", + i_buf); + pr_debug("SPC-3 PR [%s] registration on Target Port: %s,0x%04x\n", tfo->get_fabric_name(), tfo->tpg_get_wwn(se_tpg), tfo->tpg_get_tag(se_tpg)); - printk(KERN_INFO "SPC-3 PR [%s] for %s TCM Subsystem %s Object Target" + pr_debug("SPC-3 PR [%s] for %s TCM Subsystem %s Object Target" " Port(s)\n", tfo->get_fabric_name(), (pr_reg->pr_reg_all_tg_pt) ? "ALL" : "SINGLE", - TRANSPORT(dev)->name); - printk(KERN_INFO "SPC-3 PR [%s] SA Res Key: 0x%016Lx PRgeneration:" + dev->transport->name); + pr_debug("SPC-3 PR [%s] SA Res Key: 0x%016Lx PRgeneration:" " 0x%08x APTPL: %d\n", tfo->get_fabric_name(), pr_reg->pr_res_key, pr_reg->pr_res_generation, pr_reg->pr_reg_aptpl); @@ -1032,13 +995,12 @@ static void __core_scsi3_add_registration( struct se_device *dev, struct se_node_acl *nacl, struct t10_pr_registration *pr_reg, - int register_type, + enum register_type register_type, int register_move) { - struct se_subsystem_dev *su_dev = SU_DEV(dev); struct target_core_fabric_ops *tfo = nacl->se_tpg->se_tpg_tfo; struct t10_pr_registration *pr_reg_tmp, *pr_reg_tmp_safe; - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct t10_reservation *pr_tmpl = &dev->t10_pr; /* * Increment PRgeneration counter for struct se_device upon a successful @@ -1050,7 +1012,7 @@ static void __core_scsi3_add_registration( * for the REGISTER. */ pr_reg->pr_res_generation = (register_move) ? - T10_RES(su_dev)->pr_generation++ : + dev->t10_pr.pr_generation++ : core_scsi3_pr_generation(dev); spin_lock(&pr_tmpl->registration_lock); @@ -1062,7 +1024,7 @@ static void __core_scsi3_add_registration( /* * Skip extra processing for ALL_TG_PT=0 or REGISTER_AND_MOVE. */ - if (!(pr_reg->pr_reg_all_tg_pt) || (register_move)) + if (!pr_reg->pr_reg_all_tg_pt || register_move) return; /* * Walk pr_reg->pr_reg_atp_list and add registrations for ALL_TG_PT=1 @@ -1099,15 +1061,15 @@ static int core_scsi3_alloc_registration( u64 sa_res_key, int all_tg_pt, int aptpl, - int register_type, + enum register_type register_type, int register_move) { struct t10_pr_registration *pr_reg; pr_reg = __core_scsi3_alloc_registration(dev, nacl, deve, isid, sa_res_key, all_tg_pt, aptpl); - if (!(pr_reg)) - return -1; + if (!pr_reg) + return -EPERM; __core_scsi3_add_registration(dev, nacl, pr_reg, register_type, register_move); @@ -1119,7 +1081,7 @@ static struct t10_pr_registration *__core_scsi3_locate_pr_reg( struct se_node_acl *nacl, unsigned char *isid) { - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct t10_reservation *pr_tmpl = &dev->t10_pr; struct t10_pr_registration *pr_reg, *pr_reg_tmp; struct se_portal_group *tpg; @@ -1137,18 +1099,18 @@ static struct t10_pr_registration *__core_scsi3_locate_pr_reg( * If this registration does NOT contain a fabric provided * ISID, then we have found a match. */ - if (!(pr_reg->isid_present_at_reg)) { + if (!pr_reg->isid_present_at_reg) { /* * Determine if this SCSI device server requires that * SCSI Intiatior TransportID w/ ISIDs is enforced * for fabric modules (iSCSI) requiring them. */ - if (TPG_TFO(tpg)->sess_get_initiator_sid != NULL) { - if (DEV_ATTRIB(dev)->enforce_pr_isids) + if (tpg->se_tpg_tfo->sess_get_initiator_sid != NULL) { + if (dev->dev_attrib.enforce_pr_isids) continue; } atomic_inc(&pr_reg->pr_res_holders); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&pr_tmpl->registration_lock); return pr_reg; } @@ -1157,13 +1119,13 @@ static struct t10_pr_registration *__core_scsi3_locate_pr_reg( * SCSI Initiator Port TransportIDs, then we expect a valid * matching ISID to be provided by the local SCSI Initiator Port. */ - if (!(isid)) + if (!isid) continue; if (strcmp(isid, pr_reg->pr_reg_isid)) continue; atomic_inc(&pr_reg->pr_res_holders); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&pr_tmpl->registration_lock); return pr_reg; } @@ -1180,9 +1142,9 @@ static struct t10_pr_registration *core_scsi3_locate_pr_reg( struct se_portal_group *tpg = nacl->se_tpg; unsigned char buf[PR_REG_ISID_LEN], *isid_ptr = NULL; - if (TPG_TFO(tpg)->sess_get_initiator_sid != NULL) { + if (tpg->se_tpg_tfo->sess_get_initiator_sid != NULL) { memset(&buf[0], 0, PR_REG_ISID_LEN); - TPG_TFO(tpg)->sess_get_initiator_sid(sess, &buf[0], + tpg->se_tpg_tfo->sess_get_initiator_sid(sess, &buf[0], PR_REG_ISID_LEN); isid_ptr = &buf[0]; } @@ -1193,10 +1155,10 @@ static struct t10_pr_registration *core_scsi3_locate_pr_reg( static void core_scsi3_put_pr_reg(struct t10_pr_registration *pr_reg) { atomic_dec(&pr_reg->pr_res_holders); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } -static int core_scsi3_check_implict_release( +static int core_scsi3_check_implicit_release( struct se_device *dev, struct t10_pr_registration *pr_reg) { @@ -1206,13 +1168,13 @@ static int core_scsi3_check_implict_release( spin_lock(&dev->dev_reservation_lock); pr_res_holder = dev->dev_pr_res_holder; - if (!(pr_res_holder)) { + if (!pr_res_holder) { spin_unlock(&dev->dev_reservation_lock); return ret; } if (pr_res_holder == pr_reg) { /* - * Perform an implict RELEASE if the registration that + * Perform an implicit RELEASE if the registration that * is being released is holding the reservation. * * From spc4r17, section 5.7.11.1: @@ -1230,17 +1192,17 @@ static int core_scsi3_check_implict_release( * For 'All Registrants' reservation types, all existing * registrations are still processed as reservation holders * in core_scsi3_pr_seq_non_holder() after the initial - * reservation holder is implictly released here. + * reservation holder is implicitly released here. */ } else if (pr_reg->pr_reg_all_tg_pt && (!strcmp(pr_res_holder->pr_reg_nacl->initiatorname, pr_reg->pr_reg_nacl->initiatorname)) && (pr_res_holder->pr_res_key == pr_reg->pr_res_key)) { - printk(KERN_ERR "SPC-3 PR: Unable to perform ALL_TG_PT=1" + pr_err("SPC-3 PR: Unable to perform ALL_TG_PT=1" " UNREGISTER while existing reservation with matching" " key 0x%016Lx is present from another SCSI Initiator" " Port\n", pr_reg->pr_res_key); - ret = -1; + ret = -EPERM; } spin_unlock(&dev->dev_reservation_lock); @@ -1248,7 +1210,7 @@ static int core_scsi3_check_implict_release( } /* - * Called with struct t10_reservation_template->registration_lock held. + * Called with struct t10_reservation->registration_lock held. */ static void __core_scsi3_free_registration( struct se_device *dev, @@ -1258,13 +1220,11 @@ static void __core_scsi3_free_registration( { struct target_core_fabric_ops *tfo = pr_reg->pr_reg_nacl->se_tpg->se_tpg_tfo; - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct t10_reservation *pr_tmpl = &dev->t10_pr; char i_buf[PR_REG_ISID_ID_LEN]; - int prf_isid; memset(i_buf, 0, PR_REG_ISID_ID_LEN); - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); pr_reg->pr_reg_deve->def_pr_registered = 0; pr_reg->pr_reg_deve->pr_res_key = 0; @@ -1283,28 +1243,27 @@ static void __core_scsi3_free_registration( */ while (atomic_read(&pr_reg->pr_res_holders) != 0) { spin_unlock(&pr_tmpl->registration_lock); - printk("SPC-3 PR [%s] waiting for pr_res_holders\n", + pr_debug("SPC-3 PR [%s] waiting for pr_res_holders\n", tfo->get_fabric_name()); cpu_relax(); spin_lock(&pr_tmpl->registration_lock); } - printk(KERN_INFO "SPC-3 PR [%s] Service Action: UNREGISTER Initiator" + pr_debug("SPC-3 PR [%s] Service Action: UNREGISTER Initiator" " Node: %s%s\n", tfo->get_fabric_name(), pr_reg->pr_reg_nacl->initiatorname, - (prf_isid) ? &i_buf[0] : ""); - printk(KERN_INFO "SPC-3 PR [%s] for %s TCM Subsystem %s Object Target" + i_buf); + pr_debug("SPC-3 PR [%s] for %s TCM Subsystem %s Object Target" " Port(s)\n", tfo->get_fabric_name(), (pr_reg->pr_reg_all_tg_pt) ? "ALL" : "SINGLE", - TRANSPORT(dev)->name); - printk(KERN_INFO "SPC-3 PR [%s] SA Res Key: 0x%016Lx PRgeneration:" + dev->transport->name); + pr_debug("SPC-3 PR [%s] SA Res Key: 0x%016Lx PRgeneration:" " 0x%08x\n", tfo->get_fabric_name(), pr_reg->pr_res_key, pr_reg->pr_res_generation); - if (!(preempt_and_abort_list)) { + if (!preempt_and_abort_list) { pr_reg->pr_reg_deve = NULL; pr_reg->pr_reg_nacl = NULL; - kfree(pr_reg->pr_aptpl_buf); kmem_cache_free(t10_pr_reg_cache, pr_reg); return; } @@ -1319,7 +1278,7 @@ void core_scsi3_free_pr_reg_from_nacl( struct se_device *dev, struct se_node_acl *nacl) { - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct t10_reservation *pr_tmpl = &dev->t10_pr; struct t10_pr_registration *pr_reg, *pr_reg_tmp, *pr_res_holder; /* * If the passed se_node_acl matches the reservation holder, @@ -1349,7 +1308,7 @@ void core_scsi3_free_pr_reg_from_nacl( void core_scsi3_free_all_registrations( struct se_device *dev) { - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct t10_reservation *pr_tmpl = &dev->t10_pr; struct t10_pr_registration *pr_reg, *pr_reg_tmp, *pr_res_holder; spin_lock(&dev->dev_reservation_lock); @@ -1373,7 +1332,6 @@ void core_scsi3_free_all_registrations( list_for_each_entry_safe(pr_reg, pr_reg_tmp, &pr_tmpl->aptpl_reg_list, pr_reg_aptpl_list) { list_del(&pr_reg->pr_reg_aptpl_list); - kfree(pr_reg->pr_aptpl_buf); kmem_cache_free(t10_pr_reg_cache, pr_reg); } spin_unlock(&pr_tmpl->aptpl_reg_lock); @@ -1381,17 +1339,17 @@ void core_scsi3_free_all_registrations( static int core_scsi3_tpg_depend_item(struct se_portal_group *tpg) { - return configfs_depend_item(TPG_TFO(tpg)->tf_subsys, + return configfs_depend_item(tpg->se_tpg_tfo->tf_subsys, &tpg->tpg_group.cg_item); } static void core_scsi3_tpg_undepend_item(struct se_portal_group *tpg) { - configfs_undepend_item(TPG_TFO(tpg)->tf_subsys, + configfs_undepend_item(tpg->se_tpg_tfo->tf_subsys, &tpg->tpg_group.cg_item); atomic_dec(&tpg->tpg_pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } static int core_scsi3_nodeacl_depend_item(struct se_node_acl *nacl) @@ -1401,7 +1359,7 @@ static int core_scsi3_nodeacl_depend_item(struct se_node_acl *nacl) if (nacl->dynamic_node_acl) return 0; - return configfs_depend_item(TPG_TFO(tpg)->tf_subsys, + return configfs_depend_item(tpg->se_tpg_tfo->tf_subsys, &nacl->acl_group.cg_item); } @@ -1411,15 +1369,15 @@ static void core_scsi3_nodeacl_undepend_item(struct se_node_acl *nacl) if (nacl->dynamic_node_acl) { atomic_dec(&nacl->acl_pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); return; } - configfs_undepend_item(TPG_TFO(tpg)->tf_subsys, + configfs_undepend_item(tpg->se_tpg_tfo->tf_subsys, &nacl->acl_group.cg_item); atomic_dec(&nacl->acl_pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } static int core_scsi3_lunacl_depend_item(struct se_dev_entry *se_deve) @@ -1430,13 +1388,13 @@ static int core_scsi3_lunacl_depend_item(struct se_dev_entry *se_deve) /* * For nacl->dynamic_node_acl=1 */ - if (!(lun_acl)) + if (!lun_acl) return 0; nacl = lun_acl->se_lun_nacl; tpg = nacl->se_tpg; - return configfs_depend_item(TPG_TFO(tpg)->tf_subsys, + return configfs_depend_item(tpg->se_tpg_tfo->tf_subsys, &lun_acl->se_lun_group.cg_item); } @@ -1448,22 +1406,23 @@ static void core_scsi3_lunacl_undepend_item(struct se_dev_entry *se_deve) /* * For nacl->dynamic_node_acl=1 */ - if (!(lun_acl)) { + if (!lun_acl) { atomic_dec(&se_deve->pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); return; } nacl = lun_acl->se_lun_nacl; tpg = nacl->se_tpg; - configfs_undepend_item(TPG_TFO(tpg)->tf_subsys, + configfs_undepend_item(tpg->se_tpg_tfo->tf_subsys, &lun_acl->se_lun_group.cg_item); atomic_dec(&se_deve->pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } -static int core_scsi3_decode_spec_i_port( +static sense_reason_t +core_scsi3_decode_spec_i_port( struct se_cmd *cmd, struct se_portal_group *tpg, unsigned char *l_isid, @@ -1471,28 +1430,28 @@ static int core_scsi3_decode_spec_i_port( int all_tg_pt, int aptpl) { - struct se_device *dev = SE_DEV(cmd); + struct se_device *dev = cmd->se_dev; struct se_port *tmp_port; struct se_portal_group *dest_tpg = NULL, *tmp_tpg; - struct se_session *se_sess = SE_SESS(cmd); + struct se_session *se_sess = cmd->se_sess; struct se_node_acl *dest_node_acl = NULL; struct se_dev_entry *dest_se_deve = NULL, *local_se_deve; struct t10_pr_registration *dest_pr_reg, *local_pr_reg, *pr_reg_e; struct t10_pr_registration *pr_reg_tmp, *pr_reg_tmp_safe; - struct list_head tid_dest_list; + LIST_HEAD(tid_dest_list); struct pr_transport_id_holder *tidh_new, *tidh, *tidh_tmp; struct target_core_fabric_ops *tmp_tf_ops; - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; + unsigned char *buf; unsigned char *ptr, *i_str = NULL, proto_ident, tmp_proto_ident; char *iport_ptr = NULL, dest_iport[64], i_buf[PR_REG_ISID_ID_LEN]; + sense_reason_t ret; u32 tpdl, tid_len = 0; - int ret, dest_local_nexus, prf_isid; + int dest_local_nexus; u32 dest_rtpi = 0; memset(dest_iport, 0, 64); - INIT_LIST_HEAD(&tid_dest_list); - local_se_deve = &se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; + local_se_deve = se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; /* * Allocate a struct pr_transport_id_holder and setup the * local_node_acl and local_se_deve pointers and add to @@ -1500,21 +1459,21 @@ static int core_scsi3_decode_spec_i_port( * processing in the loop of tid_dest_list below. */ tidh_new = kzalloc(sizeof(struct pr_transport_id_holder), GFP_KERNEL); - if (!(tidh_new)) { - printk(KERN_ERR "Unable to allocate tidh_new\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (!tidh_new) { + pr_err("Unable to allocate tidh_new\n"); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } INIT_LIST_HEAD(&tidh_new->dest_list); tidh_new->dest_tpg = tpg; tidh_new->dest_node_acl = se_sess->se_node_acl; tidh_new->dest_se_deve = local_se_deve; - local_pr_reg = __core_scsi3_alloc_registration(SE_DEV(cmd), + local_pr_reg = __core_scsi3_alloc_registration(cmd->se_dev, se_sess->se_node_acl, local_se_deve, l_isid, sa_res_key, all_tg_pt, aptpl); - if (!(local_pr_reg)) { + if (!local_pr_reg) { kfree(tidh_new); - return PYX_TRANSPORT_LU_COMM_FAILURE; + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } tidh_new->dest_pr_reg = local_pr_reg; /* @@ -1524,6 +1483,20 @@ static int core_scsi3_decode_spec_i_port( */ tidh_new->dest_local_nexus = 1; list_add_tail(&tidh_new->dest_list, &tid_dest_list); + + if (cmd->data_length < 28) { + pr_warn("SPC-PR: Received PR OUT parameter list" + " length too small: %u\n", cmd->data_length); + ret = TCM_INVALID_PARAMETER_LIST; + goto out; + } + + buf = transport_kmap_data_sg(cmd); + if (!buf) { + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out; + } + /* * For a PERSISTENT RESERVE OUT specify initiator ports payload, * first extract TransportID Parameter Data Length, and make sure @@ -1535,11 +1508,11 @@ static int core_scsi3_decode_spec_i_port( tpdl |= buf[27] & 0xff; if ((tpdl + 28) != cmd->data_length) { - printk(KERN_ERR "SPC-3 PR: Illegal tpdl: %u + 28 byte header" + pr_err("SPC-3 PR: Illegal tpdl: %u + 28 byte header" " does not equal CDB data_length: %u\n", tpdl, cmd->data_length); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; - goto out; + ret = TCM_INVALID_PARAMETER_LIST; + goto out_unmap; } /* * Start processing the received transport IDs using the @@ -1555,13 +1528,13 @@ static int core_scsi3_decode_spec_i_port( spin_lock(&dev->se_port_lock); list_for_each_entry(tmp_port, &dev->dev_sep_list, sep_list) { tmp_tpg = tmp_port->sep_tpg; - if (!(tmp_tpg)) + if (!tmp_tpg) continue; - tmp_tf_ops = TPG_TFO(tmp_tpg); - if (!(tmp_tf_ops)) + tmp_tf_ops = tmp_tpg->se_tpg_tfo; + if (!tmp_tf_ops) continue; - if (!(tmp_tf_ops->get_fabric_proto_ident) || - !(tmp_tf_ops->tpg_parse_pr_out_transport_id)) + if (!tmp_tf_ops->get_fabric_proto_ident || + !tmp_tf_ops->tpg_parse_pr_out_transport_id) continue; /* * Look for the matching proto_ident provided by @@ -1575,57 +1548,55 @@ static int core_scsi3_decode_spec_i_port( i_str = tmp_tf_ops->tpg_parse_pr_out_transport_id( tmp_tpg, (const char *)ptr, &tid_len, &iport_ptr); - if (!(i_str)) + if (!i_str) continue; atomic_inc(&tmp_tpg->tpg_pr_ref_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&dev->se_port_lock); - ret = core_scsi3_tpg_depend_item(tmp_tpg); - if (ret != 0) { - printk(KERN_ERR " core_scsi3_tpg_depend_item()" + if (core_scsi3_tpg_depend_item(tmp_tpg)) { + pr_err(" core_scsi3_tpg_depend_item()" " for tmp_tpg\n"); atomic_dec(&tmp_tpg->tpg_pr_ref_count); - smp_mb__after_atomic_dec(); - ret = PYX_TRANSPORT_LU_COMM_FAILURE; - goto out; + smp_mb__after_atomic(); + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out_unmap; } /* - * Locate the desination initiator ACL to be registered + * Locate the destination initiator ACL to be registered * from the decoded fabric module specific TransportID * at *i_str. */ - spin_lock_bh(&tmp_tpg->acl_node_lock); + spin_lock_irq(&tmp_tpg->acl_node_lock); dest_node_acl = __core_tpg_get_initiator_node_acl( tmp_tpg, i_str); if (dest_node_acl) { atomic_inc(&dest_node_acl->acl_pr_ref_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); } - spin_unlock_bh(&tmp_tpg->acl_node_lock); + spin_unlock_irq(&tmp_tpg->acl_node_lock); - if (!(dest_node_acl)) { + if (!dest_node_acl) { core_scsi3_tpg_undepend_item(tmp_tpg); spin_lock(&dev->se_port_lock); continue; } - ret = core_scsi3_nodeacl_depend_item(dest_node_acl); - if (ret != 0) { - printk(KERN_ERR "configfs_depend_item() failed" + if (core_scsi3_nodeacl_depend_item(dest_node_acl)) { + pr_err("configfs_depend_item() failed" " for dest_node_acl->acl_group\n"); atomic_dec(&dest_node_acl->acl_pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); core_scsi3_tpg_undepend_item(tmp_tpg); - ret = PYX_TRANSPORT_LU_COMM_FAILURE; - goto out; + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out_unmap; } dest_tpg = tmp_tpg; - printk(KERN_INFO "SPC-3 PR SPEC_I_PT: Located %s Node:" + pr_debug("SPC-3 PR SPEC_I_PT: Located %s Node:" " %s Port RTPI: %hu\n", - TPG_TFO(dest_tpg)->get_fabric_name(), + dest_tpg->se_tpg_tfo->get_fabric_name(), dest_node_acl->initiatorname, dest_rtpi); spin_lock(&dev->se_port_lock); @@ -1633,25 +1604,25 @@ static int core_scsi3_decode_spec_i_port( } spin_unlock(&dev->se_port_lock); - if (!(dest_tpg)) { - printk(KERN_ERR "SPC-3 PR SPEC_I_PT: Unable to locate" + if (!dest_tpg) { + pr_err("SPC-3 PR SPEC_I_PT: Unable to locate" " dest_tpg\n"); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; - goto out; + ret = TCM_INVALID_PARAMETER_LIST; + goto out_unmap; } -#if 0 - printk("SPC-3 PR SPEC_I_PT: Got %s data_length: %u tpdl: %u" + + pr_debug("SPC-3 PR SPEC_I_PT: Got %s data_length: %u tpdl: %u" " tid_len: %d for %s + %s\n", - TPG_TFO(dest_tpg)->get_fabric_name(), cmd->data_length, + dest_tpg->se_tpg_tfo->get_fabric_name(), cmd->data_length, tpdl, tid_len, i_str, iport_ptr); -#endif + if (tid_len > tpdl) { - printk(KERN_ERR "SPC-3 PR SPEC_I_PT: Illegal tid_len:" + pr_err("SPC-3 PR SPEC_I_PT: Illegal tid_len:" " %u for Transport ID: %s\n", tid_len, ptr); core_scsi3_nodeacl_undepend_item(dest_node_acl); core_scsi3_tpg_undepend_item(dest_tpg); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; - goto out; + ret = TCM_INVALID_PARAMETER_LIST; + goto out_unmap; } /* * Locate the desintation struct se_dev_entry pointer for matching @@ -1660,35 +1631,34 @@ static int core_scsi3_decode_spec_i_port( */ dest_se_deve = core_get_se_deve_from_rtpi(dest_node_acl, dest_rtpi); - if (!(dest_se_deve)) { - printk(KERN_ERR "Unable to locate %s dest_se_deve" + if (!dest_se_deve) { + pr_err("Unable to locate %s dest_se_deve" " from destination RTPI: %hu\n", - TPG_TFO(dest_tpg)->get_fabric_name(), + dest_tpg->se_tpg_tfo->get_fabric_name(), dest_rtpi); core_scsi3_nodeacl_undepend_item(dest_node_acl); core_scsi3_tpg_undepend_item(dest_tpg); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; - goto out; + ret = TCM_INVALID_PARAMETER_LIST; + goto out_unmap; } - ret = core_scsi3_lunacl_depend_item(dest_se_deve); - if (ret < 0) { - printk(KERN_ERR "core_scsi3_lunacl_depend_item()" + if (core_scsi3_lunacl_depend_item(dest_se_deve)) { + pr_err("core_scsi3_lunacl_depend_item()" " failed\n"); atomic_dec(&dest_se_deve->pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); core_scsi3_nodeacl_undepend_item(dest_node_acl); core_scsi3_tpg_undepend_item(dest_tpg); - ret = PYX_TRANSPORT_LU_COMM_FAILURE; - goto out; + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out_unmap; } -#if 0 - printk(KERN_INFO "SPC-3 PR SPEC_I_PT: Located %s Node: %s" + + pr_debug("SPC-3 PR SPEC_I_PT: Located %s Node: %s" " dest_se_deve mapped_lun: %u\n", - TPG_TFO(dest_tpg)->get_fabric_name(), + dest_tpg->se_tpg_tfo->get_fabric_name(), dest_node_acl->initiatorname, dest_se_deve->mapped_lun); -#endif + /* * Skip any TransportIDs that already have a registration for * this target port. @@ -1712,13 +1682,13 @@ static int core_scsi3_decode_spec_i_port( */ tidh_new = kzalloc(sizeof(struct pr_transport_id_holder), GFP_KERNEL); - if (!(tidh_new)) { - printk(KERN_ERR "Unable to allocate tidh_new\n"); + if (!tidh_new) { + pr_err("Unable to allocate tidh_new\n"); core_scsi3_lunacl_undepend_item(dest_se_deve); core_scsi3_nodeacl_undepend_item(dest_node_acl); core_scsi3_tpg_undepend_item(dest_tpg); - ret = PYX_TRANSPORT_LU_COMM_FAILURE; - goto out; + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out_unmap; } INIT_LIST_HEAD(&tidh_new->dest_list); tidh_new->dest_tpg = dest_tpg; @@ -1741,16 +1711,16 @@ static int core_scsi3_decode_spec_i_port( * and then call __core_scsi3_add_registration() in the * 2nd loop which will never fail. */ - dest_pr_reg = __core_scsi3_alloc_registration(SE_DEV(cmd), + dest_pr_reg = __core_scsi3_alloc_registration(cmd->se_dev, dest_node_acl, dest_se_deve, iport_ptr, sa_res_key, all_tg_pt, aptpl); - if (!(dest_pr_reg)) { + if (!dest_pr_reg) { core_scsi3_lunacl_undepend_item(dest_se_deve); core_scsi3_nodeacl_undepend_item(dest_node_acl); core_scsi3_tpg_undepend_item(dest_tpg); kfree(tidh_new); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; - goto out; + ret = TCM_INVALID_PARAMETER_LIST; + goto out_unmap; } tidh_new->dest_pr_reg = dest_pr_reg; list_add_tail(&tidh_new->dest_list, &tid_dest_list); @@ -1760,6 +1730,9 @@ static int core_scsi3_decode_spec_i_port( tid_len = 0; } + + transport_kunmap_data_sg(cmd); + /* * Go ahead and create a registrations from tid_dest_list for the * SPEC_I_PT provided TransportID for the *tidh referenced dest_node_acl @@ -1784,17 +1757,15 @@ static int core_scsi3_decode_spec_i_port( kfree(tidh); memset(i_buf, 0, PR_REG_ISID_ID_LEN); - prf_isid = core_pr_dump_initiator_port(dest_pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + core_pr_dump_initiator_port(dest_pr_reg, i_buf, PR_REG_ISID_ID_LEN); - __core_scsi3_add_registration(SE_DEV(cmd), dest_node_acl, + __core_scsi3_add_registration(cmd->se_dev, dest_node_acl, dest_pr_reg, 0, 0); - printk(KERN_INFO "SPC-3 PR [%s] SPEC_I_PT: Successfully" + pr_debug("SPC-3 PR [%s] SPEC_I_PT: Successfully" " registered Transport ID for Node: %s%s Mapped LUN:" - " %u\n", TPG_TFO(dest_tpg)->get_fabric_name(), - dest_node_acl->initiatorname, (prf_isid) ? - &i_buf[0] : "", dest_se_deve->mapped_lun); + " %u\n", dest_tpg->se_tpg_tfo->get_fabric_name(), + dest_node_acl->initiatorname, i_buf, dest_se_deve->mapped_lun); if (dest_local_nexus) continue; @@ -1805,6 +1776,8 @@ static int core_scsi3_decode_spec_i_port( } return 0; +out_unmap: + transport_kunmap_data_sg(cmd); out: /* * For the failure case, release everything from tid_dest_list @@ -1831,7 +1804,6 @@ out: kmem_cache_free(t10_pr_reg_cache, pr_reg_tmp); } - kfree(dest_pr_reg->pr_aptpl_buf); kmem_cache_free(t10_pr_reg_cache, dest_pr_reg); if (dest_local_nexus) @@ -1844,37 +1816,25 @@ out: return ret; } -/* - * Called with struct se_device->dev_reservation_lock held - */ -static int __core_scsi3_update_aptpl_buf( +static int core_scsi3_update_aptpl_buf( struct se_device *dev, unsigned char *buf, - u32 pr_aptpl_buf_len, - int clear_aptpl_metadata) + u32 pr_aptpl_buf_len) { struct se_lun *lun; struct se_portal_group *tpg; - struct se_subsystem_dev *su_dev = SU_DEV(dev); struct t10_pr_registration *pr_reg; unsigned char tmp[512], isid_buf[32]; ssize_t len = 0; int reg_count = 0; + int ret = 0; - memset(buf, 0, pr_aptpl_buf_len); - /* - * Called to clear metadata once APTPL has been deactivated. - */ - if (clear_aptpl_metadata) { - snprintf(buf, pr_aptpl_buf_len, - "No Registrations or Reservations\n"); - return 0; - } + spin_lock(&dev->dev_reservation_lock); + spin_lock(&dev->t10_pr.registration_lock); /* * Walk the registration list.. */ - spin_lock(&T10_RES(su_dev)->registration_lock); - list_for_each_entry(pr_reg, &T10_RES(su_dev)->registration_list, + list_for_each_entry(pr_reg, &dev->t10_pr.registration_list, pr_reg_list) { tmp[0] = '\0'; @@ -1900,7 +1860,7 @@ static int __core_scsi3_update_aptpl_buf( "res_holder=1\nres_type=%02x\n" "res_scope=%02x\nres_all_tg_pt=%d\n" "mapped_lun=%u\n", reg_count, - TPG_TFO(tpg)->get_fabric_name(), + tpg->se_tpg_tfo->get_fabric_name(), pr_reg->pr_reg_nacl->initiatorname, isid_buf, pr_reg->pr_res_key, pr_reg->pr_res_type, pr_reg->pr_res_scope, pr_reg->pr_reg_all_tg_pt, @@ -1910,17 +1870,17 @@ static int __core_scsi3_update_aptpl_buf( "initiator_fabric=%s\ninitiator_node=%s\n%s" "sa_res_key=%llu\nres_holder=0\n" "res_all_tg_pt=%d\nmapped_lun=%u\n", - reg_count, TPG_TFO(tpg)->get_fabric_name(), + reg_count, tpg->se_tpg_tfo->get_fabric_name(), pr_reg->pr_reg_nacl->initiatorname, isid_buf, pr_reg->pr_res_key, pr_reg->pr_reg_all_tg_pt, pr_reg->pr_res_mapped_lun); } - if ((len + strlen(tmp) > pr_aptpl_buf_len)) { - printk(KERN_ERR "Unable to update renaming" + if ((len + strlen(tmp) >= pr_aptpl_buf_len)) { + pr_err("Unable to update renaming" " APTPL metadata\n"); - spin_unlock(&T10_RES(su_dev)->registration_lock); - return -1; + ret = -EMSGSIZE; + goto out; } len += sprintf(buf+len, "%s", tmp); @@ -1929,202 +1889,170 @@ static int __core_scsi3_update_aptpl_buf( */ snprintf(tmp, 512, "target_fabric=%s\ntarget_node=%s\n" "tpgt=%hu\nport_rtpi=%hu\ntarget_lun=%u\nPR_REG_END:" - " %d\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_wwn(tpg), - TPG_TFO(tpg)->tpg_get_tag(tpg), + " %d\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_wwn(tpg), + tpg->se_tpg_tfo->tpg_get_tag(tpg), lun->lun_sep->sep_rtpi, lun->unpacked_lun, reg_count); - if ((len + strlen(tmp) > pr_aptpl_buf_len)) { - printk(KERN_ERR "Unable to update renaming" + if ((len + strlen(tmp) >= pr_aptpl_buf_len)) { + pr_err("Unable to update renaming" " APTPL metadata\n"); - spin_unlock(&T10_RES(su_dev)->registration_lock); - return -1; + ret = -EMSGSIZE; + goto out; } len += sprintf(buf+len, "%s", tmp); reg_count++; } - spin_unlock(&T10_RES(su_dev)->registration_lock); - if (!(reg_count)) + if (!reg_count) len += sprintf(buf+len, "No Registrations or Reservations"); - return 0; -} - -static int core_scsi3_update_aptpl_buf( - struct se_device *dev, - unsigned char *buf, - u32 pr_aptpl_buf_len, - int clear_aptpl_metadata) -{ - int ret; - - spin_lock(&dev->dev_reservation_lock); - ret = __core_scsi3_update_aptpl_buf(dev, buf, pr_aptpl_buf_len, - clear_aptpl_metadata); +out: + spin_unlock(&dev->t10_pr.registration_lock); spin_unlock(&dev->dev_reservation_lock); return ret; } -/* - * Called with struct se_device->aptpl_file_mutex held - */ static int __core_scsi3_write_aptpl_to_file( struct se_device *dev, - unsigned char *buf, - u32 pr_aptpl_buf_len) + unsigned char *buf) { - struct t10_wwn *wwn = &SU_DEV(dev)->t10_wwn; + struct t10_wwn *wwn = &dev->t10_wwn; struct file *file; - struct iovec iov[1]; - mm_segment_t old_fs; int flags = O_RDWR | O_CREAT | O_TRUNC; char path[512]; + u32 pr_aptpl_buf_len; int ret; - memset(iov, 0, sizeof(struct iovec)); memset(path, 0, 512); - if (strlen(&wwn->unit_serial[0]) > 512) { - printk(KERN_ERR "WWN value for struct se_device does not fit" + if (strlen(&wwn->unit_serial[0]) >= 512) { + pr_err("WWN value for struct se_device does not fit" " into path buffer\n"); - return -1; + return -EMSGSIZE; } snprintf(path, 512, "/var/target/pr/aptpl_%s", &wwn->unit_serial[0]); file = filp_open(path, flags, 0600); - if (IS_ERR(file) || !file || !file->f_dentry) { - printk(KERN_ERR "filp_open(%s) for APTPL metadata" + if (IS_ERR(file)) { + pr_err("filp_open(%s) for APTPL metadata" " failed\n", path); - return -1; + return PTR_ERR(file); } - iov[0].iov_base = &buf[0]; - if (!(pr_aptpl_buf_len)) - iov[0].iov_len = (strlen(&buf[0]) + 1); /* Add extra for NULL */ - else - iov[0].iov_len = pr_aptpl_buf_len; + pr_aptpl_buf_len = (strlen(buf) + 1); /* Add extra for NULL */ - old_fs = get_fs(); - set_fs(get_ds()); - ret = vfs_writev(file, &iov[0], 1, &file->f_pos); - set_fs(old_fs); + ret = kernel_write(file, buf, pr_aptpl_buf_len, 0); - if (ret < 0) { - printk("Error writing APTPL metadata file: %s\n", path); - filp_close(file, NULL); - return -1; - } - filp_close(file, NULL); + if (ret < 0) + pr_debug("Error writing APTPL metadata file: %s\n", path); + fput(file); - return 0; + return (ret < 0) ? -EIO : 0; } -static int core_scsi3_update_and_write_aptpl( - struct se_device *dev, - unsigned char *in_buf, - u32 in_pr_aptpl_buf_len) +/* + * Clear the APTPL metadata if APTPL has been disabled, otherwise + * write out the updated metadata to struct file for this SCSI device. + */ +static sense_reason_t core_scsi3_update_and_write_aptpl(struct se_device *dev, bool aptpl) { - unsigned char null_buf[64], *buf; - u32 pr_aptpl_buf_len; - int ret, clear_aptpl_metadata = 0; - /* - * Can be called with a NULL pointer from PROUT service action CLEAR - */ - if (!(in_buf)) { - memset(null_buf, 0, 64); - buf = &null_buf[0]; - /* - * This will clear the APTPL metadata to: - * "No Registrations or Reservations" status - */ - pr_aptpl_buf_len = 64; - clear_aptpl_metadata = 1; - } else { - buf = in_buf; - pr_aptpl_buf_len = in_pr_aptpl_buf_len; + unsigned char *buf; + int rc; + + if (!aptpl) { + char *null_buf = "No Registrations or Reservations\n"; + + rc = __core_scsi3_write_aptpl_to_file(dev, null_buf); + dev->t10_pr.pr_aptpl_active = 0; + pr_debug("SPC-3 PR: Set APTPL Bit Deactivated\n"); + + if (rc) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + return 0; } - ret = core_scsi3_update_aptpl_buf(dev, buf, pr_aptpl_buf_len, - clear_aptpl_metadata); - if (ret != 0) - return -1; - /* - * __core_scsi3_write_aptpl_to_file() will call strlen() - * on the passed buf to determine pr_aptpl_buf_len. - */ - ret = __core_scsi3_write_aptpl_to_file(dev, buf, 0); - if (ret != 0) - return -1; + buf = kzalloc(PR_APTPL_BUF_LEN, GFP_KERNEL); + if (!buf) + return TCM_OUT_OF_RESOURCES; - return ret; + rc = core_scsi3_update_aptpl_buf(dev, buf, PR_APTPL_BUF_LEN); + if (rc < 0) { + kfree(buf); + return TCM_OUT_OF_RESOURCES; + } + + rc = __core_scsi3_write_aptpl_to_file(dev, buf); + if (rc != 0) { + pr_err("SPC-3 PR: Could not update APTPL\n"); + kfree(buf); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + dev->t10_pr.pr_aptpl_active = 1; + kfree(buf); + pr_debug("SPC-3 PR: Set APTPL Bit Activated\n"); + return 0; } -static int core_scsi3_emulate_pro_register( - struct se_cmd *cmd, - u64 res_key, - u64 sa_res_key, - int aptpl, - int all_tg_pt, - int spec_i_pt, - int ignore_key) +static sense_reason_t +core_scsi3_emulate_pro_register(struct se_cmd *cmd, u64 res_key, u64 sa_res_key, + bool aptpl, bool all_tg_pt, bool spec_i_pt, enum register_type register_type) { - struct se_session *se_sess = SE_SESS(cmd); - struct se_device *dev = SE_DEV(cmd); + struct se_session *se_sess = cmd->se_sess; + struct se_device *dev = cmd->se_dev; struct se_dev_entry *se_deve; - struct se_lun *se_lun = SE_LUN(cmd); + struct se_lun *se_lun = cmd->se_lun; struct se_portal_group *se_tpg; - struct t10_pr_registration *pr_reg, *pr_reg_p, *pr_reg_tmp, *pr_reg_e; - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; - /* Used for APTPL metadata w/ UNREGISTER */ - unsigned char *pr_aptpl_buf = NULL; + struct t10_pr_registration *pr_reg, *pr_reg_p, *pr_reg_tmp; + struct t10_reservation *pr_tmpl = &dev->t10_pr; unsigned char isid_buf[PR_REG_ISID_LEN], *isid_ptr = NULL; - int pr_holder = 0, ret = 0, type; + sense_reason_t ret = TCM_NO_SENSE; + int pr_holder = 0, type; - if (!(se_sess) || !(se_lun)) { - printk(KERN_ERR "SPC-3 PR: se_sess || struct se_lun is NULL!\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (!se_sess || !se_lun) { + pr_err("SPC-3 PR: se_sess || struct se_lun is NULL!\n"); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } se_tpg = se_sess->se_tpg; - se_deve = &se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; + se_deve = se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; - if (TPG_TFO(se_tpg)->sess_get_initiator_sid != NULL) { + if (se_tpg->se_tpg_tfo->sess_get_initiator_sid) { memset(&isid_buf[0], 0, PR_REG_ISID_LEN); - TPG_TFO(se_tpg)->sess_get_initiator_sid(se_sess, &isid_buf[0], + se_tpg->se_tpg_tfo->sess_get_initiator_sid(se_sess, &isid_buf[0], PR_REG_ISID_LEN); isid_ptr = &isid_buf[0]; } /* * Follow logic from spc4r17 Section 5.7.7, Register Behaviors Table 47 */ - pr_reg_e = core_scsi3_locate_pr_reg(dev, se_sess->se_node_acl, se_sess); - if (!(pr_reg_e)) { + pr_reg = core_scsi3_locate_pr_reg(dev, se_sess->se_node_acl, se_sess); + if (!pr_reg) { if (res_key) { - printk(KERN_WARNING "SPC-3 PR: Reservation Key non-zero" + pr_warn("SPC-3 PR: Reservation Key non-zero" " for SA REGISTER, returning CONFLICT\n"); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + return TCM_RESERVATION_CONFLICT; } /* * Do nothing but return GOOD status. */ - if (!(sa_res_key)) - return PYX_TRANSPORT_SENT_TO_TRANSPORT; + if (!sa_res_key) + return 0; - if (!(spec_i_pt)) { + if (!spec_i_pt) { /* * Perform the Service Action REGISTER on the Initiator * Port Endpoint that the PRO was received from on the * Logical Unit of the SCSI device server. */ - ret = core_scsi3_alloc_registration(SE_DEV(cmd), + if (core_scsi3_alloc_registration(cmd->se_dev, se_sess->se_node_acl, se_deve, isid_ptr, sa_res_key, all_tg_pt, aptpl, - ignore_key, 0); - if (ret != 0) { - printk(KERN_ERR "Unable to allocate" + register_type, 0)) { + pr_err("Unable to allocate" " struct t10_pr_registration\n"); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + return TCM_INVALID_PARAMETER_LIST; } } else { /* @@ -2140,213 +2068,137 @@ static int core_scsi3_emulate_pro_register( if (ret != 0) return ret; } + + return core_scsi3_update_and_write_aptpl(dev, aptpl); + } + + /* ok, existing registration */ + + if ((register_type == REGISTER) && (res_key != pr_reg->pr_res_key)) { + pr_err("SPC-3 PR REGISTER: Received" + " res_key: 0x%016Lx does not match" + " existing SA REGISTER res_key:" + " 0x%016Lx\n", res_key, + pr_reg->pr_res_key); + ret = TCM_RESERVATION_CONFLICT; + goto out; + } + + if (spec_i_pt) { + pr_err("SPC-3 PR REGISTER: SPEC_I_PT" + " set on a registered nexus\n"); + ret = TCM_INVALID_PARAMETER_LIST; + goto out; + } + + /* + * An existing ALL_TG_PT=1 registration being released + * must also set ALL_TG_PT=1 in the incoming PROUT. + */ + if (pr_reg->pr_reg_all_tg_pt && !all_tg_pt) { + pr_err("SPC-3 PR REGISTER: ALL_TG_PT=1" + " registration exists, but ALL_TG_PT=1 bit not" + " present in received PROUT\n"); + ret = TCM_INVALID_CDB_FIELD; + goto out; + } + + /* + * sa_res_key=1 Change Reservation Key for registered I_T Nexus. + */ + if (sa_res_key) { /* - * Nothing left to do for the APTPL=0 case. + * Increment PRgeneration counter for struct se_device" + * upon a successful REGISTER, see spc4r17 section 6.3.2 + * READ_KEYS service action. */ - if (!(aptpl)) { - pr_tmpl->pr_aptpl_active = 0; - core_scsi3_update_and_write_aptpl(SE_DEV(cmd), NULL, 0); - printk("SPC-3 PR: Set APTPL Bit Deactivated for" - " REGISTER\n"); - return 0; - } + pr_reg->pr_res_generation = core_scsi3_pr_generation(cmd->se_dev); + pr_reg->pr_res_key = sa_res_key; + pr_debug("SPC-3 PR [%s] REGISTER%s: Changed Reservation" + " Key for %s to: 0x%016Lx PRgeneration:" + " 0x%08x\n", cmd->se_tfo->get_fabric_name(), + (register_type == REGISTER_AND_IGNORE_EXISTING_KEY) ? "_AND_IGNORE_EXISTING_KEY" : "", + pr_reg->pr_reg_nacl->initiatorname, + pr_reg->pr_res_key, pr_reg->pr_res_generation); + + } else { /* - * Locate the newly allocated local I_T Nexus *pr_reg, and - * update the APTPL metadata information using its - * preallocated *pr_reg->pr_aptpl_buf. + * sa_res_key=0 Unregister Reservation Key for registered I_T Nexus. */ - pr_reg = core_scsi3_locate_pr_reg(SE_DEV(cmd), - se_sess->se_node_acl, se_sess); - - ret = core_scsi3_update_and_write_aptpl(SE_DEV(cmd), - &pr_reg->pr_aptpl_buf[0], - pr_tmpl->pr_aptpl_buf_len); - if (!(ret)) { - pr_tmpl->pr_aptpl_active = 1; - printk("SPC-3 PR: Set APTPL Bit Activated for REGISTER\n"); + pr_holder = core_scsi3_check_implicit_release( + cmd->se_dev, pr_reg); + if (pr_holder < 0) { + ret = TCM_RESERVATION_CONFLICT; + goto out; } + type = pr_reg->pr_res_type; - core_scsi3_put_pr_reg(pr_reg); - return ret; - } else { + spin_lock(&pr_tmpl->registration_lock); /* - * Locate the existing *pr_reg via struct se_node_acl pointers + * Release all ALL_TG_PT=1 for the matching SCSI Initiator Port + * and matching pr_res_key. */ - pr_reg = pr_reg_e; - type = pr_reg->pr_res_type; + if (pr_reg->pr_reg_all_tg_pt) { + list_for_each_entry_safe(pr_reg_p, pr_reg_tmp, + &pr_tmpl->registration_list, + pr_reg_list) { + + if (!pr_reg_p->pr_reg_all_tg_pt) + continue; + if (pr_reg_p->pr_res_key != res_key) + continue; + if (pr_reg == pr_reg_p) + continue; + if (strcmp(pr_reg->pr_reg_nacl->initiatorname, + pr_reg_p->pr_reg_nacl->initiatorname)) + continue; - if (!(ignore_key)) { - if (res_key != pr_reg->pr_res_key) { - printk(KERN_ERR "SPC-3 PR REGISTER: Received" - " res_key: 0x%016Lx does not match" - " existing SA REGISTER res_key:" - " 0x%016Lx\n", res_key, - pr_reg->pr_res_key); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + __core_scsi3_free_registration(dev, + pr_reg_p, NULL, 0); } } - if (spec_i_pt) { - printk(KERN_ERR "SPC-3 PR UNREGISTER: SPEC_I_PT" - " set while sa_res_key=0\n"); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; - } + /* - * An existing ALL_TG_PT=1 registration being released - * must also set ALL_TG_PT=1 in the incoming PROUT. + * Release the calling I_T Nexus registration now.. */ - if (pr_reg->pr_reg_all_tg_pt && !(all_tg_pt)) { - printk(KERN_ERR "SPC-3 PR UNREGISTER: ALL_TG_PT=1" - " registration exists, but ALL_TG_PT=1 bit not" - " present in received PROUT\n"); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_INVALID_CDB_FIELD; - } + __core_scsi3_free_registration(cmd->se_dev, pr_reg, NULL, 1); + pr_reg = NULL; + /* - * Allocate APTPL metadata buffer used for UNREGISTER ops + * From spc4r17, section 5.7.11.3 Unregistering + * + * If the persistent reservation is a registrants only + * type, the device server shall establish a unit + * attention condition for the initiator port associated + * with every registered I_T nexus except for the I_T + * nexus on which the PERSISTENT RESERVE OUT command was + * received, with the additional sense code set to + * RESERVATIONS RELEASED. */ - if (aptpl) { - pr_aptpl_buf = kzalloc(pr_tmpl->pr_aptpl_buf_len, - GFP_KERNEL); - if (!(pr_aptpl_buf)) { - printk(KERN_ERR "Unable to allocate" - " pr_aptpl_buf\n"); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (pr_holder && + (type == PR_TYPE_WRITE_EXCLUSIVE_REGONLY || + type == PR_TYPE_EXCLUSIVE_ACCESS_REGONLY)) { + list_for_each_entry(pr_reg_p, + &pr_tmpl->registration_list, + pr_reg_list) { + + core_scsi3_ua_allocate( + pr_reg_p->pr_reg_nacl, + pr_reg_p->pr_res_mapped_lun, + 0x2A, + ASCQ_2AH_RESERVATIONS_RELEASED); } } - /* - * sa_res_key=0 Unregister Reservation Key for registered I_T - * Nexus sa_res_key=1 Change Reservation Key for registered I_T - * Nexus. - */ - if (!(sa_res_key)) { - pr_holder = core_scsi3_check_implict_release( - SE_DEV(cmd), pr_reg); - if (pr_holder < 0) { - kfree(pr_aptpl_buf); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_RESERVATION_CONFLICT; - } - - spin_lock(&pr_tmpl->registration_lock); - /* - * Release all ALL_TG_PT=1 for the matching SCSI Initiator Port - * and matching pr_res_key. - */ - if (pr_reg->pr_reg_all_tg_pt) { - list_for_each_entry_safe(pr_reg_p, pr_reg_tmp, - &pr_tmpl->registration_list, - pr_reg_list) { - - if (!(pr_reg_p->pr_reg_all_tg_pt)) - continue; - - if (pr_reg_p->pr_res_key != res_key) - continue; - - if (pr_reg == pr_reg_p) - continue; - - if (strcmp(pr_reg->pr_reg_nacl->initiatorname, - pr_reg_p->pr_reg_nacl->initiatorname)) - continue; - - __core_scsi3_free_registration(dev, - pr_reg_p, NULL, 0); - } - } - /* - * Release the calling I_T Nexus registration now.. - */ - __core_scsi3_free_registration(SE_DEV(cmd), pr_reg, - NULL, 1); - /* - * From spc4r17, section 5.7.11.3 Unregistering - * - * If the persistent reservation is a registrants only - * type, the device server shall establish a unit - * attention condition for the initiator port associated - * with every registered I_T nexus except for the I_T - * nexus on which the PERSISTENT RESERVE OUT command was - * received, with the additional sense code set to - * RESERVATIONS RELEASED. - */ - if (pr_holder && - ((type == PR_TYPE_WRITE_EXCLUSIVE_REGONLY) || - (type == PR_TYPE_EXCLUSIVE_ACCESS_REGONLY))) { - list_for_each_entry(pr_reg_p, - &pr_tmpl->registration_list, - pr_reg_list) { - - core_scsi3_ua_allocate( - pr_reg_p->pr_reg_nacl, - pr_reg_p->pr_res_mapped_lun, - 0x2A, - ASCQ_2AH_RESERVATIONS_RELEASED); - } - } - spin_unlock(&pr_tmpl->registration_lock); - - if (!(aptpl)) { - pr_tmpl->pr_aptpl_active = 0; - core_scsi3_update_and_write_aptpl(dev, NULL, 0); - printk("SPC-3 PR: Set APTPL Bit Deactivated" - " for UNREGISTER\n"); - return 0; - } - - ret = core_scsi3_update_and_write_aptpl(dev, - &pr_aptpl_buf[0], - pr_tmpl->pr_aptpl_buf_len); - if (!(ret)) { - pr_tmpl->pr_aptpl_active = 1; - printk("SPC-3 PR: Set APTPL Bit Activated" - " for UNREGISTER\n"); - } - kfree(pr_aptpl_buf); - return ret; - } else { - /* - * Increment PRgeneration counter for struct se_device" - * upon a successful REGISTER, see spc4r17 section 6.3.2 - * READ_KEYS service action. - */ - pr_reg->pr_res_generation = core_scsi3_pr_generation( - SE_DEV(cmd)); - pr_reg->pr_res_key = sa_res_key; - printk("SPC-3 PR [%s] REGISTER%s: Changed Reservation" - " Key for %s to: 0x%016Lx PRgeneration:" - " 0x%08x\n", CMD_TFO(cmd)->get_fabric_name(), - (ignore_key) ? "_AND_IGNORE_EXISTING_KEY" : "", - pr_reg->pr_reg_nacl->initiatorname, - pr_reg->pr_res_key, pr_reg->pr_res_generation); - - if (!(aptpl)) { - pr_tmpl->pr_aptpl_active = 0; - core_scsi3_update_and_write_aptpl(dev, NULL, 0); - core_scsi3_put_pr_reg(pr_reg); - printk("SPC-3 PR: Set APTPL Bit Deactivated" - " for REGISTER\n"); - return 0; - } + spin_unlock(&pr_tmpl->registration_lock); + } - ret = core_scsi3_update_and_write_aptpl(dev, - &pr_aptpl_buf[0], - pr_tmpl->pr_aptpl_buf_len); - if (!(ret)) { - pr_tmpl->pr_aptpl_active = 1; - printk("SPC-3 PR: Set APTPL Bit Activated" - " for REGISTER\n"); - } + ret = core_scsi3_update_and_write_aptpl(dev, aptpl); - kfree(pr_aptpl_buf); - core_scsi3_put_pr_reg(pr_reg); - } - } - return 0; +out: + if (pr_reg) + core_scsi3_put_pr_reg(pr_reg); + return ret; } unsigned char *core_scsi3_pr_dump_type(int type) @@ -2371,39 +2223,32 @@ unsigned char *core_scsi3_pr_dump_type(int type) return "Unknown SPC-3 PR Type"; } -static int core_scsi3_pro_reserve( - struct se_cmd *cmd, - struct se_device *dev, - int type, - int scope, - u64 res_key) +static sense_reason_t +core_scsi3_pro_reserve(struct se_cmd *cmd, int type, int scope, u64 res_key) { - struct se_session *se_sess = SE_SESS(cmd); - struct se_dev_entry *se_deve; - struct se_lun *se_lun = SE_LUN(cmd); - struct se_portal_group *se_tpg; + struct se_device *dev = cmd->se_dev; + struct se_session *se_sess = cmd->se_sess; + struct se_lun *se_lun = cmd->se_lun; struct t10_pr_registration *pr_reg, *pr_res_holder; - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct t10_reservation *pr_tmpl = &dev->t10_pr; char i_buf[PR_REG_ISID_ID_LEN]; - int ret, prf_isid; + sense_reason_t ret; memset(i_buf, 0, PR_REG_ISID_ID_LEN); - if (!(se_sess) || !(se_lun)) { - printk(KERN_ERR "SPC-3 PR: se_sess || struct se_lun is NULL!\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (!se_sess || !se_lun) { + pr_err("SPC-3 PR: se_sess || struct se_lun is NULL!\n"); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } - se_tpg = se_sess->se_tpg; - se_deve = &se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; /* * Locate the existing *pr_reg via struct se_node_acl pointers */ - pr_reg = core_scsi3_locate_pr_reg(SE_DEV(cmd), se_sess->se_node_acl, + pr_reg = core_scsi3_locate_pr_reg(cmd->se_dev, se_sess->se_node_acl, se_sess); - if (!(pr_reg)) { - printk(KERN_ERR "SPC-3 PR: Unable to locate" + if (!pr_reg) { + pr_err("SPC-3 PR: Unable to locate" " PR_REGISTERED *pr_reg for RESERVE\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } /* * From spc4r17 Section 5.7.9: Reserving: @@ -2415,11 +2260,11 @@ static int core_scsi3_pro_reserve( * registered with the logical unit for the I_T nexus; and */ if (res_key != pr_reg->pr_res_key) { - printk(KERN_ERR "SPC-3 PR RESERVE: Received res_key: 0x%016Lx" + pr_err("SPC-3 PR RESERVE: Received res_key: 0x%016Lx" " does not match existing SA REGISTER res_key:" " 0x%016Lx\n", res_key, pr_reg->pr_res_key); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + ret = TCM_RESERVATION_CONFLICT; + goto out_put_pr_reg; } /* * From spc4r17 Section 5.7.9: Reserving: @@ -2432,9 +2277,9 @@ static int core_scsi3_pro_reserve( * and that persistent reservation has a scope of LU_SCOPE. */ if (scope != PR_SCOPE_LU_SCOPE) { - printk(KERN_ERR "SPC-3 PR: Illegal SCOPE: 0x%02x\n", scope); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + pr_err("SPC-3 PR: Illegal SCOPE: 0x%02x\n", scope); + ret = TCM_INVALID_PARAMETER_LIST; + goto out_put_pr_reg; } /* * See if we have an existing PR reservation holder pointer at @@ -2443,7 +2288,7 @@ static int core_scsi3_pro_reserve( */ spin_lock(&dev->dev_reservation_lock); pr_res_holder = dev->dev_pr_res_holder; - if ((pr_res_holder)) { + if (pr_res_holder) { /* * From spc4r17 Section 5.7.9: Reserving: * @@ -2456,17 +2301,17 @@ static int core_scsi3_pro_reserve( */ if (pr_res_holder != pr_reg) { struct se_node_acl *pr_res_nacl = pr_res_holder->pr_reg_nacl; - printk(KERN_ERR "SPC-3 PR: Attempted RESERVE from" + pr_err("SPC-3 PR: Attempted RESERVE from" " [%s]: %s while reservation already held by" " [%s]: %s, returning RESERVATION_CONFLICT\n", - CMD_TFO(cmd)->get_fabric_name(), + cmd->se_tfo->get_fabric_name(), se_sess->se_node_acl->initiatorname, - TPG_TFO(pr_res_nacl->se_tpg)->get_fabric_name(), + pr_res_nacl->se_tpg->se_tpg_tfo->get_fabric_name(), pr_res_holder->pr_reg_nacl->initiatorname); spin_unlock(&dev->dev_reservation_lock); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + ret = TCM_RESERVATION_CONFLICT; + goto out_put_pr_reg; } /* * From spc4r17 Section 5.7.9: Reserving: @@ -2478,18 +2323,18 @@ static int core_scsi3_pro_reserve( if ((pr_res_holder->pr_res_type != type) || (pr_res_holder->pr_res_scope != scope)) { struct se_node_acl *pr_res_nacl = pr_res_holder->pr_reg_nacl; - printk(KERN_ERR "SPC-3 PR: Attempted RESERVE from" + pr_err("SPC-3 PR: Attempted RESERVE from" " [%s]: %s trying to change TYPE and/or SCOPE," " while reservation already held by [%s]: %s," " returning RESERVATION_CONFLICT\n", - CMD_TFO(cmd)->get_fabric_name(), + cmd->se_tfo->get_fabric_name(), se_sess->se_node_acl->initiatorname, - TPG_TFO(pr_res_nacl->se_tpg)->get_fabric_name(), + pr_res_nacl->se_tpg->se_tpg_tfo->get_fabric_name(), pr_res_holder->pr_reg_nacl->initiatorname); spin_unlock(&dev->dev_reservation_lock); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + ret = TCM_RESERVATION_CONFLICT; + goto out_put_pr_reg; } /* * From spc4r17 Section 5.7.9: Reserving: @@ -2502,8 +2347,8 @@ static int core_scsi3_pro_reserve( * shall completethe command with GOOD status. */ spin_unlock(&dev->dev_reservation_lock); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_SENT_TO_TRANSPORT; + ret = 0; + goto out_put_pr_reg; } /* * Otherwise, our *pr_reg becomes the PR reservation holder for said @@ -2513,41 +2358,31 @@ static int core_scsi3_pro_reserve( pr_reg->pr_res_type = type; pr_reg->pr_res_holder = 1; dev->dev_pr_res_holder = pr_reg; - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); - printk(KERN_INFO "SPC-3 PR [%s] Service Action: RESERVE created new" + pr_debug("SPC-3 PR [%s] Service Action: RESERVE created new" " reservation holder TYPE: %s ALL_TG_PT: %d\n", - CMD_TFO(cmd)->get_fabric_name(), core_scsi3_pr_dump_type(type), + cmd->se_tfo->get_fabric_name(), core_scsi3_pr_dump_type(type), (pr_reg->pr_reg_all_tg_pt) ? 1 : 0); - printk(KERN_INFO "SPC-3 PR [%s] RESERVE Node: %s%s\n", - CMD_TFO(cmd)->get_fabric_name(), + pr_debug("SPC-3 PR [%s] RESERVE Node: %s%s\n", + cmd->se_tfo->get_fabric_name(), se_sess->se_node_acl->initiatorname, - (prf_isid) ? &i_buf[0] : ""); + i_buf); spin_unlock(&dev->dev_reservation_lock); - if (pr_tmpl->pr_aptpl_active) { - ret = core_scsi3_update_and_write_aptpl(SE_DEV(cmd), - &pr_reg->pr_aptpl_buf[0], - pr_tmpl->pr_aptpl_buf_len); - if (!(ret)) - printk(KERN_INFO "SPC-3 PR: Updated APTPL metadata" - " for RESERVE\n"); - } + if (pr_tmpl->pr_aptpl_active) + core_scsi3_update_and_write_aptpl(cmd->se_dev, true); + ret = 0; +out_put_pr_reg: core_scsi3_put_pr_reg(pr_reg); - return 0; + return ret; } -static int core_scsi3_emulate_pro_reserve( - struct se_cmd *cmd, - int type, - int scope, - u64 res_key) +static sense_reason_t +core_scsi3_emulate_pro_reserve(struct se_cmd *cmd, int type, int scope, + u64 res_key) { - struct se_device *dev = cmd->se_dev; - int ret = 0; - switch (type) { case PR_TYPE_WRITE_EXCLUSIVE: case PR_TYPE_EXCLUSIVE_ACCESS: @@ -2555,15 +2390,12 @@ static int core_scsi3_emulate_pro_reserve( case PR_TYPE_EXCLUSIVE_ACCESS_REGONLY: case PR_TYPE_WRITE_EXCLUSIVE_ALLREG: case PR_TYPE_EXCLUSIVE_ACCESS_ALLREG: - ret = core_scsi3_pro_reserve(cmd, dev, type, scope, res_key); - break; + return core_scsi3_pro_reserve(cmd, type, scope, res_key); default: - printk(KERN_ERR "SPC-3 PR: Unknown Service Action RESERVE Type:" + pr_err("SPC-3 PR: Unknown Service Action RESERVE Type:" " 0x%02x\n", type); - return PYX_TRANSPORT_INVALID_CDB_FIELD; + return TCM_INVALID_CDB_FIELD; } - - return ret; } /* @@ -2573,59 +2405,56 @@ static void __core_scsi3_complete_pro_release( struct se_device *dev, struct se_node_acl *se_nacl, struct t10_pr_registration *pr_reg, - int explict) + int explicit) { struct target_core_fabric_ops *tfo = se_nacl->se_tpg->se_tpg_tfo; char i_buf[PR_REG_ISID_ID_LEN]; - int prf_isid; memset(i_buf, 0, PR_REG_ISID_ID_LEN); - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); /* * Go ahead and release the current PR reservation holder. */ dev->dev_pr_res_holder = NULL; - printk(KERN_INFO "SPC-3 PR [%s] Service Action: %s RELEASE cleared" + pr_debug("SPC-3 PR [%s] Service Action: %s RELEASE cleared" " reservation holder TYPE: %s ALL_TG_PT: %d\n", - tfo->get_fabric_name(), (explict) ? "explict" : "implict", + tfo->get_fabric_name(), (explicit) ? "explicit" : "implicit", core_scsi3_pr_dump_type(pr_reg->pr_res_type), (pr_reg->pr_reg_all_tg_pt) ? 1 : 0); - printk(KERN_INFO "SPC-3 PR [%s] RELEASE Node: %s%s\n", + pr_debug("SPC-3 PR [%s] RELEASE Node: %s%s\n", tfo->get_fabric_name(), se_nacl->initiatorname, - (prf_isid) ? &i_buf[0] : ""); + i_buf); /* * Clear TYPE and SCOPE for the next PROUT Service Action: RESERVE */ pr_reg->pr_res_holder = pr_reg->pr_res_type = pr_reg->pr_res_scope = 0; } -static int core_scsi3_emulate_pro_release( - struct se_cmd *cmd, - int type, - int scope, - u64 res_key) +static sense_reason_t +core_scsi3_emulate_pro_release(struct se_cmd *cmd, int type, int scope, + u64 res_key) { struct se_device *dev = cmd->se_dev; - struct se_session *se_sess = SE_SESS(cmd); - struct se_lun *se_lun = SE_LUN(cmd); + struct se_session *se_sess = cmd->se_sess; + struct se_lun *se_lun = cmd->se_lun; struct t10_pr_registration *pr_reg, *pr_reg_p, *pr_res_holder; - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; - int ret, all_reg = 0; + struct t10_reservation *pr_tmpl = &dev->t10_pr; + int all_reg = 0; + sense_reason_t ret = 0; - if (!(se_sess) || !(se_lun)) { - printk(KERN_ERR "SPC-3 PR: se_sess || struct se_lun is NULL!\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (!se_sess || !se_lun) { + pr_err("SPC-3 PR: se_sess || struct se_lun is NULL!\n"); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } /* * Locate the existing *pr_reg via struct se_node_acl pointers */ pr_reg = core_scsi3_locate_pr_reg(dev, se_sess->se_node_acl, se_sess); - if (!(pr_reg)) { - printk(KERN_ERR "SPC-3 PR: Unable to locate" + if (!pr_reg) { + pr_err("SPC-3 PR: Unable to locate" " PR_REGISTERED *pr_reg for RELEASE\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } /* * From spc4r17 Section 5.7.11.2 Releasing: @@ -2641,13 +2470,12 @@ static int core_scsi3_emulate_pro_release( */ spin_lock(&dev->dev_reservation_lock); pr_res_holder = dev->dev_pr_res_holder; - if (!(pr_res_holder)) { + if (!pr_res_holder) { /* * No persistent reservation, return GOOD status. */ spin_unlock(&dev->dev_reservation_lock); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_SENT_TO_TRANSPORT; + goto out_put_pr_reg; } if ((pr_res_holder->pr_res_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG) || (pr_res_holder->pr_res_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG)) @@ -2660,9 +2488,9 @@ static int core_scsi3_emulate_pro_release( * persistent reservation holder. return GOOD status. */ spin_unlock(&dev->dev_reservation_lock); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_SENT_TO_TRANSPORT; + goto out_put_pr_reg; } + /* * From spc4r17 Section 5.7.11.2 Releasing: * @@ -2678,12 +2506,12 @@ static int core_scsi3_emulate_pro_release( * that is registered with the logical unit for the I_T nexus; */ if (res_key != pr_reg->pr_res_key) { - printk(KERN_ERR "SPC-3 PR RELEASE: Received res_key: 0x%016Lx" + pr_err("SPC-3 PR RELEASE: Received res_key: 0x%016Lx" " does not match existing SA REGISTER res_key:" " 0x%016Lx\n", res_key, pr_reg->pr_res_key); spin_unlock(&dev->dev_reservation_lock); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + ret = TCM_RESERVATION_CONFLICT; + goto out_put_pr_reg; } /* * From spc4r17 Section 5.7.11.2 Releasing and above: @@ -2694,18 +2522,18 @@ static int core_scsi3_emulate_pro_release( if ((pr_res_holder->pr_res_type != type) || (pr_res_holder->pr_res_scope != scope)) { struct se_node_acl *pr_res_nacl = pr_res_holder->pr_reg_nacl; - printk(KERN_ERR "SPC-3 PR RELEASE: Attempted to release" + pr_err("SPC-3 PR RELEASE: Attempted to release" " reservation from [%s]: %s with different TYPE " "and/or SCOPE while reservation already held by" " [%s]: %s, returning RESERVATION_CONFLICT\n", - CMD_TFO(cmd)->get_fabric_name(), + cmd->se_tfo->get_fabric_name(), se_sess->se_node_acl->initiatorname, - TPG_TFO(pr_res_nacl->se_tpg)->get_fabric_name(), + pr_res_nacl->se_tpg->se_tpg_tfo->get_fabric_name(), pr_res_holder->pr_reg_nacl->initiatorname); spin_unlock(&dev->dev_reservation_lock); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + ret = TCM_RESERVATION_CONFLICT; + goto out_put_pr_reg; } /* * In response to a persistent reservation release request from the @@ -2757,38 +2585,33 @@ static int core_scsi3_emulate_pro_release( spin_unlock(&pr_tmpl->registration_lock); write_aptpl: - if (pr_tmpl->pr_aptpl_active) { - ret = core_scsi3_update_and_write_aptpl(SE_DEV(cmd), - &pr_reg->pr_aptpl_buf[0], - pr_tmpl->pr_aptpl_buf_len); - if (!(ret)) - printk("SPC-3 PR: Updated APTPL metadata for RELEASE\n"); - } + if (pr_tmpl->pr_aptpl_active) + core_scsi3_update_and_write_aptpl(cmd->se_dev, true); +out_put_pr_reg: core_scsi3_put_pr_reg(pr_reg); - return 0; + return ret; } -static int core_scsi3_emulate_pro_clear( - struct se_cmd *cmd, - u64 res_key) +static sense_reason_t +core_scsi3_emulate_pro_clear(struct se_cmd *cmd, u64 res_key) { struct se_device *dev = cmd->se_dev; struct se_node_acl *pr_reg_nacl; - struct se_session *se_sess = SE_SESS(cmd); - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct se_session *se_sess = cmd->se_sess; + struct t10_reservation *pr_tmpl = &dev->t10_pr; struct t10_pr_registration *pr_reg, *pr_reg_tmp, *pr_reg_n, *pr_res_holder; u32 pr_res_mapped_lun = 0; int calling_it_nexus = 0; /* * Locate the existing *pr_reg via struct se_node_acl pointers */ - pr_reg_n = core_scsi3_locate_pr_reg(SE_DEV(cmd), + pr_reg_n = core_scsi3_locate_pr_reg(cmd->se_dev, se_sess->se_node_acl, se_sess); - if (!(pr_reg_n)) { - printk(KERN_ERR "SPC-3 PR: Unable to locate" + if (!pr_reg_n) { + pr_err("SPC-3 PR: Unable to locate" " PR_REGISTERED *pr_reg for CLEAR\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } /* * From spc4r17 section 5.7.11.6, Clearing: @@ -2802,12 +2625,12 @@ static int core_scsi3_emulate_pro_clear( * that is registered with the logical unit for the I_T nexus. */ if (res_key != pr_reg_n->pr_res_key) { - printk(KERN_ERR "SPC-3 PR REGISTER: Received" + pr_err("SPC-3 PR REGISTER: Received" " res_key: 0x%016Lx does not match" " existing SA REGISTER res_key:" " 0x%016Lx\n", res_key, pr_reg_n->pr_res_key); core_scsi3_put_pr_reg(pr_reg_n); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + return TCM_RESERVATION_CONFLICT; } /* * a) Release the persistent reservation, if any; @@ -2839,20 +2662,16 @@ static int core_scsi3_emulate_pro_clear( * command with CLEAR service action was received, with the * additional sense code set to RESERVATIONS PREEMPTED. */ - if (!(calling_it_nexus)) + if (!calling_it_nexus) core_scsi3_ua_allocate(pr_reg_nacl, pr_res_mapped_lun, 0x2A, ASCQ_2AH_RESERVATIONS_PREEMPTED); } spin_unlock(&pr_tmpl->registration_lock); - printk(KERN_INFO "SPC-3 PR [%s] Service Action: CLEAR complete\n", - CMD_TFO(cmd)->get_fabric_name()); + pr_debug("SPC-3 PR [%s] Service Action: CLEAR complete\n", + cmd->se_tfo->get_fabric_name()); - if (pr_tmpl->pr_aptpl_active) { - core_scsi3_update_and_write_aptpl(SE_DEV(cmd), NULL, 0); - printk(KERN_INFO "SPC-3 PR: Updated APTPL metadata" - " for CLEAR\n"); - } + core_scsi3_update_and_write_aptpl(cmd->se_dev, false); core_scsi3_pr_generation(dev); return 0; @@ -2867,18 +2686,16 @@ static void __core_scsi3_complete_pro_preempt( struct list_head *preempt_and_abort_list, int type, int scope, - int abort) + enum preempt_type preempt_type) { struct se_node_acl *nacl = pr_reg->pr_reg_nacl; struct target_core_fabric_ops *tfo = nacl->se_tpg->se_tpg_tfo; char i_buf[PR_REG_ISID_ID_LEN]; - int prf_isid; memset(i_buf, 0, PR_REG_ISID_ID_LEN); - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); /* - * Do an implict RELEASE of the existing reservation. + * Do an implicit RELEASE of the existing reservation. */ if (dev->dev_pr_res_holder) __core_scsi3_complete_pro_release(dev, nacl, @@ -2889,14 +2706,14 @@ static void __core_scsi3_complete_pro_preempt( pr_reg->pr_res_type = type; pr_reg->pr_res_scope = scope; - printk(KERN_INFO "SPC-3 PR [%s] Service Action: PREEMPT%s created new" + pr_debug("SPC-3 PR [%s] Service Action: PREEMPT%s created new" " reservation holder TYPE: %s ALL_TG_PT: %d\n", - tfo->get_fabric_name(), (abort) ? "_AND_ABORT" : "", + tfo->get_fabric_name(), (preempt_type == PREEMPT_AND_ABORT) ? "_AND_ABORT" : "", core_scsi3_pr_dump_type(type), (pr_reg->pr_reg_all_tg_pt) ? 1 : 0); - printk(KERN_INFO "SPC-3 PR [%s] PREEMPT%s from Node: %s%s\n", - tfo->get_fabric_name(), (abort) ? "_AND_ABORT" : "", - nacl->initiatorname, (prf_isid) ? &i_buf[0] : ""); + pr_debug("SPC-3 PR [%s] PREEMPT%s from Node: %s%s\n", + tfo->get_fabric_name(), (preempt_type == PREEMPT_AND_ABORT) ? "_AND_ABORT" : "", + nacl->initiatorname, i_buf); /* * For PREEMPT_AND_ABORT, add the preempting reservation's * struct t10_pr_registration to the list that will be compared @@ -2920,73 +2737,50 @@ static void core_scsi3_release_preempt_and_abort( if (pr_reg_holder == pr_reg) continue; if (pr_reg->pr_res_holder) { - printk(KERN_WARNING "pr_reg->pr_res_holder still set\n"); + pr_warn("pr_reg->pr_res_holder still set\n"); continue; } pr_reg->pr_reg_deve = NULL; pr_reg->pr_reg_nacl = NULL; - kfree(pr_reg->pr_aptpl_buf); kmem_cache_free(t10_pr_reg_cache, pr_reg); } } -int core_scsi3_check_cdb_abort_and_preempt( - struct list_head *preempt_and_abort_list, - struct se_cmd *cmd) -{ - struct t10_pr_registration *pr_reg, *pr_reg_tmp; - - list_for_each_entry_safe(pr_reg, pr_reg_tmp, preempt_and_abort_list, - pr_reg_abort_list) { - if (pr_reg->pr_res_key == cmd->pr_res_key) - return 0; - } - - return 1; -} - -static int core_scsi3_pro_preempt( - struct se_cmd *cmd, - int type, - int scope, - u64 res_key, - u64 sa_res_key, - int abort) +static sense_reason_t +core_scsi3_pro_preempt(struct se_cmd *cmd, int type, int scope, u64 res_key, + u64 sa_res_key, enum preempt_type preempt_type) { - struct se_device *dev = SE_DEV(cmd); - struct se_dev_entry *se_deve; + struct se_device *dev = cmd->se_dev; struct se_node_acl *pr_reg_nacl; - struct se_session *se_sess = SE_SESS(cmd); - struct list_head preempt_and_abort_list; + struct se_session *se_sess = cmd->se_sess; + LIST_HEAD(preempt_and_abort_list); struct t10_pr_registration *pr_reg, *pr_reg_tmp, *pr_reg_n, *pr_res_holder; - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; + struct t10_reservation *pr_tmpl = &dev->t10_pr; u32 pr_res_mapped_lun = 0; int all_reg = 0, calling_it_nexus = 0, released_regs = 0; - int prh_type = 0, prh_scope = 0, ret; + int prh_type = 0, prh_scope = 0; - if (!(se_sess)) - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (!se_sess) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - se_deve = &se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; - pr_reg_n = core_scsi3_locate_pr_reg(SE_DEV(cmd), se_sess->se_node_acl, + pr_reg_n = core_scsi3_locate_pr_reg(cmd->se_dev, se_sess->se_node_acl, se_sess); - if (!(pr_reg_n)) { - printk(KERN_ERR "SPC-3 PR: Unable to locate" + if (!pr_reg_n) { + pr_err("SPC-3 PR: Unable to locate" " PR_REGISTERED *pr_reg for PREEMPT%s\n", - (abort) ? "_AND_ABORT" : ""); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + (preempt_type == PREEMPT_AND_ABORT) ? "_AND_ABORT" : ""); + return TCM_RESERVATION_CONFLICT; } if (pr_reg_n->pr_res_key != res_key) { core_scsi3_put_pr_reg(pr_reg_n); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + return TCM_RESERVATION_CONFLICT; } if (scope != PR_SCOPE_LU_SCOPE) { - printk(KERN_ERR "SPC-3 PR: Illegal SCOPE: 0x%02x\n", scope); + pr_err("SPC-3 PR: Illegal SCOPE: 0x%02x\n", scope); core_scsi3_put_pr_reg(pr_reg_n); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + return TCM_INVALID_PARAMETER_LIST; } - INIT_LIST_HEAD(&preempt_and_abort_list); spin_lock(&dev->dev_reservation_lock); pr_res_holder = dev->dev_pr_res_holder; @@ -2995,10 +2789,10 @@ static int core_scsi3_pro_preempt( (pr_res_holder->pr_res_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG))) all_reg = 1; - if (!(all_reg) && !(sa_res_key)) { + if (!all_reg && !sa_res_key) { spin_unlock(&dev->dev_reservation_lock); core_scsi3_put_pr_reg(pr_reg_n); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + return TCM_INVALID_PARAMETER_LIST; } /* * From spc4r17, section 5.7.11.4.4 Removing Registrations: @@ -3009,7 +2803,7 @@ static int core_scsi3_pro_preempt( * server shall perform a preempt by doing the following in an * uninterrupted series of actions. (See below..) */ - if (!(pr_res_holder) || (pr_res_holder->pr_res_key != sa_res_key)) { + if (!pr_res_holder || (pr_res_holder->pr_res_key != sa_res_key)) { /* * No existing or SA Reservation Key matching reservations.. * @@ -3036,7 +2830,7 @@ static int core_scsi3_pro_preempt( * was received, with the additional sense code set * to REGISTRATIONS PREEMPTED. */ - if (!(all_reg)) { + if (!all_reg) { if (pr_reg->pr_res_key != sa_res_key) continue; @@ -3044,7 +2838,7 @@ static int core_scsi3_pro_preempt( pr_reg_nacl = pr_reg->pr_reg_nacl; pr_res_mapped_lun = pr_reg->pr_res_mapped_lun; __core_scsi3_free_registration(dev, pr_reg, - (abort) ? &preempt_and_abort_list : + (preempt_type == PREEMPT_AND_ABORT) ? &preempt_and_abort_list : NULL, calling_it_nexus); released_regs++; } else { @@ -3054,7 +2848,7 @@ static int core_scsi3_pro_preempt( * 5.7.11.4 Preempting, Table 52 and Figure 7. * * For a ZERO SA Reservation key, release - * all other registrations and do an implict + * all other registrations and do an implicit * release of active persistent reservation. * * For a non-ZERO SA Reservation key, only @@ -3072,14 +2866,14 @@ static int core_scsi3_pro_preempt( pr_reg_nacl = pr_reg->pr_reg_nacl; pr_res_mapped_lun = pr_reg->pr_res_mapped_lun; __core_scsi3_free_registration(dev, pr_reg, - (abort) ? &preempt_and_abort_list : + (preempt_type == PREEMPT_AND_ABORT) ? &preempt_and_abort_list : NULL, 0); released_regs++; } - if (!(calling_it_nexus)) + if (!calling_it_nexus) core_scsi3_ua_allocate(pr_reg_nacl, pr_res_mapped_lun, 0x2A, - ASCQ_2AH_RESERVATIONS_PREEMPTED); + ASCQ_2AH_REGISTRATIONS_PREEMPTED); } spin_unlock(&pr_tmpl->registration_lock); /* @@ -3089,10 +2883,10 @@ static int core_scsi3_pro_preempt( * registered reservation key, then the device server shall * complete the command with RESERVATION CONFLICT status. */ - if (!(released_regs)) { + if (!released_regs) { spin_unlock(&dev->dev_reservation_lock); core_scsi3_put_pr_reg(pr_reg_n); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + return TCM_RESERVATION_CONFLICT; } /* * For an existing all registrants type reservation @@ -3101,27 +2895,20 @@ static int core_scsi3_pro_preempt( */ if (pr_res_holder && all_reg && !(sa_res_key)) { __core_scsi3_complete_pro_preempt(dev, pr_reg_n, - (abort) ? &preempt_and_abort_list : NULL, - type, scope, abort); + (preempt_type == PREEMPT_AND_ABORT) ? &preempt_and_abort_list : NULL, + type, scope, preempt_type); - if (abort) + if (preempt_type == PREEMPT_AND_ABORT) core_scsi3_release_preempt_and_abort( &preempt_and_abort_list, pr_reg_n); } spin_unlock(&dev->dev_reservation_lock); - if (pr_tmpl->pr_aptpl_active) { - ret = core_scsi3_update_and_write_aptpl(SE_DEV(cmd), - &pr_reg_n->pr_aptpl_buf[0], - pr_tmpl->pr_aptpl_buf_len); - if (!(ret)) - printk(KERN_INFO "SPC-3 PR: Updated APTPL" - " metadata for PREEMPT%s\n", (abort) ? - "_AND_ABORT" : ""); - } + if (pr_tmpl->pr_aptpl_active) + core_scsi3_update_and_write_aptpl(cmd->se_dev, true); core_scsi3_put_pr_reg(pr_reg_n); - core_scsi3_pr_generation(SE_DEV(cmd)); + core_scsi3_pr_generation(cmd->se_dev); return 0; } /* @@ -3182,7 +2969,7 @@ static int core_scsi3_pro_preempt( pr_reg_nacl = pr_reg->pr_reg_nacl; pr_res_mapped_lun = pr_reg->pr_res_mapped_lun; __core_scsi3_free_registration(dev, pr_reg, - (abort) ? &preempt_and_abort_list : NULL, + (preempt_type == PREEMPT_AND_ABORT) ? &preempt_and_abort_list : NULL, calling_it_nexus); /* * e) Establish a unit attention condition for the initiator @@ -3191,7 +2978,7 @@ static int core_scsi3_pro_preempt( * additional sense code set to REGISTRATIONS PREEMPTED; */ core_scsi3_ua_allocate(pr_reg_nacl, pr_res_mapped_lun, 0x2A, - ASCQ_2AH_RESERVATIONS_PREEMPTED); + ASCQ_2AH_REGISTRATIONS_PREEMPTED); } spin_unlock(&pr_tmpl->registration_lock); /* @@ -3199,8 +2986,8 @@ static int core_scsi3_pro_preempt( * I_T nexus using the contents of the SCOPE and TYPE fields; */ __core_scsi3_complete_pro_preempt(dev, pr_reg_n, - (abort) ? &preempt_and_abort_list : NULL, - type, scope, abort); + (preempt_type == PREEMPT_AND_ABORT) ? &preempt_and_abort_list : NULL, + type, scope, preempt_type); /* * d) Process tasks as defined in 5.7.1; * e) See above.. @@ -3240,36 +3027,24 @@ static int core_scsi3_pro_preempt( * been removed from the primary pr_reg list), except the * new persistent reservation holder, the calling Initiator Port. */ - if (abort) { + if (preempt_type == PREEMPT_AND_ABORT) { core_tmr_lun_reset(dev, NULL, &preempt_and_abort_list, cmd); core_scsi3_release_preempt_and_abort(&preempt_and_abort_list, pr_reg_n); } - if (pr_tmpl->pr_aptpl_active) { - ret = core_scsi3_update_and_write_aptpl(SE_DEV(cmd), - &pr_reg_n->pr_aptpl_buf[0], - pr_tmpl->pr_aptpl_buf_len); - if (!(ret)) - printk("SPC-3 PR: Updated APTPL metadata for PREEMPT" - "%s\n", (abort) ? "_AND_ABORT" : ""); - } + if (pr_tmpl->pr_aptpl_active) + core_scsi3_update_and_write_aptpl(cmd->se_dev, true); core_scsi3_put_pr_reg(pr_reg_n); - core_scsi3_pr_generation(SE_DEV(cmd)); + core_scsi3_pr_generation(cmd->se_dev); return 0; } -static int core_scsi3_emulate_pro_preempt( - struct se_cmd *cmd, - int type, - int scope, - u64 res_key, - u64 sa_res_key, - int abort) +static sense_reason_t +core_scsi3_emulate_pro_preempt(struct se_cmd *cmd, int type, int scope, + u64 res_key, u64 sa_res_key, enum preempt_type preempt_type) { - int ret = 0; - switch (type) { case PR_TYPE_WRITE_EXCLUSIVE: case PR_TYPE_EXCLUSIVE_ACCESS: @@ -3277,104 +3052,108 @@ static int core_scsi3_emulate_pro_preempt( case PR_TYPE_EXCLUSIVE_ACCESS_REGONLY: case PR_TYPE_WRITE_EXCLUSIVE_ALLREG: case PR_TYPE_EXCLUSIVE_ACCESS_ALLREG: - ret = core_scsi3_pro_preempt(cmd, type, scope, - res_key, sa_res_key, abort); - break; + return core_scsi3_pro_preempt(cmd, type, scope, res_key, + sa_res_key, preempt_type); default: - printk(KERN_ERR "SPC-3 PR: Unknown Service Action PREEMPT%s" - " Type: 0x%02x\n", (abort) ? "_AND_ABORT" : "", type); - return PYX_TRANSPORT_INVALID_CDB_FIELD; + pr_err("SPC-3 PR: Unknown Service Action PREEMPT%s" + " Type: 0x%02x\n", (preempt_type == PREEMPT_AND_ABORT) ? "_AND_ABORT" : "", type); + return TCM_INVALID_CDB_FIELD; } - - return ret; } -static int core_scsi3_emulate_pro_register_and_move( - struct se_cmd *cmd, - u64 res_key, - u64 sa_res_key, - int aptpl, - int unreg) +static sense_reason_t +core_scsi3_emulate_pro_register_and_move(struct se_cmd *cmd, u64 res_key, + u64 sa_res_key, int aptpl, int unreg) { - struct se_session *se_sess = SE_SESS(cmd); - struct se_device *dev = SE_DEV(cmd); - struct se_dev_entry *se_deve, *dest_se_deve = NULL; - struct se_lun *se_lun = SE_LUN(cmd); + struct se_session *se_sess = cmd->se_sess; + struct se_device *dev = cmd->se_dev; + struct se_dev_entry *dest_se_deve = NULL; + struct se_lun *se_lun = cmd->se_lun; struct se_node_acl *pr_res_nacl, *pr_reg_nacl, *dest_node_acl = NULL; struct se_port *se_port; struct se_portal_group *se_tpg, *dest_se_tpg = NULL; struct target_core_fabric_ops *dest_tf_ops = NULL, *tf_ops; struct t10_pr_registration *pr_reg, *pr_res_holder, *dest_pr_reg; - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; + struct t10_reservation *pr_tmpl = &dev->t10_pr; + unsigned char *buf; unsigned char *initiator_str; char *iport_ptr = NULL, dest_iport[64], i_buf[PR_REG_ISID_ID_LEN]; u32 tid_len, tmp_tid_len; - int new_reg = 0, type, scope, ret, matching_iname, prf_isid; + int new_reg = 0, type, scope, matching_iname; + sense_reason_t ret; unsigned short rtpi; unsigned char proto_ident; - if (!(se_sess) || !(se_lun)) { - printk(KERN_ERR "SPC-3 PR: se_sess || struct se_lun is NULL!\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (!se_sess || !se_lun) { + pr_err("SPC-3 PR: se_sess || struct se_lun is NULL!\n"); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } + memset(dest_iport, 0, 64); memset(i_buf, 0, PR_REG_ISID_ID_LEN); se_tpg = se_sess->se_tpg; - tf_ops = TPG_TFO(se_tpg); - se_deve = &se_sess->se_node_acl->device_list[cmd->orig_fe_lun]; + tf_ops = se_tpg->se_tpg_tfo; /* * Follow logic from spc4r17 Section 5.7.8, Table 50 -- * Register behaviors for a REGISTER AND MOVE service action * * Locate the existing *pr_reg via struct se_node_acl pointers */ - pr_reg = core_scsi3_locate_pr_reg(SE_DEV(cmd), se_sess->se_node_acl, + pr_reg = core_scsi3_locate_pr_reg(cmd->se_dev, se_sess->se_node_acl, se_sess); - if (!(pr_reg)) { - printk(KERN_ERR "SPC-3 PR: Unable to locate PR_REGISTERED" + if (!pr_reg) { + pr_err("SPC-3 PR: Unable to locate PR_REGISTERED" " *pr_reg for REGISTER_AND_MOVE\n"); - return PYX_TRANSPORT_LU_COMM_FAILURE; + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } /* * The provided reservation key much match the existing reservation key * provided during this initiator's I_T nexus registration. */ if (res_key != pr_reg->pr_res_key) { - printk(KERN_WARNING "SPC-3 PR REGISTER_AND_MOVE: Received" + pr_warn("SPC-3 PR REGISTER_AND_MOVE: Received" " res_key: 0x%016Lx does not match existing SA REGISTER" " res_key: 0x%016Lx\n", res_key, pr_reg->pr_res_key); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + ret = TCM_RESERVATION_CONFLICT; + goto out_put_pr_reg; } /* * The service active reservation key needs to be non zero */ - if (!(sa_res_key)) { - printk(KERN_WARNING "SPC-3 PR REGISTER_AND_MOVE: Received zero" + if (!sa_res_key) { + pr_warn("SPC-3 PR REGISTER_AND_MOVE: Received zero" " sa_res_key\n"); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; + goto out_put_pr_reg; } + /* * Determine the Relative Target Port Identifier where the reservation * will be moved to for the TransportID containing SCSI initiator WWN * information. */ + buf = transport_kmap_data_sg(cmd); + if (!buf) { + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out_put_pr_reg; + } + rtpi = (buf[18] & 0xff) << 8; rtpi |= buf[19] & 0xff; tid_len = (buf[20] & 0xff) << 24; tid_len |= (buf[21] & 0xff) << 16; tid_len |= (buf[22] & 0xff) << 8; tid_len |= buf[23] & 0xff; + transport_kunmap_data_sg(cmd); + buf = NULL; if ((tid_len + 24) != cmd->data_length) { - printk(KERN_ERR "SPC-3 PR: Illegal tid_len: %u + 24 byte header" + pr_err("SPC-3 PR: Illegal tid_len: %u + 24 byte header" " does not equal CDB data_length: %u\n", tid_len, cmd->data_length); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; + goto out_put_pr_reg; } spin_lock(&dev->se_port_lock); @@ -3382,24 +3161,23 @@ static int core_scsi3_emulate_pro_register_and_move( if (se_port->sep_rtpi != rtpi) continue; dest_se_tpg = se_port->sep_tpg; - if (!(dest_se_tpg)) + if (!dest_se_tpg) continue; - dest_tf_ops = TPG_TFO(dest_se_tpg); - if (!(dest_tf_ops)) + dest_tf_ops = dest_se_tpg->se_tpg_tfo; + if (!dest_tf_ops) continue; atomic_inc(&dest_se_tpg->tpg_pr_ref_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&dev->se_port_lock); - ret = core_scsi3_tpg_depend_item(dest_se_tpg); - if (ret != 0) { - printk(KERN_ERR "core_scsi3_tpg_depend_item() failed" + if (core_scsi3_tpg_depend_item(dest_se_tpg)) { + pr_err("core_scsi3_tpg_depend_item() failed" " for dest_se_tpg\n"); atomic_dec(&dest_se_tpg->tpg_pr_ref_count); - smp_mb__after_atomic_dec(); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_LU_COMM_FAILURE; + smp_mb__after_atomic(); + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out_put_pr_reg; } spin_lock(&dev->se_port_lock); @@ -3407,44 +3185,53 @@ static int core_scsi3_emulate_pro_register_and_move( } spin_unlock(&dev->se_port_lock); - if (!(dest_se_tpg) || (!dest_tf_ops)) { - printk(KERN_ERR "SPC-3 PR REGISTER_AND_MOVE: Unable to locate" + if (!dest_se_tpg || !dest_tf_ops) { + pr_err("SPC-3 PR REGISTER_AND_MOVE: Unable to locate" " fabric ops from Relative Target Port Identifier:" " %hu\n", rtpi); - core_scsi3_put_pr_reg(pr_reg); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; + goto out_put_pr_reg; + } + + buf = transport_kmap_data_sg(cmd); + if (!buf) { + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out_put_pr_reg; } proto_ident = (buf[24] & 0x0f); -#if 0 - printk("SPC-3 PR REGISTER_AND_MOVE: Extracted Protocol Identifier:" + + pr_debug("SPC-3 PR REGISTER_AND_MOVE: Extracted Protocol Identifier:" " 0x%02x\n", proto_ident); -#endif + if (proto_ident != dest_tf_ops->get_fabric_proto_ident(dest_se_tpg)) { - printk(KERN_ERR "SPC-3 PR REGISTER_AND_MOVE: Received" + pr_err("SPC-3 PR REGISTER_AND_MOVE: Received" " proto_ident: 0x%02x does not match ident: 0x%02x" " from fabric: %s\n", proto_ident, dest_tf_ops->get_fabric_proto_ident(dest_se_tpg), dest_tf_ops->get_fabric_name()); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; goto out; } if (dest_tf_ops->tpg_parse_pr_out_transport_id == NULL) { - printk(KERN_ERR "SPC-3 PR REGISTER_AND_MOVE: Fabric does not" + pr_err("SPC-3 PR REGISTER_AND_MOVE: Fabric does not" " containg a valid tpg_parse_pr_out_transport_id" " function pointer\n"); - ret = PYX_TRANSPORT_LU_COMM_FAILURE; + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; goto out; } initiator_str = dest_tf_ops->tpg_parse_pr_out_transport_id(dest_se_tpg, (const char *)&buf[24], &tmp_tid_len, &iport_ptr); - if (!(initiator_str)) { - printk(KERN_ERR "SPC-3 PR REGISTER_AND_MOVE: Unable to locate" + if (!initiator_str) { + pr_err("SPC-3 PR REGISTER_AND_MOVE: Unable to locate" " initiator_str from Transport ID\n"); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; goto out; } - printk(KERN_INFO "SPC-3 PR [%s] Extracted initiator %s identifier: %s" + transport_kunmap_data_sg(cmd); + buf = NULL; + + pr_debug("SPC-3 PR [%s] Extracted initiator %s identifier: %s" " %s\n", dest_tf_ops->get_fabric_name(), (iport_ptr != NULL) ? "port" : "device", initiator_str, (iport_ptr != NULL) ? iport_ptr : ""); @@ -3459,97 +3246,96 @@ static int core_scsi3_emulate_pro_register_and_move( pr_reg_nacl = pr_reg->pr_reg_nacl; matching_iname = (!strcmp(initiator_str, pr_reg_nacl->initiatorname)) ? 1 : 0; - if (!(matching_iname)) + if (!matching_iname) goto after_iport_check; - if (!(iport_ptr) || !(pr_reg->isid_present_at_reg)) { - printk(KERN_ERR "SPC-3 PR REGISTER_AND_MOVE: TransportID: %s" + if (!iport_ptr || !pr_reg->isid_present_at_reg) { + pr_err("SPC-3 PR REGISTER_AND_MOVE: TransportID: %s" " matches: %s on received I_T Nexus\n", initiator_str, pr_reg_nacl->initiatorname); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; goto out; } - if (!(strcmp(iport_ptr, pr_reg->pr_reg_isid))) { - printk(KERN_ERR "SPC-3 PR REGISTER_AND_MOVE: TransportID: %s %s" + if (!strcmp(iport_ptr, pr_reg->pr_reg_isid)) { + pr_err("SPC-3 PR REGISTER_AND_MOVE: TransportID: %s %s" " matches: %s %s on received I_T Nexus\n", initiator_str, iport_ptr, pr_reg_nacl->initiatorname, pr_reg->pr_reg_isid); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; goto out; } after_iport_check: /* * Locate the destination struct se_node_acl from the received Transport ID */ - spin_lock_bh(&dest_se_tpg->acl_node_lock); + spin_lock_irq(&dest_se_tpg->acl_node_lock); dest_node_acl = __core_tpg_get_initiator_node_acl(dest_se_tpg, initiator_str); if (dest_node_acl) { atomic_inc(&dest_node_acl->acl_pr_ref_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); } - spin_unlock_bh(&dest_se_tpg->acl_node_lock); + spin_unlock_irq(&dest_se_tpg->acl_node_lock); - if (!(dest_node_acl)) { - printk(KERN_ERR "Unable to locate %s dest_node_acl for" + if (!dest_node_acl) { + pr_err("Unable to locate %s dest_node_acl for" " TransportID%s\n", dest_tf_ops->get_fabric_name(), initiator_str); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; goto out; } - ret = core_scsi3_nodeacl_depend_item(dest_node_acl); - if (ret != 0) { - printk(KERN_ERR "core_scsi3_nodeacl_depend_item() for" + + if (core_scsi3_nodeacl_depend_item(dest_node_acl)) { + pr_err("core_scsi3_nodeacl_depend_item() for" " dest_node_acl\n"); atomic_dec(&dest_node_acl->acl_pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); dest_node_acl = NULL; - ret = PYX_TRANSPORT_LU_COMM_FAILURE; + ret = TCM_INVALID_PARAMETER_LIST; goto out; } -#if 0 - printk(KERN_INFO "SPC-3 PR REGISTER_AND_MOVE: Found %s dest_node_acl:" + + pr_debug("SPC-3 PR REGISTER_AND_MOVE: Found %s dest_node_acl:" " %s from TransportID\n", dest_tf_ops->get_fabric_name(), dest_node_acl->initiatorname); -#endif + /* * Locate the struct se_dev_entry pointer for the matching RELATIVE TARGET * PORT IDENTIFIER. */ dest_se_deve = core_get_se_deve_from_rtpi(dest_node_acl, rtpi); - if (!(dest_se_deve)) { - printk(KERN_ERR "Unable to locate %s dest_se_deve from RTPI:" + if (!dest_se_deve) { + pr_err("Unable to locate %s dest_se_deve from RTPI:" " %hu\n", dest_tf_ops->get_fabric_name(), rtpi); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; goto out; } - ret = core_scsi3_lunacl_depend_item(dest_se_deve); - if (ret < 0) { - printk(KERN_ERR "core_scsi3_lunacl_depend_item() failed\n"); + if (core_scsi3_lunacl_depend_item(dest_se_deve)) { + pr_err("core_scsi3_lunacl_depend_item() failed\n"); atomic_dec(&dest_se_deve->pr_ref_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); dest_se_deve = NULL; - ret = PYX_TRANSPORT_LU_COMM_FAILURE; + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; goto out; } -#if 0 - printk(KERN_INFO "SPC-3 PR REGISTER_AND_MOVE: Located %s node %s LUN" + + pr_debug("SPC-3 PR REGISTER_AND_MOVE: Located %s node %s LUN" " ACL for dest_se_deve->mapped_lun: %u\n", dest_tf_ops->get_fabric_name(), dest_node_acl->initiatorname, dest_se_deve->mapped_lun); -#endif + /* * A persistent reservation needs to already existing in order to * successfully complete the REGISTER_AND_MOVE service action.. */ spin_lock(&dev->dev_reservation_lock); pr_res_holder = dev->dev_pr_res_holder; - if (!(pr_res_holder)) { - printk(KERN_WARNING "SPC-3 PR REGISTER_AND_MOVE: No reservation" + if (!pr_res_holder) { + pr_warn("SPC-3 PR REGISTER_AND_MOVE: No reservation" " currently held\n"); spin_unlock(&dev->dev_reservation_lock); - ret = PYX_TRANSPORT_INVALID_CDB_FIELD; + ret = TCM_INVALID_CDB_FIELD; goto out; } /* @@ -3559,10 +3345,10 @@ after_iport_check: * Register behaviors for a REGISTER AND MOVE service action */ if (pr_res_holder != pr_reg) { - printk(KERN_WARNING "SPC-3 PR REGISTER_AND_MOVE: Calling I_T" + pr_warn("SPC-3 PR REGISTER_AND_MOVE: Calling I_T" " Nexus is not reservation holder\n"); spin_unlock(&dev->dev_reservation_lock); - ret = PYX_TRANSPORT_RESERVATION_CONFLICT; + ret = TCM_RESERVATION_CONFLICT; goto out; } /* @@ -3576,11 +3362,11 @@ after_iport_check: */ if ((pr_res_holder->pr_res_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG) || (pr_res_holder->pr_res_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG)) { - printk(KERN_WARNING "SPC-3 PR REGISTER_AND_MOVE: Unable to move" + pr_warn("SPC-3 PR REGISTER_AND_MOVE: Unable to move" " reservation for type: %s\n", core_scsi3_pr_dump_type(pr_res_holder->pr_res_type)); spin_unlock(&dev->dev_reservation_lock); - ret = PYX_TRANSPORT_RESERVATION_CONFLICT; + ret = TCM_RESERVATION_CONFLICT; goto out; } pr_res_nacl = pr_res_holder->pr_reg_nacl; @@ -3611,13 +3397,12 @@ after_iport_check: */ dest_pr_reg = __core_scsi3_locate_pr_reg(dev, dest_node_acl, iport_ptr); - if (!(dest_pr_reg)) { - ret = core_scsi3_alloc_registration(SE_DEV(cmd), + if (!dest_pr_reg) { + if (core_scsi3_alloc_registration(cmd->se_dev, dest_node_acl, dest_se_deve, iport_ptr, - sa_res_key, 0, aptpl, 2, 1); - if (ret != 0) { + sa_res_key, 0, aptpl, 2, 1)) { spin_unlock(&dev->dev_reservation_lock); - ret = PYX_TRANSPORT_INVALID_PARAMETER_LIST; + ret = TCM_INVALID_PARAMETER_LIST; goto out; } dest_pr_reg = __core_scsi3_locate_pr_reg(dev, dest_node_acl, @@ -3639,24 +3424,23 @@ after_iport_check: dest_pr_reg->pr_res_holder = 1; dest_pr_reg->pr_res_type = type; pr_reg->pr_res_scope = scope; - prf_isid = core_pr_dump_initiator_port(pr_reg, &i_buf[0], - PR_REG_ISID_ID_LEN); + core_pr_dump_initiator_port(pr_reg, i_buf, PR_REG_ISID_ID_LEN); /* * Increment PRGeneration for existing registrations.. */ - if (!(new_reg)) + if (!new_reg) dest_pr_reg->pr_res_generation = pr_tmpl->pr_generation++; spin_unlock(&dev->dev_reservation_lock); - printk(KERN_INFO "SPC-3 PR [%s] Service Action: REGISTER_AND_MOVE" + pr_debug("SPC-3 PR [%s] Service Action: REGISTER_AND_MOVE" " created new reservation holder TYPE: %s on object RTPI:" " %hu PRGeneration: 0x%08x\n", dest_tf_ops->get_fabric_name(), core_scsi3_pr_dump_type(type), rtpi, dest_pr_reg->pr_res_generation); - printk(KERN_INFO "SPC-3 PR Successfully moved reservation from" + pr_debug("SPC-3 PR Successfully moved reservation from" " %s Fabric Node: %s%s -> %s Fabric Node: %s %s\n", tf_ops->get_fabric_name(), pr_reg_nacl->initiatorname, - (prf_isid) ? &i_buf[0] : "", dest_tf_ops->get_fabric_name(), + i_buf, dest_tf_ops->get_fabric_name(), dest_node_acl->initiatorname, (iport_ptr != NULL) ? iport_ptr : ""); /* @@ -3677,33 +3461,22 @@ after_iport_check: } else core_scsi3_put_pr_reg(pr_reg); - /* - * Clear the APTPL metadata if APTPL has been disabled, otherwise - * write out the updated metadata to struct file for this SCSI device. - */ - if (!(aptpl)) { - pr_tmpl->pr_aptpl_active = 0; - core_scsi3_update_and_write_aptpl(SE_DEV(cmd), NULL, 0); - printk("SPC-3 PR: Set APTPL Bit Deactivated for" - " REGISTER_AND_MOVE\n"); - } else { - pr_tmpl->pr_aptpl_active = 1; - ret = core_scsi3_update_and_write_aptpl(SE_DEV(cmd), - &dest_pr_reg->pr_aptpl_buf[0], - pr_tmpl->pr_aptpl_buf_len); - if (!(ret)) - printk("SPC-3 PR: Set APTPL Bit Activated for" - " REGISTER_AND_MOVE\n"); - } + core_scsi3_update_and_write_aptpl(cmd->se_dev, aptpl); + + transport_kunmap_data_sg(cmd); core_scsi3_put_pr_reg(dest_pr_reg); return 0; out: + if (buf) + transport_kunmap_data_sg(cmd); if (dest_se_deve) core_scsi3_lunacl_undepend_item(dest_se_deve); if (dest_node_acl) core_scsi3_nodeacl_undepend_item(dest_node_acl); core_scsi3_tpg_undepend_item(dest_se_tpg); + +out_put_pr_reg: core_scsi3_put_pr_reg(pr_reg); return ret; } @@ -3721,30 +3494,56 @@ static unsigned long long core_scsi3_extract_reservation_key(unsigned char *cdb) /* * See spc4r17 section 6.14 Table 170 */ -static int core_scsi3_emulate_pr_out(struct se_cmd *cmd, unsigned char *cdb) +sense_reason_t +target_scsi3_emulate_pr_out(struct se_cmd *cmd) { - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; + unsigned char *cdb = &cmd->t_task_cdb[0]; + unsigned char *buf; u64 res_key, sa_res_key; int sa, scope, type, aptpl; int spec_i_pt = 0, all_tg_pt = 0, unreg = 0; + sense_reason_t ret; + + /* + * Following spc2r20 5.5.1 Reservations overview: + * + * If a logical unit has been reserved by any RESERVE command and is + * still reserved by any initiator, all PERSISTENT RESERVE IN and all + * PERSISTENT RESERVE OUT commands shall conflict regardless of + * initiator or service action and shall terminate with a RESERVATION + * CONFLICT status. + */ + if (cmd->se_dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS) { + pr_err("Received PERSISTENT_RESERVE CDB while legacy" + " SPC-2 reservation is held, returning" + " RESERVATION_CONFLICT\n"); + return TCM_RESERVATION_CONFLICT; + } + /* * FIXME: A NULL struct se_session pointer means an this is not coming from * a $FABRIC_MOD's nexus, but from internal passthrough ops. */ - if (!(SE_SESS(cmd))) - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (!cmd->se_sess) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; if (cmd->data_length < 24) { - printk(KERN_WARNING "SPC-PR: Recieved PR OUT parameter list" + pr_warn("SPC-PR: Received PR OUT parameter list" " length too small: %u\n", cmd->data_length); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + return TCM_INVALID_PARAMETER_LIST; } + /* * From the PERSISTENT_RESERVE_OUT command descriptor block (CDB) */ sa = (cdb[1] & 0x1f); scope = (cdb[2] & 0xf0); type = (cdb[2] & 0x0f); + + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + /* * From PERSISTENT_RESERVE_OUT parameter list (payload) */ @@ -3762,11 +3561,15 @@ static int core_scsi3_emulate_pr_out(struct se_cmd *cmd, unsigned char *cdb) aptpl = (buf[17] & 0x01); unreg = (buf[17] & 0x02); } + transport_kunmap_data_sg(cmd); + buf = NULL; + /* * SPEC_I_PT=1 is only valid for Service action: REGISTER */ if (spec_i_pt && ((cdb[1] & 0x1f) != PRO_REGISTER)) - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + return TCM_INVALID_PARAMETER_LIST; + /* * From spc4r17 section 6.14: * @@ -3776,12 +3579,13 @@ static int core_scsi3_emulate_pr_out(struct se_cmd *cmd, unsigned char *cdb) * the sense key set to ILLEGAL REQUEST, and the additional sense * code set to PARAMETER LIST LENGTH ERROR. */ - if (!(spec_i_pt) && ((cdb[1] & 0x1f) != PRO_REGISTER_AND_MOVE) && + if (!spec_i_pt && ((cdb[1] & 0x1f) != PRO_REGISTER_AND_MOVE) && (cmd->data_length != 24)) { - printk(KERN_WARNING "SPC-PR: Recieved PR OUT illegal parameter" + pr_warn("SPC-PR: Received PR OUT illegal parameter" " list length: %u\n", cmd->data_length); - return PYX_TRANSPORT_INVALID_PARAMETER_LIST; + return TCM_INVALID_PARAMETER_LIST; } + /* * (core_scsi3_emulate_pro_* function parameters * are defined by spc4r17 Table 174: @@ -3789,35 +3593,43 @@ static int core_scsi3_emulate_pr_out(struct se_cmd *cmd, unsigned char *cdb) */ switch (sa) { case PRO_REGISTER: - return core_scsi3_emulate_pro_register(cmd, - res_key, sa_res_key, aptpl, all_tg_pt, spec_i_pt, 0); + ret = core_scsi3_emulate_pro_register(cmd, + res_key, sa_res_key, aptpl, all_tg_pt, spec_i_pt, REGISTER); + break; case PRO_RESERVE: - return core_scsi3_emulate_pro_reserve(cmd, - type, scope, res_key); + ret = core_scsi3_emulate_pro_reserve(cmd, type, scope, res_key); + break; case PRO_RELEASE: - return core_scsi3_emulate_pro_release(cmd, - type, scope, res_key); + ret = core_scsi3_emulate_pro_release(cmd, type, scope, res_key); + break; case PRO_CLEAR: - return core_scsi3_emulate_pro_clear(cmd, res_key); + ret = core_scsi3_emulate_pro_clear(cmd, res_key); + break; case PRO_PREEMPT: - return core_scsi3_emulate_pro_preempt(cmd, type, scope, - res_key, sa_res_key, 0); + ret = core_scsi3_emulate_pro_preempt(cmd, type, scope, + res_key, sa_res_key, PREEMPT); + break; case PRO_PREEMPT_AND_ABORT: - return core_scsi3_emulate_pro_preempt(cmd, type, scope, - res_key, sa_res_key, 1); + ret = core_scsi3_emulate_pro_preempt(cmd, type, scope, + res_key, sa_res_key, PREEMPT_AND_ABORT); + break; case PRO_REGISTER_AND_IGNORE_EXISTING_KEY: - return core_scsi3_emulate_pro_register(cmd, - 0, sa_res_key, aptpl, all_tg_pt, spec_i_pt, 1); + ret = core_scsi3_emulate_pro_register(cmd, + 0, sa_res_key, aptpl, all_tg_pt, spec_i_pt, REGISTER_AND_IGNORE_EXISTING_KEY); + break; case PRO_REGISTER_AND_MOVE: - return core_scsi3_emulate_pro_register_and_move(cmd, res_key, + ret = core_scsi3_emulate_pro_register_and_move(cmd, res_key, sa_res_key, aptpl, unreg); + break; default: - printk(KERN_ERR "Unknown PERSISTENT_RESERVE_OUT service" + pr_err("Unknown PERSISTENT_RESERVE_OUT service" " action: 0x%02x\n", cdb[1] & 0x1f); - return PYX_TRANSPORT_INVALID_CDB_FIELD; + return TCM_INVALID_CDB_FIELD; } - return PYX_TRANSPORT_INVALID_CDB_FIELD; + if (!ret) + target_complete_cmd(cmd, GOOD); + return ret; } /* @@ -3825,27 +3637,31 @@ static int core_scsi3_emulate_pr_out(struct se_cmd *cmd, unsigned char *cdb) * * See spc4r17 section 5.7.6.2 and section 6.13.2, Table 160 */ -static int core_scsi3_pri_read_keys(struct se_cmd *cmd) +static sense_reason_t +core_scsi3_pri_read_keys(struct se_cmd *cmd) { - struct se_device *se_dev = SE_DEV(cmd); - struct se_subsystem_dev *su_dev = SU_DEV(se_dev); + struct se_device *dev = cmd->se_dev; struct t10_pr_registration *pr_reg; - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; + unsigned char *buf; u32 add_len = 0, off = 8; if (cmd->data_length < 8) { - printk(KERN_ERR "PRIN SA READ_KEYS SCSI Data Length: %u" + pr_err("PRIN SA READ_KEYS SCSI Data Length: %u" " too small\n", cmd->data_length); - return PYX_TRANSPORT_INVALID_CDB_FIELD; + return TCM_INVALID_CDB_FIELD; } - buf[0] = ((T10_RES(su_dev)->pr_generation >> 24) & 0xff); - buf[1] = ((T10_RES(su_dev)->pr_generation >> 16) & 0xff); - buf[2] = ((T10_RES(su_dev)->pr_generation >> 8) & 0xff); - buf[3] = (T10_RES(su_dev)->pr_generation & 0xff); + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + buf[0] = ((dev->t10_pr.pr_generation >> 24) & 0xff); + buf[1] = ((dev->t10_pr.pr_generation >> 16) & 0xff); + buf[2] = ((dev->t10_pr.pr_generation >> 8) & 0xff); + buf[3] = (dev->t10_pr.pr_generation & 0xff); - spin_lock(&T10_RES(su_dev)->registration_lock); - list_for_each_entry(pr_reg, &T10_RES(su_dev)->registration_list, + spin_lock(&dev->t10_pr.registration_lock); + list_for_each_entry(pr_reg, &dev->t10_pr.registration_list, pr_reg_list) { /* * Check for overflow of 8byte PRI READ_KEYS payload and @@ -3865,13 +3681,15 @@ static int core_scsi3_pri_read_keys(struct se_cmd *cmd) add_len += 8; } - spin_unlock(&T10_RES(su_dev)->registration_lock); + spin_unlock(&dev->t10_pr.registration_lock); buf[4] = ((add_len >> 24) & 0xff); buf[5] = ((add_len >> 16) & 0xff); buf[6] = ((add_len >> 8) & 0xff); buf[7] = (add_len & 0xff); + transport_kunmap_data_sg(cmd); + return 0; } @@ -3880,29 +3698,33 @@ static int core_scsi3_pri_read_keys(struct se_cmd *cmd) * * See spc4r17 section 5.7.6.3 and section 6.13.3.2 Table 161 and 162 */ -static int core_scsi3_pri_read_reservation(struct se_cmd *cmd) +static sense_reason_t +core_scsi3_pri_read_reservation(struct se_cmd *cmd) { - struct se_device *se_dev = SE_DEV(cmd); - struct se_subsystem_dev *su_dev = SU_DEV(se_dev); + struct se_device *dev = cmd->se_dev; struct t10_pr_registration *pr_reg; - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; + unsigned char *buf; u64 pr_res_key; u32 add_len = 16; /* Hardcoded to 16 when a reservation is held. */ if (cmd->data_length < 8) { - printk(KERN_ERR "PRIN SA READ_RESERVATIONS SCSI Data Length: %u" + pr_err("PRIN SA READ_RESERVATIONS SCSI Data Length: %u" " too small\n", cmd->data_length); - return PYX_TRANSPORT_INVALID_CDB_FIELD; + return TCM_INVALID_CDB_FIELD; } - buf[0] = ((T10_RES(su_dev)->pr_generation >> 24) & 0xff); - buf[1] = ((T10_RES(su_dev)->pr_generation >> 16) & 0xff); - buf[2] = ((T10_RES(su_dev)->pr_generation >> 8) & 0xff); - buf[3] = (T10_RES(su_dev)->pr_generation & 0xff); + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + buf[0] = ((dev->t10_pr.pr_generation >> 24) & 0xff); + buf[1] = ((dev->t10_pr.pr_generation >> 16) & 0xff); + buf[2] = ((dev->t10_pr.pr_generation >> 8) & 0xff); + buf[3] = (dev->t10_pr.pr_generation & 0xff); - spin_lock(&se_dev->dev_reservation_lock); - pr_reg = se_dev->dev_pr_res_holder; - if ((pr_reg)) { + spin_lock(&dev->dev_reservation_lock); + pr_reg = dev->dev_pr_res_holder; + if (pr_reg) { /* * Set the hardcoded Additional Length */ @@ -3911,10 +3733,9 @@ static int core_scsi3_pri_read_reservation(struct se_cmd *cmd) buf[6] = ((add_len >> 8) & 0xff); buf[7] = (add_len & 0xff); - if (cmd->data_length < 22) { - spin_unlock(&se_dev->dev_reservation_lock); - return 0; - } + if (cmd->data_length < 22) + goto err; + /* * Set the Reservation key. * @@ -3951,7 +3772,10 @@ static int core_scsi3_pri_read_reservation(struct se_cmd *cmd) buf[21] = (pr_reg->pr_res_scope & 0xf0) | (pr_reg->pr_res_type & 0x0f); } - spin_unlock(&se_dev->dev_reservation_lock); + +err: + spin_unlock(&dev->dev_reservation_lock); + transport_kunmap_data_sg(cmd); return 0; } @@ -3961,19 +3785,24 @@ static int core_scsi3_pri_read_reservation(struct se_cmd *cmd) * * See spc4r17 section 6.13.4 Table 165 */ -static int core_scsi3_pri_report_capabilities(struct se_cmd *cmd) +static sense_reason_t +core_scsi3_pri_report_capabilities(struct se_cmd *cmd) { - struct se_device *dev = SE_DEV(cmd); - struct t10_reservation_template *pr_tmpl = &SU_DEV(dev)->t10_reservation; - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; + struct se_device *dev = cmd->se_dev; + struct t10_reservation *pr_tmpl = &dev->t10_pr; + unsigned char *buf; u16 add_len = 8; /* Hardcoded to 8. */ if (cmd->data_length < 6) { - printk(KERN_ERR "PRIN SA REPORT_CAPABILITIES SCSI Data Length:" + pr_err("PRIN SA REPORT_CAPABILITIES SCSI Data Length:" " %u too small\n", cmd->data_length); - return PYX_TRANSPORT_INVALID_CDB_FIELD; + return TCM_INVALID_CDB_FIELD; } + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + buf[0] = ((add_len << 8) & 0xff); buf[1] = (add_len & 0xff); buf[2] |= 0x10; /* CRH: Compatible Reservation Hanlding bit. */ @@ -4004,6 +3833,8 @@ static int core_scsi3_pri_report_capabilities(struct se_cmd *cmd) buf[4] |= 0x02; /* PR_TYPE_WRITE_EXCLUSIVE */ buf[5] |= 0x01; /* PR_TYPE_EXCLUSIVE_ACCESS_ALLREG */ + transport_kunmap_data_sg(cmd); + return 0; } @@ -4012,29 +3843,33 @@ static int core_scsi3_pri_report_capabilities(struct se_cmd *cmd) * * See spc4r17 section 6.13.5 Table 168 and 169 */ -static int core_scsi3_pri_read_full_status(struct se_cmd *cmd) +static sense_reason_t +core_scsi3_pri_read_full_status(struct se_cmd *cmd) { - struct se_device *se_dev = SE_DEV(cmd); + struct se_device *dev = cmd->se_dev; struct se_node_acl *se_nacl; - struct se_subsystem_dev *su_dev = SU_DEV(se_dev); struct se_portal_group *se_tpg; struct t10_pr_registration *pr_reg, *pr_reg_tmp; - struct t10_reservation_template *pr_tmpl = &SU_DEV(se_dev)->t10_reservation; - unsigned char *buf = (unsigned char *)T_TASK(cmd)->t_task_buf; + struct t10_reservation *pr_tmpl = &dev->t10_pr; + unsigned char *buf; u32 add_desc_len = 0, add_len = 0, desc_len, exp_desc_len; u32 off = 8; /* off into first Full Status descriptor */ int format_code = 0; if (cmd->data_length < 8) { - printk(KERN_ERR "PRIN SA READ_FULL_STATUS SCSI Data Length: %u" + pr_err("PRIN SA READ_FULL_STATUS SCSI Data Length: %u" " too small\n", cmd->data_length); - return PYX_TRANSPORT_INVALID_CDB_FIELD; + return TCM_INVALID_CDB_FIELD; } - buf[0] = ((T10_RES(su_dev)->pr_generation >> 24) & 0xff); - buf[1] = ((T10_RES(su_dev)->pr_generation >> 16) & 0xff); - buf[2] = ((T10_RES(su_dev)->pr_generation >> 8) & 0xff); - buf[3] = (T10_RES(su_dev)->pr_generation & 0xff); + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + buf[0] = ((dev->t10_pr.pr_generation >> 24) & 0xff); + buf[1] = ((dev->t10_pr.pr_generation >> 16) & 0xff); + buf[2] = ((dev->t10_pr.pr_generation >> 8) & 0xff); + buf[3] = (dev->t10_pr.pr_generation & 0xff); spin_lock(&pr_tmpl->registration_lock); list_for_each_entry_safe(pr_reg, pr_reg_tmp, @@ -4045,21 +3880,21 @@ static int core_scsi3_pri_read_full_status(struct se_cmd *cmd) add_desc_len = 0; atomic_inc(&pr_reg->pr_res_holders); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); spin_unlock(&pr_tmpl->registration_lock); /* * Determine expected length of $FABRIC_MOD specific * TransportID full status descriptor.. */ - exp_desc_len = TPG_TFO(se_tpg)->tpg_get_pr_transport_id_len( + exp_desc_len = se_tpg->se_tpg_tfo->tpg_get_pr_transport_id_len( se_tpg, se_nacl, pr_reg, &format_code); if ((exp_desc_len + add_len) > cmd->data_length) { - printk(KERN_WARNING "SPC-3 PRIN READ_FULL_STATUS ran" + pr_warn("SPC-3 PRIN READ_FULL_STATUS ran" " out of buffer: %d\n", cmd->data_length); spin_lock(&pr_tmpl->registration_lock); atomic_dec(&pr_reg->pr_res_holders); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); break; } /* @@ -4105,23 +3940,23 @@ static int core_scsi3_pri_read_full_status(struct se_cmd *cmd) * bit is set to one, the contents of the RELATIVE TARGET PORT * IDENTIFIER field are not defined by this standard. */ - if (!(pr_reg->pr_reg_all_tg_pt)) { + if (!pr_reg->pr_reg_all_tg_pt) { struct se_port *port = pr_reg->pr_reg_tg_pt_lun->lun_sep; buf[off++] = ((port->sep_rtpi >> 8) & 0xff); buf[off++] = (port->sep_rtpi & 0xff); } else - off += 2; /* Skip over RELATIVE TARGET PORT IDENTIFER */ + off += 2; /* Skip over RELATIVE TARGET PORT IDENTIFIER */ /* * Now, have the $FABRIC_MOD fill in the protocol identifier */ - desc_len = TPG_TFO(se_tpg)->tpg_get_pr_transport_id(se_tpg, + desc_len = se_tpg->se_tpg_tfo->tpg_get_pr_transport_id(se_tpg, se_nacl, pr_reg, &format_code, &buf[off+4]); spin_lock(&pr_tmpl->registration_lock); atomic_dec(&pr_reg->pr_res_holders); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); /* * Set the ADDITIONAL DESCRIPTOR LENGTH */ @@ -4150,32 +3985,16 @@ static int core_scsi3_pri_read_full_status(struct se_cmd *cmd) buf[6] = ((add_len >> 8) & 0xff); buf[7] = (add_len & 0xff); + transport_kunmap_data_sg(cmd); + return 0; } -static int core_scsi3_emulate_pr_in(struct se_cmd *cmd, unsigned char *cdb) +sense_reason_t +target_scsi3_emulate_pr_in(struct se_cmd *cmd) { - switch (cdb[1] & 0x1f) { - case PRI_READ_KEYS: - return core_scsi3_pri_read_keys(cmd); - case PRI_READ_RESERVATION: - return core_scsi3_pri_read_reservation(cmd); - case PRI_REPORT_CAPABILITIES: - return core_scsi3_pri_report_capabilities(cmd); - case PRI_READ_FULL_STATUS: - return core_scsi3_pri_read_full_status(cmd); - default: - printk(KERN_ERR "Unknown PERSISTENT_RESERVE_IN service" - " action: 0x%02x\n", cdb[1] & 0x1f); - return PYX_TRANSPORT_INVALID_CDB_FIELD; - } + sense_reason_t ret; -} - -int core_scsi3_emulate_pr(struct se_cmd *cmd) -{ - unsigned char *cdb = &T_TASK(cmd)->t_task_cdb[0]; - struct se_device *dev = cmd->se_dev; /* * Following spc2r20 5.5.1 Reservations overview: * @@ -4185,68 +4004,56 @@ int core_scsi3_emulate_pr(struct se_cmd *cmd) * initiator or service action and shall terminate with a RESERVATION * CONFLICT status. */ - if (dev->dev_flags & DF_SPC2_RESERVATIONS) { - printk(KERN_ERR "Received PERSISTENT_RESERVE CDB while legacy" + if (cmd->se_dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS) { + pr_err("Received PERSISTENT_RESERVE CDB while legacy" " SPC-2 reservation is held, returning" " RESERVATION_CONFLICT\n"); - return PYX_TRANSPORT_RESERVATION_CONFLICT; + return TCM_RESERVATION_CONFLICT; } - return (cdb[0] == PERSISTENT_RESERVE_OUT) ? - core_scsi3_emulate_pr_out(cmd, cdb) : - core_scsi3_emulate_pr_in(cmd, cdb); -} + switch (cmd->t_task_cdb[1] & 0x1f) { + case PRI_READ_KEYS: + ret = core_scsi3_pri_read_keys(cmd); + break; + case PRI_READ_RESERVATION: + ret = core_scsi3_pri_read_reservation(cmd); + break; + case PRI_REPORT_CAPABILITIES: + ret = core_scsi3_pri_report_capabilities(cmd); + break; + case PRI_READ_FULL_STATUS: + ret = core_scsi3_pri_read_full_status(cmd); + break; + default: + pr_err("Unknown PERSISTENT_RESERVE_IN service" + " action: 0x%02x\n", cmd->t_task_cdb[1] & 0x1f); + return TCM_INVALID_CDB_FIELD; + } -static int core_pt_reservation_check(struct se_cmd *cmd, u32 *pr_res_type) -{ - return 0; + if (!ret) + target_complete_cmd(cmd, GOOD); + return ret; } -static int core_pt_seq_non_holder( - struct se_cmd *cmd, - unsigned char *cdb, - u32 pr_reg_type) +sense_reason_t +target_check_reservation(struct se_cmd *cmd) { - return 0; -} + struct se_device *dev = cmd->se_dev; + sense_reason_t ret; -int core_setup_reservations(struct se_device *dev, int force_pt) -{ - struct se_subsystem_dev *su_dev = dev->se_sub_dev; - struct t10_reservation_template *rest = &su_dev->t10_reservation; - /* - * If this device is from Target_Core_Mod/pSCSI, use the reservations - * of the Underlying SCSI hardware. In Linux/SCSI terms, this can - * cause a problem because libata and some SATA RAID HBAs appear - * under Linux/SCSI, but to emulate reservations themselves. - */ - if (((TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) && - !(DEV_ATTRIB(dev)->emulate_reservations)) || force_pt) { - rest->res_type = SPC_PASSTHROUGH; - rest->pr_ops.t10_reservation_check = &core_pt_reservation_check; - rest->pr_ops.t10_seq_non_holder = &core_pt_seq_non_holder; - printk(KERN_INFO "%s: Using SPC_PASSTHROUGH, no reservation" - " emulation\n", TRANSPORT(dev)->name); + if (!cmd->se_sess) + return 0; + if (dev->se_hba->hba_flags & HBA_FLAGS_INTERNAL_USE) + return 0; + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) return 0; - } - /* - * If SPC-3 or above is reported by real or emulated struct se_device, - * use emulated Persistent Reservations. - */ - if (TRANSPORT(dev)->get_device_rev(dev) >= SCSI_3) { - rest->res_type = SPC3_PERSISTENT_RESERVATIONS; - rest->pr_ops.t10_reservation_check = &core_scsi3_pr_reservation_check; - rest->pr_ops.t10_seq_non_holder = &core_scsi3_pr_seq_non_holder; - printk(KERN_INFO "%s: Using SPC3_PERSISTENT_RESERVATIONS" - " emulation\n", TRANSPORT(dev)->name); - } else { - rest->res_type = SPC2_RESERVATIONS; - rest->pr_ops.t10_reservation_check = &core_scsi2_reservation_check; - rest->pr_ops.t10_seq_non_holder = - &core_scsi2_reservation_seq_non_holder; - printk(KERN_INFO "%s: Using SPC2_RESERVATIONS emulation\n", - TRANSPORT(dev)->name); - } - return 0; + spin_lock(&dev->dev_reservation_lock); + if (dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS) + ret = target_scsi2_reservation_check(cmd); + else + ret = target_scsi3_pr_reservation_check(cmd); + spin_unlock(&dev->dev_reservation_lock); + + return ret; } diff --git a/drivers/target/target_core_pr.h b/drivers/target/target_core_pr.h index 5603bcfd86d..2ee2936fa0b 100644 --- a/drivers/target/target_core_pr.h +++ b/drivers/target/target_core_pr.h @@ -43,13 +43,19 @@ #define PR_APTPL_MAX_IPORT_LEN 256 #define PR_APTPL_MAX_TPORT_LEN 256 +/* + * Function defined in target_core_spc.c + */ +void spc_parse_naa_6h_vendor_specific(struct se_device *, unsigned char *); + extern struct kmem_cache *t10_pr_reg_cache; -extern int core_pr_dump_initiator_port(struct t10_pr_registration *, +extern void core_pr_dump_initiator_port(struct t10_pr_registration *, char *, u32); -extern int core_scsi2_emulate_crh(struct se_cmd *); +extern sense_reason_t target_scsi2_reservation_release(struct se_cmd *); +extern sense_reason_t target_scsi2_reservation_reserve(struct se_cmd *); extern int core_scsi3_alloc_aptpl_registration( - struct t10_reservation_template *, u64, + struct t10_reservation *, u64, unsigned char *, unsigned char *, u32, unsigned char *, u16, u32, int, int, u8); extern int core_scsi3_check_aptpl_registration(struct se_device *, @@ -59,9 +65,9 @@ extern void core_scsi3_free_pr_reg_from_nacl(struct se_device *, struct se_node_acl *); extern void core_scsi3_free_all_registrations(struct se_device *); extern unsigned char *core_scsi3_pr_dump_type(int); -extern int core_scsi3_check_cdb_abort_and_preempt(struct list_head *, - struct se_cmd *); -extern int core_scsi3_emulate_pr(struct se_cmd *); -extern int core_setup_reservations(struct se_device *, int); + +extern sense_reason_t target_scsi3_emulate_pr_in(struct se_cmd *); +extern sense_reason_t target_scsi3_emulate_pr_out(struct se_cmd *); +extern sense_reason_t target_check_reservation(struct se_cmd *); #endif /* TARGET_CORE_PR_H */ diff --git a/drivers/target/target_core_pscsi.c b/drivers/target/target_core_pscsi.c index f2a08477a68..94d00df28f3 100644 --- a/drivers/target/target_core_pscsi.c +++ b/drivers/target/target_core_pscsi.c @@ -3,10 +3,7 @@ * * This file contains the generic target mode <-> Linux SCSI subsystem plugin. * - * Copyright (c) 2003, 2004, 2005 PyX Technologies, Inc. - * Copyright (c) 2005, 2006, 2007 SBE, Inc. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2003-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -26,7 +23,6 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/string.h> #include <linux/parser.h> #include <linux/timer.h> @@ -34,45 +30,35 @@ #include <linux/blk_types.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> #include <linux/genhd.h> #include <linux/cdrom.h> -#include <linux/file.h> +#include <linux/ratelimit.h> +#include <linux/module.h> +#include <asm/unaligned.h> + #include <scsi/scsi.h> #include <scsi/scsi_device.h> #include <scsi/scsi_cmnd.h> #include <scsi/scsi_host.h> -#include <scsi/libsas.h> /* For TASK_ATTR_* */ +#include <scsi/scsi_tcq.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_transport.h> +#include <target/target_core_backend.h> +#include "target_core_alua.h" #include "target_core_pscsi.h" #define ISPRINT(a) ((a >= ' ') && (a <= '~')) -static struct se_subsystem_api pscsi_template; - -static void pscsi_req_done(struct request *, int); - -/* pscsi_get_sh(): - * - * - */ -static struct Scsi_Host *pscsi_get_sh(u32 host_no) +static inline struct pscsi_dev_virt *PSCSI_DEV(struct se_device *dev) { - struct Scsi_Host *sh = NULL; + return container_of(dev, struct pscsi_dev_virt, dev); +} - sh = scsi_host_lookup(host_no); - if (IS_ERR(sh)) { - printk(KERN_ERR "Unable to locate SCSI HBA with Host ID:" - " %u\n", host_no); - return NULL; - } +static struct se_subsystem_api pscsi_template; - return sh; -} +static sense_reason_t pscsi_execute_cmd(struct se_cmd *cmd); +static void pscsi_req_done(struct request *, int); /* pscsi_attach_hba(): * @@ -81,28 +67,23 @@ static struct Scsi_Host *pscsi_get_sh(u32 host_no) */ static int pscsi_attach_hba(struct se_hba *hba, u32 host_id) { - int hba_depth; struct pscsi_hba_virt *phv; phv = kzalloc(sizeof(struct pscsi_hba_virt), GFP_KERNEL); - if (!(phv)) { - printk(KERN_ERR "Unable to allocate struct pscsi_hba_virt\n"); - return -1; + if (!phv) { + pr_err("Unable to allocate struct pscsi_hba_virt\n"); + return -ENOMEM; } phv->phv_host_id = host_id; - phv->phv_mode = PHV_VIRUTAL_HOST_ID; - hba_depth = PSCSI_VIRTUAL_HBA_DEPTH; - atomic_set(&hba->left_queue_depth, hba_depth); - atomic_set(&hba->max_queue_depth, hba_depth); + phv->phv_mode = PHV_VIRTUAL_HOST_ID; - hba->hba_ptr = (void *)phv; + hba->hba_ptr = phv; - printk(KERN_INFO "CORE_HBA[%d] - TCM SCSI HBA Driver %s on" + pr_debug("CORE_HBA[%d] - TCM SCSI HBA Driver %s on" " Generic Target Core Stack %s\n", hba->hba_id, PSCSI_VERSION, TARGET_CORE_MOD_VERSION); - printk(KERN_INFO "CORE_HBA[%d] - Attached SCSI HBA to Generic" - " Target Core with TCQ Depth: %d\n", hba->hba_id, - atomic_read(&hba->max_queue_depth)); + pr_debug("CORE_HBA[%d] - Attached SCSI HBA to Generic\n", + hba->hba_id); return 0; } @@ -115,12 +96,12 @@ static void pscsi_detach_hba(struct se_hba *hba) if (scsi_host) { scsi_host_put(scsi_host); - printk(KERN_INFO "CORE_HBA[%d] - Detached SCSI HBA: %s from" + pr_debug("CORE_HBA[%d] - Detached SCSI HBA: %s from" " Generic Target Core\n", hba->hba_id, (scsi_host->hostt->name) ? (scsi_host->hostt->name) : "Unknown"); } else - printk(KERN_INFO "CORE_HBA[%d] - Detached Virtual SCSI HBA" + pr_debug("CORE_HBA[%d] - Detached Virtual SCSI HBA" " from Generic Target Core\n", hba->hba_id); kfree(phv); @@ -129,22 +110,19 @@ static void pscsi_detach_hba(struct se_hba *hba) static int pscsi_pmode_enable_hba(struct se_hba *hba, unsigned long mode_flag) { - struct pscsi_hba_virt *phv = (struct pscsi_hba_virt *)hba->hba_ptr; + struct pscsi_hba_virt *phv = hba->hba_ptr; struct Scsi_Host *sh = phv->phv_lld_host; - int hba_depth = PSCSI_VIRTUAL_HBA_DEPTH; /* * Release the struct Scsi_Host */ - if (!(mode_flag)) { - if (!(sh)) + if (!mode_flag) { + if (!sh) return 0; phv->phv_lld_host = NULL; - phv->phv_mode = PHV_VIRUTAL_HOST_ID; - atomic_set(&hba->left_queue_depth, hba_depth); - atomic_set(&hba->max_queue_depth, hba_depth); + phv->phv_mode = PHV_VIRTUAL_HOST_ID; - printk(KERN_INFO "CORE_HBA[%d] - Disabled pSCSI HBA Passthrough" + pr_debug("CORE_HBA[%d] - Disabled pSCSI HBA Passthrough" " %s\n", hba->hba_id, (sh->hostt->name) ? (sh->hostt->name) : "Unknown"); @@ -155,27 +133,17 @@ static int pscsi_pmode_enable_hba(struct se_hba *hba, unsigned long mode_flag) * Otherwise, locate struct Scsi_Host from the original passed * pSCSI Host ID and enable for phba mode */ - sh = pscsi_get_sh(phv->phv_host_id); - if (!(sh)) { - printk(KERN_ERR "pSCSI: Unable to locate SCSI Host for" + sh = scsi_host_lookup(phv->phv_host_id); + if (!sh) { + pr_err("pSCSI: Unable to locate SCSI Host for" " phv_host_id: %d\n", phv->phv_host_id); - return -1; + return -EINVAL; } - /* - * Usually the SCSI LLD will use the hostt->can_queue value to define - * its HBA TCQ depth. Some other drivers (like 2.6 megaraid) don't set - * this at all and set sh->can_queue at runtime. - */ - hba_depth = (sh->hostt->can_queue > sh->can_queue) ? - sh->hostt->can_queue : sh->can_queue; - - atomic_set(&hba->left_queue_depth, hba_depth); - atomic_set(&hba->max_queue_depth, hba_depth); phv->phv_lld_host = sh; phv->phv_mode = PHV_LLD_SCSI_HOST_NO; - printk(KERN_INFO "CORE_HBA[%d] - Enabled pSCSI HBA Passthrough %s\n", + pr_debug("CORE_HBA[%d] - Enabled pSCSI HBA Passthrough %s\n", hba->hba_id, (sh->hostt->name) ? (sh->hostt->name) : "Unknown"); return 1; @@ -237,7 +205,7 @@ pscsi_get_inquiry_vpd_serial(struct scsi_device *sdev, struct t10_wwn *wwn) buf = kzalloc(INQUIRY_VPD_SERIAL_LEN, GFP_KERNEL); if (!buf) - return -1; + return -ENOMEM; memset(cdb, 0, MAX_COMMAND_SIZE); cdb[0] = INQUIRY; @@ -253,14 +221,14 @@ pscsi_get_inquiry_vpd_serial(struct scsi_device *sdev, struct t10_wwn *wwn) snprintf(&wwn->unit_serial[0], INQUIRY_VPD_SERIAL_LEN, "%s", &buf[4]); - wwn->t10_sub_dev->su_dev_flags |= SDF_FIRMWARE_VPD_UNIT_SERIAL; + wwn->t10_dev->dev_flags |= DF_FIRMWARE_VPD_UNIT_SERIAL; kfree(buf); return 0; out_free: kfree(buf); - return -1; + return -EPERM; } static void @@ -294,15 +262,15 @@ pscsi_get_inquiry_vpd_device_ident(struct scsi_device *sdev, page_83 = &buf[off]; ident_len = page_83[3]; if (!ident_len) { - printk(KERN_ERR "page_83[3]: identifier" + pr_err("page_83[3]: identifier" " length zero!\n"); break; } - printk(KERN_INFO "T10 VPD Identifer Length: %d\n", ident_len); + pr_debug("T10 VPD Identifier Length: %d\n", ident_len); vpd = kzalloc(sizeof(struct t10_vpd), GFP_KERNEL); if (!vpd) { - printk(KERN_ERR "Unable to allocate memory for" + pr_err("Unable to allocate memory for" " struct t10_vpd\n"); goto out; } @@ -333,82 +301,42 @@ out: kfree(buf); } -/* pscsi_add_device_to_list(): - * - * - */ -static struct se_device *pscsi_add_device_to_list( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - struct pscsi_dev_virt *pdv, - struct scsi_device *sd, - int dev_flags) +static int pscsi_add_device_to_list(struct se_device *dev, + struct scsi_device *sd) { - struct se_device *dev; - struct se_dev_limits dev_limits; - struct request_queue *q; - struct queue_limits *limits; + struct pscsi_dev_virt *pdv = PSCSI_DEV(dev); + struct request_queue *q = sd->request_queue; - memset(&dev_limits, 0, sizeof(struct se_dev_limits)); + pdv->pdv_sd = sd; if (!sd->queue_depth) { sd->queue_depth = PSCSI_DEFAULT_QUEUEDEPTH; - printk(KERN_ERR "Set broken SCSI Device %d:%d:%d" + pr_err("Set broken SCSI Device %d:%d:%d" " queue_depth to %d\n", sd->channel, sd->id, sd->lun, sd->queue_depth); } - /* - * Setup the local scope queue_limits from struct request_queue->limits - * to pass into transport_add_device_to_core_hba() as struct se_dev_limits. - */ - q = sd->request_queue; - limits = &dev_limits.limits; - limits->logical_block_size = sd->sector_size; - limits->max_hw_sectors = (sd->host->max_sectors > queue_max_hw_sectors(q)) ? - queue_max_hw_sectors(q) : sd->host->max_sectors; - limits->max_sectors = (sd->host->max_sectors > queue_max_sectors(q)) ? - queue_max_sectors(q) : sd->host->max_sectors; - dev_limits.hw_queue_depth = sd->queue_depth; - dev_limits.queue_depth = sd->queue_depth; + + dev->dev_attrib.hw_block_size = sd->sector_size; + dev->dev_attrib.hw_max_sectors = + min_t(int, sd->host->max_sectors, queue_max_hw_sectors(q)); + dev->dev_attrib.hw_queue_depth = sd->queue_depth; + /* * Setup our standard INQUIRY info into se_dev->t10_wwn */ - pscsi_set_inquiry_info(sd, &se_dev->t10_wwn); - - /* - * Set the pointer pdv->pdv_sd to from passed struct scsi_device, - * which has already been referenced with Linux SCSI code with - * scsi_device_get() in this file's pscsi_create_virtdevice(). - * - * The passthrough operations called by the transport_add_device_* - * function below will require this pointer to be set for passthroug - * ops. - * - * For the shutdown case in pscsi_free_device(), this struct - * scsi_device reference is released with Linux SCSI code - * scsi_device_put() and the pdv->pdv_sd cleared. - */ - pdv->pdv_sd = sd; - - dev = transport_add_device_to_core_hba(hba, &pscsi_template, - se_dev, dev_flags, (void *)pdv, - &dev_limits, NULL, NULL); - if (!(dev)) { - pdv->pdv_sd = NULL; - return NULL; - } + pscsi_set_inquiry_info(sd, &dev->t10_wwn); /* * Locate VPD WWN Information used for various purposes within * the Storage Engine. */ - if (!pscsi_get_inquiry_vpd_serial(sd, &se_dev->t10_wwn)) { + if (!pscsi_get_inquiry_vpd_serial(sd, &dev->t10_wwn)) { /* * If VPD Unit Serial returned GOOD status, try * VPD Device Identification page (0x83). */ - pscsi_get_inquiry_vpd_device_ident(sd, &se_dev->t10_wwn); + pscsi_get_inquiry_vpd_device_ident(sd, &dev->t10_wwn); } /* @@ -416,201 +344,188 @@ static struct se_device *pscsi_add_device_to_list( */ if (sd->type == TYPE_TAPE) pscsi_tape_read_blocksize(dev, sd); - return dev; + return 0; } -static void *pscsi_allocate_virtdevice(struct se_hba *hba, const char *name) +static struct se_device *pscsi_alloc_device(struct se_hba *hba, + const char *name) { struct pscsi_dev_virt *pdv; pdv = kzalloc(sizeof(struct pscsi_dev_virt), GFP_KERNEL); - if (!(pdv)) { - printk(KERN_ERR "Unable to allocate memory for struct pscsi_dev_virt\n"); + if (!pdv) { + pr_err("Unable to allocate memory for struct pscsi_dev_virt\n"); return NULL; } - pdv->pdv_se_hba = hba; - printk(KERN_INFO "PSCSI: Allocated pdv: %p for %s\n", pdv, name); - return (void *)pdv; + pr_debug("PSCSI: Allocated pdv: %p for %s\n", pdv, name); + return &pdv->dev; } /* * Called with struct Scsi_Host->host_lock called. */ -static struct se_device *pscsi_create_type_disk( - struct scsi_device *sd, - struct pscsi_dev_virt *pdv, - struct se_subsystem_dev *se_dev, - struct se_hba *hba) +static int pscsi_create_type_disk(struct se_device *dev, struct scsi_device *sd) + __releases(sh->host_lock) { - struct se_device *dev; - struct pscsi_hba_virt *phv = (struct pscsi_hba_virt *)pdv->pdv_se_hba->hba_ptr; + struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr; + struct pscsi_dev_virt *pdv = PSCSI_DEV(dev); struct Scsi_Host *sh = sd->host; struct block_device *bd; - u32 dev_flags = 0; + int ret; if (scsi_device_get(sd)) { - printk(KERN_ERR "scsi_device_get() failed for %d:%d:%d:%d\n", + pr_err("scsi_device_get() failed for %d:%d:%d:%d\n", sh->host_no, sd->channel, sd->id, sd->lun); spin_unlock_irq(sh->host_lock); - return NULL; + return -EIO; } spin_unlock_irq(sh->host_lock); /* * Claim exclusive struct block_device access to struct scsi_device * for TYPE_DISK using supplied udev_path */ - bd = blkdev_get_by_path(se_dev->se_dev_udev_path, + bd = blkdev_get_by_path(dev->udev_path, FMODE_WRITE|FMODE_READ|FMODE_EXCL, pdv); if (IS_ERR(bd)) { - printk(KERN_ERR "pSCSI: blkdev_get_by_path() failed\n"); + pr_err("pSCSI: blkdev_get_by_path() failed\n"); scsi_device_put(sd); - return NULL; + return PTR_ERR(bd); } pdv->pdv_bd = bd; - dev = pscsi_add_device_to_list(hba, se_dev, pdv, sd, dev_flags); - if (!(dev)) { + ret = pscsi_add_device_to_list(dev, sd); + if (ret) { blkdev_put(pdv->pdv_bd, FMODE_WRITE|FMODE_READ|FMODE_EXCL); scsi_device_put(sd); - return NULL; + return ret; } - printk(KERN_INFO "CORE_PSCSI[%d] - Added TYPE_DISK for %d:%d:%d:%d\n", - phv->phv_host_id, sh->host_no, sd->channel, sd->id, sd->lun); - return dev; + pr_debug("CORE_PSCSI[%d] - Added TYPE_DISK for %d:%d:%d:%d\n", + phv->phv_host_id, sh->host_no, sd->channel, sd->id, sd->lun); + return 0; } /* * Called with struct Scsi_Host->host_lock called. */ -static struct se_device *pscsi_create_type_rom( - struct scsi_device *sd, - struct pscsi_dev_virt *pdv, - struct se_subsystem_dev *se_dev, - struct se_hba *hba) +static int pscsi_create_type_rom(struct se_device *dev, struct scsi_device *sd) + __releases(sh->host_lock) { - struct se_device *dev; - struct pscsi_hba_virt *phv = (struct pscsi_hba_virt *)pdv->pdv_se_hba->hba_ptr; + struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr; struct Scsi_Host *sh = sd->host; - u32 dev_flags = 0; + int ret; if (scsi_device_get(sd)) { - printk(KERN_ERR "scsi_device_get() failed for %d:%d:%d:%d\n", + pr_err("scsi_device_get() failed for %d:%d:%d:%d\n", sh->host_no, sd->channel, sd->id, sd->lun); spin_unlock_irq(sh->host_lock); - return NULL; + return -EIO; } spin_unlock_irq(sh->host_lock); - dev = pscsi_add_device_to_list(hba, se_dev, pdv, sd, dev_flags); - if (!(dev)) { + ret = pscsi_add_device_to_list(dev, sd); + if (ret) { scsi_device_put(sd); - return NULL; + return ret; } - printk(KERN_INFO "CORE_PSCSI[%d] - Added Type: %s for %d:%d:%d:%d\n", + pr_debug("CORE_PSCSI[%d] - Added Type: %s for %d:%d:%d:%d\n", phv->phv_host_id, scsi_device_type(sd->type), sh->host_no, sd->channel, sd->id, sd->lun); - return dev; + return 0; } /* - *Called with struct Scsi_Host->host_lock called. + * Called with struct Scsi_Host->host_lock called. */ -static struct se_device *pscsi_create_type_other( - struct scsi_device *sd, - struct pscsi_dev_virt *pdv, - struct se_subsystem_dev *se_dev, - struct se_hba *hba) +static int pscsi_create_type_other(struct se_device *dev, + struct scsi_device *sd) + __releases(sh->host_lock) { - struct se_device *dev; - struct pscsi_hba_virt *phv = (struct pscsi_hba_virt *)pdv->pdv_se_hba->hba_ptr; + struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr; struct Scsi_Host *sh = sd->host; - u32 dev_flags = 0; + int ret; spin_unlock_irq(sh->host_lock); - dev = pscsi_add_device_to_list(hba, se_dev, pdv, sd, dev_flags); - if (!(dev)) - return NULL; + ret = pscsi_add_device_to_list(dev, sd); + if (ret) + return ret; - printk(KERN_INFO "CORE_PSCSI[%d] - Added Type: %s for %d:%d:%d:%d\n", + pr_debug("CORE_PSCSI[%d] - Added Type: %s for %d:%d:%d:%d\n", phv->phv_host_id, scsi_device_type(sd->type), sh->host_no, sd->channel, sd->id, sd->lun); - - return dev; + return 0; } -static struct se_device *pscsi_create_virtdevice( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - void *p) +static int pscsi_configure_device(struct se_device *dev) { - struct pscsi_dev_virt *pdv = (struct pscsi_dev_virt *)p; - struct se_device *dev; + struct se_hba *hba = dev->se_hba; + struct pscsi_dev_virt *pdv = PSCSI_DEV(dev); struct scsi_device *sd; - struct pscsi_hba_virt *phv = (struct pscsi_hba_virt *)hba->hba_ptr; + struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr; struct Scsi_Host *sh = phv->phv_lld_host; int legacy_mode_enable = 0; + int ret; - if (!(pdv)) { - printk(KERN_ERR "Unable to locate struct pscsi_dev_virt" - " parameter\n"); - return NULL; + if (!(pdv->pdv_flags & PDF_HAS_CHANNEL_ID) || + !(pdv->pdv_flags & PDF_HAS_TARGET_ID) || + !(pdv->pdv_flags & PDF_HAS_LUN_ID)) { + pr_err("Missing scsi_channel_id=, scsi_target_id= and" + " scsi_lun_id= parameters\n"); + return -EINVAL; } + /* * If not running in PHV_LLD_SCSI_HOST_NO mode, locate the * struct Scsi_Host we will need to bring the TCM/pSCSI object online */ - if (!(sh)) { + if (!sh) { if (phv->phv_mode == PHV_LLD_SCSI_HOST_NO) { - printk(KERN_ERR "pSCSI: Unable to locate struct" + pr_err("pSCSI: Unable to locate struct" " Scsi_Host for PHV_LLD_SCSI_HOST_NO\n"); - return NULL; + return -ENODEV; } /* - * For the newer PHV_VIRUTAL_HOST_ID struct scsi_device + * For the newer PHV_VIRTUAL_HOST_ID struct scsi_device * reference, we enforce that udev_path has been set */ - if (!(se_dev->su_dev_flags & SDF_USING_UDEV_PATH)) { - printk(KERN_ERR "pSCSI: udev_path attribute has not" + if (!(dev->dev_flags & DF_USING_UDEV_PATH)) { + pr_err("pSCSI: udev_path attribute has not" " been set before ENABLE=1\n"); - return NULL; + return -EINVAL; } /* - * If no scsi_host_id= was passed for PHV_VIRUTAL_HOST_ID, + * If no scsi_host_id= was passed for PHV_VIRTUAL_HOST_ID, * use the original TCM hba ID to reference Linux/SCSI Host No * and enable for PHV_LLD_SCSI_HOST_NO mode. */ if (!(pdv->pdv_flags & PDF_HAS_VIRT_HOST_ID)) { - spin_lock(&hba->device_lock); - if (!(list_empty(&hba->hba_dev_list))) { - printk(KERN_ERR "pSCSI: Unable to set hba_mode" + if (hba->dev_count) { + pr_err("pSCSI: Unable to set hba_mode" " with active devices\n"); - spin_unlock(&hba->device_lock); - return NULL; + return -EEXIST; } - spin_unlock(&hba->device_lock); if (pscsi_pmode_enable_hba(hba, 1) != 1) - return NULL; + return -ENODEV; legacy_mode_enable = 1; hba->hba_flags |= HBA_FLAGS_PSCSI_MODE; sh = phv->phv_lld_host; } else { - sh = pscsi_get_sh(pdv->pdv_host_id); - if (!(sh)) { - printk(KERN_ERR "pSCSI: Unable to locate" + sh = scsi_host_lookup(pdv->pdv_host_id); + if (!sh) { + pr_err("pSCSI: Unable to locate" " pdv_host_id: %d\n", pdv->pdv_host_id); - return NULL; + return -EINVAL; } } } else { - if (phv->phv_mode == PHV_VIRUTAL_HOST_ID) { - printk(KERN_ERR "pSCSI: PHV_VIRUTAL_HOST_ID set while" + if (phv->phv_mode == PHV_VIRTUAL_HOST_ID) { + pr_err("pSCSI: PHV_VIRTUAL_HOST_ID set while" " struct Scsi_Host exists\n"); - return NULL; + return -EEXIST; } } @@ -627,51 +542,47 @@ static struct se_device *pscsi_create_virtdevice( */ switch (sd->type) { case TYPE_DISK: - dev = pscsi_create_type_disk(sd, pdv, se_dev, hba); + ret = pscsi_create_type_disk(dev, sd); break; case TYPE_ROM: - dev = pscsi_create_type_rom(sd, pdv, se_dev, hba); + ret = pscsi_create_type_rom(dev, sd); break; default: - dev = pscsi_create_type_other(sd, pdv, se_dev, hba); + ret = pscsi_create_type_other(dev, sd); break; } - if (!(dev)) { - if (phv->phv_mode == PHV_VIRUTAL_HOST_ID) + if (ret) { + if (phv->phv_mode == PHV_VIRTUAL_HOST_ID) scsi_host_put(sh); else if (legacy_mode_enable) { pscsi_pmode_enable_hba(hba, 0); hba->hba_flags &= ~HBA_FLAGS_PSCSI_MODE; } pdv->pdv_sd = NULL; - return NULL; + return ret; } - return dev; + return 0; } spin_unlock_irq(sh->host_lock); - printk(KERN_ERR "pSCSI: Unable to locate %d:%d:%d:%d\n", sh->host_no, + pr_err("pSCSI: Unable to locate %d:%d:%d:%d\n", sh->host_no, pdv->pdv_channel_id, pdv->pdv_target_id, pdv->pdv_lun_id); - if (phv->phv_mode == PHV_VIRUTAL_HOST_ID) + if (phv->phv_mode == PHV_VIRTUAL_HOST_ID) scsi_host_put(sh); else if (legacy_mode_enable) { pscsi_pmode_enable_hba(hba, 0); hba->hba_flags &= ~HBA_FLAGS_PSCSI_MODE; } - return NULL; + return -ENODEV; } -/* pscsi_free_device(): (Part of se_subsystem_api_t template) - * - * - */ -static void pscsi_free_device(void *p) +static void pscsi_free_device(struct se_device *dev) { - struct pscsi_dev_virt *pdv = p; - struct pscsi_hba_virt *phv = pdv->pdv_se_hba->hba_ptr; + struct pscsi_dev_virt *pdv = PSCSI_DEV(dev); + struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr; struct scsi_device *sd = pdv->pdv_sd; if (sd) { @@ -701,38 +612,38 @@ static void pscsi_free_device(void *p) kfree(pdv); } -static inline struct pscsi_plugin_task *PSCSI_TASK(struct se_task *task) -{ - return container_of(task, struct pscsi_plugin_task, pscsi_task); -} - - -/* pscsi_transport_complete(): - * - * - */ -static int pscsi_transport_complete(struct se_task *task) +static void pscsi_transport_complete(struct se_cmd *cmd, struct scatterlist *sg, + unsigned char *sense_buffer) { - struct pscsi_dev_virt *pdv = task->se_dev->dev_ptr; + struct pscsi_dev_virt *pdv = PSCSI_DEV(cmd->se_dev); struct scsi_device *sd = pdv->pdv_sd; int result; - struct pscsi_plugin_task *pt = PSCSI_TASK(task); - unsigned char *cdb = &pt->pscsi_cdb[0]; + struct pscsi_plugin_task *pt = cmd->priv; + unsigned char *cdb; + /* + * Special case for REPORT_LUNs handling where pscsi_plugin_task has + * not been allocated because TCM is handling the emulation directly. + */ + if (!pt) + return; + cdb = &pt->pscsi_cdb[0]; result = pt->pscsi_result; /* * Hack to make sure that Write-Protect modepage is set if R/O mode is * forced. */ + if (!cmd->se_deve || !cmd->data_length) + goto after_mode_sense; + if (((cdb[0] == MODE_SENSE) || (cdb[0] == MODE_SENSE_10)) && (status_byte(result) << 1) == SAM_STAT_GOOD) { - if (!TASK_CMD(task)->se_deve) - goto after_mode_sense; + if (cmd->se_deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY) { + unsigned char *buf; - if (TASK_CMD(task)->se_deve->lun_flags & - TRANSPORT_LUNFLAGS_READ_ONLY) { - unsigned char *buf = (unsigned char *) - T_TASK(task->task_se_cmd)->t_task_buf; + buf = transport_kmap_data_sg(cmd); + if (!buf) + ; /* XXX: TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE */ if (cdb[0] == MODE_SENSE_10) { if (!(buf[3] & 0x80)) @@ -741,11 +652,13 @@ static int pscsi_transport_complete(struct se_task *task) if (!(buf[2] & 0x80)) buf[2] |= 0x80; } + + transport_kunmap_data_sg(cmd); } } after_mode_sense: - if (sd->type != TYPE_TAPE) + if (sd->type != TYPE_TAPE || !cmd->data_length) goto after_mode_select; /* @@ -759,13 +672,12 @@ after_mode_sense: if (((cdb[0] == MODE_SELECT) || (cdb[0] == MODE_SELECT_10)) && (status_byte(result) << 1) == SAM_STAT_GOOD) { unsigned char *buf; - struct scatterlist *sg = task->task_sg; u16 bdl; u32 blocksize; buf = sg_virt(&sg[0]); - if (!(buf)) { - printk(KERN_ERR "Unable to get buf for scatterlist\n"); + if (!buf) { + pr_err("Unable to get buf for scatterlist\n"); goto after_mode_select; } @@ -788,148 +700,10 @@ after_mode_sense: } after_mode_select: - if (status_byte(result) & CHECK_CONDITION) - return 1; - - return 0; -} - -static struct se_task * -pscsi_alloc_task(struct se_cmd *cmd) -{ - struct pscsi_plugin_task *pt; - unsigned char *cdb = T_TASK(cmd)->t_task_cdb; - - pt = kzalloc(sizeof(struct pscsi_plugin_task), GFP_KERNEL); - if (!pt) { - printk(KERN_ERR "Unable to allocate struct pscsi_plugin_task\n"); - return NULL; + if (sense_buffer && (status_byte(result) & CHECK_CONDITION)) { + memcpy(sense_buffer, pt->pscsi_sense, TRANSPORT_SENSE_BUFFER); + cmd->se_cmd_flags |= SCF_TRANSPORT_TASK_SENSE; } - - /* - * If TCM Core is signaling a > TCM_MAX_COMMAND_SIZE allocation, - * allocate the extended CDB buffer for per struct se_task context - * pt->pscsi_cdb now. - */ - if (T_TASK(cmd)->t_task_cdb != T_TASK(cmd)->__t_task_cdb) { - - pt->pscsi_cdb = kzalloc(scsi_command_size(cdb), GFP_KERNEL); - if (!(pt->pscsi_cdb)) { - printk(KERN_ERR "pSCSI: Unable to allocate extended" - " pt->pscsi_cdb\n"); - return NULL; - } - } else - pt->pscsi_cdb = &pt->__pscsi_cdb[0]; - - return &pt->pscsi_task; -} - -static inline void pscsi_blk_init_request( - struct se_task *task, - struct pscsi_plugin_task *pt, - struct request *req, - int bidi_read) -{ - /* - * Defined as "scsi command" in include/linux/blkdev.h. - */ - req->cmd_type = REQ_TYPE_BLOCK_PC; - /* - * For the extra BIDI-COMMAND READ struct request we do not - * need to setup the remaining structure members - */ - if (bidi_read) - return; - /* - * Setup the done function pointer for struct request, - * also set the end_io_data pointer.to struct se_task. - */ - req->end_io = pscsi_req_done; - req->end_io_data = (void *)task; - /* - * Load the referenced struct se_task's SCSI CDB into - * include/linux/blkdev.h:struct request->cmd - */ - req->cmd_len = scsi_command_size(pt->pscsi_cdb); - req->cmd = &pt->pscsi_cdb[0]; - /* - * Setup pointer for outgoing sense data. - */ - req->sense = (void *)&pt->pscsi_sense[0]; - req->sense_len = 0; -} - -/* - * Used for pSCSI data payloads for all *NON* SCF_SCSI_DATA_SG_IO_CDB -*/ -static int pscsi_blk_get_request(struct se_task *task) -{ - struct pscsi_plugin_task *pt = PSCSI_TASK(task); - struct pscsi_dev_virt *pdv = task->se_dev->dev_ptr; - - pt->pscsi_req = blk_get_request(pdv->pdv_sd->request_queue, - (task->task_data_direction == DMA_TO_DEVICE), - GFP_KERNEL); - if (!(pt->pscsi_req) || IS_ERR(pt->pscsi_req)) { - printk(KERN_ERR "PSCSI: blk_get_request() failed: %ld\n", - IS_ERR(pt->pscsi_req)); - return PYX_TRANSPORT_LU_COMM_FAILURE; - } - /* - * Setup the newly allocated struct request for REQ_TYPE_BLOCK_PC, - * and setup rq callback, CDB and sense. - */ - pscsi_blk_init_request(task, pt, pt->pscsi_req, 0); - return 0; -} - -/* pscsi_do_task(): (Part of se_subsystem_api_t template) - * - * - */ -static int pscsi_do_task(struct se_task *task) -{ - struct pscsi_plugin_task *pt = PSCSI_TASK(task); - struct pscsi_dev_virt *pdv = task->se_dev->dev_ptr; - /* - * Set the struct request->timeout value based on peripheral - * device type from SCSI. - */ - if (pdv->pdv_sd->type == TYPE_DISK) - pt->pscsi_req->timeout = PS_TIMEOUT_DISK; - else - pt->pscsi_req->timeout = PS_TIMEOUT_OTHER; - - pt->pscsi_req->retries = PS_RETRY; - /* - * Queue the struct request into the struct scsi_device->request_queue. - * Also check for HEAD_OF_QUEUE SAM TASK attr from received se_cmd - * descriptor - */ - blk_execute_rq_nowait(pdv->pdv_sd->request_queue, NULL, pt->pscsi_req, - (task->task_se_cmd->sam_task_attr == TASK_ATTR_HOQ), - pscsi_req_done); - - return PYX_TRANSPORT_SENT_TO_TRANSPORT; -} - -static void pscsi_free_task(struct se_task *task) -{ - struct pscsi_plugin_task *pt = PSCSI_TASK(task); - struct se_cmd *cmd = task->task_se_cmd; - - /* - * Release the extended CDB allocation from pscsi_alloc_task() - * if one exists. - */ - if (T_TASK(cmd)->t_task_cdb != T_TASK(cmd)->__t_task_cdb) - kfree(pt->pscsi_cdb); - /* - * We do not release the bio(s) here associated with this task, as - * this is handled by bio_put() and pscsi_bi_endio(). - */ - kfree(pt); } enum { @@ -945,13 +719,11 @@ static match_table_t tokens = { {Opt_err, NULL} }; -static ssize_t pscsi_set_configfs_dev_params(struct se_hba *hba, - struct se_subsystem_dev *se_dev, - const char *page, - ssize_t count) +static ssize_t pscsi_set_configfs_dev_params(struct se_device *dev, + const char *page, ssize_t count) { - struct pscsi_dev_virt *pdv = se_dev->se_dev_su_ptr; - struct pscsi_hba_virt *phv = hba->hba_ptr; + struct pscsi_dev_virt *pdv = PSCSI_DEV(dev); + struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr; char *orig, *ptr, *opts; substring_t args[MAX_OPT_ARGS]; int ret = 0, arg, token; @@ -962,7 +734,7 @@ static ssize_t pscsi_set_configfs_dev_params(struct se_hba *hba, orig = opts; - while ((ptr = strsep(&opts, ",")) != NULL) { + while ((ptr = strsep(&opts, ",\n")) != NULL) { if (!*ptr) continue; @@ -970,7 +742,7 @@ static ssize_t pscsi_set_configfs_dev_params(struct se_hba *hba, switch (token) { case Opt_scsi_host_id: if (phv->phv_mode == PHV_LLD_SCSI_HOST_NO) { - printk(KERN_ERR "PSCSI[%d]: Unable to accept" + pr_err("PSCSI[%d]: Unable to accept" " scsi_host_id while phv_mode ==" " PHV_LLD_SCSI_HOST_NO\n", phv->phv_host_id); @@ -979,14 +751,14 @@ static ssize_t pscsi_set_configfs_dev_params(struct se_hba *hba, } match_int(args, &arg); pdv->pdv_host_id = arg; - printk(KERN_INFO "PSCSI[%d]: Referencing SCSI Host ID:" + pr_debug("PSCSI[%d]: Referencing SCSI Host ID:" " %d\n", phv->phv_host_id, pdv->pdv_host_id); pdv->pdv_flags |= PDF_HAS_VIRT_HOST_ID; break; case Opt_scsi_channel_id: match_int(args, &arg); pdv->pdv_channel_id = arg; - printk(KERN_INFO "PSCSI[%d]: Referencing SCSI Channel" + pr_debug("PSCSI[%d]: Referencing SCSI Channel" " ID: %d\n", phv->phv_host_id, pdv->pdv_channel_id); pdv->pdv_flags |= PDF_HAS_CHANNEL_ID; @@ -994,7 +766,7 @@ static ssize_t pscsi_set_configfs_dev_params(struct se_hba *hba, case Opt_scsi_target_id: match_int(args, &arg); pdv->pdv_target_id = arg; - printk(KERN_INFO "PSCSI[%d]: Referencing SCSI Target" + pr_debug("PSCSI[%d]: Referencing SCSI Target" " ID: %d\n", phv->phv_host_id, pdv->pdv_target_id); pdv->pdv_flags |= PDF_HAS_TARGET_ID; @@ -1002,7 +774,7 @@ static ssize_t pscsi_set_configfs_dev_params(struct se_hba *hba, case Opt_scsi_lun_id: match_int(args, &arg); pdv->pdv_lun_id = arg; - printk(KERN_INFO "PSCSI[%d]: Referencing SCSI LUN ID:" + pr_debug("PSCSI[%d]: Referencing SCSI LUN ID:" " %d\n", phv->phv_host_id, pdv->pdv_lun_id); pdv->pdv_flags |= PDF_HAS_LUN_ID; break; @@ -1016,35 +788,16 @@ out: return (!ret) ? count : ret; } -static ssize_t pscsi_check_configfs_dev_params( - struct se_hba *hba, - struct se_subsystem_dev *se_dev) -{ - struct pscsi_dev_virt *pdv = se_dev->se_dev_su_ptr; - - if (!(pdv->pdv_flags & PDF_HAS_CHANNEL_ID) || - !(pdv->pdv_flags & PDF_HAS_TARGET_ID) || - !(pdv->pdv_flags & PDF_HAS_LUN_ID)) { - printk(KERN_ERR "Missing scsi_channel_id=, scsi_target_id= and" - " scsi_lun_id= parameters\n"); - return -1; - } - - return 0; -} - -static ssize_t pscsi_show_configfs_dev_params(struct se_hba *hba, - struct se_subsystem_dev *se_dev, - char *b) +static ssize_t pscsi_show_configfs_dev_params(struct se_device *dev, char *b) { - struct pscsi_hba_virt *phv = hba->hba_ptr; - struct pscsi_dev_virt *pdv = se_dev->se_dev_su_ptr; + struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr; + struct pscsi_dev_virt *pdv = PSCSI_DEV(dev); struct scsi_device *sd = pdv->pdv_sd; unsigned char host_id[16]; ssize_t bl; int i; - if (phv->phv_mode == PHV_VIRUTAL_HOST_ID) + if (phv->phv_mode == PHV_VIRTUAL_HOST_ID) snprintf(host_id, 16, "%d", pdv->pdv_host_id); else snprintf(host_id, 16, "PHBA Mode"); @@ -1087,16 +840,16 @@ static void pscsi_bi_endio(struct bio *bio, int error) bio_put(bio); } -static inline struct bio *pscsi_get_bio(struct pscsi_dev_virt *pdv, int sg_num) +static inline struct bio *pscsi_get_bio(int nr_vecs) { struct bio *bio; /* * Use bio_malloc() following the comment in for bio -> struct request * in block/blk-core.c:blk_make_request() */ - bio = bio_kmalloc(GFP_KERNEL, sg_num); - if (!(bio)) { - printk(KERN_ERR "PSCSI: bio_kmalloc() failed\n"); + bio = bio_kmalloc(GFP_KERNEL, nr_vecs); + if (!bio) { + pr_err("PSCSI: bio_kmalloc() failed\n"); return NULL; } bio->bi_end_io = pscsi_bi_endio; @@ -1104,85 +857,72 @@ static inline struct bio *pscsi_get_bio(struct pscsi_dev_virt *pdv, int sg_num) return bio; } -#if 0 -#define DEBUG_PSCSI(x...) printk(x) -#else -#define DEBUG_PSCSI(x...) -#endif - -static int __pscsi_map_task_SG( - struct se_task *task, - struct scatterlist *task_sg, - u32 task_sg_num, - int bidi_read) +static sense_reason_t +pscsi_map_sg(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents, + enum dma_data_direction data_direction, struct bio **hbio) { - struct pscsi_plugin_task *pt = PSCSI_TASK(task); - struct pscsi_dev_virt *pdv = task->se_dev->dev_ptr; - struct bio *bio = NULL, *hbio = NULL, *tbio = NULL; + struct pscsi_dev_virt *pdv = PSCSI_DEV(cmd->se_dev); + struct bio *bio = NULL, *tbio = NULL; struct page *page; struct scatterlist *sg; - u32 data_len = task->task_size, i, len, bytes, off; - int nr_pages = (task->task_size + task_sg[0].offset + + u32 data_len = cmd->data_length, i, len, bytes, off; + int nr_pages = (cmd->data_length + sgl[0].offset + PAGE_SIZE - 1) >> PAGE_SHIFT; - int nr_vecs = 0, rc, ret = PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES; - int rw = (task->task_data_direction == DMA_TO_DEVICE); + int nr_vecs = 0, rc; + int rw = (data_direction == DMA_TO_DEVICE); - if (!task->task_size) - return 0; - /* - * For SCF_SCSI_DATA_SG_IO_CDB, Use fs/bio.c:bio_add_page() to setup - * the bio_vec maplist from TC< struct se_mem -> task->task_sg -> - * struct scatterlist memory. The struct se_task->task_sg[] currently needs - * to be attached to struct bios for submission to Linux/SCSI using - * struct request to struct scsi_device->request_queue. - * - * Note that this will be changing post v2.6.28 as Target_Core_Mod/pSCSI - * is ported to upstream SCSI passthrough functionality that accepts - * struct scatterlist->page_link or struct page as a paraemeter. - */ - DEBUG_PSCSI("PSCSI: nr_pages: %d\n", nr_pages); + *hbio = NULL; + + pr_debug("PSCSI: nr_pages: %d\n", nr_pages); - for_each_sg(task_sg, sg, task_sg_num, i) { + for_each_sg(sgl, sg, sgl_nents, i) { page = sg_page(sg); off = sg->offset; len = sg->length; - DEBUG_PSCSI("PSCSI: i: %d page: %p len: %d off: %d\n", i, + pr_debug("PSCSI: i: %d page: %p len: %d off: %d\n", i, page, len, off); - while (len > 0 && data_len > 0) { + /* + * We only have one page of data in each sg element, + * we can not cross a page boundary. + */ + if (off + len > PAGE_SIZE) + goto fail; + + if (len > 0 && data_len > 0) { bytes = min_t(unsigned int, len, PAGE_SIZE - off); bytes = min(bytes, data_len); - if (!(bio)) { + if (!bio) { nr_vecs = min_t(int, BIO_MAX_PAGES, nr_pages); nr_pages -= nr_vecs; /* * Calls bio_kmalloc() and sets bio->bi_end_io() */ - bio = pscsi_get_bio(pdv, nr_vecs); - if (!(bio)) + bio = pscsi_get_bio(nr_vecs); + if (!bio) goto fail; if (rw) bio->bi_rw |= REQ_WRITE; - DEBUG_PSCSI("PSCSI: Allocated bio: %p," + pr_debug("PSCSI: Allocated bio: %p," " dir: %s nr_vecs: %d\n", bio, (rw) ? "rw" : "r", nr_vecs); /* * Set *hbio pointer to handle the case: * nr_pages > BIO_MAX_PAGES, where additional * bios need to be added to complete a given - * struct se_task + * command. */ - if (!hbio) - hbio = tbio = bio; + if (!*hbio) + *hbio = tbio = bio; else tbio = tbio->bi_next = bio; } - DEBUG_PSCSI("PSCSI: Calling bio_add_pc_page() i: %d" + pr_debug("PSCSI: Calling bio_add_pc_page() i: %d" " bio: %p page: %p len: %d off: %d\n", i, bio, page, len, off); @@ -1191,11 +931,11 @@ static int __pscsi_map_task_SG( if (rc != bytes) goto fail; - DEBUG_PSCSI("PSCSI: bio->bi_vcnt: %d nr_vecs: %d\n", + pr_debug("PSCSI: bio->bi_vcnt: %d nr_vecs: %d\n", bio->bi_vcnt, nr_vecs); if (bio->bi_vcnt > nr_vecs) { - DEBUG_PSCSI("PSCSI: Reached bio->bi_vcnt max:" + pr_debug("PSCSI: Reached bio->bi_vcnt max:" " %d i: %d bio: %p, allocating another" " bio\n", bio->bi_vcnt, i, bio); /* @@ -1207,145 +947,160 @@ static int __pscsi_map_task_SG( bio = NULL; } - page++; - len -= bytes; data_len -= bytes; - off = 0; } } - /* - * Setup the primary pt->pscsi_req used for non BIDI and BIDI-COMMAND - * primary SCSI WRITE poayload mapped for struct se_task->task_sg[] - */ - if (!(bidi_read)) { - /* - * Starting with v2.6.31, call blk_make_request() passing in *hbio to - * allocate the pSCSI task a struct request. - */ - pt->pscsi_req = blk_make_request(pdv->pdv_sd->request_queue, - hbio, GFP_KERNEL); - if (!(pt->pscsi_req)) { - printk(KERN_ERR "pSCSI: blk_make_request() failed\n"); - goto fail; - } - /* - * Setup the newly allocated struct request for REQ_TYPE_BLOCK_PC, - * and setup rq callback, CDB and sense. - */ - pscsi_blk_init_request(task, pt, pt->pscsi_req, 0); - - return task->task_sg_num; - } - /* - * Setup the secondary pt->pscsi_req->next_rq used for the extra BIDI-COMMAND - * SCSI READ paylaod mapped for struct se_task->task_sg_bidi[] - */ - pt->pscsi_req->next_rq = blk_make_request(pdv->pdv_sd->request_queue, - hbio, GFP_KERNEL); - if (!(pt->pscsi_req->next_rq)) { - printk(KERN_ERR "pSCSI: blk_make_request() failed for BIDI\n"); - goto fail; - } - pscsi_blk_init_request(task, pt, pt->pscsi_req->next_rq, 1); - return task->task_sg_num; + return 0; fail: - while (hbio) { - bio = hbio; - hbio = hbio->bi_next; - bio->bi_next = NULL; - bio_endio(bio, 0); + while (*hbio) { + bio = *hbio; + *hbio = (*hbio)->bi_next; + bio_endio(bio, 0); /* XXX: should be error */ } - return ret; + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } -static int pscsi_map_task_SG(struct se_task *task) +/* + * Clear a lun set in the cdb if the initiator talking to use spoke + * and old standards version, as we can't assume the underlying device + * won't choke up on it. + */ +static inline void pscsi_clear_cdb_lun(unsigned char *cdb) { - int ret; - - /* - * Setup the main struct request for the task->task_sg[] payload - */ - - ret = __pscsi_map_task_SG(task, task->task_sg, task->task_sg_num, 0); - if (ret >= 0 && task->task_sg_bidi) { - /* - * If present, set up the extra BIDI-COMMAND SCSI READ - * struct request and payload. - */ - ret = __pscsi_map_task_SG(task, task->task_sg_bidi, - task->task_sg_num, 1); + switch (cdb[0]) { + case READ_10: /* SBC - RDProtect */ + case READ_12: /* SBC - RDProtect */ + case READ_16: /* SBC - RDProtect */ + case SEND_DIAGNOSTIC: /* SPC - SELF-TEST Code */ + case VERIFY: /* SBC - VRProtect */ + case VERIFY_16: /* SBC - VRProtect */ + case WRITE_VERIFY: /* SBC - VRProtect */ + case WRITE_VERIFY_12: /* SBC - VRProtect */ + case MAINTENANCE_IN: /* SPC - Parameter Data Format for SA RTPG */ + break; + default: + cdb[1] &= 0x1f; /* clear logical unit number */ + break; } - - if (ret < 0) - return PYX_TRANSPORT_LU_COMM_FAILURE; - return 0; } -/* pscsi_map_task_non_SG(): - * - * - */ -static int pscsi_map_task_non_SG(struct se_task *task) +static sense_reason_t +pscsi_parse_cdb(struct se_cmd *cmd) { - struct se_cmd *cmd = TASK_CMD(task); - struct pscsi_plugin_task *pt = PSCSI_TASK(task); - struct pscsi_dev_virt *pdv = task->se_dev->dev_ptr; - int ret = 0; + unsigned char *cdb = cmd->t_task_cdb; - if (pscsi_blk_get_request(task) < 0) - return PYX_TRANSPORT_LU_COMM_FAILURE; + if (cmd->se_cmd_flags & SCF_BIDI) + return TCM_UNSUPPORTED_SCSI_OPCODE; - if (!task->task_size) - return 0; + pscsi_clear_cdb_lun(cdb); - ret = blk_rq_map_kern(pdv->pdv_sd->request_queue, - pt->pscsi_req, T_TASK(cmd)->t_task_buf, - task->task_size, GFP_KERNEL); - if (ret < 0) { - printk(KERN_ERR "PSCSI: blk_rq_map_kern() failed: %d\n", ret); - return PYX_TRANSPORT_LU_COMM_FAILURE; + /* + * For REPORT LUNS we always need to emulate the response, for everything + * else the default for pSCSI is to pass the command to the underlying + * LLD / physical hardware. + */ + switch (cdb[0]) { + case REPORT_LUNS: + cmd->execute_cmd = spc_emulate_report_luns; + return 0; + case READ_6: + case READ_10: + case READ_12: + case READ_16: + case WRITE_6: + case WRITE_10: + case WRITE_12: + case WRITE_16: + case WRITE_VERIFY: + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + /* FALLTHROUGH*/ + default: + cmd->execute_cmd = pscsi_execute_cmd; + return 0; } - return 0; } -static int pscsi_CDB_none(struct se_task *task) +static sense_reason_t +pscsi_execute_cmd(struct se_cmd *cmd) { - return pscsi_blk_get_request(task); -} + struct scatterlist *sgl = cmd->t_data_sg; + u32 sgl_nents = cmd->t_data_nents; + enum dma_data_direction data_direction = cmd->data_direction; + struct pscsi_dev_virt *pdv = PSCSI_DEV(cmd->se_dev); + struct pscsi_plugin_task *pt; + struct request *req; + struct bio *hbio; + sense_reason_t ret; -/* pscsi_get_cdb(): - * - * - */ -static unsigned char *pscsi_get_cdb(struct se_task *task) -{ - struct pscsi_plugin_task *pt = PSCSI_TASK(task); + /* + * Dynamically alloc cdb space, since it may be larger than + * TCM_MAX_COMMAND_SIZE + */ + pt = kzalloc(sizeof(*pt) + scsi_command_size(cmd->t_task_cdb), GFP_KERNEL); + if (!pt) { + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + cmd->priv = pt; + + memcpy(pt->pscsi_cdb, cmd->t_task_cdb, + scsi_command_size(cmd->t_task_cdb)); + + if (!sgl) { + req = blk_get_request(pdv->pdv_sd->request_queue, + (data_direction == DMA_TO_DEVICE), + GFP_KERNEL); + if (!req) { + pr_err("PSCSI: blk_get_request() failed\n"); + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto fail; + } - return pt->pscsi_cdb; -} + blk_rq_set_block_pc(req); + } else { + BUG_ON(!cmd->data_length); -/* pscsi_get_sense_buffer(): - * - * - */ -static unsigned char *pscsi_get_sense_buffer(struct se_task *task) -{ - struct pscsi_plugin_task *pt = PSCSI_TASK(task); + ret = pscsi_map_sg(cmd, sgl, sgl_nents, data_direction, &hbio); + if (ret) + goto fail; - return (unsigned char *)&pt->pscsi_sense[0]; -} + req = blk_make_request(pdv->pdv_sd->request_queue, hbio, + GFP_KERNEL); + if (IS_ERR(req)) { + pr_err("pSCSI: blk_make_request() failed\n"); + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto fail_free_bio; + } + } -/* pscsi_get_device_rev(): - * - * - */ -static u32 pscsi_get_device_rev(struct se_device *dev) -{ - struct pscsi_dev_virt *pdv = dev->dev_ptr; - struct scsi_device *sd = pdv->pdv_sd; + req->end_io = pscsi_req_done; + req->end_io_data = cmd; + req->cmd_len = scsi_command_size(pt->pscsi_cdb); + req->cmd = &pt->pscsi_cdb[0]; + req->sense = &pt->pscsi_sense[0]; + req->sense_len = 0; + if (pdv->pdv_sd->type == TYPE_DISK) + req->timeout = PS_TIMEOUT_DISK; + else + req->timeout = PS_TIMEOUT_OTHER; + req->retries = PS_RETRY; + + blk_execute_rq_nowait(pdv->pdv_sd->request_queue, NULL, req, + (cmd->sam_task_attr == MSG_HEAD_TAG), + pscsi_req_done); - return (sd->scsi_level - 1) ? sd->scsi_level - 1 : 1; + return 0; + +fail_free_bio: + while (hbio) { + struct bio *bio = hbio; + hbio = hbio->bi_next; + bio_endio(bio, 0); /* XXX: should be error */ + } + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; +fail: + kfree(pt); + return ret; } /* pscsi_get_device_type(): @@ -1354,7 +1109,7 @@ static u32 pscsi_get_device_rev(struct se_device *dev) */ static u32 pscsi_get_device_type(struct se_device *dev) { - struct pscsi_dev_virt *pdv = dev->dev_ptr; + struct pscsi_dev_virt *pdv = PSCSI_DEV(dev); struct scsi_device *sd = pdv->pdv_sd; return sd->type; @@ -1362,7 +1117,7 @@ static u32 pscsi_get_device_type(struct se_device *dev) static sector_t pscsi_get_blocks(struct se_device *dev) { - struct pscsi_dev_virt *pdv = dev->dev_ptr; + struct pscsi_dev_virt *pdv = PSCSI_DEV(dev); if (pdv->pdv_bd && pdv->pdv_bd->bd_part) return pdv->pdv_bd->bd_part->nr_sects; @@ -1371,83 +1126,51 @@ static sector_t pscsi_get_blocks(struct se_device *dev) return 0; } -/* pscsi_handle_SAM_STATUS_failures(): - * - * - */ -static inline void pscsi_process_SAM_status( - struct se_task *task, - struct pscsi_plugin_task *pt) +static void pscsi_req_done(struct request *req, int uptodate) { - task->task_scsi_status = status_byte(pt->pscsi_result); - if ((task->task_scsi_status)) { - task->task_scsi_status <<= 1; - printk(KERN_INFO "PSCSI Status Byte exception at task: %p CDB:" - " 0x%02x Result: 0x%08x\n", task, pt->pscsi_cdb[0], + struct se_cmd *cmd = req->end_io_data; + struct pscsi_plugin_task *pt = cmd->priv; + + pt->pscsi_result = req->errors; + pt->pscsi_resid = req->resid_len; + + cmd->scsi_status = status_byte(pt->pscsi_result) << 1; + if (cmd->scsi_status) { + pr_debug("PSCSI Status Byte exception at cmd: %p CDB:" + " 0x%02x Result: 0x%08x\n", cmd, pt->pscsi_cdb[0], pt->pscsi_result); } switch (host_byte(pt->pscsi_result)) { case DID_OK: - transport_complete_task(task, (!task->task_scsi_status)); + target_complete_cmd(cmd, cmd->scsi_status); break; default: - printk(KERN_INFO "PSCSI Host Byte exception at task: %p CDB:" - " 0x%02x Result: 0x%08x\n", task, pt->pscsi_cdb[0], + pr_debug("PSCSI Host Byte exception at cmd: %p CDB:" + " 0x%02x Result: 0x%08x\n", cmd, pt->pscsi_cdb[0], pt->pscsi_result); - task->task_scsi_status = SAM_STAT_CHECK_CONDITION; - task->task_error_status = PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - TASK_CMD(task)->transport_error_status = - PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - transport_complete_task(task, 0); + target_complete_cmd(cmd, SAM_STAT_CHECK_CONDITION); break; } - return; -} - -static void pscsi_req_done(struct request *req, int uptodate) -{ - struct se_task *task = req->end_io_data; - struct pscsi_plugin_task *pt = PSCSI_TASK(task); - - pt->pscsi_result = req->errors; - pt->pscsi_resid = req->resid_len; - - pscsi_process_SAM_status(task, pt); - /* - * Release BIDI-READ if present - */ - if (req->next_rq != NULL) - __blk_put_request(req->q, req->next_rq); - __blk_put_request(req->q, req); - pt->pscsi_req = NULL; + kfree(pt); } static struct se_subsystem_api pscsi_template = { .name = "pscsi", .owner = THIS_MODULE, .transport_type = TRANSPORT_PLUGIN_PHBA_PDEV, - .cdb_none = pscsi_CDB_none, - .map_task_non_SG = pscsi_map_task_non_SG, - .map_task_SG = pscsi_map_task_SG, .attach_hba = pscsi_attach_hba, .detach_hba = pscsi_detach_hba, .pmode_enable_hba = pscsi_pmode_enable_hba, - .allocate_virtdevice = pscsi_allocate_virtdevice, - .create_virtdevice = pscsi_create_virtdevice, + .alloc_device = pscsi_alloc_device, + .configure_device = pscsi_configure_device, .free_device = pscsi_free_device, .transport_complete = pscsi_transport_complete, - .alloc_task = pscsi_alloc_task, - .do_task = pscsi_do_task, - .free_task = pscsi_free_task, - .check_configfs_dev_params = pscsi_check_configfs_dev_params, + .parse_cdb = pscsi_parse_cdb, .set_configfs_dev_params = pscsi_set_configfs_dev_params, .show_configfs_dev_params = pscsi_show_configfs_dev_params, - .get_cdb = pscsi_get_cdb, - .get_sense_buffer = pscsi_get_sense_buffer, - .get_device_rev = pscsi_get_device_rev, .get_device_type = pscsi_get_device_type, .get_blocks = pscsi_get_blocks, }; @@ -1457,7 +1180,7 @@ static int __init pscsi_module_init(void) return transport_subsystem_register(&pscsi_template); } -static void pscsi_module_exit(void) +static void __exit pscsi_module_exit(void) { transport_subsystem_release(&pscsi_template); } diff --git a/drivers/target/target_core_pscsi.h b/drivers/target/target_core_pscsi.h index a4cd5d352c3..1bd757dff8e 100644 --- a/drivers/target/target_core_pscsi.h +++ b/drivers/target/target_core_pscsi.h @@ -2,7 +2,6 @@ #define TARGET_CORE_PSCSI_H #define PSCSI_VERSION "v4.0" -#define PSCSI_VIRTUAL_HBA_DEPTH 2048 /* used in pscsi_find_alloc_len() */ #ifndef INQUIRY_DATA_SIZE @@ -23,14 +22,11 @@ #include <linux/kobject.h> struct pscsi_plugin_task { - struct se_task pscsi_task; - unsigned char *pscsi_cdb; - unsigned char __pscsi_cdb[TCM_MAX_COMMAND_SIZE]; unsigned char pscsi_sense[SCSI_SENSE_BUFFERSIZE]; int pscsi_direction; int pscsi_result; u32 pscsi_resid; - struct request *pscsi_req; + unsigned char pscsi_cdb[0]; } ____cacheline_aligned; #define PDF_HAS_CHANNEL_ID 0x01 @@ -41,6 +37,7 @@ struct pscsi_plugin_task { #define PDF_HAS_VIRT_HOST_ID 0x20 struct pscsi_dev_virt { + struct se_device dev; int pdv_flags; int pdv_host_id; int pdv_channel_id; @@ -48,11 +45,10 @@ struct pscsi_dev_virt { int pdv_lun_id; struct block_device *pdv_bd; struct scsi_device *pdv_sd; - struct se_hba *pdv_se_hba; } ____cacheline_aligned; typedef enum phv_modes { - PHV_VIRUTAL_HOST_ID, + PHV_VIRTUAL_HOST_ID, PHV_LLD_SCSI_HOST_NO } phv_modes_t; diff --git a/drivers/target/target_core_rd.c b/drivers/target/target_core_rd.c index 979aebf2001..b920db3388c 100644 --- a/drivers/target/target_core_rd.c +++ b/drivers/target/target_core_rd.c @@ -4,10 +4,7 @@ * This file contains the Storage Engine <-> Ramdisk transport * specific functions. * - * Copyright (c) 2003, 2004, 2005 PyX Technologies, Inc. - * Copyright (c) 2005, 2006, 2007 SBE, Inc. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2003-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -27,29 +24,23 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/string.h> #include <linux/parser.h> #include <linux/timer.h> -#include <linux/blkdev.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> #include <scsi/scsi.h> #include <scsi/scsi_host.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_backend.h> #include "target_core_rd.h" -static struct se_subsystem_api rd_dr_template; -static struct se_subsystem_api rd_mcp_template; - -/* #define DEBUG_RAMDISK_MCP */ -/* #define DEBUG_RAMDISK_DR */ +static inline struct rd_dev *RD_DEV(struct se_device *dev) +{ + return container_of(dev, struct rd_dev, dev); +} /* rd_attach_hba(): (Part of se_subsystem_api_t template) * @@ -60,24 +51,18 @@ static int rd_attach_hba(struct se_hba *hba, u32 host_id) struct rd_host *rd_host; rd_host = kzalloc(sizeof(struct rd_host), GFP_KERNEL); - if (!(rd_host)) { - printk(KERN_ERR "Unable to allocate memory for struct rd_host\n"); + if (!rd_host) { + pr_err("Unable to allocate memory for struct rd_host\n"); return -ENOMEM; } rd_host->rd_host_id = host_id; - atomic_set(&hba->left_queue_depth, RD_HBA_QUEUE_DEPTH); - atomic_set(&hba->max_queue_depth, RD_HBA_QUEUE_DEPTH); - hba->hba_ptr = (void *) rd_host; + hba->hba_ptr = rd_host; - printk(KERN_INFO "CORE_HBA[%d] - TCM Ramdisk HBA Driver %s on" + pr_debug("CORE_HBA[%d] - TCM Ramdisk HBA Driver %s on" " Generic Target Core Stack %s\n", hba->hba_id, RD_HBA_VERSION, TARGET_CORE_MOD_VERSION); - printk(KERN_INFO "CORE_HBA[%d] - Attached Ramdisk HBA: %u to Generic" - " Target Core TCQ Depth: %d MaxSectors: %u\n", hba->hba_id, - rd_host->rd_host_id, atomic_read(&hba->max_queue_depth), - RD_MAX_SECTORS); return 0; } @@ -86,50 +71,53 @@ static void rd_detach_hba(struct se_hba *hba) { struct rd_host *rd_host = hba->hba_ptr; - printk(KERN_INFO "CORE_HBA[%d] - Detached Ramdisk HBA: %u from" + pr_debug("CORE_HBA[%d] - Detached Ramdisk HBA: %u from" " Generic Target Core\n", hba->hba_id, rd_host->rd_host_id); kfree(rd_host); hba->hba_ptr = NULL; } -/* rd_release_device_space(): - * - * - */ -static void rd_release_device_space(struct rd_dev *rd_dev) +static u32 rd_release_sgl_table(struct rd_dev *rd_dev, struct rd_dev_sg_table *sg_table, + u32 sg_table_count) { - u32 i, j, page_count = 0, sg_per_table; - struct rd_dev_sg_table *sg_table; struct page *pg; struct scatterlist *sg; + u32 i, j, page_count = 0, sg_per_table; - if (!rd_dev->sg_table_array || !rd_dev->sg_table_count) - return; - - sg_table = rd_dev->sg_table_array; - - for (i = 0; i < rd_dev->sg_table_count; i++) { + for (i = 0; i < sg_table_count; i++) { sg = sg_table[i].sg_table; sg_per_table = sg_table[i].rd_sg_count; for (j = 0; j < sg_per_table; j++) { pg = sg_page(&sg[j]); - if ((pg)) { + if (pg) { __free_page(pg); page_count++; } } - kfree(sg); } - printk(KERN_INFO "CORE_RD[%u] - Released device space for Ramdisk" + kfree(sg_table); + return page_count; +} + +static void rd_release_device_space(struct rd_dev *rd_dev) +{ + u32 page_count; + + if (!rd_dev->sg_table_array || !rd_dev->sg_table_count) + return; + + page_count = rd_release_sgl_table(rd_dev, rd_dev->sg_table_array, + rd_dev->sg_table_count); + + pr_debug("CORE_RD[%u] - Released device space for Ramdisk" " Device ID: %u, pages %u in %u tables total bytes %lu\n", rd_dev->rd_host->rd_host_id, rd_dev->rd_dev_id, page_count, rd_dev->sg_table_count, (unsigned long)page_count * PAGE_SIZE); - kfree(sg_table); rd_dev->sg_table_array = NULL; rd_dev->sg_table_count = 0; } @@ -139,33 +127,15 @@ static void rd_release_device_space(struct rd_dev *rd_dev) * * */ -static int rd_build_device_space(struct rd_dev *rd_dev) +static int rd_allocate_sgl_table(struct rd_dev *rd_dev, struct rd_dev_sg_table *sg_table, + u32 total_sg_needed, unsigned char init_payload) { - u32 i = 0, j, page_offset = 0, sg_per_table, sg_tables, total_sg_needed; + u32 i = 0, j, page_offset = 0, sg_per_table; u32 max_sg_per_table = (RD_MAX_ALLOCATION_SIZE / sizeof(struct scatterlist)); - struct rd_dev_sg_table *sg_table; struct page *pg; struct scatterlist *sg; - - if (rd_dev->rd_page_count <= 0) { - printk(KERN_ERR "Illegal page count: %u for Ramdisk device\n", - rd_dev->rd_page_count); - return -1; - } - total_sg_needed = rd_dev->rd_page_count; - - sg_tables = (total_sg_needed / max_sg_per_table) + 1; - - sg_table = kzalloc(sg_tables * sizeof(struct rd_dev_sg_table), GFP_KERNEL); - if (!(sg_table)) { - printk(KERN_ERR "Unable to allocate memory for Ramdisk" - " scatterlist tables\n"); - return -1; - } - - rd_dev->sg_table_array = sg_table; - rd_dev->sg_table_count = sg_tables; + unsigned char *p; while (total_sg_needed) { sg_per_table = (total_sg_needed > max_sg_per_table) ? @@ -173,13 +143,13 @@ static int rd_build_device_space(struct rd_dev *rd_dev) sg = kzalloc(sg_per_table * sizeof(struct scatterlist), GFP_KERNEL); - if (!(sg)) { - printk(KERN_ERR "Unable to allocate scatterlist array" + if (!sg) { + pr_err("Unable to allocate scatterlist array" " for struct rd_dev\n"); - return -1; + return -ENOMEM; } - sg_init_table((struct scatterlist *)&sg[0], sg_per_table); + sg_init_table(sg, sg_per_table); sg_table[i].sg_table = sg; sg_table[i].rd_sg_count = sg_per_table; @@ -189,754 +159,388 @@ static int rd_build_device_space(struct rd_dev *rd_dev) for (j = 0; j < sg_per_table; j++) { pg = alloc_pages(GFP_KERNEL, 0); - if (!(pg)) { - printk(KERN_ERR "Unable to allocate scatterlist" + if (!pg) { + pr_err("Unable to allocate scatterlist" " pages for struct rd_dev_sg_table\n"); - return -1; + return -ENOMEM; } sg_assign_page(&sg[j], pg); sg[j].length = PAGE_SIZE; + + p = kmap(pg); + memset(p, init_payload, PAGE_SIZE); + kunmap(pg); } page_offset += sg_per_table; total_sg_needed -= sg_per_table; } - printk(KERN_INFO "CORE_RD[%u] - Built Ramdisk Device ID: %u space of" - " %u pages in %u tables\n", rd_dev->rd_host->rd_host_id, - rd_dev->rd_dev_id, rd_dev->rd_page_count, - rd_dev->sg_table_count); - return 0; } -static void *rd_allocate_virtdevice( - struct se_hba *hba, - const char *name, - int rd_direct) +static int rd_build_device_space(struct rd_dev *rd_dev) { - struct rd_dev *rd_dev; - struct rd_host *rd_host = hba->hba_ptr; + struct rd_dev_sg_table *sg_table; + u32 sg_tables, total_sg_needed; + u32 max_sg_per_table = (RD_MAX_ALLOCATION_SIZE / + sizeof(struct scatterlist)); + int rc; - rd_dev = kzalloc(sizeof(struct rd_dev), GFP_KERNEL); - if (!(rd_dev)) { - printk(KERN_ERR "Unable to allocate memory for struct rd_dev\n"); - return NULL; + if (rd_dev->rd_page_count <= 0) { + pr_err("Illegal page count: %u for Ramdisk device\n", + rd_dev->rd_page_count); + return -EINVAL; } - rd_dev->rd_host = rd_host; - rd_dev->rd_direct = rd_direct; + /* Don't need backing pages for NULLIO */ + if (rd_dev->rd_flags & RDF_NULLIO) + return 0; + + total_sg_needed = rd_dev->rd_page_count; + + sg_tables = (total_sg_needed / max_sg_per_table) + 1; + + sg_table = kzalloc(sg_tables * sizeof(struct rd_dev_sg_table), GFP_KERNEL); + if (!sg_table) { + pr_err("Unable to allocate memory for Ramdisk" + " scatterlist tables\n"); + return -ENOMEM; + } + + rd_dev->sg_table_array = sg_table; + rd_dev->sg_table_count = sg_tables; + + rc = rd_allocate_sgl_table(rd_dev, sg_table, total_sg_needed, 0x00); + if (rc) + return rc; - return rd_dev; + pr_debug("CORE_RD[%u] - Built Ramdisk Device ID: %u space of" + " %u pages in %u tables\n", rd_dev->rd_host->rd_host_id, + rd_dev->rd_dev_id, rd_dev->rd_page_count, + rd_dev->sg_table_count); + + return 0; } -static void *rd_DIRECT_allocate_virtdevice(struct se_hba *hba, const char *name) +static void rd_release_prot_space(struct rd_dev *rd_dev) { - return rd_allocate_virtdevice(hba, name, 1); + u32 page_count; + + if (!rd_dev->sg_prot_array || !rd_dev->sg_prot_count) + return; + + page_count = rd_release_sgl_table(rd_dev, rd_dev->sg_prot_array, + rd_dev->sg_prot_count); + + pr_debug("CORE_RD[%u] - Released protection space for Ramdisk" + " Device ID: %u, pages %u in %u tables total bytes %lu\n", + rd_dev->rd_host->rd_host_id, rd_dev->rd_dev_id, page_count, + rd_dev->sg_table_count, (unsigned long)page_count * PAGE_SIZE); + + rd_dev->sg_prot_array = NULL; + rd_dev->sg_prot_count = 0; } -static void *rd_MEMCPY_allocate_virtdevice(struct se_hba *hba, const char *name) +static int rd_build_prot_space(struct rd_dev *rd_dev, int prot_length, int block_size) { - return rd_allocate_virtdevice(hba, name, 0); + struct rd_dev_sg_table *sg_table; + u32 total_sg_needed, sg_tables; + u32 max_sg_per_table = (RD_MAX_ALLOCATION_SIZE / + sizeof(struct scatterlist)); + int rc; + + if (rd_dev->rd_flags & RDF_NULLIO) + return 0; + /* + * prot_length=8byte dif data + * tot sg needed = rd_page_count * (PGSZ/block_size) * + * (prot_length/block_size) + pad + * PGSZ canceled each other. + */ + total_sg_needed = (rd_dev->rd_page_count * prot_length / block_size) + 1; + + sg_tables = (total_sg_needed / max_sg_per_table) + 1; + + sg_table = kzalloc(sg_tables * sizeof(struct rd_dev_sg_table), GFP_KERNEL); + if (!sg_table) { + pr_err("Unable to allocate memory for Ramdisk protection" + " scatterlist tables\n"); + return -ENOMEM; + } + + rd_dev->sg_prot_array = sg_table; + rd_dev->sg_prot_count = sg_tables; + + rc = rd_allocate_sgl_table(rd_dev, sg_table, total_sg_needed, 0xff); + if (rc) + return rc; + + pr_debug("CORE_RD[%u] - Built Ramdisk Device ID: %u prot space of" + " %u pages in %u tables\n", rd_dev->rd_host->rd_host_id, + rd_dev->rd_dev_id, total_sg_needed, rd_dev->sg_prot_count); + + return 0; } -/* rd_create_virtdevice(): - * - * - */ -static struct se_device *rd_create_virtdevice( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - void *p, - int rd_direct) +static struct se_device *rd_alloc_device(struct se_hba *hba, const char *name) { - struct se_device *dev; - struct se_dev_limits dev_limits; - struct rd_dev *rd_dev = p; + struct rd_dev *rd_dev; struct rd_host *rd_host = hba->hba_ptr; - int dev_flags = 0; - char prod[16], rev[4]; - memset(&dev_limits, 0, sizeof(struct se_dev_limits)); + rd_dev = kzalloc(sizeof(struct rd_dev), GFP_KERNEL); + if (!rd_dev) { + pr_err("Unable to allocate memory for struct rd_dev\n"); + return NULL; + } + + rd_dev->rd_host = rd_host; - if (rd_build_device_space(rd_dev) < 0) - goto fail; + return &rd_dev->dev; +} - snprintf(prod, 16, "RAMDISK-%s", (rd_dev->rd_direct) ? "DR" : "MCP"); - snprintf(rev, 4, "%s", (rd_dev->rd_direct) ? RD_DR_VERSION : - RD_MCP_VERSION); - - dev_limits.limits.logical_block_size = RD_BLOCKSIZE; - dev_limits.limits.max_hw_sectors = RD_MAX_SECTORS; - dev_limits.limits.max_sectors = RD_MAX_SECTORS; - dev_limits.hw_queue_depth = RD_MAX_DEVICE_QUEUE_DEPTH; - dev_limits.queue_depth = RD_DEVICE_QUEUE_DEPTH; - - dev = transport_add_device_to_core_hba(hba, - (rd_dev->rd_direct) ? &rd_dr_template : - &rd_mcp_template, se_dev, dev_flags, (void *)rd_dev, - &dev_limits, prod, rev); - if (!(dev)) +static int rd_configure_device(struct se_device *dev) +{ + struct rd_dev *rd_dev = RD_DEV(dev); + struct rd_host *rd_host = dev->se_hba->hba_ptr; + int ret; + + if (!(rd_dev->rd_flags & RDF_HAS_PAGE_COUNT)) { + pr_debug("Missing rd_pages= parameter\n"); + return -EINVAL; + } + + ret = rd_build_device_space(rd_dev); + if (ret < 0) goto fail; + dev->dev_attrib.hw_block_size = RD_BLOCKSIZE; + dev->dev_attrib.hw_max_sectors = UINT_MAX; + dev->dev_attrib.hw_queue_depth = RD_MAX_DEVICE_QUEUE_DEPTH; + rd_dev->rd_dev_id = rd_host->rd_host_dev_id_count++; - rd_dev->rd_queue_depth = dev->queue_depth; - printk(KERN_INFO "CORE_RD[%u] - Added TCM %s Ramdisk Device ID: %u of" + pr_debug("CORE_RD[%u] - Added TCM MEMCPY Ramdisk Device ID: %u of" " %u pages in %u tables, %lu total bytes\n", - rd_host->rd_host_id, (!rd_dev->rd_direct) ? "MEMCPY" : - "DIRECT", rd_dev->rd_dev_id, rd_dev->rd_page_count, + rd_host->rd_host_id, rd_dev->rd_dev_id, rd_dev->rd_page_count, rd_dev->sg_table_count, (unsigned long)(rd_dev->rd_page_count * PAGE_SIZE)); - return dev; + return 0; fail: rd_release_device_space(rd_dev); - return NULL; -} - -static struct se_device *rd_DIRECT_create_virtdevice( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - void *p) -{ - return rd_create_virtdevice(hba, se_dev, p, 1); + return ret; } -static struct se_device *rd_MEMCPY_create_virtdevice( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - void *p) +static void rd_free_device(struct se_device *dev) { - return rd_create_virtdevice(hba, se_dev, p, 0); -} - -/* rd_free_device(): (Part of se_subsystem_api_t template) - * - * - */ -static void rd_free_device(void *p) -{ - struct rd_dev *rd_dev = p; + struct rd_dev *rd_dev = RD_DEV(dev); rd_release_device_space(rd_dev); kfree(rd_dev); } -static inline struct rd_request *RD_REQ(struct se_task *task) -{ - return container_of(task, struct rd_request, rd_task); -} - -static struct se_task * -rd_alloc_task(struct se_cmd *cmd) -{ - struct rd_request *rd_req; - - rd_req = kzalloc(sizeof(struct rd_request), GFP_KERNEL); - if (!rd_req) { - printk(KERN_ERR "Unable to allocate struct rd_request\n"); - return NULL; - } - rd_req->rd_dev = SE_DEV(cmd)->dev_ptr; - - return &rd_req->rd_task; -} - -/* rd_get_sg_table(): - * - * - */ static struct rd_dev_sg_table *rd_get_sg_table(struct rd_dev *rd_dev, u32 page) { - u32 i; struct rd_dev_sg_table *sg_table; + u32 i, sg_per_table = (RD_MAX_ALLOCATION_SIZE / + sizeof(struct scatterlist)); - for (i = 0; i < rd_dev->sg_table_count; i++) { + i = page / sg_per_table; + if (i < rd_dev->sg_table_count) { sg_table = &rd_dev->sg_table_array[i]; if ((sg_table->page_start_offset <= page) && (sg_table->page_end_offset >= page)) return sg_table; } - printk(KERN_ERR "Unable to locate struct rd_dev_sg_table for page: %u\n", + pr_err("Unable to locate struct rd_dev_sg_table for page: %u\n", page); return NULL; } -/* rd_MEMCPY_read(): - * - * - */ -static int rd_MEMCPY_read(struct rd_request *req) +static struct rd_dev_sg_table *rd_get_prot_table(struct rd_dev *rd_dev, u32 page) { - struct se_task *task = &req->rd_task; - struct rd_dev *dev = req->rd_dev; - struct rd_dev_sg_table *table; - struct scatterlist *sg_d, *sg_s; - void *dst, *src; - u32 i = 0, j = 0, dst_offset = 0, src_offset = 0; - u32 length, page_end = 0, table_sg_end; - u32 rd_offset = req->rd_offset; - - table = rd_get_sg_table(dev, req->rd_page); - if (!(table)) - return -1; - - table_sg_end = (table->page_end_offset - req->rd_page); - sg_d = task->task_sg; - sg_s = &table->sg_table[req->rd_page - table->page_start_offset]; -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "RD[%u]: Read LBA: %llu, Size: %u Page: %u, Offset:" - " %u\n", dev->rd_dev_id, task->task_lba, req->rd_size, - req->rd_page, req->rd_offset); -#endif - src_offset = rd_offset; - - while (req->rd_size) { - if ((sg_d[i].length - dst_offset) < - (sg_s[j].length - src_offset)) { - length = (sg_d[i].length - dst_offset); -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "Step 1 - sg_d[%d]: %p length: %d" - " offset: %u sg_s[%d].length: %u\n", i, - &sg_d[i], sg_d[i].length, sg_d[i].offset, j, - sg_s[j].length); - printk(KERN_INFO "Step 1 - length: %u dst_offset: %u" - " src_offset: %u\n", length, dst_offset, - src_offset); -#endif - if (length > req->rd_size) - length = req->rd_size; - - dst = sg_virt(&sg_d[i++]) + dst_offset; - if (!dst) - BUG(); - - src = sg_virt(&sg_s[j]) + src_offset; - if (!src) - BUG(); - - dst_offset = 0; - src_offset = length; - page_end = 0; - } else { - length = (sg_s[j].length - src_offset); -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "Step 2 - sg_d[%d]: %p length: %d" - " offset: %u sg_s[%d].length: %u\n", i, - &sg_d[i], sg_d[i].length, sg_d[i].offset, - j, sg_s[j].length); - printk(KERN_INFO "Step 2 - length: %u dst_offset: %u" - " src_offset: %u\n", length, dst_offset, - src_offset); -#endif - if (length > req->rd_size) - length = req->rd_size; - - dst = sg_virt(&sg_d[i]) + dst_offset; - if (!dst) - BUG(); - - if (sg_d[i].length == length) { - i++; - dst_offset = 0; - } else - dst_offset = length; - - src = sg_virt(&sg_s[j++]) + src_offset; - if (!src) - BUG(); - - src_offset = 0; - page_end = 1; - } - - memcpy(dst, src, length); - -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "page: %u, remaining size: %u, length: %u," - " i: %u, j: %u\n", req->rd_page, - (req->rd_size - length), length, i, j); -#endif - req->rd_size -= length; - if (!(req->rd_size)) - return 0; - - if (!page_end) - continue; + struct rd_dev_sg_table *sg_table; + u32 i, sg_per_table = (RD_MAX_ALLOCATION_SIZE / + sizeof(struct scatterlist)); - if (++req->rd_page <= table->page_end_offset) { -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "page: %u in same page table\n", - req->rd_page); -#endif - continue; - } -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "getting new page table for page: %u\n", - req->rd_page); -#endif - table = rd_get_sg_table(dev, req->rd_page); - if (!(table)) - return -1; - - sg_s = &table->sg_table[j = 0]; + i = page / sg_per_table; + if (i < rd_dev->sg_prot_count) { + sg_table = &rd_dev->sg_prot_array[i]; + if ((sg_table->page_start_offset <= page) && + (sg_table->page_end_offset >= page)) + return sg_table; } - return 0; + pr_err("Unable to locate struct prot rd_dev_sg_table for page: %u\n", + page); + + return NULL; } -/* rd_MEMCPY_write(): - * - * - */ -static int rd_MEMCPY_write(struct rd_request *req) +static sense_reason_t +rd_execute_rw(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents, + enum dma_data_direction data_direction) { - struct se_task *task = &req->rd_task; - struct rd_dev *dev = req->rd_dev; + struct se_device *se_dev = cmd->se_dev; + struct rd_dev *dev = RD_DEV(se_dev); struct rd_dev_sg_table *table; - struct scatterlist *sg_d, *sg_s; - void *dst, *src; - u32 i = 0, j = 0, dst_offset = 0, src_offset = 0; - u32 length, page_end = 0, table_sg_end; - u32 rd_offset = req->rd_offset; - - table = rd_get_sg_table(dev, req->rd_page); - if (!(table)) - return -1; - - table_sg_end = (table->page_end_offset - req->rd_page); - sg_d = &table->sg_table[req->rd_page - table->page_start_offset]; - sg_s = task->task_sg; -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "RD[%d] Write LBA: %llu, Size: %u, Page: %u," - " Offset: %u\n", dev->rd_dev_id, task->task_lba, req->rd_size, - req->rd_page, req->rd_offset); -#endif - dst_offset = rd_offset; - - while (req->rd_size) { - if ((sg_s[i].length - src_offset) < - (sg_d[j].length - dst_offset)) { - length = (sg_s[i].length - src_offset); -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "Step 1 - sg_s[%d]: %p length: %d" - " offset: %d sg_d[%d].length: %u\n", i, - &sg_s[i], sg_s[i].length, sg_s[i].offset, - j, sg_d[j].length); - printk(KERN_INFO "Step 1 - length: %u src_offset: %u" - " dst_offset: %u\n", length, src_offset, - dst_offset); -#endif - if (length > req->rd_size) - length = req->rd_size; - - src = sg_virt(&sg_s[i++]) + src_offset; - if (!src) - BUG(); - - dst = sg_virt(&sg_d[j]) + dst_offset; - if (!dst) - BUG(); - - src_offset = 0; - dst_offset = length; - page_end = 0; - } else { - length = (sg_d[j].length - dst_offset); -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "Step 2 - sg_s[%d]: %p length: %d" - " offset: %d sg_d[%d].length: %u\n", i, - &sg_s[i], sg_s[i].length, sg_s[i].offset, - j, sg_d[j].length); - printk(KERN_INFO "Step 2 - length: %u src_offset: %u" - " dst_offset: %u\n", length, src_offset, - dst_offset); -#endif - if (length > req->rd_size) - length = req->rd_size; - - src = sg_virt(&sg_s[i]) + src_offset; - if (!src) - BUG(); - - if (sg_s[i].length == length) { - i++; - src_offset = 0; - } else - src_offset = length; - - dst = sg_virt(&sg_d[j++]) + dst_offset; - if (!dst) - BUG(); - - dst_offset = 0; - page_end = 1; - } - - memcpy(dst, src, length); - -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "page: %u, remaining size: %u, length: %u," - " i: %u, j: %u\n", req->rd_page, - (req->rd_size - length), length, i, j); -#endif - req->rd_size -= length; - if (!(req->rd_size)) - return 0; - - if (!page_end) - continue; - - if (++req->rd_page <= table->page_end_offset) { -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "page: %u in same page table\n", - req->rd_page); -#endif - continue; - } -#ifdef DEBUG_RAMDISK_MCP - printk(KERN_INFO "getting new page table for page: %u\n", - req->rd_page); -#endif - table = rd_get_sg_table(dev, req->rd_page); - if (!(table)) - return -1; - - sg_d = &table->sg_table[j = 0]; + struct scatterlist *rd_sg; + struct sg_mapping_iter m; + u32 rd_offset; + u32 rd_size; + u32 rd_page; + u32 src_len; + u64 tmp; + sense_reason_t rc; + + if (dev->rd_flags & RDF_NULLIO) { + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; } - return 0; -} - -/* rd_MEMCPY_do_task(): (Part of se_subsystem_api_t template) - * - * - */ -static int rd_MEMCPY_do_task(struct se_task *task) -{ - struct se_device *dev = task->se_dev; - struct rd_request *req = RD_REQ(task); - unsigned long long lba; - int ret; + tmp = cmd->t_task_lba * se_dev->dev_attrib.block_size; + rd_offset = do_div(tmp, PAGE_SIZE); + rd_page = tmp; + rd_size = cmd->data_length; - req->rd_page = (task->task_lba * DEV_ATTRIB(dev)->block_size) / PAGE_SIZE; - lba = task->task_lba; - req->rd_offset = (do_div(lba, - (PAGE_SIZE / DEV_ATTRIB(dev)->block_size))) * - DEV_ATTRIB(dev)->block_size; - req->rd_size = task->task_size; + table = rd_get_sg_table(dev, rd_page); + if (!table) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - if (task->task_data_direction == DMA_FROM_DEVICE) - ret = rd_MEMCPY_read(req); - else - ret = rd_MEMCPY_write(req); + rd_sg = &table->sg_table[rd_page - table->page_start_offset]; - if (ret != 0) - return ret; + pr_debug("RD[%u]: %s LBA: %llu, Size: %u Page: %u, Offset: %u\n", + dev->rd_dev_id, + data_direction == DMA_FROM_DEVICE ? "Read" : "Write", + cmd->t_task_lba, rd_size, rd_page, rd_offset); - task->task_scsi_status = GOOD; - transport_complete_task(task, 1); + if (cmd->prot_type && data_direction == DMA_TO_DEVICE) { + struct rd_dev_sg_table *prot_table; + struct scatterlist *prot_sg; + u32 sectors = cmd->data_length / se_dev->dev_attrib.block_size; + u32 prot_offset, prot_page; - return PYX_TRANSPORT_SENT_TO_TRANSPORT; -} + tmp = cmd->t_task_lba * se_dev->prot_length; + prot_offset = do_div(tmp, PAGE_SIZE); + prot_page = tmp; -/* rd_DIRECT_with_offset(): - * - * - */ -static int rd_DIRECT_with_offset( - struct se_task *task, - struct list_head *se_mem_list, - u32 *se_mem_cnt, - u32 *task_offset) -{ - struct rd_request *req = RD_REQ(task); - struct rd_dev *dev = req->rd_dev; - struct rd_dev_sg_table *table; - struct se_mem *se_mem; - struct scatterlist *sg_s; - u32 j = 0, set_offset = 1; - u32 get_next_table = 0, offset_length, table_sg_end; - - table = rd_get_sg_table(dev, req->rd_page); - if (!(table)) - return -1; - - table_sg_end = (table->page_end_offset - req->rd_page); - sg_s = &table->sg_table[req->rd_page - table->page_start_offset]; -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "%s DIRECT LBA: %llu, Size: %u Page: %u, Offset: %u\n", - (task->task_data_direction == DMA_TO_DEVICE) ? - "Write" : "Read", - task->task_lba, req->rd_size, req->rd_page, req->rd_offset); -#endif - while (req->rd_size) { - se_mem = kmem_cache_zalloc(se_mem_cache, GFP_KERNEL); - if (!(se_mem)) { - printk(KERN_ERR "Unable to allocate struct se_mem\n"); - return -1; - } - INIT_LIST_HEAD(&se_mem->se_list); + prot_table = rd_get_prot_table(dev, prot_page); + if (!prot_table) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - if (set_offset) { - offset_length = sg_s[j].length - req->rd_offset; - if (offset_length > req->rd_size) - offset_length = req->rd_size; + prot_sg = &prot_table->sg_table[prot_page - prot_table->page_start_offset]; - se_mem->se_page = sg_page(&sg_s[j++]); - se_mem->se_off = req->rd_offset; - se_mem->se_len = offset_length; + rc = sbc_dif_verify_write(cmd, cmd->t_task_lba, sectors, 0, + prot_sg, prot_offset); + if (rc) + return rc; + } - set_offset = 0; - get_next_table = (j > table_sg_end); - goto check_eot; + src_len = PAGE_SIZE - rd_offset; + sg_miter_start(&m, sgl, sgl_nents, + data_direction == DMA_FROM_DEVICE ? + SG_MITER_TO_SG : SG_MITER_FROM_SG); + while (rd_size) { + u32 len; + void *rd_addr; + + sg_miter_next(&m); + if (!(u32)m.length) { + pr_debug("RD[%u]: invalid sgl %p len %zu\n", + dev->rd_dev_id, m.addr, m.length); + sg_miter_stop(&m); + return TCM_INCORRECT_AMOUNT_OF_DATA; } + len = min((u32)m.length, src_len); + if (len > rd_size) { + pr_debug("RD[%u]: size underrun page %d offset %d " + "size %d\n", dev->rd_dev_id, + rd_page, rd_offset, rd_size); + len = rd_size; + } + m.consumed = len; - offset_length = (req->rd_size < req->rd_offset) ? - req->rd_size : req->rd_offset; - - se_mem->se_page = sg_page(&sg_s[j]); - se_mem->se_len = offset_length; - - set_offset = 1; - -check_eot: -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "page: %u, size: %u, offset_length: %u, j: %u" - " se_mem: %p, se_page: %p se_off: %u se_len: %u\n", - req->rd_page, req->rd_size, offset_length, j, se_mem, - se_mem->se_page, se_mem->se_off, se_mem->se_len); -#endif - list_add_tail(&se_mem->se_list, se_mem_list); - (*se_mem_cnt)++; + rd_addr = sg_virt(rd_sg) + rd_offset; - req->rd_size -= offset_length; - if (!(req->rd_size)) - goto out; + if (data_direction == DMA_FROM_DEVICE) + memcpy(m.addr, rd_addr, len); + else + memcpy(rd_addr, m.addr, len); - if (!set_offset && !get_next_table) + rd_size -= len; + if (!rd_size) continue; - if (++req->rd_page <= table->page_end_offset) { -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "page: %u in same page table\n", - req->rd_page); -#endif + src_len -= len; + if (src_len) { + rd_offset += len; continue; } -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "getting new page table for page: %u\n", - req->rd_page); -#endif - table = rd_get_sg_table(dev, req->rd_page); - if (!(table)) - return -1; - - sg_s = &table->sg_table[j = 0]; - } -out: - T_TASK(task->task_se_cmd)->t_tasks_se_num += *se_mem_cnt; -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "RD_DR - Allocated %u struct se_mem segments for task\n", - *se_mem_cnt); -#endif - return 0; -} - -/* rd_DIRECT_without_offset(): - * - * - */ -static int rd_DIRECT_without_offset( - struct se_task *task, - struct list_head *se_mem_list, - u32 *se_mem_cnt, - u32 *task_offset) -{ - struct rd_request *req = RD_REQ(task); - struct rd_dev *dev = req->rd_dev; - struct rd_dev_sg_table *table; - struct se_mem *se_mem; - struct scatterlist *sg_s; - u32 length, j = 0; - - table = rd_get_sg_table(dev, req->rd_page); - if (!(table)) - return -1; - - sg_s = &table->sg_table[req->rd_page - table->page_start_offset]; -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "%s DIRECT LBA: %llu, Size: %u, Page: %u\n", - (task->task_data_direction == DMA_TO_DEVICE) ? - "Write" : "Read", - task->task_lba, req->rd_size, req->rd_page); -#endif - while (req->rd_size) { - se_mem = kmem_cache_zalloc(se_mem_cache, GFP_KERNEL); - if (!(se_mem)) { - printk(KERN_ERR "Unable to allocate struct se_mem\n"); - return -1; - } - INIT_LIST_HEAD(&se_mem->se_list); - - length = (req->rd_size < sg_s[j].length) ? - req->rd_size : sg_s[j].length; - - se_mem->se_page = sg_page(&sg_s[j++]); - se_mem->se_len = length; - -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "page: %u, size: %u, j: %u se_mem: %p," - " se_page: %p se_off: %u se_len: %u\n", req->rd_page, - req->rd_size, j, se_mem, se_mem->se_page, - se_mem->se_off, se_mem->se_len); -#endif - list_add_tail(&se_mem->se_list, se_mem_list); - (*se_mem_cnt)++; - - req->rd_size -= length; - if (!(req->rd_size)) - goto out; - - if (++req->rd_page <= table->page_end_offset) { -#ifdef DEBUG_RAMDISK_DR - printk("page: %u in same page table\n", - req->rd_page); -#endif + /* rd page completed, next one please */ + rd_page++; + rd_offset = 0; + src_len = PAGE_SIZE; + if (rd_page <= table->page_end_offset) { + rd_sg++; continue; } -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "getting new page table for page: %u\n", - req->rd_page); -#endif - table = rd_get_sg_table(dev, req->rd_page); - if (!(table)) - return -1; - - sg_s = &table->sg_table[j = 0]; - } -out: - T_TASK(task->task_se_cmd)->t_tasks_se_num += *se_mem_cnt; -#ifdef DEBUG_RAMDISK_DR - printk(KERN_INFO "RD_DR - Allocated %u struct se_mem segments for task\n", - *se_mem_cnt); -#endif - return 0; -} + table = rd_get_sg_table(dev, rd_page); + if (!table) { + sg_miter_stop(&m); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } -/* rd_DIRECT_do_se_mem_map(): - * - * - */ -static int rd_DIRECT_do_se_mem_map( - struct se_task *task, - struct list_head *se_mem_list, - void *in_mem, - struct se_mem *in_se_mem, - struct se_mem **out_se_mem, - u32 *se_mem_cnt, - u32 *task_offset_in) -{ - struct se_cmd *cmd = task->task_se_cmd; - struct rd_request *req = RD_REQ(task); - u32 task_offset = *task_offset_in; - unsigned long long lba; - int ret; + /* since we increment, the first sg entry is correct */ + rd_sg = table->sg_table; + } + sg_miter_stop(&m); - req->rd_page = ((task->task_lba * DEV_ATTRIB(task->se_dev)->block_size) / - PAGE_SIZE); - lba = task->task_lba; - req->rd_offset = (do_div(lba, - (PAGE_SIZE / DEV_ATTRIB(task->se_dev)->block_size))) * - DEV_ATTRIB(task->se_dev)->block_size; - req->rd_size = task->task_size; - - if (req->rd_offset) - ret = rd_DIRECT_with_offset(task, se_mem_list, se_mem_cnt, - task_offset_in); - else - ret = rd_DIRECT_without_offset(task, se_mem_list, se_mem_cnt, - task_offset_in); + if (cmd->prot_type && data_direction == DMA_FROM_DEVICE) { + struct rd_dev_sg_table *prot_table; + struct scatterlist *prot_sg; + u32 sectors = cmd->data_length / se_dev->dev_attrib.block_size; + u32 prot_offset, prot_page; - if (ret < 0) - return ret; + tmp = cmd->t_task_lba * se_dev->prot_length; + prot_offset = do_div(tmp, PAGE_SIZE); + prot_page = tmp; - if (CMD_TFO(cmd)->task_sg_chaining == 0) - return 0; - /* - * Currently prevent writers from multiple HW fabrics doing - * pci_map_sg() to RD_DR's internal scatterlist memory. - */ - if (cmd->data_direction == DMA_TO_DEVICE) { - printk(KERN_ERR "DMA_TO_DEVICE not supported for" - " RAMDISK_DR with task_sg_chaining=1\n"); - return -1; - } - /* - * Special case for if task_sg_chaining is enabled, then - * we setup struct se_task->task_sg[], as it will be used by - * transport_do_task_sg_chain() for creating chainged SGLs - * across multiple struct se_task->task_sg[]. - */ - if (!(transport_calc_sg_num(task, - list_entry(T_TASK(cmd)->t_mem_list->next, - struct se_mem, se_list), - task_offset))) - return -1; - - return transport_map_mem_to_sg(task, se_mem_list, task->task_sg, - list_entry(T_TASK(cmd)->t_mem_list->next, - struct se_mem, se_list), - out_se_mem, se_mem_cnt, task_offset_in); -} + prot_table = rd_get_prot_table(dev, prot_page); + if (!prot_table) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; -/* rd_DIRECT_do_task(): (Part of se_subsystem_api_t template) - * - * - */ -static int rd_DIRECT_do_task(struct se_task *task) -{ - /* - * At this point the locally allocated RD tables have been mapped - * to struct se_mem elements in rd_DIRECT_do_se_mem_map(). - */ - task->task_scsi_status = GOOD; - transport_complete_task(task, 1); + prot_sg = &prot_table->sg_table[prot_page - prot_table->page_start_offset]; - return PYX_TRANSPORT_SENT_TO_TRANSPORT; -} + rc = sbc_dif_verify_read(cmd, cmd->t_task_lba, sectors, 0, + prot_sg, prot_offset); + if (rc) + return rc; + } -/* rd_free_task(): (Part of se_subsystem_api_t template) - * - * - */ -static void rd_free_task(struct se_task *task) -{ - kfree(RD_REQ(task)); + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; } enum { - Opt_rd_pages, Opt_err + Opt_rd_pages, Opt_rd_nullio, Opt_err }; static match_table_t tokens = { {Opt_rd_pages, "rd_pages=%d"}, + {Opt_rd_nullio, "rd_nullio=%d"}, {Opt_err, NULL} }; -static ssize_t rd_set_configfs_dev_params( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - const char *page, - ssize_t count) +static ssize_t rd_set_configfs_dev_params(struct se_device *dev, + const char *page, ssize_t count) { - struct rd_dev *rd_dev = se_dev->se_dev_su_ptr; + struct rd_dev *rd_dev = RD_DEV(dev); char *orig, *ptr, *opts; substring_t args[MAX_OPT_ARGS]; int ret = 0, arg, token; @@ -947,7 +551,7 @@ static ssize_t rd_set_configfs_dev_params( orig = opts; - while ((ptr = strsep(&opts, ",")) != NULL) { + while ((ptr = strsep(&opts, ",\n")) != NULL) { if (!*ptr) continue; @@ -956,10 +560,18 @@ static ssize_t rd_set_configfs_dev_params( case Opt_rd_pages: match_int(args, &arg); rd_dev->rd_page_count = arg; - printk(KERN_INFO "RAMDISK: Referencing Page" + pr_debug("RAMDISK: Referencing Page" " Count: %u\n", rd_dev->rd_page_count); rd_dev->rd_flags |= RDF_HAS_PAGE_COUNT; break; + case Opt_rd_nullio: + match_int(args, &arg); + if (arg != 1) + break; + + pr_debug("RAMDISK: Setting NULLIO flag: %d\n", arg); + rd_dev->rd_flags |= RDF_NULLIO; + break; default: break; } @@ -969,115 +581,82 @@ static ssize_t rd_set_configfs_dev_params( return (!ret) ? count : ret; } -static ssize_t rd_check_configfs_dev_params(struct se_hba *hba, struct se_subsystem_dev *se_dev) +static ssize_t rd_show_configfs_dev_params(struct se_device *dev, char *b) { - struct rd_dev *rd_dev = se_dev->se_dev_su_ptr; - - if (!(rd_dev->rd_flags & RDF_HAS_PAGE_COUNT)) { - printk(KERN_INFO "Missing rd_pages= parameter\n"); - return -1; - } - - return 0; -} + struct rd_dev *rd_dev = RD_DEV(dev); -static ssize_t rd_show_configfs_dev_params( - struct se_hba *hba, - struct se_subsystem_dev *se_dev, - char *b) -{ - struct rd_dev *rd_dev = se_dev->se_dev_su_ptr; - ssize_t bl = sprintf(b, "TCM RamDisk ID: %u RamDisk Makeup: %s\n", - rd_dev->rd_dev_id, (rd_dev->rd_direct) ? - "rd_direct" : "rd_mcp"); + ssize_t bl = sprintf(b, "TCM RamDisk ID: %u RamDisk Makeup: rd_mcp\n", + rd_dev->rd_dev_id); bl += sprintf(b + bl, " PAGES/PAGE_SIZE: %u*%lu" - " SG_table_count: %u\n", rd_dev->rd_page_count, - PAGE_SIZE, rd_dev->sg_table_count); + " SG_table_count: %u nullio: %d\n", rd_dev->rd_page_count, + PAGE_SIZE, rd_dev->sg_table_count, + !!(rd_dev->rd_flags & RDF_NULLIO)); return bl; } -/* rd_get_cdb(): (Part of se_subsystem_api_t template) - * - * - */ -static unsigned char *rd_get_cdb(struct se_task *task) +static sector_t rd_get_blocks(struct se_device *dev) { - struct rd_request *req = RD_REQ(task); + struct rd_dev *rd_dev = RD_DEV(dev); - return req->rd_scsi_cdb; -} + unsigned long long blocks_long = ((rd_dev->rd_page_count * PAGE_SIZE) / + dev->dev_attrib.block_size) - 1; -static u32 rd_get_device_rev(struct se_device *dev) -{ - return SCSI_SPC_2; /* Returns SPC-3 in Initiator Data */ + return blocks_long; } -static u32 rd_get_device_type(struct se_device *dev) +static int rd_init_prot(struct se_device *dev) { - return TYPE_DISK; + struct rd_dev *rd_dev = RD_DEV(dev); + + if (!dev->dev_attrib.pi_prot_type) + return 0; + + return rd_build_prot_space(rd_dev, dev->prot_length, + dev->dev_attrib.block_size); } -static sector_t rd_get_blocks(struct se_device *dev) +static void rd_free_prot(struct se_device *dev) { - struct rd_dev *rd_dev = dev->dev_ptr; - unsigned long long blocks_long = ((rd_dev->rd_page_count * PAGE_SIZE) / - DEV_ATTRIB(dev)->block_size) - 1; + struct rd_dev *rd_dev = RD_DEV(dev); - return blocks_long; + rd_release_prot_space(rd_dev); } -static struct se_subsystem_api rd_dr_template = { - .name = "rd_dr", - .transport_type = TRANSPORT_PLUGIN_VHBA_VDEV, - .attach_hba = rd_attach_hba, - .detach_hba = rd_detach_hba, - .allocate_virtdevice = rd_DIRECT_allocate_virtdevice, - .create_virtdevice = rd_DIRECT_create_virtdevice, - .free_device = rd_free_device, - .alloc_task = rd_alloc_task, - .do_task = rd_DIRECT_do_task, - .free_task = rd_free_task, - .check_configfs_dev_params = rd_check_configfs_dev_params, - .set_configfs_dev_params = rd_set_configfs_dev_params, - .show_configfs_dev_params = rd_show_configfs_dev_params, - .get_cdb = rd_get_cdb, - .get_device_rev = rd_get_device_rev, - .get_device_type = rd_get_device_type, - .get_blocks = rd_get_blocks, - .do_se_mem_map = rd_DIRECT_do_se_mem_map, +static struct sbc_ops rd_sbc_ops = { + .execute_rw = rd_execute_rw, }; +static sense_reason_t +rd_parse_cdb(struct se_cmd *cmd) +{ + return sbc_parse_cdb(cmd, &rd_sbc_ops); +} + static struct se_subsystem_api rd_mcp_template = { .name = "rd_mcp", + .inquiry_prod = "RAMDISK-MCP", + .inquiry_rev = RD_MCP_VERSION, .transport_type = TRANSPORT_PLUGIN_VHBA_VDEV, .attach_hba = rd_attach_hba, .detach_hba = rd_detach_hba, - .allocate_virtdevice = rd_MEMCPY_allocate_virtdevice, - .create_virtdevice = rd_MEMCPY_create_virtdevice, + .alloc_device = rd_alloc_device, + .configure_device = rd_configure_device, .free_device = rd_free_device, - .alloc_task = rd_alloc_task, - .do_task = rd_MEMCPY_do_task, - .free_task = rd_free_task, - .check_configfs_dev_params = rd_check_configfs_dev_params, + .parse_cdb = rd_parse_cdb, .set_configfs_dev_params = rd_set_configfs_dev_params, .show_configfs_dev_params = rd_show_configfs_dev_params, - .get_cdb = rd_get_cdb, - .get_device_rev = rd_get_device_rev, - .get_device_type = rd_get_device_type, + .get_device_type = sbc_get_device_type, .get_blocks = rd_get_blocks, + .init_prot = rd_init_prot, + .free_prot = rd_free_prot, }; int __init rd_module_init(void) { int ret; - ret = transport_subsystem_register(&rd_dr_template); - if (ret < 0) - return ret; - ret = transport_subsystem_register(&rd_mcp_template); if (ret < 0) { - transport_subsystem_release(&rd_dr_template); return ret; } @@ -1086,6 +665,5 @@ int __init rd_module_init(void) void rd_module_exit(void) { - transport_subsystem_release(&rd_dr_template); transport_subsystem_release(&rd_mcp_template); } diff --git a/drivers/target/target_core_rd.h b/drivers/target/target_core_rd.h index 13badfbaf9c..cc46a6a89b3 100644 --- a/drivers/target/target_core_rd.h +++ b/drivers/target/target_core_rd.h @@ -2,44 +2,18 @@ #define TARGET_CORE_RD_H #define RD_HBA_VERSION "v4.0" -#define RD_DR_VERSION "4.0" #define RD_MCP_VERSION "4.0" /* Largest piece of memory kmalloc can allocate */ #define RD_MAX_ALLOCATION_SIZE 65536 -/* Maximum queuedepth for the Ramdisk HBA */ -#define RD_HBA_QUEUE_DEPTH 256 #define RD_DEVICE_QUEUE_DEPTH 32 #define RD_MAX_DEVICE_QUEUE_DEPTH 128 #define RD_BLOCKSIZE 512 -#define RD_MAX_SECTORS 1024 - -extern struct kmem_cache *se_mem_cache; /* Used in target_core_init_configfs() for virtual LUN 0 access */ int __init rd_module_init(void); void rd_module_exit(void); -#define RRF_EMULATE_CDB 0x01 -#define RRF_GOT_LBA 0x02 - -struct rd_request { - struct se_task rd_task; - - /* SCSI CDB from iSCSI Command PDU */ - unsigned char rd_scsi_cdb[TCM_MAX_COMMAND_SIZE]; - /* Offset from start of page */ - u32 rd_offset; - /* Starting page in Ramdisk for request */ - u32 rd_page; - /* Total number of pages needed for request */ - u32 rd_page_count; - /* Scatterlist count */ - u32 rd_size; - /* Ramdisk device */ - struct rd_dev *rd_dev; -} ____cacheline_aligned; - struct rd_dev_sg_table { u32 page_start_offset; u32 page_end_offset; @@ -48,9 +22,10 @@ struct rd_dev_sg_table { } ____cacheline_aligned; #define RDF_HAS_PAGE_COUNT 0x01 +#define RDF_NULLIO 0x02 struct rd_dev { - int rd_direct; + struct se_device dev; u32 rd_flags; /* Unique Ramdisk Device ID in Ramdisk HBA */ u32 rd_dev_id; @@ -58,9 +33,12 @@ struct rd_dev { u32 rd_page_count; /* Number of SG tables in sg_table_array */ u32 sg_table_count; - u32 rd_queue_depth; + /* Number of SG tables in sg_prot_array */ + u32 sg_prot_count; /* Array of rd_dev_sg_table_t containing scatterlists */ struct rd_dev_sg_table *sg_table_array; + /* Array of rd_dev_sg_table containing protection scatterlists */ + struct rd_dev_sg_table *sg_prot_array; /* Ramdisk HBA device is connected to */ struct rd_host *rd_host; } ____cacheline_aligned; diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c new file mode 100644 index 00000000000..bd78d9235ac --- /dev/null +++ b/drivers/target/target_core_sbc.c @@ -0,0 +1,1336 @@ +/* + * SCSI Block Commands (SBC) parsing and emulation. + * + * (c) Copyright 2002-2013 Datera, Inc. + * + * Nicholas A. Bellinger <nab@kernel.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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/ratelimit.h> +#include <linux/crc-t10dif.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_tcq.h> + +#include <target/target_core_base.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> + +#include "target_core_internal.h" +#include "target_core_ua.h" +#include "target_core_alua.h" + +static sense_reason_t +sbc_emulate_readcapacity(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + unsigned char *cdb = cmd->t_task_cdb; + unsigned long long blocks_long = dev->transport->get_blocks(dev); + unsigned char *rbuf; + unsigned char buf[8]; + u32 blocks; + + /* + * SBC-2 says: + * If the PMI bit is set to zero and the LOGICAL BLOCK + * ADDRESS field is not set to zero, the device server shall + * terminate the command with CHECK CONDITION status with + * the sense key set to ILLEGAL REQUEST and the additional + * sense code set to INVALID FIELD IN CDB. + * + * In SBC-3, these fields are obsolete, but some SCSI + * compliance tests actually check this, so we might as well + * follow SBC-2. + */ + if (!(cdb[8] & 1) && !!(cdb[2] | cdb[3] | cdb[4] | cdb[5])) + return TCM_INVALID_CDB_FIELD; + + if (blocks_long >= 0x00000000ffffffff) + blocks = 0xffffffff; + else + blocks = (u32)blocks_long; + + buf[0] = (blocks >> 24) & 0xff; + buf[1] = (blocks >> 16) & 0xff; + buf[2] = (blocks >> 8) & 0xff; + buf[3] = blocks & 0xff; + buf[4] = (dev->dev_attrib.block_size >> 24) & 0xff; + buf[5] = (dev->dev_attrib.block_size >> 16) & 0xff; + buf[6] = (dev->dev_attrib.block_size >> 8) & 0xff; + buf[7] = dev->dev_attrib.block_size & 0xff; + + rbuf = transport_kmap_data_sg(cmd); + if (rbuf) { + memcpy(rbuf, buf, min_t(u32, sizeof(buf), cmd->data_length)); + transport_kunmap_data_sg(cmd); + } + + target_complete_cmd_with_length(cmd, GOOD, 8); + return 0; +} + +static sense_reason_t +sbc_emulate_readcapacity_16(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct se_session *sess = cmd->se_sess; + unsigned char *rbuf; + unsigned char buf[32]; + unsigned long long blocks = dev->transport->get_blocks(dev); + + memset(buf, 0, sizeof(buf)); + buf[0] = (blocks >> 56) & 0xff; + buf[1] = (blocks >> 48) & 0xff; + buf[2] = (blocks >> 40) & 0xff; + buf[3] = (blocks >> 32) & 0xff; + buf[4] = (blocks >> 24) & 0xff; + buf[5] = (blocks >> 16) & 0xff; + buf[6] = (blocks >> 8) & 0xff; + buf[7] = blocks & 0xff; + buf[8] = (dev->dev_attrib.block_size >> 24) & 0xff; + buf[9] = (dev->dev_attrib.block_size >> 16) & 0xff; + buf[10] = (dev->dev_attrib.block_size >> 8) & 0xff; + buf[11] = dev->dev_attrib.block_size & 0xff; + /* + * Set P_TYPE and PROT_EN bits for DIF support + */ + if (sess->sup_prot_ops & (TARGET_PROT_DIN_PASS | TARGET_PROT_DOUT_PASS)) { + if (dev->dev_attrib.pi_prot_type) + buf[12] = (dev->dev_attrib.pi_prot_type - 1) << 1 | 0x1; + } + + if (dev->transport->get_lbppbe) + buf[13] = dev->transport->get_lbppbe(dev) & 0x0f; + + if (dev->transport->get_alignment_offset_lbas) { + u16 lalba = dev->transport->get_alignment_offset_lbas(dev); + buf[14] = (lalba >> 8) & 0x3f; + buf[15] = lalba & 0xff; + } + + /* + * Set Thin Provisioning Enable bit following sbc3r22 in section + * READ CAPACITY (16) byte 14 if emulate_tpu or emulate_tpws is enabled. + */ + if (dev->dev_attrib.emulate_tpu || dev->dev_attrib.emulate_tpws) + buf[14] |= 0x80; + + rbuf = transport_kmap_data_sg(cmd); + if (rbuf) { + memcpy(rbuf, buf, min_t(u32, sizeof(buf), cmd->data_length)); + transport_kunmap_data_sg(cmd); + } + + target_complete_cmd_with_length(cmd, GOOD, 32); + return 0; +} + +sector_t sbc_get_write_same_sectors(struct se_cmd *cmd) +{ + u32 num_blocks; + + if (cmd->t_task_cdb[0] == WRITE_SAME) + num_blocks = get_unaligned_be16(&cmd->t_task_cdb[7]); + else if (cmd->t_task_cdb[0] == WRITE_SAME_16) + num_blocks = get_unaligned_be32(&cmd->t_task_cdb[10]); + else /* WRITE_SAME_32 via VARIABLE_LENGTH_CMD */ + num_blocks = get_unaligned_be32(&cmd->t_task_cdb[28]); + + /* + * Use the explicit range when non zero is supplied, otherwise calculate + * the remaining range based on ->get_blocks() - starting LBA. + */ + if (num_blocks) + return num_blocks; + + return cmd->se_dev->transport->get_blocks(cmd->se_dev) - + cmd->t_task_lba + 1; +} +EXPORT_SYMBOL(sbc_get_write_same_sectors); + +static sense_reason_t +sbc_emulate_noop(struct se_cmd *cmd) +{ + target_complete_cmd(cmd, GOOD); + return 0; +} + +static inline u32 sbc_get_size(struct se_cmd *cmd, u32 sectors) +{ + return cmd->se_dev->dev_attrib.block_size * sectors; +} + +static inline u32 transport_get_sectors_6(unsigned char *cdb) +{ + /* + * Use 8-bit sector value. SBC-3 says: + * + * A TRANSFER LENGTH field set to zero specifies that 256 + * logical blocks shall be written. Any other value + * specifies the number of logical blocks that shall be + * written. + */ + return cdb[4] ? : 256; +} + +static inline u32 transport_get_sectors_10(unsigned char *cdb) +{ + return (u32)(cdb[7] << 8) + cdb[8]; +} + +static inline u32 transport_get_sectors_12(unsigned char *cdb) +{ + return (u32)(cdb[6] << 24) + (cdb[7] << 16) + (cdb[8] << 8) + cdb[9]; +} + +static inline u32 transport_get_sectors_16(unsigned char *cdb) +{ + return (u32)(cdb[10] << 24) + (cdb[11] << 16) + + (cdb[12] << 8) + cdb[13]; +} + +/* + * Used for VARIABLE_LENGTH_CDB WRITE_32 and READ_32 variants + */ +static inline u32 transport_get_sectors_32(unsigned char *cdb) +{ + return (u32)(cdb[28] << 24) + (cdb[29] << 16) + + (cdb[30] << 8) + cdb[31]; + +} + +static inline u32 transport_lba_21(unsigned char *cdb) +{ + return ((cdb[1] & 0x1f) << 16) | (cdb[2] << 8) | cdb[3]; +} + +static inline u32 transport_lba_32(unsigned char *cdb) +{ + return (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; +} + +static inline unsigned long long transport_lba_64(unsigned char *cdb) +{ + unsigned int __v1, __v2; + + __v1 = (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; + __v2 = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9]; + + return ((unsigned long long)__v2) | (unsigned long long)__v1 << 32; +} + +/* + * For VARIABLE_LENGTH_CDB w/ 32 byte extended CDBs + */ +static inline unsigned long long transport_lba_64_ext(unsigned char *cdb) +{ + unsigned int __v1, __v2; + + __v1 = (cdb[12] << 24) | (cdb[13] << 16) | (cdb[14] << 8) | cdb[15]; + __v2 = (cdb[16] << 24) | (cdb[17] << 16) | (cdb[18] << 8) | cdb[19]; + + return ((unsigned long long)__v2) | (unsigned long long)__v1 << 32; +} + +static sense_reason_t +sbc_setup_write_same(struct se_cmd *cmd, unsigned char *flags, struct sbc_ops *ops) +{ + unsigned int sectors = sbc_get_write_same_sectors(cmd); + + if ((flags[0] & 0x04) || (flags[0] & 0x02)) { + pr_err("WRITE_SAME PBDATA and LBDATA" + " bits not supported for Block Discard" + " Emulation\n"); + return TCM_UNSUPPORTED_SCSI_OPCODE; + } + if (sectors > cmd->se_dev->dev_attrib.max_write_same_len) { + pr_warn("WRITE_SAME sectors: %u exceeds max_write_same_len: %u\n", + sectors, cmd->se_dev->dev_attrib.max_write_same_len); + return TCM_INVALID_CDB_FIELD; + } + /* We always have ANC_SUP == 0 so setting ANCHOR is always an error */ + if (flags[0] & 0x10) { + pr_warn("WRITE SAME with ANCHOR not supported\n"); + return TCM_INVALID_CDB_FIELD; + } + /* + * Special case for WRITE_SAME w/ UNMAP=1 that ends up getting + * translated into block discard requests within backend code. + */ + if (flags[0] & 0x08) { + if (!ops->execute_write_same_unmap) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + cmd->execute_cmd = ops->execute_write_same_unmap; + return 0; + } + if (!ops->execute_write_same) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + cmd->execute_cmd = ops->execute_write_same; + return 0; +} + +static sense_reason_t xdreadwrite_callback(struct se_cmd *cmd) +{ + unsigned char *buf, *addr; + struct scatterlist *sg; + unsigned int offset; + sense_reason_t ret = TCM_NO_SENSE; + int i, count; + /* + * From sbc3r22.pdf section 5.48 XDWRITEREAD (10) command + * + * 1) read the specified logical block(s); + * 2) transfer logical blocks from the data-out buffer; + * 3) XOR the logical blocks transferred from the data-out buffer with + * the logical blocks read, storing the resulting XOR data in a buffer; + * 4) if the DISABLE WRITE bit is set to zero, then write the logical + * blocks transferred from the data-out buffer; and + * 5) transfer the resulting XOR data to the data-in buffer. + */ + buf = kmalloc(cmd->data_length, GFP_KERNEL); + if (!buf) { + pr_err("Unable to allocate xor_callback buf\n"); + return TCM_OUT_OF_RESOURCES; + } + /* + * Copy the scatterlist WRITE buffer located at cmd->t_data_sg + * into the locally allocated *buf + */ + sg_copy_to_buffer(cmd->t_data_sg, + cmd->t_data_nents, + buf, + cmd->data_length); + + /* + * Now perform the XOR against the BIDI read memory located at + * cmd->t_mem_bidi_list + */ + + offset = 0; + for_each_sg(cmd->t_bidi_data_sg, sg, cmd->t_bidi_data_nents, count) { + addr = kmap_atomic(sg_page(sg)); + if (!addr) { + ret = TCM_OUT_OF_RESOURCES; + goto out; + } + + for (i = 0; i < sg->length; i++) + *(addr + sg->offset + i) ^= *(buf + offset + i); + + offset += sg->length; + kunmap_atomic(addr); + } + +out: + kfree(buf); + return ret; +} + +static sense_reason_t +sbc_execute_rw(struct se_cmd *cmd) +{ + return cmd->execute_rw(cmd, cmd->t_data_sg, cmd->t_data_nents, + cmd->data_direction); +} + +static sense_reason_t compare_and_write_post(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + + /* + * Only set SCF_COMPARE_AND_WRITE_POST to force a response fall-through + * within target_complete_ok_work() if the command was successfully + * sent to the backend driver. + */ + spin_lock_irq(&cmd->t_state_lock); + if ((cmd->transport_state & CMD_T_SENT) && !cmd->scsi_status) + cmd->se_cmd_flags |= SCF_COMPARE_AND_WRITE_POST; + spin_unlock_irq(&cmd->t_state_lock); + + /* + * Unlock ->caw_sem originally obtained during sbc_compare_and_write() + * before the original READ I/O submission. + */ + up(&dev->caw_sem); + + return TCM_NO_SENSE; +} + +static sense_reason_t compare_and_write_callback(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct scatterlist *write_sg = NULL, *sg; + unsigned char *buf = NULL, *addr; + struct sg_mapping_iter m; + unsigned int offset = 0, len; + unsigned int nlbas = cmd->t_task_nolb; + unsigned int block_size = dev->dev_attrib.block_size; + unsigned int compare_len = (nlbas * block_size); + sense_reason_t ret = TCM_NO_SENSE; + int rc, i; + + /* + * Handle early failure in transport_generic_request_failure(), + * which will not have taken ->caw_mutex yet.. + */ + if (!cmd->t_data_sg || !cmd->t_bidi_data_sg) + return TCM_NO_SENSE; + /* + * Immediately exit + release dev->caw_sem if command has already + * been failed with a non-zero SCSI status. + */ + if (cmd->scsi_status) { + pr_err("compare_and_write_callback: non zero scsi_status:" + " 0x%02x\n", cmd->scsi_status); + goto out; + } + + buf = kzalloc(cmd->data_length, GFP_KERNEL); + if (!buf) { + pr_err("Unable to allocate compare_and_write buf\n"); + ret = TCM_OUT_OF_RESOURCES; + goto out; + } + + write_sg = kmalloc(sizeof(struct scatterlist) * cmd->t_data_nents, + GFP_KERNEL); + if (!write_sg) { + pr_err("Unable to allocate compare_and_write sg\n"); + ret = TCM_OUT_OF_RESOURCES; + goto out; + } + sg_init_table(write_sg, cmd->t_data_nents); + /* + * Setup verify and write data payloads from total NumberLBAs. + */ + rc = sg_copy_to_buffer(cmd->t_data_sg, cmd->t_data_nents, buf, + cmd->data_length); + if (!rc) { + pr_err("sg_copy_to_buffer() failed for compare_and_write\n"); + ret = TCM_OUT_OF_RESOURCES; + goto out; + } + /* + * Compare against SCSI READ payload against verify payload + */ + for_each_sg(cmd->t_bidi_data_sg, sg, cmd->t_bidi_data_nents, i) { + addr = (unsigned char *)kmap_atomic(sg_page(sg)); + if (!addr) { + ret = TCM_OUT_OF_RESOURCES; + goto out; + } + + len = min(sg->length, compare_len); + + if (memcmp(addr, buf + offset, len)) { + pr_warn("Detected MISCOMPARE for addr: %p buf: %p\n", + addr, buf + offset); + kunmap_atomic(addr); + goto miscompare; + } + kunmap_atomic(addr); + + offset += len; + compare_len -= len; + if (!compare_len) + break; + } + + i = 0; + len = cmd->t_task_nolb * block_size; + sg_miter_start(&m, cmd->t_data_sg, cmd->t_data_nents, SG_MITER_TO_SG); + /* + * Currently assumes NoLB=1 and SGLs are PAGE_SIZE.. + */ + while (len) { + sg_miter_next(&m); + + if (block_size < PAGE_SIZE) { + sg_set_page(&write_sg[i], m.page, block_size, + block_size); + } else { + sg_miter_next(&m); + sg_set_page(&write_sg[i], m.page, block_size, + 0); + } + len -= block_size; + i++; + } + sg_miter_stop(&m); + /* + * Save the original SGL + nents values before updating to new + * assignments, to be released in transport_free_pages() -> + * transport_reset_sgl_orig() + */ + cmd->t_data_sg_orig = cmd->t_data_sg; + cmd->t_data_sg = write_sg; + cmd->t_data_nents_orig = cmd->t_data_nents; + cmd->t_data_nents = 1; + + cmd->sam_task_attr = MSG_HEAD_TAG; + cmd->transport_complete_callback = compare_and_write_post; + /* + * Now reset ->execute_cmd() to the normal sbc_execute_rw() handler + * for submitting the adjusted SGL to write instance user-data. + */ + cmd->execute_cmd = sbc_execute_rw; + + spin_lock_irq(&cmd->t_state_lock); + cmd->t_state = TRANSPORT_PROCESSING; + cmd->transport_state |= CMD_T_ACTIVE|CMD_T_BUSY|CMD_T_SENT; + spin_unlock_irq(&cmd->t_state_lock); + + __target_execute_cmd(cmd); + + kfree(buf); + return ret; + +miscompare: + pr_warn("Target/%s: Send MISCOMPARE check condition and sense\n", + dev->transport->name); + ret = TCM_MISCOMPARE_VERIFY; +out: + /* + * In the MISCOMPARE or failure case, unlock ->caw_sem obtained in + * sbc_compare_and_write() before the original READ I/O submission. + */ + up(&dev->caw_sem); + kfree(write_sg); + kfree(buf); + return ret; +} + +static sense_reason_t +sbc_compare_and_write(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + sense_reason_t ret; + int rc; + /* + * Submit the READ first for COMPARE_AND_WRITE to perform the + * comparision using SGLs at cmd->t_bidi_data_sg.. + */ + rc = down_interruptible(&dev->caw_sem); + if ((rc != 0) || signal_pending(current)) { + cmd->transport_complete_callback = NULL; + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + /* + * Reset cmd->data_length to individual block_size in order to not + * confuse backend drivers that depend on this value matching the + * size of the I/O being submitted. + */ + cmd->data_length = cmd->t_task_nolb * dev->dev_attrib.block_size; + + ret = cmd->execute_rw(cmd, cmd->t_bidi_data_sg, cmd->t_bidi_data_nents, + DMA_FROM_DEVICE); + if (ret) { + cmd->transport_complete_callback = NULL; + up(&dev->caw_sem); + return ret; + } + /* + * Unlock of dev->caw_sem to occur in compare_and_write_callback() + * upon MISCOMPARE, or in compare_and_write_done() upon completion + * of WRITE instance user-data. + */ + return TCM_NO_SENSE; +} + +static int +sbc_set_prot_op_checks(u8 protect, enum target_prot_type prot_type, + bool is_write, struct se_cmd *cmd) +{ + if (is_write) { + cmd->prot_op = protect ? TARGET_PROT_DOUT_PASS : + TARGET_PROT_DOUT_INSERT; + switch (protect) { + case 0x0: + case 0x3: + cmd->prot_checks = 0; + break; + case 0x1: + case 0x5: + cmd->prot_checks = TARGET_DIF_CHECK_GUARD; + if (prot_type == TARGET_DIF_TYPE1_PROT) + cmd->prot_checks |= TARGET_DIF_CHECK_REFTAG; + break; + case 0x2: + if (prot_type == TARGET_DIF_TYPE1_PROT) + cmd->prot_checks = TARGET_DIF_CHECK_REFTAG; + break; + case 0x4: + cmd->prot_checks = TARGET_DIF_CHECK_GUARD; + break; + default: + pr_err("Unsupported protect field %d\n", protect); + return -EINVAL; + } + } else { + cmd->prot_op = protect ? TARGET_PROT_DIN_PASS : + TARGET_PROT_DIN_STRIP; + switch (protect) { + case 0x0: + case 0x1: + case 0x5: + cmd->prot_checks = TARGET_DIF_CHECK_GUARD; + if (prot_type == TARGET_DIF_TYPE1_PROT) + cmd->prot_checks |= TARGET_DIF_CHECK_REFTAG; + break; + case 0x2: + if (prot_type == TARGET_DIF_TYPE1_PROT) + cmd->prot_checks = TARGET_DIF_CHECK_REFTAG; + break; + case 0x3: + cmd->prot_checks = 0; + break; + case 0x4: + cmd->prot_checks = TARGET_DIF_CHECK_GUARD; + break; + default: + pr_err("Unsupported protect field %d\n", protect); + return -EINVAL; + } + } + + return 0; +} + +static bool +sbc_check_prot(struct se_device *dev, struct se_cmd *cmd, unsigned char *cdb, + u32 sectors, bool is_write) +{ + u8 protect = cdb[1] >> 5; + + if ((!cmd->t_prot_sg || !cmd->t_prot_nents) && cmd->prot_pto) + return true; + + switch (dev->dev_attrib.pi_prot_type) { + case TARGET_DIF_TYPE3_PROT: + cmd->reftag_seed = 0xffffffff; + break; + case TARGET_DIF_TYPE2_PROT: + if (protect) + return false; + + cmd->reftag_seed = cmd->t_task_lba; + break; + case TARGET_DIF_TYPE1_PROT: + cmd->reftag_seed = cmd->t_task_lba; + break; + case TARGET_DIF_TYPE0_PROT: + default: + return true; + } + + if (sbc_set_prot_op_checks(protect, dev->dev_attrib.pi_prot_type, + is_write, cmd)) + return false; + + cmd->prot_type = dev->dev_attrib.pi_prot_type; + cmd->prot_length = dev->prot_length * sectors; + + /** + * In case protection information exists over the wire + * we modify command data length to describe pure data. + * The actual transfer length is data length + protection + * length + **/ + if (protect) + cmd->data_length = sectors * dev->dev_attrib.block_size; + + pr_debug("%s: prot_type=%d, data_length=%d, prot_length=%d " + "prot_op=%d prot_checks=%d\n", + __func__, cmd->prot_type, cmd->data_length, cmd->prot_length, + cmd->prot_op, cmd->prot_checks); + + return true; +} + +sense_reason_t +sbc_parse_cdb(struct se_cmd *cmd, struct sbc_ops *ops) +{ + struct se_device *dev = cmd->se_dev; + unsigned char *cdb = cmd->t_task_cdb; + unsigned int size; + u32 sectors = 0; + sense_reason_t ret; + + switch (cdb[0]) { + case READ_6: + sectors = transport_get_sectors_6(cdb); + cmd->t_task_lba = transport_lba_21(cdb); + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + break; + case READ_10: + sectors = transport_get_sectors_10(cdb); + cmd->t_task_lba = transport_lba_32(cdb); + + if (!sbc_check_prot(dev, cmd, cdb, sectors, false)) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + break; + case READ_12: + sectors = transport_get_sectors_12(cdb); + cmd->t_task_lba = transport_lba_32(cdb); + + if (!sbc_check_prot(dev, cmd, cdb, sectors, false)) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + break; + case READ_16: + sectors = transport_get_sectors_16(cdb); + cmd->t_task_lba = transport_lba_64(cdb); + + if (!sbc_check_prot(dev, cmd, cdb, sectors, false)) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + break; + case WRITE_6: + sectors = transport_get_sectors_6(cdb); + cmd->t_task_lba = transport_lba_21(cdb); + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + break; + case WRITE_10: + case WRITE_VERIFY: + sectors = transport_get_sectors_10(cdb); + cmd->t_task_lba = transport_lba_32(cdb); + + if (!sbc_check_prot(dev, cmd, cdb, sectors, true)) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + if (cdb[1] & 0x8) + cmd->se_cmd_flags |= SCF_FUA; + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + break; + case WRITE_12: + sectors = transport_get_sectors_12(cdb); + cmd->t_task_lba = transport_lba_32(cdb); + + if (!sbc_check_prot(dev, cmd, cdb, sectors, true)) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + if (cdb[1] & 0x8) + cmd->se_cmd_flags |= SCF_FUA; + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + break; + case WRITE_16: + sectors = transport_get_sectors_16(cdb); + cmd->t_task_lba = transport_lba_64(cdb); + + if (!sbc_check_prot(dev, cmd, cdb, sectors, true)) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + if (cdb[1] & 0x8) + cmd->se_cmd_flags |= SCF_FUA; + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + break; + case XDWRITEREAD_10: + if (cmd->data_direction != DMA_TO_DEVICE || + !(cmd->se_cmd_flags & SCF_BIDI)) + return TCM_INVALID_CDB_FIELD; + sectors = transport_get_sectors_10(cdb); + + cmd->t_task_lba = transport_lba_32(cdb); + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + + /* + * Setup BIDI XOR callback to be run after I/O completion. + */ + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + cmd->transport_complete_callback = &xdreadwrite_callback; + if (cdb[1] & 0x8) + cmd->se_cmd_flags |= SCF_FUA; + break; + case VARIABLE_LENGTH_CMD: + { + u16 service_action = get_unaligned_be16(&cdb[8]); + switch (service_action) { + case XDWRITEREAD_32: + sectors = transport_get_sectors_32(cdb); + + /* + * Use WRITE_32 and READ_32 opcodes for the emulated + * XDWRITE_READ_32 logic. + */ + cmd->t_task_lba = transport_lba_64_ext(cdb); + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB; + + /* + * Setup BIDI XOR callback to be run during after I/O + * completion. + */ + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_execute_rw; + cmd->transport_complete_callback = &xdreadwrite_callback; + if (cdb[1] & 0x8) + cmd->se_cmd_flags |= SCF_FUA; + break; + case WRITE_SAME_32: + sectors = transport_get_sectors_32(cdb); + if (!sectors) { + pr_err("WSNZ=1, WRITE_SAME w/sectors=0 not" + " supported\n"); + return TCM_INVALID_CDB_FIELD; + } + + size = sbc_get_size(cmd, 1); + cmd->t_task_lba = get_unaligned_be64(&cdb[12]); + + ret = sbc_setup_write_same(cmd, &cdb[10], ops); + if (ret) + return ret; + break; + default: + pr_err("VARIABLE_LENGTH_CMD service action" + " 0x%04x not supported\n", service_action); + return TCM_UNSUPPORTED_SCSI_OPCODE; + } + break; + } + case COMPARE_AND_WRITE: + sectors = cdb[13]; + /* + * Currently enforce COMPARE_AND_WRITE for a single sector + */ + if (sectors > 1) { + pr_err("COMPARE_AND_WRITE contains NoLB: %u greater" + " than 1\n", sectors); + return TCM_INVALID_CDB_FIELD; + } + /* + * Double size because we have two buffers, note that + * zero is not an error.. + */ + size = 2 * sbc_get_size(cmd, sectors); + cmd->t_task_lba = get_unaligned_be64(&cdb[2]); + cmd->t_task_nolb = sectors; + cmd->se_cmd_flags |= SCF_SCSI_DATA_CDB | SCF_COMPARE_AND_WRITE; + cmd->execute_rw = ops->execute_rw; + cmd->execute_cmd = sbc_compare_and_write; + cmd->transport_complete_callback = compare_and_write_callback; + break; + case READ_CAPACITY: + size = READ_CAP_LEN; + cmd->execute_cmd = sbc_emulate_readcapacity; + break; + case SERVICE_ACTION_IN: + switch (cmd->t_task_cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + cmd->execute_cmd = sbc_emulate_readcapacity_16; + break; + case SAI_REPORT_REFERRALS: + cmd->execute_cmd = target_emulate_report_referrals; + break; + default: + pr_err("Unsupported SA: 0x%02x\n", + cmd->t_task_cdb[1] & 0x1f); + return TCM_INVALID_CDB_FIELD; + } + size = (cdb[10] << 24) | (cdb[11] << 16) | + (cdb[12] << 8) | cdb[13]; + break; + case SYNCHRONIZE_CACHE: + case SYNCHRONIZE_CACHE_16: + if (cdb[0] == SYNCHRONIZE_CACHE) { + sectors = transport_get_sectors_10(cdb); + cmd->t_task_lba = transport_lba_32(cdb); + } else { + sectors = transport_get_sectors_16(cdb); + cmd->t_task_lba = transport_lba_64(cdb); + } + if (ops->execute_sync_cache) { + cmd->execute_cmd = ops->execute_sync_cache; + goto check_lba; + } + size = 0; + cmd->execute_cmd = sbc_emulate_noop; + break; + case UNMAP: + if (!ops->execute_unmap) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + size = get_unaligned_be16(&cdb[7]); + cmd->execute_cmd = ops->execute_unmap; + break; + case WRITE_SAME_16: + sectors = transport_get_sectors_16(cdb); + if (!sectors) { + pr_err("WSNZ=1, WRITE_SAME w/sectors=0 not supported\n"); + return TCM_INVALID_CDB_FIELD; + } + + size = sbc_get_size(cmd, 1); + cmd->t_task_lba = get_unaligned_be64(&cdb[2]); + + ret = sbc_setup_write_same(cmd, &cdb[1], ops); + if (ret) + return ret; + break; + case WRITE_SAME: + sectors = transport_get_sectors_10(cdb); + if (!sectors) { + pr_err("WSNZ=1, WRITE_SAME w/sectors=0 not supported\n"); + return TCM_INVALID_CDB_FIELD; + } + + size = sbc_get_size(cmd, 1); + cmd->t_task_lba = get_unaligned_be32(&cdb[2]); + + /* + * Follow sbcr26 with WRITE_SAME (10) and check for the existence + * of byte 1 bit 3 UNMAP instead of original reserved field + */ + ret = sbc_setup_write_same(cmd, &cdb[1], ops); + if (ret) + return ret; + break; + case VERIFY: + size = 0; + sectors = transport_get_sectors_10(cdb); + cmd->t_task_lba = transport_lba_32(cdb); + cmd->execute_cmd = sbc_emulate_noop; + goto check_lba; + case REZERO_UNIT: + case SEEK_6: + case SEEK_10: + /* + * There are still clients out there which use these old SCSI-2 + * commands. This mainly happens when running VMs with legacy + * guest systems, connected via SCSI command pass-through to + * iSCSI targets. Make them happy and return status GOOD. + */ + size = 0; + cmd->execute_cmd = sbc_emulate_noop; + break; + default: + ret = spc_parse_cdb(cmd, &size); + if (ret) + return ret; + } + + /* reject any command that we don't have a handler for */ + if (!(cmd->se_cmd_flags & SCF_SCSI_DATA_CDB) && !cmd->execute_cmd) + return TCM_UNSUPPORTED_SCSI_OPCODE; + + if (cmd->se_cmd_flags & SCF_SCSI_DATA_CDB) { + unsigned long long end_lba; + + if (sectors > dev->dev_attrib.fabric_max_sectors) { + printk_ratelimited(KERN_ERR "SCSI OP %02xh with too" + " big sectors %u exceeds fabric_max_sectors:" + " %u\n", cdb[0], sectors, + dev->dev_attrib.fabric_max_sectors); + return TCM_INVALID_CDB_FIELD; + } + if (sectors > dev->dev_attrib.hw_max_sectors) { + printk_ratelimited(KERN_ERR "SCSI OP %02xh with too" + " big sectors %u exceeds backend hw_max_sectors:" + " %u\n", cdb[0], sectors, + dev->dev_attrib.hw_max_sectors); + return TCM_INVALID_CDB_FIELD; + } +check_lba: + end_lba = dev->transport->get_blocks(dev) + 1; + if (cmd->t_task_lba + sectors > end_lba) { + pr_err("cmd exceeds last lba %llu " + "(lba %llu, sectors %u)\n", + end_lba, cmd->t_task_lba, sectors); + return TCM_ADDRESS_OUT_OF_RANGE; + } + + if (!(cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE)) + size = sbc_get_size(cmd, sectors); + } + + return target_cmd_size_check(cmd, size); +} +EXPORT_SYMBOL(sbc_parse_cdb); + +u32 sbc_get_device_type(struct se_device *dev) +{ + return TYPE_DISK; +} +EXPORT_SYMBOL(sbc_get_device_type); + +sense_reason_t +sbc_execute_unmap(struct se_cmd *cmd, + sense_reason_t (*do_unmap_fn)(struct se_cmd *, void *, + sector_t, sector_t), + void *priv) +{ + struct se_device *dev = cmd->se_dev; + unsigned char *buf, *ptr = NULL; + sector_t lba; + int size; + u32 range; + sense_reason_t ret = 0; + int dl, bd_dl; + + /* We never set ANC_SUP */ + if (cmd->t_task_cdb[1]) + return TCM_INVALID_CDB_FIELD; + + if (cmd->data_length == 0) { + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; + } + + if (cmd->data_length < 8) { + pr_warn("UNMAP parameter list length %u too small\n", + cmd->data_length); + return TCM_PARAMETER_LIST_LENGTH_ERROR; + } + + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + dl = get_unaligned_be16(&buf[0]); + bd_dl = get_unaligned_be16(&buf[2]); + + size = cmd->data_length - 8; + if (bd_dl > size) + pr_warn("UNMAP parameter list length %u too small, ignoring bd_dl %u\n", + cmd->data_length, bd_dl); + else + size = bd_dl; + + if (size / 16 > dev->dev_attrib.max_unmap_block_desc_count) { + ret = TCM_INVALID_PARAMETER_LIST; + goto err; + } + + /* First UNMAP block descriptor starts at 8 byte offset */ + ptr = &buf[8]; + pr_debug("UNMAP: Sub: %s Using dl: %u bd_dl: %u size: %u" + " ptr: %p\n", dev->transport->name, dl, bd_dl, size, ptr); + + while (size >= 16) { + lba = get_unaligned_be64(&ptr[0]); + range = get_unaligned_be32(&ptr[8]); + pr_debug("UNMAP: Using lba: %llu and range: %u\n", + (unsigned long long)lba, range); + + if (range > dev->dev_attrib.max_unmap_lba_count) { + ret = TCM_INVALID_PARAMETER_LIST; + goto err; + } + + if (lba + range > dev->transport->get_blocks(dev) + 1) { + ret = TCM_ADDRESS_OUT_OF_RANGE; + goto err; + } + + ret = do_unmap_fn(cmd, priv, lba, range); + if (ret) + goto err; + + ptr += 16; + size -= 16; + } + +err: + transport_kunmap_data_sg(cmd); + if (!ret) + target_complete_cmd(cmd, GOOD); + return ret; +} +EXPORT_SYMBOL(sbc_execute_unmap); + +void +sbc_dif_generate(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct se_dif_v1_tuple *sdt; + struct scatterlist *dsg, *psg = cmd->t_prot_sg; + sector_t sector = cmd->t_task_lba; + void *daddr, *paddr; + int i, j, offset = 0; + + for_each_sg(cmd->t_data_sg, dsg, cmd->t_data_nents, i) { + daddr = kmap_atomic(sg_page(dsg)) + dsg->offset; + paddr = kmap_atomic(sg_page(psg)) + psg->offset; + + for (j = 0; j < dsg->length; j += dev->dev_attrib.block_size) { + + if (offset >= psg->length) { + kunmap_atomic(paddr); + psg = sg_next(psg); + paddr = kmap_atomic(sg_page(psg)) + psg->offset; + offset = 0; + } + + sdt = paddr + offset; + sdt->guard_tag = cpu_to_be16(crc_t10dif(daddr + j, + dev->dev_attrib.block_size)); + if (dev->dev_attrib.pi_prot_type == TARGET_DIF_TYPE1_PROT) + sdt->ref_tag = cpu_to_be32(sector & 0xffffffff); + sdt->app_tag = 0; + + pr_debug("DIF WRITE INSERT sector: %llu guard_tag: 0x%04x" + " app_tag: 0x%04x ref_tag: %u\n", + (unsigned long long)sector, sdt->guard_tag, + sdt->app_tag, be32_to_cpu(sdt->ref_tag)); + + sector++; + offset += sizeof(struct se_dif_v1_tuple); + } + + kunmap_atomic(paddr); + kunmap_atomic(daddr); + } +} + +static sense_reason_t +sbc_dif_v1_verify(struct se_device *dev, struct se_dif_v1_tuple *sdt, + const void *p, sector_t sector, unsigned int ei_lba) +{ + int block_size = dev->dev_attrib.block_size; + __be16 csum; + + csum = cpu_to_be16(crc_t10dif(p, block_size)); + + if (sdt->guard_tag != csum) { + pr_err("DIFv1 checksum failed on sector %llu guard tag 0x%04x" + " csum 0x%04x\n", (unsigned long long)sector, + be16_to_cpu(sdt->guard_tag), be16_to_cpu(csum)); + return TCM_LOGICAL_BLOCK_GUARD_CHECK_FAILED; + } + + if (dev->dev_attrib.pi_prot_type == TARGET_DIF_TYPE1_PROT && + be32_to_cpu(sdt->ref_tag) != (sector & 0xffffffff)) { + pr_err("DIFv1 Type 1 reference failed on sector: %llu tag: 0x%08x" + " sector MSB: 0x%08x\n", (unsigned long long)sector, + be32_to_cpu(sdt->ref_tag), (u32)(sector & 0xffffffff)); + return TCM_LOGICAL_BLOCK_REF_TAG_CHECK_FAILED; + } + + if (dev->dev_attrib.pi_prot_type == TARGET_DIF_TYPE2_PROT && + be32_to_cpu(sdt->ref_tag) != ei_lba) { + pr_err("DIFv1 Type 2 reference failed on sector: %llu tag: 0x%08x" + " ei_lba: 0x%08x\n", (unsigned long long)sector, + be32_to_cpu(sdt->ref_tag), ei_lba); + return TCM_LOGICAL_BLOCK_REF_TAG_CHECK_FAILED; + } + + return 0; +} + +static void +sbc_dif_copy_prot(struct se_cmd *cmd, unsigned int sectors, bool read, + struct scatterlist *sg, int sg_off) +{ + struct se_device *dev = cmd->se_dev; + struct scatterlist *psg; + void *paddr, *addr; + unsigned int i, len, left; + unsigned int offset = sg_off; + + left = sectors * dev->prot_length; + + for_each_sg(cmd->t_prot_sg, psg, cmd->t_prot_nents, i) { + unsigned int psg_len, copied = 0; + + paddr = kmap_atomic(sg_page(psg)) + psg->offset; + psg_len = min(left, psg->length); + while (psg_len) { + len = min(psg_len, sg->length - offset); + addr = kmap_atomic(sg_page(sg)) + sg->offset + offset; + + if (read) + memcpy(paddr + copied, addr, len); + else + memcpy(addr, paddr + copied, len); + + left -= len; + offset += len; + copied += len; + psg_len -= len; + + if (offset >= sg->length) { + sg = sg_next(sg); + offset = 0; + } + kunmap_atomic(addr); + } + kunmap_atomic(paddr); + } +} + +sense_reason_t +sbc_dif_verify_write(struct se_cmd *cmd, sector_t start, unsigned int sectors, + unsigned int ei_lba, struct scatterlist *sg, int sg_off) +{ + struct se_device *dev = cmd->se_dev; + struct se_dif_v1_tuple *sdt; + struct scatterlist *dsg, *psg = cmd->t_prot_sg; + sector_t sector = start; + void *daddr, *paddr; + int i, j, offset = 0; + sense_reason_t rc; + + for_each_sg(cmd->t_data_sg, dsg, cmd->t_data_nents, i) { + daddr = kmap_atomic(sg_page(dsg)) + dsg->offset; + paddr = kmap_atomic(sg_page(psg)) + psg->offset; + + for (j = 0; j < dsg->length; j += dev->dev_attrib.block_size) { + + if (offset >= psg->length) { + kunmap_atomic(paddr); + psg = sg_next(psg); + paddr = kmap_atomic(sg_page(psg)) + psg->offset; + offset = 0; + } + + sdt = paddr + offset; + + pr_debug("DIF WRITE sector: %llu guard_tag: 0x%04x" + " app_tag: 0x%04x ref_tag: %u\n", + (unsigned long long)sector, sdt->guard_tag, + sdt->app_tag, be32_to_cpu(sdt->ref_tag)); + + rc = sbc_dif_v1_verify(dev, sdt, daddr + j, sector, + ei_lba); + if (rc) { + kunmap_atomic(paddr); + kunmap_atomic(daddr); + cmd->bad_sector = sector; + return rc; + } + + sector++; + ei_lba++; + offset += sizeof(struct se_dif_v1_tuple); + } + + kunmap_atomic(paddr); + kunmap_atomic(daddr); + } + sbc_dif_copy_prot(cmd, sectors, false, sg, sg_off); + + return 0; +} +EXPORT_SYMBOL(sbc_dif_verify_write); + +static sense_reason_t +__sbc_dif_verify_read(struct se_cmd *cmd, sector_t start, unsigned int sectors, + unsigned int ei_lba, struct scatterlist *sg, int sg_off) +{ + struct se_device *dev = cmd->se_dev; + struct se_dif_v1_tuple *sdt; + struct scatterlist *dsg, *psg = sg; + sector_t sector = start; + void *daddr, *paddr; + int i, j, offset = sg_off; + sense_reason_t rc; + + for_each_sg(cmd->t_data_sg, dsg, cmd->t_data_nents, i) { + daddr = kmap_atomic(sg_page(dsg)) + dsg->offset; + paddr = kmap_atomic(sg_page(psg)) + sg->offset; + + for (j = 0; j < dsg->length; j += dev->dev_attrib.block_size) { + + if (offset >= psg->length) { + kunmap_atomic(paddr); + psg = sg_next(psg); + paddr = kmap_atomic(sg_page(psg)) + psg->offset; + offset = 0; + } + + sdt = paddr + offset; + + pr_debug("DIF READ sector: %llu guard_tag: 0x%04x" + " app_tag: 0x%04x ref_tag: %u\n", + (unsigned long long)sector, sdt->guard_tag, + sdt->app_tag, be32_to_cpu(sdt->ref_tag)); + + if (sdt->app_tag == cpu_to_be16(0xffff)) { + sector++; + offset += sizeof(struct se_dif_v1_tuple); + continue; + } + + rc = sbc_dif_v1_verify(dev, sdt, daddr + j, sector, + ei_lba); + if (rc) { + kunmap_atomic(paddr); + kunmap_atomic(daddr); + cmd->bad_sector = sector; + return rc; + } + + sector++; + ei_lba++; + offset += sizeof(struct se_dif_v1_tuple); + } + + kunmap_atomic(paddr); + kunmap_atomic(daddr); + } + + return 0; +} + +sense_reason_t +sbc_dif_read_strip(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + u32 sectors = cmd->prot_length / dev->prot_length; + + return __sbc_dif_verify_read(cmd, cmd->t_task_lba, sectors, 0, + cmd->t_prot_sg, 0); +} + +sense_reason_t +sbc_dif_verify_read(struct se_cmd *cmd, sector_t start, unsigned int sectors, + unsigned int ei_lba, struct scatterlist *sg, int sg_off) +{ + sense_reason_t rc; + + rc = __sbc_dif_verify_read(cmd, start, sectors, ei_lba, sg, sg_off); + if (rc) + return rc; + + sbc_dif_copy_prot(cmd, sectors, true, sg, sg_off); + return 0; +} +EXPORT_SYMBOL(sbc_dif_verify_read); diff --git a/drivers/target/target_core_scdb.c b/drivers/target/target_core_scdb.c deleted file mode 100644 index dc6fed037ab..00000000000 --- a/drivers/target/target_core_scdb.c +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Filename: target_core_scdb.c - * - * This file contains the generic target engine Split CDB related functions. - * - * Copyright (c) 2004-2005 PyX Technologies, Inc. - * Copyright (c) 2005, 2006, 2007 SBE, Inc. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org - * - * Nicholas A. Bellinger <nab@kernel.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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - ******************************************************************************/ - -#include <linux/net.h> -#include <linux/string.h> -#include <scsi/scsi.h> -#include <asm/unaligned.h> - -#include <target/target_core_base.h> -#include <target/target_core_transport.h> - -#include "target_core_scdb.h" - -/* split_cdb_XX_6(): - * - * 21-bit LBA w/ 8-bit SECTORS - */ -void split_cdb_XX_6( - unsigned long long lba, - u32 *sectors, - unsigned char *cdb) -{ - cdb[1] = (lba >> 16) & 0x1f; - cdb[2] = (lba >> 8) & 0xff; - cdb[3] = lba & 0xff; - cdb[4] = *sectors & 0xff; -} - -/* split_cdb_XX_10(): - * - * 32-bit LBA w/ 16-bit SECTORS - */ -void split_cdb_XX_10( - unsigned long long lba, - u32 *sectors, - unsigned char *cdb) -{ - put_unaligned_be32(lba, &cdb[2]); - put_unaligned_be16(*sectors, &cdb[7]); -} - -/* split_cdb_XX_12(): - * - * 32-bit LBA w/ 32-bit SECTORS - */ -void split_cdb_XX_12( - unsigned long long lba, - u32 *sectors, - unsigned char *cdb) -{ - put_unaligned_be32(lba, &cdb[2]); - put_unaligned_be32(*sectors, &cdb[6]); -} - -/* split_cdb_XX_16(): - * - * 64-bit LBA w/ 32-bit SECTORS - */ -void split_cdb_XX_16( - unsigned long long lba, - u32 *sectors, - unsigned char *cdb) -{ - put_unaligned_be64(lba, &cdb[2]); - put_unaligned_be32(*sectors, &cdb[10]); -} - -/* - * split_cdb_XX_32(): - * - * 64-bit LBA w/ 32-bit SECTORS such as READ_32, WRITE_32 and emulated XDWRITEREAD_32 - */ -void split_cdb_XX_32( - unsigned long long lba, - u32 *sectors, - unsigned char *cdb) -{ - put_unaligned_be64(lba, &cdb[12]); - put_unaligned_be32(*sectors, &cdb[28]); -} diff --git a/drivers/target/target_core_scdb.h b/drivers/target/target_core_scdb.h deleted file mode 100644 index 98cd1c01ed8..00000000000 --- a/drivers/target/target_core_scdb.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef TARGET_CORE_SCDB_H -#define TARGET_CORE_SCDB_H - -extern void split_cdb_XX_6(unsigned long long, u32 *, unsigned char *); -extern void split_cdb_XX_10(unsigned long long, u32 *, unsigned char *); -extern void split_cdb_XX_12(unsigned long long, u32 *, unsigned char *); -extern void split_cdb_XX_16(unsigned long long, u32 *, unsigned char *); -extern void split_cdb_XX_32(unsigned long long, u32 *, unsigned char *); - -#endif /* TARGET_CORE_SCDB_H */ diff --git a/drivers/target/target_core_spc.c b/drivers/target/target_core_spc.c new file mode 100644 index 00000000000..6cd7222738f --- /dev/null +++ b/drivers/target/target_core_spc.c @@ -0,0 +1,1445 @@ +/* + * SCSI Primary Commands (SPC) parsing and emulation. + * + * (c) Copyright 2002-2013 Datera, Inc. + * + * Nicholas A. Bellinger <nab@kernel.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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <asm/unaligned.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_tcq.h> + +#include <target/target_core_base.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> + +#include "target_core_internal.h" +#include "target_core_alua.h" +#include "target_core_pr.h" +#include "target_core_ua.h" +#include "target_core_xcopy.h" + +static void spc_fill_alua_data(struct se_port *port, unsigned char *buf) +{ + struct t10_alua_tg_pt_gp *tg_pt_gp; + struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; + + /* + * Set SCCS for MAINTENANCE_IN + REPORT_TARGET_PORT_GROUPS. + */ + buf[5] = 0x80; + + /* + * Set TPGS field for explicit and/or implicit ALUA access type + * and opteration. + * + * See spc4r17 section 6.4.2 Table 135 + */ + if (!port) + return; + tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; + if (!tg_pt_gp_mem) + return; + + spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); + tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; + if (tg_pt_gp) + buf[5] |= tg_pt_gp->tg_pt_gp_alua_access_type; + spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); +} + +sense_reason_t +spc_emulate_inquiry_std(struct se_cmd *cmd, unsigned char *buf) +{ + struct se_lun *lun = cmd->se_lun; + struct se_device *dev = cmd->se_dev; + struct se_session *sess = cmd->se_sess; + + /* Set RMB (removable media) for tape devices */ + if (dev->transport->get_device_type(dev) == TYPE_TAPE) + buf[1] = 0x80; + + buf[2] = 0x05; /* SPC-3 */ + + /* + * NORMACA and HISUP = 0, RESPONSE DATA FORMAT = 2 + * + * SPC4 says: + * A RESPONSE DATA FORMAT field set to 2h indicates that the + * standard INQUIRY data is in the format defined in this + * standard. Response data format values less than 2h are + * obsolete. Response data format values greater than 2h are + * reserved. + */ + buf[3] = 2; + + /* + * Enable SCCS and TPGS fields for Emulated ALUA + */ + spc_fill_alua_data(lun->lun_sep, buf); + + /* + * Set Third-Party Copy (3PC) bit to indicate support for EXTENDED_COPY + */ + if (dev->dev_attrib.emulate_3pc) + buf[5] |= 0x8; + /* + * Set Protection (PROTECT) bit when DIF has been enabled on the + * device, and the transport supports VERIFY + PASS. + */ + if (sess->sup_prot_ops & (TARGET_PROT_DIN_PASS | TARGET_PROT_DOUT_PASS)) { + if (dev->dev_attrib.pi_prot_type) + buf[5] |= 0x1; + } + + buf[7] = 0x2; /* CmdQue=1 */ + + memcpy(&buf[8], "LIO-ORG ", 8); + memset(&buf[16], 0x20, 16); + memcpy(&buf[16], dev->t10_wwn.model, + min_t(size_t, strlen(dev->t10_wwn.model), 16)); + memcpy(&buf[32], dev->t10_wwn.revision, + min_t(size_t, strlen(dev->t10_wwn.revision), 4)); + buf[4] = 31; /* Set additional length to 31 */ + + return 0; +} +EXPORT_SYMBOL(spc_emulate_inquiry_std); + +/* unit serial number */ +static sense_reason_t +spc_emulate_evpd_80(struct se_cmd *cmd, unsigned char *buf) +{ + struct se_device *dev = cmd->se_dev; + u16 len; + + if (dev->dev_flags & DF_EMULATED_VPD_UNIT_SERIAL) { + len = sprintf(&buf[4], "%s", dev->t10_wwn.unit_serial); + len++; /* Extra Byte for NULL Terminator */ + buf[3] = len; + } + return 0; +} + +void spc_parse_naa_6h_vendor_specific(struct se_device *dev, + unsigned char *buf) +{ + unsigned char *p = &dev->t10_wwn.unit_serial[0]; + int cnt; + bool next = true; + + /* + * Generate up to 36 bits of VENDOR SPECIFIC IDENTIFIER starting on + * byte 3 bit 3-0 for NAA IEEE Registered Extended DESIGNATOR field + * format, followed by 64 bits of VENDOR SPECIFIC IDENTIFIER EXTENSION + * to complete the payload. These are based from VPD=0x80 PRODUCT SERIAL + * NUMBER set via vpd_unit_serial in target_core_configfs.c to ensure + * per device uniqeness. + */ + for (cnt = 0; *p && cnt < 13; p++) { + int val = hex_to_bin(*p); + + if (val < 0) + continue; + + if (next) { + next = false; + buf[cnt++] |= val; + } else { + next = true; + buf[cnt] = val << 4; + } + } +} + +/* + * Device identification VPD, for a complete list of + * DESIGNATOR TYPEs see spc4r17 Table 459. + */ +sense_reason_t +spc_emulate_evpd_83(struct se_cmd *cmd, unsigned char *buf) +{ + struct se_device *dev = cmd->se_dev; + struct se_lun *lun = cmd->se_lun; + struct se_port *port = NULL; + struct se_portal_group *tpg = NULL; + struct t10_alua_lu_gp_member *lu_gp_mem; + struct t10_alua_tg_pt_gp *tg_pt_gp; + struct t10_alua_tg_pt_gp_member *tg_pt_gp_mem; + unsigned char *prod = &dev->t10_wwn.model[0]; + u32 prod_len; + u32 unit_serial_len, off = 0; + u16 len = 0, id_len; + + off = 4; + + /* + * NAA IEEE Registered Extended Assigned designator format, see + * spc4r17 section 7.7.3.6.5 + * + * We depend upon a target_core_mod/ConfigFS provided + * /sys/kernel/config/target/core/$HBA/$DEV/wwn/vpd_unit_serial + * value in order to return the NAA id. + */ + if (!(dev->dev_flags & DF_EMULATED_VPD_UNIT_SERIAL)) + goto check_t10_vend_desc; + + /* CODE SET == Binary */ + buf[off++] = 0x1; + + /* Set ASSOCIATION == addressed logical unit: 0)b */ + buf[off] = 0x00; + + /* Identifier/Designator type == NAA identifier */ + buf[off++] |= 0x3; + off++; + + /* Identifier/Designator length */ + buf[off++] = 0x10; + + /* + * Start NAA IEEE Registered Extended Identifier/Designator + */ + buf[off++] = (0x6 << 4); + + /* + * Use OpenFabrics IEEE Company ID: 00 14 05 + */ + buf[off++] = 0x01; + buf[off++] = 0x40; + buf[off] = (0x5 << 4); + + /* + * Return ConfigFS Unit Serial Number information for + * VENDOR_SPECIFIC_IDENTIFIER and + * VENDOR_SPECIFIC_IDENTIFIER_EXTENTION + */ + spc_parse_naa_6h_vendor_specific(dev, &buf[off]); + + len = 20; + off = (len + 4); + +check_t10_vend_desc: + /* + * T10 Vendor Identifier Page, see spc4r17 section 7.7.3.4 + */ + id_len = 8; /* For Vendor field */ + prod_len = 4; /* For VPD Header */ + prod_len += 8; /* For Vendor field */ + prod_len += strlen(prod); + prod_len++; /* For : */ + + if (dev->dev_flags & DF_EMULATED_VPD_UNIT_SERIAL) { + unit_serial_len = strlen(&dev->t10_wwn.unit_serial[0]); + unit_serial_len++; /* For NULL Terminator */ + + id_len += sprintf(&buf[off+12], "%s:%s", prod, + &dev->t10_wwn.unit_serial[0]); + } + buf[off] = 0x2; /* ASCII */ + buf[off+1] = 0x1; /* T10 Vendor ID */ + buf[off+2] = 0x0; + memcpy(&buf[off+4], "LIO-ORG", 8); + /* Extra Byte for NULL Terminator */ + id_len++; + /* Identifier Length */ + buf[off+3] = id_len; + /* Header size for Designation descriptor */ + len += (id_len + 4); + off += (id_len + 4); + /* + * struct se_port is only set for INQUIRY VPD=1 through $FABRIC_MOD + */ + port = lun->lun_sep; + if (port) { + struct t10_alua_lu_gp *lu_gp; + u32 padding, scsi_name_len, scsi_target_len; + u16 lu_gp_id = 0; + u16 tg_pt_gp_id = 0; + u16 tpgt; + + tpg = port->sep_tpg; + /* + * Relative target port identifer, see spc4r17 + * section 7.7.3.7 + * + * Get the PROTOCOL IDENTIFIER as defined by spc4r17 + * section 7.5.1 Table 362 + */ + buf[off] = + (tpg->se_tpg_tfo->get_fabric_proto_ident(tpg) << 4); + buf[off++] |= 0x1; /* CODE SET == Binary */ + buf[off] = 0x80; /* Set PIV=1 */ + /* Set ASSOCIATION == target port: 01b */ + buf[off] |= 0x10; + /* DESIGNATOR TYPE == Relative target port identifer */ + buf[off++] |= 0x4; + off++; /* Skip over Reserved */ + buf[off++] = 4; /* DESIGNATOR LENGTH */ + /* Skip over Obsolete field in RTPI payload + * in Table 472 */ + off += 2; + buf[off++] = ((port->sep_rtpi >> 8) & 0xff); + buf[off++] = (port->sep_rtpi & 0xff); + len += 8; /* Header size + Designation descriptor */ + /* + * Target port group identifier, see spc4r17 + * section 7.7.3.8 + * + * Get the PROTOCOL IDENTIFIER as defined by spc4r17 + * section 7.5.1 Table 362 + */ + tg_pt_gp_mem = port->sep_alua_tg_pt_gp_mem; + if (!tg_pt_gp_mem) + goto check_lu_gp; + + spin_lock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); + tg_pt_gp = tg_pt_gp_mem->tg_pt_gp; + if (!tg_pt_gp) { + spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); + goto check_lu_gp; + } + tg_pt_gp_id = tg_pt_gp->tg_pt_gp_id; + spin_unlock(&tg_pt_gp_mem->tg_pt_gp_mem_lock); + + buf[off] = + (tpg->se_tpg_tfo->get_fabric_proto_ident(tpg) << 4); + buf[off++] |= 0x1; /* CODE SET == Binary */ + buf[off] = 0x80; /* Set PIV=1 */ + /* Set ASSOCIATION == target port: 01b */ + buf[off] |= 0x10; + /* DESIGNATOR TYPE == Target port group identifier */ + buf[off++] |= 0x5; + off++; /* Skip over Reserved */ + buf[off++] = 4; /* DESIGNATOR LENGTH */ + off += 2; /* Skip over Reserved Field */ + buf[off++] = ((tg_pt_gp_id >> 8) & 0xff); + buf[off++] = (tg_pt_gp_id & 0xff); + len += 8; /* Header size + Designation descriptor */ + /* + * Logical Unit Group identifier, see spc4r17 + * section 7.7.3.8 + */ +check_lu_gp: + lu_gp_mem = dev->dev_alua_lu_gp_mem; + if (!lu_gp_mem) + goto check_scsi_name; + + spin_lock(&lu_gp_mem->lu_gp_mem_lock); + lu_gp = lu_gp_mem->lu_gp; + if (!lu_gp) { + spin_unlock(&lu_gp_mem->lu_gp_mem_lock); + goto check_scsi_name; + } + lu_gp_id = lu_gp->lu_gp_id; + spin_unlock(&lu_gp_mem->lu_gp_mem_lock); + + buf[off++] |= 0x1; /* CODE SET == Binary */ + /* DESIGNATOR TYPE == Logical Unit Group identifier */ + buf[off++] |= 0x6; + off++; /* Skip over Reserved */ + buf[off++] = 4; /* DESIGNATOR LENGTH */ + off += 2; /* Skip over Reserved Field */ + buf[off++] = ((lu_gp_id >> 8) & 0xff); + buf[off++] = (lu_gp_id & 0xff); + len += 8; /* Header size + Designation descriptor */ + /* + * SCSI name string designator, see spc4r17 + * section 7.7.3.11 + * + * Get the PROTOCOL IDENTIFIER as defined by spc4r17 + * section 7.5.1 Table 362 + */ +check_scsi_name: + buf[off] = + (tpg->se_tpg_tfo->get_fabric_proto_ident(tpg) << 4); + buf[off++] |= 0x3; /* CODE SET == UTF-8 */ + buf[off] = 0x80; /* Set PIV=1 */ + /* Set ASSOCIATION == target port: 01b */ + buf[off] |= 0x10; + /* DESIGNATOR TYPE == SCSI name string */ + buf[off++] |= 0x8; + off += 2; /* Skip over Reserved and length */ + /* + * SCSI name string identifer containing, $FABRIC_MOD + * dependent information. For LIO-Target and iSCSI + * Target Port, this means "<iSCSI name>,t,0x<TPGT> in + * UTF-8 encoding. + */ + tpgt = tpg->se_tpg_tfo->tpg_get_tag(tpg); + scsi_name_len = sprintf(&buf[off], "%s,t,0x%04x", + tpg->se_tpg_tfo->tpg_get_wwn(tpg), tpgt); + scsi_name_len += 1 /* Include NULL terminator */; + /* + * The null-terminated, null-padded (see 4.4.2) SCSI + * NAME STRING field contains a UTF-8 format string. + * The number of bytes in the SCSI NAME STRING field + * (i.e., the value in the DESIGNATOR LENGTH field) + * shall be no larger than 256 and shall be a multiple + * of four. + */ + padding = ((-scsi_name_len) & 3); + if (padding) + scsi_name_len += padding; + if (scsi_name_len > 256) + scsi_name_len = 256; + + buf[off-1] = scsi_name_len; + off += scsi_name_len; + /* Header size + Designation descriptor */ + len += (scsi_name_len + 4); + + /* + * Target device designator + */ + buf[off] = + (tpg->se_tpg_tfo->get_fabric_proto_ident(tpg) << 4); + buf[off++] |= 0x3; /* CODE SET == UTF-8 */ + buf[off] = 0x80; /* Set PIV=1 */ + /* Set ASSOCIATION == target device: 10b */ + buf[off] |= 0x20; + /* DESIGNATOR TYPE == SCSI name string */ + buf[off++] |= 0x8; + off += 2; /* Skip over Reserved and length */ + /* + * SCSI name string identifer containing, $FABRIC_MOD + * dependent information. For LIO-Target and iSCSI + * Target Port, this means "<iSCSI name>" in + * UTF-8 encoding. + */ + scsi_target_len = sprintf(&buf[off], "%s", + tpg->se_tpg_tfo->tpg_get_wwn(tpg)); + scsi_target_len += 1 /* Include NULL terminator */; + /* + * The null-terminated, null-padded (see 4.4.2) SCSI + * NAME STRING field contains a UTF-8 format string. + * The number of bytes in the SCSI NAME STRING field + * (i.e., the value in the DESIGNATOR LENGTH field) + * shall be no larger than 256 and shall be a multiple + * of four. + */ + padding = ((-scsi_target_len) & 3); + if (padding) + scsi_target_len += padding; + if (scsi_target_len > 256) + scsi_target_len = 256; + + buf[off-1] = scsi_target_len; + off += scsi_target_len; + + /* Header size + Designation descriptor */ + len += (scsi_target_len + 4); + } + buf[2] = ((len >> 8) & 0xff); + buf[3] = (len & 0xff); /* Page Length for VPD 0x83 */ + return 0; +} +EXPORT_SYMBOL(spc_emulate_evpd_83); + +static bool +spc_check_dev_wce(struct se_device *dev) +{ + bool wce = false; + + if (dev->transport->get_write_cache) + wce = dev->transport->get_write_cache(dev); + else if (dev->dev_attrib.emulate_write_cache > 0) + wce = true; + + return wce; +} + +/* Extended INQUIRY Data VPD Page */ +static sense_reason_t +spc_emulate_evpd_86(struct se_cmd *cmd, unsigned char *buf) +{ + struct se_device *dev = cmd->se_dev; + struct se_session *sess = cmd->se_sess; + + buf[3] = 0x3c; + /* + * Set GRD_CHK + REF_CHK for TYPE1 protection, or GRD_CHK + * only for TYPE3 protection. + */ + if (sess->sup_prot_ops & (TARGET_PROT_DIN_PASS | TARGET_PROT_DOUT_PASS)) { + if (dev->dev_attrib.pi_prot_type == TARGET_DIF_TYPE1_PROT) + buf[4] = 0x5; + else if (dev->dev_attrib.pi_prot_type == TARGET_DIF_TYPE3_PROT) + buf[4] = 0x4; + } + + /* Set HEADSUP, ORDSUP, SIMPSUP */ + buf[5] = 0x07; + + /* If WriteCache emulation is enabled, set V_SUP */ + if (spc_check_dev_wce(dev)) + buf[6] = 0x01; + /* If an LBA map is present set R_SUP */ + spin_lock(&cmd->se_dev->t10_alua.lba_map_lock); + if (!list_empty(&dev->t10_alua.lba_map_list)) + buf[8] = 0x10; + spin_unlock(&cmd->se_dev->t10_alua.lba_map_lock); + return 0; +} + +/* Block Limits VPD page */ +static sense_reason_t +spc_emulate_evpd_b0(struct se_cmd *cmd, unsigned char *buf) +{ + struct se_device *dev = cmd->se_dev; + u32 max_sectors; + int have_tp = 0; + int opt, min; + + /* + * Following spc3r22 section 6.5.3 Block Limits VPD page, when + * emulate_tpu=1 or emulate_tpws=1 we will be expect a + * different page length for Thin Provisioning. + */ + if (dev->dev_attrib.emulate_tpu || dev->dev_attrib.emulate_tpws) + have_tp = 1; + + buf[0] = dev->transport->get_device_type(dev); + buf[3] = have_tp ? 0x3c : 0x10; + + /* Set WSNZ to 1 */ + buf[4] = 0x01; + /* + * Set MAXIMUM COMPARE AND WRITE LENGTH + */ + if (dev->dev_attrib.emulate_caw) + buf[5] = 0x01; + + /* + * Set OPTIMAL TRANSFER LENGTH GRANULARITY + */ + if (dev->transport->get_io_min && (min = dev->transport->get_io_min(dev))) + put_unaligned_be16(min / dev->dev_attrib.block_size, &buf[6]); + else + put_unaligned_be16(1, &buf[6]); + + /* + * Set MAXIMUM TRANSFER LENGTH + */ + max_sectors = min(dev->dev_attrib.fabric_max_sectors, + dev->dev_attrib.hw_max_sectors); + put_unaligned_be32(max_sectors, &buf[8]); + + /* + * Set OPTIMAL TRANSFER LENGTH + */ + if (dev->transport->get_io_opt && (opt = dev->transport->get_io_opt(dev))) + put_unaligned_be32(opt / dev->dev_attrib.block_size, &buf[12]); + else + put_unaligned_be32(dev->dev_attrib.optimal_sectors, &buf[12]); + + /* + * Exit now if we don't support TP. + */ + if (!have_tp) + goto max_write_same; + + /* + * Set MAXIMUM UNMAP LBA COUNT + */ + put_unaligned_be32(dev->dev_attrib.max_unmap_lba_count, &buf[20]); + + /* + * Set MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT + */ + put_unaligned_be32(dev->dev_attrib.max_unmap_block_desc_count, + &buf[24]); + + /* + * Set OPTIMAL UNMAP GRANULARITY + */ + put_unaligned_be32(dev->dev_attrib.unmap_granularity, &buf[28]); + + /* + * UNMAP GRANULARITY ALIGNMENT + */ + put_unaligned_be32(dev->dev_attrib.unmap_granularity_alignment, + &buf[32]); + if (dev->dev_attrib.unmap_granularity_alignment != 0) + buf[32] |= 0x80; /* Set the UGAVALID bit */ + + /* + * MAXIMUM WRITE SAME LENGTH + */ +max_write_same: + put_unaligned_be64(dev->dev_attrib.max_write_same_len, &buf[36]); + + return 0; +} + +/* Block Device Characteristics VPD page */ +static sense_reason_t +spc_emulate_evpd_b1(struct se_cmd *cmd, unsigned char *buf) +{ + struct se_device *dev = cmd->se_dev; + + buf[0] = dev->transport->get_device_type(dev); + buf[3] = 0x3c; + buf[5] = dev->dev_attrib.is_nonrot ? 1 : 0; + + return 0; +} + +/* Thin Provisioning VPD */ +static sense_reason_t +spc_emulate_evpd_b2(struct se_cmd *cmd, unsigned char *buf) +{ + struct se_device *dev = cmd->se_dev; + + /* + * From spc3r22 section 6.5.4 Thin Provisioning VPD page: + * + * The PAGE LENGTH field is defined in SPC-4. If the DP bit is set to + * zero, then the page length shall be set to 0004h. If the DP bit + * is set to one, then the page length shall be set to the value + * defined in table 162. + */ + buf[0] = dev->transport->get_device_type(dev); + + /* + * Set Hardcoded length mentioned above for DP=0 + */ + put_unaligned_be16(0x0004, &buf[2]); + + /* + * The THRESHOLD EXPONENT field indicates the threshold set size in + * LBAs as a power of 2 (i.e., the threshold set size is equal to + * 2(threshold exponent)). + * + * Note that this is currently set to 0x00 as mkp says it will be + * changing again. We can enable this once it has settled in T10 + * and is actually used by Linux/SCSI ML code. + */ + buf[4] = 0x00; + + /* + * A TPU bit set to one indicates that the device server supports + * the UNMAP command (see 5.25). A TPU bit set to zero indicates + * that the device server does not support the UNMAP command. + */ + if (dev->dev_attrib.emulate_tpu != 0) + buf[5] = 0x80; + + /* + * A TPWS bit set to one indicates that the device server supports + * the use of the WRITE SAME (16) command (see 5.42) to unmap LBAs. + * A TPWS bit set to zero indicates that the device server does not + * support the use of the WRITE SAME (16) command to unmap LBAs. + */ + if (dev->dev_attrib.emulate_tpws != 0) + buf[5] |= 0x40; + + return 0; +} + +/* Referrals VPD page */ +static sense_reason_t +spc_emulate_evpd_b3(struct se_cmd *cmd, unsigned char *buf) +{ + struct se_device *dev = cmd->se_dev; + + buf[0] = dev->transport->get_device_type(dev); + buf[3] = 0x0c; + put_unaligned_be32(dev->t10_alua.lba_map_segment_size, &buf[8]); + put_unaligned_be32(dev->t10_alua.lba_map_segment_size, &buf[12]); + + return 0; +} + +static sense_reason_t +spc_emulate_evpd_00(struct se_cmd *cmd, unsigned char *buf); + +static struct { + uint8_t page; + sense_reason_t (*emulate)(struct se_cmd *, unsigned char *); +} evpd_handlers[] = { + { .page = 0x00, .emulate = spc_emulate_evpd_00 }, + { .page = 0x80, .emulate = spc_emulate_evpd_80 }, + { .page = 0x83, .emulate = spc_emulate_evpd_83 }, + { .page = 0x86, .emulate = spc_emulate_evpd_86 }, + { .page = 0xb0, .emulate = spc_emulate_evpd_b0 }, + { .page = 0xb1, .emulate = spc_emulate_evpd_b1 }, + { .page = 0xb2, .emulate = spc_emulate_evpd_b2 }, + { .page = 0xb3, .emulate = spc_emulate_evpd_b3 }, +}; + +/* supported vital product data pages */ +static sense_reason_t +spc_emulate_evpd_00(struct se_cmd *cmd, unsigned char *buf) +{ + int p; + + /* + * Only report the INQUIRY EVPD=1 pages after a valid NAA + * Registered Extended LUN WWN has been set via ConfigFS + * during device creation/restart. + */ + if (cmd->se_dev->dev_flags & DF_EMULATED_VPD_UNIT_SERIAL) { + buf[3] = ARRAY_SIZE(evpd_handlers); + for (p = 0; p < ARRAY_SIZE(evpd_handlers); ++p) + buf[p + 4] = evpd_handlers[p].page; + } + + return 0; +} + +static sense_reason_t +spc_emulate_inquiry(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct se_portal_group *tpg = cmd->se_lun->lun_sep->sep_tpg; + unsigned char *rbuf; + unsigned char *cdb = cmd->t_task_cdb; + unsigned char *buf; + sense_reason_t ret; + int p; + int len = 0; + + buf = kzalloc(SE_INQUIRY_BUF, GFP_KERNEL); + if (!buf) { + pr_err("Unable to allocate response buffer for INQUIRY\n"); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + + if (dev == tpg->tpg_virt_lun0.lun_se_dev) + buf[0] = 0x3f; /* Not connected */ + else + buf[0] = dev->transport->get_device_type(dev); + + if (!(cdb[1] & 0x1)) { + if (cdb[2]) { + pr_err("INQUIRY with EVPD==0 but PAGE CODE=%02x\n", + cdb[2]); + ret = TCM_INVALID_CDB_FIELD; + goto out; + } + + ret = spc_emulate_inquiry_std(cmd, buf); + len = buf[4] + 5; + goto out; + } + + for (p = 0; p < ARRAY_SIZE(evpd_handlers); ++p) { + if (cdb[2] == evpd_handlers[p].page) { + buf[1] = cdb[2]; + ret = evpd_handlers[p].emulate(cmd, buf); + len = get_unaligned_be16(&buf[2]) + 4; + goto out; + } + } + + pr_err("Unknown VPD Code: 0x%02x\n", cdb[2]); + ret = TCM_INVALID_CDB_FIELD; + +out: + rbuf = transport_kmap_data_sg(cmd); + if (rbuf) { + memcpy(rbuf, buf, min_t(u32, SE_INQUIRY_BUF, cmd->data_length)); + transport_kunmap_data_sg(cmd); + } + kfree(buf); + + if (!ret) + target_complete_cmd_with_length(cmd, GOOD, len); + return ret; +} + +static int spc_modesense_rwrecovery(struct se_cmd *cmd, u8 pc, u8 *p) +{ + p[0] = 0x01; + p[1] = 0x0a; + + /* No changeable values for now */ + if (pc == 1) + goto out; + +out: + return 12; +} + +static int spc_modesense_control(struct se_cmd *cmd, u8 pc, u8 *p) +{ + struct se_device *dev = cmd->se_dev; + struct se_session *sess = cmd->se_sess; + + p[0] = 0x0a; + p[1] = 0x0a; + + /* No changeable values for now */ + if (pc == 1) + goto out; + + p[2] = 2; + /* + * From spc4r23, 7.4.7 Control mode page + * + * The QUEUE ALGORITHM MODIFIER field (see table 368) specifies + * restrictions on the algorithm used for reordering commands + * having the SIMPLE task attribute (see SAM-4). + * + * Table 368 -- QUEUE ALGORITHM MODIFIER field + * Code Description + * 0h Restricted reordering + * 1h Unrestricted reordering allowed + * 2h to 7h Reserved + * 8h to Fh Vendor specific + * + * A value of zero in the QUEUE ALGORITHM MODIFIER field specifies that + * the device server shall order the processing sequence of commands + * having the SIMPLE task attribute such that data integrity is maintained + * for that I_T nexus (i.e., if the transmission of new SCSI transport protocol + * requests is halted at any time, the final value of all data observable + * on the medium shall be the same as if all the commands had been processed + * with the ORDERED task attribute). + * + * A value of one in the QUEUE ALGORITHM MODIFIER field specifies that the + * device server may reorder the processing sequence of commands having the + * SIMPLE task attribute in any manner. Any data integrity exposures related to + * command sequence order shall be explicitly handled by the application client + * through the selection of appropriate ommands and task attributes. + */ + p[3] = (dev->dev_attrib.emulate_rest_reord == 1) ? 0x00 : 0x10; + /* + * From spc4r17, section 7.4.6 Control mode Page + * + * Unit Attention interlocks control (UN_INTLCK_CTRL) to code 00b + * + * 00b: The logical unit shall clear any unit attention condition + * reported in the same I_T_L_Q nexus transaction as a CHECK CONDITION + * status and shall not establish a unit attention condition when a com- + * mand is completed with BUSY, TASK SET FULL, or RESERVATION CONFLICT + * status. + * + * 10b: The logical unit shall not clear any unit attention condition + * reported in the same I_T_L_Q nexus transaction as a CHECK CONDITION + * status and shall not establish a unit attention condition when + * a command is completed with BUSY, TASK SET FULL, or RESERVATION + * CONFLICT status. + * + * 11b a The logical unit shall not clear any unit attention condition + * reported in the same I_T_L_Q nexus transaction as a CHECK CONDITION + * status and shall establish a unit attention condition for the + * initiator port associated with the I_T nexus on which the BUSY, + * TASK SET FULL, or RESERVATION CONFLICT status is being returned. + * Depending on the status, the additional sense code shall be set to + * PREVIOUS BUSY STATUS, PREVIOUS TASK SET FULL STATUS, or PREVIOUS + * RESERVATION CONFLICT STATUS. Until it is cleared by a REQUEST SENSE + * command, a unit attention condition shall be established only once + * for a BUSY, TASK SET FULL, or RESERVATION CONFLICT status regardless + * to the number of commands completed with one of those status codes. + */ + p[4] = (dev->dev_attrib.emulate_ua_intlck_ctrl == 2) ? 0x30 : + (dev->dev_attrib.emulate_ua_intlck_ctrl == 1) ? 0x20 : 0x00; + /* + * From spc4r17, section 7.4.6 Control mode Page + * + * Task Aborted Status (TAS) bit set to zero. + * + * A task aborted status (TAS) bit set to zero specifies that aborted + * tasks shall be terminated by the device server without any response + * to the application client. A TAS bit set to one specifies that tasks + * aborted by the actions of an I_T nexus other than the I_T nexus on + * which the command was received shall be completed with TASK ABORTED + * status (see SAM-4). + */ + p[5] = (dev->dev_attrib.emulate_tas) ? 0x40 : 0x00; + /* + * From spc4r30, section 7.5.7 Control mode page + * + * Application Tag Owner (ATO) bit set to one. + * + * If the ATO bit is set to one the device server shall not modify the + * LOGICAL BLOCK APPLICATION TAG field and, depending on the protection + * type, shall not modify the contents of the LOGICAL BLOCK REFERENCE + * TAG field. + */ + if (sess->sup_prot_ops & (TARGET_PROT_DIN_PASS | TARGET_PROT_DOUT_PASS)) { + if (dev->dev_attrib.pi_prot_type) + p[5] |= 0x80; + } + + p[8] = 0xff; + p[9] = 0xff; + p[11] = 30; + +out: + return 12; +} + +static int spc_modesense_caching(struct se_cmd *cmd, u8 pc, u8 *p) +{ + struct se_device *dev = cmd->se_dev; + + p[0] = 0x08; + p[1] = 0x12; + + /* No changeable values for now */ + if (pc == 1) + goto out; + + if (spc_check_dev_wce(dev)) + p[2] = 0x04; /* Write Cache Enable */ + p[12] = 0x20; /* Disabled Read Ahead */ + +out: + return 20; +} + +static int spc_modesense_informational_exceptions(struct se_cmd *cmd, u8 pc, unsigned char *p) +{ + p[0] = 0x1c; + p[1] = 0x0a; + + /* No changeable values for now */ + if (pc == 1) + goto out; + +out: + return 12; +} + +static struct { + uint8_t page; + uint8_t subpage; + int (*emulate)(struct se_cmd *, u8, unsigned char *); +} modesense_handlers[] = { + { .page = 0x01, .subpage = 0x00, .emulate = spc_modesense_rwrecovery }, + { .page = 0x08, .subpage = 0x00, .emulate = spc_modesense_caching }, + { .page = 0x0a, .subpage = 0x00, .emulate = spc_modesense_control }, + { .page = 0x1c, .subpage = 0x00, .emulate = spc_modesense_informational_exceptions }, +}; + +static void spc_modesense_write_protect(unsigned char *buf, int type) +{ + /* + * I believe that the WP bit (bit 7) in the mode header is the same for + * all device types.. + */ + switch (type) { + case TYPE_DISK: + case TYPE_TAPE: + default: + buf[0] |= 0x80; /* WP bit */ + break; + } +} + +static void spc_modesense_dpofua(unsigned char *buf, int type) +{ + switch (type) { + case TYPE_DISK: + buf[0] |= 0x10; /* DPOFUA bit */ + break; + default: + break; + } +} + +static int spc_modesense_blockdesc(unsigned char *buf, u64 blocks, u32 block_size) +{ + *buf++ = 8; + put_unaligned_be32(min(blocks, 0xffffffffull), buf); + buf += 4; + put_unaligned_be32(block_size, buf); + return 9; +} + +static int spc_modesense_long_blockdesc(unsigned char *buf, u64 blocks, u32 block_size) +{ + if (blocks <= 0xffffffff) + return spc_modesense_blockdesc(buf + 3, blocks, block_size) + 3; + + *buf++ = 1; /* LONGLBA */ + buf += 2; + *buf++ = 16; + put_unaligned_be64(blocks, buf); + buf += 12; + put_unaligned_be32(block_size, buf); + + return 17; +} + +static sense_reason_t spc_emulate_modesense(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + char *cdb = cmd->t_task_cdb; + unsigned char buf[SE_MODE_PAGE_BUF], *rbuf; + int type = dev->transport->get_device_type(dev); + int ten = (cmd->t_task_cdb[0] == MODE_SENSE_10); + bool dbd = !!(cdb[1] & 0x08); + bool llba = ten ? !!(cdb[1] & 0x10) : false; + u8 pc = cdb[2] >> 6; + u8 page = cdb[2] & 0x3f; + u8 subpage = cdb[3]; + int length = 0; + int ret; + int i; + + memset(buf, 0, SE_MODE_PAGE_BUF); + + /* + * Skip over MODE DATA LENGTH + MEDIUM TYPE fields to byte 3 for + * MODE_SENSE_10 and byte 2 for MODE_SENSE (6). + */ + length = ten ? 3 : 2; + + /* DEVICE-SPECIFIC PARAMETER */ + if ((cmd->se_lun->lun_access & TRANSPORT_LUNFLAGS_READ_ONLY) || + (cmd->se_deve && + (cmd->se_deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY))) + spc_modesense_write_protect(&buf[length], type); + + if ((spc_check_dev_wce(dev)) && + (dev->dev_attrib.emulate_fua_write > 0)) + spc_modesense_dpofua(&buf[length], type); + + ++length; + + /* BLOCK DESCRIPTOR */ + + /* + * For now we only include a block descriptor for disk (SBC) + * devices; other command sets use a slightly different format. + */ + if (!dbd && type == TYPE_DISK) { + u64 blocks = dev->transport->get_blocks(dev); + u32 block_size = dev->dev_attrib.block_size; + + if (ten) { + if (llba) { + length += spc_modesense_long_blockdesc(&buf[length], + blocks, block_size); + } else { + length += 3; + length += spc_modesense_blockdesc(&buf[length], + blocks, block_size); + } + } else { + length += spc_modesense_blockdesc(&buf[length], blocks, + block_size); + } + } else { + if (ten) + length += 4; + else + length += 1; + } + + if (page == 0x3f) { + if (subpage != 0x00 && subpage != 0xff) { + pr_warn("MODE_SENSE: Invalid subpage code: 0x%02x\n", subpage); + return TCM_INVALID_CDB_FIELD; + } + + for (i = 0; i < ARRAY_SIZE(modesense_handlers); ++i) { + /* + * Tricky way to say all subpage 00h for + * subpage==0, all subpages for subpage==0xff + * (and we just checked above that those are + * the only two possibilities). + */ + if ((modesense_handlers[i].subpage & ~subpage) == 0) { + ret = modesense_handlers[i].emulate(cmd, pc, &buf[length]); + if (!ten && length + ret >= 255) + break; + length += ret; + } + } + + goto set_length; + } + + for (i = 0; i < ARRAY_SIZE(modesense_handlers); ++i) + if (modesense_handlers[i].page == page && + modesense_handlers[i].subpage == subpage) { + length += modesense_handlers[i].emulate(cmd, pc, &buf[length]); + goto set_length; + } + + /* + * We don't intend to implement: + * - obsolete page 03h "format parameters" (checked by Solaris) + */ + if (page != 0x03) + pr_err("MODE SENSE: unimplemented page/subpage: 0x%02x/0x%02x\n", + page, subpage); + + return TCM_UNKNOWN_MODE_PAGE; + +set_length: + if (ten) + put_unaligned_be16(length - 2, buf); + else + buf[0] = length - 1; + + rbuf = transport_kmap_data_sg(cmd); + if (rbuf) { + memcpy(rbuf, buf, min_t(u32, SE_MODE_PAGE_BUF, cmd->data_length)); + transport_kunmap_data_sg(cmd); + } + + target_complete_cmd_with_length(cmd, GOOD, length); + return 0; +} + +static sense_reason_t spc_emulate_modeselect(struct se_cmd *cmd) +{ + char *cdb = cmd->t_task_cdb; + bool ten = cdb[0] == MODE_SELECT_10; + int off = ten ? 8 : 4; + bool pf = !!(cdb[1] & 0x10); + u8 page, subpage; + unsigned char *buf; + unsigned char tbuf[SE_MODE_PAGE_BUF]; + int length; + int ret = 0; + int i; + + if (!cmd->data_length) { + target_complete_cmd(cmd, GOOD); + return 0; + } + + if (cmd->data_length < off + 2) + return TCM_PARAMETER_LIST_LENGTH_ERROR; + + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + if (!pf) { + ret = TCM_INVALID_CDB_FIELD; + goto out; + } + + page = buf[off] & 0x3f; + subpage = buf[off] & 0x40 ? buf[off + 1] : 0; + + for (i = 0; i < ARRAY_SIZE(modesense_handlers); ++i) + if (modesense_handlers[i].page == page && + modesense_handlers[i].subpage == subpage) { + memset(tbuf, 0, SE_MODE_PAGE_BUF); + length = modesense_handlers[i].emulate(cmd, 0, tbuf); + goto check_contents; + } + + ret = TCM_UNKNOWN_MODE_PAGE; + goto out; + +check_contents: + if (cmd->data_length < off + length) { + ret = TCM_PARAMETER_LIST_LENGTH_ERROR; + goto out; + } + + if (memcmp(buf + off, tbuf, length)) + ret = TCM_INVALID_PARAMETER_LIST; + +out: + transport_kunmap_data_sg(cmd); + + if (!ret) + target_complete_cmd(cmd, GOOD); + return ret; +} + +static sense_reason_t spc_emulate_request_sense(struct se_cmd *cmd) +{ + unsigned char *cdb = cmd->t_task_cdb; + unsigned char *rbuf; + u8 ua_asc = 0, ua_ascq = 0; + unsigned char buf[SE_SENSE_BUF]; + + memset(buf, 0, SE_SENSE_BUF); + + if (cdb[1] & 0x01) { + pr_err("REQUEST_SENSE description emulation not" + " supported\n"); + return TCM_INVALID_CDB_FIELD; + } + + rbuf = transport_kmap_data_sg(cmd); + if (!rbuf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + if (!core_scsi3_ua_clear_for_request_sense(cmd, &ua_asc, &ua_ascq)) { + /* + * CURRENT ERROR, UNIT ATTENTION + */ + buf[0] = 0x70; + buf[SPC_SENSE_KEY_OFFSET] = UNIT_ATTENTION; + + /* + * The Additional Sense Code (ASC) from the UNIT ATTENTION + */ + buf[SPC_ASC_KEY_OFFSET] = ua_asc; + buf[SPC_ASCQ_KEY_OFFSET] = ua_ascq; + buf[7] = 0x0A; + } else { + /* + * CURRENT ERROR, NO SENSE + */ + buf[0] = 0x70; + buf[SPC_SENSE_KEY_OFFSET] = NO_SENSE; + + /* + * NO ADDITIONAL SENSE INFORMATION + */ + buf[SPC_ASC_KEY_OFFSET] = 0x00; + buf[7] = 0x0A; + } + + memcpy(rbuf, buf, min_t(u32, sizeof(buf), cmd->data_length)); + transport_kunmap_data_sg(cmd); + + target_complete_cmd(cmd, GOOD); + return 0; +} + +sense_reason_t spc_emulate_report_luns(struct se_cmd *cmd) +{ + struct se_dev_entry *deve; + struct se_session *sess = cmd->se_sess; + unsigned char *buf; + u32 lun_count = 0, offset = 8, i; + + if (cmd->data_length < 16) { + pr_warn("REPORT LUNS allocation length %u too small\n", + cmd->data_length); + return TCM_INVALID_CDB_FIELD; + } + + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + /* + * If no struct se_session pointer is present, this struct se_cmd is + * coming via a target_core_mod PASSTHROUGH op, and not through + * a $FABRIC_MOD. In that case, report LUN=0 only. + */ + if (!sess) { + int_to_scsilun(0, (struct scsi_lun *)&buf[offset]); + lun_count = 1; + goto done; + } + + spin_lock_irq(&sess->se_node_acl->device_list_lock); + for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { + deve = sess->se_node_acl->device_list[i]; + if (!(deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS)) + continue; + /* + * We determine the correct LUN LIST LENGTH even once we + * have reached the initial allocation length. + * See SPC2-R20 7.19. + */ + lun_count++; + if ((offset + 8) > cmd->data_length) + continue; + + int_to_scsilun(deve->mapped_lun, (struct scsi_lun *)&buf[offset]); + offset += 8; + } + spin_unlock_irq(&sess->se_node_acl->device_list_lock); + + /* + * See SPC3 r07, page 159. + */ +done: + lun_count *= 8; + buf[0] = ((lun_count >> 24) & 0xff); + buf[1] = ((lun_count >> 16) & 0xff); + buf[2] = ((lun_count >> 8) & 0xff); + buf[3] = (lun_count & 0xff); + transport_kunmap_data_sg(cmd); + + target_complete_cmd_with_length(cmd, GOOD, 8 + lun_count * 8); + return 0; +} +EXPORT_SYMBOL(spc_emulate_report_luns); + +static sense_reason_t +spc_emulate_testunitready(struct se_cmd *cmd) +{ + target_complete_cmd(cmd, GOOD); + return 0; +} + +sense_reason_t +spc_parse_cdb(struct se_cmd *cmd, unsigned int *size) +{ + struct se_device *dev = cmd->se_dev; + unsigned char *cdb = cmd->t_task_cdb; + + switch (cdb[0]) { + case MODE_SELECT: + *size = cdb[4]; + cmd->execute_cmd = spc_emulate_modeselect; + break; + case MODE_SELECT_10: + *size = (cdb[7] << 8) + cdb[8]; + cmd->execute_cmd = spc_emulate_modeselect; + break; + case MODE_SENSE: + *size = cdb[4]; + cmd->execute_cmd = spc_emulate_modesense; + break; + case MODE_SENSE_10: + *size = (cdb[7] << 8) + cdb[8]; + cmd->execute_cmd = spc_emulate_modesense; + break; + case LOG_SELECT: + case LOG_SENSE: + *size = (cdb[7] << 8) + cdb[8]; + break; + case PERSISTENT_RESERVE_IN: + *size = (cdb[7] << 8) + cdb[8]; + cmd->execute_cmd = target_scsi3_emulate_pr_in; + break; + case PERSISTENT_RESERVE_OUT: + *size = (cdb[7] << 8) + cdb[8]; + cmd->execute_cmd = target_scsi3_emulate_pr_out; + break; + case RELEASE: + case RELEASE_10: + if (cdb[0] == RELEASE_10) + *size = (cdb[7] << 8) | cdb[8]; + else + *size = cmd->data_length; + + cmd->execute_cmd = target_scsi2_reservation_release; + break; + case RESERVE: + case RESERVE_10: + /* + * The SPC-2 RESERVE does not contain a size in the SCSI CDB. + * Assume the passthrough or $FABRIC_MOD will tell us about it. + */ + if (cdb[0] == RESERVE_10) + *size = (cdb[7] << 8) | cdb[8]; + else + *size = cmd->data_length; + + cmd->execute_cmd = target_scsi2_reservation_reserve; + break; + case REQUEST_SENSE: + *size = cdb[4]; + cmd->execute_cmd = spc_emulate_request_sense; + break; + case INQUIRY: + *size = (cdb[3] << 8) + cdb[4]; + + /* + * Do implicit HEAD_OF_QUEUE processing for INQUIRY. + * See spc4r17 section 5.3 + */ + cmd->sam_task_attr = MSG_HEAD_TAG; + cmd->execute_cmd = spc_emulate_inquiry; + break; + case SECURITY_PROTOCOL_IN: + case SECURITY_PROTOCOL_OUT: + *size = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9]; + break; + case EXTENDED_COPY: + *size = get_unaligned_be32(&cdb[10]); + cmd->execute_cmd = target_do_xcopy; + break; + case RECEIVE_COPY_RESULTS: + *size = get_unaligned_be32(&cdb[10]); + cmd->execute_cmd = target_do_receive_copy_results; + break; + case READ_ATTRIBUTE: + case WRITE_ATTRIBUTE: + *size = (cdb[10] << 24) | (cdb[11] << 16) | + (cdb[12] << 8) | cdb[13]; + break; + case RECEIVE_DIAGNOSTIC: + case SEND_DIAGNOSTIC: + *size = (cdb[3] << 8) | cdb[4]; + break; + case WRITE_BUFFER: + *size = (cdb[6] << 16) + (cdb[7] << 8) + cdb[8]; + break; + case REPORT_LUNS: + cmd->execute_cmd = spc_emulate_report_luns; + *size = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9]; + /* + * Do implicit HEAD_OF_QUEUE processing for REPORT_LUNS + * See spc4r17 section 5.3 + */ + cmd->sam_task_attr = MSG_HEAD_TAG; + break; + case TEST_UNIT_READY: + cmd->execute_cmd = spc_emulate_testunitready; + *size = 0; + break; + case MAINTENANCE_IN: + if (dev->transport->get_device_type(dev) != TYPE_ROM) { + /* + * MAINTENANCE_IN from SCC-2 + * Check for emulated MI_REPORT_TARGET_PGS + */ + if ((cdb[1] & 0x1f) == MI_REPORT_TARGET_PGS) { + cmd->execute_cmd = + target_emulate_report_target_port_groups; + } + *size = get_unaligned_be32(&cdb[6]); + } else { + /* + * GPCMD_SEND_KEY from multi media commands + */ + *size = get_unaligned_be16(&cdb[8]); + } + break; + case MAINTENANCE_OUT: + if (dev->transport->get_device_type(dev) != TYPE_ROM) { + /* + * MAINTENANCE_OUT from SCC-2 + * Check for emulated MO_SET_TARGET_PGS. + */ + if (cdb[1] == MO_SET_TARGET_PGS) { + cmd->execute_cmd = + target_emulate_set_target_port_groups; + } + *size = get_unaligned_be32(&cdb[6]); + } else { + /* + * GPCMD_SEND_KEY from multi media commands + */ + *size = get_unaligned_be16(&cdb[8]); + } + break; + default: + pr_warn("TARGET_CORE[%s]: Unsupported SCSI Opcode" + " 0x%02x, sending CHECK_CONDITION.\n", + cmd->se_tfo->get_fabric_name(), cdb[0]); + return TCM_UNSUPPORTED_SCSI_OPCODE; + } + + return 0; +} +EXPORT_SYMBOL(spc_parse_cdb); diff --git a/drivers/target/target_core_stat.c b/drivers/target/target_core_stat.c new file mode 100644 index 00000000000..03538994d2f --- /dev/null +++ b/drivers/target/target_core_stat.c @@ -0,0 +1,1635 @@ +/******************************************************************************* + * Filename: target_core_stat.c + * + * Modern ConfigFS group context specific statistics based on original + * target_core_mib.c code + * + * (c) Copyright 2006-2013 Datera, Inc. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ******************************************************************************/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/utsname.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/configfs.h> +#include <scsi/scsi.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> + +#include <target/target_core_base.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> + +#include "target_core_internal.h" + +#ifndef INITIAL_JIFFIES +#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ)) +#endif + +#define NONE "None" +#define ISPRINT(a) ((a >= ' ') && (a <= '~')) + +#define SCSI_LU_INDEX 1 +#define LU_COUNT 1 + +/* + * SCSI Device Table + */ + +CONFIGFS_EATTR_STRUCT(target_stat_scsi_dev, se_dev_stat_grps); +#define DEV_STAT_SCSI_DEV_ATTR(_name, _mode) \ +static struct target_stat_scsi_dev_attribute \ + target_stat_scsi_dev_##_name = \ + __CONFIGFS_EATTR(_name, _mode, \ + target_stat_scsi_dev_show_attr_##_name, \ + target_stat_scsi_dev_store_attr_##_name); + +#define DEV_STAT_SCSI_DEV_ATTR_RO(_name) \ +static struct target_stat_scsi_dev_attribute \ + target_stat_scsi_dev_##_name = \ + __CONFIGFS_EATTR_RO(_name, \ + target_stat_scsi_dev_show_attr_##_name); + +static ssize_t target_stat_scsi_dev_show_attr_inst( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + struct se_hba *hba = dev->se_hba; + + return snprintf(page, PAGE_SIZE, "%u\n", hba->hba_index); +} +DEV_STAT_SCSI_DEV_ATTR_RO(inst); + +static ssize_t target_stat_scsi_dev_show_attr_indx( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + return snprintf(page, PAGE_SIZE, "%u\n", dev->dev_index); +} +DEV_STAT_SCSI_DEV_ATTR_RO(indx); + +static ssize_t target_stat_scsi_dev_show_attr_role( + struct se_dev_stat_grps *sgrps, char *page) +{ + return snprintf(page, PAGE_SIZE, "Target\n"); +} +DEV_STAT_SCSI_DEV_ATTR_RO(role); + +static ssize_t target_stat_scsi_dev_show_attr_ports( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + return snprintf(page, PAGE_SIZE, "%u\n", dev->dev_port_count); +} +DEV_STAT_SCSI_DEV_ATTR_RO(ports); + +CONFIGFS_EATTR_OPS(target_stat_scsi_dev, se_dev_stat_grps, scsi_dev_group); + +static struct configfs_attribute *target_stat_scsi_dev_attrs[] = { + &target_stat_scsi_dev_inst.attr, + &target_stat_scsi_dev_indx.attr, + &target_stat_scsi_dev_role.attr, + &target_stat_scsi_dev_ports.attr, + NULL, +}; + +static struct configfs_item_operations target_stat_scsi_dev_attrib_ops = { + .show_attribute = target_stat_scsi_dev_attr_show, + .store_attribute = target_stat_scsi_dev_attr_store, +}; + +static struct config_item_type target_stat_scsi_dev_cit = { + .ct_item_ops = &target_stat_scsi_dev_attrib_ops, + .ct_attrs = target_stat_scsi_dev_attrs, + .ct_owner = THIS_MODULE, +}; + +/* + * SCSI Target Device Table + */ + +CONFIGFS_EATTR_STRUCT(target_stat_scsi_tgt_dev, se_dev_stat_grps); +#define DEV_STAT_SCSI_TGT_DEV_ATTR(_name, _mode) \ +static struct target_stat_scsi_tgt_dev_attribute \ + target_stat_scsi_tgt_dev_##_name = \ + __CONFIGFS_EATTR(_name, _mode, \ + target_stat_scsi_tgt_dev_show_attr_##_name, \ + target_stat_scsi_tgt_dev_store_attr_##_name); + +#define DEV_STAT_SCSI_TGT_DEV_ATTR_RO(_name) \ +static struct target_stat_scsi_tgt_dev_attribute \ + target_stat_scsi_tgt_dev_##_name = \ + __CONFIGFS_EATTR_RO(_name, \ + target_stat_scsi_tgt_dev_show_attr_##_name); + +static ssize_t target_stat_scsi_tgt_dev_show_attr_inst( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + struct se_hba *hba = dev->se_hba; + + return snprintf(page, PAGE_SIZE, "%u\n", hba->hba_index); +} +DEV_STAT_SCSI_TGT_DEV_ATTR_RO(inst); + +static ssize_t target_stat_scsi_tgt_dev_show_attr_indx( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + return snprintf(page, PAGE_SIZE, "%u\n", dev->dev_index); +} +DEV_STAT_SCSI_TGT_DEV_ATTR_RO(indx); + +static ssize_t target_stat_scsi_tgt_dev_show_attr_num_lus( + struct se_dev_stat_grps *sgrps, char *page) +{ + return snprintf(page, PAGE_SIZE, "%u\n", LU_COUNT); +} +DEV_STAT_SCSI_TGT_DEV_ATTR_RO(num_lus); + +static ssize_t target_stat_scsi_tgt_dev_show_attr_status( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + if (dev->export_count) + return snprintf(page, PAGE_SIZE, "activated"); + else + return snprintf(page, PAGE_SIZE, "deactivated"); +} +DEV_STAT_SCSI_TGT_DEV_ATTR_RO(status); + +static ssize_t target_stat_scsi_tgt_dev_show_attr_non_access_lus( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + int non_accessible_lus; + + if (dev->export_count) + non_accessible_lus = 0; + else + non_accessible_lus = 1; + + return snprintf(page, PAGE_SIZE, "%u\n", non_accessible_lus); +} +DEV_STAT_SCSI_TGT_DEV_ATTR_RO(non_access_lus); + +static ssize_t target_stat_scsi_tgt_dev_show_attr_resets( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + return snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&dev->num_resets)); +} +DEV_STAT_SCSI_TGT_DEV_ATTR_RO(resets); + + +CONFIGFS_EATTR_OPS(target_stat_scsi_tgt_dev, se_dev_stat_grps, scsi_tgt_dev_group); + +static struct configfs_attribute *target_stat_scsi_tgt_dev_attrs[] = { + &target_stat_scsi_tgt_dev_inst.attr, + &target_stat_scsi_tgt_dev_indx.attr, + &target_stat_scsi_tgt_dev_num_lus.attr, + &target_stat_scsi_tgt_dev_status.attr, + &target_stat_scsi_tgt_dev_non_access_lus.attr, + &target_stat_scsi_tgt_dev_resets.attr, + NULL, +}; + +static struct configfs_item_operations target_stat_scsi_tgt_dev_attrib_ops = { + .show_attribute = target_stat_scsi_tgt_dev_attr_show, + .store_attribute = target_stat_scsi_tgt_dev_attr_store, +}; + +static struct config_item_type target_stat_scsi_tgt_dev_cit = { + .ct_item_ops = &target_stat_scsi_tgt_dev_attrib_ops, + .ct_attrs = target_stat_scsi_tgt_dev_attrs, + .ct_owner = THIS_MODULE, +}; + +/* + * SCSI Logical Unit Table + */ + +CONFIGFS_EATTR_STRUCT(target_stat_scsi_lu, se_dev_stat_grps); +#define DEV_STAT_SCSI_LU_ATTR(_name, _mode) \ +static struct target_stat_scsi_lu_attribute target_stat_scsi_lu_##_name = \ + __CONFIGFS_EATTR(_name, _mode, \ + target_stat_scsi_lu_show_attr_##_name, \ + target_stat_scsi_lu_store_attr_##_name); + +#define DEV_STAT_SCSI_LU_ATTR_RO(_name) \ +static struct target_stat_scsi_lu_attribute target_stat_scsi_lu_##_name = \ + __CONFIGFS_EATTR_RO(_name, \ + target_stat_scsi_lu_show_attr_##_name); + +static ssize_t target_stat_scsi_lu_show_attr_inst( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + struct se_hba *hba = dev->se_hba; + + return snprintf(page, PAGE_SIZE, "%u\n", hba->hba_index); +} +DEV_STAT_SCSI_LU_ATTR_RO(inst); + +static ssize_t target_stat_scsi_lu_show_attr_dev( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + return snprintf(page, PAGE_SIZE, "%u\n", dev->dev_index); +} +DEV_STAT_SCSI_LU_ATTR_RO(dev); + +static ssize_t target_stat_scsi_lu_show_attr_indx( + struct se_dev_stat_grps *sgrps, char *page) +{ + return snprintf(page, PAGE_SIZE, "%u\n", SCSI_LU_INDEX); +} +DEV_STAT_SCSI_LU_ATTR_RO(indx); + +static ssize_t target_stat_scsi_lu_show_attr_lun( + struct se_dev_stat_grps *sgrps, char *page) +{ + /* FIXME: scsiLuDefaultLun */ + return snprintf(page, PAGE_SIZE, "%llu\n", (unsigned long long)0); +} +DEV_STAT_SCSI_LU_ATTR_RO(lun); + +static ssize_t target_stat_scsi_lu_show_attr_lu_name( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + /* scsiLuWwnName */ + return snprintf(page, PAGE_SIZE, "%s\n", + (strlen(dev->t10_wwn.unit_serial)) ? + dev->t10_wwn.unit_serial : "None"); +} +DEV_STAT_SCSI_LU_ATTR_RO(lu_name); + +static ssize_t target_stat_scsi_lu_show_attr_vend( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + int i; + char str[sizeof(dev->t10_wwn.vendor)+1]; + + /* scsiLuVendorId */ + for (i = 0; i < sizeof(dev->t10_wwn.vendor); i++) + str[i] = ISPRINT(dev->t10_wwn.vendor[i]) ? + dev->t10_wwn.vendor[i] : ' '; + str[i] = '\0'; + return snprintf(page, PAGE_SIZE, "%s\n", str); +} +DEV_STAT_SCSI_LU_ATTR_RO(vend); + +static ssize_t target_stat_scsi_lu_show_attr_prod( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + int i; + char str[sizeof(dev->t10_wwn.model)+1]; + + /* scsiLuProductId */ + for (i = 0; i < sizeof(dev->t10_wwn.vendor); i++) + str[i] = ISPRINT(dev->t10_wwn.model[i]) ? + dev->t10_wwn.model[i] : ' '; + str[i] = '\0'; + return snprintf(page, PAGE_SIZE, "%s\n", str); +} +DEV_STAT_SCSI_LU_ATTR_RO(prod); + +static ssize_t target_stat_scsi_lu_show_attr_rev( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + int i; + char str[sizeof(dev->t10_wwn.revision)+1]; + + /* scsiLuRevisionId */ + for (i = 0; i < sizeof(dev->t10_wwn.revision); i++) + str[i] = ISPRINT(dev->t10_wwn.revision[i]) ? + dev->t10_wwn.revision[i] : ' '; + str[i] = '\0'; + return snprintf(page, PAGE_SIZE, "%s\n", str); +} +DEV_STAT_SCSI_LU_ATTR_RO(rev); + +static ssize_t target_stat_scsi_lu_show_attr_dev_type( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + /* scsiLuPeripheralType */ + return snprintf(page, PAGE_SIZE, "%u\n", + dev->transport->get_device_type(dev)); +} +DEV_STAT_SCSI_LU_ATTR_RO(dev_type); + +static ssize_t target_stat_scsi_lu_show_attr_status( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + /* scsiLuStatus */ + return snprintf(page, PAGE_SIZE, "%s\n", + (dev->export_count) ? "available" : "notavailable"); +} +DEV_STAT_SCSI_LU_ATTR_RO(status); + +static ssize_t target_stat_scsi_lu_show_attr_state_bit( + struct se_dev_stat_grps *sgrps, char *page) +{ + /* scsiLuState */ + return snprintf(page, PAGE_SIZE, "exposed\n"); +} +DEV_STAT_SCSI_LU_ATTR_RO(state_bit); + +static ssize_t target_stat_scsi_lu_show_attr_num_cmds( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + /* scsiLuNumCommands */ + return snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&dev->num_cmds)); +} +DEV_STAT_SCSI_LU_ATTR_RO(num_cmds); + +static ssize_t target_stat_scsi_lu_show_attr_read_mbytes( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + /* scsiLuReadMegaBytes */ + return snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&dev->read_bytes) >> 20); +} +DEV_STAT_SCSI_LU_ATTR_RO(read_mbytes); + +static ssize_t target_stat_scsi_lu_show_attr_write_mbytes( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + /* scsiLuWrittenMegaBytes */ + return snprintf(page, PAGE_SIZE, "%lu\n", + atomic_long_read(&dev->write_bytes) >> 20); +} +DEV_STAT_SCSI_LU_ATTR_RO(write_mbytes); + +static ssize_t target_stat_scsi_lu_show_attr_resets( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + /* scsiLuInResets */ + return snprintf(page, PAGE_SIZE, "%lu\n", atomic_long_read(&dev->num_resets)); +} +DEV_STAT_SCSI_LU_ATTR_RO(resets); + +static ssize_t target_stat_scsi_lu_show_attr_full_stat( + struct se_dev_stat_grps *sgrps, char *page) +{ + /* FIXME: scsiLuOutTaskSetFullStatus */ + return snprintf(page, PAGE_SIZE, "%u\n", 0); +} +DEV_STAT_SCSI_LU_ATTR_RO(full_stat); + +static ssize_t target_stat_scsi_lu_show_attr_hs_num_cmds( + struct se_dev_stat_grps *sgrps, char *page) +{ + /* FIXME: scsiLuHSInCommands */ + return snprintf(page, PAGE_SIZE, "%u\n", 0); +} +DEV_STAT_SCSI_LU_ATTR_RO(hs_num_cmds); + +static ssize_t target_stat_scsi_lu_show_attr_creation_time( + struct se_dev_stat_grps *sgrps, char *page) +{ + struct se_device *dev = + container_of(sgrps, struct se_device, dev_stat_grps); + + /* scsiLuCreationTime */ + return snprintf(page, PAGE_SIZE, "%u\n", (u32)(((u32)dev->creation_time - + INITIAL_JIFFIES) * 100 / HZ)); +} +DEV_STAT_SCSI_LU_ATTR_RO(creation_time); + +CONFIGFS_EATTR_OPS(target_stat_scsi_lu, se_dev_stat_grps, scsi_lu_group); + +static struct configfs_attribute *target_stat_scsi_lu_attrs[] = { + &target_stat_scsi_lu_inst.attr, + &target_stat_scsi_lu_dev.attr, + &target_stat_scsi_lu_indx.attr, + &target_stat_scsi_lu_lun.attr, + &target_stat_scsi_lu_lu_name.attr, + &target_stat_scsi_lu_vend.attr, + &target_stat_scsi_lu_prod.attr, + &target_stat_scsi_lu_rev.attr, + &target_stat_scsi_lu_dev_type.attr, + &target_stat_scsi_lu_status.attr, + &target_stat_scsi_lu_state_bit.attr, + &target_stat_scsi_lu_num_cmds.attr, + &target_stat_scsi_lu_read_mbytes.attr, + &target_stat_scsi_lu_write_mbytes.attr, + &target_stat_scsi_lu_resets.attr, + &target_stat_scsi_lu_full_stat.attr, + &target_stat_scsi_lu_hs_num_cmds.attr, + &target_stat_scsi_lu_creation_time.attr, + NULL, +}; + +static struct configfs_item_operations target_stat_scsi_lu_attrib_ops = { + .show_attribute = target_stat_scsi_lu_attr_show, + .store_attribute = target_stat_scsi_lu_attr_store, +}; + +static struct config_item_type target_stat_scsi_lu_cit = { + .ct_item_ops = &target_stat_scsi_lu_attrib_ops, + .ct_attrs = target_stat_scsi_lu_attrs, + .ct_owner = THIS_MODULE, +}; + +/* + * Called from target_core_configfs.c:target_core_make_subdev() to setup + * the target statistics groups + configfs CITs located in target_core_stat.c + */ +void target_stat_setup_dev_default_groups(struct se_device *dev) +{ + struct config_group *dev_stat_grp = &dev->dev_stat_grps.stat_group; + + config_group_init_type_name(&dev->dev_stat_grps.scsi_dev_group, + "scsi_dev", &target_stat_scsi_dev_cit); + config_group_init_type_name(&dev->dev_stat_grps.scsi_tgt_dev_group, + "scsi_tgt_dev", &target_stat_scsi_tgt_dev_cit); + config_group_init_type_name(&dev->dev_stat_grps.scsi_lu_group, + "scsi_lu", &target_stat_scsi_lu_cit); + + dev_stat_grp->default_groups[0] = &dev->dev_stat_grps.scsi_dev_group; + dev_stat_grp->default_groups[1] = &dev->dev_stat_grps.scsi_tgt_dev_group; + dev_stat_grp->default_groups[2] = &dev->dev_stat_grps.scsi_lu_group; + dev_stat_grp->default_groups[3] = NULL; +} + +/* + * SCSI Port Table + */ + +CONFIGFS_EATTR_STRUCT(target_stat_scsi_port, se_port_stat_grps); +#define DEV_STAT_SCSI_PORT_ATTR(_name, _mode) \ +static struct target_stat_scsi_port_attribute \ + target_stat_scsi_port_##_name = \ + __CONFIGFS_EATTR(_name, _mode, \ + target_stat_scsi_port_show_attr_##_name, \ + target_stat_scsi_port_store_attr_##_name); + +#define DEV_STAT_SCSI_PORT_ATTR_RO(_name) \ +static struct target_stat_scsi_port_attribute \ + target_stat_scsi_port_##_name = \ + __CONFIGFS_EATTR_RO(_name, \ + target_stat_scsi_port_show_attr_##_name); + +static ssize_t target_stat_scsi_port_show_attr_inst( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + struct se_device *dev = lun->lun_se_dev; + struct se_hba *hba; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + hba = dev->se_hba; + ret = snprintf(page, PAGE_SIZE, "%u\n", hba->hba_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_PORT_ATTR_RO(inst); + +static ssize_t target_stat_scsi_port_show_attr_dev( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + struct se_device *dev = lun->lun_se_dev; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + ret = snprintf(page, PAGE_SIZE, "%u\n", dev->dev_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_PORT_ATTR_RO(dev); + +static ssize_t target_stat_scsi_port_show_attr_indx( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + ret = snprintf(page, PAGE_SIZE, "%u\n", sep->sep_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_PORT_ATTR_RO(indx); + +static ssize_t target_stat_scsi_port_show_attr_role( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_device *dev = lun->lun_se_dev; + struct se_port *sep; + ssize_t ret; + + if (!dev) + return -ENODEV; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + ret = snprintf(page, PAGE_SIZE, "%s%u\n", "Device", dev->dev_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_PORT_ATTR_RO(role); + +static ssize_t target_stat_scsi_port_show_attr_busy_count( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + /* FIXME: scsiPortBusyStatuses */ + ret = snprintf(page, PAGE_SIZE, "%u\n", 0); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_PORT_ATTR_RO(busy_count); + +CONFIGFS_EATTR_OPS(target_stat_scsi_port, se_port_stat_grps, scsi_port_group); + +static struct configfs_attribute *target_stat_scsi_port_attrs[] = { + &target_stat_scsi_port_inst.attr, + &target_stat_scsi_port_dev.attr, + &target_stat_scsi_port_indx.attr, + &target_stat_scsi_port_role.attr, + &target_stat_scsi_port_busy_count.attr, + NULL, +}; + +static struct configfs_item_operations target_stat_scsi_port_attrib_ops = { + .show_attribute = target_stat_scsi_port_attr_show, + .store_attribute = target_stat_scsi_port_attr_store, +}; + +static struct config_item_type target_stat_scsi_port_cit = { + .ct_item_ops = &target_stat_scsi_port_attrib_ops, + .ct_attrs = target_stat_scsi_port_attrs, + .ct_owner = THIS_MODULE, +}; + +/* + * SCSI Target Port Table + */ +CONFIGFS_EATTR_STRUCT(target_stat_scsi_tgt_port, se_port_stat_grps); +#define DEV_STAT_SCSI_TGT_PORT_ATTR(_name, _mode) \ +static struct target_stat_scsi_tgt_port_attribute \ + target_stat_scsi_tgt_port_##_name = \ + __CONFIGFS_EATTR(_name, _mode, \ + target_stat_scsi_tgt_port_show_attr_##_name, \ + target_stat_scsi_tgt_port_store_attr_##_name); + +#define DEV_STAT_SCSI_TGT_PORT_ATTR_RO(_name) \ +static struct target_stat_scsi_tgt_port_attribute \ + target_stat_scsi_tgt_port_##_name = \ + __CONFIGFS_EATTR_RO(_name, \ + target_stat_scsi_tgt_port_show_attr_##_name); + +static ssize_t target_stat_scsi_tgt_port_show_attr_inst( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_device *dev = lun->lun_se_dev; + struct se_port *sep; + struct se_hba *hba; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + hba = dev->se_hba; + ret = snprintf(page, PAGE_SIZE, "%u\n", hba->hba_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(inst); + +static ssize_t target_stat_scsi_tgt_port_show_attr_dev( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_device *dev = lun->lun_se_dev; + struct se_port *sep; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + ret = snprintf(page, PAGE_SIZE, "%u\n", dev->dev_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(dev); + +static ssize_t target_stat_scsi_tgt_port_show_attr_indx( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + ret = snprintf(page, PAGE_SIZE, "%u\n", sep->sep_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(indx); + +static ssize_t target_stat_scsi_tgt_port_show_attr_name( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + tpg = sep->sep_tpg; + + ret = snprintf(page, PAGE_SIZE, "%sPort#%u\n", + tpg->se_tpg_tfo->get_fabric_name(), sep->sep_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(name); + +static ssize_t target_stat_scsi_tgt_port_show_attr_port_index( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + tpg = sep->sep_tpg; + + ret = snprintf(page, PAGE_SIZE, "%s%s%d\n", + tpg->se_tpg_tfo->tpg_get_wwn(tpg), "+t+", + tpg->se_tpg_tfo->tpg_get_tag(tpg)); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(port_index); + +static ssize_t target_stat_scsi_tgt_port_show_attr_in_cmds( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + + ret = snprintf(page, PAGE_SIZE, "%llu\n", sep->sep_stats.cmd_pdus); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(in_cmds); + +static ssize_t target_stat_scsi_tgt_port_show_attr_write_mbytes( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + + ret = snprintf(page, PAGE_SIZE, "%u\n", + (u32)(sep->sep_stats.rx_data_octets >> 20)); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(write_mbytes); + +static ssize_t target_stat_scsi_tgt_port_show_attr_read_mbytes( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + + ret = snprintf(page, PAGE_SIZE, "%u\n", + (u32)(sep->sep_stats.tx_data_octets >> 20)); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(read_mbytes); + +static ssize_t target_stat_scsi_tgt_port_show_attr_hs_in_cmds( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + + /* FIXME: scsiTgtPortHsInCommands */ + ret = snprintf(page, PAGE_SIZE, "%u\n", 0); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TGT_PORT_ATTR_RO(hs_in_cmds); + +CONFIGFS_EATTR_OPS(target_stat_scsi_tgt_port, se_port_stat_grps, + scsi_tgt_port_group); + +static struct configfs_attribute *target_stat_scsi_tgt_port_attrs[] = { + &target_stat_scsi_tgt_port_inst.attr, + &target_stat_scsi_tgt_port_dev.attr, + &target_stat_scsi_tgt_port_indx.attr, + &target_stat_scsi_tgt_port_name.attr, + &target_stat_scsi_tgt_port_port_index.attr, + &target_stat_scsi_tgt_port_in_cmds.attr, + &target_stat_scsi_tgt_port_write_mbytes.attr, + &target_stat_scsi_tgt_port_read_mbytes.attr, + &target_stat_scsi_tgt_port_hs_in_cmds.attr, + NULL, +}; + +static struct configfs_item_operations target_stat_scsi_tgt_port_attrib_ops = { + .show_attribute = target_stat_scsi_tgt_port_attr_show, + .store_attribute = target_stat_scsi_tgt_port_attr_store, +}; + +static struct config_item_type target_stat_scsi_tgt_port_cit = { + .ct_item_ops = &target_stat_scsi_tgt_port_attrib_ops, + .ct_attrs = target_stat_scsi_tgt_port_attrs, + .ct_owner = THIS_MODULE, +}; + +/* + * SCSI Transport Table +o */ + +CONFIGFS_EATTR_STRUCT(target_stat_scsi_transport, se_port_stat_grps); +#define DEV_STAT_SCSI_TRANSPORT_ATTR(_name, _mode) \ +static struct target_stat_scsi_transport_attribute \ + target_stat_scsi_transport_##_name = \ + __CONFIGFS_EATTR(_name, _mode, \ + target_stat_scsi_transport_show_attr_##_name, \ + target_stat_scsi_transport_store_attr_##_name); + +#define DEV_STAT_SCSI_TRANSPORT_ATTR_RO(_name) \ +static struct target_stat_scsi_transport_attribute \ + target_stat_scsi_transport_##_name = \ + __CONFIGFS_EATTR_RO(_name, \ + target_stat_scsi_transport_show_attr_##_name); + +static ssize_t target_stat_scsi_transport_show_attr_inst( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_device *dev = lun->lun_se_dev; + struct se_port *sep; + struct se_hba *hba; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + + hba = dev->se_hba; + ret = snprintf(page, PAGE_SIZE, "%u\n", hba->hba_index); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TRANSPORT_ATTR_RO(inst); + +static ssize_t target_stat_scsi_transport_show_attr_device( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + tpg = sep->sep_tpg; + /* scsiTransportType */ + ret = snprintf(page, PAGE_SIZE, "scsiTransport%s\n", + tpg->se_tpg_tfo->get_fabric_name()); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TRANSPORT_ATTR_RO(device); + +static ssize_t target_stat_scsi_transport_show_attr_indx( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_port *sep; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + tpg = sep->sep_tpg; + ret = snprintf(page, PAGE_SIZE, "%u\n", + tpg->se_tpg_tfo->tpg_get_inst_index(tpg)); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TRANSPORT_ATTR_RO(indx); + +static ssize_t target_stat_scsi_transport_show_attr_dev_name( + struct se_port_stat_grps *pgrps, char *page) +{ + struct se_lun *lun = container_of(pgrps, struct se_lun, port_stat_grps); + struct se_device *dev = lun->lun_se_dev; + struct se_port *sep; + struct se_portal_group *tpg; + struct t10_wwn *wwn; + ssize_t ret; + + spin_lock(&lun->lun_sep_lock); + sep = lun->lun_sep; + if (!sep) { + spin_unlock(&lun->lun_sep_lock); + return -ENODEV; + } + tpg = sep->sep_tpg; + wwn = &dev->t10_wwn; + /* scsiTransportDevName */ + ret = snprintf(page, PAGE_SIZE, "%s+%s\n", + tpg->se_tpg_tfo->tpg_get_wwn(tpg), + (strlen(wwn->unit_serial)) ? wwn->unit_serial : + wwn->vendor); + spin_unlock(&lun->lun_sep_lock); + return ret; +} +DEV_STAT_SCSI_TRANSPORT_ATTR_RO(dev_name); + +CONFIGFS_EATTR_OPS(target_stat_scsi_transport, se_port_stat_grps, + scsi_transport_group); + +static struct configfs_attribute *target_stat_scsi_transport_attrs[] = { + &target_stat_scsi_transport_inst.attr, + &target_stat_scsi_transport_device.attr, + &target_stat_scsi_transport_indx.attr, + &target_stat_scsi_transport_dev_name.attr, + NULL, +}; + +static struct configfs_item_operations target_stat_scsi_transport_attrib_ops = { + .show_attribute = target_stat_scsi_transport_attr_show, + .store_attribute = target_stat_scsi_transport_attr_store, +}; + +static struct config_item_type target_stat_scsi_transport_cit = { + .ct_item_ops = &target_stat_scsi_transport_attrib_ops, + .ct_attrs = target_stat_scsi_transport_attrs, + .ct_owner = THIS_MODULE, +}; + +/* + * Called from target_core_fabric_configfs.c:target_fabric_make_lun() to setup + * the target port statistics groups + configfs CITs located in target_core_stat.c + */ +void target_stat_setup_port_default_groups(struct se_lun *lun) +{ + struct config_group *port_stat_grp = &lun->port_stat_grps.stat_group; + + config_group_init_type_name(&lun->port_stat_grps.scsi_port_group, + "scsi_port", &target_stat_scsi_port_cit); + config_group_init_type_name(&lun->port_stat_grps.scsi_tgt_port_group, + "scsi_tgt_port", &target_stat_scsi_tgt_port_cit); + config_group_init_type_name(&lun->port_stat_grps.scsi_transport_group, + "scsi_transport", &target_stat_scsi_transport_cit); + + port_stat_grp->default_groups[0] = &lun->port_stat_grps.scsi_port_group; + port_stat_grp->default_groups[1] = &lun->port_stat_grps.scsi_tgt_port_group; + port_stat_grp->default_groups[2] = &lun->port_stat_grps.scsi_transport_group; + port_stat_grp->default_groups[3] = NULL; +} + +/* + * SCSI Authorized Initiator Table + */ + +CONFIGFS_EATTR_STRUCT(target_stat_scsi_auth_intr, se_ml_stat_grps); +#define DEV_STAT_SCSI_AUTH_INTR_ATTR(_name, _mode) \ +static struct target_stat_scsi_auth_intr_attribute \ + target_stat_scsi_auth_intr_##_name = \ + __CONFIGFS_EATTR(_name, _mode, \ + target_stat_scsi_auth_intr_show_attr_##_name, \ + target_stat_scsi_auth_intr_store_attr_##_name); + +#define DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(_name) \ +static struct target_stat_scsi_auth_intr_attribute \ + target_stat_scsi_auth_intr_##_name = \ + __CONFIGFS_EATTR_RO(_name, \ + target_stat_scsi_auth_intr_show_attr_##_name); + +static ssize_t target_stat_scsi_auth_intr_show_attr_inst( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + tpg = nacl->se_tpg; + /* scsiInstIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", + tpg->se_tpg_tfo->tpg_get_inst_index(tpg)); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(inst); + +static ssize_t target_stat_scsi_auth_intr_show_attr_dev( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + struct se_lun *lun; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + lun = deve->se_lun; + /* scsiDeviceIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", lun->lun_se_dev->dev_index); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(dev); + +static ssize_t target_stat_scsi_auth_intr_show_attr_port( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + tpg = nacl->se_tpg; + /* scsiAuthIntrTgtPortIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", tpg->se_tpg_tfo->tpg_get_tag(tpg)); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(port); + +static ssize_t target_stat_scsi_auth_intr_show_attr_indx( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAuthIntrIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", nacl->acl_index); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(indx); + +static ssize_t target_stat_scsi_auth_intr_show_attr_dev_or_port( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAuthIntrDevOrPort */ + ret = snprintf(page, PAGE_SIZE, "%u\n", 1); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(dev_or_port); + +static ssize_t target_stat_scsi_auth_intr_show_attr_intr_name( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAuthIntrName */ + ret = snprintf(page, PAGE_SIZE, "%s\n", nacl->initiatorname); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(intr_name); + +static ssize_t target_stat_scsi_auth_intr_show_attr_map_indx( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* FIXME: scsiAuthIntrLunMapIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", 0); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(map_indx); + +static ssize_t target_stat_scsi_auth_intr_show_attr_att_count( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAuthIntrAttachedTimes */ + ret = snprintf(page, PAGE_SIZE, "%u\n", deve->attach_count); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(att_count); + +static ssize_t target_stat_scsi_auth_intr_show_attr_num_cmds( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAuthIntrOutCommands */ + ret = snprintf(page, PAGE_SIZE, "%u\n", deve->total_cmds); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(num_cmds); + +static ssize_t target_stat_scsi_auth_intr_show_attr_read_mbytes( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAuthIntrReadMegaBytes */ + ret = snprintf(page, PAGE_SIZE, "%u\n", (u32)(deve->read_bytes >> 20)); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(read_mbytes); + +static ssize_t target_stat_scsi_auth_intr_show_attr_write_mbytes( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAuthIntrWrittenMegaBytes */ + ret = snprintf(page, PAGE_SIZE, "%u\n", (u32)(deve->write_bytes >> 20)); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(write_mbytes); + +static ssize_t target_stat_scsi_auth_intr_show_attr_hs_num_cmds( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* FIXME: scsiAuthIntrHSOutCommands */ + ret = snprintf(page, PAGE_SIZE, "%u\n", 0); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(hs_num_cmds); + +static ssize_t target_stat_scsi_auth_intr_show_attr_creation_time( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAuthIntrLastCreation */ + ret = snprintf(page, PAGE_SIZE, "%u\n", (u32)(((u32)deve->creation_time - + INITIAL_JIFFIES) * 100 / HZ)); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(creation_time); + +static ssize_t target_stat_scsi_auth_intr_show_attr_row_status( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* FIXME: scsiAuthIntrRowStatus */ + ret = snprintf(page, PAGE_SIZE, "Ready\n"); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_AUTH_INTR_ATTR_RO(row_status); + +CONFIGFS_EATTR_OPS(target_stat_scsi_auth_intr, se_ml_stat_grps, + scsi_auth_intr_group); + +static struct configfs_attribute *target_stat_scsi_auth_intr_attrs[] = { + &target_stat_scsi_auth_intr_inst.attr, + &target_stat_scsi_auth_intr_dev.attr, + &target_stat_scsi_auth_intr_port.attr, + &target_stat_scsi_auth_intr_indx.attr, + &target_stat_scsi_auth_intr_dev_or_port.attr, + &target_stat_scsi_auth_intr_intr_name.attr, + &target_stat_scsi_auth_intr_map_indx.attr, + &target_stat_scsi_auth_intr_att_count.attr, + &target_stat_scsi_auth_intr_num_cmds.attr, + &target_stat_scsi_auth_intr_read_mbytes.attr, + &target_stat_scsi_auth_intr_write_mbytes.attr, + &target_stat_scsi_auth_intr_hs_num_cmds.attr, + &target_stat_scsi_auth_intr_creation_time.attr, + &target_stat_scsi_auth_intr_row_status.attr, + NULL, +}; + +static struct configfs_item_operations target_stat_scsi_auth_intr_attrib_ops = { + .show_attribute = target_stat_scsi_auth_intr_attr_show, + .store_attribute = target_stat_scsi_auth_intr_attr_store, +}; + +static struct config_item_type target_stat_scsi_auth_intr_cit = { + .ct_item_ops = &target_stat_scsi_auth_intr_attrib_ops, + .ct_attrs = target_stat_scsi_auth_intr_attrs, + .ct_owner = THIS_MODULE, +}; + +/* + * SCSI Attached Initiator Port Table + */ + +CONFIGFS_EATTR_STRUCT(target_stat_scsi_att_intr_port, se_ml_stat_grps); +#define DEV_STAT_SCSI_ATTR_INTR_PORT_ATTR(_name, _mode) \ +static struct target_stat_scsi_att_intr_port_attribute \ + target_stat_scsi_att_intr_port_##_name = \ + __CONFIGFS_EATTR(_name, _mode, \ + target_stat_scsi_att_intr_port_show_attr_##_name, \ + target_stat_scsi_att_intr_port_store_attr_##_name); + +#define DEV_STAT_SCSI_ATTR_INTR_PORT_ATTR_RO(_name) \ +static struct target_stat_scsi_att_intr_port_attribute \ + target_stat_scsi_att_intr_port_##_name = \ + __CONFIGFS_EATTR_RO(_name, \ + target_stat_scsi_att_intr_port_show_attr_##_name); + +static ssize_t target_stat_scsi_att_intr_port_show_attr_inst( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + tpg = nacl->se_tpg; + /* scsiInstIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", + tpg->se_tpg_tfo->tpg_get_inst_index(tpg)); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_ATTR_INTR_PORT_ATTR_RO(inst); + +static ssize_t target_stat_scsi_att_intr_port_show_attr_dev( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + struct se_lun *lun; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + lun = deve->se_lun; + /* scsiDeviceIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", lun->lun_se_dev->dev_index); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_ATTR_INTR_PORT_ATTR_RO(dev); + +static ssize_t target_stat_scsi_att_intr_port_show_attr_port( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + tpg = nacl->se_tpg; + /* scsiPortIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", tpg->se_tpg_tfo->tpg_get_tag(tpg)); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_ATTR_INTR_PORT_ATTR_RO(port); + +static ssize_t target_stat_scsi_att_intr_port_show_attr_indx( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_session *se_sess; + struct se_portal_group *tpg; + ssize_t ret; + + spin_lock_irq(&nacl->nacl_sess_lock); + se_sess = nacl->nacl_sess; + if (!se_sess) { + spin_unlock_irq(&nacl->nacl_sess_lock); + return -ENODEV; + } + + tpg = nacl->se_tpg; + /* scsiAttIntrPortIndex */ + ret = snprintf(page, PAGE_SIZE, "%u\n", + tpg->se_tpg_tfo->sess_get_index(se_sess)); + spin_unlock_irq(&nacl->nacl_sess_lock); + return ret; +} +DEV_STAT_SCSI_ATTR_INTR_PORT_ATTR_RO(indx); + +static ssize_t target_stat_scsi_att_intr_port_show_attr_port_auth_indx( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_dev_entry *deve; + ssize_t ret; + + spin_lock_irq(&nacl->device_list_lock); + deve = nacl->device_list[lacl->mapped_lun]; + if (!deve->se_lun || !deve->se_lun_acl) { + spin_unlock_irq(&nacl->device_list_lock); + return -ENODEV; + } + /* scsiAttIntrPortAuthIntrIdx */ + ret = snprintf(page, PAGE_SIZE, "%u\n", nacl->acl_index); + spin_unlock_irq(&nacl->device_list_lock); + return ret; +} +DEV_STAT_SCSI_ATTR_INTR_PORT_ATTR_RO(port_auth_indx); + +static ssize_t target_stat_scsi_att_intr_port_show_attr_port_ident( + struct se_ml_stat_grps *lgrps, char *page) +{ + struct se_lun_acl *lacl = container_of(lgrps, + struct se_lun_acl, ml_stat_grps); + struct se_node_acl *nacl = lacl->se_lun_nacl; + struct se_session *se_sess; + struct se_portal_group *tpg; + ssize_t ret; + unsigned char buf[64]; + + spin_lock_irq(&nacl->nacl_sess_lock); + se_sess = nacl->nacl_sess; + if (!se_sess) { + spin_unlock_irq(&nacl->nacl_sess_lock); + return -ENODEV; + } + + tpg = nacl->se_tpg; + /* scsiAttIntrPortName+scsiAttIntrPortIdentifier */ + memset(buf, 0, 64); + if (tpg->se_tpg_tfo->sess_get_initiator_sid != NULL) + tpg->se_tpg_tfo->sess_get_initiator_sid(se_sess, buf, 64); + + ret = snprintf(page, PAGE_SIZE, "%s+i+%s\n", nacl->initiatorname, buf); + spin_unlock_irq(&nacl->nacl_sess_lock); + return ret; +} +DEV_STAT_SCSI_ATTR_INTR_PORT_ATTR_RO(port_ident); + +CONFIGFS_EATTR_OPS(target_stat_scsi_att_intr_port, se_ml_stat_grps, + scsi_att_intr_port_group); + +static struct configfs_attribute *target_stat_scsi_ath_intr_port_attrs[] = { + &target_stat_scsi_att_intr_port_inst.attr, + &target_stat_scsi_att_intr_port_dev.attr, + &target_stat_scsi_att_intr_port_port.attr, + &target_stat_scsi_att_intr_port_indx.attr, + &target_stat_scsi_att_intr_port_port_auth_indx.attr, + &target_stat_scsi_att_intr_port_port_ident.attr, + NULL, +}; + +static struct configfs_item_operations target_stat_scsi_att_intr_port_attrib_ops = { + .show_attribute = target_stat_scsi_att_intr_port_attr_show, + .store_attribute = target_stat_scsi_att_intr_port_attr_store, +}; + +static struct config_item_type target_stat_scsi_att_intr_port_cit = { + .ct_item_ops = &target_stat_scsi_att_intr_port_attrib_ops, + .ct_attrs = target_stat_scsi_ath_intr_port_attrs, + .ct_owner = THIS_MODULE, +}; + +/* + * Called from target_core_fabric_configfs.c:target_fabric_make_mappedlun() to setup + * the target MappedLUN statistics groups + configfs CITs located in target_core_stat.c + */ +void target_stat_setup_mappedlun_default_groups(struct se_lun_acl *lacl) +{ + struct config_group *ml_stat_grp = &lacl->ml_stat_grps.stat_group; + + config_group_init_type_name(&lacl->ml_stat_grps.scsi_auth_intr_group, + "scsi_auth_intr", &target_stat_scsi_auth_intr_cit); + config_group_init_type_name(&lacl->ml_stat_grps.scsi_att_intr_port_group, + "scsi_att_intr_port", &target_stat_scsi_att_intr_port_cit); + + ml_stat_grp->default_groups[0] = &lacl->ml_stat_grps.scsi_auth_intr_group; + ml_stat_grp->default_groups[1] = &lacl->ml_stat_grps.scsi_att_intr_port_group; + ml_stat_grp->default_groups[2] = NULL; +} diff --git a/drivers/target/target_core_tmr.c b/drivers/target/target_core_tmr.c index 158cecbec71..f7cd95e8111 100644 --- a/drivers/target/target_core_tmr.c +++ b/drivers/target/target_core_tmr.c @@ -3,8 +3,7 @@ * * This file contains SPC-3 task management infrastructure * - * Copyright (c) 2009,2010 Rising Tide Systems - * Copyright (c) 2009,2010 Linux-iSCSI.org + * (c) Copyright 2009-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -24,48 +23,44 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/list.h> +#include <linux/export.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_tmr.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> #include <target/target_core_configfs.h> +#include "target_core_internal.h" #include "target_core_alua.h" #include "target_core_pr.h" -#define DEBUG_LUN_RESET -#ifdef DEBUG_LUN_RESET -#define DEBUG_LR(x...) printk(KERN_INFO x) -#else -#define DEBUG_LR(x...) -#endif - -struct se_tmr_req *core_tmr_alloc_req( +int core_tmr_alloc_req( struct se_cmd *se_cmd, void *fabric_tmr_ptr, - u8 function) + u8 function, + gfp_t gfp_flags) { struct se_tmr_req *tmr; - tmr = kmem_cache_zalloc(se_tmr_req_cache, GFP_KERNEL); - if (!(tmr)) { - printk(KERN_ERR "Unable to allocate struct se_tmr_req\n"); - return ERR_PTR(-ENOMEM); + tmr = kzalloc(sizeof(struct se_tmr_req), gfp_flags); + if (!tmr) { + pr_err("Unable to allocate struct se_tmr_req\n"); + return -ENOMEM; } + + se_cmd->se_cmd_flags |= SCF_SCSI_TMR_CDB; + se_cmd->se_tmr_req = tmr; tmr->task_cmd = se_cmd; tmr->fabric_tmr_ptr = fabric_tmr_ptr; tmr->function = function; INIT_LIST_HEAD(&tmr->tmr_list); - return tmr; + return 0; } EXPORT_SYMBOL(core_tmr_alloc_req); @@ -73,92 +68,137 @@ void core_tmr_release_req( struct se_tmr_req *tmr) { struct se_device *dev = tmr->tmr_dev; + unsigned long flags; - spin_lock(&dev->se_tmr_lock); + if (!dev) { + kfree(tmr); + return; + } + + spin_lock_irqsave(&dev->se_tmr_lock, flags); list_del(&tmr->tmr_list); - kmem_cache_free(se_tmr_req_cache, tmr); - spin_unlock(&dev->se_tmr_lock); + spin_unlock_irqrestore(&dev->se_tmr_lock, flags); + + kfree(tmr); } static void core_tmr_handle_tas_abort( struct se_node_acl *tmr_nacl, struct se_cmd *cmd, - int tas, - int fe_count) + int tas) { - if (!(fe_count)) { - transport_cmd_finish_abort(cmd, 1); - return; - } + bool remove = true; /* * TASK ABORTED status (TAS) bit support */ - if (((tmr_nacl != NULL) && - (tmr_nacl == cmd->se_sess->se_node_acl)) || tas) + if ((tmr_nacl && + (tmr_nacl != cmd->se_sess->se_node_acl)) && tas) { + remove = false; transport_send_task_abort(cmd); + } - transport_cmd_finish_abort(cmd, 0); + transport_cmd_finish_abort(cmd, remove); } -int core_tmr_lun_reset( +static int target_check_cdb_and_preempt(struct list_head *list, + struct se_cmd *cmd) +{ + struct t10_pr_registration *reg; + + if (!list) + return 0; + list_for_each_entry(reg, list, pr_reg_abort_list) { + if (reg->pr_res_key == cmd->pr_res_key) + return 0; + } + + return 1; +} + +void core_tmr_abort_task( struct se_device *dev, struct se_tmr_req *tmr, - struct list_head *preempt_and_abort_list, - struct se_cmd *prout_cmd) + struct se_session *se_sess) { - struct se_cmd *cmd; - struct se_queue_req *qr, *qr_tmp; - struct se_node_acl *tmr_nacl = NULL; - struct se_portal_group *tmr_tpg = NULL; - struct se_queue_obj *qobj = dev->dev_queue_obj; - struct se_tmr_req *tmr_p, *tmr_pp; - struct se_task *task, *task_tmp; + struct se_cmd *se_cmd, *tmp_cmd; unsigned long flags; - int fe_count, state, tas; - /* - * TASK_ABORTED status bit, this is configurable via ConfigFS - * struct se_device attributes. spc4r17 section 7.4.6 Control mode page - * - * A task aborted status (TAS) bit set to zero specifies that aborted - * tasks shall be terminated by the device server without any response - * to the application client. A TAS bit set to one specifies that tasks - * aborted by the actions of an I_T nexus other than the I_T nexus on - * which the command was received shall be completed with TASK ABORTED - * status (see SAM-4). - */ - tas = DEV_ATTRIB(dev)->emulate_tas; - /* - * Determine if this se_tmr is coming from a $FABRIC_MOD - * or struct se_device passthrough.. - */ - if (tmr && tmr->task_cmd && tmr->task_cmd->se_sess) { - tmr_nacl = tmr->task_cmd->se_sess->se_node_acl; - tmr_tpg = tmr->task_cmd->se_sess->se_tpg; - if (tmr_nacl && tmr_tpg) { - DEBUG_LR("LUN_RESET: TMR caller fabric: %s" - " initiator port %s\n", - TPG_TFO(tmr_tpg)->get_fabric_name(), - tmr_nacl->initiatorname); + int ref_tag; + + spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); + list_for_each_entry_safe(se_cmd, tmp_cmd, + &se_sess->sess_cmd_list, se_cmd_list) { + + if (dev != se_cmd->se_dev) + continue; + + /* skip se_cmd associated with tmr */ + if (tmr->task_cmd == se_cmd) + continue; + + ref_tag = se_cmd->se_tfo->get_task_tag(se_cmd); + if (tmr->ref_task_tag != ref_tag) + continue; + + printk("ABORT_TASK: Found referenced %s task_tag: %u\n", + se_cmd->se_tfo->get_fabric_name(), ref_tag); + + spin_lock(&se_cmd->t_state_lock); + if (se_cmd->transport_state & CMD_T_COMPLETE) { + printk("ABORT_TASK: ref_tag: %u already complete, skipping\n", ref_tag); + spin_unlock(&se_cmd->t_state_lock); + spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); + goto out; } + se_cmd->transport_state |= CMD_T_ABORTED; + spin_unlock(&se_cmd->t_state_lock); + + list_del_init(&se_cmd->se_cmd_list); + kref_get(&se_cmd->cmd_kref); + spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); + + cancel_work_sync(&se_cmd->work); + transport_wait_for_tasks(se_cmd); + + target_put_sess_cmd(se_sess, se_cmd); + transport_cmd_finish_abort(se_cmd, true); + + printk("ABORT_TASK: Sending TMR_FUNCTION_COMPLETE for" + " ref_tag: %d\n", ref_tag); + tmr->response = TMR_FUNCTION_COMPLETE; + return; } - DEBUG_LR("LUN_RESET: %s starting for [%s], tas: %d\n", - (preempt_and_abort_list) ? "Preempt" : "TMR", - TRANSPORT(dev)->name, tas); + spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); + +out: + printk("ABORT_TASK: Sending TMR_TASK_DOES_NOT_EXIST for ref_tag: %d\n", + tmr->ref_task_tag); + tmr->response = TMR_TASK_DOES_NOT_EXIST; +} + +static void core_tmr_drain_tmr_list( + struct se_device *dev, + struct se_tmr_req *tmr, + struct list_head *preempt_and_abort_list) +{ + LIST_HEAD(drain_tmr_list); + struct se_tmr_req *tmr_p, *tmr_pp; + struct se_cmd *cmd; + unsigned long flags; /* * Release all pending and outgoing TMRs aside from the received * LUN_RESET tmr.. */ - spin_lock(&dev->se_tmr_lock); + spin_lock_irqsave(&dev->se_tmr_lock, flags); list_for_each_entry_safe(tmr_p, tmr_pp, &dev->dev_tmr_list, tmr_list) { /* * Allow the received TMR to return with FUNCTION_COMPLETE. */ - if (tmr && (tmr_p == tmr)) + if (tmr_p == tmr) continue; cmd = tmr_p->task_cmd; - if (!(cmd)) { - printk(KERN_ERR "Unable to locate struct se_cmd for TMR\n"); + if (!cmd) { + pr_err("Unable to locate struct se_cmd for TMR\n"); continue; } /* @@ -166,35 +206,51 @@ int core_tmr_lun_reset( * parameter (eg: for PROUT PREEMPT_AND_ABORT service action * skip non regisration key matching TMRs. */ - if ((preempt_and_abort_list != NULL) && - (core_scsi3_check_cdb_abort_and_preempt( - preempt_and_abort_list, cmd) != 0)) + if (target_check_cdb_and_preempt(preempt_and_abort_list, cmd)) continue; - spin_unlock(&dev->se_tmr_lock); - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (!(atomic_read(&T_TASK(cmd)->t_transport_active))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - spin_lock(&dev->se_tmr_lock); + spin_lock(&cmd->t_state_lock); + if (!(cmd->transport_state & CMD_T_ACTIVE)) { + spin_unlock(&cmd->t_state_lock); continue; } if (cmd->t_state == TRANSPORT_ISTATE_PROCESSING) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - spin_lock(&dev->se_tmr_lock); + spin_unlock(&cmd->t_state_lock); continue; } - DEBUG_LR("LUN_RESET: %s releasing TMR %p Function: 0x%02x," + spin_unlock(&cmd->t_state_lock); + + list_move_tail(&tmr_p->tmr_list, &drain_tmr_list); + } + spin_unlock_irqrestore(&dev->se_tmr_lock, flags); + + list_for_each_entry_safe(tmr_p, tmr_pp, &drain_tmr_list, tmr_list) { + list_del_init(&tmr_p->tmr_list); + cmd = tmr_p->task_cmd; + + pr_debug("LUN_RESET: %s releasing TMR %p Function: 0x%02x," " Response: 0x%02x, t_state: %d\n", (preempt_and_abort_list) ? "Preempt" : "", tmr_p, tmr_p->function, tmr_p->response, cmd->t_state); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - transport_cmd_finish_abort_tmr(cmd); - spin_lock(&dev->se_tmr_lock); + transport_cmd_finish_abort(cmd, 1); } - spin_unlock(&dev->se_tmr_lock); +} + +static void core_tmr_drain_state_list( + struct se_device *dev, + struct se_cmd *prout_cmd, + struct se_node_acl *tmr_nacl, + int tas, + struct list_head *preempt_and_abort_list) +{ + LIST_HEAD(drain_task_list); + struct se_cmd *cmd, *next; + unsigned long flags; + /* - * Complete outstanding struct se_task CDBs with TASK_ABORTED SAM status. + * Complete outstanding commands with TASK_ABORTED SAM status. + * * This is following sam4r17, section 5.6 Aborting commands, Table 38 * for TMR LUN_RESET: * @@ -215,190 +271,125 @@ int core_tmr_lun_reset( * in the Control Mode Page. */ spin_lock_irqsave(&dev->execute_task_lock, flags); - list_for_each_entry_safe(task, task_tmp, &dev->state_task_list, - t_state_list) { - if (!(TASK_CMD(task))) { - printk(KERN_ERR "TASK_CMD(task) is NULL!\n"); - continue; - } - cmd = TASK_CMD(task); - - if (!T_TASK(cmd)) { - printk(KERN_ERR "T_TASK(cmd) is NULL for task: %p cmd:" - " %p ITT: 0x%08x\n", task, cmd, - CMD_TFO(cmd)->get_task_tag(cmd)); - continue; - } + list_for_each_entry_safe(cmd, next, &dev->state_list, state_list) { /* * For PREEMPT_AND_ABORT usage, only process commands * with a matching reservation key. */ - if ((preempt_and_abort_list != NULL) && - (core_scsi3_check_cdb_abort_and_preempt( - preempt_and_abort_list, cmd) != 0)) + if (target_check_cdb_and_preempt(preempt_and_abort_list, cmd)) continue; + /* * Not aborting PROUT PREEMPT_AND_ABORT CDB.. */ if (prout_cmd == cmd) continue; - list_del(&task->t_state_list); - atomic_set(&task->task_state_active, 0); - spin_unlock_irqrestore(&dev->execute_task_lock, flags); - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - DEBUG_LR("LUN_RESET: %s cmd: %p task: %p" - " ITT/CmdSN: 0x%08x/0x%08x, i_state: %d, t_state/" - "def_t_state: %d/%d cdb: 0x%02x\n", - (preempt_and_abort_list) ? "Preempt" : "", cmd, task, - CMD_TFO(cmd)->get_task_tag(cmd), 0, - CMD_TFO(cmd)->get_cmd_state(cmd), cmd->t_state, - cmd->deferred_t_state, T_TASK(cmd)->t_task_cdb[0]); - DEBUG_LR("LUN_RESET: ITT[0x%08x] - pr_res_key: 0x%016Lx" - " t_task_cdbs: %d t_task_cdbs_left: %d" - " t_task_cdbs_sent: %d -- t_transport_active: %d" - " t_transport_stop: %d t_transport_sent: %d\n", - CMD_TFO(cmd)->get_task_tag(cmd), cmd->pr_res_key, - T_TASK(cmd)->t_task_cdbs, - atomic_read(&T_TASK(cmd)->t_task_cdbs_left), - atomic_read(&T_TASK(cmd)->t_task_cdbs_sent), - atomic_read(&T_TASK(cmd)->t_transport_active), - atomic_read(&T_TASK(cmd)->t_transport_stop), - atomic_read(&T_TASK(cmd)->t_transport_sent)); - - if (atomic_read(&task->task_active)) { - atomic_set(&task->task_stop, 1); - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - - DEBUG_LR("LUN_RESET: Waiting for task: %p to shutdown" - " for dev: %p\n", task, dev); - wait_for_completion(&task->task_stop_comp); - DEBUG_LR("LUN_RESET Completed task: %p shutdown for" - " dev: %p\n", task, dev); - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - atomic_dec(&T_TASK(cmd)->t_task_cdbs_left); - - atomic_set(&task->task_active, 0); - atomic_set(&task->task_stop, 0); - } - __transport_stop_task_timer(task, &flags); + list_move_tail(&cmd->state_list, &drain_task_list); + cmd->state_active = false; + } + spin_unlock_irqrestore(&dev->execute_task_lock, flags); - if (!(atomic_dec_and_test(&T_TASK(cmd)->t_task_cdbs_ex_left))) { - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - DEBUG_LR("LUN_RESET: Skipping task: %p, dev: %p for" - " t_task_cdbs_ex_left: %d\n", task, dev, - atomic_read(&T_TASK(cmd)->t_task_cdbs_ex_left)); + while (!list_empty(&drain_task_list)) { + cmd = list_entry(drain_task_list.next, struct se_cmd, state_list); + list_del(&cmd->state_list); + + pr_debug("LUN_RESET: %s cmd: %p" + " ITT/CmdSN: 0x%08x/0x%08x, i_state: %d, t_state: %d" + "cdb: 0x%02x\n", + (preempt_and_abort_list) ? "Preempt" : "", cmd, + cmd->se_tfo->get_task_tag(cmd), 0, + cmd->se_tfo->get_cmd_state(cmd), cmd->t_state, + cmd->t_task_cdb[0]); + pr_debug("LUN_RESET: ITT[0x%08x] - pr_res_key: 0x%016Lx" + " -- CMD_T_ACTIVE: %d" + " CMD_T_STOP: %d CMD_T_SENT: %d\n", + cmd->se_tfo->get_task_tag(cmd), cmd->pr_res_key, + (cmd->transport_state & CMD_T_ACTIVE) != 0, + (cmd->transport_state & CMD_T_STOP) != 0, + (cmd->transport_state & CMD_T_SENT) != 0); - spin_lock_irqsave(&dev->execute_task_lock, flags); - continue; - } - fe_count = atomic_read(&T_TASK(cmd)->t_fe_count); + /* + * If the command may be queued onto a workqueue cancel it now. + * + * This is equivalent to removal from the execute queue in the + * loop above, but we do it down here given that + * cancel_work_sync may block. + */ + if (cmd->t_state == TRANSPORT_COMPLETE) + cancel_work_sync(&cmd->work); - if (atomic_read(&T_TASK(cmd)->t_transport_active)) { - DEBUG_LR("LUN_RESET: got t_transport_active = 1 for" - " task: %p, t_fe_count: %d dev: %p\n", task, - fe_count, dev); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, - flags); - core_tmr_handle_tas_abort(tmr_nacl, cmd, tas, fe_count); + spin_lock_irqsave(&cmd->t_state_lock, flags); + target_stop_cmd(cmd, &flags); - spin_lock_irqsave(&dev->execute_task_lock, flags); - continue; - } - DEBUG_LR("LUN_RESET: Got t_transport_active = 0 for task: %p," - " t_fe_count: %d dev: %p\n", task, fe_count, dev); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - core_tmr_handle_tas_abort(tmr_nacl, cmd, tas, fe_count); + cmd->transport_state |= CMD_T_ABORTED; + spin_unlock_irqrestore(&cmd->t_state_lock, flags); - spin_lock_irqsave(&dev->execute_task_lock, flags); + core_tmr_handle_tas_abort(tmr_nacl, cmd, tas); } - spin_unlock_irqrestore(&dev->execute_task_lock, flags); - /* - * Release all commands remaining in the struct se_device cmd queue. +} + +int core_tmr_lun_reset( + struct se_device *dev, + struct se_tmr_req *tmr, + struct list_head *preempt_and_abort_list, + struct se_cmd *prout_cmd) +{ + struct se_node_acl *tmr_nacl = NULL; + struct se_portal_group *tmr_tpg = NULL; + int tas; + /* + * TASK_ABORTED status bit, this is configurable via ConfigFS + * struct se_device attributes. spc4r17 section 7.4.6 Control mode page * - * This follows the same logic as above for the struct se_device - * struct se_task state list, where commands are returned with - * TASK_ABORTED status, if there is an outstanding $FABRIC_MOD - * reference, otherwise the struct se_cmd is released. + * A task aborted status (TAS) bit set to zero specifies that aborted + * tasks shall be terminated by the device server without any response + * to the application client. A TAS bit set to one specifies that tasks + * aborted by the actions of an I_T nexus other than the I_T nexus on + * which the command was received shall be completed with TASK ABORTED + * status (see SAM-4). */ - spin_lock_irqsave(&qobj->cmd_queue_lock, flags); - list_for_each_entry_safe(qr, qr_tmp, &qobj->qobj_list, qr_list) { - cmd = (struct se_cmd *)qr->cmd; - if (!(cmd)) { - /* - * Skip these for non PREEMPT_AND_ABORT usage.. - */ - if (preempt_and_abort_list != NULL) - continue; - - atomic_dec(&qobj->queue_cnt); - list_del(&qr->qr_list); - kfree(qr); - continue; + tas = dev->dev_attrib.emulate_tas; + /* + * Determine if this se_tmr is coming from a $FABRIC_MOD + * or struct se_device passthrough.. + */ + if (tmr && tmr->task_cmd && tmr->task_cmd->se_sess) { + tmr_nacl = tmr->task_cmd->se_sess->se_node_acl; + tmr_tpg = tmr->task_cmd->se_sess->se_tpg; + if (tmr_nacl && tmr_tpg) { + pr_debug("LUN_RESET: TMR caller fabric: %s" + " initiator port %s\n", + tmr_tpg->se_tpg_tfo->get_fabric_name(), + tmr_nacl->initiatorname); } - /* - * For PREEMPT_AND_ABORT usage, only process commands - * with a matching reservation key. - */ - if ((preempt_and_abort_list != NULL) && - (core_scsi3_check_cdb_abort_and_preempt( - preempt_and_abort_list, cmd) != 0)) - continue; - /* - * Not aborting PROUT PREEMPT_AND_ABORT CDB.. - */ - if (prout_cmd == cmd) - continue; - - atomic_dec(&T_TASK(cmd)->t_transport_queue_active); - atomic_dec(&qobj->queue_cnt); - list_del(&qr->qr_list); - spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); + } + pr_debug("LUN_RESET: %s starting for [%s], tas: %d\n", + (preempt_and_abort_list) ? "Preempt" : "TMR", + dev->transport->name, tas); - state = qr->state; - kfree(qr); + core_tmr_drain_tmr_list(dev, tmr, preempt_and_abort_list); + core_tmr_drain_state_list(dev, prout_cmd, tmr_nacl, tas, + preempt_and_abort_list); - DEBUG_LR("LUN_RESET: %s from Device Queue: cmd: %p t_state:" - " %d t_fe_count: %d\n", (preempt_and_abort_list) ? - "Preempt" : "", cmd, state, - atomic_read(&T_TASK(cmd)->t_fe_count)); - /* - * Signal that the command has failed via cmd->se_cmd_flags, - * and call TFO->new_cmd_failure() to wakeup any fabric - * dependent code used to wait for unsolicited data out - * allocation to complete. The fabric module is expected - * to dump any remaining unsolicited data out for the aborted - * command at this point. - */ - transport_new_cmd_failure(cmd); - - core_tmr_handle_tas_abort(tmr_nacl, cmd, tas, - atomic_read(&T_TASK(cmd)->t_fe_count)); - spin_lock_irqsave(&qobj->cmd_queue_lock, flags); - } - spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); /* * Clear any legacy SPC-2 reservation when called during * LOGICAL UNIT RESET */ - if (!(preempt_and_abort_list) && - (dev->dev_flags & DF_SPC2_RESERVATIONS)) { + if (!preempt_and_abort_list && + (dev->dev_reservation_flags & DRF_SPC2_RESERVATIONS)) { spin_lock(&dev->dev_reservation_lock); dev->dev_reserved_node_acl = NULL; - dev->dev_flags &= ~DF_SPC2_RESERVATIONS; + dev->dev_reservation_flags &= ~DRF_SPC2_RESERVATIONS; spin_unlock(&dev->dev_reservation_lock); - printk(KERN_INFO "LUN_RESET: SCSI-2 Released reservation\n"); + pr_debug("LUN_RESET: SCSI-2 Released reservation\n"); } - spin_lock(&dev->stats_lock); - dev->num_resets++; - spin_unlock(&dev->stats_lock); + atomic_long_inc(&dev->num_resets); - DEBUG_LR("LUN_RESET: %s for [%s] Complete\n", + pr_debug("LUN_RESET: %s for [%s] Complete\n", (preempt_and_abort_list) ? "Preempt" : "TMR", - TRANSPORT(dev)->name); + dev->transport->name); return 0; } + diff --git a/drivers/target/target_core_tpg.c b/drivers/target/target_core_tpg.c index c26f6746762..c036595b17c 100644 --- a/drivers/target/target_core_tpg.c +++ b/drivers/target/target_core_tpg.c @@ -3,10 +3,7 @@ * * This file contains generic Target Portal Group related functions. * - * Copyright (c) 2002, 2003, 2004, 2005 PyX Technologies, Inc. - * Copyright (c) 2005, 2006, 2007 SBE, Inc. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2002-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -31,20 +28,23 @@ #include <linux/timer.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> #include <linux/in.h> +#include <linux/export.h> #include <net/sock.h> #include <net/tcp.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_tpg.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> -#include "target_core_hba.h" +#include "target_core_internal.h" + +extern struct se_device *g_lun0_dev; + +static DEFINE_SPINLOCK(tpg_lock); +static LIST_HEAD(tpg_list); /* core_clear_initiator_node_from_tpg(): * @@ -57,50 +57,27 @@ static void core_clear_initiator_node_from_tpg( int i; struct se_dev_entry *deve; struct se_lun *lun; - struct se_lun_acl *acl, *acl_tmp; spin_lock_irq(&nacl->device_list_lock); for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - deve = &nacl->device_list[i]; + deve = nacl->device_list[i]; if (!(deve->lun_flags & TRANSPORT_LUNFLAGS_INITIATOR_ACCESS)) continue; if (!deve->se_lun) { - printk(KERN_ERR "%s device entries device pointer is" + pr_err("%s device entries device pointer is" " NULL, but Initiator has access.\n", - TPG_TFO(tpg)->get_fabric_name()); + tpg->se_tpg_tfo->get_fabric_name()); continue; } lun = deve->se_lun; spin_unlock_irq(&nacl->device_list_lock); - core_update_device_list_for_node(lun, NULL, deve->mapped_lun, - TRANSPORT_LUNFLAGS_NO_ACCESS, nacl, tpg, 0); - - spin_lock(&lun->lun_acl_lock); - list_for_each_entry_safe(acl, acl_tmp, - &lun->lun_acl_list, lacl_list) { - if (!(strcmp(acl->initiatorname, - nacl->initiatorname)) && - (acl->mapped_lun == deve->mapped_lun)) - break; - } - - if (!acl) { - printk(KERN_ERR "Unable to locate struct se_lun_acl for %s," - " mapped_lun: %u\n", nacl->initiatorname, - deve->mapped_lun); - spin_unlock(&lun->lun_acl_lock); - spin_lock_irq(&nacl->device_list_lock); - continue; - } - - list_del(&acl->lacl_list); - spin_unlock(&lun->lun_acl_lock); + core_disable_device_list_for_node(lun, NULL, deve->mapped_lun, + TRANSPORT_LUNFLAGS_NO_ACCESS, nacl, tpg); spin_lock_irq(&nacl->device_list_lock); - kfree(acl); } spin_unlock_irq(&nacl->device_list_lock); } @@ -116,7 +93,7 @@ struct se_node_acl *__core_tpg_get_initiator_node_acl( struct se_node_acl *acl; list_for_each_entry(acl, &tpg->acl_node_list, acl_list) { - if (!(strcmp(acl->initiatorname, initiatorname))) + if (!strcmp(acl->initiatorname, initiatorname)) return acl; } @@ -133,18 +110,13 @@ struct se_node_acl *core_tpg_get_initiator_node_acl( { struct se_node_acl *acl; - spin_lock_bh(&tpg->acl_node_lock); - list_for_each_entry(acl, &tpg->acl_node_list, acl_list) { - if (!(strcmp(acl->initiatorname, initiatorname)) && - (!(acl->dynamic_node_acl))) { - spin_unlock_bh(&tpg->acl_node_lock); - return acl; - } - } - spin_unlock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); + acl = __core_tpg_get_initiator_node_acl(tpg, initiatorname); + spin_unlock_irq(&tpg->acl_node_lock); - return NULL; + return acl; } +EXPORT_SYMBOL(core_tpg_get_initiator_node_acl); /* core_tpg_add_node_to_devs(): * @@ -161,7 +133,7 @@ void core_tpg_add_node_to_devs( spin_lock(&tpg->tpg_lun_lock); for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - lun = &tpg->tpg_lun_list[i]; + lun = tpg->tpg_lun_list[i]; if (lun->lun_status != TRANSPORT_LUN_STATUS_ACTIVE) continue; @@ -172,31 +144,28 @@ void core_tpg_add_node_to_devs( * By default in LIO-Target $FABRIC_MOD, * demo_mode_write_protect is ON, or READ_ONLY; */ - if (!(TPG_TFO(tpg)->tpg_check_demo_mode_write_protect(tpg))) { - if (dev->dev_flags & DF_READ_ONLY) - lun_access = TRANSPORT_LUNFLAGS_READ_ONLY; - else - lun_access = TRANSPORT_LUNFLAGS_READ_WRITE; + if (!tpg->se_tpg_tfo->tpg_check_demo_mode_write_protect(tpg)) { + lun_access = TRANSPORT_LUNFLAGS_READ_WRITE; } else { /* * Allow only optical drives to issue R/W in default RO * demo mode. */ - if (TRANSPORT(dev)->get_device_type(dev) == TYPE_DISK) + if (dev->transport->get_device_type(dev) == TYPE_DISK) lun_access = TRANSPORT_LUNFLAGS_READ_ONLY; else lun_access = TRANSPORT_LUNFLAGS_READ_WRITE; } - printk(KERN_INFO "TARGET_CORE[%s]->TPG[%u]_LUN[%u] - Adding %s" + pr_debug("TARGET_CORE[%s]->TPG[%u]_LUN[%u] - Adding %s" " access for LUN in Demo Mode\n", - TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), lun->unpacked_lun, + tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), lun->unpacked_lun, (lun_access == TRANSPORT_LUNFLAGS_READ_WRITE) ? "READ-WRITE" : "READ-ONLY"); - core_update_device_list_for_node(lun, NULL, lun->unpacked_lun, - lun_access, acl, tpg, 1); + core_enable_device_list_for_node(lun, NULL, lun->unpacked_lun, + lun_access, acl, tpg); spin_lock(&tpg->tpg_lun_lock); } spin_unlock(&tpg->tpg_lun_lock); @@ -211,8 +180,8 @@ static int core_set_queue_depth_for_node( struct se_node_acl *acl) { if (!acl->queue_depth) { - printk(KERN_ERR "Queue depth for %s Initiator Node: %s is 0," - "defaulting to 1.\n", TPG_TFO(tpg)->get_fabric_name(), + pr_err("Queue depth for %s Initiator Node: %s is 0," + "defaulting to 1.\n", tpg->se_tpg_tfo->get_fabric_name(), acl->initiatorname); acl->queue_depth = 1; } @@ -220,6 +189,34 @@ static int core_set_queue_depth_for_node( return 0; } +void array_free(void *array, int n) +{ + void **a = array; + int i; + + for (i = 0; i < n; i++) + kfree(a[i]); + kfree(a); +} + +static void *array_zalloc(int n, size_t size, gfp_t flags) +{ + void **a; + int i; + + a = kzalloc(n * sizeof(void*), flags); + if (!a) + return NULL; + for (i = 0; i < n; i++) { + a[i] = kzalloc(size, flags); + if (!a[i]) { + array_free(a, n); + return NULL; + } + } + return a; +} + /* core_create_device_list_for_node(): * * @@ -229,15 +226,15 @@ static int core_create_device_list_for_node(struct se_node_acl *nacl) struct se_dev_entry *deve; int i; - nacl->device_list = kzalloc(sizeof(struct se_dev_entry) * - TRANSPORT_MAX_LUNS_PER_TPG, GFP_KERNEL); - if (!(nacl->device_list)) { - printk(KERN_ERR "Unable to allocate memory for" + nacl->device_list = array_zalloc(TRANSPORT_MAX_LUNS_PER_TPG, + sizeof(struct se_dev_entry), GFP_KERNEL); + if (!nacl->device_list) { + pr_err("Unable to allocate memory for" " struct se_node_acl->device_list\n"); - return -1; + return -ENOMEM; } for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - deve = &nacl->device_list[i]; + deve = nacl->device_list[i]; atomic_set(&deve->ua_count, 0); atomic_set(&deve->pr_ref_count, 0); @@ -260,52 +257,59 @@ struct se_node_acl *core_tpg_check_initiator_node_acl( struct se_node_acl *acl; acl = core_tpg_get_initiator_node_acl(tpg, initiatorname); - if ((acl)) + if (acl) return acl; - if (!(TPG_TFO(tpg)->tpg_check_demo_mode(tpg))) + if (!tpg->se_tpg_tfo->tpg_check_demo_mode(tpg)) return NULL; - acl = TPG_TFO(tpg)->tpg_alloc_fabric_acl(tpg); - if (!(acl)) + acl = tpg->se_tpg_tfo->tpg_alloc_fabric_acl(tpg); + if (!acl) return NULL; INIT_LIST_HEAD(&acl->acl_list); INIT_LIST_HEAD(&acl->acl_sess_list); + kref_init(&acl->acl_kref); + init_completion(&acl->acl_free_comp); spin_lock_init(&acl->device_list_lock); spin_lock_init(&acl->nacl_sess_lock); atomic_set(&acl->acl_pr_ref_count, 0); - acl->queue_depth = TPG_TFO(tpg)->tpg_get_default_depth(tpg); + acl->queue_depth = tpg->se_tpg_tfo->tpg_get_default_depth(tpg); snprintf(acl->initiatorname, TRANSPORT_IQN_LEN, "%s", initiatorname); acl->se_tpg = tpg; acl->acl_index = scsi_get_new_index(SCSI_AUTH_INTR_INDEX); - spin_lock_init(&acl->stats_lock); acl->dynamic_node_acl = 1; - TPG_TFO(tpg)->set_default_node_attributes(acl); + tpg->se_tpg_tfo->set_default_node_attributes(acl); if (core_create_device_list_for_node(acl) < 0) { - TPG_TFO(tpg)->tpg_release_fabric_acl(tpg, acl); + tpg->se_tpg_tfo->tpg_release_fabric_acl(tpg, acl); return NULL; } if (core_set_queue_depth_for_node(tpg, acl) < 0) { core_free_device_list_for_node(acl, tpg); - TPG_TFO(tpg)->tpg_release_fabric_acl(tpg, acl); + tpg->se_tpg_tfo->tpg_release_fabric_acl(tpg, acl); return NULL; } + /* + * Here we only create demo-mode MappedLUNs from the active + * TPG LUNs if the fabric is not explicitly asking for + * tpg_check_demo_mode_login_only() == 1. + */ + if ((tpg->se_tpg_tfo->tpg_check_demo_mode_login_only == NULL) || + (tpg->se_tpg_tfo->tpg_check_demo_mode_login_only(tpg) != 1)) + core_tpg_add_node_to_devs(acl, tpg); - core_tpg_add_node_to_devs(acl, tpg); - - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); list_add_tail(&acl->acl_list, &tpg->acl_node_list); tpg->num_node_acls++; - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); - printk("%s_TPG[%u] - Added DYNAMIC ACL with TCQ Depth: %d for %s" - " Initiator Node: %s\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), acl->queue_depth, - TPG_TFO(tpg)->get_fabric_name(), initiatorname); + pr_debug("%s_TPG[%u] - Added DYNAMIC ACL with TCQ Depth: %d for %s" + " Initiator Node: %s\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), acl->queue_depth, + tpg->se_tpg_tfo->get_fabric_name(), initiatorname); return acl; } @@ -319,19 +323,19 @@ void core_tpg_wait_for_nacl_pr_ref(struct se_node_acl *nacl) void core_tpg_clear_object_luns(struct se_portal_group *tpg) { - int i, ret; + int i; struct se_lun *lun; spin_lock(&tpg->tpg_lun_lock); for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - lun = &tpg->tpg_lun_list[i]; + lun = tpg->tpg_lun_list[i]; if ((lun->lun_status != TRANSPORT_LUN_STATUS_ACTIVE) || (lun->lun_se_dev == NULL)) continue; spin_unlock(&tpg->tpg_lun_lock); - ret = core_dev_del_lun(tpg, lun->unpacked_lun); + core_dev_del_lun(tpg, lun->unpacked_lun); spin_lock(&tpg->tpg_lun_lock); } spin_unlock(&tpg->tpg_lun_lock); @@ -350,37 +354,37 @@ struct se_node_acl *core_tpg_add_initiator_node_acl( { struct se_node_acl *acl = NULL; - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); acl = __core_tpg_get_initiator_node_acl(tpg, initiatorname); - if ((acl)) { + if (acl) { if (acl->dynamic_node_acl) { acl->dynamic_node_acl = 0; - printk(KERN_INFO "%s_TPG[%u] - Replacing dynamic ACL" - " for %s\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), initiatorname); - spin_unlock_bh(&tpg->acl_node_lock); + pr_debug("%s_TPG[%u] - Replacing dynamic ACL" + " for %s\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), initiatorname); + spin_unlock_irq(&tpg->acl_node_lock); /* * Release the locally allocated struct se_node_acl * because * core_tpg_add_initiator_node_acl() returned * a pointer to an existing demo mode node ACL. */ if (se_nacl) - TPG_TFO(tpg)->tpg_release_fabric_acl(tpg, + tpg->se_tpg_tfo->tpg_release_fabric_acl(tpg, se_nacl); goto done; } - printk(KERN_ERR "ACL entry for %s Initiator" + pr_err("ACL entry for %s Initiator" " Node %s already exists for TPG %u, ignoring" - " request.\n", TPG_TFO(tpg)->get_fabric_name(), - initiatorname, TPG_TFO(tpg)->tpg_get_tag(tpg)); - spin_unlock_bh(&tpg->acl_node_lock); + " request.\n", tpg->se_tpg_tfo->get_fabric_name(), + initiatorname, tpg->se_tpg_tfo->tpg_get_tag(tpg)); + spin_unlock_irq(&tpg->acl_node_lock); return ERR_PTR(-EEXIST); } - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); - if (!(se_nacl)) { - printk("struct se_node_acl pointer is NULL\n"); + if (!se_nacl) { + pr_err("struct se_node_acl pointer is NULL\n"); return ERR_PTR(-EINVAL); } /* @@ -392,6 +396,8 @@ struct se_node_acl *core_tpg_add_initiator_node_acl( INIT_LIST_HEAD(&acl->acl_list); INIT_LIST_HEAD(&acl->acl_sess_list); + kref_init(&acl->acl_kref); + init_completion(&acl->acl_free_comp); spin_lock_init(&acl->device_list_lock); spin_lock_init(&acl->nacl_sess_lock); atomic_set(&acl->acl_pr_ref_count, 0); @@ -399,31 +405,30 @@ struct se_node_acl *core_tpg_add_initiator_node_acl( snprintf(acl->initiatorname, TRANSPORT_IQN_LEN, "%s", initiatorname); acl->se_tpg = tpg; acl->acl_index = scsi_get_new_index(SCSI_AUTH_INTR_INDEX); - spin_lock_init(&acl->stats_lock); - TPG_TFO(tpg)->set_default_node_attributes(acl); + tpg->se_tpg_tfo->set_default_node_attributes(acl); if (core_create_device_list_for_node(acl) < 0) { - TPG_TFO(tpg)->tpg_release_fabric_acl(tpg, acl); + tpg->se_tpg_tfo->tpg_release_fabric_acl(tpg, acl); return ERR_PTR(-ENOMEM); } if (core_set_queue_depth_for_node(tpg, acl) < 0) { core_free_device_list_for_node(acl, tpg); - TPG_TFO(tpg)->tpg_release_fabric_acl(tpg, acl); + tpg->se_tpg_tfo->tpg_release_fabric_acl(tpg, acl); return ERR_PTR(-EINVAL); } - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); list_add_tail(&acl->acl_list, &tpg->acl_node_list); tpg->num_node_acls++; - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); done: - printk(KERN_INFO "%s_TPG[%hu] - Added ACL with TCQ Depth: %d for %s" - " Initiator Node: %s\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), acl->queue_depth, - TPG_TFO(tpg)->get_fabric_name(), initiatorname); + pr_debug("%s_TPG[%hu] - Added ACL with TCQ Depth: %d for %s" + " Initiator Node: %s\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), acl->queue_depth, + tpg->se_tpg_tfo->get_fabric_name(), initiatorname); return acl; } @@ -438,48 +443,56 @@ int core_tpg_del_initiator_node_acl( struct se_node_acl *acl, int force) { + LIST_HEAD(sess_list); struct se_session *sess, *sess_tmp; - int dynamic_acl = 0; + unsigned long flags; + int rc; - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); if (acl->dynamic_node_acl) { acl->dynamic_node_acl = 0; - dynamic_acl = 1; } list_del(&acl->acl_list); tpg->num_node_acls--; - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); - spin_lock_bh(&tpg->session_lock); - list_for_each_entry_safe(sess, sess_tmp, - &tpg->tpg_sess_list, sess_list) { - if (sess->se_node_acl != acl) - continue; - /* - * Determine if the session needs to be closed by our context. - */ - if (!(TPG_TFO(tpg)->shutdown_session(sess))) + spin_lock_irqsave(&acl->nacl_sess_lock, flags); + acl->acl_stop = 1; + + list_for_each_entry_safe(sess, sess_tmp, &acl->acl_sess_list, + sess_acl_list) { + if (sess->sess_tearing_down != 0) continue; - spin_unlock_bh(&tpg->session_lock); - /* - * If the $FABRIC_MOD session for the Initiator Node ACL exists, - * forcefully shutdown the $FABRIC_MOD session/nexus. - */ - TPG_TFO(tpg)->close_session(sess); + target_get_session(sess); + list_move(&sess->sess_acl_list, &sess_list); + } + spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); + + list_for_each_entry_safe(sess, sess_tmp, &sess_list, sess_acl_list) { + list_del(&sess->sess_acl_list); - spin_lock_bh(&tpg->session_lock); + rc = tpg->se_tpg_tfo->shutdown_session(sess); + target_put_session(sess); + if (!rc) + continue; + target_put_session(sess); } - spin_unlock_bh(&tpg->session_lock); + target_put_nacl(acl); + /* + * Wait for last target_put_nacl() to complete in target_complete_nacl() + * for active fabric session transport_deregister_session() callbacks. + */ + wait_for_completion(&acl->acl_free_comp); core_tpg_wait_for_nacl_pr_ref(acl); core_clear_initiator_node_from_tpg(acl, tpg); core_free_device_list_for_node(acl, tpg); - printk(KERN_INFO "%s_TPG[%hu] - Deleted ACL with TCQ Depth: %d for %s" - " Initiator Node: %s\n", TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg), acl->queue_depth, - TPG_TFO(tpg)->get_fabric_name(), acl->initiatorname); + pr_debug("%s_TPG[%hu] - Deleted ACL with TCQ Depth: %d for %s" + " Initiator Node: %s\n", tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg), acl->queue_depth, + tpg->se_tpg_tfo->get_fabric_name(), acl->initiatorname); return 0; } @@ -497,48 +510,49 @@ int core_tpg_set_initiator_node_queue_depth( { struct se_session *sess, *init_sess = NULL; struct se_node_acl *acl; + unsigned long flags; int dynamic_acl = 0; - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); acl = __core_tpg_get_initiator_node_acl(tpg, initiatorname); - if (!(acl)) { - printk(KERN_ERR "Access Control List entry for %s Initiator" + if (!acl) { + pr_err("Access Control List entry for %s Initiator" " Node %s does not exists for TPG %hu, ignoring" - " request.\n", TPG_TFO(tpg)->get_fabric_name(), - initiatorname, TPG_TFO(tpg)->tpg_get_tag(tpg)); - spin_unlock_bh(&tpg->acl_node_lock); + " request.\n", tpg->se_tpg_tfo->get_fabric_name(), + initiatorname, tpg->se_tpg_tfo->tpg_get_tag(tpg)); + spin_unlock_irq(&tpg->acl_node_lock); return -ENODEV; } if (acl->dynamic_node_acl) { acl->dynamic_node_acl = 0; dynamic_acl = 1; } - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); - spin_lock_bh(&tpg->session_lock); + spin_lock_irqsave(&tpg->session_lock, flags); list_for_each_entry(sess, &tpg->tpg_sess_list, sess_list) { if (sess->se_node_acl != acl) continue; if (!force) { - printk(KERN_ERR "Unable to change queue depth for %s" + pr_err("Unable to change queue depth for %s" " Initiator Node: %s while session is" " operational. To forcefully change the queue" " depth and force session reinstatement" " use the \"force=1\" parameter.\n", - TPG_TFO(tpg)->get_fabric_name(), initiatorname); - spin_unlock_bh(&tpg->session_lock); + tpg->se_tpg_tfo->get_fabric_name(), initiatorname); + spin_unlock_irqrestore(&tpg->session_lock, flags); - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); if (dynamic_acl) acl->dynamic_node_acl = 1; - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); return -EEXIST; } /* * Determine if the session needs to be closed by our context. */ - if (!(TPG_TFO(tpg)->shutdown_session(sess))) + if (!tpg->se_tpg_tfo->shutdown_session(sess)) continue; init_sess = sess; @@ -550,55 +564,85 @@ int core_tpg_set_initiator_node_queue_depth( * Change the value in the Node's struct se_node_acl, and call * core_set_queue_depth_for_node() to add the requested queue depth. * - * Finally call TPG_TFO(tpg)->close_session() to force session + * Finally call tpg->se_tpg_tfo->close_session() to force session * reinstatement to occur if there is an active session for the * $FABRIC_MOD Initiator Node in question. */ acl->queue_depth = queue_depth; if (core_set_queue_depth_for_node(tpg, acl) < 0) { - spin_unlock_bh(&tpg->session_lock); + spin_unlock_irqrestore(&tpg->session_lock, flags); /* * Force session reinstatement if * core_set_queue_depth_for_node() failed, because we assume * the $FABRIC_MOD has already the set session reinstatement - * bit from TPG_TFO(tpg)->shutdown_session() called above. + * bit from tpg->se_tpg_tfo->shutdown_session() called above. */ if (init_sess) - TPG_TFO(tpg)->close_session(init_sess); + tpg->se_tpg_tfo->close_session(init_sess); - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); if (dynamic_acl) acl->dynamic_node_acl = 1; - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); return -EINVAL; } - spin_unlock_bh(&tpg->session_lock); + spin_unlock_irqrestore(&tpg->session_lock, flags); /* * If the $FABRIC_MOD session for the Initiator Node ACL exists, * forcefully shutdown the $FABRIC_MOD session/nexus. */ if (init_sess) - TPG_TFO(tpg)->close_session(init_sess); + tpg->se_tpg_tfo->close_session(init_sess); - printk(KERN_INFO "Successfuly changed queue depth to: %d for Initiator" + pr_debug("Successfully changed queue depth to: %d for Initiator" " Node: %s on %s Target Portal Group: %u\n", queue_depth, - initiatorname, TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg)); + initiatorname, tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg)); - spin_lock_bh(&tpg->acl_node_lock); + spin_lock_irq(&tpg->acl_node_lock); if (dynamic_acl) acl->dynamic_node_acl = 1; - spin_unlock_bh(&tpg->acl_node_lock); + spin_unlock_irq(&tpg->acl_node_lock); return 0; } EXPORT_SYMBOL(core_tpg_set_initiator_node_queue_depth); +/* core_tpg_set_initiator_node_tag(): + * + * Initiator nodeacl tags are not used internally, but may be used by + * userspace to emulate aliases or groups. + * Returns length of newly-set tag or -EINVAL. + */ +int core_tpg_set_initiator_node_tag( + struct se_portal_group *tpg, + struct se_node_acl *acl, + const char *new_tag) +{ + if (strlen(new_tag) >= MAX_ACL_TAG_SIZE) + return -EINVAL; + + if (!strncmp("NULL", new_tag, 4)) { + acl->acl_tag[0] = '\0'; + return 0; + } + + return snprintf(acl->acl_tag, MAX_ACL_TAG_SIZE, "%s", new_tag); +} +EXPORT_SYMBOL(core_tpg_set_initiator_node_tag); + +static void core_tpg_lun_ref_release(struct percpu_ref *ref) +{ + struct se_lun *lun = container_of(ref, struct se_lun, lun_ref); + + complete(&lun->lun_ref_comp); +} + static int core_tpg_setup_virtual_lun0(struct se_portal_group *se_tpg) { /* Set in core_dev_setup_virtual_lun0() */ - struct se_device *dev = se_global->g_lun0_dev; + struct se_device *dev = g_lun0_dev; struct se_lun *lun = &se_tpg->tpg_virt_lun0; u32 lun_access = TRANSPORT_LUNFLAGS_READ_ONLY; int ret; @@ -608,14 +652,13 @@ static int core_tpg_setup_virtual_lun0(struct se_portal_group *se_tpg) atomic_set(&lun->lun_acl_count, 0); init_completion(&lun->lun_shutdown_comp); INIT_LIST_HEAD(&lun->lun_acl_list); - INIT_LIST_HEAD(&lun->lun_cmd_list); spin_lock_init(&lun->lun_acl_lock); - spin_lock_init(&lun->lun_cmd_lock); spin_lock_init(&lun->lun_sep_lock); + init_completion(&lun->lun_ref_comp); - ret = core_tpg_post_addlun(se_tpg, lun, lun_access, dev); + ret = core_tpg_add_lun(se_tpg, lun, lun_access, dev); if (ret < 0) - return -1; + return ret; return 0; } @@ -637,25 +680,25 @@ int core_tpg_register( struct se_lun *lun; u32 i; - se_tpg->tpg_lun_list = kzalloc((sizeof(struct se_lun) * - TRANSPORT_MAX_LUNS_PER_TPG), GFP_KERNEL); - if (!(se_tpg->tpg_lun_list)) { - printk(KERN_ERR "Unable to allocate struct se_portal_group->" + se_tpg->tpg_lun_list = array_zalloc(TRANSPORT_MAX_LUNS_PER_TPG, + sizeof(struct se_lun), GFP_KERNEL); + if (!se_tpg->tpg_lun_list) { + pr_err("Unable to allocate struct se_portal_group->" "tpg_lun_list\n"); return -ENOMEM; } for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { - lun = &se_tpg->tpg_lun_list[i]; + lun = se_tpg->tpg_lun_list[i]; lun->unpacked_lun = i; + lun->lun_link_magic = SE_LUN_LINK_MAGIC; lun->lun_status = TRANSPORT_LUN_STATUS_FREE; atomic_set(&lun->lun_acl_count, 0); init_completion(&lun->lun_shutdown_comp); INIT_LIST_HEAD(&lun->lun_acl_list); - INIT_LIST_HEAD(&lun->lun_cmd_list); spin_lock_init(&lun->lun_acl_lock); - spin_lock_init(&lun->lun_cmd_lock); spin_lock_init(&lun->lun_sep_lock); + init_completion(&lun->lun_ref_comp); } se_tpg->se_tpg_type = se_tpg_type; @@ -664,7 +707,7 @@ int core_tpg_register( se_tpg->se_tpg_wwn = se_wwn; atomic_set(&se_tpg->tpg_pr_ref_count, 0); INIT_LIST_HEAD(&se_tpg->acl_node_list); - INIT_LIST_HEAD(&se_tpg->se_tpg_list); + INIT_LIST_HEAD(&se_tpg->se_tpg_node); INIT_LIST_HEAD(&se_tpg->tpg_sess_list); spin_lock_init(&se_tpg->acl_node_lock); spin_lock_init(&se_tpg->session_lock); @@ -672,16 +715,17 @@ int core_tpg_register( if (se_tpg->se_tpg_type == TRANSPORT_TPG_TYPE_NORMAL) { if (core_tpg_setup_virtual_lun0(se_tpg) < 0) { - kfree(se_tpg); + array_free(se_tpg->tpg_lun_list, + TRANSPORT_MAX_LUNS_PER_TPG); return -ENOMEM; } } - spin_lock_bh(&se_global->se_tpg_lock); - list_add_tail(&se_tpg->se_tpg_list, &se_global->g_se_tpg_list); - spin_unlock_bh(&se_global->se_tpg_lock); + spin_lock_bh(&tpg_lock); + list_add_tail(&se_tpg->se_tpg_node, &tpg_list); + spin_unlock_bh(&tpg_lock); - printk(KERN_INFO "TARGET_CORE[%s]: Allocated %s struct se_portal_group for" + pr_debug("TARGET_CORE[%s]: Allocated %s struct se_portal_group for" " endpoint: %s, Portal Tag: %u\n", tfo->get_fabric_name(), (se_tpg->se_tpg_type == TRANSPORT_TPG_TYPE_NORMAL) ? "Normal" : "Discovery", (tfo->tpg_get_wwn(se_tpg) == NULL) ? @@ -695,16 +739,16 @@ int core_tpg_deregister(struct se_portal_group *se_tpg) { struct se_node_acl *nacl, *nacl_tmp; - printk(KERN_INFO "TARGET_CORE[%s]: Deallocating %s struct se_portal_group" + pr_debug("TARGET_CORE[%s]: Deallocating %s struct se_portal_group" " for endpoint: %s Portal Tag %u\n", (se_tpg->se_tpg_type == TRANSPORT_TPG_TYPE_NORMAL) ? - "Normal" : "Discovery", TPG_TFO(se_tpg)->get_fabric_name(), - TPG_TFO(se_tpg)->tpg_get_wwn(se_tpg), - TPG_TFO(se_tpg)->tpg_get_tag(se_tpg)); + "Normal" : "Discovery", se_tpg->se_tpg_tfo->get_fabric_name(), + se_tpg->se_tpg_tfo->tpg_get_wwn(se_tpg), + se_tpg->se_tpg_tfo->tpg_get_tag(se_tpg)); - spin_lock_bh(&se_global->se_tpg_lock); - list_del(&se_tpg->se_tpg_list); - spin_unlock_bh(&se_global->se_tpg_lock); + spin_lock_bh(&tpg_lock); + list_del(&se_tpg->se_tpg_node); + spin_unlock_bh(&tpg_lock); while (atomic_read(&se_tpg->tpg_pr_ref_count) != 0) cpu_relax(); @@ -713,52 +757,52 @@ int core_tpg_deregister(struct se_portal_group *se_tpg) * not been released because of TFO->tpg_check_demo_mode_cache() == 1 * in transport_deregister_session(). */ - spin_lock_bh(&se_tpg->acl_node_lock); + spin_lock_irq(&se_tpg->acl_node_lock); list_for_each_entry_safe(nacl, nacl_tmp, &se_tpg->acl_node_list, acl_list) { list_del(&nacl->acl_list); se_tpg->num_node_acls--; - spin_unlock_bh(&se_tpg->acl_node_lock); + spin_unlock_irq(&se_tpg->acl_node_lock); core_tpg_wait_for_nacl_pr_ref(nacl); core_free_device_list_for_node(nacl, se_tpg); - TPG_TFO(se_tpg)->tpg_release_fabric_acl(se_tpg, nacl); + se_tpg->se_tpg_tfo->tpg_release_fabric_acl(se_tpg, nacl); - spin_lock_bh(&se_tpg->acl_node_lock); + spin_lock_irq(&se_tpg->acl_node_lock); } - spin_unlock_bh(&se_tpg->acl_node_lock); + spin_unlock_irq(&se_tpg->acl_node_lock); if (se_tpg->se_tpg_type == TRANSPORT_TPG_TYPE_NORMAL) core_tpg_release_virtual_lun0(se_tpg); se_tpg->se_tpg_fabric_ptr = NULL; - kfree(se_tpg->tpg_lun_list); + array_free(se_tpg->tpg_lun_list, TRANSPORT_MAX_LUNS_PER_TPG); return 0; } EXPORT_SYMBOL(core_tpg_deregister); -struct se_lun *core_tpg_pre_addlun( +struct se_lun *core_tpg_alloc_lun( struct se_portal_group *tpg, u32 unpacked_lun) { struct se_lun *lun; if (unpacked_lun > (TRANSPORT_MAX_LUNS_PER_TPG-1)) { - printk(KERN_ERR "%s LUN: %u exceeds TRANSPORT_MAX_LUNS_PER_TPG" + pr_err("%s LUN: %u exceeds TRANSPORT_MAX_LUNS_PER_TPG" "-1: %u for Target Portal Group: %u\n", - TPG_TFO(tpg)->get_fabric_name(), + tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, TRANSPORT_MAX_LUNS_PER_TPG-1, - TPG_TFO(tpg)->tpg_get_tag(tpg)); + tpg->se_tpg_tfo->tpg_get_tag(tpg)); return ERR_PTR(-EOVERFLOW); } spin_lock(&tpg->tpg_lun_lock); - lun = &tpg->tpg_lun_list[unpacked_lun]; + lun = tpg->tpg_lun_list[unpacked_lun]; if (lun->lun_status == TRANSPORT_LUN_STATUS_ACTIVE) { - printk(KERN_ERR "TPG Logical Unit Number: %u is already active" + pr_err("TPG Logical Unit Number: %u is already active" " on %s Target Portal Group: %u, ignoring request.\n", - unpacked_lun, TPG_TFO(tpg)->get_fabric_name(), - TPG_TFO(tpg)->tpg_get_tag(tpg)); + unpacked_lun, tpg->se_tpg_tfo->get_fabric_name(), + tpg->se_tpg_tfo->tpg_get_tag(tpg)); spin_unlock(&tpg->tpg_lun_lock); return ERR_PTR(-EINVAL); } @@ -767,14 +811,23 @@ struct se_lun *core_tpg_pre_addlun( return lun; } -int core_tpg_post_addlun( +int core_tpg_add_lun( struct se_portal_group *tpg, struct se_lun *lun, u32 lun_access, - void *lun_ptr) + struct se_device *dev) { - if (core_dev_export(lun_ptr, tpg, lun) < 0) - return -1; + int ret; + + ret = percpu_ref_init(&lun->lun_ref, core_tpg_lun_ref_release); + if (ret < 0) + return ret; + + ret = core_dev_export(dev, tpg, lun); + if (ret < 0) { + percpu_ref_cancel_init(&lun->lun_ref); + return ret; + } spin_lock(&tpg->tpg_lun_lock); lun->lun_access = lun_access; @@ -784,37 +837,28 @@ int core_tpg_post_addlun( return 0; } -static void core_tpg_shutdown_lun( - struct se_portal_group *tpg, - struct se_lun *lun) -{ - core_clear_lun_from_tpg(lun, tpg); - transport_clear_lun_from_sessions(lun); -} - struct se_lun *core_tpg_pre_dellun( struct se_portal_group *tpg, - u32 unpacked_lun, - int *ret) + u32 unpacked_lun) { struct se_lun *lun; if (unpacked_lun > (TRANSPORT_MAX_LUNS_PER_TPG-1)) { - printk(KERN_ERR "%s LUN: %u exceeds TRANSPORT_MAX_LUNS_PER_TPG" + pr_err("%s LUN: %u exceeds TRANSPORT_MAX_LUNS_PER_TPG" "-1: %u for Target Portal Group: %u\n", - TPG_TFO(tpg)->get_fabric_name(), unpacked_lun, + tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, TRANSPORT_MAX_LUNS_PER_TPG-1, - TPG_TFO(tpg)->tpg_get_tag(tpg)); + tpg->se_tpg_tfo->tpg_get_tag(tpg)); return ERR_PTR(-EOVERFLOW); } spin_lock(&tpg->tpg_lun_lock); - lun = &tpg->tpg_lun_list[unpacked_lun]; + lun = tpg->tpg_lun_list[unpacked_lun]; if (lun->lun_status != TRANSPORT_LUN_STATUS_ACTIVE) { - printk(KERN_ERR "%s Logical Unit Number: %u is not active on" + pr_err("%s Logical Unit Number: %u is not active on" " Target Portal Group: %u, ignoring request.\n", - TPG_TFO(tpg)->get_fabric_name(), unpacked_lun, - TPG_TFO(tpg)->tpg_get_tag(tpg)); + tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, + tpg->se_tpg_tfo->tpg_get_tag(tpg)); spin_unlock(&tpg->tpg_lun_lock); return ERR_PTR(-ENODEV); } @@ -827,7 +871,8 @@ int core_tpg_post_dellun( struct se_portal_group *tpg, struct se_lun *lun) { - core_tpg_shutdown_lun(tpg, lun); + core_clear_lun_from_tpg(lun, tpg); + transport_clear_lun_ref(lun); core_dev_unexport(lun->lun_se_dev, tpg, lun); diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 236e22d8cfa..7fa62fc93e0 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -3,10 +3,7 @@ * * This file contains the Generic Target Engine Core. * - * Copyright (c) 2002, 2003, 2004, 2005 PyX Technologies, Inc. - * Copyright (c) 2005, 2006, 2007 SBE, Inc. - * Copyright (c) 2007-2010 Rising Tide Systems - * Copyright (c) 2008-2010 Linux-iSCSI.org + * (c) Copyright 2002-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -26,371 +23,178 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/net.h> #include <linux/delay.h> #include <linux/string.h> #include <linux/timer.h> #include <linux/slab.h> -#include <linux/blkdev.h> #include <linux/spinlock.h> -#include <linux/smp_lock.h> #include <linux/kthread.h> #include <linux/in.h> #include <linux/cdrom.h> +#include <linux/module.h> +#include <linux/ratelimit.h> #include <asm/unaligned.h> #include <net/sock.h> #include <net/tcp.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> -#include <scsi/libsas.h> /* For TASK_ATTR_* */ +#include <scsi/scsi_tcq.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_tmr.h> -#include <target/target_core_tpg.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> #include <target/target_core_configfs.h> +#include "target_core_internal.h" #include "target_core_alua.h" -#include "target_core_hba.h" #include "target_core_pr.h" -#include "target_core_scdb.h" #include "target_core_ua.h" -/* #define DEBUG_CDB_HANDLER */ -#ifdef DEBUG_CDB_HANDLER -#define DEBUG_CDB_H(x...) printk(KERN_INFO x) -#else -#define DEBUG_CDB_H(x...) -#endif - -/* #define DEBUG_CMD_MAP */ -#ifdef DEBUG_CMD_MAP -#define DEBUG_CMD_M(x...) printk(KERN_INFO x) -#else -#define DEBUG_CMD_M(x...) -#endif - -/* #define DEBUG_MEM_ALLOC */ -#ifdef DEBUG_MEM_ALLOC -#define DEBUG_MEM(x...) printk(KERN_INFO x) -#else -#define DEBUG_MEM(x...) -#endif - -/* #define DEBUG_MEM2_ALLOC */ -#ifdef DEBUG_MEM2_ALLOC -#define DEBUG_MEM2(x...) printk(KERN_INFO x) -#else -#define DEBUG_MEM2(x...) -#endif - -/* #define DEBUG_SG_CALC */ -#ifdef DEBUG_SG_CALC -#define DEBUG_SC(x...) printk(KERN_INFO x) -#else -#define DEBUG_SC(x...) -#endif - -/* #define DEBUG_SE_OBJ */ -#ifdef DEBUG_SE_OBJ -#define DEBUG_SO(x...) printk(KERN_INFO x) -#else -#define DEBUG_SO(x...) -#endif - -/* #define DEBUG_CMD_VOL */ -#ifdef DEBUG_CMD_VOL -#define DEBUG_VOL(x...) printk(KERN_INFO x) -#else -#define DEBUG_VOL(x...) -#endif - -/* #define DEBUG_CMD_STOP */ -#ifdef DEBUG_CMD_STOP -#define DEBUG_CS(x...) printk(KERN_INFO x) -#else -#define DEBUG_CS(x...) -#endif - -/* #define DEBUG_PASSTHROUGH */ -#ifdef DEBUG_PASSTHROUGH -#define DEBUG_PT(x...) printk(KERN_INFO x) -#else -#define DEBUG_PT(x...) -#endif - -/* #define DEBUG_TASK_STOP */ -#ifdef DEBUG_TASK_STOP -#define DEBUG_TS(x...) printk(KERN_INFO x) -#else -#define DEBUG_TS(x...) -#endif - -/* #define DEBUG_TRANSPORT_STOP */ -#ifdef DEBUG_TRANSPORT_STOP -#define DEBUG_TRANSPORT_S(x...) printk(KERN_INFO x) -#else -#define DEBUG_TRANSPORT_S(x...) -#endif - -/* #define DEBUG_TASK_FAILURE */ -#ifdef DEBUG_TASK_FAILURE -#define DEBUG_TF(x...) printk(KERN_INFO x) -#else -#define DEBUG_TF(x...) -#endif - -/* #define DEBUG_DEV_OFFLINE */ -#ifdef DEBUG_DEV_OFFLINE -#define DEBUG_DO(x...) printk(KERN_INFO x) -#else -#define DEBUG_DO(x...) -#endif - -/* #define DEBUG_TASK_STATE */ -#ifdef DEBUG_TASK_STATE -#define DEBUG_TSTATE(x...) printk(KERN_INFO x) -#else -#define DEBUG_TSTATE(x...) -#endif - -/* #define DEBUG_STATUS_THR */ -#ifdef DEBUG_STATUS_THR -#define DEBUG_ST(x...) printk(KERN_INFO x) -#else -#define DEBUG_ST(x...) -#endif - -/* #define DEBUG_TASK_TIMEOUT */ -#ifdef DEBUG_TASK_TIMEOUT -#define DEBUG_TT(x...) printk(KERN_INFO x) -#else -#define DEBUG_TT(x...) -#endif - -/* #define DEBUG_GENERIC_REQUEST_FAILURE */ -#ifdef DEBUG_GENERIC_REQUEST_FAILURE -#define DEBUG_GRF(x...) printk(KERN_INFO x) -#else -#define DEBUG_GRF(x...) -#endif - -/* #define DEBUG_SAM_TASK_ATTRS */ -#ifdef DEBUG_SAM_TASK_ATTRS -#define DEBUG_STA(x...) printk(KERN_INFO x) -#else -#define DEBUG_STA(x...) -#endif - -struct se_global *se_global; - -static struct kmem_cache *se_cmd_cache; +#define CREATE_TRACE_POINTS +#include <trace/events/target.h> + +static struct workqueue_struct *target_completion_wq; static struct kmem_cache *se_sess_cache; -struct kmem_cache *se_tmr_req_cache; struct kmem_cache *se_ua_cache; -struct kmem_cache *se_mem_cache; struct kmem_cache *t10_pr_reg_cache; struct kmem_cache *t10_alua_lu_gp_cache; struct kmem_cache *t10_alua_lu_gp_mem_cache; struct kmem_cache *t10_alua_tg_pt_gp_cache; struct kmem_cache *t10_alua_tg_pt_gp_mem_cache; +struct kmem_cache *t10_alua_lba_map_cache; +struct kmem_cache *t10_alua_lba_map_mem_cache; -/* Used for transport_dev_get_map_*() */ -typedef int (*map_func_t)(struct se_task *, u32); - -static int transport_generic_write_pending(struct se_cmd *); -static int transport_processing_thread(void *); -static int __transport_execute_tasks(struct se_device *dev); static void transport_complete_task_attr(struct se_cmd *cmd); -static void transport_direct_request_timeout(struct se_cmd *cmd); -static void transport_free_dev_tasks(struct se_cmd *cmd); -static u32 transport_generic_get_cdb_count(struct se_cmd *cmd, - unsigned long long starting_lba, u32 sectors, - enum dma_data_direction data_direction, - struct list_head *mem_list, int set_counts); -static int transport_generic_get_mem(struct se_cmd *cmd, u32 length, - u32 dma_size); -static int transport_generic_remove(struct se_cmd *cmd, - int release_to_pool, int session_reinstatement); -static int transport_get_sectors(struct se_cmd *cmd); -static struct list_head *transport_init_se_mem_list(void); -static int transport_map_sg_to_mem(struct se_cmd *cmd, - struct list_head *se_mem_list, void *in_mem, - u32 *se_mem_cnt); -static void transport_memcpy_se_mem_read_contig(struct se_cmd *cmd, - unsigned char *dst, struct list_head *se_mem_list); -static void transport_release_fe_cmd(struct se_cmd *cmd); -static void transport_remove_cmd_from_queue(struct se_cmd *cmd, - struct se_queue_obj *qobj); -static int transport_set_sense_codes(struct se_cmd *cmd, u8 asc, u8 ascq); -static void transport_stop_all_task_timers(struct se_cmd *cmd); - -int transport_emulate_control_cdb(struct se_task *task); - -int init_se_global(void) -{ - struct se_global *global; +static void transport_handle_queue_full(struct se_cmd *cmd, + struct se_device *dev); +static int transport_put_cmd(struct se_cmd *cmd); +static void target_complete_ok_work(struct work_struct *work); - global = kzalloc(sizeof(struct se_global), GFP_KERNEL); - if (!(global)) { - printk(KERN_ERR "Unable to allocate memory for struct se_global\n"); - return -1; - } - - INIT_LIST_HEAD(&global->g_lu_gps_list); - INIT_LIST_HEAD(&global->g_se_tpg_list); - INIT_LIST_HEAD(&global->g_hba_list); - INIT_LIST_HEAD(&global->g_se_dev_list); - spin_lock_init(&global->g_device_lock); - spin_lock_init(&global->hba_lock); - spin_lock_init(&global->se_tpg_lock); - spin_lock_init(&global->lu_gps_lock); - spin_lock_init(&global->plugin_class_lock); - - se_cmd_cache = kmem_cache_create("se_cmd_cache", - sizeof(struct se_cmd), __alignof__(struct se_cmd), 0, NULL); - if (!(se_cmd_cache)) { - printk(KERN_ERR "kmem_cache_create for struct se_cmd failed\n"); - goto out; - } - se_tmr_req_cache = kmem_cache_create("se_tmr_cache", - sizeof(struct se_tmr_req), __alignof__(struct se_tmr_req), - 0, NULL); - if (!(se_tmr_req_cache)) { - printk(KERN_ERR "kmem_cache_create() for struct se_tmr_req" - " failed\n"); - goto out; - } +int init_se_kmem_caches(void) +{ se_sess_cache = kmem_cache_create("se_sess_cache", sizeof(struct se_session), __alignof__(struct se_session), 0, NULL); - if (!(se_sess_cache)) { - printk(KERN_ERR "kmem_cache_create() for struct se_session" + if (!se_sess_cache) { + pr_err("kmem_cache_create() for struct se_session" " failed\n"); goto out; } se_ua_cache = kmem_cache_create("se_ua_cache", sizeof(struct se_ua), __alignof__(struct se_ua), 0, NULL); - if (!(se_ua_cache)) { - printk(KERN_ERR "kmem_cache_create() for struct se_ua failed\n"); - goto out; - } - se_mem_cache = kmem_cache_create("se_mem_cache", - sizeof(struct se_mem), __alignof__(struct se_mem), 0, NULL); - if (!(se_mem_cache)) { - printk(KERN_ERR "kmem_cache_create() for struct se_mem failed\n"); - goto out; + if (!se_ua_cache) { + pr_err("kmem_cache_create() for struct se_ua failed\n"); + goto out_free_sess_cache; } t10_pr_reg_cache = kmem_cache_create("t10_pr_reg_cache", sizeof(struct t10_pr_registration), __alignof__(struct t10_pr_registration), 0, NULL); - if (!(t10_pr_reg_cache)) { - printk(KERN_ERR "kmem_cache_create() for struct t10_pr_registration" + if (!t10_pr_reg_cache) { + pr_err("kmem_cache_create() for struct t10_pr_registration" " failed\n"); - goto out; + goto out_free_ua_cache; } t10_alua_lu_gp_cache = kmem_cache_create("t10_alua_lu_gp_cache", sizeof(struct t10_alua_lu_gp), __alignof__(struct t10_alua_lu_gp), 0, NULL); - if (!(t10_alua_lu_gp_cache)) { - printk(KERN_ERR "kmem_cache_create() for t10_alua_lu_gp_cache" + if (!t10_alua_lu_gp_cache) { + pr_err("kmem_cache_create() for t10_alua_lu_gp_cache" " failed\n"); - goto out; + goto out_free_pr_reg_cache; } t10_alua_lu_gp_mem_cache = kmem_cache_create("t10_alua_lu_gp_mem_cache", sizeof(struct t10_alua_lu_gp_member), __alignof__(struct t10_alua_lu_gp_member), 0, NULL); - if (!(t10_alua_lu_gp_mem_cache)) { - printk(KERN_ERR "kmem_cache_create() for t10_alua_lu_gp_mem_" + if (!t10_alua_lu_gp_mem_cache) { + pr_err("kmem_cache_create() for t10_alua_lu_gp_mem_" "cache failed\n"); - goto out; + goto out_free_lu_gp_cache; } t10_alua_tg_pt_gp_cache = kmem_cache_create("t10_alua_tg_pt_gp_cache", sizeof(struct t10_alua_tg_pt_gp), __alignof__(struct t10_alua_tg_pt_gp), 0, NULL); - if (!(t10_alua_tg_pt_gp_cache)) { - printk(KERN_ERR "kmem_cache_create() for t10_alua_tg_pt_gp_" + if (!t10_alua_tg_pt_gp_cache) { + pr_err("kmem_cache_create() for t10_alua_tg_pt_gp_" "cache failed\n"); - goto out; + goto out_free_lu_gp_mem_cache; } t10_alua_tg_pt_gp_mem_cache = kmem_cache_create( "t10_alua_tg_pt_gp_mem_cache", sizeof(struct t10_alua_tg_pt_gp_member), __alignof__(struct t10_alua_tg_pt_gp_member), 0, NULL); - if (!(t10_alua_tg_pt_gp_mem_cache)) { - printk(KERN_ERR "kmem_cache_create() for t10_alua_tg_pt_gp_" + if (!t10_alua_tg_pt_gp_mem_cache) { + pr_err("kmem_cache_create() for t10_alua_tg_pt_gp_" "mem_t failed\n"); - goto out; + goto out_free_tg_pt_gp_cache; + } + t10_alua_lba_map_cache = kmem_cache_create( + "t10_alua_lba_map_cache", + sizeof(struct t10_alua_lba_map), + __alignof__(struct t10_alua_lba_map), 0, NULL); + if (!t10_alua_lba_map_cache) { + pr_err("kmem_cache_create() for t10_alua_lba_map_" + "cache failed\n"); + goto out_free_tg_pt_gp_mem_cache; + } + t10_alua_lba_map_mem_cache = kmem_cache_create( + "t10_alua_lba_map_mem_cache", + sizeof(struct t10_alua_lba_map_member), + __alignof__(struct t10_alua_lba_map_member), 0, NULL); + if (!t10_alua_lba_map_mem_cache) { + pr_err("kmem_cache_create() for t10_alua_lba_map_mem_" + "cache failed\n"); + goto out_free_lba_map_cache; } - se_global = global; + target_completion_wq = alloc_workqueue("target_completion", + WQ_MEM_RECLAIM, 0); + if (!target_completion_wq) + goto out_free_lba_map_mem_cache; return 0; + +out_free_lba_map_mem_cache: + kmem_cache_destroy(t10_alua_lba_map_mem_cache); +out_free_lba_map_cache: + kmem_cache_destroy(t10_alua_lba_map_cache); +out_free_tg_pt_gp_mem_cache: + kmem_cache_destroy(t10_alua_tg_pt_gp_mem_cache); +out_free_tg_pt_gp_cache: + kmem_cache_destroy(t10_alua_tg_pt_gp_cache); +out_free_lu_gp_mem_cache: + kmem_cache_destroy(t10_alua_lu_gp_mem_cache); +out_free_lu_gp_cache: + kmem_cache_destroy(t10_alua_lu_gp_cache); +out_free_pr_reg_cache: + kmem_cache_destroy(t10_pr_reg_cache); +out_free_ua_cache: + kmem_cache_destroy(se_ua_cache); +out_free_sess_cache: + kmem_cache_destroy(se_sess_cache); out: - if (se_cmd_cache) - kmem_cache_destroy(se_cmd_cache); - if (se_tmr_req_cache) - kmem_cache_destroy(se_tmr_req_cache); - if (se_sess_cache) - kmem_cache_destroy(se_sess_cache); - if (se_ua_cache) - kmem_cache_destroy(se_ua_cache); - if (se_mem_cache) - kmem_cache_destroy(se_mem_cache); - if (t10_pr_reg_cache) - kmem_cache_destroy(t10_pr_reg_cache); - if (t10_alua_lu_gp_cache) - kmem_cache_destroy(t10_alua_lu_gp_cache); - if (t10_alua_lu_gp_mem_cache) - kmem_cache_destroy(t10_alua_lu_gp_mem_cache); - if (t10_alua_tg_pt_gp_cache) - kmem_cache_destroy(t10_alua_tg_pt_gp_cache); - if (t10_alua_tg_pt_gp_mem_cache) - kmem_cache_destroy(t10_alua_tg_pt_gp_mem_cache); - kfree(global); - return -1; + return -ENOMEM; } -void release_se_global(void) +void release_se_kmem_caches(void) { - struct se_global *global; - - global = se_global; - if (!(global)) - return; - - kmem_cache_destroy(se_cmd_cache); - kmem_cache_destroy(se_tmr_req_cache); + destroy_workqueue(target_completion_wq); kmem_cache_destroy(se_sess_cache); kmem_cache_destroy(se_ua_cache); - kmem_cache_destroy(se_mem_cache); kmem_cache_destroy(t10_pr_reg_cache); kmem_cache_destroy(t10_alua_lu_gp_cache); kmem_cache_destroy(t10_alua_lu_gp_mem_cache); kmem_cache_destroy(t10_alua_tg_pt_gp_cache); kmem_cache_destroy(t10_alua_tg_pt_gp_mem_cache); - kfree(global); - - se_global = NULL; + kmem_cache_destroy(t10_alua_lba_map_cache); + kmem_cache_destroy(t10_alua_lba_map_mem_cache); } -/* SCSI statistics table index */ -static struct scsi_index_table scsi_index_table; - -/* - * Initialize the index table for allocating unique row indexes to various mib - * tables. - */ -void init_scsi_index_table(void) -{ - memset(&scsi_index_table, 0, sizeof(struct scsi_index_table)); - spin_lock_init(&scsi_index_table.lock); -} +/* This code ensures unique mib indexes are handed out. */ +static DEFINE_SPINLOCK(scsi_mib_index_lock); +static u32 scsi_mib_index[SCSI_INDEX_TYPE_MAX]; /* * Allocate a new row index for the entry type specified @@ -399,85 +203,114 @@ u32 scsi_get_new_index(scsi_index_t type) { u32 new_index; - if ((type < 0) || (type >= SCSI_INDEX_TYPE_MAX)) { - printk(KERN_ERR "Invalid index type %d\n", type); - return -EINVAL; - } + BUG_ON((type < 0) || (type >= SCSI_INDEX_TYPE_MAX)); - spin_lock(&scsi_index_table.lock); - new_index = ++scsi_index_table.scsi_mib_index[type]; - if (new_index == 0) - new_index = ++scsi_index_table.scsi_mib_index[type]; - spin_unlock(&scsi_index_table.lock); + spin_lock(&scsi_mib_index_lock); + new_index = ++scsi_mib_index[type]; + spin_unlock(&scsi_mib_index_lock); return new_index; } -void transport_init_queue_obj(struct se_queue_obj *qobj) -{ - atomic_set(&qobj->queue_cnt, 0); - INIT_LIST_HEAD(&qobj->qobj_list); - init_waitqueue_head(&qobj->thread_wq); - spin_lock_init(&qobj->cmd_queue_lock); -} -EXPORT_SYMBOL(transport_init_queue_obj); - -static int transport_subsystem_reqmods(void) +void transport_subsystem_check_init(void) { int ret; + static int sub_api_initialized; + + if (sub_api_initialized) + return; ret = request_module("target_core_iblock"); if (ret != 0) - printk(KERN_ERR "Unable to load target_core_iblock\n"); + pr_err("Unable to load target_core_iblock\n"); ret = request_module("target_core_file"); if (ret != 0) - printk(KERN_ERR "Unable to load target_core_file\n"); + pr_err("Unable to load target_core_file\n"); ret = request_module("target_core_pscsi"); if (ret != 0) - printk(KERN_ERR "Unable to load target_core_pscsi\n"); + pr_err("Unable to load target_core_pscsi\n"); - ret = request_module("target_core_stgt"); - if (ret != 0) - printk(KERN_ERR "Unable to load target_core_stgt\n"); + sub_api_initialized = 1; +} - return 0; +struct se_session *transport_init_session(enum target_prot_op sup_prot_ops) +{ + struct se_session *se_sess; + + se_sess = kmem_cache_zalloc(se_sess_cache, GFP_KERNEL); + if (!se_sess) { + pr_err("Unable to allocate struct se_session from" + " se_sess_cache\n"); + return ERR_PTR(-ENOMEM); + } + INIT_LIST_HEAD(&se_sess->sess_list); + INIT_LIST_HEAD(&se_sess->sess_acl_list); + INIT_LIST_HEAD(&se_sess->sess_cmd_list); + INIT_LIST_HEAD(&se_sess->sess_wait_list); + spin_lock_init(&se_sess->sess_cmd_lock); + kref_init(&se_sess->sess_kref); + se_sess->sup_prot_ops = sup_prot_ops; + + return se_sess; } +EXPORT_SYMBOL(transport_init_session); -int transport_subsystem_check_init(void) +int transport_alloc_session_tags(struct se_session *se_sess, + unsigned int tag_num, unsigned int tag_size) { - if (se_global->g_sub_api_initialized) - return 0; - /* - * Request the loading of known TCM subsystem plugins.. - */ - if (transport_subsystem_reqmods() < 0) - return -1; + int rc; + + se_sess->sess_cmd_map = kzalloc(tag_num * tag_size, + GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT); + if (!se_sess->sess_cmd_map) { + se_sess->sess_cmd_map = vzalloc(tag_num * tag_size); + if (!se_sess->sess_cmd_map) { + pr_err("Unable to allocate se_sess->sess_cmd_map\n"); + return -ENOMEM; + } + } + + rc = percpu_ida_init(&se_sess->sess_tag_pool, tag_num); + if (rc < 0) { + pr_err("Unable to init se_sess->sess_tag_pool," + " tag_num: %u\n", tag_num); + if (is_vmalloc_addr(se_sess->sess_cmd_map)) + vfree(se_sess->sess_cmd_map); + else + kfree(se_sess->sess_cmd_map); + se_sess->sess_cmd_map = NULL; + return -ENOMEM; + } - se_global->g_sub_api_initialized = 1; return 0; } +EXPORT_SYMBOL(transport_alloc_session_tags); -struct se_session *transport_init_session(void) +struct se_session *transport_init_session_tags(unsigned int tag_num, + unsigned int tag_size, + enum target_prot_op sup_prot_ops) { struct se_session *se_sess; + int rc; - se_sess = kmem_cache_zalloc(se_sess_cache, GFP_KERNEL); - if (!(se_sess)) { - printk(KERN_ERR "Unable to allocate struct se_session from" - " se_sess_cache\n"); + se_sess = transport_init_session(sup_prot_ops); + if (IS_ERR(se_sess)) + return se_sess; + + rc = transport_alloc_session_tags(se_sess, tag_num, tag_size); + if (rc < 0) { + transport_free_session(se_sess); return ERR_PTR(-ENOMEM); } - INIT_LIST_HEAD(&se_sess->sess_list); - INIT_LIST_HEAD(&se_sess->sess_acl_list); return se_sess; } -EXPORT_SYMBOL(transport_init_session); +EXPORT_SYMBOL(transport_init_session_tags); /* - * Called with spin_lock_bh(&struct se_portal_group->session_lock called. + * Called with spin_lock_irqsave(&struct se_portal_group->session_lock called. */ void __transport_register_session( struct se_portal_group *se_tpg, @@ -500,12 +333,14 @@ void __transport_register_session( * If the fabric module supports an ISID based TransportID, * save this value in binary from the fabric I_T Nexus now. */ - if (TPG_TFO(se_tpg)->sess_get_initiator_sid != NULL) { + if (se_tpg->se_tpg_tfo->sess_get_initiator_sid != NULL) { memset(&buf[0], 0, PR_REG_ISID_LEN); - TPG_TFO(se_tpg)->sess_get_initiator_sid(se_sess, + se_tpg->se_tpg_tfo->sess_get_initiator_sid(se_sess, &buf[0], PR_REG_ISID_LEN); se_sess->sess_bin_isid = get_unaligned_be64(&buf[0]); } + kref_get(&se_nacl->acl_kref); + spin_lock_irq(&se_nacl->nacl_sess_lock); /* * The se_nacl->nacl_sess pointer will be set to the @@ -519,8 +354,8 @@ void __transport_register_session( } list_add_tail(&se_sess->sess_list, &se_tpg->tpg_sess_list); - printk(KERN_INFO "TARGET_CORE[%s]: Registered fabric_sess_ptr: %p\n", - TPG_TFO(se_tpg)->get_fabric_name(), se_sess->fabric_sess_ptr); + pr_debug("TARGET_CORE[%s]: Registered fabric_sess_ptr: %p\n", + se_tpg->se_tpg_tfo->get_fabric_name(), se_sess->fabric_sess_ptr); } EXPORT_SYMBOL(__transport_register_session); @@ -530,23 +365,66 @@ void transport_register_session( struct se_session *se_sess, void *fabric_sess_ptr) { - spin_lock_bh(&se_tpg->session_lock); + unsigned long flags; + + spin_lock_irqsave(&se_tpg->session_lock, flags); __transport_register_session(se_tpg, se_nacl, se_sess, fabric_sess_ptr); - spin_unlock_bh(&se_tpg->session_lock); + spin_unlock_irqrestore(&se_tpg->session_lock, flags); } EXPORT_SYMBOL(transport_register_session); +static void target_release_session(struct kref *kref) +{ + struct se_session *se_sess = container_of(kref, + struct se_session, sess_kref); + struct se_portal_group *se_tpg = se_sess->se_tpg; + + se_tpg->se_tpg_tfo->close_session(se_sess); +} + +void target_get_session(struct se_session *se_sess) +{ + kref_get(&se_sess->sess_kref); +} +EXPORT_SYMBOL(target_get_session); + +void target_put_session(struct se_session *se_sess) +{ + struct se_portal_group *tpg = se_sess->se_tpg; + + if (tpg->se_tpg_tfo->put_session != NULL) { + tpg->se_tpg_tfo->put_session(se_sess); + return; + } + kref_put(&se_sess->sess_kref, target_release_session); +} +EXPORT_SYMBOL(target_put_session); + +static void target_complete_nacl(struct kref *kref) +{ + struct se_node_acl *nacl = container_of(kref, + struct se_node_acl, acl_kref); + + complete(&nacl->acl_free_comp); +} + +void target_put_nacl(struct se_node_acl *nacl) +{ + kref_put(&nacl->acl_kref, target_complete_nacl); +} + void transport_deregister_session_configfs(struct se_session *se_sess) { struct se_node_acl *se_nacl; - + unsigned long flags; /* * Used by struct se_node_acl's under ConfigFS to locate active struct se_session */ se_nacl = se_sess->se_node_acl; - if ((se_nacl)) { - spin_lock_irq(&se_nacl->nacl_sess_lock); - list_del(&se_sess->sess_acl_list); + if (se_nacl) { + spin_lock_irqsave(&se_nacl->nacl_sess_lock, flags); + if (se_nacl->acl_stop == 0) + list_del(&se_sess->sess_acl_list); /* * If the session list is empty, then clear the pointer. * Otherwise, set the struct se_session pointer from the tail @@ -559,13 +437,20 @@ void transport_deregister_session_configfs(struct se_session *se_sess) se_nacl->acl_sess_list.prev, struct se_session, sess_acl_list); } - spin_unlock_irq(&se_nacl->nacl_sess_lock); + spin_unlock_irqrestore(&se_nacl->nacl_sess_lock, flags); } } EXPORT_SYMBOL(transport_deregister_session_configfs); void transport_free_session(struct se_session *se_sess) { + if (se_sess->sess_cmd_map) { + percpu_ida_destroy(&se_sess->sess_tag_pool); + if (is_vmalloc_addr(se_sess->sess_cmd_map)) + vfree(se_sess->sess_cmd_map); + else + kfree(se_sess->sess_cmd_map); + } kmem_cache_free(se_sess_cache, se_sess); } EXPORT_SYMBOL(transport_free_session); @@ -573,650 +458,314 @@ EXPORT_SYMBOL(transport_free_session); void transport_deregister_session(struct se_session *se_sess) { struct se_portal_group *se_tpg = se_sess->se_tpg; + struct target_core_fabric_ops *se_tfo; struct se_node_acl *se_nacl; + unsigned long flags; + bool comp_nacl = true; - if (!(se_tpg)) { + if (!se_tpg) { transport_free_session(se_sess); return; } + se_tfo = se_tpg->se_tpg_tfo; - spin_lock_bh(&se_tpg->session_lock); + spin_lock_irqsave(&se_tpg->session_lock, flags); list_del(&se_sess->sess_list); se_sess->se_tpg = NULL; se_sess->fabric_sess_ptr = NULL; - spin_unlock_bh(&se_tpg->session_lock); + spin_unlock_irqrestore(&se_tpg->session_lock, flags); /* * Determine if we need to do extra work for this initiator node's * struct se_node_acl if it had been previously dynamically generated. */ se_nacl = se_sess->se_node_acl; - if ((se_nacl)) { - spin_lock_bh(&se_tpg->acl_node_lock); - if (se_nacl->dynamic_node_acl) { - if (!(TPG_TFO(se_tpg)->tpg_check_demo_mode_cache( - se_tpg))) { - list_del(&se_nacl->acl_list); - se_tpg->num_node_acls--; - spin_unlock_bh(&se_tpg->acl_node_lock); - - core_tpg_wait_for_nacl_pr_ref(se_nacl); - core_free_device_list_for_node(se_nacl, se_tpg); - TPG_TFO(se_tpg)->tpg_release_fabric_acl(se_tpg, - se_nacl); - spin_lock_bh(&se_tpg->acl_node_lock); - } + + spin_lock_irqsave(&se_tpg->acl_node_lock, flags); + if (se_nacl && se_nacl->dynamic_node_acl) { + if (!se_tfo->tpg_check_demo_mode_cache(se_tpg)) { + list_del(&se_nacl->acl_list); + se_tpg->num_node_acls--; + spin_unlock_irqrestore(&se_tpg->acl_node_lock, flags); + core_tpg_wait_for_nacl_pr_ref(se_nacl); + core_free_device_list_for_node(se_nacl, se_tpg); + se_tfo->tpg_release_fabric_acl(se_tpg, se_nacl); + + comp_nacl = false; + spin_lock_irqsave(&se_tpg->acl_node_lock, flags); } - spin_unlock_bh(&se_tpg->acl_node_lock); } + spin_unlock_irqrestore(&se_tpg->acl_node_lock, flags); - transport_free_session(se_sess); + pr_debug("TARGET_CORE[%s]: Deregistered fabric_sess\n", + se_tpg->se_tpg_tfo->get_fabric_name()); + /* + * If last kref is dropping now for an explicit NodeACL, awake sleeping + * ->acl_free_comp caller to wakeup configfs se_node_acl->acl_group + * removal context. + */ + if (se_nacl && comp_nacl) + target_put_nacl(se_nacl); - printk(KERN_INFO "TARGET_CORE[%s]: Deregistered fabric_sess\n", - TPG_TFO(se_tpg)->get_fabric_name()); + transport_free_session(se_sess); } EXPORT_SYMBOL(transport_deregister_session); /* - * Called with T_TASK(cmd)->t_state_lock held. + * Called with cmd->t_state_lock held. */ -static void transport_all_task_dev_remove_state(struct se_cmd *cmd) +static void target_remove_from_state_list(struct se_cmd *cmd) { - struct se_device *dev; - struct se_task *task; + struct se_device *dev = cmd->se_dev; unsigned long flags; - if (!T_TASK(cmd)) + if (!dev) return; - list_for_each_entry(task, &T_TASK(cmd)->t_task_list, t_list) { - dev = task->se_dev; - if (!(dev)) - continue; - - if (atomic_read(&task->task_active)) - continue; - - if (!(atomic_read(&task->task_state_active))) - continue; - - spin_lock_irqsave(&dev->execute_task_lock, flags); - list_del(&task->t_state_list); - DEBUG_TSTATE("Removed ITT: 0x%08x dev: %p task[%p]\n", - CMD_TFO(cmd)->tfo_get_task_tag(cmd), dev, task); - spin_unlock_irqrestore(&dev->execute_task_lock, flags); + if (cmd->transport_state & CMD_T_BUSY) + return; - atomic_set(&task->task_state_active, 0); - atomic_dec(&T_TASK(cmd)->t_task_cdbs_ex_left); + spin_lock_irqsave(&dev->execute_task_lock, flags); + if (cmd->state_active) { + list_del(&cmd->state_list); + cmd->state_active = false; } + spin_unlock_irqrestore(&dev->execute_task_lock, flags); } -/* transport_cmd_check_stop(): - * - * 'transport_off = 1' determines if t_transport_active should be cleared. - * 'transport_off = 2' determines if task_dev_state should be removed. - * - * A non-zero u8 t_state sets cmd->t_state. - * Returns 1 when command is stopped, else 0. - */ -static int transport_cmd_check_stop( - struct se_cmd *cmd, - int transport_off, - u8 t_state) +static int transport_cmd_check_stop(struct se_cmd *cmd, bool remove_from_lists, + bool write_pending) { unsigned long flags; - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - /* - * Determine if IOCTL context caller in requesting the stopping of this - * command for LUN shutdown purposes. - */ - if (atomic_read(&T_TASK(cmd)->transport_lun_stop)) { - DEBUG_CS("%s:%d atomic_read(&T_TASK(cmd)->transport_lun_stop)" - " == TRUE for ITT: 0x%08x\n", __func__, __LINE__, - CMD_TFO(cmd)->get_task_tag(cmd)); - - cmd->deferred_t_state = cmd->t_state; - cmd->t_state = TRANSPORT_DEFERRED_CMD; - atomic_set(&T_TASK(cmd)->t_transport_active, 0); - if (transport_off == 2) - transport_all_task_dev_remove_state(cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - complete(&T_TASK(cmd)->transport_lun_stop_comp); - return 1; + spin_lock_irqsave(&cmd->t_state_lock, flags); + if (write_pending) + cmd->t_state = TRANSPORT_WRITE_PENDING; + + if (remove_from_lists) { + target_remove_from_state_list(cmd); + + /* + * Clear struct se_cmd->se_lun before the handoff to FE. + */ + cmd->se_lun = NULL; } + /* * Determine if frontend context caller is requesting the stopping of - * this command for frontend excpections. + * this command for frontend exceptions. */ - if (atomic_read(&T_TASK(cmd)->t_transport_stop)) { - DEBUG_CS("%s:%d atomic_read(&T_TASK(cmd)->t_transport_stop) ==" - " TRUE for ITT: 0x%08x\n", __func__, __LINE__, - CMD_TFO(cmd)->get_task_tag(cmd)); + if (cmd->transport_state & CMD_T_STOP) { + pr_debug("%s:%d CMD_T_STOP for ITT: 0x%08x\n", + __func__, __LINE__, + cmd->se_tfo->get_task_tag(cmd)); - cmd->deferred_t_state = cmd->t_state; - cmd->t_state = TRANSPORT_DEFERRED_CMD; - if (transport_off == 2) - transport_all_task_dev_remove_state(cmd); + spin_unlock_irqrestore(&cmd->t_state_lock, flags); - /* - * Clear struct se_cmd->se_lun before the transport_off == 2 handoff - * to FE. - */ - if (transport_off == 2) - cmd->se_lun = NULL; - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - complete(&T_TASK(cmd)->t_transport_stop_comp); + complete_all(&cmd->t_transport_stop_comp); return 1; } - if (transport_off) { - atomic_set(&T_TASK(cmd)->t_transport_active, 0); - if (transport_off == 2) { - transport_all_task_dev_remove_state(cmd); - /* - * Clear struct se_cmd->se_lun before the transport_off == 2 - * handoff to fabric module. - */ - cmd->se_lun = NULL; - /* - * Some fabric modules like tcm_loop can release - * their internally allocated I/O refrence now and - * struct se_cmd now. - */ - if (CMD_TFO(cmd)->check_stop_free != NULL) { - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - - CMD_TFO(cmd)->check_stop_free(cmd); - return 1; - } - } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return 0; - } else if (t_state) - cmd->t_state = t_state; - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); + cmd->transport_state &= ~CMD_T_ACTIVE; + if (remove_from_lists) { + /* + * Some fabric modules like tcm_loop can release + * their internally allocated I/O reference now and + * struct se_cmd now. + * + * Fabric modules are expected to return '1' here if the + * se_cmd being passed is released at this point, + * or zero if not being released. + */ + if (cmd->se_tfo->check_stop_free != NULL) { + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + return cmd->se_tfo->check_stop_free(cmd); + } + } + spin_unlock_irqrestore(&cmd->t_state_lock, flags); return 0; } static int transport_cmd_check_stop_to_fabric(struct se_cmd *cmd) { - return transport_cmd_check_stop(cmd, 2, 0); + return transport_cmd_check_stop(cmd, true, false); } static void transport_lun_remove_cmd(struct se_cmd *cmd) { - struct se_lun *lun = SE_LUN(cmd); - unsigned long flags; + struct se_lun *lun = cmd->se_lun; if (!lun) return; - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (!(atomic_read(&T_TASK(cmd)->transport_dev_active))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - goto check_lun; - } - atomic_set(&T_TASK(cmd)->transport_dev_active, 0); - transport_all_task_dev_remove_state(cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - transport_free_dev_tasks(cmd); - -check_lun: - spin_lock_irqsave(&lun->lun_cmd_lock, flags); - if (atomic_read(&T_TASK(cmd)->transport_lun_active)) { - list_del(&cmd->se_lun_list); - atomic_set(&T_TASK(cmd)->transport_lun_active, 0); -#if 0 - printk(KERN_INFO "Removed ITT: 0x%08x from LUN LIST[%d]\n" - CMD_TFO(cmd)->get_task_tag(cmd), lun->unpacked_lun); -#endif - } - spin_unlock_irqrestore(&lun->lun_cmd_lock, flags); + if (cmpxchg(&cmd->lun_ref_active, true, false)) + percpu_ref_put(&lun->lun_ref); } void transport_cmd_finish_abort(struct se_cmd *cmd, int remove) { - transport_remove_cmd_from_queue(cmd, SE_DEV(cmd)->dev_queue_obj); - transport_lun_remove_cmd(cmd); - - if (transport_cmd_check_stop_to_fabric(cmd)) - return; + if (cmd->se_cmd_flags & SCF_SE_LUN_CMD) + transport_lun_remove_cmd(cmd); + /* + * Allow the fabric driver to unmap any resources before + * releasing the descriptor via TFO->release_cmd() + */ if (remove) - transport_generic_remove(cmd, 0, 0); -} - -void transport_cmd_finish_abort_tmr(struct se_cmd *cmd) -{ - transport_remove_cmd_from_queue(cmd, SE_DEV(cmd)->dev_queue_obj); + cmd->se_tfo->aborted_task(cmd); if (transport_cmd_check_stop_to_fabric(cmd)) return; - - transport_generic_remove(cmd, 0, 0); + if (remove) + transport_put_cmd(cmd); } -static int transport_add_cmd_to_queue( - struct se_cmd *cmd, - int t_state) +static void target_complete_failure_work(struct work_struct *work) { - struct se_device *dev = cmd->se_dev; - struct se_queue_obj *qobj = dev->dev_queue_obj; - struct se_queue_req *qr; - unsigned long flags; - - qr = kzalloc(sizeof(struct se_queue_req), GFP_ATOMIC); - if (!(qr)) { - printk(KERN_ERR "Unable to allocate memory for" - " struct se_queue_req\n"); - return -1; - } - INIT_LIST_HEAD(&qr->qr_list); - - qr->cmd = (void *)cmd; - qr->state = t_state; + struct se_cmd *cmd = container_of(work, struct se_cmd, work); - if (t_state) { - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - cmd->t_state = t_state; - atomic_set(&T_TASK(cmd)->t_transport_active, 1); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - } - - spin_lock_irqsave(&qobj->cmd_queue_lock, flags); - list_add_tail(&qr->qr_list, &qobj->qobj_list); - atomic_inc(&T_TASK(cmd)->t_transport_queue_active); - spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); - - atomic_inc(&qobj->queue_cnt); - wake_up_interruptible(&qobj->thread_wq); - return 0; + transport_generic_request_failure(cmd, + TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE); } /* - * Called with struct se_queue_obj->cmd_queue_lock held. + * Used when asking transport to copy Sense Data from the underlying + * Linux/SCSI struct scsi_cmnd */ -static struct se_queue_req * -__transport_get_qr_from_queue(struct se_queue_obj *qobj) +static unsigned char *transport_get_sense_buffer(struct se_cmd *cmd) { - struct se_cmd *cmd; - struct se_queue_req *qr = NULL; - - if (list_empty(&qobj->qobj_list)) - return NULL; - - list_for_each_entry(qr, &qobj->qobj_list, qr_list) - break; + struct se_device *dev = cmd->se_dev; - if (qr->cmd) { - cmd = (struct se_cmd *)qr->cmd; - atomic_dec(&T_TASK(cmd)->t_transport_queue_active); - } - list_del(&qr->qr_list); - atomic_dec(&qobj->queue_cnt); + WARN_ON(!cmd->se_lun); - return qr; -} - -static struct se_queue_req * -transport_get_qr_from_queue(struct se_queue_obj *qobj) -{ - struct se_cmd *cmd; - struct se_queue_req *qr; - unsigned long flags; - - spin_lock_irqsave(&qobj->cmd_queue_lock, flags); - if (list_empty(&qobj->qobj_list)) { - spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); + if (!dev) return NULL; - } - list_for_each_entry(qr, &qobj->qobj_list, qr_list) - break; + if (cmd->se_cmd_flags & SCF_SENT_CHECK_CONDITION) + return NULL; - if (qr->cmd) { - cmd = (struct se_cmd *)qr->cmd; - atomic_dec(&T_TASK(cmd)->t_transport_queue_active); - } - list_del(&qr->qr_list); - atomic_dec(&qobj->queue_cnt); - spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); + cmd->scsi_sense_length = TRANSPORT_SENSE_BUFFER; - return qr; + pr_debug("HBA_[%u]_PLUG[%s]: Requesting sense for SAM STATUS: 0x%02x\n", + dev->se_hba->hba_id, dev->transport->name, cmd->scsi_status); + return cmd->sense_buffer; } -static void transport_remove_cmd_from_queue(struct se_cmd *cmd, - struct se_queue_obj *qobj) +void target_complete_cmd(struct se_cmd *cmd, u8 scsi_status) { - struct se_cmd *q_cmd; - struct se_queue_req *qr = NULL, *qr_p = NULL; + struct se_device *dev = cmd->se_dev; + int success = scsi_status == GOOD; unsigned long flags; - spin_lock_irqsave(&qobj->cmd_queue_lock, flags); - if (!(atomic_read(&T_TASK(cmd)->t_transport_queue_active))) { - spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); - return; - } - - list_for_each_entry_safe(qr, qr_p, &qobj->qobj_list, qr_list) { - q_cmd = (struct se_cmd *)qr->cmd; - if (q_cmd != cmd) - continue; - - atomic_dec(&T_TASK(q_cmd)->t_transport_queue_active); - atomic_dec(&qobj->queue_cnt); - list_del(&qr->qr_list); - kfree(qr); - } - spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); - - if (atomic_read(&T_TASK(cmd)->t_transport_queue_active)) { - printk(KERN_ERR "ITT: 0x%08x t_transport_queue_active: %d\n", - CMD_TFO(cmd)->get_task_tag(cmd), - atomic_read(&T_TASK(cmd)->t_transport_queue_active)); - } -} - -/* - * Completion function used by TCM subsystem plugins (such as FILEIO) - * for queueing up response from struct se_subsystem_api->do_task() - */ -void transport_complete_sync_cache(struct se_cmd *cmd, int good) -{ - struct se_task *task = list_entry(T_TASK(cmd)->t_task_list.next, - struct se_task, t_list); - - if (good) { - cmd->scsi_status = SAM_STAT_GOOD; - task->task_scsi_status = GOOD; - } else { - task->task_scsi_status = SAM_STAT_CHECK_CONDITION; - task->task_error_status = PYX_TRANSPORT_ILLEGAL_REQUEST; - TASK_CMD(task)->transport_error_status = - PYX_TRANSPORT_ILLEGAL_REQUEST; - } + cmd->scsi_status = scsi_status; - transport_complete_task(task, good); -} -EXPORT_SYMBOL(transport_complete_sync_cache); -/* transport_complete_task(): - * - * Called from interrupt and non interrupt context depending - * on the transport plugin. - */ -void transport_complete_task(struct se_task *task, int success) -{ - struct se_cmd *cmd = TASK_CMD(task); - struct se_device *dev = task->se_dev; - int t_state; - unsigned long flags; -#if 0 - printk(KERN_INFO "task: %p CDB: 0x%02x obj_ptr: %p\n", task, - T_TASK(cmd)->t_task_cdb[0], dev); -#endif - if (dev) { - spin_lock_irqsave(&SE_HBA(dev)->hba_queue_lock, flags); - atomic_inc(&dev->depth_left); - atomic_inc(&SE_HBA(dev)->left_queue_depth); - spin_unlock_irqrestore(&SE_HBA(dev)->hba_queue_lock, flags); - } - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - atomic_set(&task->task_active, 0); + spin_lock_irqsave(&cmd->t_state_lock, flags); + cmd->transport_state &= ~CMD_T_BUSY; - /* - * See if any sense data exists, if so set the TASK_SENSE flag. - * Also check for any other post completion work that needs to be - * done by the plugins. - */ if (dev && dev->transport->transport_complete) { - if (dev->transport->transport_complete(task) != 0) { - cmd->se_cmd_flags |= SCF_TRANSPORT_TASK_SENSE; - task->task_sense = 1; + dev->transport->transport_complete(cmd, + cmd->t_data_sg, + transport_get_sense_buffer(cmd)); + if (cmd->se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) success = 1; - } } /* - * See if we are waiting for outstanding struct se_task - * to complete for an exception condition + * See if we are waiting to complete for an exception condition. */ - if (atomic_read(&task->task_stop)) { - /* - * Decrement T_TASK(cmd)->t_se_count if this task had - * previously thrown its timeout exception handler. - */ - if (atomic_read(&task->task_timeout)) { - atomic_dec(&T_TASK(cmd)->t_se_count); - atomic_set(&task->task_timeout, 0); - } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - complete(&task->task_stop_comp); - return; - } - /* - * If the task's timeout handler has fired, use the t_task_cdbs_timeout - * left counter to determine when the struct se_cmd is ready to be queued to - * the processing thread. - */ - if (atomic_read(&task->task_timeout)) { - if (!(atomic_dec_and_test( - &T_TASK(cmd)->t_task_cdbs_timeout_left))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, - flags); - return; - } - t_state = TRANSPORT_COMPLETE_TIMEOUT; - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - transport_add_cmd_to_queue(cmd, t_state); + if (cmd->transport_state & CMD_T_REQUEST_STOP) { + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + complete(&cmd->task_stop_comp); return; } - atomic_dec(&T_TASK(cmd)->t_task_cdbs_timeout_left); /* - * Decrement the outstanding t_task_cdbs_left count. The last - * struct se_task from struct se_cmd will complete itself into the - * device queue depending upon int success. + * Check for case where an explicit ABORT_TASK has been received + * and transport_wait_for_tasks() will be waiting for completion.. */ - if (!(atomic_dec_and_test(&T_TASK(cmd)->t_task_cdbs_left))) { - if (!success) - T_TASK(cmd)->t_tasks_failed = 1; - - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); + if (cmd->transport_state & CMD_T_ABORTED && + cmd->transport_state & CMD_T_STOP) { + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + complete_all(&cmd->t_transport_stop_comp); return; - } - - if (!success || T_TASK(cmd)->t_tasks_failed) { - t_state = TRANSPORT_COMPLETE_FAILURE; - if (!task->task_error_status) { - task->task_error_status = - PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - cmd->transport_error_status = - PYX_TRANSPORT_UNKNOWN_SAM_OPCODE; - } + } else if (!success) { + INIT_WORK(&cmd->work, target_complete_failure_work); } else { - atomic_set(&T_TASK(cmd)->t_transport_complete, 1); - t_state = TRANSPORT_COMPLETE_OK; - } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - transport_add_cmd_to_queue(cmd, t_state); -} -EXPORT_SYMBOL(transport_complete_task); - -/* - * Called by transport_add_tasks_from_cmd() once a struct se_cmd's - * struct se_task list are ready to be added to the active execution list - * struct se_device - - * Called with se_dev_t->execute_task_lock called. - */ -static inline int transport_add_task_check_sam_attr( - struct se_task *task, - struct se_task *task_prev, - struct se_device *dev) -{ - /* - * No SAM Task attribute emulation enabled, add to tail of - * execution queue - */ - if (dev->dev_task_attr_type != SAM_TASK_ATTR_EMULATED) { - list_add_tail(&task->t_execute_list, &dev->execute_task_list); - return 0; + INIT_WORK(&cmd->work, target_complete_ok_work); } - /* - * HEAD_OF_QUEUE attribute for received CDB, which means - * the first task that is associated with a struct se_cmd goes to - * head of the struct se_device->execute_task_list, and task_prev - * after that for each subsequent task - */ - if (task->task_se_cmd->sam_task_attr == TASK_ATTR_HOQ) { - list_add(&task->t_execute_list, - (task_prev != NULL) ? - &task_prev->t_execute_list : - &dev->execute_task_list); - - DEBUG_STA("Set HEAD_OF_QUEUE for task CDB: 0x%02x" - " in execution queue\n", - T_TASK(task->task_se_cmd)->t_task_cdb[0]); - return 1; - } - /* - * For ORDERED, SIMPLE or UNTAGGED attribute tasks once they have been - * transitioned from Dermant -> Active state, and are added to the end - * of the struct se_device->execute_task_list - */ - list_add_tail(&task->t_execute_list, &dev->execute_task_list); - return 0; -} -/* __transport_add_task_to_execute_queue(): - * - * Called with se_dev_t->execute_task_lock called. - */ -static void __transport_add_task_to_execute_queue( - struct se_task *task, - struct se_task *task_prev, - struct se_device *dev) -{ - int head_of_queue; - - head_of_queue = transport_add_task_check_sam_attr(task, task_prev, dev); - atomic_inc(&dev->execute_tasks); + cmd->t_state = TRANSPORT_COMPLETE; + cmd->transport_state |= (CMD_T_COMPLETE | CMD_T_ACTIVE); + spin_unlock_irqrestore(&cmd->t_state_lock, flags); - if (atomic_read(&task->task_state_active)) - return; - /* - * Determine if this task needs to go to HEAD_OF_QUEUE for the - * state list as well. Running with SAM Task Attribute emulation - * will always return head_of_queue == 0 here - */ - if (head_of_queue) - list_add(&task->t_state_list, (task_prev) ? - &task_prev->t_state_list : - &dev->state_task_list); - else - list_add_tail(&task->t_state_list, &dev->state_task_list); - - atomic_set(&task->task_state_active, 1); - - DEBUG_TSTATE("Added ITT: 0x%08x task[%p] to dev: %p\n", - CMD_TFO(task->task_se_cmd)->get_task_tag(task->task_se_cmd), - task, dev); + queue_work(target_completion_wq, &cmd->work); } +EXPORT_SYMBOL(target_complete_cmd); -static void transport_add_tasks_to_state_queue(struct se_cmd *cmd) +void target_complete_cmd_with_length(struct se_cmd *cmd, u8 scsi_status, int length) { - struct se_device *dev; - struct se_task *task; - unsigned long flags; - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - list_for_each_entry(task, &T_TASK(cmd)->t_task_list, t_list) { - dev = task->se_dev; - - if (atomic_read(&task->task_state_active)) - continue; - - spin_lock(&dev->execute_task_lock); - list_add_tail(&task->t_state_list, &dev->state_task_list); - atomic_set(&task->task_state_active, 1); - - DEBUG_TSTATE("Added ITT: 0x%08x task[%p] to dev: %p\n", - CMD_TFO(task->task_se_cmd)->get_task_tag( - task->task_se_cmd), task, dev); + if (scsi_status == SAM_STAT_GOOD && length < cmd->data_length) { + if (cmd->se_cmd_flags & SCF_UNDERFLOW_BIT) { + cmd->residual_count += cmd->data_length - length; + } else { + cmd->se_cmd_flags |= SCF_UNDERFLOW_BIT; + cmd->residual_count = cmd->data_length - length; + } - spin_unlock(&dev->execute_task_lock); + cmd->data_length = length; } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); + + target_complete_cmd(cmd, scsi_status); } +EXPORT_SYMBOL(target_complete_cmd_with_length); -static void transport_add_tasks_from_cmd(struct se_cmd *cmd) +static void target_add_to_state_list(struct se_cmd *cmd) { - struct se_device *dev = SE_DEV(cmd); - struct se_task *task, *task_prev = NULL; + struct se_device *dev = cmd->se_dev; unsigned long flags; spin_lock_irqsave(&dev->execute_task_lock, flags); - list_for_each_entry(task, &T_TASK(cmd)->t_task_list, t_list) { - if (atomic_read(&task->task_execute_queue)) - continue; - /* - * __transport_add_task_to_execute_queue() handles the - * SAM Task Attribute emulation if enabled - */ - __transport_add_task_to_execute_queue(task, task_prev, dev); - atomic_set(&task->task_execute_queue, 1); - task_prev = task; + if (!cmd->state_active) { + list_add_tail(&cmd->state_list, &dev->state_list); + cmd->state_active = true; } spin_unlock_irqrestore(&dev->execute_task_lock, flags); - - return; } -/* transport_get_task_from_execute_queue(): - * - * Called with dev->execute_task_lock held. +/* + * Handle QUEUE_FULL / -EAGAIN and -ENOMEM status */ -static struct se_task * -transport_get_task_from_execute_queue(struct se_device *dev) +static void transport_write_pending_qf(struct se_cmd *cmd); +static void transport_complete_qf(struct se_cmd *cmd); + +void target_qf_do_work(struct work_struct *work) { - struct se_task *task; + struct se_device *dev = container_of(work, struct se_device, + qf_work_queue); + LIST_HEAD(qf_cmd_list); + struct se_cmd *cmd, *cmd_tmp; - if (list_empty(&dev->execute_task_list)) - return NULL; + spin_lock_irq(&dev->qf_cmd_lock); + list_splice_init(&dev->qf_cmd_list, &qf_cmd_list); + spin_unlock_irq(&dev->qf_cmd_lock); - list_for_each_entry(task, &dev->execute_task_list, t_execute_list) - break; + list_for_each_entry_safe(cmd, cmd_tmp, &qf_cmd_list, se_qf_node) { + list_del(&cmd->se_qf_node); + atomic_dec(&dev->dev_qf_count); + smp_mb__after_atomic(); - list_del(&task->t_execute_list); - atomic_dec(&dev->execute_tasks); + pr_debug("Processing %s cmd: %p QUEUE_FULL in work queue" + " context: %s\n", cmd->se_tfo->get_fabric_name(), cmd, + (cmd->t_state == TRANSPORT_COMPLETE_QF_OK) ? "COMPLETE_OK" : + (cmd->t_state == TRANSPORT_COMPLETE_QF_WP) ? "WRITE_PENDING" + : "UNKNOWN"); - return task; -} - -/* transport_remove_task_from_execute_queue(): - * - * - */ -static void transport_remove_task_from_execute_queue( - struct se_task *task, - struct se_device *dev) -{ - unsigned long flags; - - spin_lock_irqsave(&dev->execute_task_lock, flags); - list_del(&task->t_execute_list); - atomic_dec(&dev->execute_tasks); - spin_unlock_irqrestore(&dev->execute_task_lock, flags); + if (cmd->t_state == TRANSPORT_COMPLETE_QF_WP) + transport_write_pending_qf(cmd); + else if (cmd->t_state == TRANSPORT_COMPLETE_QF_OK) + transport_complete_qf(cmd); + } } unsigned char *transport_dump_cmd_direction(struct se_cmd *cmd) @@ -1243,72 +792,18 @@ void transport_dump_dev_state( int *bl) { *bl += sprintf(b + *bl, "Status: "); - switch (dev->dev_status) { - case TRANSPORT_DEVICE_ACTIVATED: + if (dev->export_count) *bl += sprintf(b + *bl, "ACTIVATED"); - break; - case TRANSPORT_DEVICE_DEACTIVATED: + else *bl += sprintf(b + *bl, "DEACTIVATED"); - break; - case TRANSPORT_DEVICE_SHUTDOWN: - *bl += sprintf(b + *bl, "SHUTDOWN"); - break; - case TRANSPORT_DEVICE_OFFLINE_ACTIVATED: - case TRANSPORT_DEVICE_OFFLINE_DEACTIVATED: - *bl += sprintf(b + *bl, "OFFLINE"); - break; - default: - *bl += sprintf(b + *bl, "UNKNOWN=%d", dev->dev_status); - break; - } - *bl += sprintf(b + *bl, " Execute/Left/Max Queue Depth: %d/%d/%d", - atomic_read(&dev->execute_tasks), atomic_read(&dev->depth_left), - dev->queue_depth); - *bl += sprintf(b + *bl, " SectorSize: %u MaxSectors: %u\n", - DEV_ATTRIB(dev)->block_size, DEV_ATTRIB(dev)->max_sectors); + *bl += sprintf(b + *bl, " Max Queue Depth: %d", dev->queue_depth); + *bl += sprintf(b + *bl, " SectorSize: %u HwMaxSectors: %u\n", + dev->dev_attrib.block_size, + dev->dev_attrib.hw_max_sectors); *bl += sprintf(b + *bl, " "); } -/* transport_release_all_cmds(): - * - * - */ -static void transport_release_all_cmds(struct se_device *dev) -{ - struct se_cmd *cmd = NULL; - struct se_queue_req *qr = NULL, *qr_p = NULL; - int bug_out = 0, t_state; - unsigned long flags; - - spin_lock_irqsave(&dev->dev_queue_obj->cmd_queue_lock, flags); - list_for_each_entry_safe(qr, qr_p, &dev->dev_queue_obj->qobj_list, - qr_list) { - - cmd = (struct se_cmd *)qr->cmd; - t_state = qr->state; - list_del(&qr->qr_list); - kfree(qr); - spin_unlock_irqrestore(&dev->dev_queue_obj->cmd_queue_lock, - flags); - - printk(KERN_ERR "Releasing ITT: 0x%08x, i_state: %u," - " t_state: %u directly\n", - CMD_TFO(cmd)->get_task_tag(cmd), - CMD_TFO(cmd)->get_cmd_state(cmd), t_state); - - transport_release_fe_cmd(cmd); - bug_out = 1; - - spin_lock_irqsave(&dev->dev_queue_obj->cmd_queue_lock, flags); - } - spin_unlock_irqrestore(&dev->dev_queue_obj->cmd_queue_lock, flags); -#if 0 - if (bug_out) - BUG(); -#endif -} - void transport_dump_vpd_proto_id( struct t10_vpd *vpd, unsigned char *p_buf, @@ -1359,7 +854,7 @@ void transport_dump_vpd_proto_id( if (p_buf) strncpy(p_buf, buf, p_buf_len); else - printk(KERN_INFO "%s", buf); + pr_debug("%s", buf); } void @@ -1384,7 +879,8 @@ int transport_dump_vpd_assoc( int p_buf_len) { unsigned char buf[VPD_TMP_BUF_SIZE]; - int ret = 0, len; + int ret = 0; + int len; memset(buf, 0, VPD_TMP_BUF_SIZE); len = sprintf(buf, "T10 VPD Identifier Association: "); @@ -1401,14 +897,14 @@ int transport_dump_vpd_assoc( break; default: sprintf(buf+len, "Unknown 0x%02x\n", vpd->association); - ret = -1; + ret = -EINVAL; break; } if (p_buf) strncpy(p_buf, buf, p_buf_len); else - printk("%s", buf); + pr_debug("%s", buf); return ret; } @@ -1431,7 +927,8 @@ int transport_dump_vpd_ident_type( int p_buf_len) { unsigned char buf[VPD_TMP_BUF_SIZE]; - int ret = 0, len; + int ret = 0; + int len; memset(buf, 0, VPD_TMP_BUF_SIZE); len = sprintf(buf, "T10 VPD Identifier Type: "); @@ -1458,14 +955,17 @@ int transport_dump_vpd_ident_type( default: sprintf(buf+len, "Unsupported: 0x%02x\n", vpd->device_identifier_type); - ret = -1; + ret = -EINVAL; break; } - if (p_buf) + if (p_buf) { + if (p_buf_len < strlen(buf)+1) + return -EINVAL; strncpy(p_buf, buf, p_buf_len); - else - printk("%s", buf); + } else { + pr_debug("%s", buf); + } return ret; } @@ -1494,28 +994,31 @@ int transport_dump_vpd_ident( switch (vpd->device_identifier_code_set) { case 0x01: /* Binary */ - sprintf(buf, "T10 VPD Binary Device Identifier: %s\n", + snprintf(buf, sizeof(buf), + "T10 VPD Binary Device Identifier: %s\n", &vpd->device_identifier[0]); break; case 0x02: /* ASCII */ - sprintf(buf, "T10 VPD ASCII Device Identifier: %s\n", + snprintf(buf, sizeof(buf), + "T10 VPD ASCII Device Identifier: %s\n", &vpd->device_identifier[0]); break; case 0x03: /* UTF-8 */ - sprintf(buf, "T10 VPD UTF-8 Device Identifier: %s\n", + snprintf(buf, sizeof(buf), + "T10 VPD UTF-8 Device Identifier: %s\n", &vpd->device_identifier[0]); break; default: sprintf(buf, "T10 VPD Device Identifier encoding unsupported:" " 0x%02x", vpd->device_identifier_code_set); - ret = -1; + ret = -EINVAL; break; } if (p_buf) strncpy(p_buf, buf, p_buf_len); else - printk("%s", buf); + pr_debug("%s", buf); return ret; } @@ -1524,7 +1027,7 @@ int transport_set_vpd_ident(struct t10_vpd *vpd, unsigned char *page_83) { static const char hex_str[] = "0123456789abcdef"; - int j = 0, i = 4; /* offset to start of the identifer */ + int j = 0, i = 4; /* offset to start of the identifier */ /* * The VPD Code Set (encoding) @@ -1557,273 +1060,54 @@ transport_set_vpd_ident(struct t10_vpd *vpd, unsigned char *page_83) } EXPORT_SYMBOL(transport_set_vpd_ident); -static void core_setup_task_attr_emulation(struct se_device *dev) +sense_reason_t +target_cmd_size_check(struct se_cmd *cmd, unsigned int size) { - /* - * If this device is from Target_Core_Mod/pSCSI, disable the - * SAM Task Attribute emulation. - * - * This is currently not available in upsream Linux/SCSI Target - * mode code, and is assumed to be disabled while using TCM/pSCSI. - */ - if (TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) { - dev->dev_task_attr_type = SAM_TASK_ATTR_PASSTHROUGH; - return; - } - - dev->dev_task_attr_type = SAM_TASK_ATTR_EMULATED; - DEBUG_STA("%s: Using SAM_TASK_ATTR_EMULATED for SPC: 0x%02x" - " device\n", TRANSPORT(dev)->name, - TRANSPORT(dev)->get_device_rev(dev)); -} - -static void scsi_dump_inquiry(struct se_device *dev) -{ - struct t10_wwn *wwn = DEV_T10_WWN(dev); - int i, device_type; - /* - * Print Linux/SCSI style INQUIRY formatting to the kernel ring buffer - */ - printk(" Vendor: "); - for (i = 0; i < 8; i++) - if (wwn->vendor[i] >= 0x20) - printk("%c", wwn->vendor[i]); - else - printk(" "); - - printk(" Model: "); - for (i = 0; i < 16; i++) - if (wwn->model[i] >= 0x20) - printk("%c", wwn->model[i]); - else - printk(" "); - - printk(" Revision: "); - for (i = 0; i < 4; i++) - if (wwn->revision[i] >= 0x20) - printk("%c", wwn->revision[i]); - else - printk(" "); - - printk("\n"); - - device_type = TRANSPORT(dev)->get_device_type(dev); - printk(" Type: %s ", scsi_device_type(device_type)); - printk(" ANSI SCSI revision: %02x\n", - TRANSPORT(dev)->get_device_rev(dev)); -} - -struct se_device *transport_add_device_to_core_hba( - struct se_hba *hba, - struct se_subsystem_api *transport, - struct se_subsystem_dev *se_dev, - u32 device_flags, - void *transport_dev, - struct se_dev_limits *dev_limits, - const char *inquiry_prod, - const char *inquiry_rev) -{ - int ret = 0, force_pt; - struct se_device *dev; - - dev = kzalloc(sizeof(struct se_device), GFP_KERNEL); - if (!(dev)) { - printk(KERN_ERR "Unable to allocate memory for se_dev_t\n"); - return NULL; - } - dev->dev_queue_obj = kzalloc(sizeof(struct se_queue_obj), GFP_KERNEL); - if (!(dev->dev_queue_obj)) { - printk(KERN_ERR "Unable to allocate memory for" - " dev->dev_queue_obj\n"); - kfree(dev); - return NULL; - } - transport_init_queue_obj(dev->dev_queue_obj); - - dev->dev_status_queue_obj = kzalloc(sizeof(struct se_queue_obj), - GFP_KERNEL); - if (!(dev->dev_status_queue_obj)) { - printk(KERN_ERR "Unable to allocate memory for" - " dev->dev_status_queue_obj\n"); - kfree(dev->dev_queue_obj); - kfree(dev); - return NULL; - } - transport_init_queue_obj(dev->dev_status_queue_obj); - - dev->dev_flags = device_flags; - dev->dev_status |= TRANSPORT_DEVICE_DEACTIVATED; - dev->dev_ptr = (void *) transport_dev; - dev->se_hba = hba; - dev->se_sub_dev = se_dev; - dev->transport = transport; - atomic_set(&dev->active_cmds, 0); - INIT_LIST_HEAD(&dev->dev_list); - INIT_LIST_HEAD(&dev->dev_sep_list); - INIT_LIST_HEAD(&dev->dev_tmr_list); - INIT_LIST_HEAD(&dev->execute_task_list); - INIT_LIST_HEAD(&dev->delayed_cmd_list); - INIT_LIST_HEAD(&dev->ordered_cmd_list); - INIT_LIST_HEAD(&dev->state_task_list); - spin_lock_init(&dev->execute_task_lock); - spin_lock_init(&dev->delayed_cmd_lock); - spin_lock_init(&dev->ordered_cmd_lock); - spin_lock_init(&dev->state_task_lock); - spin_lock_init(&dev->dev_alua_lock); - spin_lock_init(&dev->dev_reservation_lock); - spin_lock_init(&dev->dev_status_lock); - spin_lock_init(&dev->dev_status_thr_lock); - spin_lock_init(&dev->se_port_lock); - spin_lock_init(&dev->se_tmr_lock); - - dev->queue_depth = dev_limits->queue_depth; - atomic_set(&dev->depth_left, dev->queue_depth); - atomic_set(&dev->dev_ordered_id, 0); - - se_dev_set_default_attribs(dev, dev_limits); - - dev->dev_index = scsi_get_new_index(SCSI_DEVICE_INDEX); - dev->creation_time = get_jiffies_64(); - spin_lock_init(&dev->stats_lock); - - spin_lock(&hba->device_lock); - list_add_tail(&dev->dev_list, &hba->hba_dev_list); - hba->dev_count++; - spin_unlock(&hba->device_lock); - /* - * Setup the SAM Task Attribute emulation for struct se_device - */ - core_setup_task_attr_emulation(dev); - /* - * Force PR and ALUA passthrough emulation with internal object use. - */ - force_pt = (hba->hba_flags & HBA_FLAGS_INTERNAL_USE); - /* - * Setup the Reservations infrastructure for struct se_device - */ - core_setup_reservations(dev, force_pt); - /* - * Setup the Asymmetric Logical Unit Assignment for struct se_device - */ - if (core_setup_alua(dev, force_pt) < 0) - goto out; + struct se_device *dev = cmd->se_dev; - /* - * Startup the struct se_device processing thread - */ - dev->process_thread = kthread_run(transport_processing_thread, dev, - "LIO_%s", TRANSPORT(dev)->name); - if (IS_ERR(dev->process_thread)) { - printk(KERN_ERR "Unable to create kthread: LIO_%s\n", - TRANSPORT(dev)->name); - goto out; - } + if (cmd->unknown_data_length) { + cmd->data_length = size; + } else if (size != cmd->data_length) { + pr_warn("TARGET_CORE[%s]: Expected Transfer Length:" + " %u does not match SCSI CDB Length: %u for SAM Opcode:" + " 0x%02x\n", cmd->se_tfo->get_fabric_name(), + cmd->data_length, size, cmd->t_task_cdb[0]); - /* - * Preload the initial INQUIRY const values if we are doing - * anything virtual (IBLOCK, FILEIO, RAMDISK), but not for TCM/pSCSI - * passthrough because this is being provided by the backend LLD. - * This is required so that transport_get_inquiry() copies these - * originals once back into DEV_T10_WWN(dev) for the virtual device - * setup. - */ - if (TRANSPORT(dev)->transport_type != TRANSPORT_PLUGIN_PHBA_PDEV) { - if (!(inquiry_prod) || !(inquiry_prod)) { - printk(KERN_ERR "All non TCM/pSCSI plugins require" - " INQUIRY consts\n"); - goto out; + if (cmd->data_direction == DMA_TO_DEVICE) { + pr_err("Rejecting underflow/overflow" + " WRITE data\n"); + return TCM_INVALID_CDB_FIELD; + } + /* + * Reject READ_* or WRITE_* with overflow/underflow for + * type SCF_SCSI_DATA_CDB. + */ + if (dev->dev_attrib.block_size != 512) { + pr_err("Failing OVERFLOW/UNDERFLOW for LBA op" + " CDB on non 512-byte sector setup subsystem" + " plugin: %s\n", dev->transport->name); + /* Returns CHECK_CONDITION + INVALID_CDB_FIELD */ + return TCM_INVALID_CDB_FIELD; + } + /* + * For the overflow case keep the existing fabric provided + * ->data_length. Otherwise for the underflow case, reset + * ->data_length to the smaller SCSI expected data transfer + * length. + */ + if (size > cmd->data_length) { + cmd->se_cmd_flags |= SCF_OVERFLOW_BIT; + cmd->residual_count = (size - cmd->data_length); + } else { + cmd->se_cmd_flags |= SCF_UNDERFLOW_BIT; + cmd->residual_count = (cmd->data_length - size); + cmd->data_length = size; } - - strncpy(&DEV_T10_WWN(dev)->vendor[0], "LIO-ORG", 8); - strncpy(&DEV_T10_WWN(dev)->model[0], inquiry_prod, 16); - strncpy(&DEV_T10_WWN(dev)->revision[0], inquiry_rev, 4); - } - scsi_dump_inquiry(dev); - -out: - if (!ret) - return dev; - kthread_stop(dev->process_thread); - - spin_lock(&hba->device_lock); - list_del(&dev->dev_list); - hba->dev_count--; - spin_unlock(&hba->device_lock); - - se_release_vpd_for_dev(dev); - - kfree(dev->dev_status_queue_obj); - kfree(dev->dev_queue_obj); - kfree(dev); - - return NULL; -} -EXPORT_SYMBOL(transport_add_device_to_core_hba); - -/* transport_generic_prepare_cdb(): - * - * Since the Initiator sees iSCSI devices as LUNs, the SCSI CDB will - * contain the iSCSI LUN in bits 7-5 of byte 1 as per SAM-2. - * The point of this is since we are mapping iSCSI LUNs to - * SCSI Target IDs having a non-zero LUN in the CDB will throw the - * devices and HBAs for a loop. - */ -static inline void transport_generic_prepare_cdb( - unsigned char *cdb) -{ - switch (cdb[0]) { - case READ_10: /* SBC - RDProtect */ - case READ_12: /* SBC - RDProtect */ - case READ_16: /* SBC - RDProtect */ - case SEND_DIAGNOSTIC: /* SPC - SELF-TEST Code */ - case VERIFY: /* SBC - VRProtect */ - case VERIFY_16: /* SBC - VRProtect */ - case WRITE_VERIFY: /* SBC - VRProtect */ - case WRITE_VERIFY_12: /* SBC - VRProtect */ - break; - default: - cdb[1] &= 0x1f; /* clear logical unit number */ - break; - } -} - -static struct se_task * -transport_generic_get_task(struct se_cmd *cmd, - enum dma_data_direction data_direction) -{ - struct se_task *task; - struct se_device *dev = SE_DEV(cmd); - unsigned long flags; - - task = dev->transport->alloc_task(cmd); - if (!task) { - printk(KERN_ERR "Unable to allocate struct se_task\n"); - return NULL; } - INIT_LIST_HEAD(&task->t_list); - INIT_LIST_HEAD(&task->t_execute_list); - INIT_LIST_HEAD(&task->t_state_list); - init_completion(&task->task_stop_comp); - task->task_no = T_TASK(cmd)->t_tasks_no++; - task->task_se_cmd = cmd; - task->se_dev = dev; - task->task_data_direction = data_direction; - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - list_add_tail(&task->t_list, &T_TASK(cmd)->t_task_list); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - return task; -} - -static int transport_generic_cmd_sequencer(struct se_cmd *, unsigned char *); + return 0; -void transport_device_setup_cmd(struct se_cmd *cmd) -{ - cmd->se_dev = SE_LUN(cmd)->lun_se_dev; } -EXPORT_SYMBOL(transport_device_setup_cmd); /* * Used by fabric modules containing a local struct se_cmd within their @@ -1838,20 +1122,16 @@ void transport_init_se_cmd( int task_attr, unsigned char *sense_buffer) { - INIT_LIST_HEAD(&cmd->se_lun_list); - INIT_LIST_HEAD(&cmd->se_delayed_list); - INIT_LIST_HEAD(&cmd->se_ordered_list); - /* - * Setup t_task pointer to t_task_backstore - */ - cmd->t_task = &cmd->t_task_backstore; - - INIT_LIST_HEAD(&T_TASK(cmd)->t_task_list); - init_completion(&T_TASK(cmd)->transport_lun_fe_stop_comp); - init_completion(&T_TASK(cmd)->transport_lun_stop_comp); - init_completion(&T_TASK(cmd)->t_transport_stop_comp); - spin_lock_init(&T_TASK(cmd)->t_state_lock); - atomic_set(&T_TASK(cmd)->transport_dev_active, 1); + INIT_LIST_HEAD(&cmd->se_delayed_node); + INIT_LIST_HEAD(&cmd->se_qf_node); + INIT_LIST_HEAD(&cmd->se_cmd_list); + INIT_LIST_HEAD(&cmd->state_list); + init_completion(&cmd->t_transport_stop_comp); + init_completion(&cmd->cmd_wait_comp); + init_completion(&cmd->task_stop_comp); + spin_lock_init(&cmd->t_state_lock); + kref_init(&cmd->cmd_kref); + cmd->transport_state = CMD_T_DEV_ACTIVE; cmd->se_tfo = tfo; cmd->se_sess = se_sess; @@ -1859,2067 +1139,919 @@ void transport_init_se_cmd( cmd->data_direction = data_direction; cmd->sam_task_attr = task_attr; cmd->sense_buffer = sense_buffer; + + cmd->state_active = false; } EXPORT_SYMBOL(transport_init_se_cmd); -static int transport_check_alloc_task_attr(struct se_cmd *cmd) +static sense_reason_t +transport_check_alloc_task_attr(struct se_cmd *cmd) { + struct se_device *dev = cmd->se_dev; + /* * Check if SAM Task Attribute emulation is enabled for this * struct se_device storage object */ - if (SE_DEV(cmd)->dev_task_attr_type != SAM_TASK_ATTR_EMULATED) + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) return 0; - if (cmd->sam_task_attr == TASK_ATTR_ACA) { - DEBUG_STA("SAM Task Attribute ACA" + if (cmd->sam_task_attr == MSG_ACA_TAG) { + pr_debug("SAM Task Attribute ACA" " emulation is not supported\n"); - return -1; + return TCM_INVALID_CDB_FIELD; } /* * Used to determine when ORDERED commands should go from * Dormant to Active status. */ - cmd->se_ordered_id = atomic_inc_return(&SE_DEV(cmd)->dev_ordered_id); - smp_mb__after_atomic_inc(); - DEBUG_STA("Allocated se_ordered_id: %u for Task Attr: 0x%02x on %s\n", + cmd->se_ordered_id = atomic_inc_return(&dev->dev_ordered_id); + smp_mb__after_atomic(); + pr_debug("Allocated se_ordered_id: %u for Task Attr: 0x%02x on %s\n", cmd->se_ordered_id, cmd->sam_task_attr, - TRANSPORT(cmd->se_dev)->name); + dev->transport->name); return 0; } -void transport_free_se_cmd( - struct se_cmd *se_cmd) -{ - if (se_cmd->se_tmr_req) - core_tmr_release_req(se_cmd->se_tmr_req); - /* - * Check and free any extended CDB buffer that was allocated - */ - if (T_TASK(se_cmd)->t_task_cdb != T_TASK(se_cmd)->__t_task_cdb) - kfree(T_TASK(se_cmd)->t_task_cdb); -} -EXPORT_SYMBOL(transport_free_se_cmd); - -static void transport_generic_wait_for_tasks(struct se_cmd *, int, int); - -/* transport_generic_allocate_tasks(): - * - * Called from fabric RX Thread. - */ -int transport_generic_allocate_tasks( - struct se_cmd *cmd, - unsigned char *cdb) +sense_reason_t +target_setup_cmd_from_cdb(struct se_cmd *cmd, unsigned char *cdb) { - int ret; - - transport_generic_prepare_cdb(cdb); - - /* - * This is needed for early exceptions. - */ - cmd->transport_wait_for_tasks = &transport_generic_wait_for_tasks; + struct se_device *dev = cmd->se_dev; + sense_reason_t ret; - transport_device_setup_cmd(cmd); /* * Ensure that the received CDB is less than the max (252 + 8) bytes * for VARIABLE_LENGTH_CMD */ if (scsi_command_size(cdb) > SCSI_MAX_VARLEN_CDB_SIZE) { - printk(KERN_ERR "Received SCSI CDB with command_size: %d that" + pr_err("Received SCSI CDB with command_size: %d that" " exceeds SCSI_MAX_VARLEN_CDB_SIZE: %d\n", scsi_command_size(cdb), SCSI_MAX_VARLEN_CDB_SIZE); - return -1; + return TCM_INVALID_CDB_FIELD; } /* * If the received CDB is larger than TCM_MAX_COMMAND_SIZE, * allocate the additional extended CDB buffer now.. Otherwise * setup the pointer from __t_task_cdb to t_task_cdb. */ - if (scsi_command_size(cdb) > sizeof(T_TASK(cmd)->__t_task_cdb)) { - T_TASK(cmd)->t_task_cdb = kzalloc(scsi_command_size(cdb), + if (scsi_command_size(cdb) > sizeof(cmd->__t_task_cdb)) { + cmd->t_task_cdb = kzalloc(scsi_command_size(cdb), GFP_KERNEL); - if (!(T_TASK(cmd)->t_task_cdb)) { - printk(KERN_ERR "Unable to allocate T_TASK(cmd)->t_task_cdb" - " %u > sizeof(T_TASK(cmd)->__t_task_cdb): %lu ops\n", + if (!cmd->t_task_cdb) { + pr_err("Unable to allocate cmd->t_task_cdb" + " %u > sizeof(cmd->__t_task_cdb): %lu ops\n", scsi_command_size(cdb), - (unsigned long)sizeof(T_TASK(cmd)->__t_task_cdb)); - return -1; + (unsigned long)sizeof(cmd->__t_task_cdb)); + return TCM_OUT_OF_RESOURCES; } } else - T_TASK(cmd)->t_task_cdb = &T_TASK(cmd)->__t_task_cdb[0]; + cmd->t_task_cdb = &cmd->__t_task_cdb[0]; /* - * Copy the original CDB into T_TASK(cmd). + * Copy the original CDB into cmd-> */ - memcpy(T_TASK(cmd)->t_task_cdb, cdb, scsi_command_size(cdb)); + memcpy(cmd->t_task_cdb, cdb, scsi_command_size(cdb)); + + trace_target_sequencer_start(cmd); + /* - * Setup the received CDB based on SCSI defined opcodes and - * perform unit attention, persistent reservations and ALUA - * checks for virtual device backends. The T_TASK(cmd)->t_task_cdb - * pointer is expected to be setup before we reach this point. + * Check for an existing UNIT ATTENTION condition */ - ret = transport_generic_cmd_sequencer(cmd, cdb); - if (ret < 0) + ret = target_scsi3_ua_check(cmd); + if (ret) + return ret; + + ret = target_alua_state_check(cmd); + if (ret) + return ret; + + ret = target_check_reservation(cmd); + if (ret) { + cmd->scsi_status = SAM_STAT_RESERVATION_CONFLICT; return ret; - /* - * Check for SAM Task Attribute Emulation - */ - if (transport_check_alloc_task_attr(cmd) < 0) { - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD; - return -2; } + + ret = dev->transport->parse_cdb(cmd); + if (ret) + return ret; + + ret = transport_check_alloc_task_attr(cmd); + if (ret) + return ret; + + cmd->se_cmd_flags |= SCF_SUPPORTED_SAM_OPCODE; + spin_lock(&cmd->se_lun->lun_sep_lock); if (cmd->se_lun->lun_sep) cmd->se_lun->lun_sep->sep_stats.cmd_pdus++; spin_unlock(&cmd->se_lun->lun_sep_lock); return 0; } -EXPORT_SYMBOL(transport_generic_allocate_tasks); +EXPORT_SYMBOL(target_setup_cmd_from_cdb); /* - * Used by fabric module frontends not defining a TFO->new_cmd_map() - * to queue up a newly setup se_cmd w/ TRANSPORT_NEW_CMD statis + * Used by fabric module frontends to queue tasks directly. + * Many only be used from process context only */ -int transport_generic_handle_cdb( +int transport_handle_cdb_direct( struct se_cmd *cmd) { - if (!SE_LUN(cmd)) { + sense_reason_t ret; + + if (!cmd->se_lun) { + dump_stack(); + pr_err("cmd->se_lun is NULL\n"); + return -EINVAL; + } + if (in_interrupt()) { dump_stack(); - printk(KERN_ERR "SE_LUN(cmd) is NULL\n"); - return -1; + pr_err("transport_generic_handle_cdb cannot be called" + " from interrupt context\n"); + return -EINVAL; } + /* + * Set TRANSPORT_NEW_CMD state and CMD_T_ACTIVE to ensure that + * outstanding descriptors are handled correctly during shutdown via + * transport_wait_for_tasks() + * + * Also, we don't take cmd->t_state_lock here as we only expect + * this to be called for initial descriptor submission. + */ + cmd->t_state = TRANSPORT_NEW_CMD; + cmd->transport_state |= CMD_T_ACTIVE; - transport_add_cmd_to_queue(cmd, TRANSPORT_NEW_CMD); + /* + * transport_generic_new_cmd() is already handling QUEUE_FULL, + * so follow TRANSPORT_NEW_CMD processing thread context usage + * and call transport_generic_request_failure() if necessary.. + */ + ret = transport_generic_new_cmd(cmd); + if (ret) + transport_generic_request_failure(cmd, ret); return 0; } -EXPORT_SYMBOL(transport_generic_handle_cdb); +EXPORT_SYMBOL(transport_handle_cdb_direct); -/* - * Used by fabric module frontends defining a TFO->new_cmd_map() caller - * to queue up a newly setup se_cmd w/ TRANSPORT_NEW_CMD_MAP in order to - * complete setup in TCM process context w/ TFO->new_cmd_map(). - */ -int transport_generic_handle_cdb_map( - struct se_cmd *cmd) +sense_reason_t +transport_generic_map_mem_to_cmd(struct se_cmd *cmd, struct scatterlist *sgl, + u32 sgl_count, struct scatterlist *sgl_bidi, u32 sgl_bidi_count) { - if (!SE_LUN(cmd)) { - dump_stack(); - printk(KERN_ERR "SE_LUN(cmd) is NULL\n"); - return -1; + if (!sgl || !sgl_count) + return 0; + + /* + * Reject SCSI data overflow with map_mem_to_cmd() as incoming + * scatterlists already have been set to follow what the fabric + * passes for the original expected data transfer length. + */ + if (cmd->se_cmd_flags & SCF_OVERFLOW_BIT) { + pr_warn("Rejecting SCSI DATA overflow for fabric using" + " SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC\n"); + return TCM_INVALID_CDB_FIELD; } - transport_add_cmd_to_queue(cmd, TRANSPORT_NEW_CMD_MAP); + cmd->t_data_sg = sgl; + cmd->t_data_nents = sgl_count; + + if (sgl_bidi && sgl_bidi_count) { + cmd->t_bidi_data_sg = sgl_bidi; + cmd->t_bidi_data_nents = sgl_bidi_count; + } + cmd->se_cmd_flags |= SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC; return 0; } -EXPORT_SYMBOL(transport_generic_handle_cdb_map); -/* transport_generic_handle_data(): +/* + * target_submit_cmd_map_sgls - lookup unpacked lun and submit uninitialized + * se_cmd + use pre-allocated SGL memory. + * + * @se_cmd: command descriptor to submit + * @se_sess: associated se_sess for endpoint + * @cdb: pointer to SCSI CDB + * @sense: pointer to SCSI sense buffer + * @unpacked_lun: unpacked LUN to reference for struct se_lun + * @data_length: fabric expected data transfer length + * @task_addr: SAM task attribute + * @data_dir: DMA data direction + * @flags: flags for command submission from target_sc_flags_tables + * @sgl: struct scatterlist memory for unidirectional mapping + * @sgl_count: scatterlist count for unidirectional mapping + * @sgl_bidi: struct scatterlist memory for bidirectional READ mapping + * @sgl_bidi_count: scatterlist count for bidirectional READ mapping + * @sgl_prot: struct scatterlist memory protection information + * @sgl_prot_count: scatterlist count for protection information * + * Returns non zero to signal active I/O shutdown failure. All other + * setup exceptions will be returned as a SCSI CHECK_CONDITION response, + * but still return zero here. * + * This may only be called from process context, and also currently + * assumes internal allocation of fabric payload buffer by target-core. */ -int transport_generic_handle_data( - struct se_cmd *cmd) +int target_submit_cmd_map_sgls(struct se_cmd *se_cmd, struct se_session *se_sess, + unsigned char *cdb, unsigned char *sense, u32 unpacked_lun, + u32 data_length, int task_attr, int data_dir, int flags, + struct scatterlist *sgl, u32 sgl_count, + struct scatterlist *sgl_bidi, u32 sgl_bidi_count, + struct scatterlist *sgl_prot, u32 sgl_prot_count) { + struct se_portal_group *se_tpg; + sense_reason_t rc; + int ret; + + se_tpg = se_sess->se_tpg; + BUG_ON(!se_tpg); + BUG_ON(se_cmd->se_tfo || se_cmd->se_sess); + BUG_ON(in_interrupt()); /* - * For the software fabric case, then we assume the nexus is being - * failed/shutdown when signals are pending from the kthread context - * caller, so we return a failure. For the HW target mode case running - * in interrupt code, the signal_pending() check is skipped. + * Initialize se_cmd for target operation. From this point + * exceptions are handled by sending exception status via + * target_core_fabric_ops->queue_status() callback */ - if (!in_interrupt() && signal_pending(current)) - return -1; + transport_init_se_cmd(se_cmd, se_tpg->se_tpg_tfo, se_sess, + data_length, data_dir, task_attr, sense); + if (flags & TARGET_SCF_UNKNOWN_SIZE) + se_cmd->unknown_data_length = 1; /* - * If the received CDB has aleady been ABORTED by the generic - * target engine, we now call transport_check_aborted_status() - * to queue any delated TASK_ABORTED status for the received CDB to the - * fabric module as we are expecting no futher incoming DATA OUT - * sequences at this point. + * Obtain struct se_cmd->cmd_kref reference and add new cmd to + * se_sess->sess_cmd_list. A second kref_get here is necessary + * for fabrics using TARGET_SCF_ACK_KREF that expect a second + * kref_put() to happen during fabric packet acknowledgement. */ - if (transport_check_aborted_status(cmd, 1) != 0) - return 0; - - transport_add_cmd_to_queue(cmd, TRANSPORT_PROCESS_WRITE); - return 0; -} -EXPORT_SYMBOL(transport_generic_handle_data); - -/* transport_generic_handle_tmr(): - * - * - */ -int transport_generic_handle_tmr( - struct se_cmd *cmd) -{ + ret = target_get_sess_cmd(se_sess, se_cmd, (flags & TARGET_SCF_ACK_KREF)); + if (ret) + return ret; /* - * This is needed for early exceptions. + * Signal bidirectional data payloads to target-core */ - cmd->transport_wait_for_tasks = &transport_generic_wait_for_tasks; - transport_device_setup_cmd(cmd); - - transport_add_cmd_to_queue(cmd, TRANSPORT_PROCESS_TMR); - return 0; -} -EXPORT_SYMBOL(transport_generic_handle_tmr); - -static int transport_stop_tasks_for_cmd(struct se_cmd *cmd) -{ - struct se_task *task, *task_tmp; - unsigned long flags; - int ret = 0; - - DEBUG_TS("ITT[0x%08x] - Stopping tasks\n", - CMD_TFO(cmd)->get_task_tag(cmd)); - + if (flags & TARGET_SCF_BIDI_OP) + se_cmd->se_cmd_flags |= SCF_BIDI; /* - * No tasks remain in the execution queue + * Locate se_lun pointer and attach it to struct se_cmd */ - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - list_for_each_entry_safe(task, task_tmp, - &T_TASK(cmd)->t_task_list, t_list) { - DEBUG_TS("task_no[%d] - Processing task %p\n", - task->task_no, task); - /* - * If the struct se_task has not been sent and is not active, - * remove the struct se_task from the execution queue. - */ - if (!atomic_read(&task->task_sent) && - !atomic_read(&task->task_active)) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, - flags); - transport_remove_task_from_execute_queue(task, - task->se_dev); - - DEBUG_TS("task_no[%d] - Removed from execute queue\n", - task->task_no); - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - continue; - } - - /* - * If the struct se_task is active, sleep until it is returned - * from the plugin. - */ - if (atomic_read(&task->task_active)) { - atomic_set(&task->task_stop, 1); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, - flags); - - DEBUG_TS("task_no[%d] - Waiting to complete\n", - task->task_no); - wait_for_completion(&task->task_stop_comp); - DEBUG_TS("task_no[%d] - Stopped successfully\n", - task->task_no); - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - atomic_dec(&T_TASK(cmd)->t_task_cdbs_left); - - atomic_set(&task->task_active, 0); - atomic_set(&task->task_stop, 0); - } else { - DEBUG_TS("task_no[%d] - Did nothing\n", task->task_no); - ret++; - } - - __transport_stop_task_timer(task, &flags); + rc = transport_lookup_cmd_lun(se_cmd, unpacked_lun); + if (rc) { + transport_send_check_condition_and_sense(se_cmd, rc, 0); + target_put_sess_cmd(se_sess, se_cmd); + return 0; } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return ret; -} - -static void transport_failure_reset_queue_depth(struct se_device *dev) -{ - unsigned long flags; - - spin_lock_irqsave(&SE_HBA(dev)->hba_queue_lock, flags);; - atomic_inc(&dev->depth_left); - atomic_inc(&SE_HBA(dev)->left_queue_depth); - spin_unlock_irqrestore(&SE_HBA(dev)->hba_queue_lock, flags); -} + rc = target_setup_cmd_from_cdb(se_cmd, cdb); + if (rc != 0) { + transport_generic_request_failure(se_cmd, rc); + return 0; + } -/* - * Handle SAM-esque emulation for generic transport request failures. - */ -static void transport_generic_request_failure( - struct se_cmd *cmd, - struct se_device *dev, - int complete, - int sc) -{ - DEBUG_GRF("-----[ Storage Engine Exception for cmd: %p ITT: 0x%08x" - " CDB: 0x%02x\n", cmd, CMD_TFO(cmd)->get_task_tag(cmd), - T_TASK(cmd)->t_task_cdb[0]); - DEBUG_GRF("-----[ i_state: %d t_state/def_t_state:" - " %d/%d transport_error_status: %d\n", - CMD_TFO(cmd)->get_cmd_state(cmd), - cmd->t_state, cmd->deferred_t_state, - cmd->transport_error_status); - DEBUG_GRF("-----[ t_task_cdbs: %d t_task_cdbs_left: %d" - " t_task_cdbs_sent: %d t_task_cdbs_ex_left: %d --" - " t_transport_active: %d t_transport_stop: %d" - " t_transport_sent: %d\n", T_TASK(cmd)->t_task_cdbs, - atomic_read(&T_TASK(cmd)->t_task_cdbs_left), - atomic_read(&T_TASK(cmd)->t_task_cdbs_sent), - atomic_read(&T_TASK(cmd)->t_task_cdbs_ex_left), - atomic_read(&T_TASK(cmd)->t_transport_active), - atomic_read(&T_TASK(cmd)->t_transport_stop), - atomic_read(&T_TASK(cmd)->t_transport_sent)); - - transport_stop_all_task_timers(cmd); - - if (dev) - transport_failure_reset_queue_depth(dev); /* - * For SAM Task Attribute emulation for failed struct se_cmd + * Save pointers for SGLs containing protection information, + * if present. */ - if (cmd->se_dev->dev_task_attr_type == SAM_TASK_ATTR_EMULATED) - transport_complete_task_attr(cmd); - - if (complete) { - transport_direct_request_timeout(cmd); - cmd->transport_error_status = PYX_TRANSPORT_LU_COMM_FAILURE; + if (sgl_prot_count) { + se_cmd->t_prot_sg = sgl_prot; + se_cmd->t_prot_nents = sgl_prot_count; } - switch (cmd->transport_error_status) { - case PYX_TRANSPORT_UNKNOWN_SAM_OPCODE: - cmd->scsi_sense_reason = TCM_UNSUPPORTED_SCSI_OPCODE; - break; - case PYX_TRANSPORT_REQ_TOO_MANY_SECTORS: - cmd->scsi_sense_reason = TCM_SECTOR_COUNT_TOO_MANY; - break; - case PYX_TRANSPORT_INVALID_CDB_FIELD: - cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD; - break; - case PYX_TRANSPORT_INVALID_PARAMETER_LIST: - cmd->scsi_sense_reason = TCM_INVALID_PARAMETER_LIST; - break; - case PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES: - if (!sc) - transport_new_cmd_failure(cmd); - /* - * Currently for PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES, - * we force this session to fall back to session - * recovery. - */ - CMD_TFO(cmd)->fall_back_to_erl0(cmd->se_sess); - CMD_TFO(cmd)->stop_session(cmd->se_sess, 0, 0); - - goto check_stop; - case PYX_TRANSPORT_LU_COMM_FAILURE: - case PYX_TRANSPORT_ILLEGAL_REQUEST: - cmd->scsi_sense_reason = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - break; - case PYX_TRANSPORT_UNKNOWN_MODE_PAGE: - cmd->scsi_sense_reason = TCM_UNKNOWN_MODE_PAGE; - break; - case PYX_TRANSPORT_WRITE_PROTECTED: - cmd->scsi_sense_reason = TCM_WRITE_PROTECTED; - break; - case PYX_TRANSPORT_RESERVATION_CONFLICT: - /* - * No SENSE Data payload for this case, set SCSI Status - * and queue the response to $FABRIC_MOD. - * - * Uses linux/include/scsi/scsi.h SAM status codes defs - */ - cmd->scsi_status = SAM_STAT_RESERVATION_CONFLICT; - /* - * For UA Interlock Code 11b, a RESERVATION CONFLICT will - * establish a UNIT ATTENTION with PREVIOUS RESERVATION - * CONFLICT STATUS. - * - * See spc4r17, section 7.4.6 Control Mode Page, Table 349 - */ - if (SE_SESS(cmd) && - DEV_ATTRIB(cmd->se_dev)->emulate_ua_intlck_ctrl == 2) - core_scsi3_ua_allocate(SE_SESS(cmd)->se_node_acl, - cmd->orig_fe_lun, 0x2C, - ASCQ_2CH_PREVIOUS_RESERVATION_CONFLICT_STATUS); + /* + * When a non zero sgl_count has been passed perform SGL passthrough + * mapping for pre-allocated fabric memory instead of having target + * core perform an internal SGL allocation.. + */ + if (sgl_count != 0) { + BUG_ON(!sgl); - CMD_TFO(cmd)->queue_status(cmd); - goto check_stop; - case PYX_TRANSPORT_USE_SENSE_REASON: /* - * struct se_cmd->scsi_sense_reason already set + * A work-around for tcm_loop as some userspace code via + * scsi-generic do not memset their associated read buffers, + * so go ahead and do that here for type non-data CDBs. Also + * note that this is currently guaranteed to be a single SGL + * for this case by target core in target_setup_cmd_from_cdb() + * -> transport_generic_cmd_sequencer(). */ - break; - default: - printk(KERN_ERR "Unknown transport error for CDB 0x%02x: %d\n", - T_TASK(cmd)->t_task_cdb[0], - cmd->transport_error_status); - cmd->scsi_sense_reason = TCM_UNSUPPORTED_SCSI_OPCODE; - break; - } + if (!(se_cmd->se_cmd_flags & SCF_SCSI_DATA_CDB) && + se_cmd->data_direction == DMA_FROM_DEVICE) { + unsigned char *buf = NULL; - if (!sc) - transport_new_cmd_failure(cmd); - else - transport_send_check_condition_and_sense(cmd, - cmd->scsi_sense_reason, 0); -check_stop: - transport_lun_remove_cmd(cmd); - if (!(transport_cmd_check_stop_to_fabric(cmd))) - ; -} + if (sgl) + buf = kmap(sg_page(sgl)) + sgl->offset; -static void transport_direct_request_timeout(struct se_cmd *cmd) -{ - unsigned long flags; + if (buf) { + memset(buf, 0, sgl->length); + kunmap(sg_page(sgl)); + } + } - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (!(atomic_read(&T_TASK(cmd)->t_transport_timeout))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return; - } - if (atomic_read(&T_TASK(cmd)->t_task_cdbs_timeout_left)) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return; + rc = transport_generic_map_mem_to_cmd(se_cmd, sgl, sgl_count, + sgl_bidi, sgl_bidi_count); + if (rc != 0) { + transport_generic_request_failure(se_cmd, rc); + return 0; + } } - atomic_sub(atomic_read(&T_TASK(cmd)->t_transport_timeout), - &T_TASK(cmd)->t_se_count); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); -} - -static void transport_generic_request_timeout(struct se_cmd *cmd) -{ - unsigned long flags; - /* - * Reset T_TASK(cmd)->t_se_count to allow transport_generic_remove() - * to allow last call to free memory resources. + * Check if we need to delay processing because of ALUA + * Active/NonOptimized primary access state.. */ - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (atomic_read(&T_TASK(cmd)->t_transport_timeout) > 1) { - int tmp = (atomic_read(&T_TASK(cmd)->t_transport_timeout) - 1); - - atomic_sub(tmp, &T_TASK(cmd)->t_se_count); - } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - transport_generic_remove(cmd, 0, 0); -} - -static int -transport_generic_allocate_buf(struct se_cmd *cmd, u32 data_length) -{ - unsigned char *buf; - - buf = kzalloc(data_length, GFP_KERNEL); - if (!(buf)) { - printk(KERN_ERR "Unable to allocate memory for buffer\n"); - return -1; - } - - T_TASK(cmd)->t_tasks_se_num = 0; - T_TASK(cmd)->t_task_buf = buf; + core_alua_check_nonop_delay(se_cmd); + transport_handle_cdb_direct(se_cmd); return 0; } - -static inline u32 transport_lba_21(unsigned char *cdb) -{ - return ((cdb[1] & 0x1f) << 16) | (cdb[2] << 8) | cdb[3]; -} - -static inline u32 transport_lba_32(unsigned char *cdb) -{ - return (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; -} - -static inline unsigned long long transport_lba_64(unsigned char *cdb) -{ - unsigned int __v1, __v2; - - __v1 = (cdb[2] << 24) | (cdb[3] << 16) | (cdb[4] << 8) | cdb[5]; - __v2 = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9]; - - return ((unsigned long long)__v2) | (unsigned long long)__v1 << 32; -} +EXPORT_SYMBOL(target_submit_cmd_map_sgls); /* - * For VARIABLE_LENGTH_CDB w/ 32 byte extended CDBs + * target_submit_cmd - lookup unpacked lun and submit uninitialized se_cmd + * + * @se_cmd: command descriptor to submit + * @se_sess: associated se_sess for endpoint + * @cdb: pointer to SCSI CDB + * @sense: pointer to SCSI sense buffer + * @unpacked_lun: unpacked LUN to reference for struct se_lun + * @data_length: fabric expected data transfer length + * @task_addr: SAM task attribute + * @data_dir: DMA data direction + * @flags: flags for command submission from target_sc_flags_tables + * + * Returns non zero to signal active I/O shutdown failure. All other + * setup exceptions will be returned as a SCSI CHECK_CONDITION response, + * but still return zero here. + * + * This may only be called from process context, and also currently + * assumes internal allocation of fabric payload buffer by target-core. + * + * It also assumes interal target core SGL memory allocation. */ -static inline unsigned long long transport_lba_64_ext(unsigned char *cdb) +int target_submit_cmd(struct se_cmd *se_cmd, struct se_session *se_sess, + unsigned char *cdb, unsigned char *sense, u32 unpacked_lun, + u32 data_length, int task_attr, int data_dir, int flags) { - unsigned int __v1, __v2; - - __v1 = (cdb[12] << 24) | (cdb[13] << 16) | (cdb[14] << 8) | cdb[15]; - __v2 = (cdb[16] << 24) | (cdb[17] << 16) | (cdb[18] << 8) | cdb[19]; - - return ((unsigned long long)__v2) | (unsigned long long)__v1 << 32; + return target_submit_cmd_map_sgls(se_cmd, se_sess, cdb, sense, + unpacked_lun, data_length, task_attr, data_dir, + flags, NULL, 0, NULL, 0, NULL, 0); } +EXPORT_SYMBOL(target_submit_cmd); -static void transport_set_supported_SAM_opcode(struct se_cmd *se_cmd) +static void target_complete_tmr_failure(struct work_struct *work) { - unsigned long flags; + struct se_cmd *se_cmd = container_of(work, struct se_cmd, work); - spin_lock_irqsave(&T_TASK(se_cmd)->t_state_lock, flags); - se_cmd->se_cmd_flags |= SCF_SUPPORTED_SAM_OPCODE; - spin_unlock_irqrestore(&T_TASK(se_cmd)->t_state_lock, flags); + se_cmd->se_tmr_req->response = TMR_LUN_DOES_NOT_EXIST; + se_cmd->se_tfo->queue_tm_rsp(se_cmd); + + transport_cmd_check_stop_to_fabric(se_cmd); } -/* - * Called from interrupt context. - */ -static void transport_task_timeout_handler(unsigned long data) +/** + * target_submit_tmr - lookup unpacked lun and submit uninitialized se_cmd + * for TMR CDBs + * + * @se_cmd: command descriptor to submit + * @se_sess: associated se_sess for endpoint + * @sense: pointer to SCSI sense buffer + * @unpacked_lun: unpacked LUN to reference for struct se_lun + * @fabric_context: fabric context for TMR req + * @tm_type: Type of TM request + * @gfp: gfp type for caller + * @tag: referenced task tag for TMR_ABORT_TASK + * @flags: submit cmd flags + * + * Callable from all contexts. + **/ + +int target_submit_tmr(struct se_cmd *se_cmd, struct se_session *se_sess, + unsigned char *sense, u32 unpacked_lun, + void *fabric_tmr_ptr, unsigned char tm_type, + gfp_t gfp, unsigned int tag, int flags) { - struct se_task *task = (struct se_task *)data; - struct se_cmd *cmd = TASK_CMD(task); - unsigned long flags; + struct se_portal_group *se_tpg; + int ret; - DEBUG_TT("transport task timeout fired! task: %p cmd: %p\n", task, cmd); - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (task->task_flags & TF_STOP) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return; - } - task->task_flags &= ~TF_RUNNING; + se_tpg = se_sess->se_tpg; + BUG_ON(!se_tpg); + transport_init_se_cmd(se_cmd, se_tpg->se_tpg_tfo, se_sess, + 0, DMA_NONE, MSG_SIMPLE_TAG, sense); /* - * Determine if transport_complete_task() has already been called. + * FIXME: Currently expect caller to handle se_cmd->se_tmr_req + * allocation failure. */ - if (!(atomic_read(&task->task_active))) { - DEBUG_TT("transport task: %p cmd: %p timeout task_active" - " == 0\n", task, cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return; - } - - atomic_inc(&T_TASK(cmd)->t_se_count); - atomic_inc(&T_TASK(cmd)->t_transport_timeout); - T_TASK(cmd)->t_tasks_failed = 1; + ret = core_tmr_alloc_req(se_cmd, fabric_tmr_ptr, tm_type, gfp); + if (ret < 0) + return -ENOMEM; - atomic_set(&task->task_timeout, 1); - task->task_error_status = PYX_TRANSPORT_TASK_TIMEOUT; - task->task_scsi_status = 1; + if (tm_type == TMR_ABORT_TASK) + se_cmd->se_tmr_req->ref_task_tag = tag; - if (atomic_read(&task->task_stop)) { - DEBUG_TT("transport task: %p cmd: %p timeout task_stop" - " == 1\n", task, cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - complete(&task->task_stop_comp); - return; + /* See target_submit_cmd for commentary */ + ret = target_get_sess_cmd(se_sess, se_cmd, (flags & TARGET_SCF_ACK_KREF)); + if (ret) { + core_tmr_release_req(se_cmd->se_tmr_req); + return ret; } - if (!(atomic_dec_and_test(&T_TASK(cmd)->t_task_cdbs_left))) { - DEBUG_TT("transport task: %p cmd: %p timeout non zero" - " t_task_cdbs_left\n", task, cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return; + ret = transport_lookup_tmr_lun(se_cmd, unpacked_lun); + if (ret) { + /* + * For callback during failure handling, push this work off + * to process context with TMR_LUN_DOES_NOT_EXIST status. + */ + INIT_WORK(&se_cmd->work, target_complete_tmr_failure); + schedule_work(&se_cmd->work); + return 0; } - DEBUG_TT("transport task: %p cmd: %p timeout ZERO t_task_cdbs_left\n", - task, cmd); - - cmd->t_state = TRANSPORT_COMPLETE_FAILURE; - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - transport_add_cmd_to_queue(cmd, TRANSPORT_COMPLETE_FAILURE); -} - -/* - * Called with T_TASK(cmd)->t_state_lock held. - */ -static void transport_start_task_timer(struct se_task *task) -{ - struct se_device *dev = task->se_dev; - int timeout; - - if (task->task_flags & TF_RUNNING) - return; - /* - * If the task_timeout is disabled, exit now. - */ - timeout = DEV_ATTRIB(dev)->task_timeout; - if (!(timeout)) - return; - - init_timer(&task->task_timer); - task->task_timer.expires = (get_jiffies_64() + timeout * HZ); - task->task_timer.data = (unsigned long) task; - task->task_timer.function = transport_task_timeout_handler; - - task->task_flags |= TF_RUNNING; - add_timer(&task->task_timer); -#if 0 - printk(KERN_INFO "Starting task timer for cmd: %p task: %p seconds:" - " %d\n", task->task_se_cmd, task, timeout); -#endif + transport_generic_handle_tmr(se_cmd); + return 0; } +EXPORT_SYMBOL(target_submit_tmr); /* - * Called with spin_lock_irq(&T_TASK(cmd)->t_state_lock) held. + * If the cmd is active, request it to be stopped and sleep until it + * has completed. */ -void __transport_stop_task_timer(struct se_task *task, unsigned long *flags) +bool target_stop_cmd(struct se_cmd *cmd, unsigned long *flags) { - struct se_cmd *cmd = TASK_CMD(task); - - if (!(task->task_flags & TF_RUNNING)) - return; - - task->task_flags |= TF_STOP; - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, *flags); - - del_timer_sync(&task->task_timer); - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, *flags); - task->task_flags &= ~TF_RUNNING; - task->task_flags &= ~TF_STOP; -} + bool was_active = false; -static void transport_stop_all_task_timers(struct se_cmd *cmd) -{ - struct se_task *task = NULL, *task_tmp; - unsigned long flags; + if (cmd->transport_state & CMD_T_BUSY) { + cmd->transport_state |= CMD_T_REQUEST_STOP; + spin_unlock_irqrestore(&cmd->t_state_lock, *flags); - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - list_for_each_entry_safe(task, task_tmp, - &T_TASK(cmd)->t_task_list, t_list) - __transport_stop_task_timer(task, &flags); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); -} + pr_debug("cmd %p waiting to complete\n", cmd); + wait_for_completion(&cmd->task_stop_comp); + pr_debug("cmd %p stopped successfully\n", cmd); -static inline int transport_tcq_window_closed(struct se_device *dev) -{ - if (dev->dev_tcq_window_closed++ < - PYX_TRANSPORT_WINDOW_CLOSED_THRESHOLD) { - msleep(PYX_TRANSPORT_WINDOW_CLOSED_WAIT_SHORT); - } else - msleep(PYX_TRANSPORT_WINDOW_CLOSED_WAIT_LONG); + spin_lock_irqsave(&cmd->t_state_lock, *flags); + cmd->transport_state &= ~CMD_T_REQUEST_STOP; + cmd->transport_state &= ~CMD_T_BUSY; + was_active = true; + } - wake_up_interruptible(&dev->dev_queue_obj->thread_wq); - return 0; + return was_active; } /* - * Called from Fabric Module context from transport_execute_tasks() - * - * The return of this function determins if the tasks from struct se_cmd - * get added to the execution queue in transport_execute_tasks(), - * or are added to the delayed or ordered lists here. + * Handle SAM-esque emulation for generic transport request failures. */ -static inline int transport_execute_task_attr(struct se_cmd *cmd) +void transport_generic_request_failure(struct se_cmd *cmd, + sense_reason_t sense_reason) { - if (SE_DEV(cmd)->dev_task_attr_type != SAM_TASK_ATTR_EMULATED) - return 1; + int ret = 0; + + pr_debug("-----[ Storage Engine Exception for cmd: %p ITT: 0x%08x" + " CDB: 0x%02x\n", cmd, cmd->se_tfo->get_task_tag(cmd), + cmd->t_task_cdb[0]); + pr_debug("-----[ i_state: %d t_state: %d sense_reason: %d\n", + cmd->se_tfo->get_cmd_state(cmd), + cmd->t_state, sense_reason); + pr_debug("-----[ CMD_T_ACTIVE: %d CMD_T_STOP: %d CMD_T_SENT: %d\n", + (cmd->transport_state & CMD_T_ACTIVE) != 0, + (cmd->transport_state & CMD_T_STOP) != 0, + (cmd->transport_state & CMD_T_SENT) != 0); + /* - * Check for the existance of HEAD_OF_QUEUE, and if true return 1 - * to allow the passed struct se_cmd list of tasks to the front of the list. + * For SAM Task Attribute emulation for failed struct se_cmd */ - if (cmd->sam_task_attr == TASK_ATTR_HOQ) { - atomic_inc(&SE_DEV(cmd)->dev_hoq_count); - smp_mb__after_atomic_inc(); - DEBUG_STA("Added HEAD_OF_QUEUE for CDB:" - " 0x%02x, se_ordered_id: %u\n", - T_TASK(cmd)->t_task_cdb[0], - cmd->se_ordered_id); - return 1; - } else if (cmd->sam_task_attr == TASK_ATTR_ORDERED) { - spin_lock(&SE_DEV(cmd)->ordered_cmd_lock); - list_add_tail(&cmd->se_ordered_list, - &SE_DEV(cmd)->ordered_cmd_list); - spin_unlock(&SE_DEV(cmd)->ordered_cmd_lock); - - atomic_inc(&SE_DEV(cmd)->dev_ordered_sync); - smp_mb__after_atomic_inc(); - - DEBUG_STA("Added ORDERED for CDB: 0x%02x to ordered" - " list, se_ordered_id: %u\n", - T_TASK(cmd)->t_task_cdb[0], - cmd->se_ordered_id); - /* - * Add ORDERED command to tail of execution queue if - * no other older commands exist that need to be - * completed first. - */ - if (!(atomic_read(&SE_DEV(cmd)->simple_cmds))) - return 1; - } else { - /* - * For SIMPLE and UNTAGGED Task Attribute commands - */ - atomic_inc(&SE_DEV(cmd)->simple_cmds); - smp_mb__after_atomic_inc(); - } + transport_complete_task_attr(cmd); /* - * Otherwise if one or more outstanding ORDERED task attribute exist, - * add the dormant task(s) built for the passed struct se_cmd to the - * execution queue and become in Active state for this struct se_device. + * Handle special case for COMPARE_AND_WRITE failure, where the + * callback is expected to drop the per device ->caw_mutex. */ - if (atomic_read(&SE_DEV(cmd)->dev_ordered_sync) != 0) { + if ((cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE) && + cmd->transport_complete_callback) + cmd->transport_complete_callback(cmd); + + switch (sense_reason) { + case TCM_NON_EXISTENT_LUN: + case TCM_UNSUPPORTED_SCSI_OPCODE: + case TCM_INVALID_CDB_FIELD: + case TCM_INVALID_PARAMETER_LIST: + case TCM_PARAMETER_LIST_LENGTH_ERROR: + case TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE: + case TCM_UNKNOWN_MODE_PAGE: + case TCM_WRITE_PROTECTED: + case TCM_ADDRESS_OUT_OF_RANGE: + case TCM_CHECK_CONDITION_ABORT_CMD: + case TCM_CHECK_CONDITION_UNIT_ATTENTION: + case TCM_CHECK_CONDITION_NOT_READY: + case TCM_LOGICAL_BLOCK_GUARD_CHECK_FAILED: + case TCM_LOGICAL_BLOCK_APP_TAG_CHECK_FAILED: + case TCM_LOGICAL_BLOCK_REF_TAG_CHECK_FAILED: + break; + case TCM_OUT_OF_RESOURCES: + sense_reason = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + break; + case TCM_RESERVATION_CONFLICT: /* - * Otherwise, add cmd w/ tasks to delayed cmd queue that - * will be drained upon competion of HEAD_OF_QUEUE task. + * No SENSE Data payload for this case, set SCSI Status + * and queue the response to $FABRIC_MOD. + * + * Uses linux/include/scsi/scsi.h SAM status codes defs */ - spin_lock(&SE_DEV(cmd)->delayed_cmd_lock); - cmd->se_cmd_flags |= SCF_DELAYED_CMD_FROM_SAM_ATTR; - list_add_tail(&cmd->se_delayed_list, - &SE_DEV(cmd)->delayed_cmd_list); - spin_unlock(&SE_DEV(cmd)->delayed_cmd_lock); - - DEBUG_STA("Added CDB: 0x%02x Task Attr: 0x%02x to" - " delayed CMD list, se_ordered_id: %u\n", - T_TASK(cmd)->t_task_cdb[0], cmd->sam_task_attr, - cmd->se_ordered_id); + cmd->scsi_status = SAM_STAT_RESERVATION_CONFLICT; /* - * Return zero to let transport_execute_tasks() know - * not to add the delayed tasks to the execution list. + * For UA Interlock Code 11b, a RESERVATION CONFLICT will + * establish a UNIT ATTENTION with PREVIOUS RESERVATION + * CONFLICT STATUS. + * + * See spc4r17, section 7.4.6 Control Mode Page, Table 349 */ - return 0; + if (cmd->se_sess && + cmd->se_dev->dev_attrib.emulate_ua_intlck_ctrl == 2) + core_scsi3_ua_allocate(cmd->se_sess->se_node_acl, + cmd->orig_fe_lun, 0x2C, + ASCQ_2CH_PREVIOUS_RESERVATION_CONFLICT_STATUS); + + trace_target_cmd_complete(cmd); + ret = cmd->se_tfo-> queue_status(cmd); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; + goto check_stop; + default: + pr_err("Unknown transport error for CDB 0x%02x: %d\n", + cmd->t_task_cdb[0], sense_reason); + sense_reason = TCM_UNSUPPORTED_SCSI_OPCODE; + break; } - /* - * Otherwise, no ORDERED task attributes exist.. - */ - return 1; + + ret = transport_send_check_condition_and_sense(cmd, sense_reason, 0); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; + +check_stop: + transport_lun_remove_cmd(cmd); + if (!transport_cmd_check_stop_to_fabric(cmd)) + ; + return; + +queue_full: + cmd->t_state = TRANSPORT_COMPLETE_QF_OK; + transport_handle_queue_full(cmd, cmd->se_dev); } +EXPORT_SYMBOL(transport_generic_request_failure); -/* - * Called from fabric module context in transport_generic_new_cmd() and - * transport_generic_process_write() - */ -static int transport_execute_tasks(struct se_cmd *cmd) +void __target_execute_cmd(struct se_cmd *cmd) { - int add_tasks; + sense_reason_t ret; - if (!(cmd->se_cmd_flags & SCF_SE_DISABLE_ONLINE_CHECK)) { - if (se_dev_check_online(cmd->se_orig_obj_ptr) != 0) { - cmd->transport_error_status = - PYX_TRANSPORT_LU_COMM_FAILURE; - transport_generic_request_failure(cmd, NULL, 0, 1); - return 0; + if (cmd->execute_cmd) { + ret = cmd->execute_cmd(cmd); + if (ret) { + spin_lock_irq(&cmd->t_state_lock); + cmd->transport_state &= ~(CMD_T_BUSY|CMD_T_SENT); + spin_unlock_irq(&cmd->t_state_lock); + + transport_generic_request_failure(cmd, ret); } } - /* - * Call transport_cmd_check_stop() to see if a fabric exception - * has occured that prevents execution. - */ - if (!(transport_cmd_check_stop(cmd, 0, TRANSPORT_PROCESSING))) { - /* - * Check for SAM Task Attribute emulation and HEAD_OF_QUEUE - * attribute for the tasks of the received struct se_cmd CDB - */ - add_tasks = transport_execute_task_attr(cmd); - if (add_tasks == 0) - goto execute_tasks; - /* - * This calls transport_add_tasks_from_cmd() to handle - * HEAD_OF_QUEUE ordering for SAM Task Attribute emulation - * (if enabled) in __transport_add_task_to_execute_queue() and - * transport_add_task_check_sam_attr(). - */ - transport_add_tasks_from_cmd(cmd); - } - /* - * Kick the execution queue for the cmd associated struct se_device - * storage object. - */ -execute_tasks: - __transport_execute_tasks(SE_DEV(cmd)); - return 0; } -/* - * Called to check struct se_device tcq depth window, and once open pull struct se_task - * from struct se_device->execute_task_list and - * - * Called from transport_processing_thread() - */ -static int __transport_execute_tasks(struct se_device *dev) +static bool target_handle_task_attr(struct se_cmd *cmd) { - int error; - struct se_cmd *cmd = NULL; - struct se_task *task; - unsigned long flags; + struct se_device *dev = cmd->se_dev; + + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) + return false; /* - * Check if there is enough room in the device and HBA queue to send - * struct se_transport_task's to the selected transport. + * Check for the existence of HEAD_OF_QUEUE, and if true return 1 + * to allow the passed struct se_cmd list of tasks to the front of the list. */ -check_depth: - spin_lock_irqsave(&SE_HBA(dev)->hba_queue_lock, flags); - if (!(atomic_read(&dev->depth_left)) || - !(atomic_read(&SE_HBA(dev)->left_queue_depth))) { - spin_unlock_irqrestore(&SE_HBA(dev)->hba_queue_lock, flags); - return transport_tcq_window_closed(dev); - } - dev->dev_tcq_window_closed = 0; - - spin_lock(&dev->execute_task_lock); - task = transport_get_task_from_execute_queue(dev); - spin_unlock(&dev->execute_task_lock); - - if (!task) { - spin_unlock_irqrestore(&SE_HBA(dev)->hba_queue_lock, flags); - return 0; - } - - atomic_dec(&dev->depth_left); - atomic_dec(&SE_HBA(dev)->left_queue_depth); - spin_unlock_irqrestore(&SE_HBA(dev)->hba_queue_lock, flags); - - cmd = TASK_CMD(task); + switch (cmd->sam_task_attr) { + case MSG_HEAD_TAG: + pr_debug("Added HEAD_OF_QUEUE for CDB: 0x%02x, " + "se_ordered_id: %u\n", + cmd->t_task_cdb[0], cmd->se_ordered_id); + return false; + case MSG_ORDERED_TAG: + atomic_inc(&dev->dev_ordered_sync); + smp_mb__after_atomic(); - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - atomic_set(&task->task_active, 1); - atomic_set(&task->task_sent, 1); - atomic_inc(&T_TASK(cmd)->t_task_cdbs_sent); + pr_debug("Added ORDERED for CDB: 0x%02x to ordered list, " + " se_ordered_id: %u\n", + cmd->t_task_cdb[0], cmd->se_ordered_id); - if (atomic_read(&T_TASK(cmd)->t_task_cdbs_sent) == - T_TASK(cmd)->t_task_cdbs) - atomic_set(&cmd->transport_sent, 1); - - transport_start_task_timer(task); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - /* - * The struct se_cmd->transport_emulate_cdb() function pointer is used - * to grab REPORT_LUNS CDBs before they hit the - * struct se_subsystem_api->do_task() caller below. - */ - if (cmd->transport_emulate_cdb) { - error = cmd->transport_emulate_cdb(cmd); - if (error != 0) { - cmd->transport_error_status = error; - atomic_set(&task->task_active, 0); - atomic_set(&cmd->transport_sent, 0); - transport_stop_tasks_for_cmd(cmd); - transport_generic_request_failure(cmd, dev, 0, 1); - goto check_depth; - } /* - * Handle the successful completion for transport_emulate_cdb() - * for synchronous operation, following SCF_EMULATE_CDB_ASYNC - * Otherwise the caller is expected to complete the task with - * proper status. + * Execute an ORDERED command if no other older commands + * exist that need to be completed first. */ - if (!(cmd->se_cmd_flags & SCF_EMULATE_CDB_ASYNC)) { - cmd->scsi_status = SAM_STAT_GOOD; - task->task_scsi_status = GOOD; - transport_complete_task(task, 1); - } - } else { + if (!atomic_read(&dev->simple_cmds)) + return false; + break; + default: /* - * Currently for all virtual TCM plugins including IBLOCK, FILEIO and - * RAMDISK we use the internal transport_emulate_control_cdb() logic - * with struct se_subsystem_api callers for the primary SPC-3 TYPE_DISK - * LUN emulation code. - * - * For TCM/pSCSI and all other SCF_SCSI_DATA_SG_IO_CDB I/O tasks we - * call ->do_task() directly and let the underlying TCM subsystem plugin - * code handle the CDB emulation. + * For SIMPLE and UNTAGGED Task Attribute commands */ - if ((TRANSPORT(dev)->transport_type != TRANSPORT_PLUGIN_PHBA_PDEV) && - (!(TASK_CMD(task)->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB))) - error = transport_emulate_control_cdb(task); - else - error = TRANSPORT(dev)->do_task(task); - - if (error != 0) { - cmd->transport_error_status = error; - atomic_set(&task->task_active, 0); - atomic_set(&cmd->transport_sent, 0); - transport_stop_tasks_for_cmd(cmd); - transport_generic_request_failure(cmd, dev, 0, 1); - } + atomic_inc(&dev->simple_cmds); + smp_mb__after_atomic(); + break; } - goto check_depth; + if (atomic_read(&dev->dev_ordered_sync) == 0) + return false; - return 0; -} - -void transport_new_cmd_failure(struct se_cmd *se_cmd) -{ - unsigned long flags; - /* - * Any unsolicited data will get dumped for failed command inside of - * the fabric plugin - */ - spin_lock_irqsave(&T_TASK(se_cmd)->t_state_lock, flags); - se_cmd->se_cmd_flags |= SCF_SE_CMD_FAILED; - se_cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - spin_unlock_irqrestore(&T_TASK(se_cmd)->t_state_lock, flags); + spin_lock(&dev->delayed_cmd_lock); + list_add_tail(&cmd->se_delayed_node, &dev->delayed_cmd_list); + spin_unlock(&dev->delayed_cmd_lock); - CMD_TFO(se_cmd)->new_cmd_failure(se_cmd); + pr_debug("Added CDB: 0x%02x Task Attr: 0x%02x to" + " delayed CMD list, se_ordered_id: %u\n", + cmd->t_task_cdb[0], cmd->sam_task_attr, + cmd->se_ordered_id); + return true; } -static void transport_nop_wait_for_tasks(struct se_cmd *, int, int); - -static inline u32 transport_get_sectors_6( - unsigned char *cdb, - struct se_cmd *cmd, - int *ret) +void target_execute_cmd(struct se_cmd *cmd) { - struct se_device *dev = SE_LUN(cmd)->lun_se_dev; - /* - * Assume TYPE_DISK for non struct se_device objects. - * Use 8-bit sector value. + * If the received CDB has aleady been aborted stop processing it here. */ - if (!dev) - goto type_disk; - - /* - * Use 24-bit allocation length for TYPE_TAPE. - */ - if (TRANSPORT(dev)->get_device_type(dev) == TYPE_TAPE) - return (u32)(cdb[2] << 16) + (cdb[3] << 8) + cdb[4]; - - /* - * Everything else assume TYPE_DISK Sector CDB location. - * Use 8-bit sector value. - */ -type_disk: - return (u32)cdb[4]; -} - -static inline u32 transport_get_sectors_10( - unsigned char *cdb, - struct se_cmd *cmd, - int *ret) -{ - struct se_device *dev = SE_LUN(cmd)->lun_se_dev; + if (transport_check_aborted_status(cmd, 1)) + return; /* - * Assume TYPE_DISK for non struct se_device objects. - * Use 16-bit sector value. + * Determine if frontend context caller is requesting the stopping of + * this command for frontend exceptions. */ - if (!dev) - goto type_disk; + spin_lock_irq(&cmd->t_state_lock); + if (cmd->transport_state & CMD_T_STOP) { + pr_debug("%s:%d CMD_T_STOP for ITT: 0x%08x\n", + __func__, __LINE__, + cmd->se_tfo->get_task_tag(cmd)); - /* - * XXX_10 is not defined in SSC, throw an exception - */ - if (TRANSPORT(dev)->get_device_type(dev) == TYPE_TAPE) { - *ret = -1; - return 0; + spin_unlock_irq(&cmd->t_state_lock); + complete_all(&cmd->t_transport_stop_comp); + return; } + cmd->t_state = TRANSPORT_PROCESSING; + cmd->transport_state |= CMD_T_ACTIVE|CMD_T_BUSY|CMD_T_SENT; + spin_unlock_irq(&cmd->t_state_lock); /* - * Everything else assume TYPE_DISK Sector CDB location. - * Use 16-bit sector value. + * Perform WRITE_INSERT of PI using software emulation when backend + * device has PI enabled, if the transport has not already generated + * PI using hardware WRITE_INSERT offload. */ -type_disk: - return (u32)(cdb[7] << 8) + cdb[8]; -} - -static inline u32 transport_get_sectors_12( - unsigned char *cdb, - struct se_cmd *cmd, - int *ret) -{ - struct se_device *dev = SE_LUN(cmd)->lun_se_dev; - - /* - * Assume TYPE_DISK for non struct se_device objects. - * Use 32-bit sector value. - */ - if (!dev) - goto type_disk; - - /* - * XXX_12 is not defined in SSC, throw an exception - */ - if (TRANSPORT(dev)->get_device_type(dev) == TYPE_TAPE) { - *ret = -1; - return 0; + if (cmd->prot_op == TARGET_PROT_DOUT_INSERT) { + if (!(cmd->se_sess->sup_prot_ops & TARGET_PROT_DOUT_INSERT)) + sbc_dif_generate(cmd); } - /* - * Everything else assume TYPE_DISK Sector CDB location. - * Use 32-bit sector value. - */ -type_disk: - return (u32)(cdb[6] << 24) + (cdb[7] << 16) + (cdb[8] << 8) + cdb[9]; -} - -static inline u32 transport_get_sectors_16( - unsigned char *cdb, - struct se_cmd *cmd, - int *ret) -{ - struct se_device *dev = SE_LUN(cmd)->lun_se_dev; - - /* - * Assume TYPE_DISK for non struct se_device objects. - * Use 32-bit sector value. - */ - if (!dev) - goto type_disk; - - /* - * Use 24-bit allocation length for TYPE_TAPE. - */ - if (TRANSPORT(dev)->get_device_type(dev) == TYPE_TAPE) - return (u32)(cdb[12] << 16) + (cdb[13] << 8) + cdb[14]; + if (target_handle_task_attr(cmd)) { + spin_lock_irq(&cmd->t_state_lock); + cmd->transport_state &= ~CMD_T_BUSY|CMD_T_SENT; + spin_unlock_irq(&cmd->t_state_lock); + return; + } -type_disk: - return (u32)(cdb[10] << 24) + (cdb[11] << 16) + - (cdb[12] << 8) + cdb[13]; + __target_execute_cmd(cmd); } +EXPORT_SYMBOL(target_execute_cmd); /* - * Used for VARIABLE_LENGTH_CDB WRITE_32 and READ_32 variants + * Process all commands up to the last received ORDERED task attribute which + * requires another blocking boundary */ -static inline u32 transport_get_sectors_32( - unsigned char *cdb, - struct se_cmd *cmd, - int *ret) +static void target_restart_delayed_cmds(struct se_device *dev) { - /* - * Assume TYPE_DISK for non struct se_device objects. - * Use 32-bit sector value. - */ - return (u32)(cdb[28] << 24) + (cdb[29] << 16) + - (cdb[30] << 8) + cdb[31]; + for (;;) { + struct se_cmd *cmd; -} - -static inline u32 transport_get_size( - u32 sectors, - unsigned char *cdb, - struct se_cmd *cmd) -{ - struct se_device *dev = SE_DEV(cmd); - - if (TRANSPORT(dev)->get_device_type(dev) == TYPE_TAPE) { - if (cdb[1] & 1) { /* sectors */ - return DEV_ATTRIB(dev)->block_size * sectors; - } else /* bytes */ - return sectors; - } -#if 0 - printk(KERN_INFO "Returning block_size: %u, sectors: %u == %u for" - " %s object\n", DEV_ATTRIB(dev)->block_size, sectors, - DEV_ATTRIB(dev)->block_size * sectors, - TRANSPORT(dev)->name); -#endif - return DEV_ATTRIB(dev)->block_size * sectors; -} - -unsigned char transport_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; -} -EXPORT_SYMBOL(transport_asciihex_to_binaryhex); - -static void transport_xor_callback(struct se_cmd *cmd) -{ - unsigned char *buf, *addr; - struct se_mem *se_mem; - unsigned int offset; - int i; - /* - * From sbc3r22.pdf section 5.48 XDWRITEREAD (10) command - * - * 1) read the specified logical block(s); - * 2) transfer logical blocks from the data-out buffer; - * 3) XOR the logical blocks transferred from the data-out buffer with - * the logical blocks read, storing the resulting XOR data in a buffer; - * 4) if the DISABLE WRITE bit is set to zero, then write the logical - * blocks transferred from the data-out buffer; and - * 5) transfer the resulting XOR data to the data-in buffer. - */ - buf = kmalloc(cmd->data_length, GFP_KERNEL); - if (!(buf)) { - printk(KERN_ERR "Unable to allocate xor_callback buf\n"); - return; - } - /* - * Copy the scatterlist WRITE buffer located at T_TASK(cmd)->t_mem_list - * into the locally allocated *buf - */ - transport_memcpy_se_mem_read_contig(cmd, buf, T_TASK(cmd)->t_mem_list); - /* - * Now perform the XOR against the BIDI read memory located at - * T_TASK(cmd)->t_mem_bidi_list - */ + spin_lock(&dev->delayed_cmd_lock); + if (list_empty(&dev->delayed_cmd_list)) { + spin_unlock(&dev->delayed_cmd_lock); + break; + } - offset = 0; - list_for_each_entry(se_mem, T_TASK(cmd)->t_mem_bidi_list, se_list) { - addr = (unsigned char *)kmap_atomic(se_mem->se_page, KM_USER0); - if (!(addr)) - goto out; + cmd = list_entry(dev->delayed_cmd_list.next, + struct se_cmd, se_delayed_node); + list_del(&cmd->se_delayed_node); + spin_unlock(&dev->delayed_cmd_lock); - for (i = 0; i < se_mem->se_len; i++) - *(addr + se_mem->se_off + i) ^= *(buf + offset + i); + __target_execute_cmd(cmd); - offset += se_mem->se_len; - kunmap_atomic(addr, KM_USER0); + if (cmd->sam_task_attr == MSG_ORDERED_TAG) + break; } -out: - kfree(buf); } /* - * Used to obtain Sense Data from underlying Linux/SCSI struct scsi_cmnd + * Called from I/O completion to determine which dormant/delayed + * and ordered cmds need to have their tasks added to the execution queue. */ -static int transport_get_sense_data(struct se_cmd *cmd) +static void transport_complete_task_attr(struct se_cmd *cmd) { - unsigned char *buffer = cmd->sense_buffer, *sense_buffer = NULL; - struct se_device *dev; - struct se_task *task = NULL, *task_tmp; - unsigned long flags; - u32 offset = 0; - - if (!SE_LUN(cmd)) { - printk(KERN_ERR "SE_LUN(cmd) is NULL\n"); - return -1; - } - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (cmd->se_cmd_flags & SCF_SENT_CHECK_CONDITION) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return 0; - } - - list_for_each_entry_safe(task, task_tmp, - &T_TASK(cmd)->t_task_list, t_list) { - - if (!task->task_sense) - continue; + struct se_device *dev = cmd->se_dev; - dev = task->se_dev; - if (!(dev)) - continue; + if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) + return; - if (!TRANSPORT(dev)->get_sense_buffer) { - printk(KERN_ERR "TRANSPORT(dev)->get_sense_buffer" - " is NULL\n"); - continue; - } + if (cmd->sam_task_attr == MSG_SIMPLE_TAG) { + atomic_dec(&dev->simple_cmds); + smp_mb__after_atomic(); + dev->dev_cur_ordered_id++; + pr_debug("Incremented dev->dev_cur_ordered_id: %u for" + " SIMPLE: %u\n", dev->dev_cur_ordered_id, + cmd->se_ordered_id); + } else if (cmd->sam_task_attr == MSG_HEAD_TAG) { + dev->dev_cur_ordered_id++; + pr_debug("Incremented dev_cur_ordered_id: %u for" + " HEAD_OF_QUEUE: %u\n", dev->dev_cur_ordered_id, + cmd->se_ordered_id); + } else if (cmd->sam_task_attr == MSG_ORDERED_TAG) { + atomic_dec(&dev->dev_ordered_sync); + smp_mb__after_atomic(); - sense_buffer = TRANSPORT(dev)->get_sense_buffer(task); - if (!(sense_buffer)) { - printk(KERN_ERR "ITT[0x%08x]_TASK[%d]: Unable to locate" - " sense buffer for task with sense\n", - CMD_TFO(cmd)->get_task_tag(cmd), task->task_no); - continue; - } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - offset = CMD_TFO(cmd)->set_fabric_sense_len(cmd, - TRANSPORT_SENSE_BUFFER); - - memcpy((void *)&buffer[offset], (void *)sense_buffer, - TRANSPORT_SENSE_BUFFER); - cmd->scsi_status = task->task_scsi_status; - /* Automatically padded */ - cmd->scsi_sense_length = - (TRANSPORT_SENSE_BUFFER + offset); - - printk(KERN_INFO "HBA_[%u]_PLUG[%s]: Set SAM STATUS: 0x%02x" - " and sense\n", - dev->se_hba->hba_id, TRANSPORT(dev)->name, - cmd->scsi_status); - return 0; + dev->dev_cur_ordered_id++; + pr_debug("Incremented dev_cur_ordered_id: %u for ORDERED:" + " %u\n", dev->dev_cur_ordered_id, cmd->se_ordered_id); } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - return -1; + target_restart_delayed_cmds(dev); } -static int transport_allocate_resources(struct se_cmd *cmd) +static void transport_complete_qf(struct se_cmd *cmd) { - u32 length = cmd->data_length; - - if ((cmd->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB) || - (cmd->se_cmd_flags & SCF_SCSI_CONTROL_SG_IO_CDB)) - return transport_generic_get_mem(cmd, length, PAGE_SIZE); - else if (cmd->se_cmd_flags & SCF_SCSI_CONTROL_NONSG_IO_CDB) - return transport_generic_allocate_buf(cmd, length); - else - return 0; -} + int ret = 0; -static int -transport_handle_reservation_conflict(struct se_cmd *cmd) -{ - cmd->transport_wait_for_tasks = &transport_nop_wait_for_tasks; - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->se_cmd_flags |= SCF_SCSI_RESERVATION_CONFLICT; - cmd->scsi_status = SAM_STAT_RESERVATION_CONFLICT; - /* - * For UA Interlock Code 11b, a RESERVATION CONFLICT will - * establish a UNIT ATTENTION with PREVIOUS RESERVATION - * CONFLICT STATUS. - * - * See spc4r17, section 7.4.6 Control Mode Page, Table 349 - */ - if (SE_SESS(cmd) && - DEV_ATTRIB(cmd->se_dev)->emulate_ua_intlck_ctrl == 2) - core_scsi3_ua_allocate(SE_SESS(cmd)->se_node_acl, - cmd->orig_fe_lun, 0x2C, - ASCQ_2CH_PREVIOUS_RESERVATION_CONFLICT_STATUS); - return -2; -} + transport_complete_task_attr(cmd); -/* transport_generic_cmd_sequencer(): - * - * Generic Command Sequencer that should work for most DAS transport - * drivers. - * - * Called from transport_generic_allocate_tasks() in the $FABRIC_MOD - * RX Thread. - * - * FIXME: Need to support other SCSI OPCODES where as well. - */ -static int transport_generic_cmd_sequencer( - struct se_cmd *cmd, - unsigned char *cdb) -{ - struct se_device *dev = SE_DEV(cmd); - struct se_subsystem_dev *su_dev = dev->se_sub_dev; - int ret = 0, sector_ret = 0, passthrough; - u32 sectors = 0, size = 0, pr_reg_type = 0; - u16 service_action; - u8 alua_ascq = 0; - /* - * Check for an existing UNIT ATTENTION condition - */ - if (core_scsi3_ua_check(cmd, cdb) < 0) { - cmd->transport_wait_for_tasks = - &transport_nop_wait_for_tasks; - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->scsi_sense_reason = TCM_CHECK_CONDITION_UNIT_ATTENTION; - return -2; - } - /* - * Check status of Asymmetric Logical Unit Assignment port - */ - ret = T10_ALUA(su_dev)->alua_state_check(cmd, cdb, &alua_ascq); - if (ret != 0) { - cmd->transport_wait_for_tasks = &transport_nop_wait_for_tasks; - /* - * Set SCSI additional sense code (ASC) to 'LUN Not Accessable'; - * The ALUA additional sense code qualifier (ASCQ) is determined - * by the ALUA primary or secondary access state.. - */ - if (ret > 0) { -#if 0 - printk(KERN_INFO "[%s]: ALUA TG Port not available," - " SenseKey: NOT_READY, ASC/ASCQ: 0x04/0x%02x\n", - CMD_TFO(cmd)->get_fabric_name(), alua_ascq); -#endif - transport_set_sense_codes(cmd, 0x04, alua_ascq); - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->scsi_sense_reason = TCM_CHECK_CONDITION_NOT_READY; - return -2; - } - goto out_invalid_cdb_field; - } - /* - * Check status for SPC-3 Persistent Reservations - */ - if (T10_PR_OPS(su_dev)->t10_reservation_check(cmd, &pr_reg_type) != 0) { - if (T10_PR_OPS(su_dev)->t10_seq_non_holder( - cmd, cdb, pr_reg_type) != 0) - return transport_handle_reservation_conflict(cmd); - /* - * This means the CDB is allowed for the SCSI Initiator port - * when said port is *NOT* holding the legacy SPC-2 or - * SPC-3 Persistent Reservation. - */ + if (cmd->se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) { + trace_target_cmd_complete(cmd); + ret = cmd->se_tfo->queue_status(cmd); + if (ret) + goto out; } - switch (cdb[0]) { - case READ_6: - sectors = transport_get_sectors_6(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_6; - T_TASK(cmd)->t_task_lba = transport_lba_21(cdb); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - break; - case READ_10: - sectors = transport_get_sectors_10(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_10; - T_TASK(cmd)->t_task_lba = transport_lba_32(cdb); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - break; - case READ_12: - sectors = transport_get_sectors_12(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_12; - T_TASK(cmd)->t_task_lba = transport_lba_32(cdb); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - break; - case READ_16: - sectors = transport_get_sectors_16(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_16; - T_TASK(cmd)->t_task_lba = transport_lba_64(cdb); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - break; - case WRITE_6: - sectors = transport_get_sectors_6(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_6; - T_TASK(cmd)->t_task_lba = transport_lba_21(cdb); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - break; - case WRITE_10: - sectors = transport_get_sectors_10(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_10; - T_TASK(cmd)->t_task_lba = transport_lba_32(cdb); - T_TASK(cmd)->t_tasks_fua = (cdb[1] & 0x8); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - break; - case WRITE_12: - sectors = transport_get_sectors_12(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_12; - T_TASK(cmd)->t_task_lba = transport_lba_32(cdb); - T_TASK(cmd)->t_tasks_fua = (cdb[1] & 0x8); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - break; - case WRITE_16: - sectors = transport_get_sectors_16(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_16; - T_TASK(cmd)->t_task_lba = transport_lba_64(cdb); - T_TASK(cmd)->t_tasks_fua = (cdb[1] & 0x8); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - break; - case XDWRITEREAD_10: - if ((cmd->data_direction != DMA_TO_DEVICE) || - !(T_TASK(cmd)->t_tasks_bidi)) - goto out_invalid_cdb_field; - sectors = transport_get_sectors_10(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - cmd->transport_split_cdb = &split_cdb_XX_10; - T_TASK(cmd)->t_task_lba = transport_lba_32(cdb); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - passthrough = (TRANSPORT(dev)->transport_type == - TRANSPORT_PLUGIN_PHBA_PDEV); - /* - * Skip the remaining assignments for TCM/PSCSI passthrough - */ - if (passthrough) - break; - /* - * Setup BIDI XOR callback to be run during transport_generic_complete_ok() - */ - cmd->transport_complete_callback = &transport_xor_callback; - T_TASK(cmd)->t_tasks_fua = (cdb[1] & 0x8); + switch (cmd->data_direction) { + case DMA_FROM_DEVICE: + trace_target_cmd_complete(cmd); + ret = cmd->se_tfo->queue_data_in(cmd); break; - case VARIABLE_LENGTH_CMD: - service_action = get_unaligned_be16(&cdb[8]); - /* - * Determine if this is TCM/PSCSI device and we should disable - * internal emulation for this CDB. - */ - passthrough = (TRANSPORT(dev)->transport_type == - TRANSPORT_PLUGIN_PHBA_PDEV); - - switch (service_action) { - case XDWRITEREAD_32: - sectors = transport_get_sectors_32(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - /* - * Use WRITE_32 and READ_32 opcodes for the emulated - * XDWRITE_READ_32 logic. - */ - cmd->transport_split_cdb = &split_cdb_XX_32; - T_TASK(cmd)->t_task_lba = transport_lba_64_ext(cdb); - cmd->se_cmd_flags |= SCF_SCSI_DATA_SG_IO_CDB; - - /* - * Skip the remaining assignments for TCM/PSCSI passthrough - */ - if (passthrough) - break; - - /* - * Setup BIDI XOR callback to be run during - * transport_generic_complete_ok() - */ - cmd->transport_complete_callback = &transport_xor_callback; - T_TASK(cmd)->t_tasks_fua = (cdb[10] & 0x8); - break; - case WRITE_SAME_32: - sectors = transport_get_sectors_32(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - T_TASK(cmd)->t_task_lba = get_unaligned_be64(&cdb[12]); - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_SG_IO_CDB; - - /* - * Skip the remaining assignments for TCM/PSCSI passthrough - */ - if (passthrough) + case DMA_TO_DEVICE: + if (cmd->se_cmd_flags & SCF_BIDI) { + ret = cmd->se_tfo->queue_data_in(cmd); + if (ret < 0) break; - - if ((cdb[10] & 0x04) || (cdb[10] & 0x02)) { - printk(KERN_ERR "WRITE_SAME PBDATA and LBDATA" - " bits not supported for Block Discard" - " Emulation\n"); - goto out_invalid_cdb_field; - } - /* - * Currently for the emulated case we only accept - * tpws with the UNMAP=1 bit set. - */ - if (!(cdb[10] & 0x08)) { - printk(KERN_ERR "WRITE_SAME w/o UNMAP bit not" - " supported for Block Discard Emulation\n"); - goto out_invalid_cdb_field; - } - break; - default: - printk(KERN_ERR "VARIABLE_LENGTH_CMD service action" - " 0x%04x not supported\n", service_action); - goto out_unsupported_cdb; - } - break; - case 0xa3: - if (TRANSPORT(dev)->get_device_type(dev) != TYPE_ROM) { - /* MAINTENANCE_IN from SCC-2 */ - /* - * Check for emulated MI_REPORT_TARGET_PGS. - */ - if (cdb[1] == MI_REPORT_TARGET_PGS) { - cmd->transport_emulate_cdb = - (T10_ALUA(su_dev)->alua_type == - SPC3_ALUA_EMULATED) ? - &core_emulate_report_target_port_groups : - NULL; - } - size = (cdb[6] << 24) | (cdb[7] << 16) | - (cdb[8] << 8) | cdb[9]; - } else { - /* GPCMD_SEND_KEY from multi media commands */ - size = (cdb[8] << 8) + cdb[9]; } - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case MODE_SELECT: - size = cdb[4]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_SG_IO_CDB; - break; - case MODE_SELECT_10: - size = (cdb[7] << 8) + cdb[8]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_SG_IO_CDB; - break; - case MODE_SENSE: - size = cdb[4]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case MODE_SENSE_10: - case GPCMD_READ_BUFFER_CAPACITY: - case GPCMD_SEND_OPC: - case LOG_SELECT: - case LOG_SENSE: - size = (cdb[7] << 8) + cdb[8]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case READ_BLOCK_LIMITS: - size = READ_BLOCK_LEN; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case GPCMD_GET_CONFIGURATION: - case GPCMD_READ_FORMAT_CAPACITIES: - case GPCMD_READ_DISC_INFO: - case GPCMD_READ_TRACK_RZONE_INFO: - size = (cdb[7] << 8) + cdb[8]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_SG_IO_CDB; - break; - case PERSISTENT_RESERVE_IN: - case PERSISTENT_RESERVE_OUT: - cmd->transport_emulate_cdb = - (T10_RES(su_dev)->res_type == - SPC3_PERSISTENT_RESERVATIONS) ? - &core_scsi3_emulate_pr : NULL; - size = (cdb[7] << 8) + cdb[8]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case GPCMD_MECHANISM_STATUS: - case GPCMD_READ_DVD_STRUCTURE: - size = (cdb[8] << 8) + cdb[9]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_SG_IO_CDB; - break; - case READ_POSITION: - size = READ_POSITION_LEN; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case 0xa4: - if (TRANSPORT(dev)->get_device_type(dev) != TYPE_ROM) { - /* MAINTENANCE_OUT from SCC-2 - * - * Check for emulated MO_SET_TARGET_PGS. - */ - if (cdb[1] == MO_SET_TARGET_PGS) { - cmd->transport_emulate_cdb = - (T10_ALUA(su_dev)->alua_type == - SPC3_ALUA_EMULATED) ? - &core_emulate_set_target_port_groups : - NULL; - } - - size = (cdb[6] << 24) | (cdb[7] << 16) | - (cdb[8] << 8) | cdb[9]; - } else { - /* GPCMD_REPORT_KEY from multi media commands */ - size = (cdb[8] << 8) + cdb[9]; - } - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case INQUIRY: - size = (cdb[3] << 8) + cdb[4]; - /* - * Do implict HEAD_OF_QUEUE processing for INQUIRY. - * See spc4r17 section 5.3 - */ - if (SE_DEV(cmd)->dev_task_attr_type == SAM_TASK_ATTR_EMULATED) - cmd->sam_task_attr = TASK_ATTR_HOQ; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case READ_BUFFER: - size = (cdb[6] << 16) + (cdb[7] << 8) + cdb[8]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case READ_CAPACITY: - size = READ_CAP_LEN; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case READ_MEDIA_SERIAL_NUMBER: - case SECURITY_PROTOCOL_IN: - case SECURITY_PROTOCOL_OUT: - size = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case SERVICE_ACTION_IN: - case ACCESS_CONTROL_IN: - case ACCESS_CONTROL_OUT: - case EXTENDED_COPY: - case READ_ATTRIBUTE: - case RECEIVE_COPY_RESULTS: - case WRITE_ATTRIBUTE: - size = (cdb[10] << 24) | (cdb[11] << 16) | - (cdb[12] << 8) | cdb[13]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case RECEIVE_DIAGNOSTIC: - case SEND_DIAGNOSTIC: - size = (cdb[3] << 8) | cdb[4]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; -/* #warning FIXME: Figure out correct GPCMD_READ_CD blocksize. */ -#if 0 - case GPCMD_READ_CD: - sectors = (cdb[6] << 16) + (cdb[7] << 8) + cdb[8]; - size = (2336 * sectors); - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; -#endif - case READ_TOC: - size = cdb[8]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case REQUEST_SENSE: - size = cdb[4]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case READ_ELEMENT_STATUS: - size = 65536 * cdb[7] + 256 * cdb[8] + cdb[9]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case WRITE_BUFFER: - size = (cdb[6] << 16) + (cdb[7] << 8) + cdb[8]; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case RESERVE: - case RESERVE_10: - /* - * The SPC-2 RESERVE does not contain a size in the SCSI CDB. - * Assume the passthrough or $FABRIC_MOD will tell us about it. - */ - if (cdb[0] == RESERVE_10) - size = (cdb[7] << 8) | cdb[8]; - else - size = cmd->data_length; - - /* - * Setup the legacy emulated handler for SPC-2 and - * >= SPC-3 compatible reservation handling (CRH=1) - * Otherwise, we assume the underlying SCSI logic is - * is running in SPC_PASSTHROUGH, and wants reservations - * emulation disabled. - */ - cmd->transport_emulate_cdb = - (T10_RES(su_dev)->res_type != - SPC_PASSTHROUGH) ? - &core_scsi2_emulate_crh : NULL; - cmd->se_cmd_flags |= SCF_SCSI_NON_DATA_CDB; - break; - case RELEASE: - case RELEASE_10: - /* - * The SPC-2 RELEASE does not contain a size in the SCSI CDB. - * Assume the passthrough or $FABRIC_MOD will tell us about it. - */ - if (cdb[0] == RELEASE_10) - size = (cdb[7] << 8) | cdb[8]; - else - size = cmd->data_length; - - cmd->transport_emulate_cdb = - (T10_RES(su_dev)->res_type != - SPC_PASSTHROUGH) ? - &core_scsi2_emulate_crh : NULL; - cmd->se_cmd_flags |= SCF_SCSI_NON_DATA_CDB; - break; - case SYNCHRONIZE_CACHE: - case 0x91: /* SYNCHRONIZE_CACHE_16: */ - /* - * Extract LBA and range to be flushed for emulated SYNCHRONIZE_CACHE - */ - if (cdb[0] == SYNCHRONIZE_CACHE) { - sectors = transport_get_sectors_10(cdb, cmd, §or_ret); - T_TASK(cmd)->t_task_lba = transport_lba_32(cdb); - } else { - sectors = transport_get_sectors_16(cdb, cmd, §or_ret); - T_TASK(cmd)->t_task_lba = transport_lba_64(cdb); - } - if (sector_ret) - goto out_unsupported_cdb; - - size = transport_get_size(sectors, cdb, cmd); - cmd->se_cmd_flags |= SCF_SCSI_NON_DATA_CDB; - - /* - * For TCM/pSCSI passthrough, skip cmd->transport_emulate_cdb() - */ - if (TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) - break; - /* - * Set SCF_EMULATE_CDB_ASYNC to ensure asynchronous operation - * for SYNCHRONIZE_CACHE* Immed=1 case in __transport_execute_tasks() - */ - cmd->se_cmd_flags |= SCF_EMULATE_CDB_ASYNC; - /* - * Check to ensure that LBA + Range does not exceed past end of - * device. - */ - if (transport_get_sectors(cmd) < 0) - goto out_invalid_cdb_field; - break; - case UNMAP: - size = get_unaligned_be16(&cdb[7]); - passthrough = (TRANSPORT(dev)->transport_type == - TRANSPORT_PLUGIN_PHBA_PDEV); - /* - * Determine if the received UNMAP used to for direct passthrough - * into Linux/SCSI with struct request via TCM/pSCSI or we are - * signaling the use of internal transport_generic_unmap() emulation - * for UNMAP -> Linux/BLOCK disbard with TCM/IBLOCK and TCM/FILEIO - * subsystem plugin backstores. - */ - if (!(passthrough)) - cmd->se_cmd_flags |= SCF_EMULATE_SYNC_UNMAP; - - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; - break; - case WRITE_SAME_16: - sectors = transport_get_sectors_16(cdb, cmd, §or_ret); - if (sector_ret) - goto out_unsupported_cdb; - size = transport_get_size(sectors, cdb, cmd); - T_TASK(cmd)->t_task_lba = get_unaligned_be16(&cdb[2]); - passthrough = (TRANSPORT(dev)->transport_type == - TRANSPORT_PLUGIN_PHBA_PDEV); - /* - * Determine if the received WRITE_SAME_16 is used to for direct - * passthrough into Linux/SCSI with struct request via TCM/pSCSI - * or we are signaling the use of internal WRITE_SAME + UNMAP=1 - * emulation for -> Linux/BLOCK disbard with TCM/IBLOCK and - * TCM/FILEIO subsystem plugin backstores. - */ - if (!(passthrough)) { - if ((cdb[1] & 0x04) || (cdb[1] & 0x02)) { - printk(KERN_ERR "WRITE_SAME PBDATA and LBDATA" - " bits not supported for Block Discard" - " Emulation\n"); - goto out_invalid_cdb_field; - } - /* - * Currently for the emulated case we only accept - * tpws with the UNMAP=1 bit set. - */ - if (!(cdb[1] & 0x08)) { - printk(KERN_ERR "WRITE_SAME w/o UNMAP bit not " - " supported for Block Discard Emulation\n"); - goto out_invalid_cdb_field; - } - } - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_SG_IO_CDB; - break; - case ALLOW_MEDIUM_REMOVAL: - case GPCMD_CLOSE_TRACK: - case ERASE: - case INITIALIZE_ELEMENT_STATUS: - case GPCMD_LOAD_UNLOAD: - case REZERO_UNIT: - case SEEK_10: - case GPCMD_SET_SPEED: - case SPACE: - case START_STOP: - case TEST_UNIT_READY: - case VERIFY: - case WRITE_FILEMARKS: - case MOVE_MEDIUM: - cmd->se_cmd_flags |= SCF_SCSI_NON_DATA_CDB; - break; - case REPORT_LUNS: - cmd->transport_emulate_cdb = - &transport_core_report_lun_response; - size = (cdb[6] << 24) | (cdb[7] << 16) | (cdb[8] << 8) | cdb[9]; - /* - * Do implict HEAD_OF_QUEUE processing for REPORT_LUNS - * See spc4r17 section 5.3 - */ - if (SE_DEV(cmd)->dev_task_attr_type == SAM_TASK_ATTR_EMULATED) - cmd->sam_task_attr = TASK_ATTR_HOQ; - cmd->se_cmd_flags |= SCF_SCSI_CONTROL_NONSG_IO_CDB; + /* Fall through for DMA_TO_DEVICE */ + case DMA_NONE: + trace_target_cmd_complete(cmd); + ret = cmd->se_tfo->queue_status(cmd); break; default: - printk(KERN_WARNING "TARGET_CORE[%s]: Unsupported SCSI Opcode" - " 0x%02x, sending CHECK_CONDITION.\n", - CMD_TFO(cmd)->get_fabric_name(), cdb[0]); - cmd->transport_wait_for_tasks = &transport_nop_wait_for_tasks; - goto out_unsupported_cdb; + break; } - if (size != cmd->data_length) { - printk(KERN_WARNING "TARGET_CORE[%s]: Expected Transfer Length:" - " %u does not match SCSI CDB Length: %u for SAM Opcode:" - " 0x%02x\n", CMD_TFO(cmd)->get_fabric_name(), - cmd->data_length, size, cdb[0]); - - cmd->cmd_spdtl = size; - - if (cmd->data_direction == DMA_TO_DEVICE) { - printk(KERN_ERR "Rejecting underflow/overflow" - " WRITE data\n"); - goto out_invalid_cdb_field; - } - /* - * Reject READ_* or WRITE_* with overflow/underflow for - * type SCF_SCSI_DATA_SG_IO_CDB. - */ - if (!(ret) && (DEV_ATTRIB(dev)->block_size != 512)) { - printk(KERN_ERR "Failing OVERFLOW/UNDERFLOW for LBA op" - " CDB on non 512-byte sector setup subsystem" - " plugin: %s\n", TRANSPORT(dev)->name); - /* Returns CHECK_CONDITION + INVALID_CDB_FIELD */ - goto out_invalid_cdb_field; - } - - if (size > cmd->data_length) { - cmd->se_cmd_flags |= SCF_OVERFLOW_BIT; - cmd->residual_count = (size - cmd->data_length); - } else { - cmd->se_cmd_flags |= SCF_UNDERFLOW_BIT; - cmd->residual_count = (cmd->data_length - size); - } - cmd->data_length = size; +out: + if (ret < 0) { + transport_handle_queue_full(cmd, cmd->se_dev); + return; } - - transport_set_supported_SAM_opcode(cmd); - return ret; - -out_unsupported_cdb: - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->scsi_sense_reason = TCM_UNSUPPORTED_SCSI_OPCODE; - return -2; -out_invalid_cdb_field: - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD; - return -2; + transport_lun_remove_cmd(cmd); + transport_cmd_check_stop_to_fabric(cmd); } -static inline void transport_release_tasks(struct se_cmd *); - -/* - * This function will copy a contiguous *src buffer into a destination - * struct scatterlist array. - */ -static void transport_memcpy_write_contig( +static void transport_handle_queue_full( struct se_cmd *cmd, - struct scatterlist *sg_d, - unsigned char *src) + struct se_device *dev) { - u32 i = 0, length = 0, total_length = cmd->data_length; - void *dst; - - while (total_length) { - length = sg_d[i].length; - - if (length > total_length) - length = total_length; - - dst = sg_virt(&sg_d[i]); - - memcpy(dst, src, length); - - if (!(total_length -= length)) - return; + spin_lock_irq(&dev->qf_cmd_lock); + list_add_tail(&cmd->se_qf_node, &cmd->se_dev->qf_cmd_list); + atomic_inc(&dev->dev_qf_count); + smp_mb__after_atomic(); + spin_unlock_irq(&cmd->se_dev->qf_cmd_lock); - src += length; - i++; - } + schedule_work(&cmd->se_dev->qf_work_queue); } -/* - * This function will copy a struct scatterlist array *sg_s into a destination - * contiguous *dst buffer. - */ -static void transport_memcpy_read_contig( - struct se_cmd *cmd, - unsigned char *dst, - struct scatterlist *sg_s) +static bool target_check_read_strip(struct se_cmd *cmd) { - u32 i = 0, length = 0, total_length = cmd->data_length; - void *src; - - while (total_length) { - length = sg_s[i].length; - - if (length > total_length) - length = total_length; + sense_reason_t rc; - src = sg_virt(&sg_s[i]); - - memcpy(dst, src, length); - - if (!(total_length -= length)) - return; - - dst += length; - i++; + if (!(cmd->se_sess->sup_prot_ops & TARGET_PROT_DIN_STRIP)) { + rc = sbc_dif_read_strip(cmd); + if (rc) { + cmd->pi_err = rc; + return true; + } } -} - -static void transport_memcpy_se_mem_read_contig( - struct se_cmd *cmd, - unsigned char *dst, - struct list_head *se_mem_list) -{ - struct se_mem *se_mem; - void *src; - u32 length = 0, total_length = cmd->data_length; - - list_for_each_entry(se_mem, se_mem_list, se_list) { - length = se_mem->se_len; - - if (length > total_length) - length = total_length; - - src = page_address(se_mem->se_page) + se_mem->se_off; - memcpy(dst, src, length); - - if (!(total_length -= length)) - return; - - dst += length; - } + return false; } -/* - * Called from transport_generic_complete_ok() and - * transport_generic_request_failure() to determine which dormant/delayed - * and ordered cmds need to have their tasks added to the execution queue. - */ -static void transport_complete_task_attr(struct se_cmd *cmd) +static void target_complete_ok_work(struct work_struct *work) { - struct se_device *dev = SE_DEV(cmd); - struct se_cmd *cmd_p, *cmd_tmp; - int new_active_tasks = 0; - - if (cmd->sam_task_attr == TASK_ATTR_SIMPLE) { - atomic_dec(&dev->simple_cmds); - smp_mb__after_atomic_dec(); - dev->dev_cur_ordered_id++; - DEBUG_STA("Incremented dev->dev_cur_ordered_id: %u for" - " SIMPLE: %u\n", dev->dev_cur_ordered_id, - cmd->se_ordered_id); - } else if (cmd->sam_task_attr == TASK_ATTR_HOQ) { - atomic_dec(&dev->dev_hoq_count); - smp_mb__after_atomic_dec(); - dev->dev_cur_ordered_id++; - DEBUG_STA("Incremented dev_cur_ordered_id: %u for" - " HEAD_OF_QUEUE: %u\n", dev->dev_cur_ordered_id, - cmd->se_ordered_id); - } else if (cmd->sam_task_attr == TASK_ATTR_ORDERED) { - spin_lock(&dev->ordered_cmd_lock); - list_del(&cmd->se_ordered_list); - atomic_dec(&dev->dev_ordered_sync); - smp_mb__after_atomic_dec(); - spin_unlock(&dev->ordered_cmd_lock); + struct se_cmd *cmd = container_of(work, struct se_cmd, work); + int ret; - dev->dev_cur_ordered_id++; - DEBUG_STA("Incremented dev_cur_ordered_id: %u for ORDERED:" - " %u\n", dev->dev_cur_ordered_id, cmd->se_ordered_id); - } /* - * Process all commands up to the last received - * ORDERED task attribute which requires another blocking - * boundary + * Check if we need to move delayed/dormant tasks from cmds on the + * delayed execution list after a HEAD_OF_QUEUE or ORDERED Task + * Attribute. */ - spin_lock(&dev->delayed_cmd_lock); - list_for_each_entry_safe(cmd_p, cmd_tmp, - &dev->delayed_cmd_list, se_delayed_list) { - - list_del(&cmd_p->se_delayed_list); - spin_unlock(&dev->delayed_cmd_lock); + transport_complete_task_attr(cmd); - DEBUG_STA("Calling add_tasks() for" - " cmd_p: 0x%02x Task Attr: 0x%02x" - " Dormant -> Active, se_ordered_id: %u\n", - T_TASK(cmd_p)->t_task_cdb[0], - cmd_p->sam_task_attr, cmd_p->se_ordered_id); - - transport_add_tasks_from_cmd(cmd_p); - new_active_tasks++; - - spin_lock(&dev->delayed_cmd_lock); - if (cmd_p->sam_task_attr == TASK_ATTR_ORDERED) - break; - } - spin_unlock(&dev->delayed_cmd_lock); /* - * If new tasks have become active, wake up the transport thread - * to do the processing of the Active tasks. + * Check to schedule QUEUE_FULL work, or execute an existing + * cmd->transport_qf_callback() */ - if (new_active_tasks != 0) - wake_up_interruptible(&dev->dev_queue_obj->thread_wq); -} + if (atomic_read(&cmd->se_dev->dev_qf_count) != 0) + schedule_work(&cmd->se_dev->qf_work_queue); -static void transport_generic_complete_ok(struct se_cmd *cmd) -{ - int reason = 0; - /* - * Check if we need to move delayed/dormant tasks from cmds on the - * delayed execution list after a HEAD_OF_QUEUE or ORDERED Task - * Attribute. - */ - if (SE_DEV(cmd)->dev_task_attr_type == SAM_TASK_ATTR_EMULATED) - transport_complete_task_attr(cmd); /* - * Check if we need to retrieve a sense buffer from + * Check if we need to send a sense buffer from * the struct se_cmd in question. */ if (cmd->se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) { - if (transport_get_sense_data(cmd) < 0) - reason = TCM_NON_EXISTENT_LUN; + WARN_ON(!cmd->scsi_status); + ret = transport_send_check_condition_and_sense( + cmd, 0, 1); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; + + transport_lun_remove_cmd(cmd); + transport_cmd_check_stop_to_fabric(cmd); + return; + } + /* + * Check for a callback, used by amongst other things + * XDWRITE_READ_10 and COMPARE_AND_WRITE emulation. + */ + if (cmd->transport_complete_callback) { + sense_reason_t rc; + + rc = cmd->transport_complete_callback(cmd); + if (!rc && !(cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE_POST)) { + return; + } else if (rc) { + ret = transport_send_check_condition_and_sense(cmd, + rc, 0); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; - /* - * Only set when an struct se_task->task_scsi_status returned - * a non GOOD status. - */ - if (cmd->scsi_status) { - transport_send_check_condition_and_sense( - cmd, reason, 1); transport_lun_remove_cmd(cmd); transport_cmd_check_stop_to_fabric(cmd); return; } } - /* - * Check for a callback, used by amoungst other things - * XDWRITE_READ_10 emulation. - */ - if (cmd->transport_complete_callback) - cmd->transport_complete_callback(cmd); switch (cmd->data_direction) { case DMA_FROM_DEVICE: spin_lock(&cmd->se_lun->lun_sep_lock); - if (SE_LUN(cmd)->lun_sep) { - SE_LUN(cmd)->lun_sep->sep_stats.tx_data_octets += + if (cmd->se_lun->lun_sep) { + cmd->se_lun->lun_sep->sep_stats.tx_data_octets += cmd->data_length; } spin_unlock(&cmd->se_lun->lun_sep_lock); /* - * If enabled by TCM fabirc module pre-registered SGL - * memory, perform the memcpy() from the TCM internal - * contigious buffer back to the original SGL. + * Perform READ_STRIP of PI using software emulation when + * backend had PI enabled, if the transport will not be + * performing hardware READ_STRIP offload. */ - if (cmd->se_cmd_flags & SCF_PASSTHROUGH_CONTIG_TO_SG) - transport_memcpy_write_contig(cmd, - T_TASK(cmd)->t_task_pt_sgl, - T_TASK(cmd)->t_task_buf); + if (cmd->prot_op == TARGET_PROT_DIN_STRIP && + target_check_read_strip(cmd)) { + ret = transport_send_check_condition_and_sense(cmd, + cmd->pi_err, 0); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; - CMD_TFO(cmd)->queue_data_in(cmd); + transport_lun_remove_cmd(cmd); + transport_cmd_check_stop_to_fabric(cmd); + return; + } + + trace_target_cmd_complete(cmd); + ret = cmd->se_tfo->queue_data_in(cmd); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; break; case DMA_TO_DEVICE: spin_lock(&cmd->se_lun->lun_sep_lock); - if (SE_LUN(cmd)->lun_sep) { - SE_LUN(cmd)->lun_sep->sep_stats.rx_data_octets += + if (cmd->se_lun->lun_sep) { + cmd->se_lun->lun_sep->sep_stats.rx_data_octets += cmd->data_length; } spin_unlock(&cmd->se_lun->lun_sep_lock); /* * Check if we need to send READ payload for BIDI-COMMAND */ - if (T_TASK(cmd)->t_mem_bidi_list != NULL) { + if (cmd->se_cmd_flags & SCF_BIDI) { spin_lock(&cmd->se_lun->lun_sep_lock); - if (SE_LUN(cmd)->lun_sep) { - SE_LUN(cmd)->lun_sep->sep_stats.tx_data_octets += + if (cmd->se_lun->lun_sep) { + cmd->se_lun->lun_sep->sep_stats.tx_data_octets += cmd->data_length; } spin_unlock(&cmd->se_lun->lun_sep_lock); - CMD_TFO(cmd)->queue_data_in(cmd); + ret = cmd->se_tfo->queue_data_in(cmd); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; break; } /* Fall through for DMA_TO_DEVICE */ case DMA_NONE: - CMD_TFO(cmd)->queue_status(cmd); + trace_target_cmd_complete(cmd); + ret = cmd->se_tfo->queue_status(cmd); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; break; default: break; @@ -3927,1659 +2059,515 @@ static void transport_generic_complete_ok(struct se_cmd *cmd) transport_lun_remove_cmd(cmd); transport_cmd_check_stop_to_fabric(cmd); + return; + +queue_full: + pr_debug("Handling complete_ok QUEUE_FULL: se_cmd: %p," + " data_direction: %d\n", cmd, cmd->data_direction); + cmd->t_state = TRANSPORT_COMPLETE_QF_OK; + transport_handle_queue_full(cmd, cmd->se_dev); } -static void transport_free_dev_tasks(struct se_cmd *cmd) +static inline void transport_free_sgl(struct scatterlist *sgl, int nents) { - struct se_task *task, *task_tmp; - unsigned long flags; - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - list_for_each_entry_safe(task, task_tmp, - &T_TASK(cmd)->t_task_list, t_list) { - if (atomic_read(&task->task_active)) - continue; - - kfree(task->task_sg_bidi); - kfree(task->task_sg); + struct scatterlist *sg; + int count; - list_del(&task->t_list); + for_each_sg(sgl, sg, nents, count) + __free_page(sg_page(sg)); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - if (task->se_dev) - TRANSPORT(task->se_dev)->free_task(task); - else - printk(KERN_ERR "task[%u] - task->se_dev is NULL\n", - task->task_no); - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); + kfree(sgl); } -static inline void transport_free_pages(struct se_cmd *cmd) +static inline void transport_reset_sgl_orig(struct se_cmd *cmd) { - struct se_mem *se_mem, *se_mem_tmp; - int free_page = 1; - - if (cmd->se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC) - free_page = 0; - if (cmd->se_dev->transport->do_se_mem_map) - free_page = 0; - - if (T_TASK(cmd)->t_task_buf) { - kfree(T_TASK(cmd)->t_task_buf); - T_TASK(cmd)->t_task_buf = NULL; - return; - } - /* - * Caller will handle releasing of struct se_mem. + * Check for saved t_data_sg that may be used for COMPARE_AND_WRITE + * emulation, and free + reset pointers if necessary.. */ - if (cmd->se_cmd_flags & SCF_CMD_PASSTHROUGH_NOALLOC) + if (!cmd->t_data_sg_orig) return; - if (!(T_TASK(cmd)->t_tasks_se_num)) - return; - - list_for_each_entry_safe(se_mem, se_mem_tmp, - T_TASK(cmd)->t_mem_list, se_list) { - /* - * We only release call __free_page(struct se_mem->se_page) when - * SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC is NOT in use, - */ - if (free_page) - __free_page(se_mem->se_page); - - list_del(&se_mem->se_list); - kmem_cache_free(se_mem_cache, se_mem); - } - - if (T_TASK(cmd)->t_mem_bidi_list && T_TASK(cmd)->t_tasks_se_bidi_num) { - list_for_each_entry_safe(se_mem, se_mem_tmp, - T_TASK(cmd)->t_mem_bidi_list, se_list) { - /* - * We only release call __free_page(struct se_mem->se_page) when - * SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC is NOT in use, - */ - if (free_page) - __free_page(se_mem->se_page); - - list_del(&se_mem->se_list); - kmem_cache_free(se_mem_cache, se_mem); - } - } - - kfree(T_TASK(cmd)->t_mem_bidi_list); - T_TASK(cmd)->t_mem_bidi_list = NULL; - kfree(T_TASK(cmd)->t_mem_list); - T_TASK(cmd)->t_mem_list = NULL; - T_TASK(cmd)->t_tasks_se_num = 0; + kfree(cmd->t_data_sg); + cmd->t_data_sg = cmd->t_data_sg_orig; + cmd->t_data_sg_orig = NULL; + cmd->t_data_nents = cmd->t_data_nents_orig; + cmd->t_data_nents_orig = 0; } -static inline void transport_release_tasks(struct se_cmd *cmd) -{ - transport_free_dev_tasks(cmd); -} - -static inline int transport_dec_and_check(struct se_cmd *cmd) -{ - unsigned long flags; - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (atomic_read(&T_TASK(cmd)->t_fe_count)) { - if (!(atomic_dec_and_test(&T_TASK(cmd)->t_fe_count))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, - flags); - return 1; - } - } - - if (atomic_read(&T_TASK(cmd)->t_se_count)) { - if (!(atomic_dec_and_test(&T_TASK(cmd)->t_se_count))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, - flags); - return 1; - } - } - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - return 0; -} - -static void transport_release_fe_cmd(struct se_cmd *cmd) +static inline void transport_free_pages(struct se_cmd *cmd) { - unsigned long flags; - - if (transport_dec_and_check(cmd)) + if (cmd->se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC) { + transport_reset_sgl_orig(cmd); return; - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (!(atomic_read(&T_TASK(cmd)->transport_dev_active))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - goto free_pages; - } - atomic_set(&T_TASK(cmd)->transport_dev_active, 0); - transport_all_task_dev_remove_state(cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - - transport_release_tasks(cmd); -free_pages: - transport_free_pages(cmd); - transport_free_se_cmd(cmd); - CMD_TFO(cmd)->release_cmd_direct(cmd); -} - -static int transport_generic_remove( - struct se_cmd *cmd, - int release_to_pool, - int session_reinstatement) -{ - unsigned long flags; - - if (!(T_TASK(cmd))) - goto release_cmd; - - if (transport_dec_and_check(cmd)) { - if (session_reinstatement) { - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - transport_all_task_dev_remove_state(cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, - flags); - } - return 1; - } - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (!(atomic_read(&T_TASK(cmd)->transport_dev_active))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - goto free_pages; } - atomic_set(&T_TASK(cmd)->transport_dev_active, 0); - transport_all_task_dev_remove_state(cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); + transport_reset_sgl_orig(cmd); - transport_release_tasks(cmd); -free_pages: - transport_free_pages(cmd); + transport_free_sgl(cmd->t_data_sg, cmd->t_data_nents); + cmd->t_data_sg = NULL; + cmd->t_data_nents = 0; -release_cmd: - if (release_to_pool) { - transport_release_cmd_to_pool(cmd); - } else { - transport_free_se_cmd(cmd); - CMD_TFO(cmd)->release_cmd_direct(cmd); - } + transport_free_sgl(cmd->t_bidi_data_sg, cmd->t_bidi_data_nents); + cmd->t_bidi_data_sg = NULL; + cmd->t_bidi_data_nents = 0; - return 0; + transport_free_sgl(cmd->t_prot_sg, cmd->t_prot_nents); + cmd->t_prot_sg = NULL; + cmd->t_prot_nents = 0; } -/* - * transport_generic_map_mem_to_cmd - Perform SGL -> struct se_mem map - * @cmd: Associated se_cmd descriptor - * @mem: SGL style memory for TCM WRITE / READ - * @sg_mem_num: Number of SGL elements - * @mem_bidi_in: SGL style memory for TCM BIDI READ - * @sg_mem_bidi_num: Number of BIDI READ SGL elements +/** + * transport_release_cmd - free a command + * @cmd: command to free * - * Return: nonzero return cmd was rejected for -ENOMEM or inproper usage - * of parameters. + * This routine unconditionally frees a command, and reference counting + * or list removal must be done in the caller. */ -int transport_generic_map_mem_to_cmd( - struct se_cmd *cmd, - struct scatterlist *mem, - u32 sg_mem_num, - struct scatterlist *mem_bidi_in, - u32 sg_mem_bidi_num) +static int transport_release_cmd(struct se_cmd *cmd) { - u32 se_mem_cnt_out = 0; - int ret; + BUG_ON(!cmd->se_tfo); - if (!(mem) || !(sg_mem_num)) - return 0; - /* - * Passed *mem will contain a list_head containing preformatted - * struct se_mem elements... - */ - if (!(cmd->se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM)) { - if ((mem_bidi_in) || (sg_mem_bidi_num)) { - printk(KERN_ERR "SCF_CMD_PASSTHROUGH_NOALLOC not supported" - " with BIDI-COMMAND\n"); - return -ENOSYS; - } - - T_TASK(cmd)->t_mem_list = (struct list_head *)mem; - T_TASK(cmd)->t_tasks_se_num = sg_mem_num; - cmd->se_cmd_flags |= SCF_CMD_PASSTHROUGH_NOALLOC; - return 0; - } + if (cmd->se_cmd_flags & SCF_SCSI_TMR_CDB) + core_tmr_release_req(cmd->se_tmr_req); + if (cmd->t_task_cdb != cmd->__t_task_cdb) + kfree(cmd->t_task_cdb); /* - * Otherwise, assume the caller is passing a struct scatterlist - * array from include/linux/scatterlist.h + * If this cmd has been setup with target_get_sess_cmd(), drop + * the kref and call ->release_cmd() in kref callback. */ - if ((cmd->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB) || - (cmd->se_cmd_flags & SCF_SCSI_CONTROL_SG_IO_CDB)) { - /* - * For CDB using TCM struct se_mem linked list scatterlist memory - * processed into a TCM struct se_subsystem_dev, we do the mapping - * from the passed physical memory to struct se_mem->se_page here. - */ - T_TASK(cmd)->t_mem_list = transport_init_se_mem_list(); - if (!(T_TASK(cmd)->t_mem_list)) - return -ENOMEM; - - ret = transport_map_sg_to_mem(cmd, - T_TASK(cmd)->t_mem_list, mem, &se_mem_cnt_out); - if (ret < 0) - return -ENOMEM; - - T_TASK(cmd)->t_tasks_se_num = se_mem_cnt_out; - /* - * Setup BIDI READ list of struct se_mem elements - */ - if ((mem_bidi_in) && (sg_mem_bidi_num)) { - T_TASK(cmd)->t_mem_bidi_list = transport_init_se_mem_list(); - if (!(T_TASK(cmd)->t_mem_bidi_list)) { - kfree(T_TASK(cmd)->t_mem_list); - return -ENOMEM; - } - se_mem_cnt_out = 0; - - ret = transport_map_sg_to_mem(cmd, - T_TASK(cmd)->t_mem_bidi_list, mem_bidi_in, - &se_mem_cnt_out); - if (ret < 0) { - kfree(T_TASK(cmd)->t_mem_list); - return -ENOMEM; - } - - T_TASK(cmd)->t_tasks_se_bidi_num = se_mem_cnt_out; - } - cmd->se_cmd_flags |= SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC; - - } else if (cmd->se_cmd_flags & SCF_SCSI_CONTROL_NONSG_IO_CDB) { - if (mem_bidi_in || sg_mem_bidi_num) { - printk(KERN_ERR "BIDI-Commands not supported using " - "SCF_SCSI_CONTROL_NONSG_IO_CDB\n"); - return -ENOSYS; - } - /* - * For incoming CDBs using a contiguous buffer internall with TCM, - * save the passed struct scatterlist memory. After TCM storage object - * processing has completed for this struct se_cmd, TCM core will call - * transport_memcpy_[write,read]_contig() as necessary from - * transport_generic_complete_ok() and transport_write_pending() in order - * to copy the TCM buffer to/from the original passed *mem in SGL -> - * struct scatterlist format. - */ - cmd->se_cmd_flags |= SCF_PASSTHROUGH_CONTIG_TO_SG; - T_TASK(cmd)->t_task_pt_sgl = mem; - } - - return 0; + return target_put_sess_cmd(cmd->se_sess, cmd); } -EXPORT_SYMBOL(transport_generic_map_mem_to_cmd); - -static inline long long transport_dev_end_lba(struct se_device *dev) +/** + * transport_put_cmd - release a reference to a command + * @cmd: command to release + * + * This routine releases our reference to the command and frees it if possible. + */ +static int transport_put_cmd(struct se_cmd *cmd) { - return dev->transport->get_blocks(dev) + 1; + transport_free_pages(cmd); + return transport_release_cmd(cmd); } -static int transport_get_sectors(struct se_cmd *cmd) +void *transport_kmap_data_sg(struct se_cmd *cmd) { - struct se_device *dev = SE_DEV(cmd); - - T_TASK(cmd)->t_tasks_sectors = - (cmd->data_length / DEV_ATTRIB(dev)->block_size); - if (!(T_TASK(cmd)->t_tasks_sectors)) - T_TASK(cmd)->t_tasks_sectors = 1; - - if (TRANSPORT(dev)->get_device_type(dev) != TYPE_DISK) - return 0; - - if ((T_TASK(cmd)->t_task_lba + T_TASK(cmd)->t_tasks_sectors) > - transport_dev_end_lba(dev)) { - printk(KERN_ERR "LBA: %llu Sectors: %u exceeds" - " transport_dev_end_lba(): %llu\n", - T_TASK(cmd)->t_task_lba, T_TASK(cmd)->t_tasks_sectors, - transport_dev_end_lba(dev)); - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->scsi_sense_reason = TCM_SECTOR_COUNT_TOO_MANY; - return PYX_TRANSPORT_REQ_TOO_MANY_SECTORS; - } + struct scatterlist *sg = cmd->t_data_sg; + struct page **pages; + int i; - return 0; -} + /* + * We need to take into account a possible offset here for fabrics like + * tcm_loop who may be using a contig buffer from the SCSI midlayer for + * control CDBs passed as SGLs via transport_generic_map_mem_to_cmd() + */ + if (!cmd->t_data_nents) + return NULL; -static int transport_new_cmd_obj(struct se_cmd *cmd) -{ - struct se_device *dev = SE_DEV(cmd); - u32 task_cdbs = 0, rc; + BUG_ON(!sg); + if (cmd->t_data_nents == 1) + return kmap(sg_page(sg)) + sg->offset; - if (!(cmd->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB)) { - task_cdbs++; - T_TASK(cmd)->t_task_cdbs++; - } else { - int set_counts = 1; + /* >1 page. use vmap */ + pages = kmalloc(sizeof(*pages) * cmd->t_data_nents, GFP_KERNEL); + if (!pages) + return NULL; - /* - * Setup any BIDI READ tasks and memory from - * T_TASK(cmd)->t_mem_bidi_list so the READ struct se_tasks - * are queued first for the non pSCSI passthrough case. - */ - if ((T_TASK(cmd)->t_mem_bidi_list != NULL) && - (TRANSPORT(dev)->transport_type != TRANSPORT_PLUGIN_PHBA_PDEV)) { - rc = transport_generic_get_cdb_count(cmd, - T_TASK(cmd)->t_task_lba, - T_TASK(cmd)->t_tasks_sectors, - DMA_FROM_DEVICE, T_TASK(cmd)->t_mem_bidi_list, - set_counts); - if (!(rc)) { - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->scsi_sense_reason = - TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - return PYX_TRANSPORT_LU_COMM_FAILURE; - } - set_counts = 0; - } - /* - * Setup the tasks and memory from T_TASK(cmd)->t_mem_list - * Note for BIDI transfers this will contain the WRITE payload - */ - task_cdbs = transport_generic_get_cdb_count(cmd, - T_TASK(cmd)->t_task_lba, - T_TASK(cmd)->t_tasks_sectors, - cmd->data_direction, T_TASK(cmd)->t_mem_list, - set_counts); - if (!(task_cdbs)) { - cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION; - cmd->scsi_sense_reason = - TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - return PYX_TRANSPORT_LU_COMM_FAILURE; - } - T_TASK(cmd)->t_task_cdbs += task_cdbs; - -#if 0 - printk(KERN_INFO "data_length: %u, LBA: %llu t_tasks_sectors:" - " %u, t_task_cdbs: %u\n", obj_ptr, cmd->data_length, - T_TASK(cmd)->t_task_lba, T_TASK(cmd)->t_tasks_sectors, - T_TASK(cmd)->t_task_cdbs); -#endif + /* convert sg[] to pages[] */ + for_each_sg(cmd->t_data_sg, sg, cmd->t_data_nents, i) { + pages[i] = sg_page(sg); } - atomic_set(&T_TASK(cmd)->t_task_cdbs_left, task_cdbs); - atomic_set(&T_TASK(cmd)->t_task_cdbs_ex_left, task_cdbs); - atomic_set(&T_TASK(cmd)->t_task_cdbs_timeout_left, task_cdbs); - return 0; + cmd->t_data_vmap = vmap(pages, cmd->t_data_nents, VM_MAP, PAGE_KERNEL); + kfree(pages); + if (!cmd->t_data_vmap) + return NULL; + + return cmd->t_data_vmap + cmd->t_data_sg[0].offset; } +EXPORT_SYMBOL(transport_kmap_data_sg); -static struct list_head *transport_init_se_mem_list(void) +void transport_kunmap_data_sg(struct se_cmd *cmd) { - struct list_head *se_mem_list; - - se_mem_list = kzalloc(sizeof(struct list_head), GFP_KERNEL); - if (!(se_mem_list)) { - printk(KERN_ERR "Unable to allocate memory for se_mem_list\n"); - return NULL; + if (!cmd->t_data_nents) { + return; + } else if (cmd->t_data_nents == 1) { + kunmap(sg_page(cmd->t_data_sg)); + return; } - INIT_LIST_HEAD(se_mem_list); - return se_mem_list; + vunmap(cmd->t_data_vmap); + cmd->t_data_vmap = NULL; } +EXPORT_SYMBOL(transport_kunmap_data_sg); -static int -transport_generic_get_mem(struct se_cmd *cmd, u32 length, u32 dma_size) +int +target_alloc_sgl(struct scatterlist **sgl, unsigned int *nents, u32 length, + bool zero_page) { - unsigned char *buf; - struct se_mem *se_mem; - - T_TASK(cmd)->t_mem_list = transport_init_se_mem_list(); - if (!(T_TASK(cmd)->t_mem_list)) + struct scatterlist *sg; + struct page *page; + gfp_t zero_flag = (zero_page) ? __GFP_ZERO : 0; + unsigned int nent; + int i = 0; + + nent = DIV_ROUND_UP(length, PAGE_SIZE); + sg = kmalloc(sizeof(struct scatterlist) * nent, GFP_KERNEL); + if (!sg) return -ENOMEM; - /* - * If the device uses memory mapping this is enough. - */ - if (cmd->se_dev->transport->do_se_mem_map) - return 0; - - /* - * Setup BIDI-COMMAND READ list of struct se_mem elements - */ - if (T_TASK(cmd)->t_tasks_bidi) { - T_TASK(cmd)->t_mem_bidi_list = transport_init_se_mem_list(); - if (!(T_TASK(cmd)->t_mem_bidi_list)) { - kfree(T_TASK(cmd)->t_mem_list); - return -ENOMEM; - } - } + sg_init_table(sg, nent); while (length) { - se_mem = kmem_cache_zalloc(se_mem_cache, GFP_KERNEL); - if (!(se_mem)) { - printk(KERN_ERR "Unable to allocate struct se_mem\n"); + u32 page_len = min_t(u32, length, PAGE_SIZE); + page = alloc_page(GFP_KERNEL | zero_flag); + if (!page) goto out; - } - INIT_LIST_HEAD(&se_mem->se_list); - se_mem->se_len = (length > dma_size) ? dma_size : length; -/* #warning FIXME Allocate contigous pages for struct se_mem elements */ - se_mem->se_page = (struct page *) alloc_pages(GFP_KERNEL, 0); - if (!(se_mem->se_page)) { - printk(KERN_ERR "alloc_pages() failed\n"); - goto out; - } - - buf = kmap_atomic(se_mem->se_page, KM_IRQ0); - if (!(buf)) { - printk(KERN_ERR "kmap_atomic() failed\n"); - goto out; - } - memset(buf, 0, se_mem->se_len); - kunmap_atomic(buf, KM_IRQ0); - - list_add_tail(&se_mem->se_list, T_TASK(cmd)->t_mem_list); - T_TASK(cmd)->t_tasks_se_num++; - - DEBUG_MEM("Allocated struct se_mem page(%p) Length(%u)" - " Offset(%u)\n", se_mem->se_page, se_mem->se_len, - se_mem->se_off); - - length -= se_mem->se_len; + sg_set_page(&sg[i], page, page_len, 0); + length -= page_len; + i++; } - - DEBUG_MEM("Allocated total struct se_mem elements(%u)\n", - T_TASK(cmd)->t_tasks_se_num); - + *sgl = sg; + *nents = nent; return 0; + out: - return -1; + while (i > 0) { + i--; + __free_page(sg_page(&sg[i])); + } + kfree(sg); + return -ENOMEM; } -extern u32 transport_calc_sg_num( - struct se_task *task, - struct se_mem *in_se_mem, - u32 task_offset) +/* + * Allocate any required resources to execute the command. For writes we + * might not have the payload yet, so notify the fabric via a call to + * ->write_pending instead. Otherwise place it on the execution queue. + */ +sense_reason_t +transport_generic_new_cmd(struct se_cmd *cmd) { - struct se_cmd *se_cmd = task->task_se_cmd; - struct se_device *se_dev = SE_DEV(se_cmd); - struct se_mem *se_mem = in_se_mem; - struct target_core_fabric_ops *tfo = CMD_TFO(se_cmd); - u32 sg_length, task_size = task->task_size, task_sg_num_padded; - - while (task_size != 0) { - DEBUG_SC("se_mem->se_page(%p) se_mem->se_len(%u)" - " se_mem->se_off(%u) task_offset(%u)\n", - se_mem->se_page, se_mem->se_len, - se_mem->se_off, task_offset); - - if (task_offset == 0) { - if (task_size >= se_mem->se_len) { - sg_length = se_mem->se_len; - - if (!(list_is_last(&se_mem->se_list, - T_TASK(se_cmd)->t_mem_list))) - se_mem = list_entry(se_mem->se_list.next, - struct se_mem, se_list); - } else { - sg_length = task_size; - task_size -= sg_length; - goto next; - } + int ret = 0; - DEBUG_SC("sg_length(%u) task_size(%u)\n", - sg_length, task_size); - } else { - if ((se_mem->se_len - task_offset) > task_size) { - sg_length = task_size; - task_size -= sg_length; - goto next; - } else { - sg_length = (se_mem->se_len - task_offset); - - if (!(list_is_last(&se_mem->se_list, - T_TASK(se_cmd)->t_mem_list))) - se_mem = list_entry(se_mem->se_list.next, - struct se_mem, se_list); - } + /* + * Determine is the TCM fabric module has already allocated physical + * memory, and is directly calling transport_generic_map_mem_to_cmd() + * beforehand. + */ + if (!(cmd->se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC) && + cmd->data_length) { + bool zero_flag = !(cmd->se_cmd_flags & SCF_SCSI_DATA_CDB); - DEBUG_SC("sg_length(%u) task_size(%u)\n", - sg_length, task_size); + if ((cmd->se_cmd_flags & SCF_BIDI) || + (cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE)) { + u32 bidi_length; - task_offset = 0; - } - task_size -= sg_length; -next: - DEBUG_SC("task[%u] - Reducing task_size to(%u)\n", - task->task_no, task_size); + if (cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE) + bidi_length = cmd->t_task_nolb * + cmd->se_dev->dev_attrib.block_size; + else + bidi_length = cmd->data_length; - task->task_sg_num++; - } - /* - * Check if the fabric module driver is requesting that all - * struct se_task->task_sg[] be chained together.. If so, - * then allocate an extra padding SG entry for linking and - * marking the end of the chained SGL. - */ - if (tfo->task_sg_chaining) { - task_sg_num_padded = (task->task_sg_num + 1); - task->task_padded_sg = 1; - } else - task_sg_num_padded = task->task_sg_num; + ret = target_alloc_sgl(&cmd->t_bidi_data_sg, + &cmd->t_bidi_data_nents, + bidi_length, zero_flag); + if (ret < 0) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } - task->task_sg = kzalloc(task_sg_num_padded * - sizeof(struct scatterlist), GFP_KERNEL); - if (!(task->task_sg)) { - printk(KERN_ERR "Unable to allocate memory for" - " task->task_sg\n"); - return 0; - } - sg_init_table(&task->task_sg[0], task_sg_num_padded); - /* - * Setup task->task_sg_bidi for SCSI READ payload for - * TCM/pSCSI passthrough if present for BIDI-COMMAND - */ - if ((T_TASK(se_cmd)->t_mem_bidi_list != NULL) && - (TRANSPORT(se_dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV)) { - task->task_sg_bidi = kzalloc(task_sg_num_padded * - sizeof(struct scatterlist), GFP_KERNEL); - if (!(task->task_sg_bidi)) { - printk(KERN_ERR "Unable to allocate memory for" - " task->task_sg_bidi\n"); - return 0; + if (cmd->prot_op != TARGET_PROT_NORMAL) { + ret = target_alloc_sgl(&cmd->t_prot_sg, + &cmd->t_prot_nents, + cmd->prot_length, true); + if (ret < 0) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } - sg_init_table(&task->task_sg_bidi[0], task_sg_num_padded); + + ret = target_alloc_sgl(&cmd->t_data_sg, &cmd->t_data_nents, + cmd->data_length, zero_flag); + if (ret < 0) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; } /* - * For the chaining case, setup the proper end of SGL for the - * initial submission struct task into struct se_subsystem_api. - * This will be cleared later by transport_do_task_sg_chain() + * If this command is not a write we can execute it right here, + * for write buffers we need to notify the fabric driver first + * and let it call back once the write buffers are ready. */ - if (task->task_padded_sg) { - sg_mark_end(&task->task_sg[task->task_sg_num - 1]); - /* - * Added the 'if' check before marking end of bi-directional - * scatterlist (which gets created only in case of request - * (RD + WR). - */ - if (task->task_sg_bidi) - sg_mark_end(&task->task_sg_bidi[task->task_sg_num - 1]); + target_add_to_state_list(cmd); + if (cmd->data_direction != DMA_TO_DEVICE) { + target_execute_cmd(cmd); + return 0; } + transport_cmd_check_stop(cmd, false, true); - DEBUG_SC("Successfully allocated task->task_sg_num(%u)," - " task_sg_num_padded(%u)\n", task->task_sg_num, - task_sg_num_padded); + ret = cmd->se_tfo->write_pending(cmd); + if (ret == -EAGAIN || ret == -ENOMEM) + goto queue_full; - return task->task_sg_num; -} + /* fabric drivers should only return -EAGAIN or -ENOMEM as error */ + WARN_ON(ret); -static inline int transport_set_tasks_sectors_disk( - struct se_task *task, - struct se_device *dev, - unsigned long long lba, - u32 sectors, - int *max_sectors_set) -{ - if ((lba + sectors) > transport_dev_end_lba(dev)) { - task->task_sectors = ((transport_dev_end_lba(dev) - lba) + 1); - - if (task->task_sectors > DEV_ATTRIB(dev)->max_sectors) { - task->task_sectors = DEV_ATTRIB(dev)->max_sectors; - *max_sectors_set = 1; - } - } else { - if (sectors > DEV_ATTRIB(dev)->max_sectors) { - task->task_sectors = DEV_ATTRIB(dev)->max_sectors; - *max_sectors_set = 1; - } else - task->task_sectors = sectors; - } - - return 0; -} - -static inline int transport_set_tasks_sectors_non_disk( - struct se_task *task, - struct se_device *dev, - unsigned long long lba, - u32 sectors, - int *max_sectors_set) -{ - if (sectors > DEV_ATTRIB(dev)->max_sectors) { - task->task_sectors = DEV_ATTRIB(dev)->max_sectors; - *max_sectors_set = 1; - } else - task->task_sectors = sectors; + return (!ret) ? 0 : TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; +queue_full: + pr_debug("Handling write_pending QUEUE__FULL: se_cmd: %p\n", cmd); + cmd->t_state = TRANSPORT_COMPLETE_QF_WP; + transport_handle_queue_full(cmd, cmd->se_dev); return 0; } +EXPORT_SYMBOL(transport_generic_new_cmd); -static inline int transport_set_tasks_sectors( - struct se_task *task, - struct se_device *dev, - unsigned long long lba, - u32 sectors, - int *max_sectors_set) +static void transport_write_pending_qf(struct se_cmd *cmd) { - return (TRANSPORT(dev)->get_device_type(dev) == TYPE_DISK) ? - transport_set_tasks_sectors_disk(task, dev, lba, sectors, - max_sectors_set) : - transport_set_tasks_sectors_non_disk(task, dev, lba, sectors, - max_sectors_set); -} - -static int transport_map_sg_to_mem( - struct se_cmd *cmd, - struct list_head *se_mem_list, - void *in_mem, - u32 *se_mem_cnt) -{ - struct se_mem *se_mem; - struct scatterlist *sg; - u32 sg_count = 1, cmd_size = cmd->data_length; - - if (!in_mem) { - printk(KERN_ERR "No source scatterlist\n"); - return -1; - } - sg = (struct scatterlist *)in_mem; + int ret; - while (cmd_size) { - se_mem = kmem_cache_zalloc(se_mem_cache, GFP_KERNEL); - if (!(se_mem)) { - printk(KERN_ERR "Unable to allocate struct se_mem\n"); - return -1; - } - INIT_LIST_HEAD(&se_mem->se_list); - DEBUG_MEM("sg_to_mem: Starting loop with cmd_size: %u" - " sg_page: %p offset: %d length: %d\n", cmd_size, - sg_page(sg), sg->offset, sg->length); - - se_mem->se_page = sg_page(sg); - se_mem->se_off = sg->offset; - - if (cmd_size > sg->length) { - se_mem->se_len = sg->length; - sg = sg_next(sg); - sg_count++; - } else - se_mem->se_len = cmd_size; - - cmd_size -= se_mem->se_len; - - DEBUG_MEM("sg_to_mem: *se_mem_cnt: %u cmd_size: %u\n", - *se_mem_cnt, cmd_size); - DEBUG_MEM("sg_to_mem: Final se_page: %p se_off: %d se_len: %d\n", - se_mem->se_page, se_mem->se_off, se_mem->se_len); - - list_add_tail(&se_mem->se_list, se_mem_list); - (*se_mem_cnt)++; + ret = cmd->se_tfo->write_pending(cmd); + if (ret == -EAGAIN || ret == -ENOMEM) { + pr_debug("Handling write_pending QUEUE__FULL: se_cmd: %p\n", + cmd); + transport_handle_queue_full(cmd, cmd->se_dev); } - - DEBUG_MEM("task[0] - Mapped(%u) struct scatterlist segments to(%u)" - " struct se_mem\n", sg_count, *se_mem_cnt); - - if (sg_count != *se_mem_cnt) - BUG(); - - return 0; } -/* transport_map_mem_to_sg(): - * - * - */ -int transport_map_mem_to_sg( - struct se_task *task, - struct list_head *se_mem_list, - void *in_mem, - struct se_mem *in_se_mem, - struct se_mem **out_se_mem, - u32 *se_mem_cnt, - u32 *task_offset) +int transport_generic_free_cmd(struct se_cmd *cmd, int wait_for_tasks) { - struct se_cmd *se_cmd = task->task_se_cmd; - struct se_mem *se_mem = in_se_mem; - struct scatterlist *sg = (struct scatterlist *)in_mem; - u32 task_size = task->task_size, sg_no = 0; - - if (!sg) { - printk(KERN_ERR "Unable to locate valid struct" - " scatterlist pointer\n"); - return -1; - } + unsigned long flags; + int ret = 0; + + if (!(cmd->se_cmd_flags & SCF_SE_LUN_CMD)) { + if (wait_for_tasks && (cmd->se_cmd_flags & SCF_SCSI_TMR_CDB)) + transport_wait_for_tasks(cmd); - while (task_size != 0) { + ret = transport_release_cmd(cmd); + } else { + if (wait_for_tasks) + transport_wait_for_tasks(cmd); /* - * Setup the contigious array of scatterlists for - * this struct se_task. + * Handle WRITE failure case where transport_generic_new_cmd() + * has already added se_cmd to state_list, but fabric has + * failed command before I/O submission. */ - sg_assign_page(sg, se_mem->se_page); - - if (*task_offset == 0) { - sg->offset = se_mem->se_off; - - if (task_size >= se_mem->se_len) { - sg->length = se_mem->se_len; - - if (!(list_is_last(&se_mem->se_list, - T_TASK(se_cmd)->t_mem_list))) { - se_mem = list_entry(se_mem->se_list.next, - struct se_mem, se_list); - (*se_mem_cnt)++; - } - } else { - sg->length = task_size; - /* - * Determine if we need to calculate an offset - * into the struct se_mem on the next go around.. - */ - task_size -= sg->length; - if (!(task_size)) - *task_offset = sg->length; - - goto next; - } - - } else { - sg->offset = (*task_offset + se_mem->se_off); - - if ((se_mem->se_len - *task_offset) > task_size) { - sg->length = task_size; - /* - * Determine if we need to calculate an offset - * into the struct se_mem on the next go around.. - */ - task_size -= sg->length; - if (!(task_size)) - *task_offset += sg->length; - - goto next; - } else { - sg->length = (se_mem->se_len - *task_offset); - - if (!(list_is_last(&se_mem->se_list, - T_TASK(se_cmd)->t_mem_list))) { - se_mem = list_entry(se_mem->se_list.next, - struct se_mem, se_list); - (*se_mem_cnt)++; - } - } - - *task_offset = 0; + if (cmd->state_active) { + spin_lock_irqsave(&cmd->t_state_lock, flags); + target_remove_from_state_list(cmd); + spin_unlock_irqrestore(&cmd->t_state_lock, flags); } - task_size -= sg->length; -next: - DEBUG_MEM("task[%u] mem_to_sg - sg[%u](%p)(%u)(%u) - Reducing" - " task_size to(%u), task_offset: %u\n", task->task_no, sg_no, - sg_page(sg), sg->length, sg->offset, task_size, *task_offset); - - sg_no++; - if (!(task_size)) - break; - sg = sg_next(sg); + if (cmd->se_lun) + transport_lun_remove_cmd(cmd); - if (task_size > se_cmd->data_length) - BUG(); + ret = transport_put_cmd(cmd); } - *out_se_mem = se_mem; - - DEBUG_MEM("task[%u] - Mapped(%u) struct se_mem segments to total(%u)" - " SGs\n", task->task_no, *se_mem_cnt, sg_no); - - return 0; + return ret; } +EXPORT_SYMBOL(transport_generic_free_cmd); -/* - * This function can be used by HW target mode drivers to create a linked - * scatterlist from all contiguously allocated struct se_task->task_sg[]. - * This is intended to be called during the completion path by TCM Core - * when struct target_core_fabric_ops->check_task_sg_chaining is enabled. +/* target_get_sess_cmd - Add command to active ->sess_cmd_list + * @se_sess: session to reference + * @se_cmd: command descriptor to add + * @ack_kref: Signal that fabric will perform an ack target_put_sess_cmd() */ -void transport_do_task_sg_chain(struct se_cmd *cmd) -{ - struct scatterlist *sg_head = NULL, *sg_link = NULL, *sg_first = NULL; - struct scatterlist *sg_head_cur = NULL, *sg_link_cur = NULL; - struct scatterlist *sg, *sg_end = NULL, *sg_end_cur = NULL; - struct se_task *task; - struct target_core_fabric_ops *tfo = CMD_TFO(cmd); - u32 task_sg_num = 0, sg_count = 0; - int i; - - if (tfo->task_sg_chaining == 0) { - printk(KERN_ERR "task_sg_chaining is diabled for fabric module:" - " %s\n", tfo->get_fabric_name()); - dump_stack(); - return; - } - /* - * Walk the struct se_task list and setup scatterlist chains - * for each contiguosly allocated struct se_task->task_sg[]. - */ - list_for_each_entry(task, &T_TASK(cmd)->t_task_list, t_list) { - if (!(task->task_sg) || !(task->task_padded_sg)) - continue; - - if (sg_head && sg_link) { - sg_head_cur = &task->task_sg[0]; - sg_link_cur = &task->task_sg[task->task_sg_num]; - /* - * Either add chain or mark end of scatterlist - */ - if (!(list_is_last(&task->t_list, - &T_TASK(cmd)->t_task_list))) { - /* - * Clear existing SGL termination bit set in - * transport_calc_sg_num(), see sg_mark_end() - */ - sg_end_cur = &task->task_sg[task->task_sg_num - 1]; - sg_end_cur->page_link &= ~0x02; - - sg_chain(sg_head, task_sg_num, sg_head_cur); - sg_count += (task->task_sg_num + 1); - } else - sg_count += task->task_sg_num; - - sg_head = sg_head_cur; - sg_link = sg_link_cur; - task_sg_num = task->task_sg_num; - continue; - } - sg_head = sg_first = &task->task_sg[0]; - sg_link = &task->task_sg[task->task_sg_num]; - task_sg_num = task->task_sg_num; - /* - * Check for single task.. - */ - if (!(list_is_last(&task->t_list, &T_TASK(cmd)->t_task_list))) { - /* - * Clear existing SGL termination bit set in - * transport_calc_sg_num(), see sg_mark_end() - */ - sg_end = &task->task_sg[task->task_sg_num - 1]; - sg_end->page_link &= ~0x02; - sg_count += (task->task_sg_num + 1); - } else - sg_count += task->task_sg_num; - } - /* - * Setup the starting pointer and total t_tasks_sg_linked_no including - * padding SGs for linking and to mark the end. - */ - T_TASK(cmd)->t_tasks_sg_chained = sg_first; - T_TASK(cmd)->t_tasks_sg_chained_no = sg_count; - - DEBUG_CMD_M("Setup T_TASK(cmd)->t_tasks_sg_chained: %p and" - " t_tasks_sg_chained_no: %u\n", T_TASK(cmd)->t_tasks_sg_chained, - T_TASK(cmd)->t_tasks_sg_chained_no); - - for_each_sg(T_TASK(cmd)->t_tasks_sg_chained, sg, - T_TASK(cmd)->t_tasks_sg_chained_no, i) { - - DEBUG_CMD_M("SG: %p page: %p length: %d offset: %d\n", - sg, sg_page(sg), sg->length, sg->offset); - if (sg_is_chain(sg)) - DEBUG_CMD_M("SG: %p sg_is_chain=1\n", sg); - if (sg_is_last(sg)) - DEBUG_CMD_M("SG: %p sg_is_last=1\n", sg); - } - -} -EXPORT_SYMBOL(transport_do_task_sg_chain); - -static int transport_do_se_mem_map( - struct se_device *dev, - struct se_task *task, - struct list_head *se_mem_list, - void *in_mem, - struct se_mem *in_se_mem, - struct se_mem **out_se_mem, - u32 *se_mem_cnt, - u32 *task_offset_in) +int target_get_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cmd, + bool ack_kref) { - u32 task_offset = *task_offset_in; + unsigned long flags; int ret = 0; - /* - * se_subsystem_api_t->do_se_mem_map is used when internal allocation - * has been done by the transport plugin. - */ - if (TRANSPORT(dev)->do_se_mem_map) { - ret = TRANSPORT(dev)->do_se_mem_map(task, se_mem_list, - in_mem, in_se_mem, out_se_mem, se_mem_cnt, - task_offset_in); - if (ret == 0) - T_TASK(task->task_se_cmd)->t_tasks_se_num += *se_mem_cnt; - return ret; - } - - BUG_ON(list_empty(se_mem_list)); /* - * This is the normal path for all normal non BIDI and BIDI-COMMAND - * WRITE payloads.. If we need to do BIDI READ passthrough for - * TCM/pSCSI the first call to transport_do_se_mem_map -> - * transport_calc_sg_num() -> transport_map_mem_to_sg() will do the - * allocation for task->task_sg_bidi, and the subsequent call to - * transport_do_se_mem_map() from transport_generic_get_cdb_count() + * Add a second kref if the fabric caller is expecting to handle + * fabric acknowledgement that requires two target_put_sess_cmd() + * invocations before se_cmd descriptor release. */ - if (!(task->task_sg_bidi)) { - /* - * Assume default that transport plugin speaks preallocated - * scatterlists. - */ - if (!(transport_calc_sg_num(task, in_se_mem, task_offset))) - return -1; - /* - * struct se_task->task_sg now contains the struct scatterlist array. - */ - return transport_map_mem_to_sg(task, se_mem_list, task->task_sg, - in_se_mem, out_se_mem, se_mem_cnt, - task_offset_in); + if (ack_kref) { + kref_get(&se_cmd->cmd_kref); + se_cmd->se_cmd_flags |= SCF_ACK_KREF; } - /* - * Handle the se_mem_list -> struct task->task_sg_bidi - * memory map for the extra BIDI READ payload - */ - return transport_map_mem_to_sg(task, se_mem_list, task->task_sg_bidi, - in_se_mem, out_se_mem, se_mem_cnt, - task_offset_in); -} -static u32 transport_generic_get_cdb_count( - struct se_cmd *cmd, - unsigned long long lba, - u32 sectors, - enum dma_data_direction data_direction, - struct list_head *mem_list, - int set_counts) -{ - unsigned char *cdb = NULL; - struct se_task *task; - struct se_mem *se_mem = NULL, *se_mem_lout = NULL; - struct se_mem *se_mem_bidi = NULL, *se_mem_bidi_lout = NULL; - struct se_device *dev = SE_DEV(cmd); - int max_sectors_set = 0, ret; - u32 task_offset_in = 0, se_mem_cnt = 0, se_mem_bidi_cnt = 0, task_cdbs = 0; - - if (!mem_list) { - printk(KERN_ERR "mem_list is NULL in transport_generic_get" - "_cdb_count()\n"); - return 0; - } - /* - * While using RAMDISK_DR backstores is the only case where - * mem_list will ever be empty at this point. - */ - if (!(list_empty(mem_list))) - se_mem = list_entry(mem_list->next, struct se_mem, se_list); - /* - * Check for extra se_mem_bidi mapping for BIDI-COMMANDs to - * struct se_task->task_sg_bidi for TCM/pSCSI passthrough operation - */ - if ((T_TASK(cmd)->t_mem_bidi_list != NULL) && - !(list_empty(T_TASK(cmd)->t_mem_bidi_list)) && - (TRANSPORT(dev)->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV)) - se_mem_bidi = list_entry(T_TASK(cmd)->t_mem_bidi_list->next, - struct se_mem, se_list); - - while (sectors) { - DEBUG_VOL("ITT[0x%08x] LBA(%llu) SectorsLeft(%u) EOBJ(%llu)\n", - CMD_TFO(cmd)->get_task_tag(cmd), lba, sectors, - transport_dev_end_lba(dev)); - - task = transport_generic_get_task(cmd, data_direction); - if (!(task)) - goto out; - - transport_set_tasks_sectors(task, dev, lba, sectors, - &max_sectors_set); - - task->task_lba = lba; - lba += task->task_sectors; - sectors -= task->task_sectors; - task->task_size = (task->task_sectors * - DEV_ATTRIB(dev)->block_size); - - cdb = TRANSPORT(dev)->get_cdb(task); - if ((cdb)) { - memcpy(cdb, T_TASK(cmd)->t_task_cdb, - scsi_command_size(T_TASK(cmd)->t_task_cdb)); - cmd->transport_split_cdb(task->task_lba, - &task->task_sectors, cdb); - } - - /* - * Perform the SE OBJ plugin and/or Transport plugin specific - * mapping for T_TASK(cmd)->t_mem_list. And setup the - * task->task_sg and if necessary task->task_sg_bidi - */ - ret = transport_do_se_mem_map(dev, task, mem_list, - NULL, se_mem, &se_mem_lout, &se_mem_cnt, - &task_offset_in); - if (ret < 0) - goto out; - - se_mem = se_mem_lout; - /* - * Setup the T_TASK(cmd)->t_mem_bidi_list -> task->task_sg_bidi - * mapping for SCSI READ for BIDI-COMMAND passthrough with TCM/pSCSI - * - * Note that the first call to transport_do_se_mem_map() above will - * allocate struct se_task->task_sg_bidi in transport_do_se_mem_map() - * -> transport_calc_sg_num(), and the second here will do the - * mapping for SCSI READ for BIDI-COMMAND passthrough with TCM/pSCSI. - */ - if (task->task_sg_bidi != NULL) { - ret = transport_do_se_mem_map(dev, task, - T_TASK(cmd)->t_mem_bidi_list, NULL, - se_mem_bidi, &se_mem_bidi_lout, &se_mem_bidi_cnt, - &task_offset_in); - if (ret < 0) - goto out; - - se_mem_bidi = se_mem_bidi_lout; - } - task_cdbs++; - - DEBUG_VOL("Incremented task_cdbs(%u) task->task_sg_num(%u)\n", - task_cdbs, task->task_sg_num); - - if (max_sectors_set) { - max_sectors_set = 0; - continue; - } - - if (!sectors) - break; - } - - if (set_counts) { - atomic_inc(&T_TASK(cmd)->t_fe_count); - atomic_inc(&T_TASK(cmd)->t_se_count); + spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); + if (se_sess->sess_tearing_down) { + ret = -ESHUTDOWN; + goto out; } - - DEBUG_VOL("ITT[0x%08x] total %s cdbs(%u)\n", - CMD_TFO(cmd)->get_task_tag(cmd), (data_direction == DMA_TO_DEVICE) - ? "DMA_TO_DEVICE" : "DMA_FROM_DEVICE", task_cdbs); - - return task_cdbs; + list_add_tail(&se_cmd->se_cmd_list, &se_sess->sess_cmd_list); out: - return 0; -} - -static int -transport_map_control_cmd_to_task(struct se_cmd *cmd) -{ - struct se_device *dev = SE_DEV(cmd); - unsigned char *cdb; - struct se_task *task; - int ret; - - task = transport_generic_get_task(cmd, cmd->data_direction); - if (!task) - return PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES; - - cdb = TRANSPORT(dev)->get_cdb(task); - if (cdb) - memcpy(cdb, cmd->t_task->t_task_cdb, - scsi_command_size(cmd->t_task->t_task_cdb)); - - task->task_size = cmd->data_length; - task->task_sg_num = - (cmd->se_cmd_flags & SCF_SCSI_CONTROL_SG_IO_CDB) ? 1 : 0; - - atomic_inc(&cmd->t_task->t_fe_count); - atomic_inc(&cmd->t_task->t_se_count); - - if (cmd->se_cmd_flags & SCF_SCSI_CONTROL_SG_IO_CDB) { - struct se_mem *se_mem = NULL, *se_mem_lout = NULL; - u32 se_mem_cnt = 0, task_offset = 0; - - if (!list_empty(T_TASK(cmd)->t_mem_list)) - se_mem = list_entry(T_TASK(cmd)->t_mem_list->next, - struct se_mem, se_list); - - ret = transport_do_se_mem_map(dev, task, - cmd->t_task->t_mem_list, NULL, se_mem, - &se_mem_lout, &se_mem_cnt, &task_offset); - if (ret < 0) - return PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES; - - if (dev->transport->map_task_SG) - return dev->transport->map_task_SG(task); - return 0; - } else if (cmd->se_cmd_flags & SCF_SCSI_CONTROL_NONSG_IO_CDB) { - if (dev->transport->map_task_non_SG) - return dev->transport->map_task_non_SG(task); - return 0; - } else if (cmd->se_cmd_flags & SCF_SCSI_NON_DATA_CDB) { - if (dev->transport->cdb_none) - return dev->transport->cdb_none(task); - return 0; - } else { - BUG(); - return PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES; - } + spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); + return ret; } +EXPORT_SYMBOL(target_get_sess_cmd); -/* transport_generic_new_cmd(): Called from transport_processing_thread() - * - * Allocate storage transport resources from a set of values predefined - * by transport_generic_cmd_sequencer() from the iSCSI Target RX process. - * Any non zero return here is treated as an "out of resource' op here. - */ - /* - * Generate struct se_task(s) and/or their payloads for this CDB. - */ -static int transport_generic_new_cmd(struct se_cmd *cmd) +static void target_release_cmd_kref(struct kref *kref) { - struct se_portal_group *se_tpg; - struct se_task *task; - struct se_device *dev = SE_DEV(cmd); - int ret = 0; + struct se_cmd *se_cmd = container_of(kref, struct se_cmd, cmd_kref); + struct se_session *se_sess = se_cmd->se_sess; - /* - * Determine is the TCM fabric module has already allocated physical - * memory, and is directly calling transport_generic_map_mem_to_cmd() - * to setup beforehand the linked list of physical memory at - * T_TASK(cmd)->t_mem_list of struct se_mem->se_page - */ - if (!(cmd->se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC)) { - ret = transport_allocate_resources(cmd); - if (ret < 0) - return ret; - } - - ret = transport_get_sectors(cmd); - if (ret < 0) - return ret; - - ret = transport_new_cmd_obj(cmd); - if (ret < 0) - return ret; - - /* - * Determine if the calling TCM fabric module is talking to - * Linux/NET via kernel sockets and needs to allocate a - * struct iovec array to complete the struct se_cmd - */ - se_tpg = SE_LUN(cmd)->lun_sep->sep_tpg; - if (TPG_TFO(se_tpg)->alloc_cmd_iovecs != NULL) { - ret = TPG_TFO(se_tpg)->alloc_cmd_iovecs(cmd); - if (ret < 0) - return PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES; + if (list_empty(&se_cmd->se_cmd_list)) { + spin_unlock(&se_sess->sess_cmd_lock); + se_cmd->se_tfo->release_cmd(se_cmd); + return; } - - if (cmd->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB) { - list_for_each_entry(task, &T_TASK(cmd)->t_task_list, t_list) { - if (atomic_read(&task->task_sent)) - continue; - if (!dev->transport->map_task_SG) - continue; - - ret = dev->transport->map_task_SG(task); - if (ret < 0) - return ret; - } - } else { - ret = transport_map_control_cmd_to_task(cmd); - if (ret < 0) - return ret; + if (se_sess->sess_tearing_down && se_cmd->cmd_wait_set) { + spin_unlock(&se_sess->sess_cmd_lock); + complete(&se_cmd->cmd_wait_comp); + return; } + list_del(&se_cmd->se_cmd_list); + spin_unlock(&se_sess->sess_cmd_lock); - /* - * For WRITEs, let the iSCSI Target RX Thread know its buffer is ready.. - * This WRITE struct se_cmd (and all of its associated struct se_task's) - * will be added to the struct se_device execution queue after its WRITE - * data has arrived. (ie: It gets handled by the transport processing - * thread a second time) - */ - if (cmd->data_direction == DMA_TO_DEVICE) { - transport_add_tasks_to_state_queue(cmd); - return transport_generic_write_pending(cmd); - } - /* - * Everything else but a WRITE, add the struct se_cmd's struct se_task's - * to the execution queue. - */ - transport_execute_tasks(cmd); - return 0; + se_cmd->se_tfo->release_cmd(se_cmd); } -/* transport_generic_process_write(): - * - * +/* target_put_sess_cmd - Check for active I/O shutdown via kref_put + * @se_sess: session to reference + * @se_cmd: command descriptor to drop */ -void transport_generic_process_write(struct se_cmd *cmd) +int target_put_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cmd) { -#if 0 - /* - * Copy SCSI Presented DTL sector(s) from received buffers allocated to - * original EDTL - */ - if (cmd->se_cmd_flags & SCF_UNDERFLOW_BIT) { - if (!T_TASK(cmd)->t_tasks_se_num) { - unsigned char *dst, *buf = - (unsigned char *)T_TASK(cmd)->t_task_buf; - - dst = kzalloc(cmd->cmd_spdtl), GFP_KERNEL); - if (!(dst)) { - printk(KERN_ERR "Unable to allocate memory for" - " WRITE underflow\n"); - transport_generic_request_failure(cmd, NULL, - PYX_TRANSPORT_REQ_TOO_MANY_SECTORS, 1); - return; - } - memcpy(dst, buf, cmd->cmd_spdtl); - - kfree(T_TASK(cmd)->t_task_buf); - T_TASK(cmd)->t_task_buf = dst; - } else { - struct scatterlist *sg = - (struct scatterlist *sg)T_TASK(cmd)->t_task_buf; - struct scatterlist *orig_sg; - - orig_sg = kzalloc(sizeof(struct scatterlist) * - T_TASK(cmd)->t_tasks_se_num, - GFP_KERNEL))) { - if (!(orig_sg)) { - printk(KERN_ERR "Unable to allocate memory" - " for WRITE underflow\n"); - transport_generic_request_failure(cmd, NULL, - PYX_TRANSPORT_REQ_TOO_MANY_SECTORS, 1); - return; - } - - memcpy(orig_sg, T_TASK(cmd)->t_task_buf, - sizeof(struct scatterlist) * - T_TASK(cmd)->t_tasks_se_num); - - cmd->data_length = cmd->cmd_spdtl; - /* - * FIXME, clear out original struct se_task and state - * information. - */ - if (transport_generic_new_cmd(cmd) < 0) { - transport_generic_request_failure(cmd, NULL, - PYX_TRANSPORT_REQ_TOO_MANY_SECTORS, 1); - kfree(orig_sg); - return; - } - - transport_memcpy_write_sg(cmd, orig_sg); - } + if (!se_sess) { + se_cmd->se_tfo->release_cmd(se_cmd); + return 1; } -#endif - transport_execute_tasks(cmd); + return kref_put_spinlock_irqsave(&se_cmd->cmd_kref, target_release_cmd_kref, + &se_sess->sess_cmd_lock); } -EXPORT_SYMBOL(transport_generic_process_write); +EXPORT_SYMBOL(target_put_sess_cmd); -/* transport_generic_write_pending(): - * - * +/* target_sess_cmd_list_set_waiting - Flag all commands in + * sess_cmd_list to complete cmd_wait_comp. Set + * sess_tearing_down so no more commands are queued. + * @se_sess: session to flag */ -static int transport_generic_write_pending(struct se_cmd *cmd) +void target_sess_cmd_list_set_waiting(struct se_session *se_sess) { + struct se_cmd *se_cmd; unsigned long flags; - int ret; - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - cmd->t_state = TRANSPORT_WRITE_PENDING; - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - /* - * For the TCM control CDBs using a contiguous buffer, do the memcpy - * from the passed Linux/SCSI struct scatterlist located at - * T_TASK(se_cmd)->t_task_pt_buf to the contiguous buffer at - * T_TASK(se_cmd)->t_task_buf. - */ - if (cmd->se_cmd_flags & SCF_PASSTHROUGH_CONTIG_TO_SG) - transport_memcpy_read_contig(cmd, - T_TASK(cmd)->t_task_buf, - T_TASK(cmd)->t_task_pt_sgl); - /* - * Clear the se_cmd for WRITE_PENDING status in order to set - * T_TASK(cmd)->t_transport_active=0 so that transport_generic_handle_data - * can be called from HW target mode interrupt code. This is safe - * to be called with transport_off=1 before the CMD_TFO(cmd)->write_pending - * because the se_cmd->se_lun pointer is not being cleared. - */ - transport_cmd_check_stop(cmd, 1, 0); + spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); + if (se_sess->sess_tearing_down) { + spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); + return; + } + se_sess->sess_tearing_down = 1; + list_splice_init(&se_sess->sess_cmd_list, &se_sess->sess_wait_list); - /* - * Call the fabric write_pending function here to let the - * frontend know that WRITE buffers are ready. - */ - ret = CMD_TFO(cmd)->write_pending(cmd); - if (ret < 0) - return ret; + list_for_each_entry(se_cmd, &se_sess->sess_wait_list, se_cmd_list) + se_cmd->cmd_wait_set = 1; - return PYX_TRANSPORT_WRITE_PENDING; + spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); } +EXPORT_SYMBOL(target_sess_cmd_list_set_waiting); -/* transport_release_cmd_to_pool(): - * - * +/* target_wait_for_sess_cmds - Wait for outstanding descriptors + * @se_sess: session to wait for active I/O */ -void transport_release_cmd_to_pool(struct se_cmd *cmd) +void target_wait_for_sess_cmds(struct se_session *se_sess) { - BUG_ON(!T_TASK(cmd)); - BUG_ON(!CMD_TFO(cmd)); - - transport_free_se_cmd(cmd); - CMD_TFO(cmd)->release_cmd_to_pool(cmd); -} -EXPORT_SYMBOL(transport_release_cmd_to_pool); - -/* transport_generic_free_cmd(): - * - * Called from processing frontend to release storage engine resources - */ -void transport_generic_free_cmd( - struct se_cmd *cmd, - int wait_for_tasks, - int release_to_pool, - int session_reinstatement) -{ - if (!(cmd->se_cmd_flags & SCF_SE_LUN_CMD) || !T_TASK(cmd)) - transport_release_cmd_to_pool(cmd); - else { - core_dec_lacl_count(cmd->se_sess->se_node_acl, cmd); - - if (SE_LUN(cmd)) { -#if 0 - printk(KERN_INFO "cmd: %p ITT: 0x%08x contains" - " SE_LUN(cmd)\n", cmd, - CMD_TFO(cmd)->get_task_tag(cmd)); -#endif - transport_lun_remove_cmd(cmd); - } + struct se_cmd *se_cmd, *tmp_cmd; + unsigned long flags; - if (wait_for_tasks && cmd->transport_wait_for_tasks) - cmd->transport_wait_for_tasks(cmd, 0, 0); + list_for_each_entry_safe(se_cmd, tmp_cmd, + &se_sess->sess_wait_list, se_cmd_list) { + list_del(&se_cmd->se_cmd_list); - transport_generic_remove(cmd, release_to_pool, - session_reinstatement); - } -} -EXPORT_SYMBOL(transport_generic_free_cmd); + pr_debug("Waiting for se_cmd: %p t_state: %d, fabric state:" + " %d\n", se_cmd, se_cmd->t_state, + se_cmd->se_tfo->get_cmd_state(se_cmd)); -static void transport_nop_wait_for_tasks( - struct se_cmd *cmd, - int remove_cmd, - int session_reinstatement) -{ - return; -} + wait_for_completion(&se_cmd->cmd_wait_comp); + pr_debug("After cmd_wait_comp: se_cmd: %p t_state: %d" + " fabric state: %d\n", se_cmd, se_cmd->t_state, + se_cmd->se_tfo->get_cmd_state(se_cmd)); -/* transport_lun_wait_for_tasks(): - * - * Called from ConfigFS context to stop the passed struct se_cmd to allow - * an struct se_lun to be successfully shutdown. - */ -static int transport_lun_wait_for_tasks(struct se_cmd *cmd, struct se_lun *lun) -{ - unsigned long flags; - int ret; - /* - * If the frontend has already requested this struct se_cmd to - * be stopped, we can safely ignore this struct se_cmd. - */ - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - if (atomic_read(&T_TASK(cmd)->t_transport_stop)) { - atomic_set(&T_TASK(cmd)->transport_lun_stop, 0); - DEBUG_TRANSPORT_S("ConfigFS ITT[0x%08x] - t_transport_stop ==" - " TRUE, skipping\n", CMD_TFO(cmd)->get_task_tag(cmd)); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - transport_cmd_check_stop(cmd, 1, 0); - return -1; + se_cmd->se_tfo->release_cmd(se_cmd); } - atomic_set(&T_TASK(cmd)->transport_lun_fe_stop, 1); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - wake_up_interruptible(&SE_DEV(cmd)->dev_queue_obj->thread_wq); + spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); + WARN_ON(!list_empty(&se_sess->sess_cmd_list)); + spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); - ret = transport_stop_tasks_for_cmd(cmd); - - DEBUG_TRANSPORT_S("ConfigFS: cmd: %p t_task_cdbs: %d stop tasks ret:" - " %d\n", cmd, T_TASK(cmd)->t_task_cdbs, ret); - if (!ret) { - DEBUG_TRANSPORT_S("ConfigFS: ITT[0x%08x] - stopping cmd....\n", - CMD_TFO(cmd)->get_task_tag(cmd)); - wait_for_completion(&T_TASK(cmd)->transport_lun_stop_comp); - DEBUG_TRANSPORT_S("ConfigFS: ITT[0x%08x] - stopped cmd....\n", - CMD_TFO(cmd)->get_task_tag(cmd)); - } - transport_remove_cmd_from_queue(cmd, SE_DEV(cmd)->dev_queue_obj); - - return 0; } +EXPORT_SYMBOL(target_wait_for_sess_cmds); -/* #define DEBUG_CLEAR_LUN */ -#ifdef DEBUG_CLEAR_LUN -#define DEBUG_CLEAR_L(x...) printk(KERN_INFO x) -#else -#define DEBUG_CLEAR_L(x...) -#endif - -static void __transport_clear_lun_from_sessions(struct se_lun *lun) +static int transport_clear_lun_ref_thread(void *p) { - struct se_cmd *cmd = NULL; - unsigned long lun_flags, cmd_flags; - /* - * Do exception processing and return CHECK_CONDITION status to the - * Initiator Port. - */ - spin_lock_irqsave(&lun->lun_cmd_lock, lun_flags); - while (!list_empty_careful(&lun->lun_cmd_list)) { - cmd = list_entry(lun->lun_cmd_list.next, - struct se_cmd, se_lun_list); - list_del(&cmd->se_lun_list); - - if (!(T_TASK(cmd))) { - printk(KERN_ERR "ITT: 0x%08x, T_TASK(cmd) = NULL" - "[i,t]_state: %u/%u\n", - CMD_TFO(cmd)->get_task_tag(cmd), - CMD_TFO(cmd)->get_cmd_state(cmd), cmd->t_state); - BUG(); - } - atomic_set(&T_TASK(cmd)->transport_lun_active, 0); - /* - * This will notify iscsi_target_transport.c: - * transport_cmd_check_stop() that a LUN shutdown is in - * progress for the iscsi_cmd_t. - */ - spin_lock(&T_TASK(cmd)->t_state_lock); - DEBUG_CLEAR_L("SE_LUN[%d] - Setting T_TASK(cmd)->transport" - "_lun_stop for ITT: 0x%08x\n", - SE_LUN(cmd)->unpacked_lun, - CMD_TFO(cmd)->get_task_tag(cmd)); - atomic_set(&T_TASK(cmd)->transport_lun_stop, 1); - spin_unlock(&T_TASK(cmd)->t_state_lock); - - spin_unlock_irqrestore(&lun->lun_cmd_lock, lun_flags); - - if (!(SE_LUN(cmd))) { - printk(KERN_ERR "ITT: 0x%08x, [i,t]_state: %u/%u\n", - CMD_TFO(cmd)->get_task_tag(cmd), - CMD_TFO(cmd)->get_cmd_state(cmd), cmd->t_state); - BUG(); - } - /* - * If the Storage engine still owns the iscsi_cmd_t, determine - * and/or stop its context. - */ - DEBUG_CLEAR_L("SE_LUN[%d] - ITT: 0x%08x before transport" - "_lun_wait_for_tasks()\n", SE_LUN(cmd)->unpacked_lun, - CMD_TFO(cmd)->get_task_tag(cmd)); + struct se_lun *lun = p; - if (transport_lun_wait_for_tasks(cmd, SE_LUN(cmd)) < 0) { - spin_lock_irqsave(&lun->lun_cmd_lock, lun_flags); - continue; - } - - DEBUG_CLEAR_L("SE_LUN[%d] - ITT: 0x%08x after transport_lun" - "_wait_for_tasks(): SUCCESS\n", - SE_LUN(cmd)->unpacked_lun, - CMD_TFO(cmd)->get_task_tag(cmd)); - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, cmd_flags); - if (!(atomic_read(&T_TASK(cmd)->transport_dev_active))) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, cmd_flags); - goto check_cond; - } - atomic_set(&T_TASK(cmd)->transport_dev_active, 0); - transport_all_task_dev_remove_state(cmd); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, cmd_flags); + percpu_ref_kill(&lun->lun_ref); - transport_free_dev_tasks(cmd); - /* - * The Storage engine stopped this struct se_cmd before it was - * send to the fabric frontend for delivery back to the - * Initiator Node. Return this SCSI CDB back with an - * CHECK_CONDITION status. - */ -check_cond: - transport_send_check_condition_and_sense(cmd, - TCM_NON_EXISTENT_LUN, 0); - /* - * If the fabric frontend is waiting for this iscsi_cmd_t to - * be released, notify the waiting thread now that LU has - * finished accessing it. - */ - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, cmd_flags); - if (atomic_read(&T_TASK(cmd)->transport_lun_fe_stop)) { - DEBUG_CLEAR_L("SE_LUN[%d] - Detected FE stop for" - " struct se_cmd: %p ITT: 0x%08x\n", - lun->unpacked_lun, - cmd, CMD_TFO(cmd)->get_task_tag(cmd)); - - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, - cmd_flags); - transport_cmd_check_stop(cmd, 1, 0); - complete(&T_TASK(cmd)->transport_lun_fe_stop_comp); - spin_lock_irqsave(&lun->lun_cmd_lock, lun_flags); - continue; - } - DEBUG_CLEAR_L("SE_LUN[%d] - ITT: 0x%08x finished processing\n", - lun->unpacked_lun, CMD_TFO(cmd)->get_task_tag(cmd)); - - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, cmd_flags); - spin_lock_irqsave(&lun->lun_cmd_lock, lun_flags); - } - spin_unlock_irqrestore(&lun->lun_cmd_lock, lun_flags); -} - -static int transport_clear_lun_thread(void *p) -{ - struct se_lun *lun = (struct se_lun *)p; - - __transport_clear_lun_from_sessions(lun); + wait_for_completion(&lun->lun_ref_comp); complete(&lun->lun_shutdown_comp); return 0; } -int transport_clear_lun_from_sessions(struct se_lun *lun) +int transport_clear_lun_ref(struct se_lun *lun) { struct task_struct *kt; - kt = kthread_run(transport_clear_lun_thread, (void *)lun, + kt = kthread_run(transport_clear_lun_ref_thread, lun, "tcm_cl_%u", lun->unpacked_lun); if (IS_ERR(kt)) { - printk(KERN_ERR "Unable to start clear_lun thread\n"); - return -1; + pr_err("Unable to start clear_lun thread\n"); + return PTR_ERR(kt); } wait_for_completion(&lun->lun_shutdown_comp); return 0; } -/* transport_generic_wait_for_tasks(): +/** + * transport_wait_for_tasks - wait for completion to occur + * @cmd: command to wait * - * Called from frontend or passthrough context to wait for storage engine - * to pause and/or release frontend generated struct se_cmd. + * Called from frontend fabric context to wait for storage engine + * to pause and/or release frontend generated struct se_cmd. */ -static void transport_generic_wait_for_tasks( - struct se_cmd *cmd, - int remove_cmd, - int session_reinstatement) +bool transport_wait_for_tasks(struct se_cmd *cmd) { unsigned long flags; - if (!(cmd->se_cmd_flags & SCF_SE_LUN_CMD) && !(cmd->se_tmr_req)) - return; - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - /* - * If we are already stopped due to an external event (ie: LUN shutdown) - * sleep until the connection can have the passed struct se_cmd back. - * The T_TASK(cmd)->transport_lun_stopped_sem will be upped by - * transport_clear_lun_from_sessions() once the ConfigFS context caller - * has completed its operation on the struct se_cmd. - */ - if (atomic_read(&T_TASK(cmd)->transport_lun_stop)) { - - DEBUG_TRANSPORT_S("wait_for_tasks: Stopping" - " wait_for_completion(&T_TASK(cmd)transport_lun_fe" - "_stop_comp); for ITT: 0x%08x\n", - CMD_TFO(cmd)->get_task_tag(cmd)); - /* - * There is a special case for WRITES where a FE exception + - * LUN shutdown means ConfigFS context is still sleeping on - * transport_lun_stop_comp in transport_lun_wait_for_tasks(). - * We go ahead and up transport_lun_stop_comp just to be sure - * here. - */ - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - complete(&T_TASK(cmd)->transport_lun_stop_comp); - wait_for_completion(&T_TASK(cmd)->transport_lun_fe_stop_comp); - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); + spin_lock_irqsave(&cmd->t_state_lock, flags); + if (!(cmd->se_cmd_flags & SCF_SE_LUN_CMD) && + !(cmd->se_cmd_flags & SCF_SCSI_TMR_CDB)) { + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + return false; + } - transport_all_task_dev_remove_state(cmd); - /* - * At this point, the frontend who was the originator of this - * struct se_cmd, now owns the structure and can be released through - * normal means below. - */ - DEBUG_TRANSPORT_S("wait_for_tasks: Stopped" - " wait_for_completion(&T_TASK(cmd)transport_lun_fe_" - "stop_comp); for ITT: 0x%08x\n", - CMD_TFO(cmd)->get_task_tag(cmd)); + if (!(cmd->se_cmd_flags & SCF_SUPPORTED_SAM_OPCODE) && + !(cmd->se_cmd_flags & SCF_SCSI_TMR_CDB)) { + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + return false; + } - atomic_set(&T_TASK(cmd)->transport_lun_stop, 0); + if (!(cmd->transport_state & CMD_T_ACTIVE)) { + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + return false; } - if (!atomic_read(&T_TASK(cmd)->t_transport_active)) - goto remove; - atomic_set(&T_TASK(cmd)->t_transport_stop, 1); + cmd->transport_state |= CMD_T_STOP; - DEBUG_TRANSPORT_S("wait_for_tasks: Stopping %p ITT: 0x%08x" - " i_state: %d, t_state/def_t_state: %d/%d, t_transport_stop" - " = TRUE\n", cmd, CMD_TFO(cmd)->get_task_tag(cmd), - CMD_TFO(cmd)->get_cmd_state(cmd), cmd->t_state, - cmd->deferred_t_state); + pr_debug("wait_for_tasks: Stopping %p ITT: 0x%08x" + " i_state: %d, t_state: %d, CMD_T_STOP\n", + cmd, cmd->se_tfo->get_task_tag(cmd), + cmd->se_tfo->get_cmd_state(cmd), cmd->t_state); - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); + spin_unlock_irqrestore(&cmd->t_state_lock, flags); - wake_up_interruptible(&SE_DEV(cmd)->dev_queue_obj->thread_wq); + wait_for_completion(&cmd->t_transport_stop_comp); - wait_for_completion(&T_TASK(cmd)->t_transport_stop_comp); + spin_lock_irqsave(&cmd->t_state_lock, flags); + cmd->transport_state &= ~(CMD_T_ACTIVE | CMD_T_STOP); - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - atomic_set(&T_TASK(cmd)->t_transport_active, 0); - atomic_set(&T_TASK(cmd)->t_transport_stop, 0); + pr_debug("wait_for_tasks: Stopped wait_for_completion(" + "&cmd->t_transport_stop_comp) for ITT: 0x%08x\n", + cmd->se_tfo->get_task_tag(cmd)); - DEBUG_TRANSPORT_S("wait_for_tasks: Stopped wait_for_compltion(" - "&T_TASK(cmd)->t_transport_stop_comp) for ITT: 0x%08x\n", - CMD_TFO(cmd)->get_task_tag(cmd)); -remove: - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); - if (!remove_cmd) - return; + spin_unlock_irqrestore(&cmd->t_state_lock, flags); - transport_generic_free_cmd(cmd, 0, 0, session_reinstatement); + return true; } +EXPORT_SYMBOL(transport_wait_for_tasks); static int transport_get_sense_codes( struct se_cmd *cmd, @@ -5592,170 +2580,259 @@ static int transport_get_sense_codes( return 0; } -static int transport_set_sense_codes( - struct se_cmd *cmd, - u8 asc, - u8 ascq) +static +void transport_err_sector_info(unsigned char *buffer, sector_t bad_sector) { - cmd->scsi_asc = asc; - cmd->scsi_ascq = ascq; + /* Place failed LBA in sense data information descriptor 0. */ + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 0xc; + buffer[SPC_DESC_TYPE_OFFSET] = 0; /* Information */ + buffer[SPC_ADDITIONAL_DESC_LEN_OFFSET] = 0xa; + buffer[SPC_VALIDITY_OFFSET] = 0x80; - return 0; + /* Descriptor Information: failing sector */ + put_unaligned_be64(bad_sector, &buffer[12]); } -int transport_send_check_condition_and_sense( - struct se_cmd *cmd, - u8 reason, - int from_transport) +int +transport_send_check_condition_and_sense(struct se_cmd *cmd, + sense_reason_t reason, int from_transport) { unsigned char *buffer = cmd->sense_buffer; unsigned long flags; - int offset; u8 asc = 0, ascq = 0; - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); + spin_lock_irqsave(&cmd->t_state_lock, flags); if (cmd->se_cmd_flags & SCF_SENT_CHECK_CONDITION) { - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); + spin_unlock_irqrestore(&cmd->t_state_lock, flags); return 0; } cmd->se_cmd_flags |= SCF_SENT_CHECK_CONDITION; - spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); + spin_unlock_irqrestore(&cmd->t_state_lock, flags); if (!reason && from_transport) goto after_reason; if (!from_transport) cmd->se_cmd_flags |= SCF_EMULATED_TASK_SENSE; - /* - * Data Segment and SenseLength of the fabric response PDU. - * - * TRANSPORT_SENSE_BUFFER is now set to SCSI_SENSE_BUFFERSIZE - * from include/scsi/scsi_cmnd.h - */ - offset = CMD_TFO(cmd)->set_fabric_sense_len(cmd, - TRANSPORT_SENSE_BUFFER); + /* * Actual SENSE DATA, see SPC-3 7.23.2 SPC_SENSE_KEY_OFFSET uses * SENSE KEY values from include/scsi/scsi.h */ switch (reason) { + case TCM_NO_SENSE: + /* CURRENT ERROR */ + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* Not Ready */ + buffer[SPC_SENSE_KEY_OFFSET] = NOT_READY; + /* NO ADDITIONAL SENSE INFORMATION */ + buffer[SPC_ASC_KEY_OFFSET] = 0; + buffer[SPC_ASCQ_KEY_OFFSET] = 0; + break; case TCM_NON_EXISTENT_LUN: + /* CURRENT ERROR */ + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* ILLEGAL REQUEST */ + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + /* LOGICAL UNIT NOT SUPPORTED */ + buffer[SPC_ASC_KEY_OFFSET] = 0x25; + break; case TCM_UNSUPPORTED_SCSI_OPCODE: case TCM_SECTOR_COUNT_TOO_MANY: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* ILLEGAL REQUEST */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; /* INVALID COMMAND OPERATION CODE */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x20; + buffer[SPC_ASC_KEY_OFFSET] = 0x20; break; case TCM_UNKNOWN_MODE_PAGE: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* ILLEGAL REQUEST */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; /* INVALID FIELD IN CDB */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x24; + buffer[SPC_ASC_KEY_OFFSET] = 0x24; break; case TCM_CHECK_CONDITION_ABORT_CMD: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* ABORTED COMMAND */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; + buffer[SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; /* BUS DEVICE RESET FUNCTION OCCURRED */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x29; - buffer[offset+SPC_ASCQ_KEY_OFFSET] = 0x03; + buffer[SPC_ASC_KEY_OFFSET] = 0x29; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x03; break; case TCM_INCORRECT_AMOUNT_OF_DATA: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* ABORTED COMMAND */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; + buffer[SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; /* WRITE ERROR */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x0c; + buffer[SPC_ASC_KEY_OFFSET] = 0x0c; /* NOT ENOUGH UNSOLICITED DATA */ - buffer[offset+SPC_ASCQ_KEY_OFFSET] = 0x0d; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x0d; break; case TCM_INVALID_CDB_FIELD: /* CURRENT ERROR */ - buffer[offset] = 0x70; - /* ABORTED COMMAND */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* ILLEGAL REQUEST */ + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; /* INVALID FIELD IN CDB */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x24; + buffer[SPC_ASC_KEY_OFFSET] = 0x24; break; case TCM_INVALID_PARAMETER_LIST: /* CURRENT ERROR */ - buffer[offset] = 0x70; - /* ABORTED COMMAND */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* ILLEGAL REQUEST */ + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; /* INVALID FIELD IN PARAMETER LIST */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x26; + buffer[SPC_ASC_KEY_OFFSET] = 0x26; + break; + case TCM_PARAMETER_LIST_LENGTH_ERROR: + /* CURRENT ERROR */ + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* ILLEGAL REQUEST */ + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + /* PARAMETER LIST LENGTH ERROR */ + buffer[SPC_ASC_KEY_OFFSET] = 0x1a; break; case TCM_UNEXPECTED_UNSOLICITED_DATA: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* ABORTED COMMAND */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; + buffer[SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; /* WRITE ERROR */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x0c; + buffer[SPC_ASC_KEY_OFFSET] = 0x0c; /* UNEXPECTED_UNSOLICITED_DATA */ - buffer[offset+SPC_ASCQ_KEY_OFFSET] = 0x0c; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x0c; break; case TCM_SERVICE_CRC_ERROR: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* ABORTED COMMAND */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; + buffer[SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; /* PROTOCOL SERVICE CRC ERROR */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x47; + buffer[SPC_ASC_KEY_OFFSET] = 0x47; /* N/A */ - buffer[offset+SPC_ASCQ_KEY_OFFSET] = 0x05; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x05; break; case TCM_SNACK_REJECTED: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* ABORTED COMMAND */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; + buffer[SPC_SENSE_KEY_OFFSET] = ABORTED_COMMAND; /* READ ERROR */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x11; + buffer[SPC_ASC_KEY_OFFSET] = 0x11; /* FAILED RETRANSMISSION REQUEST */ - buffer[offset+SPC_ASCQ_KEY_OFFSET] = 0x13; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x13; break; case TCM_WRITE_PROTECTED: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* DATA PROTECT */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = DATA_PROTECT; + buffer[SPC_SENSE_KEY_OFFSET] = DATA_PROTECT; /* WRITE PROTECTED */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x27; + buffer[SPC_ASC_KEY_OFFSET] = 0x27; + break; + case TCM_ADDRESS_OUT_OF_RANGE: + /* CURRENT ERROR */ + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* ILLEGAL REQUEST */ + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + /* LOGICAL BLOCK ADDRESS OUT OF RANGE */ + buffer[SPC_ASC_KEY_OFFSET] = 0x21; break; case TCM_CHECK_CONDITION_UNIT_ATTENTION: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* UNIT ATTENTION */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = UNIT_ATTENTION; + buffer[SPC_SENSE_KEY_OFFSET] = UNIT_ATTENTION; core_scsi3_ua_for_check_condition(cmd, &asc, &ascq); - buffer[offset+SPC_ASC_KEY_OFFSET] = asc; - buffer[offset+SPC_ASCQ_KEY_OFFSET] = ascq; + buffer[SPC_ASC_KEY_OFFSET] = asc; + buffer[SPC_ASCQ_KEY_OFFSET] = ascq; break; case TCM_CHECK_CONDITION_NOT_READY: /* CURRENT ERROR */ - buffer[offset] = 0x70; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; /* Not Ready */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = NOT_READY; + buffer[SPC_SENSE_KEY_OFFSET] = NOT_READY; transport_get_sense_codes(cmd, &asc, &ascq); - buffer[offset+SPC_ASC_KEY_OFFSET] = asc; - buffer[offset+SPC_ASCQ_KEY_OFFSET] = ascq; + buffer[SPC_ASC_KEY_OFFSET] = asc; + buffer[SPC_ASCQ_KEY_OFFSET] = ascq; + break; + case TCM_MISCOMPARE_VERIFY: + /* CURRENT ERROR */ + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + buffer[SPC_SENSE_KEY_OFFSET] = MISCOMPARE; + /* MISCOMPARE DURING VERIFY OPERATION */ + buffer[SPC_ASC_KEY_OFFSET] = 0x1d; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x00; + break; + case TCM_LOGICAL_BLOCK_GUARD_CHECK_FAILED: + /* CURRENT ERROR */ + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* ILLEGAL REQUEST */ + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + /* LOGICAL BLOCK GUARD CHECK FAILED */ + buffer[SPC_ASC_KEY_OFFSET] = 0x10; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x01; + transport_err_sector_info(buffer, cmd->bad_sector); + break; + case TCM_LOGICAL_BLOCK_APP_TAG_CHECK_FAILED: + /* CURRENT ERROR */ + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* ILLEGAL REQUEST */ + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + /* LOGICAL BLOCK APPLICATION TAG CHECK FAILED */ + buffer[SPC_ASC_KEY_OFFSET] = 0x10; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x02; + transport_err_sector_info(buffer, cmd->bad_sector); + break; + case TCM_LOGICAL_BLOCK_REF_TAG_CHECK_FAILED: + /* CURRENT ERROR */ + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* ILLEGAL REQUEST */ + buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + /* LOGICAL BLOCK REFERENCE TAG CHECK FAILED */ + buffer[SPC_ASC_KEY_OFFSET] = 0x10; + buffer[SPC_ASCQ_KEY_OFFSET] = 0x03; + transport_err_sector_info(buffer, cmd->bad_sector); break; case TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE: default: /* CURRENT ERROR */ - buffer[offset] = 0x70; - /* ILLEGAL REQUEST */ - buffer[offset+SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + buffer[0] = 0x70; + buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; + /* + * Returning ILLEGAL REQUEST would cause immediate IO errors on + * Solaris initiators. Returning NOT READY instead means the + * operations will be retried a finite number of times and we + * can survive intermittent errors. + */ + buffer[SPC_SENSE_KEY_OFFSET] = NOT_READY; /* LOGICAL UNIT COMMUNICATION FAILURE */ - buffer[offset+SPC_ASC_KEY_OFFSET] = 0x80; + buffer[SPC_ASC_KEY_OFFSET] = 0x08; break; } /* @@ -5766,38 +2843,49 @@ int transport_send_check_condition_and_sense( * Automatically padded, this value is encoded in the fabric's * data_length response PDU containing the SCSI defined sense data. */ - cmd->scsi_sense_length = TRANSPORT_SENSE_BUFFER + offset; + cmd->scsi_sense_length = TRANSPORT_SENSE_BUFFER; after_reason: - CMD_TFO(cmd)->queue_status(cmd); - return 0; + trace_target_cmd_complete(cmd); + return cmd->se_tfo->queue_status(cmd); } EXPORT_SYMBOL(transport_send_check_condition_and_sense); int transport_check_aborted_status(struct se_cmd *cmd, int send_status) { - int ret = 0; + if (!(cmd->transport_state & CMD_T_ABORTED)) + return 0; - if (atomic_read(&T_TASK(cmd)->t_transport_aborted) != 0) { - if (!(send_status) || - (cmd->se_cmd_flags & SCF_SENT_DELAYED_TAS)) - return 1; -#if 0 - printk(KERN_INFO "Sending delayed SAM_STAT_TASK_ABORTED" - " status for CDB: 0x%02x ITT: 0x%08x\n", - T_TASK(cmd)->t_task_cdb[0], - CMD_TFO(cmd)->get_task_tag(cmd)); -#endif - cmd->se_cmd_flags |= SCF_SENT_DELAYED_TAS; - CMD_TFO(cmd)->queue_status(cmd); - ret = 1; - } - return ret; + /* + * If cmd has been aborted but either no status is to be sent or it has + * already been sent, just return + */ + if (!send_status || !(cmd->se_cmd_flags & SCF_SEND_DELAYED_TAS)) + return 1; + + pr_debug("Sending delayed SAM_STAT_TASK_ABORTED status for CDB: 0x%02x ITT: 0x%08x\n", + cmd->t_task_cdb[0], cmd->se_tfo->get_task_tag(cmd)); + + cmd->se_cmd_flags &= ~SCF_SEND_DELAYED_TAS; + cmd->scsi_status = SAM_STAT_TASK_ABORTED; + trace_target_cmd_complete(cmd); + cmd->se_tfo->queue_status(cmd); + + return 1; } EXPORT_SYMBOL(transport_check_aborted_status); void transport_send_task_abort(struct se_cmd *cmd) { + unsigned long flags; + + spin_lock_irqsave(&cmd->t_state_lock, flags); + if (cmd->se_cmd_flags & (SCF_SENT_CHECK_CONDITION)) { + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + return; + } + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + /* * If there are still expected incoming fabric WRITEs, we wait * until until they have completed before sending a TASK_ABORTED @@ -5805,360 +2893,76 @@ void transport_send_task_abort(struct se_cmd *cmd) * queued back to fabric module by transport_check_aborted_status(). */ if (cmd->data_direction == DMA_TO_DEVICE) { - if (CMD_TFO(cmd)->write_pending_status(cmd) != 0) { - atomic_inc(&T_TASK(cmd)->t_transport_aborted); - smp_mb__after_atomic_inc(); - cmd->scsi_status = SAM_STAT_TASK_ABORTED; - transport_new_cmd_failure(cmd); + if (cmd->se_tfo->write_pending_status(cmd) != 0) { + cmd->transport_state |= CMD_T_ABORTED; + cmd->se_cmd_flags |= SCF_SEND_DELAYED_TAS; + smp_mb__after_atomic(); return; } } cmd->scsi_status = SAM_STAT_TASK_ABORTED; -#if 0 - printk(KERN_INFO "Setting SAM_STAT_TASK_ABORTED status for CDB: 0x%02x," - " ITT: 0x%08x\n", T_TASK(cmd)->t_task_cdb[0], - CMD_TFO(cmd)->get_task_tag(cmd)); -#endif - CMD_TFO(cmd)->queue_status(cmd); + + transport_lun_remove_cmd(cmd); + + pr_debug("Setting SAM_STAT_TASK_ABORTED status for CDB: 0x%02x," + " ITT: 0x%08x\n", cmd->t_task_cdb[0], + cmd->se_tfo->get_task_tag(cmd)); + + trace_target_cmd_complete(cmd); + cmd->se_tfo->queue_status(cmd); } -/* transport_generic_do_tmr(): - * - * - */ -int transport_generic_do_tmr(struct se_cmd *cmd) +static void target_tmr_work(struct work_struct *work) { - struct se_cmd *ref_cmd; - struct se_device *dev = SE_DEV(cmd); + struct se_cmd *cmd = container_of(work, struct se_cmd, work); + struct se_device *dev = cmd->se_dev; struct se_tmr_req *tmr = cmd->se_tmr_req; int ret; switch (tmr->function) { - case ABORT_TASK: - ref_cmd = tmr->ref_cmd; - tmr->response = TMR_FUNCTION_REJECTED; + case TMR_ABORT_TASK: + core_tmr_abort_task(dev, tmr, cmd->se_sess); break; - case ABORT_TASK_SET: - case CLEAR_ACA: - case CLEAR_TASK_SET: + case TMR_ABORT_TASK_SET: + case TMR_CLEAR_ACA: + case TMR_CLEAR_TASK_SET: tmr->response = TMR_TASK_MGMT_FUNCTION_NOT_SUPPORTED; break; - case LUN_RESET: + case TMR_LUN_RESET: ret = core_tmr_lun_reset(dev, tmr, NULL, NULL); tmr->response = (!ret) ? TMR_FUNCTION_COMPLETE : TMR_FUNCTION_REJECTED; break; -#if 0 - case TARGET_WARM_RESET: - transport_generic_host_reset(dev->se_hba); + case TMR_TARGET_WARM_RESET: tmr->response = TMR_FUNCTION_REJECTED; break; - case TARGET_COLD_RESET: - transport_generic_host_reset(dev->se_hba); - transport_generic_cold_reset(dev->se_hba); + case TMR_TARGET_COLD_RESET: tmr->response = TMR_FUNCTION_REJECTED; break; -#endif default: - printk(KERN_ERR "Uknown TMR function: 0x%02x.\n", + pr_err("Uknown TMR function: 0x%02x.\n", tmr->function); tmr->response = TMR_FUNCTION_REJECTED; break; } cmd->t_state = TRANSPORT_ISTATE_PROCESSING; - CMD_TFO(cmd)->queue_tm_rsp(cmd); - - transport_cmd_check_stop(cmd, 2, 0); - return 0; -} - -/* - * Called with spin_lock_irq(&dev->execute_task_lock); held - * - */ -static struct se_task * -transport_get_task_from_state_list(struct se_device *dev) -{ - struct se_task *task; + cmd->se_tfo->queue_tm_rsp(cmd); - if (list_empty(&dev->state_task_list)) - return NULL; - - list_for_each_entry(task, &dev->state_task_list, t_state_list) - break; - - list_del(&task->t_state_list); - atomic_set(&task->task_state_active, 0); - - return task; + transport_cmd_check_stop_to_fabric(cmd); } -static void transport_processing_shutdown(struct se_device *dev) +int transport_generic_handle_tmr( + struct se_cmd *cmd) { - struct se_cmd *cmd; - struct se_queue_req *qr; - struct se_task *task; - u8 state; unsigned long flags; - /* - * Empty the struct se_device's struct se_task state list. - */ - spin_lock_irqsave(&dev->execute_task_lock, flags); - while ((task = transport_get_task_from_state_list(dev))) { - if (!(TASK_CMD(task))) { - printk(KERN_ERR "TASK_CMD(task) is NULL!\n"); - continue; - } - cmd = TASK_CMD(task); - if (!T_TASK(cmd)) { - printk(KERN_ERR "T_TASK(cmd) is NULL for task: %p cmd:" - " %p ITT: 0x%08x\n", task, cmd, - CMD_TFO(cmd)->get_task_tag(cmd)); - continue; - } - spin_unlock_irqrestore(&dev->execute_task_lock, flags); - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - - DEBUG_DO("PT: cmd: %p task: %p ITT/CmdSN: 0x%08x/0x%08x," - " i_state/def_i_state: %d/%d, t_state/def_t_state:" - " %d/%d cdb: 0x%02x\n", cmd, task, - CMD_TFO(cmd)->get_task_tag(cmd), cmd->cmd_sn, - CMD_TFO(cmd)->get_cmd_state(cmd), cmd->deferred_i_state, - cmd->t_state, cmd->deferred_t_state, - T_TASK(cmd)->t_task_cdb[0]); - DEBUG_DO("PT: ITT[0x%08x] - t_task_cdbs: %d t_task_cdbs_left:" - " %d t_task_cdbs_sent: %d -- t_transport_active: %d" - " t_transport_stop: %d t_transport_sent: %d\n", - CMD_TFO(cmd)->get_task_tag(cmd), - T_TASK(cmd)->t_task_cdbs, - atomic_read(&T_TASK(cmd)->t_task_cdbs_left), - atomic_read(&T_TASK(cmd)->t_task_cdbs_sent), - atomic_read(&T_TASK(cmd)->t_transport_active), - atomic_read(&T_TASK(cmd)->t_transport_stop), - atomic_read(&T_TASK(cmd)->t_transport_sent)); - - if (atomic_read(&task->task_active)) { - atomic_set(&task->task_stop, 1); - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - - DEBUG_DO("Waiting for task: %p to shutdown for dev:" - " %p\n", task, dev); - wait_for_completion(&task->task_stop_comp); - DEBUG_DO("Completed task: %p shutdown for dev: %p\n", - task, dev); - - spin_lock_irqsave(&T_TASK(cmd)->t_state_lock, flags); - atomic_dec(&T_TASK(cmd)->t_task_cdbs_left); - - atomic_set(&task->task_active, 0); - atomic_set(&task->task_stop, 0); - } - __transport_stop_task_timer(task, &flags); - - if (!(atomic_dec_and_test(&T_TASK(cmd)->t_task_cdbs_ex_left))) { - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - - DEBUG_DO("Skipping task: %p, dev: %p for" - " t_task_cdbs_ex_left: %d\n", task, dev, - atomic_read(&T_TASK(cmd)->t_task_cdbs_ex_left)); - - spin_lock_irqsave(&dev->execute_task_lock, flags); - continue; - } - - if (atomic_read(&T_TASK(cmd)->t_transport_active)) { - DEBUG_DO("got t_transport_active = 1 for task: %p, dev:" - " %p\n", task, dev); - - if (atomic_read(&T_TASK(cmd)->t_fe_count)) { - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - transport_send_check_condition_and_sense( - cmd, TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE, - 0); - transport_remove_cmd_from_queue(cmd, - SE_DEV(cmd)->dev_queue_obj); - - transport_lun_remove_cmd(cmd); - transport_cmd_check_stop(cmd, 1, 0); - } else { - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - - transport_remove_cmd_from_queue(cmd, - SE_DEV(cmd)->dev_queue_obj); - - transport_lun_remove_cmd(cmd); - - if (transport_cmd_check_stop(cmd, 1, 0)) - transport_generic_remove(cmd, 0, 0); - } - - spin_lock_irqsave(&dev->execute_task_lock, flags); - continue; - } - DEBUG_DO("Got t_transport_active = 0 for task: %p, dev: %p\n", - task, dev); + spin_lock_irqsave(&cmd->t_state_lock, flags); + cmd->transport_state |= CMD_T_ACTIVE; + spin_unlock_irqrestore(&cmd->t_state_lock, flags); - if (atomic_read(&T_TASK(cmd)->t_fe_count)) { - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - transport_send_check_condition_and_sense(cmd, - TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE, 0); - transport_remove_cmd_from_queue(cmd, - SE_DEV(cmd)->dev_queue_obj); - - transport_lun_remove_cmd(cmd); - transport_cmd_check_stop(cmd, 1, 0); - } else { - spin_unlock_irqrestore( - &T_TASK(cmd)->t_state_lock, flags); - - transport_remove_cmd_from_queue(cmd, - SE_DEV(cmd)->dev_queue_obj); - transport_lun_remove_cmd(cmd); - - if (transport_cmd_check_stop(cmd, 1, 0)) - transport_generic_remove(cmd, 0, 0); - } - - spin_lock_irqsave(&dev->execute_task_lock, flags); - } - spin_unlock_irqrestore(&dev->execute_task_lock, flags); - /* - * Empty the struct se_device's struct se_cmd list. - */ - spin_lock_irqsave(&dev->dev_queue_obj->cmd_queue_lock, flags); - while ((qr = __transport_get_qr_from_queue(dev->dev_queue_obj))) { - spin_unlock_irqrestore( - &dev->dev_queue_obj->cmd_queue_lock, flags); - cmd = (struct se_cmd *)qr->cmd; - state = qr->state; - kfree(qr); - - DEBUG_DO("From Device Queue: cmd: %p t_state: %d\n", - cmd, state); - - if (atomic_read(&T_TASK(cmd)->t_fe_count)) { - transport_send_check_condition_and_sense(cmd, - TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE, 0); - - transport_lun_remove_cmd(cmd); - transport_cmd_check_stop(cmd, 1, 0); - } else { - transport_lun_remove_cmd(cmd); - if (transport_cmd_check_stop(cmd, 1, 0)) - transport_generic_remove(cmd, 0, 0); - } - spin_lock_irqsave(&dev->dev_queue_obj->cmd_queue_lock, flags); - } - spin_unlock_irqrestore(&dev->dev_queue_obj->cmd_queue_lock, flags); -} - -/* transport_processing_thread(): - * - * - */ -static int transport_processing_thread(void *param) -{ - int ret, t_state; - struct se_cmd *cmd; - struct se_device *dev = (struct se_device *) param; - struct se_queue_req *qr; - - set_user_nice(current, -20); - - while (!kthread_should_stop()) { - ret = wait_event_interruptible(dev->dev_queue_obj->thread_wq, - atomic_read(&dev->dev_queue_obj->queue_cnt) || - kthread_should_stop()); - if (ret < 0) - goto out; - - spin_lock_irq(&dev->dev_status_lock); - if (dev->dev_status & TRANSPORT_DEVICE_SHUTDOWN) { - spin_unlock_irq(&dev->dev_status_lock); - transport_processing_shutdown(dev); - continue; - } - spin_unlock_irq(&dev->dev_status_lock); - -get_cmd: - __transport_execute_tasks(dev); - - qr = transport_get_qr_from_queue(dev->dev_queue_obj); - if (!(qr)) - continue; - - cmd = (struct se_cmd *)qr->cmd; - t_state = qr->state; - kfree(qr); - - switch (t_state) { - case TRANSPORT_NEW_CMD_MAP: - if (!(CMD_TFO(cmd)->new_cmd_map)) { - printk(KERN_ERR "CMD_TFO(cmd)->new_cmd_map is" - " NULL for TRANSPORT_NEW_CMD_MAP\n"); - BUG(); - } - ret = CMD_TFO(cmd)->new_cmd_map(cmd); - if (ret < 0) { - cmd->transport_error_status = ret; - transport_generic_request_failure(cmd, NULL, - 0, (cmd->data_direction != - DMA_TO_DEVICE)); - break; - } - /* Fall through */ - case TRANSPORT_NEW_CMD: - ret = transport_generic_new_cmd(cmd); - if (ret < 0) { - cmd->transport_error_status = ret; - transport_generic_request_failure(cmd, NULL, - 0, (cmd->data_direction != - DMA_TO_DEVICE)); - } - break; - case TRANSPORT_PROCESS_WRITE: - transport_generic_process_write(cmd); - break; - case TRANSPORT_COMPLETE_OK: - transport_stop_all_task_timers(cmd); - transport_generic_complete_ok(cmd); - break; - case TRANSPORT_REMOVE: - transport_generic_remove(cmd, 1, 0); - break; - case TRANSPORT_PROCESS_TMR: - transport_generic_do_tmr(cmd); - break; - case TRANSPORT_COMPLETE_FAILURE: - transport_generic_request_failure(cmd, NULL, 1, 1); - break; - case TRANSPORT_COMPLETE_TIMEOUT: - transport_stop_all_task_timers(cmd); - transport_generic_request_timeout(cmd); - break; - default: - printk(KERN_ERR "Unknown t_state: %d deferred_t_state:" - " %d for ITT: 0x%08x i_state: %d on SE LUN:" - " %u\n", t_state, cmd->deferred_t_state, - CMD_TFO(cmd)->get_task_tag(cmd), - CMD_TFO(cmd)->get_cmd_state(cmd), - SE_LUN(cmd)->unpacked_lun); - BUG(); - } - - goto get_cmd; - } - -out: - transport_release_all_cmds(dev); - dev->process_thread = NULL; + INIT_WORK(&cmd->work, target_tmr_work); + queue_work(cmd->se_dev->tmr_wq, &cmd->work); return 0; } +EXPORT_SYMBOL(transport_generic_handle_tmr); diff --git a/drivers/target/target_core_ua.c b/drivers/target/target_core_ua.c index a2ef346087e..101858e245b 100644 --- a/drivers/target/target_core_ua.c +++ b/drivers/target/target_core_ua.c @@ -3,8 +3,7 @@ * * This file contains logic for SPC-3 Unit Attention emulation * - * Copyright (c) 2009,2010 Rising Tide Systems - * Copyright (c) 2009,2010 Linux-iSCSI.org + * (c) Copyright 2009-2013 Datera, Inc. * * Nicholas A. Bellinger <nab@kernel.org> * @@ -24,40 +23,36 @@ * ******************************************************************************/ -#include <linux/version.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> #include <target/target_core_base.h> -#include <target/target_core_device.h> -#include <target/target_core_transport.h> -#include <target/target_core_fabric_ops.h> +#include <target/target_core_fabric.h> #include <target/target_core_configfs.h> +#include "target_core_internal.h" #include "target_core_alua.h" -#include "target_core_hba.h" #include "target_core_pr.h" #include "target_core_ua.h" -int core_scsi3_ua_check( - struct se_cmd *cmd, - unsigned char *cdb) +sense_reason_t +target_scsi3_ua_check(struct se_cmd *cmd) { struct se_dev_entry *deve; struct se_session *sess = cmd->se_sess; struct se_node_acl *nacl; - if (!(sess)) + if (!sess) return 0; nacl = sess->se_node_acl; - if (!(nacl)) + if (!nacl) return 0; - deve = &nacl->device_list[cmd->orig_fe_lun]; - if (!(atomic_read(&deve->ua_count))) + deve = nacl->device_list[cmd->orig_fe_lun]; + if (!atomic_read(&deve->ua_count)) return 0; /* * From sam4r14, section 5.14 Unit attention condition: @@ -74,16 +69,14 @@ int core_scsi3_ua_check( * was received, then the device server shall process the command * and either: */ - switch (cdb[0]) { + switch (cmd->t_task_cdb[0]) { case INQUIRY: case REPORT_LUNS: case REQUEST_SENSE: return 0; default: - return -1; + return TCM_CHECK_CONDITION_UNIT_ATTENTION; } - - return -1; } int core_scsi3_ua_allocate( @@ -97,15 +90,14 @@ int core_scsi3_ua_allocate( /* * PASSTHROUGH OPS */ - if (!(nacl)) - return -1; + if (!nacl) + return -EINVAL; ua = kmem_cache_zalloc(se_ua_cache, GFP_ATOMIC); - if (!(ua)) { - printk(KERN_ERR "Unable to allocate struct se_ua\n"); - return -1; + if (!ua) { + pr_err("Unable to allocate struct se_ua\n"); + return -ENOMEM; } - INIT_LIST_HEAD(&ua->ua_dev_list); INIT_LIST_HEAD(&ua->ua_nacl_list); ua->ua_nacl = nacl; @@ -113,7 +105,7 @@ int core_scsi3_ua_allocate( ua->ua_ascq = ascq; spin_lock_irq(&nacl->device_list_lock); - deve = &nacl->device_list[unpacked_lun]; + deve = nacl->device_list[unpacked_lun]; spin_lock(&deve->ua_lock); list_for_each_entry_safe(ua_p, ua_tmp, &deve->ua_list, ua_nacl_list) { @@ -170,20 +162,20 @@ int core_scsi3_ua_allocate( spin_unlock_irq(&nacl->device_list_lock); atomic_inc(&deve->ua_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); return 0; } list_add_tail(&ua->ua_nacl_list, &deve->ua_list); spin_unlock(&deve->ua_lock); spin_unlock_irq(&nacl->device_list_lock); - printk(KERN_INFO "[%s]: Allocated UNIT ATTENTION, mapped LUN: %u, ASC:" + pr_debug("[%s]: Allocated UNIT ATTENTION, mapped LUN: %u, ASC:" " 0x%02x, ASCQ: 0x%02x\n", - TPG_TFO(nacl->se_tpg)->get_fabric_name(), unpacked_lun, + nacl->se_tpg->se_tpg_tfo->get_fabric_name(), unpacked_lun, asc, ascq); atomic_inc(&deve->ua_count); - smp_mb__after_atomic_inc(); + smp_mb__after_atomic(); return 0; } @@ -198,7 +190,7 @@ void core_scsi3_ua_release_all( kmem_cache_free(se_ua_cache, ua); atomic_dec(&deve->ua_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } spin_unlock(&deve->ua_lock); } @@ -208,23 +200,23 @@ void core_scsi3_ua_for_check_condition( u8 *asc, u8 *ascq) { - struct se_device *dev = SE_DEV(cmd); + struct se_device *dev = cmd->se_dev; struct se_dev_entry *deve; struct se_session *sess = cmd->se_sess; struct se_node_acl *nacl; struct se_ua *ua = NULL, *ua_p; int head = 1; - if (!(sess)) + if (!sess) return; nacl = sess->se_node_acl; - if (!(nacl)) + if (!nacl) return; spin_lock_irq(&nacl->device_list_lock); - deve = &nacl->device_list[cmd->orig_fe_lun]; - if (!(atomic_read(&deve->ua_count))) { + deve = nacl->device_list[cmd->orig_fe_lun]; + if (!atomic_read(&deve->ua_count)) { spin_unlock_irq(&nacl->device_list_lock); return; } @@ -240,14 +232,14 @@ void core_scsi3_ua_for_check_condition( * highest priority UNIT_ATTENTION and ASC/ASCQ without * clearing it. */ - if (DEV_ATTRIB(dev)->emulate_ua_intlck_ctrl != 0) { + if (dev->dev_attrib.emulate_ua_intlck_ctrl != 0) { *asc = ua->ua_asc; *ascq = ua->ua_ascq; break; } /* * Otherwise for the default 00b, release the UNIT ATTENTION - * condition. Return the ASC/ASCQ of the higest priority UA + * condition. Return the ASC/ASCQ of the highest priority UA * (head of the list) in the outgoing CHECK_CONDITION + sense. */ if (head) { @@ -259,18 +251,18 @@ void core_scsi3_ua_for_check_condition( kmem_cache_free(se_ua_cache, ua); atomic_dec(&deve->ua_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } spin_unlock(&deve->ua_lock); spin_unlock_irq(&nacl->device_list_lock); - printk(KERN_INFO "[%s]: %s UNIT ATTENTION condition with" + pr_debug("[%s]: %s UNIT ATTENTION condition with" " INTLCK_CTRL: %d, mapped LUN: %u, got CDB: 0x%02x" " reported ASC: 0x%02x, ASCQ: 0x%02x\n", - TPG_TFO(nacl->se_tpg)->get_fabric_name(), - (DEV_ATTRIB(dev)->emulate_ua_intlck_ctrl != 0) ? "Reporting" : - "Releasing", DEV_ATTRIB(dev)->emulate_ua_intlck_ctrl, - cmd->orig_fe_lun, T_TASK(cmd)->t_task_cdb[0], *asc, *ascq); + nacl->se_tpg->se_tpg_tfo->get_fabric_name(), + (dev->dev_attrib.emulate_ua_intlck_ctrl != 0) ? "Reporting" : + "Releasing", dev->dev_attrib.emulate_ua_intlck_ctrl, + cmd->orig_fe_lun, cmd->t_task_cdb[0], *asc, *ascq); } int core_scsi3_ua_clear_for_request_sense( @@ -284,18 +276,18 @@ int core_scsi3_ua_clear_for_request_sense( struct se_ua *ua = NULL, *ua_p; int head = 1; - if (!(sess)) - return -1; + if (!sess) + return -EINVAL; nacl = sess->se_node_acl; - if (!(nacl)) - return -1; + if (!nacl) + return -EINVAL; spin_lock_irq(&nacl->device_list_lock); - deve = &nacl->device_list[cmd->orig_fe_lun]; - if (!(atomic_read(&deve->ua_count))) { + deve = nacl->device_list[cmd->orig_fe_lun]; + if (!atomic_read(&deve->ua_count)) { spin_unlock_irq(&nacl->device_list_lock); - return -1; + return -EPERM; } /* * The highest priority Unit Attentions are placed at the head of the @@ -304,7 +296,7 @@ int core_scsi3_ua_clear_for_request_sense( * matching struct se_lun. * * Once the returning ASC/ASCQ values are set, we go ahead and - * release all of the Unit Attention conditions for the assoicated + * release all of the Unit Attention conditions for the associated * struct se_lun. */ spin_lock(&deve->ua_lock); @@ -318,15 +310,15 @@ int core_scsi3_ua_clear_for_request_sense( kmem_cache_free(se_ua_cache, ua); atomic_dec(&deve->ua_count); - smp_mb__after_atomic_dec(); + smp_mb__after_atomic(); } spin_unlock(&deve->ua_lock); spin_unlock_irq(&nacl->device_list_lock); - printk(KERN_INFO "[%s]: Released UNIT ATTENTION condition, mapped" + pr_debug("[%s]: Released UNIT ATTENTION condition, mapped" " LUN: %u, got REQUEST_SENSE reported ASC: 0x%02x," - " ASCQ: 0x%02x\n", TPG_TFO(nacl->se_tpg)->get_fabric_name(), + " ASCQ: 0x%02x\n", nacl->se_tpg->se_tpg_tfo->get_fabric_name(), cmd->orig_fe_lun, *asc, *ascq); - return (head) ? -1 : 0; + return (head) ? -EPERM : 0; } diff --git a/drivers/target/target_core_ua.h b/drivers/target/target_core_ua.h index 6e6b03460a1..be912b36daa 100644 --- a/drivers/target/target_core_ua.h +++ b/drivers/target/target_core_ua.h @@ -19,14 +19,14 @@ #define ASCQ_2AH_RESERVATIONS_RELEASED 0x04 #define ASCQ_2AH_REGISTRATIONS_PREEMPTED 0x05 #define ASCQ_2AH_ASYMMETRIC_ACCESS_STATE_CHANGED 0x06 -#define ASCQ_2AH_IMPLICT_ASYMMETRIC_ACCESS_STATE_TRANSITION_FAILED 0x07 +#define ASCQ_2AH_IMPLICIT_ASYMMETRIC_ACCESS_STATE_TRANSITION_FAILED 0x07 #define ASCQ_2AH_PRIORITY_CHANGED 0x08 #define ASCQ_2CH_PREVIOUS_RESERVATION_CONFLICT_STATUS 0x09 extern struct kmem_cache *se_ua_cache; -extern int core_scsi3_ua_check(struct se_cmd *, unsigned char *); +extern sense_reason_t target_scsi3_ua_check(struct se_cmd *); extern int core_scsi3_ua_allocate(struct se_node_acl *, u32, u8, u8); extern void core_scsi3_ua_release_all(struct se_dev_entry *); extern void core_scsi3_ua_for_check_condition(struct se_cmd *, u8 *, u8 *); diff --git a/drivers/target/target_core_xcopy.c b/drivers/target/target_core_xcopy.c new file mode 100644 index 00000000000..e9186cdf35e --- /dev/null +++ b/drivers/target/target_core_xcopy.c @@ -0,0 +1,1081 @@ +/******************************************************************************* + * Filename: target_core_xcopy.c + * + * This file contains support for SPC-4 Extended-Copy offload with generic + * TCM backends. + * + * Copyright (c) 2011-2013 Datera, Inc. All rights reserved. + * + * Author: + * Nicholas A. Bellinger <nab@daterainc.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + ******************************************************************************/ + +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/configfs.h> +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <asm/unaligned.h> + +#include <target/target_core_base.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> +#include <target/target_core_configfs.h> + +#include "target_core_pr.h" +#include "target_core_ua.h" +#include "target_core_xcopy.h" + +static struct workqueue_struct *xcopy_wq = NULL; +/* + * From target_core_device.c + */ +extern struct mutex g_device_mutex; +extern struct list_head g_device_list; +/* + * From target_core_configfs.c + */ +extern struct configfs_subsystem *target_core_subsystem[]; + +static int target_xcopy_gen_naa_ieee(struct se_device *dev, unsigned char *buf) +{ + int off = 0; + + buf[off++] = (0x6 << 4); + buf[off++] = 0x01; + buf[off++] = 0x40; + buf[off] = (0x5 << 4); + + spc_parse_naa_6h_vendor_specific(dev, &buf[off]); + return 0; +} + +static int target_xcopy_locate_se_dev_e4(struct se_cmd *se_cmd, struct xcopy_op *xop, + bool src) +{ + struct se_device *se_dev; + struct configfs_subsystem *subsys = target_core_subsystem[0]; + unsigned char tmp_dev_wwn[XCOPY_NAA_IEEE_REGEX_LEN], *dev_wwn; + int rc; + + if (src) + dev_wwn = &xop->dst_tid_wwn[0]; + else + dev_wwn = &xop->src_tid_wwn[0]; + + mutex_lock(&g_device_mutex); + list_for_each_entry(se_dev, &g_device_list, g_dev_node) { + + if (!se_dev->dev_attrib.emulate_3pc) + continue; + + memset(&tmp_dev_wwn[0], 0, XCOPY_NAA_IEEE_REGEX_LEN); + target_xcopy_gen_naa_ieee(se_dev, &tmp_dev_wwn[0]); + + rc = memcmp(&tmp_dev_wwn[0], dev_wwn, XCOPY_NAA_IEEE_REGEX_LEN); + if (rc != 0) + continue; + + if (src) { + xop->dst_dev = se_dev; + pr_debug("XCOPY 0xe4: Setting xop->dst_dev: %p from located" + " se_dev\n", xop->dst_dev); + } else { + xop->src_dev = se_dev; + pr_debug("XCOPY 0xe4: Setting xop->src_dev: %p from located" + " se_dev\n", xop->src_dev); + } + + rc = configfs_depend_item(subsys, + &se_dev->dev_group.cg_item); + if (rc != 0) { + pr_err("configfs_depend_item attempt failed:" + " %d for se_dev: %p\n", rc, se_dev); + mutex_unlock(&g_device_mutex); + return rc; + } + + pr_debug("Called configfs_depend_item for subsys: %p se_dev: %p" + " se_dev->se_dev_group: %p\n", subsys, se_dev, + &se_dev->dev_group); + + mutex_unlock(&g_device_mutex); + return 0; + } + mutex_unlock(&g_device_mutex); + + pr_err("Unable to locate 0xe4 descriptor for EXTENDED_COPY\n"); + return -EINVAL; +} + +static int target_xcopy_parse_tiddesc_e4(struct se_cmd *se_cmd, struct xcopy_op *xop, + unsigned char *p, bool src) +{ + unsigned char *desc = p; + unsigned short ript; + u8 desig_len; + /* + * Extract RELATIVE INITIATOR PORT IDENTIFIER + */ + ript = get_unaligned_be16(&desc[2]); + pr_debug("XCOPY 0xe4: RELATIVE INITIATOR PORT IDENTIFIER: %hu\n", ript); + /* + * Check for supported code set, association, and designator type + */ + if ((desc[4] & 0x0f) != 0x1) { + pr_err("XCOPY 0xe4: code set of non binary type not supported\n"); + return -EINVAL; + } + if ((desc[5] & 0x30) != 0x00) { + pr_err("XCOPY 0xe4: association other than LUN not supported\n"); + return -EINVAL; + } + if ((desc[5] & 0x0f) != 0x3) { + pr_err("XCOPY 0xe4: designator type unsupported: 0x%02x\n", + (desc[5] & 0x0f)); + return -EINVAL; + } + /* + * Check for matching 16 byte length for NAA IEEE Registered Extended + * Assigned designator + */ + desig_len = desc[7]; + if (desig_len != 16) { + pr_err("XCOPY 0xe4: invalid desig_len: %d\n", (int)desig_len); + return -EINVAL; + } + pr_debug("XCOPY 0xe4: desig_len: %d\n", (int)desig_len); + /* + * Check for NAA IEEE Registered Extended Assigned header.. + */ + if ((desc[8] & 0xf0) != 0x60) { + pr_err("XCOPY 0xe4: Unsupported DESIGNATOR TYPE: 0x%02x\n", + (desc[8] & 0xf0)); + return -EINVAL; + } + + if (src) { + memcpy(&xop->src_tid_wwn[0], &desc[8], XCOPY_NAA_IEEE_REGEX_LEN); + /* + * Determine if the source designator matches the local device + */ + if (!memcmp(&xop->local_dev_wwn[0], &xop->src_tid_wwn[0], + XCOPY_NAA_IEEE_REGEX_LEN)) { + xop->op_origin = XCOL_SOURCE_RECV_OP; + xop->src_dev = se_cmd->se_dev; + pr_debug("XCOPY 0xe4: Set xop->src_dev %p from source" + " received xop\n", xop->src_dev); + } + } else { + memcpy(&xop->dst_tid_wwn[0], &desc[8], XCOPY_NAA_IEEE_REGEX_LEN); + /* + * Determine if the destination designator matches the local device + */ + if (!memcmp(&xop->local_dev_wwn[0], &xop->dst_tid_wwn[0], + XCOPY_NAA_IEEE_REGEX_LEN)) { + xop->op_origin = XCOL_DEST_RECV_OP; + xop->dst_dev = se_cmd->se_dev; + pr_debug("XCOPY 0xe4: Set xop->dst_dev: %p from destination" + " received xop\n", xop->dst_dev); + } + } + + return 0; +} + +static int target_xcopy_parse_target_descriptors(struct se_cmd *se_cmd, + struct xcopy_op *xop, unsigned char *p, + unsigned short tdll) +{ + struct se_device *local_dev = se_cmd->se_dev; + unsigned char *desc = p; + int offset = tdll % XCOPY_TARGET_DESC_LEN, rc, ret = 0; + unsigned short start = 0; + bool src = true; + + if (offset != 0) { + pr_err("XCOPY target descriptor list length is not" + " multiple of %d\n", XCOPY_TARGET_DESC_LEN); + return -EINVAL; + } + if (tdll > 64) { + pr_err("XCOPY target descriptor supports a maximum" + " two src/dest descriptors, tdll: %hu too large..\n", tdll); + return -EINVAL; + } + /* + * Generate an IEEE Registered Extended designator based upon the + * se_device the XCOPY was received upon.. + */ + memset(&xop->local_dev_wwn[0], 0, XCOPY_NAA_IEEE_REGEX_LEN); + target_xcopy_gen_naa_ieee(local_dev, &xop->local_dev_wwn[0]); + + while (start < tdll) { + /* + * Check target descriptor identification with 0xE4 type with + * use VPD 0x83 WWPN matching .. + */ + switch (desc[0]) { + case 0xe4: + rc = target_xcopy_parse_tiddesc_e4(se_cmd, xop, + &desc[0], src); + if (rc != 0) + goto out; + /* + * Assume target descriptors are in source -> destination order.. + */ + if (src) + src = false; + else + src = true; + start += XCOPY_TARGET_DESC_LEN; + desc += XCOPY_TARGET_DESC_LEN; + ret++; + break; + default: + pr_err("XCOPY unsupported descriptor type code:" + " 0x%02x\n", desc[0]); + goto out; + } + } + + if (xop->op_origin == XCOL_SOURCE_RECV_OP) + rc = target_xcopy_locate_se_dev_e4(se_cmd, xop, true); + else + rc = target_xcopy_locate_se_dev_e4(se_cmd, xop, false); + + if (rc < 0) + goto out; + + pr_debug("XCOPY TGT desc: Source dev: %p NAA IEEE WWN: 0x%16phN\n", + xop->src_dev, &xop->src_tid_wwn[0]); + pr_debug("XCOPY TGT desc: Dest dev: %p NAA IEEE WWN: 0x%16phN\n", + xop->dst_dev, &xop->dst_tid_wwn[0]); + + return ret; + +out: + return -EINVAL; +} + +static int target_xcopy_parse_segdesc_02(struct se_cmd *se_cmd, struct xcopy_op *xop, + unsigned char *p) +{ + unsigned char *desc = p; + int dc = (desc[1] & 0x02); + unsigned short desc_len; + + desc_len = get_unaligned_be16(&desc[2]); + if (desc_len != 0x18) { + pr_err("XCOPY segment desc 0x02: Illegal desc_len:" + " %hu\n", desc_len); + return -EINVAL; + } + + xop->stdi = get_unaligned_be16(&desc[4]); + xop->dtdi = get_unaligned_be16(&desc[6]); + pr_debug("XCOPY seg desc 0x02: desc_len: %hu stdi: %hu dtdi: %hu, DC: %d\n", + desc_len, xop->stdi, xop->dtdi, dc); + + xop->nolb = get_unaligned_be16(&desc[10]); + xop->src_lba = get_unaligned_be64(&desc[12]); + xop->dst_lba = get_unaligned_be64(&desc[20]); + pr_debug("XCOPY seg desc 0x02: nolb: %hu src_lba: %llu dst_lba: %llu\n", + xop->nolb, (unsigned long long)xop->src_lba, + (unsigned long long)xop->dst_lba); + + if (dc != 0) { + xop->dbl = (desc[29] & 0xff) << 16; + xop->dbl |= (desc[30] & 0xff) << 8; + xop->dbl |= desc[31] & 0xff; + + pr_debug("XCOPY seg desc 0x02: DC=1 w/ dbl: %u\n", xop->dbl); + } + return 0; +} + +static int target_xcopy_parse_segment_descriptors(struct se_cmd *se_cmd, + struct xcopy_op *xop, unsigned char *p, + unsigned int sdll) +{ + unsigned char *desc = p; + unsigned int start = 0; + int offset = sdll % XCOPY_SEGMENT_DESC_LEN, rc, ret = 0; + + if (offset != 0) { + pr_err("XCOPY segment descriptor list length is not" + " multiple of %d\n", XCOPY_SEGMENT_DESC_LEN); + return -EINVAL; + } + + while (start < sdll) { + /* + * Check segment descriptor type code for block -> block + */ + switch (desc[0]) { + case 0x02: + rc = target_xcopy_parse_segdesc_02(se_cmd, xop, desc); + if (rc < 0) + goto out; + + ret++; + start += XCOPY_SEGMENT_DESC_LEN; + desc += XCOPY_SEGMENT_DESC_LEN; + break; + default: + pr_err("XCOPY unspported segment descriptor" + "type: 0x%02x\n", desc[0]); + goto out; + } + } + + return ret; + +out: + return -EINVAL; +} + +/* + * Start xcopy_pt ops + */ + +struct xcopy_pt_cmd { + bool remote_port; + struct se_cmd se_cmd; + struct xcopy_op *xcopy_op; + struct completion xpt_passthrough_sem; + unsigned char sense_buffer[TRANSPORT_SENSE_BUFFER]; +}; + +static struct se_port xcopy_pt_port; +static struct se_portal_group xcopy_pt_tpg; +static struct se_session xcopy_pt_sess; +static struct se_node_acl xcopy_pt_nacl; + +static char *xcopy_pt_get_fabric_name(void) +{ + return "xcopy-pt"; +} + +static u32 xcopy_pt_get_tag(struct se_cmd *se_cmd) +{ + return 0; +} + +static int xcopy_pt_get_cmd_state(struct se_cmd *se_cmd) +{ + return 0; +} + +static void xcopy_pt_undepend_remotedev(struct xcopy_op *xop) +{ + struct configfs_subsystem *subsys = target_core_subsystem[0]; + struct se_device *remote_dev; + + if (xop->op_origin == XCOL_SOURCE_RECV_OP) + remote_dev = xop->dst_dev; + else + remote_dev = xop->src_dev; + + pr_debug("Calling configfs_undepend_item for subsys: %p" + " remote_dev: %p remote_dev->dev_group: %p\n", + subsys, remote_dev, &remote_dev->dev_group.cg_item); + + configfs_undepend_item(subsys, &remote_dev->dev_group.cg_item); +} + +static void xcopy_pt_release_cmd(struct se_cmd *se_cmd) +{ + struct xcopy_pt_cmd *xpt_cmd = container_of(se_cmd, + struct xcopy_pt_cmd, se_cmd); + + kfree(xpt_cmd); +} + +static int xcopy_pt_check_stop_free(struct se_cmd *se_cmd) +{ + struct xcopy_pt_cmd *xpt_cmd = container_of(se_cmd, + struct xcopy_pt_cmd, se_cmd); + + complete(&xpt_cmd->xpt_passthrough_sem); + return 0; +} + +static int xcopy_pt_write_pending(struct se_cmd *se_cmd) +{ + return 0; +} + +static int xcopy_pt_write_pending_status(struct se_cmd *se_cmd) +{ + return 0; +} + +static int xcopy_pt_queue_data_in(struct se_cmd *se_cmd) +{ + return 0; +} + +static int xcopy_pt_queue_status(struct se_cmd *se_cmd) +{ + return 0; +} + +static struct target_core_fabric_ops xcopy_pt_tfo = { + .get_fabric_name = xcopy_pt_get_fabric_name, + .get_task_tag = xcopy_pt_get_tag, + .get_cmd_state = xcopy_pt_get_cmd_state, + .release_cmd = xcopy_pt_release_cmd, + .check_stop_free = xcopy_pt_check_stop_free, + .write_pending = xcopy_pt_write_pending, + .write_pending_status = xcopy_pt_write_pending_status, + .queue_data_in = xcopy_pt_queue_data_in, + .queue_status = xcopy_pt_queue_status, +}; + +/* + * End xcopy_pt_ops + */ + +int target_xcopy_setup_pt(void) +{ + xcopy_wq = alloc_workqueue("xcopy_wq", WQ_MEM_RECLAIM, 0); + if (!xcopy_wq) { + pr_err("Unable to allocate xcopy_wq\n"); + return -ENOMEM; + } + + memset(&xcopy_pt_port, 0, sizeof(struct se_port)); + INIT_LIST_HEAD(&xcopy_pt_port.sep_alua_list); + INIT_LIST_HEAD(&xcopy_pt_port.sep_list); + mutex_init(&xcopy_pt_port.sep_tg_pt_md_mutex); + + memset(&xcopy_pt_tpg, 0, sizeof(struct se_portal_group)); + INIT_LIST_HEAD(&xcopy_pt_tpg.se_tpg_node); + INIT_LIST_HEAD(&xcopy_pt_tpg.acl_node_list); + INIT_LIST_HEAD(&xcopy_pt_tpg.tpg_sess_list); + + xcopy_pt_port.sep_tpg = &xcopy_pt_tpg; + xcopy_pt_tpg.se_tpg_tfo = &xcopy_pt_tfo; + + memset(&xcopy_pt_nacl, 0, sizeof(struct se_node_acl)); + INIT_LIST_HEAD(&xcopy_pt_nacl.acl_list); + INIT_LIST_HEAD(&xcopy_pt_nacl.acl_sess_list); + memset(&xcopy_pt_sess, 0, sizeof(struct se_session)); + INIT_LIST_HEAD(&xcopy_pt_sess.sess_list); + INIT_LIST_HEAD(&xcopy_pt_sess.sess_acl_list); + + xcopy_pt_nacl.se_tpg = &xcopy_pt_tpg; + xcopy_pt_nacl.nacl_sess = &xcopy_pt_sess; + + xcopy_pt_sess.se_tpg = &xcopy_pt_tpg; + xcopy_pt_sess.se_node_acl = &xcopy_pt_nacl; + + return 0; +} + +void target_xcopy_release_pt(void) +{ + if (xcopy_wq) + destroy_workqueue(xcopy_wq); +} + +static void target_xcopy_setup_pt_port( + struct xcopy_pt_cmd *xpt_cmd, + struct xcopy_op *xop, + bool remote_port) +{ + struct se_cmd *ec_cmd = xop->xop_se_cmd; + struct se_cmd *pt_cmd = &xpt_cmd->se_cmd; + + if (xop->op_origin == XCOL_SOURCE_RECV_OP) { + /* + * Honor destination port reservations for X-COPY PUSH emulation + * when CDB is received on local source port, and READs blocks to + * WRITE on remote destination port. + */ + if (remote_port) { + xpt_cmd->remote_port = remote_port; + pt_cmd->se_lun->lun_sep = &xcopy_pt_port; + pr_debug("Setup emulated remote DEST xcopy_pt_port: %p to" + " cmd->se_lun->lun_sep for X-COPY data PUSH\n", + pt_cmd->se_lun->lun_sep); + } else { + pt_cmd->se_lun = ec_cmd->se_lun; + pt_cmd->se_dev = ec_cmd->se_dev; + + pr_debug("Honoring local SRC port from ec_cmd->se_dev:" + " %p\n", pt_cmd->se_dev); + pt_cmd->se_lun = ec_cmd->se_lun; + pr_debug("Honoring local SRC port from ec_cmd->se_lun: %p\n", + pt_cmd->se_lun); + } + } else { + /* + * Honor source port reservation for X-COPY PULL emulation + * when CDB is received on local desintation port, and READs + * blocks from the remote source port to WRITE on local + * destination port. + */ + if (remote_port) { + xpt_cmd->remote_port = remote_port; + pt_cmd->se_lun->lun_sep = &xcopy_pt_port; + pr_debug("Setup emulated remote SRC xcopy_pt_port: %p to" + " cmd->se_lun->lun_sep for X-COPY data PULL\n", + pt_cmd->se_lun->lun_sep); + } else { + pt_cmd->se_lun = ec_cmd->se_lun; + pt_cmd->se_dev = ec_cmd->se_dev; + + pr_debug("Honoring local DST port from ec_cmd->se_dev:" + " %p\n", pt_cmd->se_dev); + pt_cmd->se_lun = ec_cmd->se_lun; + pr_debug("Honoring local DST port from ec_cmd->se_lun: %p\n", + pt_cmd->se_lun); + } + } +} + +static int target_xcopy_init_pt_lun( + struct xcopy_pt_cmd *xpt_cmd, + struct xcopy_op *xop, + struct se_device *se_dev, + struct se_cmd *pt_cmd, + bool remote_port) +{ + /* + * Don't allocate + init an pt_cmd->se_lun if honoring local port for + * reservations. The pt_cmd->se_lun pointer will be setup from within + * target_xcopy_setup_pt_port() + */ + if (!remote_port) { + pt_cmd->se_cmd_flags |= SCF_SE_LUN_CMD | SCF_CMD_XCOPY_PASSTHROUGH; + return 0; + } + + pt_cmd->se_lun = &se_dev->xcopy_lun; + pt_cmd->se_dev = se_dev; + + pr_debug("Setup emulated se_dev: %p from se_dev\n", pt_cmd->se_dev); + pt_cmd->se_cmd_flags |= SCF_SE_LUN_CMD | SCF_CMD_XCOPY_PASSTHROUGH; + + pr_debug("Setup emulated se_dev: %p to pt_cmd->se_lun->lun_se_dev\n", + pt_cmd->se_lun->lun_se_dev); + + return 0; +} + +static int target_xcopy_setup_pt_cmd( + struct xcopy_pt_cmd *xpt_cmd, + struct xcopy_op *xop, + struct se_device *se_dev, + unsigned char *cdb, + bool remote_port, + bool alloc_mem) +{ + struct se_cmd *cmd = &xpt_cmd->se_cmd; + sense_reason_t sense_rc; + int ret = 0, rc; + /* + * Setup LUN+port to honor reservations based upon xop->op_origin for + * X-COPY PUSH or X-COPY PULL based upon where the CDB was received. + */ + rc = target_xcopy_init_pt_lun(xpt_cmd, xop, se_dev, cmd, remote_port); + if (rc < 0) { + ret = rc; + goto out; + } + xpt_cmd->xcopy_op = xop; + target_xcopy_setup_pt_port(xpt_cmd, xop, remote_port); + + sense_rc = target_setup_cmd_from_cdb(cmd, cdb); + if (sense_rc) { + ret = -EINVAL; + goto out; + } + + if (alloc_mem) { + rc = target_alloc_sgl(&cmd->t_data_sg, &cmd->t_data_nents, + cmd->data_length, false); + if (rc < 0) { + ret = rc; + goto out; + } + /* + * Set this bit so that transport_free_pages() allows the + * caller to release SGLs + physical memory allocated by + * transport_generic_get_mem().. + */ + cmd->se_cmd_flags |= SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC; + } else { + /* + * Here the previously allocated SGLs for the internal READ + * are mapped zero-copy to the internal WRITE. + */ + sense_rc = transport_generic_map_mem_to_cmd(cmd, + xop->xop_data_sg, xop->xop_data_nents, + NULL, 0); + if (sense_rc) { + ret = -EINVAL; + goto out; + } + + pr_debug("Setup PASSTHROUGH_NOALLOC t_data_sg: %p t_data_nents:" + " %u\n", cmd->t_data_sg, cmd->t_data_nents); + } + + return 0; + +out: + return ret; +} + +static int target_xcopy_issue_pt_cmd(struct xcopy_pt_cmd *xpt_cmd) +{ + struct se_cmd *se_cmd = &xpt_cmd->se_cmd; + sense_reason_t sense_rc; + + sense_rc = transport_generic_new_cmd(se_cmd); + if (sense_rc) + return -EINVAL; + + if (se_cmd->data_direction == DMA_TO_DEVICE) + target_execute_cmd(se_cmd); + + wait_for_completion_interruptible(&xpt_cmd->xpt_passthrough_sem); + + pr_debug("target_xcopy_issue_pt_cmd(): SCSI status: 0x%02x\n", + se_cmd->scsi_status); + + return (se_cmd->scsi_status) ? -EINVAL : 0; +} + +static int target_xcopy_read_source( + struct se_cmd *ec_cmd, + struct xcopy_op *xop, + struct se_device *src_dev, + sector_t src_lba, + u32 src_sectors) +{ + struct xcopy_pt_cmd *xpt_cmd; + struct se_cmd *se_cmd; + u32 length = (src_sectors * src_dev->dev_attrib.block_size); + int rc; + unsigned char cdb[16]; + bool remote_port = (xop->op_origin == XCOL_DEST_RECV_OP); + + xpt_cmd = kzalloc(sizeof(struct xcopy_pt_cmd), GFP_KERNEL); + if (!xpt_cmd) { + pr_err("Unable to allocate xcopy_pt_cmd\n"); + return -ENOMEM; + } + init_completion(&xpt_cmd->xpt_passthrough_sem); + se_cmd = &xpt_cmd->se_cmd; + + memset(&cdb[0], 0, 16); + cdb[0] = READ_16; + put_unaligned_be64(src_lba, &cdb[2]); + put_unaligned_be32(src_sectors, &cdb[10]); + pr_debug("XCOPY: Built READ_16: LBA: %llu Sectors: %u Length: %u\n", + (unsigned long long)src_lba, src_sectors, length); + + transport_init_se_cmd(se_cmd, &xcopy_pt_tfo, NULL, length, + DMA_FROM_DEVICE, 0, &xpt_cmd->sense_buffer[0]); + xop->src_pt_cmd = xpt_cmd; + + rc = target_xcopy_setup_pt_cmd(xpt_cmd, xop, src_dev, &cdb[0], + remote_port, true); + if (rc < 0) { + transport_generic_free_cmd(se_cmd, 0); + return rc; + } + + xop->xop_data_sg = se_cmd->t_data_sg; + xop->xop_data_nents = se_cmd->t_data_nents; + pr_debug("XCOPY-READ: Saved xop->xop_data_sg: %p, num: %u for READ" + " memory\n", xop->xop_data_sg, xop->xop_data_nents); + + rc = target_xcopy_issue_pt_cmd(xpt_cmd); + if (rc < 0) { + transport_generic_free_cmd(se_cmd, 0); + return rc; + } + /* + * Clear off the allocated t_data_sg, that has been saved for + * zero-copy WRITE submission reuse in struct xcopy_op.. + */ + se_cmd->t_data_sg = NULL; + se_cmd->t_data_nents = 0; + + return 0; +} + +static int target_xcopy_write_destination( + struct se_cmd *ec_cmd, + struct xcopy_op *xop, + struct se_device *dst_dev, + sector_t dst_lba, + u32 dst_sectors) +{ + struct xcopy_pt_cmd *xpt_cmd; + struct se_cmd *se_cmd; + u32 length = (dst_sectors * dst_dev->dev_attrib.block_size); + int rc; + unsigned char cdb[16]; + bool remote_port = (xop->op_origin == XCOL_SOURCE_RECV_OP); + + xpt_cmd = kzalloc(sizeof(struct xcopy_pt_cmd), GFP_KERNEL); + if (!xpt_cmd) { + pr_err("Unable to allocate xcopy_pt_cmd\n"); + return -ENOMEM; + } + init_completion(&xpt_cmd->xpt_passthrough_sem); + se_cmd = &xpt_cmd->se_cmd; + + memset(&cdb[0], 0, 16); + cdb[0] = WRITE_16; + put_unaligned_be64(dst_lba, &cdb[2]); + put_unaligned_be32(dst_sectors, &cdb[10]); + pr_debug("XCOPY: Built WRITE_16: LBA: %llu Sectors: %u Length: %u\n", + (unsigned long long)dst_lba, dst_sectors, length); + + transport_init_se_cmd(se_cmd, &xcopy_pt_tfo, NULL, length, + DMA_TO_DEVICE, 0, &xpt_cmd->sense_buffer[0]); + xop->dst_pt_cmd = xpt_cmd; + + rc = target_xcopy_setup_pt_cmd(xpt_cmd, xop, dst_dev, &cdb[0], + remote_port, false); + if (rc < 0) { + struct se_cmd *src_cmd = &xop->src_pt_cmd->se_cmd; + /* + * If the failure happened before the t_mem_list hand-off in + * target_xcopy_setup_pt_cmd(), Reset memory + clear flag so that + * core releases this memory on error during X-COPY WRITE I/O. + */ + src_cmd->se_cmd_flags &= ~SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC; + src_cmd->t_data_sg = xop->xop_data_sg; + src_cmd->t_data_nents = xop->xop_data_nents; + + transport_generic_free_cmd(se_cmd, 0); + return rc; + } + + rc = target_xcopy_issue_pt_cmd(xpt_cmd); + if (rc < 0) { + se_cmd->se_cmd_flags &= ~SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC; + transport_generic_free_cmd(se_cmd, 0); + return rc; + } + + return 0; +} + +static void target_xcopy_do_work(struct work_struct *work) +{ + struct xcopy_op *xop = container_of(work, struct xcopy_op, xop_work); + struct se_device *src_dev = xop->src_dev, *dst_dev = xop->dst_dev; + struct se_cmd *ec_cmd = xop->xop_se_cmd; + sector_t src_lba = xop->src_lba, dst_lba = xop->dst_lba, end_lba; + unsigned int max_sectors; + int rc; + unsigned short nolb = xop->nolb, cur_nolb, max_nolb, copied_nolb = 0; + + end_lba = src_lba + nolb; + /* + * Break up XCOPY I/O into hw_max_sectors sized I/O based on the + * smallest max_sectors between src_dev + dev_dev, or + */ + max_sectors = min(src_dev->dev_attrib.hw_max_sectors, + dst_dev->dev_attrib.hw_max_sectors); + max_sectors = min_t(u32, max_sectors, XCOPY_MAX_SECTORS); + + max_nolb = min_t(u16, max_sectors, ((u16)(~0U))); + + pr_debug("target_xcopy_do_work: nolb: %hu, max_nolb: %hu end_lba: %llu\n", + nolb, max_nolb, (unsigned long long)end_lba); + pr_debug("target_xcopy_do_work: Starting src_lba: %llu, dst_lba: %llu\n", + (unsigned long long)src_lba, (unsigned long long)dst_lba); + + while (src_lba < end_lba) { + cur_nolb = min(nolb, max_nolb); + + pr_debug("target_xcopy_do_work: Calling read src_dev: %p src_lba: %llu," + " cur_nolb: %hu\n", src_dev, (unsigned long long)src_lba, cur_nolb); + + rc = target_xcopy_read_source(ec_cmd, xop, src_dev, src_lba, cur_nolb); + if (rc < 0) + goto out; + + src_lba += cur_nolb; + pr_debug("target_xcopy_do_work: Incremented READ src_lba to %llu\n", + (unsigned long long)src_lba); + + pr_debug("target_xcopy_do_work: Calling write dst_dev: %p dst_lba: %llu," + " cur_nolb: %hu\n", dst_dev, (unsigned long long)dst_lba, cur_nolb); + + rc = target_xcopy_write_destination(ec_cmd, xop, dst_dev, + dst_lba, cur_nolb); + if (rc < 0) { + transport_generic_free_cmd(&xop->src_pt_cmd->se_cmd, 0); + goto out; + } + + dst_lba += cur_nolb; + pr_debug("target_xcopy_do_work: Incremented WRITE dst_lba to %llu\n", + (unsigned long long)dst_lba); + + copied_nolb += cur_nolb; + nolb -= cur_nolb; + + transport_generic_free_cmd(&xop->src_pt_cmd->se_cmd, 0); + xop->dst_pt_cmd->se_cmd.se_cmd_flags &= ~SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC; + + transport_generic_free_cmd(&xop->dst_pt_cmd->se_cmd, 0); + } + + xcopy_pt_undepend_remotedev(xop); + kfree(xop); + + pr_debug("target_xcopy_do_work: Final src_lba: %llu, dst_lba: %llu\n", + (unsigned long long)src_lba, (unsigned long long)dst_lba); + pr_debug("target_xcopy_do_work: Blocks copied: %hu, Bytes Copied: %u\n", + copied_nolb, copied_nolb * dst_dev->dev_attrib.block_size); + + pr_debug("target_xcopy_do_work: Setting X-COPY GOOD status -> sending response\n"); + target_complete_cmd(ec_cmd, SAM_STAT_GOOD); + return; + +out: + xcopy_pt_undepend_remotedev(xop); + kfree(xop); + + pr_warn("target_xcopy_do_work: Setting X-COPY CHECK_CONDITION -> sending response\n"); + ec_cmd->scsi_status = SAM_STAT_CHECK_CONDITION; + target_complete_cmd(ec_cmd, SAM_STAT_CHECK_CONDITION); +} + +sense_reason_t target_do_xcopy(struct se_cmd *se_cmd) +{ + struct se_device *dev = se_cmd->se_dev; + struct xcopy_op *xop = NULL; + unsigned char *p = NULL, *seg_desc; + unsigned int list_id, list_id_usage, sdll, inline_dl, sa; + sense_reason_t ret = TCM_INVALID_PARAMETER_LIST; + int rc; + unsigned short tdll; + + if (!dev->dev_attrib.emulate_3pc) { + pr_err("EXTENDED_COPY operation explicitly disabled\n"); + return TCM_UNSUPPORTED_SCSI_OPCODE; + } + + sa = se_cmd->t_task_cdb[1] & 0x1f; + if (sa != 0x00) { + pr_err("EXTENDED_COPY(LID4) not supported\n"); + return TCM_UNSUPPORTED_SCSI_OPCODE; + } + + xop = kzalloc(sizeof(struct xcopy_op), GFP_KERNEL); + if (!xop) { + pr_err("Unable to allocate xcopy_op\n"); + return TCM_OUT_OF_RESOURCES; + } + xop->xop_se_cmd = se_cmd; + + p = transport_kmap_data_sg(se_cmd); + if (!p) { + pr_err("transport_kmap_data_sg() failed in target_do_xcopy\n"); + kfree(xop); + return TCM_OUT_OF_RESOURCES; + } + + list_id = p[0]; + list_id_usage = (p[1] & 0x18) >> 3; + + /* + * Determine TARGET DESCRIPTOR LIST LENGTH + SEGMENT DESCRIPTOR LIST LENGTH + */ + tdll = get_unaligned_be16(&p[2]); + sdll = get_unaligned_be32(&p[8]); + + inline_dl = get_unaligned_be32(&p[12]); + if (inline_dl != 0) { + pr_err("XCOPY with non zero inline data length\n"); + goto out; + } + + pr_debug("Processing XCOPY with list_id: 0x%02x list_id_usage: 0x%02x" + " tdll: %hu sdll: %u inline_dl: %u\n", list_id, list_id_usage, + tdll, sdll, inline_dl); + + rc = target_xcopy_parse_target_descriptors(se_cmd, xop, &p[16], tdll); + if (rc <= 0) + goto out; + + if (xop->src_dev->dev_attrib.block_size != + xop->dst_dev->dev_attrib.block_size) { + pr_err("XCOPY: Non matching src_dev block_size: %u + dst_dev" + " block_size: %u currently unsupported\n", + xop->src_dev->dev_attrib.block_size, + xop->dst_dev->dev_attrib.block_size); + xcopy_pt_undepend_remotedev(xop); + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto out; + } + + pr_debug("XCOPY: Processed %d target descriptors, length: %u\n", rc, + rc * XCOPY_TARGET_DESC_LEN); + seg_desc = &p[16]; + seg_desc += (rc * XCOPY_TARGET_DESC_LEN); + + rc = target_xcopy_parse_segment_descriptors(se_cmd, xop, seg_desc, sdll); + if (rc <= 0) { + xcopy_pt_undepend_remotedev(xop); + goto out; + } + transport_kunmap_data_sg(se_cmd); + + pr_debug("XCOPY: Processed %d segment descriptors, length: %u\n", rc, + rc * XCOPY_SEGMENT_DESC_LEN); + INIT_WORK(&xop->xop_work, target_xcopy_do_work); + queue_work(xcopy_wq, &xop->xop_work); + return TCM_NO_SENSE; + +out: + if (p) + transport_kunmap_data_sg(se_cmd); + kfree(xop); + return ret; +} + +static sense_reason_t target_rcr_operating_parameters(struct se_cmd *se_cmd) +{ + unsigned char *p; + + p = transport_kmap_data_sg(se_cmd); + if (!p) { + pr_err("transport_kmap_data_sg failed in" + " target_rcr_operating_parameters\n"); + return TCM_OUT_OF_RESOURCES; + } + + if (se_cmd->data_length < 54) { + pr_err("Receive Copy Results Op Parameters length" + " too small: %u\n", se_cmd->data_length); + transport_kunmap_data_sg(se_cmd); + return TCM_INVALID_CDB_FIELD; + } + /* + * Set SNLID=1 (Supports no List ID) + */ + p[4] = 0x1; + /* + * MAXIMUM TARGET DESCRIPTOR COUNT + */ + put_unaligned_be16(RCR_OP_MAX_TARGET_DESC_COUNT, &p[8]); + /* + * MAXIMUM SEGMENT DESCRIPTOR COUNT + */ + put_unaligned_be16(RCR_OP_MAX_SG_DESC_COUNT, &p[10]); + /* + * MAXIMUM DESCRIPTOR LIST LENGTH + */ + put_unaligned_be32(RCR_OP_MAX_DESC_LIST_LEN, &p[12]); + /* + * MAXIMUM SEGMENT LENGTH + */ + put_unaligned_be32(RCR_OP_MAX_SEGMENT_LEN, &p[16]); + /* + * MAXIMUM INLINE DATA LENGTH for SA 0x04 (NOT SUPPORTED) + */ + put_unaligned_be32(0x0, &p[20]); + /* + * HELD DATA LIMIT + */ + put_unaligned_be32(0x0, &p[24]); + /* + * MAXIMUM STREAM DEVICE TRANSFER SIZE + */ + put_unaligned_be32(0x0, &p[28]); + /* + * TOTAL CONCURRENT COPIES + */ + put_unaligned_be16(RCR_OP_TOTAL_CONCURR_COPIES, &p[34]); + /* + * MAXIMUM CONCURRENT COPIES + */ + p[36] = RCR_OP_MAX_CONCURR_COPIES; + /* + * DATA SEGMENT GRANULARITY (log 2) + */ + p[37] = RCR_OP_DATA_SEG_GRAN_LOG2; + /* + * INLINE DATA GRANULARITY log 2) + */ + p[38] = RCR_OP_INLINE_DATA_GRAN_LOG2; + /* + * HELD DATA GRANULARITY + */ + p[39] = RCR_OP_HELD_DATA_GRAN_LOG2; + /* + * IMPLEMENTED DESCRIPTOR LIST LENGTH + */ + p[43] = 0x2; + /* + * List of implemented descriptor type codes (ordered) + */ + p[44] = 0x02; /* Copy Block to Block device */ + p[45] = 0xe4; /* Identification descriptor target descriptor */ + + /* + * AVAILABLE DATA (n-3) + */ + put_unaligned_be32(42, &p[0]); + + transport_kunmap_data_sg(se_cmd); + target_complete_cmd(se_cmd, GOOD); + + return TCM_NO_SENSE; +} + +sense_reason_t target_do_receive_copy_results(struct se_cmd *se_cmd) +{ + unsigned char *cdb = &se_cmd->t_task_cdb[0]; + int sa = (cdb[1] & 0x1f), list_id = cdb[2]; + sense_reason_t rc = TCM_NO_SENSE; + + pr_debug("Entering target_do_receive_copy_results: SA: 0x%02x, List ID:" + " 0x%02x, AL: %u\n", sa, list_id, se_cmd->data_length); + + if (list_id != 0) { + pr_err("Receive Copy Results with non zero list identifier" + " not supported\n"); + return TCM_INVALID_CDB_FIELD; + } + + switch (sa) { + case RCR_SA_OPERATING_PARAMETERS: + rc = target_rcr_operating_parameters(se_cmd); + break; + case RCR_SA_COPY_STATUS: + case RCR_SA_RECEIVE_DATA: + case RCR_SA_FAILED_SEGMENT_DETAILS: + default: + pr_err("Unsupported SA for receive copy results: 0x%02x\n", sa); + return TCM_INVALID_CDB_FIELD; + } + + return rc; +} diff --git a/drivers/target/target_core_xcopy.h b/drivers/target/target_core_xcopy.h new file mode 100644 index 00000000000..700a981c7b4 --- /dev/null +++ b/drivers/target/target_core_xcopy.h @@ -0,0 +1,62 @@ +#define XCOPY_TARGET_DESC_LEN 32 +#define XCOPY_SEGMENT_DESC_LEN 28 +#define XCOPY_NAA_IEEE_REGEX_LEN 16 +#define XCOPY_MAX_SECTORS 1024 + +enum xcopy_origin_list { + XCOL_SOURCE_RECV_OP = 0x01, + XCOL_DEST_RECV_OP = 0x02, +}; + +struct xcopy_pt_cmd; + +struct xcopy_op { + int op_origin; + + struct se_cmd *xop_se_cmd; + struct se_device *src_dev; + unsigned char src_tid_wwn[XCOPY_NAA_IEEE_REGEX_LEN]; + struct se_device *dst_dev; + unsigned char dst_tid_wwn[XCOPY_NAA_IEEE_REGEX_LEN]; + unsigned char local_dev_wwn[XCOPY_NAA_IEEE_REGEX_LEN]; + + sector_t src_lba; + sector_t dst_lba; + unsigned short stdi; + unsigned short dtdi; + unsigned short nolb; + unsigned int dbl; + + struct xcopy_pt_cmd *src_pt_cmd; + struct xcopy_pt_cmd *dst_pt_cmd; + + u32 xop_data_nents; + struct scatterlist *xop_data_sg; + struct work_struct xop_work; +}; + +/* + * Receive Copy Results Sevice Actions + */ +#define RCR_SA_COPY_STATUS 0x00 +#define RCR_SA_RECEIVE_DATA 0x01 +#define RCR_SA_OPERATING_PARAMETERS 0x03 +#define RCR_SA_FAILED_SEGMENT_DETAILS 0x04 + +/* + * Receive Copy Results defs for Operating Parameters + */ +#define RCR_OP_MAX_TARGET_DESC_COUNT 0x2 +#define RCR_OP_MAX_SG_DESC_COUNT 0x1 +#define RCR_OP_MAX_DESC_LIST_LEN 1024 +#define RCR_OP_MAX_SEGMENT_LEN 268435456 /* 256 MB */ +#define RCR_OP_TOTAL_CONCURR_COPIES 0x1 /* Must be <= 16384 */ +#define RCR_OP_MAX_CONCURR_COPIES 0x1 /* Must be <= 255 */ +#define RCR_OP_DATA_SEG_GRAN_LOG2 9 /* 512 bytes in log 2 */ +#define RCR_OP_INLINE_DATA_GRAN_LOG2 9 /* 512 bytes in log 2 */ +#define RCR_OP_HELD_DATA_GRAN_LOG2 9 /* 512 bytes in log 2 */ + +extern int target_xcopy_setup_pt(void); +extern void target_xcopy_release_pt(void); +extern sense_reason_t target_do_xcopy(struct se_cmd *); +extern sense_reason_t target_do_receive_copy_results(struct se_cmd *); diff --git a/drivers/target/tcm_fc/Kconfig b/drivers/target/tcm_fc/Kconfig new file mode 100644 index 00000000000..40caf458e89 --- /dev/null +++ b/drivers/target/tcm_fc/Kconfig @@ -0,0 +1,5 @@ +config TCM_FC + tristate "TCM_FC fabric Plugin" + depends on LIBFC + help + Say Y here to enable the TCM FC plugin for accessing FC fabrics in TCM diff --git a/drivers/target/tcm_fc/Makefile b/drivers/target/tcm_fc/Makefile new file mode 100644 index 00000000000..20b14bb087c --- /dev/null +++ b/drivers/target/tcm_fc/Makefile @@ -0,0 +1,6 @@ +tcm_fc-y += tfc_cmd.o \ + tfc_conf.o \ + tfc_io.o \ + tfc_sess.o + +obj-$(CONFIG_TCM_FC) += tcm_fc.o diff --git a/drivers/target/tcm_fc/tcm_fc.h b/drivers/target/tcm_fc/tcm_fc.h new file mode 100644 index 00000000000..a0bcfd3e7e7 --- /dev/null +++ b/drivers/target/tcm_fc/tcm_fc.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2010 Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef __TCM_FC_H__ +#define __TCM_FC_H__ + +#define FT_VERSION "0.4" + +#define FT_NAMELEN 32 /* length of ASCII WWPNs including pad */ +#define FT_TPG_NAMELEN 32 /* max length of TPG name */ +#define FT_LUN_NAMELEN 32 /* max length of LUN name */ +#define TCM_FC_DEFAULT_TAGS 512 /* tags used for per-session preallocation */ + +struct ft_transport_id { + __u8 format; + __u8 __resvd1[7]; + __u8 wwpn[8]; + __u8 __resvd2[8]; +} __attribute__((__packed__)); + +/* + * Session (remote port). + */ +struct ft_sess { + u32 port_id; /* for hash lookup use only */ + u32 params; + u16 max_frame; /* maximum frame size */ + u64 port_name; /* port name for transport ID */ + struct ft_tport *tport; + struct se_session *se_sess; + struct hlist_node hash; /* linkage in ft_sess_hash table */ + struct rcu_head rcu; + struct kref kref; /* ref for hash and outstanding I/Os */ +}; + +/* + * Hash table of sessions per local port. + * Hash lookup by remote port FC_ID. + */ +#define FT_SESS_HASH_BITS 6 +#define FT_SESS_HASH_SIZE (1 << FT_SESS_HASH_BITS) + +/* + * Per local port data. + * This is created only after a TPG exists that allows target function + * for the local port. If the TPG exists, this is allocated when + * we're notified that the local port has been created, or when + * the first PRLI provider callback is received. + */ +struct ft_tport { + struct fc_lport *lport; + struct ft_tpg *tpg; /* NULL if TPG deleted before tport */ + u32 sess_count; /* number of sessions in hash */ + struct rcu_head rcu; + struct hlist_head hash[FT_SESS_HASH_SIZE]; /* list of sessions */ +}; + +/* + * Node ID and authentication. + */ +struct ft_node_auth { + u64 port_name; + u64 node_name; +}; + +/* + * Node ACL for FC remote port session. + */ +struct ft_node_acl { + struct ft_node_auth node_auth; + struct se_node_acl se_node_acl; +}; + +struct ft_lun { + u32 index; + char name[FT_LUN_NAMELEN]; +}; + +/* + * Target portal group (local port). + */ +struct ft_tpg { + u32 index; + struct ft_lport_wwn *lport_wwn; + struct ft_tport *tport; /* active tport or NULL */ + struct list_head lun_list; /* head of LUNs */ + struct se_portal_group se_tpg; + struct workqueue_struct *workqueue; +}; + +struct ft_lport_wwn { + u64 wwpn; + char name[FT_NAMELEN]; + struct list_head ft_wwn_node; + struct ft_tpg *tpg; + struct se_wwn se_wwn; +}; + +/* + * Commands + */ +struct ft_cmd { + struct ft_sess *sess; /* session held for cmd */ + struct fc_seq *seq; /* sequence in exchange mgr */ + struct se_cmd se_cmd; /* Local TCM I/O descriptor */ + struct fc_frame *req_frame; + u32 write_data_len; /* data received on writes */ + struct work_struct work; + /* Local sense buffer */ + unsigned char ft_sense_buffer[TRANSPORT_SENSE_BUFFER]; + u32 was_ddp_setup:1; /* Set only if ddp is setup */ + u32 aborted:1; /* Set if aborted by reset or timeout */ + struct scatterlist *sg; /* Set only if DDP is setup */ + u32 sg_cnt; /* No. of item in scatterlist */ +}; + +extern struct mutex ft_lport_lock; +extern struct fc4_prov ft_prov; +extern struct target_fabric_configfs *ft_configfs; +extern unsigned int ft_debug_logging; + +/* + * Fabric methods. + */ + +/* + * Session ops. + */ +void ft_sess_put(struct ft_sess *); +int ft_sess_shutdown(struct se_session *); +void ft_sess_close(struct se_session *); +u32 ft_sess_get_index(struct se_session *); +u32 ft_sess_get_port_name(struct se_session *, unsigned char *, u32); + +void ft_lport_add(struct fc_lport *, void *); +void ft_lport_del(struct fc_lport *, void *); +int ft_lport_notify(struct notifier_block *, unsigned long, void *); + +/* + * IO methods. + */ +int ft_check_stop_free(struct se_cmd *); +void ft_release_cmd(struct se_cmd *); +int ft_queue_status(struct se_cmd *); +int ft_queue_data_in(struct se_cmd *); +int ft_write_pending(struct se_cmd *); +int ft_write_pending_status(struct se_cmd *); +u32 ft_get_task_tag(struct se_cmd *); +int ft_get_cmd_state(struct se_cmd *); +void ft_queue_tm_resp(struct se_cmd *); +void ft_aborted_task(struct se_cmd *); + +/* + * other internal functions. + */ +void ft_recv_req(struct ft_sess *, struct fc_frame *); +struct ft_tpg *ft_lport_find_tpg(struct fc_lport *); +struct ft_node_acl *ft_acl_get(struct ft_tpg *, struct fc_rport_priv *); + +void ft_recv_write_data(struct ft_cmd *, struct fc_frame *); +void ft_dump_cmd(struct ft_cmd *, const char *caller); + +ssize_t ft_format_wwn(char *, size_t, u64); + +/* + * Underlying HW specific helper function + */ +void ft_invl_hw_context(struct ft_cmd *); + +#endif /* __TCM_FC_H__ */ diff --git a/drivers/target/tcm_fc/tfc_cmd.c b/drivers/target/tcm_fc/tfc_cmd.c new file mode 100644 index 00000000000..be0c0d08c56 --- /dev/null +++ b/drivers/target/tcm_fc/tfc_cmd.c @@ -0,0 +1,585 @@ +/* + * Copyright (c) 2010 Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* XXX TBD some includes may be extraneous */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/utsname.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <linux/hash.h> +#include <linux/percpu_ida.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_tcq.h> +#include <scsi/libfc.h> +#include <scsi/fc_encode.h> + +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> + +#include "tcm_fc.h" + +/* + * Dump cmd state for debugging. + */ +static void _ft_dump_cmd(struct ft_cmd *cmd, const char *caller) +{ + struct fc_exch *ep; + struct fc_seq *sp; + struct se_cmd *se_cmd; + struct scatterlist *sg; + int count; + + se_cmd = &cmd->se_cmd; + pr_debug("%s: cmd %p sess %p seq %p se_cmd %p\n", + caller, cmd, cmd->sess, cmd->seq, se_cmd); + + pr_debug("%s: cmd %p data_nents %u len %u se_cmd_flags <0x%x>\n", + caller, cmd, se_cmd->t_data_nents, + se_cmd->data_length, se_cmd->se_cmd_flags); + + for_each_sg(se_cmd->t_data_sg, sg, se_cmd->t_data_nents, count) + pr_debug("%s: cmd %p sg %p page %p " + "len 0x%x off 0x%x\n", + caller, cmd, sg, + sg_page(sg), sg->length, sg->offset); + + sp = cmd->seq; + if (sp) { + ep = fc_seq_exch(sp); + pr_debug("%s: cmd %p sid %x did %x " + "ox_id %x rx_id %x seq_id %x e_stat %x\n", + caller, cmd, ep->sid, ep->did, ep->oxid, ep->rxid, + sp->id, ep->esb_stat); + } +} + +void ft_dump_cmd(struct ft_cmd *cmd, const char *caller) +{ + if (unlikely(ft_debug_logging)) + _ft_dump_cmd(cmd, caller); +} + +static void ft_free_cmd(struct ft_cmd *cmd) +{ + struct fc_frame *fp; + struct fc_lport *lport; + struct ft_sess *sess; + + if (!cmd) + return; + sess = cmd->sess; + fp = cmd->req_frame; + lport = fr_dev(fp); + if (fr_seq(fp)) + lport->tt.seq_release(fr_seq(fp)); + fc_frame_free(fp); + percpu_ida_free(&sess->se_sess->sess_tag_pool, cmd->se_cmd.map_tag); + ft_sess_put(sess); /* undo get from lookup at recv */ +} + +void ft_release_cmd(struct se_cmd *se_cmd) +{ + struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); + + ft_free_cmd(cmd); +} + +int ft_check_stop_free(struct se_cmd *se_cmd) +{ + transport_generic_free_cmd(se_cmd, 0); + return 1; +} + +/* + * Send response. + */ +int ft_queue_status(struct se_cmd *se_cmd) +{ + struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); + struct fc_frame *fp; + struct fcp_resp_with_ext *fcp; + struct fc_lport *lport; + struct fc_exch *ep; + size_t len; + int rc; + + if (cmd->aborted) + return 0; + ft_dump_cmd(cmd, __func__); + ep = fc_seq_exch(cmd->seq); + lport = ep->lp; + len = sizeof(*fcp) + se_cmd->scsi_sense_length; + fp = fc_frame_alloc(lport, len); + if (!fp) { + se_cmd->scsi_status = SAM_STAT_TASK_SET_FULL; + return -ENOMEM; + } + + fcp = fc_frame_payload_get(fp, len); + memset(fcp, 0, len); + fcp->resp.fr_status = se_cmd->scsi_status; + + len = se_cmd->scsi_sense_length; + if (len) { + fcp->resp.fr_flags |= FCP_SNS_LEN_VAL; + fcp->ext.fr_sns_len = htonl(len); + memcpy((fcp + 1), se_cmd->sense_buffer, len); + } + + /* + * Test underflow and overflow with one mask. Usually both are off. + * Bidirectional commands are not handled yet. + */ + if (se_cmd->se_cmd_flags & (SCF_OVERFLOW_BIT | SCF_UNDERFLOW_BIT)) { + if (se_cmd->se_cmd_flags & SCF_OVERFLOW_BIT) + fcp->resp.fr_flags |= FCP_RESID_OVER; + else + fcp->resp.fr_flags |= FCP_RESID_UNDER; + fcp->ext.fr_resid = cpu_to_be32(se_cmd->residual_count); + } + + /* + * Send response. + */ + cmd->seq = lport->tt.seq_start_next(cmd->seq); + fc_fill_fc_hdr(fp, FC_RCTL_DD_CMD_STATUS, ep->did, ep->sid, FC_TYPE_FCP, + FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ, 0); + + rc = lport->tt.seq_send(lport, cmd->seq, fp); + if (rc) { + pr_info_ratelimited("%s: Failed to send response frame %p, " + "xid <0x%x>\n", __func__, fp, ep->xid); + /* + * Generate a TASK_SET_FULL status to notify the initiator + * to reduce it's queue_depth after the se_cmd response has + * been re-queued by target-core. + */ + se_cmd->scsi_status = SAM_STAT_TASK_SET_FULL; + return -ENOMEM; + } + lport->tt.exch_done(cmd->seq); + return 0; +} + +int ft_write_pending_status(struct se_cmd *se_cmd) +{ + struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); + + return cmd->write_data_len != se_cmd->data_length; +} + +/* + * Send TX_RDY (transfer ready). + */ +int ft_write_pending(struct se_cmd *se_cmd) +{ + struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); + struct fc_frame *fp; + struct fcp_txrdy *txrdy; + struct fc_lport *lport; + struct fc_exch *ep; + struct fc_frame_header *fh; + u32 f_ctl; + + ft_dump_cmd(cmd, __func__); + + if (cmd->aborted) + return 0; + ep = fc_seq_exch(cmd->seq); + lport = ep->lp; + fp = fc_frame_alloc(lport, sizeof(*txrdy)); + if (!fp) + return -ENOMEM; /* Signal QUEUE_FULL */ + + txrdy = fc_frame_payload_get(fp, sizeof(*txrdy)); + memset(txrdy, 0, sizeof(*txrdy)); + txrdy->ft_burst_len = htonl(se_cmd->data_length); + + cmd->seq = lport->tt.seq_start_next(cmd->seq); + fc_fill_fc_hdr(fp, FC_RCTL_DD_DATA_DESC, ep->did, ep->sid, FC_TYPE_FCP, + FC_FC_EX_CTX | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0); + + fh = fc_frame_header_get(fp); + f_ctl = ntoh24(fh->fh_f_ctl); + + /* Only if it is 'Exchange Responder' */ + if (f_ctl & FC_FC_EX_CTX) { + /* Target is 'exchange responder' and sending XFER_READY + * to 'exchange initiator (initiator)' + */ + if ((ep->xid <= lport->lro_xid) && + (fh->fh_r_ctl == FC_RCTL_DD_DATA_DESC)) { + if ((se_cmd->se_cmd_flags & SCF_SCSI_DATA_CDB) && + lport->tt.ddp_target(lport, ep->xid, + se_cmd->t_data_sg, + se_cmd->t_data_nents)) + cmd->was_ddp_setup = 1; + } + } + lport->tt.seq_send(lport, cmd->seq, fp); + return 0; +} + +u32 ft_get_task_tag(struct se_cmd *se_cmd) +{ + struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); + + if (cmd->aborted) + return ~0; + return fc_seq_exch(cmd->seq)->rxid; +} + +int ft_get_cmd_state(struct se_cmd *se_cmd) +{ + return 0; +} + +/* + * FC sequence response handler for follow-on sequences (data) and aborts. + */ +static void ft_recv_seq(struct fc_seq *sp, struct fc_frame *fp, void *arg) +{ + struct ft_cmd *cmd = arg; + struct fc_frame_header *fh; + + if (unlikely(IS_ERR(fp))) { + /* XXX need to find cmd if queued */ + cmd->seq = NULL; + cmd->aborted = true; + return; + } + + fh = fc_frame_header_get(fp); + + switch (fh->fh_r_ctl) { + case FC_RCTL_DD_SOL_DATA: /* write data */ + ft_recv_write_data(cmd, fp); + break; + case FC_RCTL_DD_UNSOL_CTL: /* command */ + case FC_RCTL_DD_SOL_CTL: /* transfer ready */ + case FC_RCTL_DD_DATA_DESC: /* transfer ready */ + default: + pr_debug("%s: unhandled frame r_ctl %x\n", + __func__, fh->fh_r_ctl); + ft_invl_hw_context(cmd); + fc_frame_free(fp); + transport_generic_free_cmd(&cmd->se_cmd, 0); + break; + } +} + +/* + * Send a FCP response including SCSI status and optional FCP rsp_code. + * status is SAM_STAT_GOOD (zero) iff code is valid. + * This is used in error cases, such as allocation failures. + */ +static void ft_send_resp_status(struct fc_lport *lport, + const struct fc_frame *rx_fp, + u32 status, enum fcp_resp_rsp_codes code) +{ + struct fc_frame *fp; + struct fc_seq *sp; + const struct fc_frame_header *fh; + size_t len; + struct fcp_resp_with_ext *fcp; + struct fcp_resp_rsp_info *info; + + fh = fc_frame_header_get(rx_fp); + pr_debug("FCP error response: did %x oxid %x status %x code %x\n", + ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id), status, code); + len = sizeof(*fcp); + if (status == SAM_STAT_GOOD) + len += sizeof(*info); + fp = fc_frame_alloc(lport, len); + if (!fp) + return; + fcp = fc_frame_payload_get(fp, len); + memset(fcp, 0, len); + fcp->resp.fr_status = status; + if (status == SAM_STAT_GOOD) { + fcp->ext.fr_rsp_len = htonl(sizeof(*info)); + fcp->resp.fr_flags |= FCP_RSP_LEN_VAL; + info = (struct fcp_resp_rsp_info *)(fcp + 1); + info->rsp_code = code; + } + + fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_DD_CMD_STATUS, 0); + sp = fr_seq(fp); + if (sp) { + lport->tt.seq_send(lport, sp, fp); + lport->tt.exch_done(sp); + } else { + lport->tt.frame_send(lport, fp); + } +} + +/* + * Send error or task management response. + */ +static void ft_send_resp_code(struct ft_cmd *cmd, + enum fcp_resp_rsp_codes code) +{ + ft_send_resp_status(cmd->sess->tport->lport, + cmd->req_frame, SAM_STAT_GOOD, code); +} + + +/* + * Send error or task management response. + * Always frees the cmd and associated state. + */ +static void ft_send_resp_code_and_free(struct ft_cmd *cmd, + enum fcp_resp_rsp_codes code) +{ + ft_send_resp_code(cmd, code); + ft_free_cmd(cmd); +} + +/* + * Handle Task Management Request. + */ +static void ft_send_tm(struct ft_cmd *cmd) +{ + struct fcp_cmnd *fcp; + int rc; + u8 tm_func; + + fcp = fc_frame_payload_get(cmd->req_frame, sizeof(*fcp)); + + switch (fcp->fc_tm_flags) { + case FCP_TMF_LUN_RESET: + tm_func = TMR_LUN_RESET; + break; + case FCP_TMF_TGT_RESET: + tm_func = TMR_TARGET_WARM_RESET; + break; + case FCP_TMF_CLR_TASK_SET: + tm_func = TMR_CLEAR_TASK_SET; + break; + case FCP_TMF_ABT_TASK_SET: + tm_func = TMR_ABORT_TASK_SET; + break; + case FCP_TMF_CLR_ACA: + tm_func = TMR_CLEAR_ACA; + break; + default: + /* + * FCP4r01 indicates having a combination of + * tm_flags set is invalid. + */ + pr_debug("invalid FCP tm_flags %x\n", fcp->fc_tm_flags); + ft_send_resp_code_and_free(cmd, FCP_CMND_FIELDS_INVALID); + return; + } + + /* FIXME: Add referenced task tag for ABORT_TASK */ + rc = target_submit_tmr(&cmd->se_cmd, cmd->sess->se_sess, + &cmd->ft_sense_buffer[0], scsilun_to_int(&fcp->fc_lun), + cmd, tm_func, GFP_KERNEL, 0, 0); + if (rc < 0) + ft_send_resp_code_and_free(cmd, FCP_TMF_FAILED); +} + +/* + * Send status from completed task management request. + */ +void ft_queue_tm_resp(struct se_cmd *se_cmd) +{ + struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); + struct se_tmr_req *tmr = se_cmd->se_tmr_req; + enum fcp_resp_rsp_codes code; + + if (cmd->aborted) + return; + switch (tmr->response) { + case TMR_FUNCTION_COMPLETE: + code = FCP_TMF_CMPL; + break; + case TMR_LUN_DOES_NOT_EXIST: + code = FCP_TMF_INVALID_LUN; + break; + case TMR_FUNCTION_REJECTED: + code = FCP_TMF_REJECTED; + break; + case TMR_TASK_DOES_NOT_EXIST: + case TMR_TASK_MGMT_FUNCTION_NOT_SUPPORTED: + default: + code = FCP_TMF_FAILED; + break; + } + pr_debug("tmr fn %d resp %d fcp code %d\n", + tmr->function, tmr->response, code); + ft_send_resp_code(cmd, code); +} + +void ft_aborted_task(struct se_cmd *se_cmd) +{ + return; +} + +static void ft_send_work(struct work_struct *work); + +/* + * Handle incoming FCP command. + */ +static void ft_recv_cmd(struct ft_sess *sess, struct fc_frame *fp) +{ + struct ft_cmd *cmd; + struct fc_lport *lport = sess->tport->lport; + struct se_session *se_sess = sess->se_sess; + int tag; + + tag = percpu_ida_alloc(&se_sess->sess_tag_pool, TASK_RUNNING); + if (tag < 0) + goto busy; + + cmd = &((struct ft_cmd *)se_sess->sess_cmd_map)[tag]; + memset(cmd, 0, sizeof(struct ft_cmd)); + + cmd->se_cmd.map_tag = tag; + cmd->sess = sess; + cmd->seq = lport->tt.seq_assign(lport, fp); + if (!cmd->seq) { + percpu_ida_free(&se_sess->sess_tag_pool, tag); + goto busy; + } + cmd->req_frame = fp; /* hold frame during cmd */ + + INIT_WORK(&cmd->work, ft_send_work); + queue_work(sess->tport->tpg->workqueue, &cmd->work); + return; + +busy: + pr_debug("cmd or seq allocation failure - sending BUSY\n"); + ft_send_resp_status(lport, fp, SAM_STAT_BUSY, 0); + fc_frame_free(fp); + ft_sess_put(sess); /* undo get from lookup */ +} + + +/* + * Handle incoming FCP frame. + * Caller has verified that the frame is type FCP. + */ +void ft_recv_req(struct ft_sess *sess, struct fc_frame *fp) +{ + struct fc_frame_header *fh = fc_frame_header_get(fp); + + switch (fh->fh_r_ctl) { + case FC_RCTL_DD_UNSOL_CMD: /* command */ + ft_recv_cmd(sess, fp); + break; + case FC_RCTL_DD_SOL_DATA: /* write data */ + case FC_RCTL_DD_UNSOL_CTL: + case FC_RCTL_DD_SOL_CTL: + case FC_RCTL_DD_DATA_DESC: /* transfer ready */ + case FC_RCTL_ELS4_REQ: /* SRR, perhaps */ + default: + pr_debug("%s: unhandled frame r_ctl %x\n", + __func__, fh->fh_r_ctl); + fc_frame_free(fp); + ft_sess_put(sess); /* undo get from lookup */ + break; + } +} + +/* + * Send new command to target. + */ +static void ft_send_work(struct work_struct *work) +{ + struct ft_cmd *cmd = container_of(work, struct ft_cmd, work); + struct fc_frame_header *fh = fc_frame_header_get(cmd->req_frame); + struct fcp_cmnd *fcp; + int data_dir = 0; + int task_attr; + + fcp = fc_frame_payload_get(cmd->req_frame, sizeof(*fcp)); + if (!fcp) + goto err; + + if (fcp->fc_flags & FCP_CFL_LEN_MASK) + goto err; /* not handling longer CDBs yet */ + + /* + * Check for FCP task management flags + */ + if (fcp->fc_tm_flags) { + ft_send_tm(cmd); + return; + } + + switch (fcp->fc_flags & (FCP_CFL_RDDATA | FCP_CFL_WRDATA)) { + case 0: + data_dir = DMA_NONE; + break; + case FCP_CFL_RDDATA: + data_dir = DMA_FROM_DEVICE; + break; + case FCP_CFL_WRDATA: + data_dir = DMA_TO_DEVICE; + break; + case FCP_CFL_WRDATA | FCP_CFL_RDDATA: + goto err; /* TBD not supported by tcm_fc yet */ + } + /* + * Locate the SAM Task Attr from fc_pri_ta + */ + switch (fcp->fc_pri_ta & FCP_PTA_MASK) { + case FCP_PTA_HEADQ: + task_attr = MSG_HEAD_TAG; + break; + case FCP_PTA_ORDERED: + task_attr = MSG_ORDERED_TAG; + break; + case FCP_PTA_ACA: + task_attr = MSG_ACA_TAG; + break; + case FCP_PTA_SIMPLE: /* Fallthrough */ + default: + task_attr = MSG_SIMPLE_TAG; + } + + fc_seq_exch(cmd->seq)->lp->tt.seq_set_resp(cmd->seq, ft_recv_seq, cmd); + /* + * Use a single se_cmd->cmd_kref as we expect to release se_cmd + * directly from ft_check_stop_free callback in response path. + */ + if (target_submit_cmd(&cmd->se_cmd, cmd->sess->se_sess, fcp->fc_cdb, + &cmd->ft_sense_buffer[0], scsilun_to_int(&fcp->fc_lun), + ntohl(fcp->fc_dl), task_attr, data_dir, 0)) + goto err; + + pr_debug("r_ctl %x alloc target_submit_cmd\n", fh->fh_r_ctl); + return; + +err: + ft_send_resp_code_and_free(cmd, FCP_CMND_FIELDS_INVALID); +} diff --git a/drivers/target/tcm_fc/tfc_conf.c b/drivers/target/tcm_fc/tfc_conf.c new file mode 100644 index 00000000000..efdcb9663a1 --- /dev/null +++ b/drivers/target/tcm_fc/tfc_conf.c @@ -0,0 +1,642 @@ +/******************************************************************************* + * Filename: tcm_fc.c + * + * This file contains the configfs implementation for TCM_fc fabric node. + * Based on tcm_loop_configfs.c + * + * Copyright (c) 2010 Cisco Systems, Inc. + * Copyright (c) 2009,2010 Rising Tide, Inc. + * Copyright (c) 2009,2010 Linux-iSCSI.org + * + * Copyright (c) 2009,2010 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/module.h> +#include <linux/moduleparam.h> +#include <generated/utsrelease.h> +#include <linux/utsname.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/kernel.h> +#include <linux/ctype.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/libfc.h> + +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_fabric_configfs.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> + +#include "tcm_fc.h" + +struct target_fabric_configfs *ft_configfs; + +static LIST_HEAD(ft_wwn_list); +DEFINE_MUTEX(ft_lport_lock); + +unsigned int ft_debug_logging; +module_param_named(debug_logging, ft_debug_logging, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels"); + +/* + * Parse WWN. + * If strict, we require lower-case hex and colon separators to be sure + * the name is the same as what would be generated by ft_format_wwn() + * so the name and wwn are mapped one-to-one. + */ +static ssize_t ft_parse_wwn(const char *name, u64 *wwn, int strict) +{ + const char *cp; + char c; + u32 byte = 0; + u32 pos = 0; + u32 err; + int val; + + *wwn = 0; + for (cp = name; cp < &name[FT_NAMELEN - 1]; cp++) { + c = *cp; + if (c == '\n' && cp[1] == '\0') + continue; + if (strict && pos++ == 2 && byte++ < 7) { + pos = 0; + if (c == ':') + continue; + err = 1; + goto fail; + } + if (c == '\0') { + err = 2; + if (strict && byte != 8) + goto fail; + return cp - name; + } + err = 3; + val = hex_to_bin(c); + if (val < 0 || (strict && isupper(c))) + goto fail; + *wwn = (*wwn << 4) | val; + } + err = 4; +fail: + pr_debug("err %u len %zu pos %u byte %u\n", + err, cp - name, pos, byte); + return -1; +} + +ssize_t ft_format_wwn(char *buf, size_t len, u64 wwn) +{ + u8 b[8]; + + put_unaligned_be64(wwn, b); + return snprintf(buf, len, + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); +} + +static ssize_t ft_wwn_show(void *arg, char *buf) +{ + u64 *wwn = arg; + ssize_t len; + + len = ft_format_wwn(buf, PAGE_SIZE - 2, *wwn); + buf[len++] = '\n'; + return len; +} + +static ssize_t ft_wwn_store(void *arg, const char *buf, size_t len) +{ + ssize_t ret; + u64 wwn; + + ret = ft_parse_wwn(buf, &wwn, 0); + if (ret > 0) + *(u64 *)arg = wwn; + return ret; +} + +/* + * ACL auth ops. + */ + +static ssize_t ft_nacl_show_port_name( + struct se_node_acl *se_nacl, + char *page) +{ + struct ft_node_acl *acl = container_of(se_nacl, + struct ft_node_acl, se_node_acl); + + return ft_wwn_show(&acl->node_auth.port_name, page); +} + +static ssize_t ft_nacl_store_port_name( + struct se_node_acl *se_nacl, + const char *page, + size_t count) +{ + struct ft_node_acl *acl = container_of(se_nacl, + struct ft_node_acl, se_node_acl); + + return ft_wwn_store(&acl->node_auth.port_name, page, count); +} + +TF_NACL_BASE_ATTR(ft, port_name, S_IRUGO | S_IWUSR); + +static ssize_t ft_nacl_show_node_name( + struct se_node_acl *se_nacl, + char *page) +{ + struct ft_node_acl *acl = container_of(se_nacl, + struct ft_node_acl, se_node_acl); + + return ft_wwn_show(&acl->node_auth.node_name, page); +} + +static ssize_t ft_nacl_store_node_name( + struct se_node_acl *se_nacl, + const char *page, + size_t count) +{ + struct ft_node_acl *acl = container_of(se_nacl, + struct ft_node_acl, se_node_acl); + + return ft_wwn_store(&acl->node_auth.node_name, page, count); +} + +TF_NACL_BASE_ATTR(ft, node_name, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *ft_nacl_base_attrs[] = { + &ft_nacl_port_name.attr, + &ft_nacl_node_name.attr, + NULL, +}; + +/* + * ACL ops. + */ + +/* + * Add ACL for an initiator. The ACL is named arbitrarily. + * The port_name and/or node_name are attributes. + */ +static struct se_node_acl *ft_add_acl( + struct se_portal_group *se_tpg, + struct config_group *group, + const char *name) +{ + struct ft_node_acl *acl; + struct ft_tpg *tpg; + u64 wwpn; + u32 q_depth; + + pr_debug("add acl %s\n", name); + tpg = container_of(se_tpg, struct ft_tpg, se_tpg); + + if (ft_parse_wwn(name, &wwpn, 1) < 0) + return ERR_PTR(-EINVAL); + + acl = kzalloc(sizeof(struct ft_node_acl), GFP_KERNEL); + if (!acl) + return ERR_PTR(-ENOMEM); + acl->node_auth.port_name = wwpn; + + q_depth = 32; /* XXX bogus default - get from tpg? */ + return core_tpg_add_initiator_node_acl(&tpg->se_tpg, + &acl->se_node_acl, name, q_depth); +} + +static void ft_del_acl(struct se_node_acl *se_acl) +{ + struct se_portal_group *se_tpg = se_acl->se_tpg; + struct ft_tpg *tpg; + struct ft_node_acl *acl = container_of(se_acl, + struct ft_node_acl, se_node_acl); + + pr_debug("del acl %s\n", + config_item_name(&se_acl->acl_group.cg_item)); + + tpg = container_of(se_tpg, struct ft_tpg, se_tpg); + pr_debug("del acl %p se_acl %p tpg %p se_tpg %p\n", + acl, se_acl, tpg, &tpg->se_tpg); + + core_tpg_del_initiator_node_acl(&tpg->se_tpg, se_acl, 1); + kfree(acl); +} + +struct ft_node_acl *ft_acl_get(struct ft_tpg *tpg, struct fc_rport_priv *rdata) +{ + struct ft_node_acl *found = NULL; + struct ft_node_acl *acl; + struct se_portal_group *se_tpg = &tpg->se_tpg; + struct se_node_acl *se_acl; + + spin_lock_irq(&se_tpg->acl_node_lock); + list_for_each_entry(se_acl, &se_tpg->acl_node_list, acl_list) { + acl = container_of(se_acl, struct ft_node_acl, se_node_acl); + pr_debug("acl %p port_name %llx\n", + acl, (unsigned long long)acl->node_auth.port_name); + if (acl->node_auth.port_name == rdata->ids.port_name || + acl->node_auth.node_name == rdata->ids.node_name) { + pr_debug("acl %p port_name %llx matched\n", acl, + (unsigned long long)rdata->ids.port_name); + found = acl; + /* XXX need to hold onto ACL */ + break; + } + } + spin_unlock_irq(&se_tpg->acl_node_lock); + return found; +} + +static struct se_node_acl *ft_tpg_alloc_fabric_acl(struct se_portal_group *se_tpg) +{ + struct ft_node_acl *acl; + + acl = kzalloc(sizeof(*acl), GFP_KERNEL); + if (!acl) { + pr_err("Unable to allocate struct ft_node_acl\n"); + return NULL; + } + pr_debug("acl %p\n", acl); + return &acl->se_node_acl; +} + +static void ft_tpg_release_fabric_acl(struct se_portal_group *se_tpg, + struct se_node_acl *se_acl) +{ + struct ft_node_acl *acl = container_of(se_acl, + struct ft_node_acl, se_node_acl); + + pr_debug("acl %p\n", acl); + kfree(acl); +} + +/* + * local_port port_group (tpg) ops. + */ +static struct se_portal_group *ft_add_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct ft_lport_wwn *ft_wwn; + struct ft_tpg *tpg; + struct workqueue_struct *wq; + unsigned long index; + int ret; + + pr_debug("tcm_fc: add tpg %s\n", name); + + /* + * Name must be "tpgt_" followed by the index. + */ + if (strstr(name, "tpgt_") != name) + return NULL; + + ret = kstrtoul(name + 5, 10, &index); + if (ret) + return NULL; + if (index > UINT_MAX) + return NULL; + + if ((index != 1)) { + pr_err("Error, a single TPG=1 is used for HW port mappings\n"); + return ERR_PTR(-ENOSYS); + } + + ft_wwn = container_of(wwn, struct ft_lport_wwn, se_wwn); + tpg = kzalloc(sizeof(*tpg), GFP_KERNEL); + if (!tpg) + return NULL; + tpg->index = index; + tpg->lport_wwn = ft_wwn; + INIT_LIST_HEAD(&tpg->lun_list); + + wq = alloc_workqueue("tcm_fc", 0, 1); + if (!wq) { + kfree(tpg); + return NULL; + } + + ret = core_tpg_register(&ft_configfs->tf_ops, wwn, &tpg->se_tpg, + tpg, TRANSPORT_TPG_TYPE_NORMAL); + if (ret < 0) { + destroy_workqueue(wq); + kfree(tpg); + return NULL; + } + tpg->workqueue = wq; + + mutex_lock(&ft_lport_lock); + ft_wwn->tpg = tpg; + mutex_unlock(&ft_lport_lock); + + return &tpg->se_tpg; +} + +static void ft_del_tpg(struct se_portal_group *se_tpg) +{ + struct ft_tpg *tpg = container_of(se_tpg, struct ft_tpg, se_tpg); + struct ft_lport_wwn *ft_wwn = tpg->lport_wwn; + + pr_debug("del tpg %s\n", + config_item_name(&tpg->se_tpg.tpg_group.cg_item)); + + destroy_workqueue(tpg->workqueue); + + /* Wait for sessions to be freed thru RCU, for BUG_ON below */ + synchronize_rcu(); + + mutex_lock(&ft_lport_lock); + ft_wwn->tpg = NULL; + if (tpg->tport) { + tpg->tport->tpg = NULL; + tpg->tport = NULL; + } + mutex_unlock(&ft_lport_lock); + + core_tpg_deregister(se_tpg); + kfree(tpg); +} + +/* + * Verify that an lport is configured to use the tcm_fc module, and return + * the target port group that should be used. + * + * The caller holds ft_lport_lock. + */ +struct ft_tpg *ft_lport_find_tpg(struct fc_lport *lport) +{ + struct ft_lport_wwn *ft_wwn; + + list_for_each_entry(ft_wwn, &ft_wwn_list, ft_wwn_node) { + if (ft_wwn->wwpn == lport->wwpn) + return ft_wwn->tpg; + } + return NULL; +} + +/* + * target config instance ops. + */ + +/* + * Add lport to allowed config. + * The name is the WWPN in lower-case ASCII, colon-separated bytes. + */ +static struct se_wwn *ft_add_wwn( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct ft_lport_wwn *ft_wwn; + struct ft_lport_wwn *old_ft_wwn; + u64 wwpn; + + pr_debug("add wwn %s\n", name); + if (ft_parse_wwn(name, &wwpn, 1) < 0) + return NULL; + ft_wwn = kzalloc(sizeof(*ft_wwn), GFP_KERNEL); + if (!ft_wwn) + return NULL; + ft_wwn->wwpn = wwpn; + + mutex_lock(&ft_lport_lock); + list_for_each_entry(old_ft_wwn, &ft_wwn_list, ft_wwn_node) { + if (old_ft_wwn->wwpn == wwpn) { + mutex_unlock(&ft_lport_lock); + kfree(ft_wwn); + return NULL; + } + } + list_add_tail(&ft_wwn->ft_wwn_node, &ft_wwn_list); + ft_format_wwn(ft_wwn->name, sizeof(ft_wwn->name), wwpn); + mutex_unlock(&ft_lport_lock); + + return &ft_wwn->se_wwn; +} + +static void ft_del_wwn(struct se_wwn *wwn) +{ + struct ft_lport_wwn *ft_wwn = container_of(wwn, + struct ft_lport_wwn, se_wwn); + + pr_debug("del wwn %s\n", ft_wwn->name); + mutex_lock(&ft_lport_lock); + list_del(&ft_wwn->ft_wwn_node); + mutex_unlock(&ft_lport_lock); + + kfree(ft_wwn); +} + +static ssize_t ft_wwn_show_attr_version( + struct target_fabric_configfs *tf, + char *page) +{ + return sprintf(page, "TCM FC " FT_VERSION " on %s/%s on " + ""UTS_RELEASE"\n", utsname()->sysname, utsname()->machine); +} + +TF_WWN_ATTR_RO(ft, version); + +static struct configfs_attribute *ft_wwn_attrs[] = { + &ft_wwn_version.attr, + NULL, +}; + +static char *ft_get_fabric_name(void) +{ + return "fc"; +} + +static char *ft_get_fabric_wwn(struct se_portal_group *se_tpg) +{ + struct ft_tpg *tpg = se_tpg->se_tpg_fabric_ptr; + + return tpg->lport_wwn->name; +} + +static u16 ft_get_tag(struct se_portal_group *se_tpg) +{ + struct ft_tpg *tpg = se_tpg->se_tpg_fabric_ptr; + + /* + * This tag is used when forming SCSI Name identifier in EVPD=1 0x83 + * to represent the SCSI Target Port. + */ + return tpg->index; +} + +static u32 ft_get_default_depth(struct se_portal_group *se_tpg) +{ + return 1; +} + +static int ft_check_false(struct se_portal_group *se_tpg) +{ + return 0; +} + +static void ft_set_default_node_attr(struct se_node_acl *se_nacl) +{ +} + +static u32 ft_tpg_get_inst_index(struct se_portal_group *se_tpg) +{ + struct ft_tpg *tpg = se_tpg->se_tpg_fabric_ptr; + + return tpg->index; +} + +static struct target_core_fabric_ops ft_fabric_ops = { + .get_fabric_name = ft_get_fabric_name, + .get_fabric_proto_ident = fc_get_fabric_proto_ident, + .tpg_get_wwn = ft_get_fabric_wwn, + .tpg_get_tag = ft_get_tag, + .tpg_get_default_depth = ft_get_default_depth, + .tpg_get_pr_transport_id = fc_get_pr_transport_id, + .tpg_get_pr_transport_id_len = fc_get_pr_transport_id_len, + .tpg_parse_pr_out_transport_id = fc_parse_pr_out_transport_id, + .tpg_check_demo_mode = ft_check_false, + .tpg_check_demo_mode_cache = ft_check_false, + .tpg_check_demo_mode_write_protect = ft_check_false, + .tpg_check_prod_mode_write_protect = ft_check_false, + .tpg_alloc_fabric_acl = ft_tpg_alloc_fabric_acl, + .tpg_release_fabric_acl = ft_tpg_release_fabric_acl, + .tpg_get_inst_index = ft_tpg_get_inst_index, + .check_stop_free = ft_check_stop_free, + .release_cmd = ft_release_cmd, + .shutdown_session = ft_sess_shutdown, + .close_session = ft_sess_close, + .sess_get_index = ft_sess_get_index, + .sess_get_initiator_sid = NULL, + .write_pending = ft_write_pending, + .write_pending_status = ft_write_pending_status, + .set_default_node_attributes = ft_set_default_node_attr, + .get_task_tag = ft_get_task_tag, + .get_cmd_state = ft_get_cmd_state, + .queue_data_in = ft_queue_data_in, + .queue_status = ft_queue_status, + .queue_tm_rsp = ft_queue_tm_resp, + .aborted_task = ft_aborted_task, + /* + * Setup function pointers for generic logic in + * target_core_fabric_configfs.c + */ + .fabric_make_wwn = &ft_add_wwn, + .fabric_drop_wwn = &ft_del_wwn, + .fabric_make_tpg = &ft_add_tpg, + .fabric_drop_tpg = &ft_del_tpg, + .fabric_post_link = NULL, + .fabric_pre_unlink = NULL, + .fabric_make_np = NULL, + .fabric_drop_np = NULL, + .fabric_make_nodeacl = &ft_add_acl, + .fabric_drop_nodeacl = &ft_del_acl, +}; + +static int ft_register_configfs(void) +{ + struct target_fabric_configfs *fabric; + int ret; + + /* + * Register the top level struct config_item_type with TCM core + */ + fabric = target_fabric_configfs_init(THIS_MODULE, "fc"); + if (IS_ERR(fabric)) { + pr_err("%s: target_fabric_configfs_init() failed!\n", + __func__); + return PTR_ERR(fabric); + } + fabric->tf_ops = ft_fabric_ops; + + /* + * Setup default attribute lists for various fabric->tf_cit_tmpl + */ + fabric->tf_cit_tmpl.tfc_wwn_cit.ct_attrs = ft_wwn_attrs; + fabric->tf_cit_tmpl.tfc_tpg_base_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_attrib_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_param_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_np_base_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_nacl_base_cit.ct_attrs = + ft_nacl_base_attrs; + fabric->tf_cit_tmpl.tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_nacl_auth_cit.ct_attrs = NULL; + fabric->tf_cit_tmpl.tfc_tpg_nacl_param_cit.ct_attrs = NULL; + /* + * register the fabric for use within TCM + */ + ret = target_fabric_configfs_register(fabric); + if (ret < 0) { + pr_debug("target_fabric_configfs_register() for" + " FC Target failed!\n"); + target_fabric_configfs_free(fabric); + return -1; + } + + /* + * Setup our local pointer to *fabric. + */ + ft_configfs = fabric; + return 0; +} + +static void ft_deregister_configfs(void) +{ + if (!ft_configfs) + return; + target_fabric_configfs_deregister(ft_configfs); + ft_configfs = NULL; +} + +static struct notifier_block ft_notifier = { + .notifier_call = ft_lport_notify +}; + +static int __init ft_init(void) +{ + if (ft_register_configfs()) + return -1; + if (fc_fc4_register_provider(FC_TYPE_FCP, &ft_prov)) { + ft_deregister_configfs(); + return -1; + } + blocking_notifier_chain_register(&fc_lport_notifier_head, &ft_notifier); + fc_lport_iterate(ft_lport_add, NULL); + return 0; +} + +static void __exit ft_exit(void) +{ + blocking_notifier_chain_unregister(&fc_lport_notifier_head, + &ft_notifier); + fc_fc4_deregister_provider(FC_TYPE_FCP, &ft_prov); + fc_lport_iterate(ft_lport_del, NULL); + ft_deregister_configfs(); + synchronize_rcu(); +} + +MODULE_DESCRIPTION("FC TCM fabric driver " FT_VERSION); +MODULE_LICENSE("GPL"); +module_init(ft_init); +module_exit(ft_exit); diff --git a/drivers/target/tcm_fc/tfc_io.c b/drivers/target/tcm_fc/tfc_io.c new file mode 100644 index 00000000000..97b486c3dda --- /dev/null +++ b/drivers/target/tcm_fc/tfc_io.c @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2010 Cisco Systems, Inc. + * + * Portions based on tcm_loop_fabric_scsi.c and libfc/fc_fcp.c + * + * Copyright (c) 2007 Intel Corporation. All rights reserved. + * Copyright (c) 2008 Red Hat, Inc. All rights reserved. + * Copyright (c) 2008 Mike Christie + * Copyright (c) 2009 Rising Tide, Inc. + * Copyright (c) 2009 Linux-iSCSI.org + * Copyright (c) 2009 Nicholas A. Bellinger <nab@linux-iscsi.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* XXX TBD some includes may be extraneous */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/utsname.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <linux/hash.h> +#include <linux/ratelimit.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/libfc.h> +#include <scsi/fc_encode.h> + +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> + +#include "tcm_fc.h" + +/* + * Deliver read data back to initiator. + * XXX TBD handle resource problems later. + */ +int ft_queue_data_in(struct se_cmd *se_cmd) +{ + struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); + struct fc_frame *fp = NULL; + struct fc_exch *ep; + struct fc_lport *lport; + struct scatterlist *sg = NULL; + size_t remaining; + u32 f_ctl = FC_FC_EX_CTX | FC_FC_REL_OFF; + u32 mem_off = 0; + u32 fh_off = 0; + u32 frame_off = 0; + size_t frame_len = 0; + size_t mem_len = 0; + size_t tlen; + size_t off_in_page; + struct page *page = NULL; + int use_sg; + int error; + void *page_addr; + void *from; + void *to = NULL; + + if (cmd->aborted) + return 0; + + if (se_cmd->scsi_status == SAM_STAT_TASK_SET_FULL) + goto queue_status; + + ep = fc_seq_exch(cmd->seq); + lport = ep->lp; + cmd->seq = lport->tt.seq_start_next(cmd->seq); + + remaining = se_cmd->data_length; + + /* + * Setup to use first mem list entry, unless no data. + */ + BUG_ON(remaining && !se_cmd->t_data_sg); + if (remaining) { + sg = se_cmd->t_data_sg; + mem_len = sg->length; + mem_off = sg->offset; + page = sg_page(sg); + } + + /* no scatter/gather in skb for odd word length due to fc_seq_send() */ + use_sg = !(remaining % 4); + + while (remaining) { + struct fc_seq *seq = cmd->seq; + + if (!seq) { + pr_debug("%s: Command aborted, xid 0x%x\n", + __func__, ep->xid); + break; + } + if (!mem_len) { + sg = sg_next(sg); + mem_len = min((size_t)sg->length, remaining); + mem_off = sg->offset; + page = sg_page(sg); + } + if (!frame_len) { + /* + * If lport's has capability of Large Send Offload LSO) + * , then allow 'frame_len' to be as big as 'lso_max' + * if indicated transfer length is >= lport->lso_max + */ + frame_len = (lport->seq_offload) ? lport->lso_max : + cmd->sess->max_frame; + frame_len = min(frame_len, remaining); + fp = fc_frame_alloc(lport, use_sg ? 0 : frame_len); + if (!fp) + return -ENOMEM; + to = fc_frame_payload_get(fp, 0); + fh_off = frame_off; + frame_off += frame_len; + /* + * Setup the frame's max payload which is used by base + * driver to indicate HW about max frame size, so that + * HW can do fragmentation appropriately based on + * "gso_max_size" of underline netdev. + */ + fr_max_payload(fp) = cmd->sess->max_frame; + } + tlen = min(mem_len, frame_len); + + if (use_sg) { + off_in_page = mem_off; + BUG_ON(!page); + get_page(page); + skb_fill_page_desc(fp_skb(fp), + skb_shinfo(fp_skb(fp))->nr_frags, + page, off_in_page, tlen); + fr_len(fp) += tlen; + fp_skb(fp)->data_len += tlen; + fp_skb(fp)->truesize += + PAGE_SIZE << compound_order(page); + } else { + BUG_ON(!page); + from = kmap_atomic(page + (mem_off >> PAGE_SHIFT)); + page_addr = from; + from += mem_off & ~PAGE_MASK; + tlen = min(tlen, (size_t)(PAGE_SIZE - + (mem_off & ~PAGE_MASK))); + memcpy(to, from, tlen); + kunmap_atomic(page_addr); + to += tlen; + } + + mem_off += tlen; + mem_len -= tlen; + frame_len -= tlen; + remaining -= tlen; + + if (frame_len && + (skb_shinfo(fp_skb(fp))->nr_frags < FC_FRAME_SG_LEN)) + continue; + if (!remaining) + f_ctl |= FC_FC_END_SEQ; + fc_fill_fc_hdr(fp, FC_RCTL_DD_SOL_DATA, ep->did, ep->sid, + FC_TYPE_FCP, f_ctl, fh_off); + error = lport->tt.seq_send(lport, seq, fp); + if (error) { + pr_info_ratelimited("%s: Failed to send frame %p, " + "xid <0x%x>, remaining %zu, " + "lso_max <0x%x>\n", + __func__, fp, ep->xid, + remaining, lport->lso_max); + /* + * Go ahead and set TASK_SET_FULL status ignoring the + * rest of the DataIN, and immediately attempt to + * send the response via ft_queue_status() in order + * to notify the initiator that it should reduce it's + * per LUN queue_depth. + */ + se_cmd->scsi_status = SAM_STAT_TASK_SET_FULL; + break; + } + } +queue_status: + return ft_queue_status(se_cmd); +} + +static void ft_execute_work(struct work_struct *work) +{ + struct ft_cmd *cmd = container_of(work, struct ft_cmd, work); + + target_execute_cmd(&cmd->se_cmd); +} + +/* + * Receive write data frame. + */ +void ft_recv_write_data(struct ft_cmd *cmd, struct fc_frame *fp) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct fc_seq *seq = cmd->seq; + struct fc_exch *ep; + struct fc_lport *lport; + struct fc_frame_header *fh; + struct scatterlist *sg = NULL; + u32 mem_off = 0; + u32 rel_off; + size_t frame_len; + size_t mem_len = 0; + size_t tlen; + struct page *page = NULL; + void *page_addr; + void *from; + void *to; + u32 f_ctl; + void *buf; + + fh = fc_frame_header_get(fp); + if (!(ntoh24(fh->fh_f_ctl) & FC_FC_REL_OFF)) + goto drop; + + f_ctl = ntoh24(fh->fh_f_ctl); + ep = fc_seq_exch(seq); + lport = ep->lp; + if (cmd->was_ddp_setup) { + BUG_ON(!ep); + BUG_ON(!lport); + /* + * Since DDP (Large Rx offload) was setup for this request, + * payload is expected to be copied directly to user buffers. + */ + buf = fc_frame_payload_get(fp, 1); + if (buf) + pr_err("%s: xid 0x%x, f_ctl 0x%x, cmd->sg %p, " + "cmd->sg_cnt 0x%x. DDP was setup" + " hence not expected to receive frame with " + "payload, Frame will be dropped if" + "'Sequence Initiative' bit in f_ctl is" + "not set\n", __func__, ep->xid, f_ctl, + se_cmd->t_data_sg, se_cmd->t_data_nents); + /* + * Invalidate HW DDP context if it was setup for respective + * command. Invalidation of HW DDP context is requited in both + * situation (success and error). + */ + ft_invl_hw_context(cmd); + + /* + * If "Sequence Initiative (TSI)" bit set in f_ctl, means last + * write data frame is received successfully where payload is + * posted directly to user buffer and only the last frame's + * header is posted in receive queue. + * + * If "Sequence Initiative (TSI)" bit is not set, means error + * condition w.r.t. DDP, hence drop the packet and let explict + * ABORTS from other end of exchange timer trigger the recovery. + */ + if (f_ctl & FC_FC_SEQ_INIT) + goto last_frame; + else + goto drop; + } + + rel_off = ntohl(fh->fh_parm_offset); + frame_len = fr_len(fp); + if (frame_len <= sizeof(*fh)) + goto drop; + frame_len -= sizeof(*fh); + from = fc_frame_payload_get(fp, 0); + if (rel_off >= se_cmd->data_length) + goto drop; + if (frame_len + rel_off > se_cmd->data_length) + frame_len = se_cmd->data_length - rel_off; + + /* + * Setup to use first mem list entry, unless no data. + */ + BUG_ON(frame_len && !se_cmd->t_data_sg); + if (frame_len) { + sg = se_cmd->t_data_sg; + mem_len = sg->length; + mem_off = sg->offset; + page = sg_page(sg); + } + + while (frame_len) { + if (!mem_len) { + sg = sg_next(sg); + mem_len = sg->length; + mem_off = sg->offset; + page = sg_page(sg); + } + if (rel_off >= mem_len) { + rel_off -= mem_len; + mem_len = 0; + continue; + } + mem_off += rel_off; + mem_len -= rel_off; + rel_off = 0; + + tlen = min(mem_len, frame_len); + + to = kmap_atomic(page + (mem_off >> PAGE_SHIFT)); + page_addr = to; + to += mem_off & ~PAGE_MASK; + tlen = min(tlen, (size_t)(PAGE_SIZE - + (mem_off & ~PAGE_MASK))); + memcpy(to, from, tlen); + kunmap_atomic(page_addr); + + from += tlen; + frame_len -= tlen; + mem_off += tlen; + mem_len -= tlen; + cmd->write_data_len += tlen; + } +last_frame: + if (cmd->write_data_len == se_cmd->data_length) { + INIT_WORK(&cmd->work, ft_execute_work); + queue_work(cmd->sess->tport->tpg->workqueue, &cmd->work); + } +drop: + fc_frame_free(fp); +} + +/* + * Handle and cleanup any HW specific resources if + * received ABORTS, errors, timeouts. + */ +void ft_invl_hw_context(struct ft_cmd *cmd) +{ + struct fc_seq *seq; + struct fc_exch *ep = NULL; + struct fc_lport *lport = NULL; + + BUG_ON(!cmd); + seq = cmd->seq; + + /* Cleanup the DDP context in HW if DDP was setup */ + if (cmd->was_ddp_setup && seq) { + ep = fc_seq_exch(seq); + if (ep) { + lport = ep->lp; + if (lport && (ep->xid <= lport->lro_xid)) + /* + * "ddp_done" trigger invalidation of HW + * specific DDP context + */ + cmd->write_data_len = lport->tt.ddp_done(lport, + ep->xid); + + /* + * Resetting same variable to indicate HW's + * DDP context has been invalidated to avoid + * re_invalidation of same context (context is + * identified using ep->xid) + */ + cmd->was_ddp_setup = 0; + } + } +} diff --git a/drivers/target/tcm_fc/tfc_sess.c b/drivers/target/tcm_fc/tfc_sess.c new file mode 100644 index 00000000000..21ce50880c7 --- /dev/null +++ b/drivers/target/tcm_fc/tfc_sess.c @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2010 Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* XXX TBD some includes may be extraneous */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/utsname.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <linux/hash.h> +#include <linux/rcupdate.h> +#include <linux/rculist.h> +#include <linux/kref.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/libfc.h> + +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> + +#include "tcm_fc.h" + +static void ft_sess_delete_all(struct ft_tport *); + +/* + * Lookup or allocate target local port. + * Caller holds ft_lport_lock. + */ +static struct ft_tport *ft_tport_get(struct fc_lport *lport) +{ + struct ft_tpg *tpg; + struct ft_tport *tport; + int i; + + tport = rcu_dereference_protected(lport->prov[FC_TYPE_FCP], + lockdep_is_held(&ft_lport_lock)); + if (tport && tport->tpg) + return tport; + + tpg = ft_lport_find_tpg(lport); + if (!tpg) + return NULL; + + if (tport) { + tport->tpg = tpg; + tpg->tport = tport; + return tport; + } + + tport = kzalloc(sizeof(*tport), GFP_KERNEL); + if (!tport) + return NULL; + + tport->lport = lport; + tport->tpg = tpg; + tpg->tport = tport; + for (i = 0; i < FT_SESS_HASH_SIZE; i++) + INIT_HLIST_HEAD(&tport->hash[i]); + + rcu_assign_pointer(lport->prov[FC_TYPE_FCP], tport); + return tport; +} + +/* + * Delete a target local port. + * Caller holds ft_lport_lock. + */ +static void ft_tport_delete(struct ft_tport *tport) +{ + struct fc_lport *lport; + struct ft_tpg *tpg; + + ft_sess_delete_all(tport); + lport = tport->lport; + BUG_ON(tport != lport->prov[FC_TYPE_FCP]); + rcu_assign_pointer(lport->prov[FC_TYPE_FCP], NULL); + + tpg = tport->tpg; + if (tpg) { + tpg->tport = NULL; + tport->tpg = NULL; + } + kfree_rcu(tport, rcu); +} + +/* + * Add local port. + * Called thru fc_lport_iterate(). + */ +void ft_lport_add(struct fc_lport *lport, void *arg) +{ + mutex_lock(&ft_lport_lock); + ft_tport_get(lport); + mutex_unlock(&ft_lport_lock); +} + +/* + * Delete local port. + * Called thru fc_lport_iterate(). + */ +void ft_lport_del(struct fc_lport *lport, void *arg) +{ + struct ft_tport *tport; + + mutex_lock(&ft_lport_lock); + tport = lport->prov[FC_TYPE_FCP]; + if (tport) + ft_tport_delete(tport); + mutex_unlock(&ft_lport_lock); +} + +/* + * Notification of local port change from libfc. + * Create or delete local port and associated tport. + */ +int ft_lport_notify(struct notifier_block *nb, unsigned long event, void *arg) +{ + struct fc_lport *lport = arg; + + switch (event) { + case FC_LPORT_EV_ADD: + ft_lport_add(lport, NULL); + break; + case FC_LPORT_EV_DEL: + ft_lport_del(lport, NULL); + break; + } + return NOTIFY_DONE; +} + +/* + * Hash function for FC_IDs. + */ +static u32 ft_sess_hash(u32 port_id) +{ + return hash_32(port_id, FT_SESS_HASH_BITS); +} + +/* + * Find session in local port. + * Sessions and hash lists are RCU-protected. + * A reference is taken which must be eventually freed. + */ +static struct ft_sess *ft_sess_get(struct fc_lport *lport, u32 port_id) +{ + struct ft_tport *tport; + struct hlist_head *head; + struct ft_sess *sess; + + rcu_read_lock(); + tport = rcu_dereference(lport->prov[FC_TYPE_FCP]); + if (!tport) + goto out; + + head = &tport->hash[ft_sess_hash(port_id)]; + hlist_for_each_entry_rcu(sess, head, hash) { + if (sess->port_id == port_id) { + kref_get(&sess->kref); + rcu_read_unlock(); + pr_debug("port_id %x found %p\n", port_id, sess); + return sess; + } + } +out: + rcu_read_unlock(); + pr_debug("port_id %x not found\n", port_id); + return NULL; +} + +/* + * Allocate session and enter it in the hash for the local port. + * Caller holds ft_lport_lock. + */ +static struct ft_sess *ft_sess_create(struct ft_tport *tport, u32 port_id, + struct ft_node_acl *acl) +{ + struct ft_sess *sess; + struct hlist_head *head; + + head = &tport->hash[ft_sess_hash(port_id)]; + hlist_for_each_entry_rcu(sess, head, hash) + if (sess->port_id == port_id) + return sess; + + sess = kzalloc(sizeof(*sess), GFP_KERNEL); + if (!sess) + return NULL; + + sess->se_sess = transport_init_session_tags(TCM_FC_DEFAULT_TAGS, + sizeof(struct ft_cmd), + TARGET_PROT_NORMAL); + if (IS_ERR(sess->se_sess)) { + kfree(sess); + return NULL; + } + sess->se_sess->se_node_acl = &acl->se_node_acl; + sess->tport = tport; + sess->port_id = port_id; + kref_init(&sess->kref); /* ref for table entry */ + hlist_add_head_rcu(&sess->hash, head); + tport->sess_count++; + + pr_debug("port_id %x sess %p\n", port_id, sess); + + transport_register_session(&tport->tpg->se_tpg, &acl->se_node_acl, + sess->se_sess, sess); + return sess; +} + +/* + * Unhash the session. + * Caller holds ft_lport_lock. + */ +static void ft_sess_unhash(struct ft_sess *sess) +{ + struct ft_tport *tport = sess->tport; + + hlist_del_rcu(&sess->hash); + BUG_ON(!tport->sess_count); + tport->sess_count--; + sess->port_id = -1; + sess->params = 0; +} + +/* + * Delete session from hash. + * Caller holds ft_lport_lock. + */ +static struct ft_sess *ft_sess_delete(struct ft_tport *tport, u32 port_id) +{ + struct hlist_head *head; + struct ft_sess *sess; + + head = &tport->hash[ft_sess_hash(port_id)]; + hlist_for_each_entry_rcu(sess, head, hash) { + if (sess->port_id == port_id) { + ft_sess_unhash(sess); + return sess; + } + } + return NULL; +} + +/* + * Delete all sessions from tport. + * Caller holds ft_lport_lock. + */ +static void ft_sess_delete_all(struct ft_tport *tport) +{ + struct hlist_head *head; + struct ft_sess *sess; + + for (head = tport->hash; + head < &tport->hash[FT_SESS_HASH_SIZE]; head++) { + hlist_for_each_entry_rcu(sess, head, hash) { + ft_sess_unhash(sess); + transport_deregister_session_configfs(sess->se_sess); + ft_sess_put(sess); /* release from table */ + } + } +} + +/* + * TCM ops for sessions. + */ + +/* + * Determine whether session is allowed to be shutdown in the current context. + * Returns non-zero if the session should be shutdown. + */ +int ft_sess_shutdown(struct se_session *se_sess) +{ + struct ft_sess *sess = se_sess->fabric_sess_ptr; + + pr_debug("port_id %x\n", sess->port_id); + return 1; +} + +/* + * Remove session and send PRLO. + * This is called when the ACL is being deleted or queue depth is changing. + */ +void ft_sess_close(struct se_session *se_sess) +{ + struct ft_sess *sess = se_sess->fabric_sess_ptr; + u32 port_id; + + mutex_lock(&ft_lport_lock); + port_id = sess->port_id; + if (port_id == -1) { + mutex_unlock(&ft_lport_lock); + return; + } + pr_debug("port_id %x\n", port_id); + ft_sess_unhash(sess); + mutex_unlock(&ft_lport_lock); + transport_deregister_session_configfs(se_sess); + ft_sess_put(sess); + /* XXX Send LOGO or PRLO */ + synchronize_rcu(); /* let transport deregister happen */ +} + +u32 ft_sess_get_index(struct se_session *se_sess) +{ + struct ft_sess *sess = se_sess->fabric_sess_ptr; + + return sess->port_id; /* XXX TBD probably not what is needed */ +} + +u32 ft_sess_get_port_name(struct se_session *se_sess, + unsigned char *buf, u32 len) +{ + struct ft_sess *sess = se_sess->fabric_sess_ptr; + + return ft_format_wwn(buf, len, sess->port_name); +} + +/* + * libfc ops involving sessions. + */ + +static int ft_prli_locked(struct fc_rport_priv *rdata, u32 spp_len, + const struct fc_els_spp *rspp, struct fc_els_spp *spp) +{ + struct ft_tport *tport; + struct ft_sess *sess; + struct ft_node_acl *acl; + u32 fcp_parm; + + tport = ft_tport_get(rdata->local_port); + if (!tport) + goto not_target; /* not a target for this local port */ + + acl = ft_acl_get(tport->tpg, rdata); + if (!acl) + goto not_target; /* no target for this remote */ + + if (!rspp) + goto fill; + + if (rspp->spp_flags & (FC_SPP_OPA_VAL | FC_SPP_RPA_VAL)) + return FC_SPP_RESP_NO_PA; + + /* + * If both target and initiator bits are off, the SPP is invalid. + */ + fcp_parm = ntohl(rspp->spp_params); + if (!(fcp_parm & (FCP_SPPF_INIT_FCN | FCP_SPPF_TARG_FCN))) + return FC_SPP_RESP_INVL; + + /* + * Create session (image pair) only if requested by + * EST_IMG_PAIR flag and if the requestor is an initiator. + */ + if (rspp->spp_flags & FC_SPP_EST_IMG_PAIR) { + spp->spp_flags |= FC_SPP_EST_IMG_PAIR; + if (!(fcp_parm & FCP_SPPF_INIT_FCN)) + return FC_SPP_RESP_CONF; + sess = ft_sess_create(tport, rdata->ids.port_id, acl); + if (!sess) + return FC_SPP_RESP_RES; + if (!sess->params) + rdata->prli_count++; + sess->params = fcp_parm; + sess->port_name = rdata->ids.port_name; + sess->max_frame = rdata->maxframe_size; + + /* XXX TBD - clearing actions. unit attn, see 4.10 */ + } + + /* + * OR in our service parameters with other provider (initiator), if any. + */ +fill: + fcp_parm = ntohl(spp->spp_params); + fcp_parm &= ~FCP_SPPF_RETRY; + spp->spp_params = htonl(fcp_parm | FCP_SPPF_TARG_FCN); + return FC_SPP_RESP_ACK; + +not_target: + fcp_parm = ntohl(spp->spp_params); + fcp_parm &= ~FCP_SPPF_TARG_FCN; + spp->spp_params = htonl(fcp_parm); + return 0; +} + +/** + * tcm_fcp_prli() - Handle incoming or outgoing PRLI for the FCP target + * @rdata: remote port private + * @spp_len: service parameter page length + * @rspp: received service parameter page (NULL for outgoing PRLI) + * @spp: response service parameter page + * + * Returns spp response code. + */ +static int ft_prli(struct fc_rport_priv *rdata, u32 spp_len, + const struct fc_els_spp *rspp, struct fc_els_spp *spp) +{ + int ret; + + mutex_lock(&ft_lport_lock); + ret = ft_prli_locked(rdata, spp_len, rspp, spp); + mutex_unlock(&ft_lport_lock); + pr_debug("port_id %x flags %x ret %x\n", + rdata->ids.port_id, rspp ? rspp->spp_flags : 0, ret); + return ret; +} + +static void ft_sess_free(struct kref *kref) +{ + struct ft_sess *sess = container_of(kref, struct ft_sess, kref); + + transport_deregister_session(sess->se_sess); + kfree_rcu(sess, rcu); +} + +void ft_sess_put(struct ft_sess *sess) +{ + int sess_held = atomic_read(&sess->kref.refcount); + + BUG_ON(!sess_held); + kref_put(&sess->kref, ft_sess_free); +} + +static void ft_prlo(struct fc_rport_priv *rdata) +{ + struct ft_sess *sess; + struct ft_tport *tport; + + mutex_lock(&ft_lport_lock); + tport = rcu_dereference_protected(rdata->local_port->prov[FC_TYPE_FCP], + lockdep_is_held(&ft_lport_lock)); + + if (!tport) { + mutex_unlock(&ft_lport_lock); + return; + } + sess = ft_sess_delete(tport, rdata->ids.port_id); + if (!sess) { + mutex_unlock(&ft_lport_lock); + return; + } + mutex_unlock(&ft_lport_lock); + transport_deregister_session_configfs(sess->se_sess); + ft_sess_put(sess); /* release from table */ + rdata->prli_count--; + /* XXX TBD - clearing actions. unit attn, see 4.10 */ +} + +/* + * Handle incoming FCP request. + * Caller has verified that the frame is type FCP. + */ +static void ft_recv(struct fc_lport *lport, struct fc_frame *fp) +{ + struct ft_sess *sess; + u32 sid = fc_frame_sid(fp); + + pr_debug("sid %x\n", sid); + + sess = ft_sess_get(lport, sid); + if (!sess) { + pr_debug("sid %x sess lookup failed\n", sid); + /* TBD XXX - if FCP_CMND, send PRLO */ + fc_frame_free(fp); + return; + } + ft_recv_req(sess, fp); /* must do ft_sess_put() */ +} + +/* + * Provider ops for libfc. + */ +struct fc4_prov ft_prov = { + .prli = ft_prli, + .prlo = ft_prlo, + .recv = ft_recv, + .module = THIS_MODULE, +}; |
