| /* |
| * Copyright (C) 2002 - 2003 Ardis Technologies <roman@ardistech.com> |
| * Copyright (C) 2007 - 2018 Vladislav Bolkhovitin |
| * Copyright (C) 2007 - 2018 Western Digital Corporation |
| * |
| * 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, version 2 |
| * of the License. |
| * |
| * 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. |
| */ |
| |
| #ifndef INSIDE_KERNEL_TREE |
| #include <linux/version.h> |
| #endif |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) |
| #include <linux/export.h> |
| #endif |
| |
| #include "iscsi_trace_flag.h" |
| #include "iscsi.h" |
| |
| /* target_mutex supposed to be locked */ |
| struct iscsi_session *session_lookup(struct iscsi_target *target, u64 sid) |
| { |
| struct iscsi_session *session; |
| |
| lockdep_assert_held(&target->target_mutex); |
| |
| list_for_each_entry(session, &target->session_list, |
| session_list_entry) { |
| if (session->sid == sid) |
| return session; |
| } |
| return NULL; |
| } |
| |
| /* target_mgmt_mutex supposed to be locked */ |
| static int iscsi_session_alloc(struct iscsi_target *target, |
| struct iscsi_kern_session_info *info, struct iscsi_session **result) |
| { |
| int err; |
| unsigned int i; |
| struct iscsi_session *session; |
| char *name = NULL; |
| |
| lockdep_assert_held(&target_mgmt_mutex); |
| |
| session = kmem_cache_zalloc(iscsi_sess_cache, GFP_KERNEL); |
| if (!session) |
| return -ENOMEM; |
| |
| session->target = target; |
| session->sid = info->sid; |
| atomic_set(&session->active_cmds, 0); |
| session->exp_cmd_sn = info->exp_cmd_sn; |
| |
| session->initiator_name = kstrdup(info->initiator_name, GFP_KERNEL); |
| if (!session->initiator_name) { |
| err = -ENOMEM; |
| goto err; |
| } |
| |
| name = info->full_initiator_name; |
| |
| INIT_LIST_HEAD(&session->conn_list); |
| INIT_LIST_HEAD(&session->pending_list); |
| |
| spin_lock_init(&session->sn_lock); |
| |
| spin_lock_init(&session->cmnd_data_wait_hash_lock); |
| for (i = 0; i < ARRAY_SIZE(session->cmnd_data_wait_hash); i++) |
| INIT_LIST_HEAD(&session->cmnd_data_wait_hash[i]); |
| |
| session->next_ttt = 1; |
| |
| session->scst_sess = scst_register_session(target->scst_tgt, 0, |
| name, session, NULL, NULL); |
| if (session->scst_sess == NULL) { |
| PRINT_ERROR("%s", "scst_register_session() failed"); |
| err = -ENOMEM; |
| goto err; |
| } |
| |
| if (!session->sess_params.rdma_extensions) { |
| err = iscsi_threads_pool_get( |
| scst_get_acg_tgt_priv(session->scst_sess->acg) != NULL, |
| &session->scst_sess->acg->acg_cpu_mask, |
| &session->sess_thr_pool); |
| if (err != 0) |
| goto err_unreg; |
| } |
| |
| |
| TRACE(TRACE_MGMT, "Session %p created: target %p, tid %u, sid %#Lx, initiator %s", |
| session, target, target->tid, info->sid, |
| session->scst_sess->initiator_name); |
| |
| *result = session; |
| return 0; |
| |
| err_unreg: |
| scst_unregister_session(session->scst_sess, 1, NULL); |
| |
| err: |
| if (session) { |
| kfree(session->initiator_name); |
| kmem_cache_free(iscsi_sess_cache, session); |
| } |
| return err; |
| } |
| |
| /* target_mutex supposed to be locked */ |
| void sess_reinst_finished(struct iscsi_session *sess) |
| { |
| struct iscsi_conn *c; |
| |
| TRACE_ENTRY(); |
| |
| TRACE_MGMT_DBG("Enabling reinstate successor sess %p", sess); |
| |
| lockdep_assert_held(&sess->target->target_mutex); |
| |
| sBUG_ON(!sess->sess_reinstating); |
| |
| list_for_each_entry(c, &sess->conn_list, conn_list_entry) { |
| conn_reinst_finished(c); |
| } |
| sess->sess_reinstating = 0; |
| |
| TRACE_EXIT(); |
| return; |
| } |
| |
| /* target_mgmt_mutex supposed to be locked */ |
| int __add_session(struct iscsi_target *target, |
| struct iscsi_kern_session_info *info) |
| { |
| struct iscsi_session *new_sess = NULL, *sess, *old_sess; |
| int err = 0, i; |
| union iscsi_sid sid; |
| bool reinstatement = false; |
| struct iscsi_kern_params_info *params_info; |
| |
| char addr[64]; |
| struct iscsi_conn *conn; |
| |
| TRACE_MGMT_DBG("Adding session SID %llx", info->sid); |
| |
| lockdep_assert_held(&target_mgmt_mutex); |
| |
| err = iscsi_session_alloc(target, info, &new_sess); |
| if (err != 0) |
| goto out; |
| |
| mutex_lock(&target->target_mutex); |
| |
| sess = session_lookup(target, info->sid); |
| if (sess != NULL) { |
| PRINT_ERROR("Attempt to add session with existing SID %llx", |
| info->sid); |
| err = -EEXIST; |
| goto out_err_unlock; |
| } |
| |
| params_info = kmalloc(sizeof(*params_info), GFP_KERNEL); |
| if (params_info == NULL) { |
| PRINT_ERROR("Unable to allocate params info (size %zd)", |
| sizeof(*params_info)); |
| err = -ENOMEM; |
| goto out_err_unlock; |
| } |
| |
| sid = *(union iscsi_sid *)&info->sid; |
| sid.id.tsih = 0; |
| old_sess = NULL; |
| |
| /* |
| * We need to find the latest session to correctly handle |
| * multi-reinstatements |
| */ |
| list_for_each_entry_reverse(sess, &target->session_list, |
| session_list_entry) { |
| union iscsi_sid s = *(union iscsi_sid *)&sess->sid; |
| |
| s.id.tsih = 0; |
| if ((sid.id64 == s.id64) && |
| (strcmp(info->initiator_name, sess->initiator_name) == 0)) { |
| if (!sess->sess_shutting_down) { |
| /* session reinstatement */ |
| old_sess = sess; |
| } |
| break; |
| } |
| } |
| sess = NULL; |
| |
| list_add_tail(&new_sess->session_list_entry, &target->session_list); |
| |
| memset(params_info, 0, sizeof(*params_info)); |
| params_info->tid = target->tid; |
| params_info->sid = info->sid; |
| params_info->params_type = key_session; |
| for (i = 0; i < session_key_last; i++) |
| params_info->session_params[i] = info->session_params[i]; |
| |
| err = iscsi_params_set(target, params_info, 1); |
| if (err != 0) |
| goto out_del; |
| |
| memset(params_info, 0, sizeof(*params_info)); |
| params_info->tid = target->tid; |
| params_info->sid = info->sid; |
| params_info->params_type = key_target; |
| for (i = 0; i < target_key_last; i++) |
| params_info->target_params[i] = info->target_params[i]; |
| |
| err = iscsi_params_set(target, params_info, 1); |
| if (err != 0) |
| goto out_del; |
| |
| kfree(params_info); |
| params_info = NULL; |
| |
| if (old_sess != NULL) { |
| reinstatement = true; |
| TRACE(TRACE_MGMT, "Reinstating sess %p with SID %llx (old %p, " |
| "SID %llx)", new_sess, new_sess->sid, old_sess, |
| old_sess->sid); |
| |
| list_for_each_entry(conn, &old_sess->conn_list, conn_list_entry) { |
| conn->transport->iscsit_get_initiator_ip(conn, addr, sizeof(addr)); |
| TRACE(TRACE_MGMT, "Deleting session for initiator %s at IP: %s", old_sess->initiator_name, addr); |
| } |
| new_sess->sess_reinstating = 1; |
| old_sess->sess_reinst_successor = new_sess; |
| |
| target_del_session(old_sess->target, old_sess, 0); |
| } |
| |
| mutex_unlock(&target->target_mutex); |
| |
| if (reinstatement) { |
| /* |
| * Mutex target_mgmt_mutex won't allow to add connections to |
| * the new session after target_mutex was dropped, so it's safe |
| * to replace the initial UA without it. We can't do it under |
| * target_mutex, because otherwise we can establish a |
| * circular locking dependency between target_mutex and |
| * scst_mutex in SCST core (iscsi_report_aen() called by |
| * SCST core under scst_mutex). |
| */ |
| scst_set_initial_UA(new_sess->scst_sess, |
| SCST_LOAD_SENSE(scst_sense_nexus_loss_UA)); |
| } |
| |
| out: |
| return err; |
| |
| out_del: |
| list_del(&new_sess->session_list_entry); |
| kfree(params_info); |
| |
| out_err_unlock: |
| mutex_unlock(&target->target_mutex); |
| |
| scst_unregister_session(new_sess->scst_sess, 1, NULL); |
| new_sess->scst_sess = NULL; |
| |
| mutex_lock(&target->target_mutex); |
| session_free(new_sess, false); |
| mutex_unlock(&target->target_mutex); |
| goto out; |
| } |
| |
| static void __session_free(struct iscsi_session *session) |
| { |
| if (session->sess_thr_pool) |
| iscsi_threads_pool_put(session->sess_thr_pool); |
| kfree(session->initiator_name); |
| kmem_cache_free(iscsi_sess_cache, session); |
| } |
| |
| static void iscsi_unreg_sess_done(struct scst_session *scst_sess) |
| { |
| struct iscsi_session *session; |
| |
| TRACE_ENTRY(); |
| |
| session = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); |
| |
| session->scst_sess = NULL; |
| __session_free(session); |
| |
| TRACE_EXIT(); |
| return; |
| } |
| |
| /* target_mutex supposed to be locked */ |
| int session_free(struct iscsi_session *session, bool del) |
| { |
| unsigned int i; |
| |
| TRACE(TRACE_MGMT, "Freeing session %p (SID %llx)", |
| session, session->sid); |
| |
| lockdep_assert_held(&session->target->target_mutex); |
| |
| sBUG_ON(!list_empty(&session->conn_list)); |
| if (unlikely(atomic_read(&session->active_cmds) != 0)) { |
| PRINT_CRIT_ERROR("active_cmds not 0 (%d)!!", |
| atomic_read(&session->active_cmds)); |
| sBUG(); |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(session->cmnd_data_wait_hash); i++) |
| sBUG_ON(!list_empty(&session->cmnd_data_wait_hash[i])); |
| |
| if (session->sess_reinst_successor != NULL) |
| sess_reinst_finished(session->sess_reinst_successor); |
| |
| if (session->sess_reinstating) { |
| struct iscsi_session *s; |
| |
| TRACE_MGMT_DBG("Freeing being reinstated sess %p", session); |
| list_for_each_entry(s, &session->target->session_list, |
| session_list_entry) { |
| if (s->sess_reinst_successor == session) { |
| s->sess_reinst_successor = NULL; |
| break; |
| } |
| } |
| } |
| |
| if (del) |
| list_del(&session->session_list_entry); |
| |
| if (session->scst_sess != NULL) { |
| /* |
| * We must NOT call scst_unregister_session() in the waiting |
| * mode, since we are under target_mutex. Otherwise we can |
| * establish a circular locking dependency between target_mutex |
| * and scst_mutex in SCST core (iscsi_report_aen() called by |
| * SCST core under scst_mutex). |
| */ |
| scst_unregister_session(session->scst_sess, 0, |
| iscsi_unreg_sess_done); |
| } else |
| __session_free(session); |
| |
| return 0; |
| } |
| |
| /* target_mutex supposed to be locked */ |
| int __del_session(struct iscsi_target *target, u64 sid) |
| { |
| struct iscsi_session *session; |
| |
| lockdep_assert_held(&target->target_mutex); |
| |
| session = session_lookup(target, sid); |
| if (!session) |
| return -ENOENT; |
| |
| if (!list_empty(&session->conn_list)) { |
| PRINT_ERROR("%llx still have connections", |
| (unsigned long long)session->sid); |
| return -EBUSY; |
| } |
| |
| return session_free(session, true); |
| } |
| |
| /* Must be called under target_mutex */ |
| void iscsi_sess_force_close(struct iscsi_session *sess) |
| { |
| struct iscsi_conn *conn; |
| |
| TRACE_ENTRY(); |
| |
| lockdep_assert_held(&sess->target->target_mutex); |
| |
| PRINT_INFO("Force closing session %llx with initiator %s (%p)", |
| (unsigned long long)sess->sid, sess->initiator_name, sess); |
| |
| list_for_each_entry(conn, &sess->conn_list, conn_list_entry) { |
| TRACE(TRACE_MGMT, "Force closing connection %p", conn); |
| __mark_conn_closed(conn, |
| ISCSI_CONN_ACTIVE_CLOSE|ISCSI_CONN_DELETING); |
| } |
| |
| TRACE_EXIT(); |
| return; |
| } |
| |
| |
| #define ISCSI_SESS_BOOL_PARAM_ATTR(name, exported_name) \ |
| static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \ |
| struct kobj_attribute *attr, char *buf) \ |
| { \ |
| int pos; \ |
| struct scst_session *scst_sess; \ |
| struct iscsi_session *sess; \ |
| \ |
| scst_sess = container_of(kobj, struct scst_session, sess_kobj); \ |
| sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \ |
| \ |
| pos = sprintf(buf, "%s\n", \ |
| iscsi_get_bool_value(sess->sess_params.name)); \ |
| \ |
| return pos; \ |
| } \ |
| \ |
| static struct kobj_attribute iscsi_sess_attr_##name = \ |
| __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL) |
| |
| #define ISCSI_SESS_INT_PARAM_ATTR(name, exported_name) \ |
| static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \ |
| struct kobj_attribute *attr, char *buf) \ |
| { \ |
| int pos; \ |
| struct scst_session *scst_sess; \ |
| struct iscsi_session *sess; \ |
| \ |
| scst_sess = container_of(kobj, struct scst_session, sess_kobj); \ |
| sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \ |
| \ |
| pos = sprintf(buf, "%d\n", sess->sess_params.name); \ |
| \ |
| return pos; \ |
| } \ |
| \ |
| static struct kobj_attribute iscsi_sess_attr_##name = \ |
| __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL) |
| |
| #define ISCSI_SESS_DIGEST_PARAM_ATTR(name, exported_name) \ |
| static ssize_t iscsi_sess_show_##name(struct kobject *kobj, \ |
| struct kobj_attribute *attr, char *buf) \ |
| { \ |
| int pos; \ |
| struct scst_session *scst_sess; \ |
| struct iscsi_session *sess; \ |
| char digest_name[64]; \ |
| \ |
| scst_sess = container_of(kobj, struct scst_session, sess_kobj); \ |
| sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); \ |
| \ |
| pos = sprintf(buf, "%s\n", iscsi_get_digest_name( \ |
| sess->sess_params.name, digest_name)); \ |
| \ |
| return pos; \ |
| } \ |
| \ |
| static struct kobj_attribute iscsi_sess_attr_##name = \ |
| __ATTR(exported_name, S_IRUGO, iscsi_sess_show_##name, NULL) |
| |
| ISCSI_SESS_BOOL_PARAM_ATTR(initial_r2t, InitialR2T); |
| ISCSI_SESS_BOOL_PARAM_ATTR(immediate_data, ImmediateData); |
| ISCSI_SESS_INT_PARAM_ATTR(max_recv_data_length, MaxRecvDataSegmentLength); |
| ISCSI_SESS_INT_PARAM_ATTR(max_xmit_data_length, MaxXmitDataSegmentLength); |
| ISCSI_SESS_INT_PARAM_ATTR(max_burst_length, MaxBurstLength); |
| ISCSI_SESS_INT_PARAM_ATTR(first_burst_length, FirstBurstLength); |
| ISCSI_SESS_INT_PARAM_ATTR(max_outstanding_r2t, MaxOutstandingR2T); |
| ISCSI_SESS_DIGEST_PARAM_ATTR(header_digest, HeaderDigest); |
| ISCSI_SESS_DIGEST_PARAM_ATTR(data_digest, DataDigest); |
| |
| static ssize_t iscsi_sess_sid_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| int pos; |
| struct scst_session *scst_sess; |
| struct iscsi_session *sess; |
| |
| TRACE_ENTRY(); |
| |
| scst_sess = container_of(kobj, struct scst_session, sess_kobj); |
| sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); |
| |
| pos = sprintf(buf, "%llx\n", sess->sid); |
| |
| TRACE_EXIT_RES(pos); |
| return pos; |
| } |
| |
| static struct kobj_attribute iscsi_attr_sess_sid = |
| __ATTR(sid, S_IRUGO, iscsi_sess_sid_show, NULL); |
| |
| static ssize_t iscsi_sess_reinstating_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| int pos; |
| struct scst_session *scst_sess; |
| struct iscsi_session *sess; |
| |
| TRACE_ENTRY(); |
| |
| scst_sess = container_of(kobj, struct scst_session, sess_kobj); |
| sess = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); |
| |
| pos = sprintf(buf, "%d\n", sess->sess_reinstating ? 1 : 0); |
| |
| TRACE_EXIT_RES(pos); |
| return pos; |
| } |
| |
| static struct kobj_attribute iscsi_sess_attr_reinstating = |
| __ATTR(reinstating, S_IRUGO, iscsi_sess_reinstating_show, NULL); |
| |
| static ssize_t iscsi_sess_thread_pid_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| struct scst_session *scst_sess = container_of(kobj, struct scst_session, |
| sess_kobj); |
| struct iscsi_session *sess = scst_sess_get_tgt_priv(scst_sess); |
| struct iscsi_thread_pool *thr_pool = sess->sess_thr_pool; |
| struct iscsi_thread *t; |
| int res = -ENOENT; |
| |
| if (!thr_pool) |
| goto out; |
| |
| res = 0; |
| |
| mutex_lock(&thr_pool->tp_mutex); |
| list_for_each_entry(t, &thr_pool->threads_list, threads_list_entry) |
| res += scnprintf(buf + res, PAGE_SIZE - res, "%d%s", |
| task_pid_vnr(t->thr), |
| list_is_last(&t->threads_list_entry, |
| &thr_pool->threads_list) ? |
| "\n" : " "); |
| mutex_unlock(&thr_pool->tp_mutex); |
| |
| out: |
| return res; |
| } |
| |
| static struct kobj_attribute iscsi_sess_thread_pid = |
| __ATTR(thread_pid, S_IRUGO, iscsi_sess_thread_pid_show, NULL); |
| |
| const struct attribute *iscsi_sess_attrs[] = { |
| &iscsi_sess_attr_initial_r2t.attr, |
| &iscsi_sess_attr_immediate_data.attr, |
| &iscsi_sess_attr_max_recv_data_length.attr, |
| &iscsi_sess_attr_max_xmit_data_length.attr, |
| &iscsi_sess_attr_max_burst_length.attr, |
| &iscsi_sess_attr_first_burst_length.attr, |
| &iscsi_sess_attr_max_outstanding_r2t.attr, |
| &iscsi_sess_attr_header_digest.attr, |
| &iscsi_sess_attr_data_digest.attr, |
| &iscsi_attr_sess_sid.attr, |
| &iscsi_sess_attr_reinstating.attr, |
| &iscsi_sess_thread_pid.attr, |
| NULL, |
| }; |
| |