scsi: iscsi: Stop queueing during ep_disconnect

During ep_disconnect we have been doing iscsi_suspend_tx/queue to block new
I/O but every driver except cxgbi and iscsi_tcp can still get I/O from
__iscsi_conn_send_pdu() if we haven't called iscsi_conn_failure() before
ep_disconnect. This could happen if we were terminating the session, and
the logout timed out before it was even sent to libiscsi.

Fix the issue by adding a helper which reverses the bind_conn call that
allows new I/O to be queued. Drivers implementing ep_disconnect can use this
to make sure new I/O is not queued to them when handling the disconnect.

Link: https://lore.kernel.org/r/20210525181821.7617-3-michael.christie@oracle.com
Reviewed-by: Lee Duncan <lduncan@suse.com>
Signed-off-by: Mike Christie <michael.christie@oracle.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
This commit is contained in:
Mike Christie 2021-05-25 13:17:55 -05:00 committed by Martin K. Petersen
parent 1486a4f5c2
commit 891e2639de
11 changed files with 78 additions and 11 deletions

View File

@ -1002,6 +1002,7 @@ static struct iscsi_transport iscsi_iser_transport = {
/* connection management */
.create_conn = iscsi_iser_conn_create,
.bind_conn = iscsi_iser_conn_bind,
.unbind_conn = iscsi_conn_unbind,
.destroy_conn = iscsi_conn_teardown,
.attr_is_visible = iser_attr_is_visible,
.set_param = iscsi_iser_set_param,

View File

@ -5807,6 +5807,7 @@ struct iscsi_transport beiscsi_iscsi_transport = {
.destroy_session = beiscsi_session_destroy,
.create_conn = beiscsi_conn_create,
.bind_conn = beiscsi_conn_bind,
.unbind_conn = iscsi_conn_unbind,
.destroy_conn = iscsi_conn_teardown,
.attr_is_visible = beiscsi_attr_is_visible,
.set_iface_param = beiscsi_iface_set_param,

View File

@ -2276,6 +2276,7 @@ struct iscsi_transport bnx2i_iscsi_transport = {
.destroy_session = bnx2i_session_destroy,
.create_conn = bnx2i_conn_create,
.bind_conn = bnx2i_conn_bind,
.unbind_conn = iscsi_conn_unbind,
.destroy_conn = bnx2i_conn_destroy,
.attr_is_visible = bnx2i_attr_is_visible,
.set_param = iscsi_set_param,

View File

@ -117,6 +117,7 @@ static struct iscsi_transport cxgb3i_iscsi_transport = {
/* connection management */
.create_conn = cxgbi_create_conn,
.bind_conn = cxgbi_bind_conn,
.unbind_conn = iscsi_conn_unbind,
.destroy_conn = iscsi_tcp_conn_teardown,
.start_conn = iscsi_conn_start,
.stop_conn = iscsi_conn_stop,

View File

@ -134,6 +134,7 @@ static struct iscsi_transport cxgb4i_iscsi_transport = {
/* connection management */
.create_conn = cxgbi_create_conn,
.bind_conn = cxgbi_bind_conn,
.unbind_conn = iscsi_conn_unbind,
.destroy_conn = iscsi_tcp_conn_teardown,
.start_conn = iscsi_conn_start,
.stop_conn = iscsi_conn_stop,

View File

@ -1387,23 +1387,32 @@ void iscsi_session_failure(struct iscsi_session *session,
}
EXPORT_SYMBOL_GPL(iscsi_session_failure);
void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err)
static bool iscsi_set_conn_failed(struct iscsi_conn *conn)
{
struct iscsi_session *session = conn->session;
spin_lock_bh(&session->frwd_lock);
if (session->state == ISCSI_STATE_FAILED) {
spin_unlock_bh(&session->frwd_lock);
return;
}
if (session->state == ISCSI_STATE_FAILED)
return false;
if (conn->stop_stage == 0)
session->state = ISCSI_STATE_FAILED;
spin_unlock_bh(&session->frwd_lock);
set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx);
set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
iscsi_conn_error_event(conn->cls_conn, err);
return true;
}
void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err)
{
struct iscsi_session *session = conn->session;
bool needs_evt;
spin_lock_bh(&session->frwd_lock);
needs_evt = iscsi_set_conn_failed(conn);
spin_unlock_bh(&session->frwd_lock);
if (needs_evt)
iscsi_conn_error_event(conn->cls_conn, err);
}
EXPORT_SYMBOL_GPL(iscsi_conn_failure);
@ -2180,6 +2189,51 @@ done:
spin_unlock(&session->frwd_lock);
}
/**
* iscsi_conn_unbind - prevent queueing to conn.
* @cls_conn: iscsi conn ep is bound to.
* @is_active: is the conn in use for boot or is this for EH/termination
*
* This must be called by drivers implementing the ep_disconnect callout.
* It disables queueing to the connection from libiscsi in preparation for
* an ep_disconnect call.
*/
void iscsi_conn_unbind(struct iscsi_cls_conn *cls_conn, bool is_active)
{
struct iscsi_session *session;
struct iscsi_conn *conn;
if (!cls_conn)
return;
conn = cls_conn->dd_data;
session = conn->session;
/*
* Wait for iscsi_eh calls to exit. We don't wait for the tmf to
* complete or timeout. The caller just wants to know what's running
* is everything that needs to be cleaned up, and no cmds will be
* queued.
*/
mutex_lock(&session->eh_mutex);
iscsi_suspend_queue(conn);
iscsi_suspend_tx(conn);
spin_lock_bh(&session->frwd_lock);
if (!is_active) {
/*
* if logout timed out before userspace could even send a PDU
* the state might still be in ISCSI_STATE_LOGGED_IN and
* allowing new cmds and TMFs.
*/
if (session->state == ISCSI_STATE_LOGGED_IN)
iscsi_set_conn_failed(conn);
}
spin_unlock_bh(&session->frwd_lock);
mutex_unlock(&session->eh_mutex);
}
EXPORT_SYMBOL_GPL(iscsi_conn_unbind);
static void iscsi_prep_abort_task_pdu(struct iscsi_task *task,
struct iscsi_tm *hdr)
{

View File

@ -1401,6 +1401,7 @@ struct iscsi_transport qedi_iscsi_transport = {
.destroy_session = qedi_session_destroy,
.create_conn = qedi_conn_create,
.bind_conn = qedi_conn_bind,
.unbind_conn = iscsi_conn_unbind,
.start_conn = qedi_conn_start,
.stop_conn = iscsi_conn_stop,
.destroy_conn = qedi_conn_destroy,

View File

@ -259,6 +259,7 @@ static struct iscsi_transport qla4xxx_iscsi_transport = {
.start_conn = qla4xxx_conn_start,
.create_conn = qla4xxx_conn_create,
.bind_conn = qla4xxx_conn_bind,
.unbind_conn = iscsi_conn_unbind,
.stop_conn = iscsi_conn_stop,
.destroy_conn = qla4xxx_conn_destroy,
.set_param = iscsi_set_param,

View File

@ -2964,7 +2964,7 @@ release_host:
}
static int iscsi_if_ep_disconnect(struct iscsi_transport *transport,
u64 ep_handle)
u64 ep_handle, bool is_active)
{
struct iscsi_cls_conn *conn;
struct iscsi_endpoint *ep;
@ -2981,6 +2981,8 @@ static int iscsi_if_ep_disconnect(struct iscsi_transport *transport,
conn->ep = NULL;
mutex_unlock(&conn->ep_mutex);
conn->state = ISCSI_CONN_FAILED;
transport->unbind_conn(conn, is_active);
}
transport->ep_disconnect(ep);
@ -3012,7 +3014,8 @@ iscsi_if_transport_ep(struct iscsi_transport *transport,
break;
case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT:
rc = iscsi_if_ep_disconnect(transport,
ev->u.ep_disconnect.ep_handle);
ev->u.ep_disconnect.ep_handle,
false);
break;
}
return rc;
@ -3737,7 +3740,7 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, uint32_t *group)
conn = iscsi_conn_lookup(ev->u.b_conn.sid, ev->u.b_conn.cid);
if (conn && conn->ep)
iscsi_if_ep_disconnect(transport, conn->ep->id);
iscsi_if_ep_disconnect(transport, conn->ep->id, true);
if (!session || !conn) {
err = -EINVAL;
@ -4656,6 +4659,7 @@ iscsi_register_transport(struct iscsi_transport *tt)
int err;
BUG_ON(!tt);
WARN_ON(tt->ep_disconnect && !tt->unbind_conn);
priv = iscsi_if_transport_lookup(tt);
if (priv)

View File

@ -431,6 +431,7 @@ extern int iscsi_conn_start(struct iscsi_cls_conn *);
extern void iscsi_conn_stop(struct iscsi_cls_conn *, int);
extern int iscsi_conn_bind(struct iscsi_cls_session *, struct iscsi_cls_conn *,
int);
extern void iscsi_conn_unbind(struct iscsi_cls_conn *cls_conn, bool is_active);
extern void iscsi_conn_failure(struct iscsi_conn *conn, enum iscsi_err err);
extern void iscsi_session_failure(struct iscsi_session *session,
enum iscsi_err err);

View File

@ -82,6 +82,7 @@ struct iscsi_transport {
void (*destroy_session) (struct iscsi_cls_session *session);
struct iscsi_cls_conn *(*create_conn) (struct iscsi_cls_session *sess,
uint32_t cid);
void (*unbind_conn) (struct iscsi_cls_conn *conn, bool is_active);
int (*bind_conn) (struct iscsi_cls_session *session,
struct iscsi_cls_conn *cls_conn,
uint64_t transport_eph, int is_leading);