Commit b4a9c7ed authored by Joe Eykholt's avatar Joe Eykholt Committed by James Bottomley

[SCSI] libfc: fix free of fc_rport_priv with timer pending

Timer crashes were caused by freeing a struct fc_rport_priv
with a timer pending, causing the timer facility list to be
corrupted.  This was during FC uplink flap tests with a lot
of targets.

After discovery, we were doing an PLOGI on an rdata that was
in DELETE state but not yet removed from the lookup list.
This moved the rdata from DELETE state to PLOGI state.
If the PLOGI exchange allocation failed and needed to be
retried, the timer scheduling could race with the free
being done by fc_rport_work().

When fc_rport_login() is called on a rport in DELETE state,
move it to a new state RESTART.  In fc_rport_work, when
handling a LOGO, STOPPED or FAILED event, look for restart
state.  In the RESTART case, don't take the rdata off the
list and after the transport remote port is deleted and
exchanges are reset, re-login to the remote port.

Note that the new RESTART state also corrects a problem we
had when re-discovering a port that had moved to DELETE state.
In that case, a new rdata was created, but the old rdata
would do an exchange manager reset affecting the FC_ID
for both the new rdata and old rdata.  With the new state,
the new port isn't logged into until after any old exchanges
are reset.
Signed-off-by: default avatarJoe Eykholt <jeykholt@cisco.com>
Signed-off-by: default avatarRobert Love <robert.w.love@intel.com>
Signed-off-by: default avatarJames Bottomley <James.Bottomley@suse.de>
parent 4b53662b
...@@ -86,6 +86,7 @@ static const char *fc_rport_state_names[] = { ...@@ -86,6 +86,7 @@ static const char *fc_rport_state_names[] = {
[RPORT_ST_LOGO] = "LOGO", [RPORT_ST_LOGO] = "LOGO",
[RPORT_ST_ADISC] = "ADISC", [RPORT_ST_ADISC] = "ADISC",
[RPORT_ST_DELETE] = "Delete", [RPORT_ST_DELETE] = "Delete",
[RPORT_ST_RESTART] = "Restart",
}; };
/** /**
...@@ -99,8 +100,7 @@ static struct fc_rport_priv *fc_rport_lookup(const struct fc_lport *lport, ...@@ -99,8 +100,7 @@ static struct fc_rport_priv *fc_rport_lookup(const struct fc_lport *lport,
struct fc_rport_priv *rdata; struct fc_rport_priv *rdata;
list_for_each_entry(rdata, &lport->disc.rports, peers) list_for_each_entry(rdata, &lport->disc.rports, peers)
if (rdata->ids.port_id == port_id && if (rdata->ids.port_id == port_id)
rdata->rp_state != RPORT_ST_DELETE)
return rdata; return rdata;
return NULL; return NULL;
} }
...@@ -235,6 +235,7 @@ static void fc_rport_work(struct work_struct *work) ...@@ -235,6 +235,7 @@ static void fc_rport_work(struct work_struct *work)
struct fc_rport_operations *rport_ops; struct fc_rport_operations *rport_ops;
struct fc_rport_identifiers ids; struct fc_rport_identifiers ids;
struct fc_rport *rport; struct fc_rport *rport;
int restart = 0;
mutex_lock(&rdata->rp_mutex); mutex_lock(&rdata->rp_mutex);
event = rdata->event; event = rdata->event;
...@@ -287,8 +288,19 @@ static void fc_rport_work(struct work_struct *work) ...@@ -287,8 +288,19 @@ static void fc_rport_work(struct work_struct *work)
mutex_unlock(&rdata->rp_mutex); mutex_unlock(&rdata->rp_mutex);
if (port_id != FC_FID_DIR_SERV) { if (port_id != FC_FID_DIR_SERV) {
/*
* We must drop rp_mutex before taking disc_mutex.
* Re-evaluate state to allow for restart.
* A transition to RESTART state must only happen
* while disc_mutex is held and rdata is on the list.
*/
mutex_lock(&lport->disc.disc_mutex); mutex_lock(&lport->disc.disc_mutex);
list_del(&rdata->peers); mutex_lock(&rdata->rp_mutex);
if (rdata->rp_state == RPORT_ST_RESTART)
restart = 1;
else
list_del(&rdata->peers);
mutex_unlock(&rdata->rp_mutex);
mutex_unlock(&lport->disc.disc_mutex); mutex_unlock(&lport->disc.disc_mutex);
} }
...@@ -312,7 +324,13 @@ static void fc_rport_work(struct work_struct *work) ...@@ -312,7 +324,13 @@ static void fc_rport_work(struct work_struct *work)
mutex_unlock(&rdata->rp_mutex); mutex_unlock(&rdata->rp_mutex);
fc_remote_port_delete(rport); fc_remote_port_delete(rport);
} }
kref_put(&rdata->kref, lport->tt.rport_destroy); if (restart) {
mutex_lock(&rdata->rp_mutex);
FC_RPORT_DBG(rdata, "work restart\n");
fc_rport_enter_plogi(rdata);
mutex_unlock(&rdata->rp_mutex);
} else
kref_put(&rdata->kref, lport->tt.rport_destroy);
break; break;
default: default:
...@@ -342,6 +360,12 @@ int fc_rport_login(struct fc_rport_priv *rdata) ...@@ -342,6 +360,12 @@ int fc_rport_login(struct fc_rport_priv *rdata)
FC_RPORT_DBG(rdata, "ADISC port\n"); FC_RPORT_DBG(rdata, "ADISC port\n");
fc_rport_enter_adisc(rdata); fc_rport_enter_adisc(rdata);
break; break;
case RPORT_ST_RESTART:
break;
case RPORT_ST_DELETE:
FC_RPORT_DBG(rdata, "Restart deleted port\n");
fc_rport_state_enter(rdata, RPORT_ST_RESTART);
break;
default: default:
FC_RPORT_DBG(rdata, "Login to port\n"); FC_RPORT_DBG(rdata, "Login to port\n");
fc_rport_enter_plogi(rdata); fc_rport_enter_plogi(rdata);
...@@ -397,20 +421,21 @@ int fc_rport_logoff(struct fc_rport_priv *rdata) ...@@ -397,20 +421,21 @@ int fc_rport_logoff(struct fc_rport_priv *rdata)
if (rdata->rp_state == RPORT_ST_DELETE) { if (rdata->rp_state == RPORT_ST_DELETE) {
FC_RPORT_DBG(rdata, "Port in Delete state, not removing\n"); FC_RPORT_DBG(rdata, "Port in Delete state, not removing\n");
mutex_unlock(&rdata->rp_mutex);
goto out; goto out;
} }
fc_rport_enter_logo(rdata); if (rdata->rp_state == RPORT_ST_RESTART)
FC_RPORT_DBG(rdata, "Port in Restart state, deleting\n");
else
fc_rport_enter_logo(rdata);
/* /*
* Change the state to Delete so that we discard * Change the state to Delete so that we discard
* the response. * the response.
*/ */
fc_rport_enter_delete(rdata, RPORT_EV_STOP); fc_rport_enter_delete(rdata, RPORT_EV_STOP);
mutex_unlock(&rdata->rp_mutex);
out: out:
mutex_unlock(&rdata->rp_mutex);
return 0; return 0;
} }
...@@ -466,6 +491,7 @@ static void fc_rport_timeout(struct work_struct *work) ...@@ -466,6 +491,7 @@ static void fc_rport_timeout(struct work_struct *work)
case RPORT_ST_READY: case RPORT_ST_READY:
case RPORT_ST_INIT: case RPORT_ST_INIT:
case RPORT_ST_DELETE: case RPORT_ST_DELETE:
case RPORT_ST_RESTART:
break; break;
} }
...@@ -499,6 +525,7 @@ static void fc_rport_error(struct fc_rport_priv *rdata, struct fc_frame *fp) ...@@ -499,6 +525,7 @@ static void fc_rport_error(struct fc_rport_priv *rdata, struct fc_frame *fp)
fc_rport_enter_logo(rdata); fc_rport_enter_logo(rdata);
break; break;
case RPORT_ST_DELETE: case RPORT_ST_DELETE:
case RPORT_ST_RESTART:
case RPORT_ST_READY: case RPORT_ST_READY:
case RPORT_ST_INIT: case RPORT_ST_INIT:
break; break;
...@@ -1248,6 +1275,7 @@ static void fc_rport_recv_plogi_req(struct fc_lport *lport, ...@@ -1248,6 +1275,7 @@ static void fc_rport_recv_plogi_req(struct fc_lport *lport,
} }
break; break;
case RPORT_ST_PRLI: case RPORT_ST_PRLI:
case RPORT_ST_RTV:
case RPORT_ST_READY: case RPORT_ST_READY:
case RPORT_ST_ADISC: case RPORT_ST_ADISC:
FC_RPORT_DBG(rdata, "Received PLOGI in logged-in state %d " FC_RPORT_DBG(rdata, "Received PLOGI in logged-in state %d "
...@@ -1255,11 +1283,14 @@ static void fc_rport_recv_plogi_req(struct fc_lport *lport, ...@@ -1255,11 +1283,14 @@ static void fc_rport_recv_plogi_req(struct fc_lport *lport,
/* XXX TBD - should reset */ /* XXX TBD - should reset */
break; break;
case RPORT_ST_DELETE: case RPORT_ST_DELETE:
default: case RPORT_ST_LOGO:
FC_RPORT_DBG(rdata, "Received PLOGI in unexpected state %d\n", case RPORT_ST_RESTART:
rdata->rp_state); FC_RPORT_DBG(rdata, "Received PLOGI in state %s - send busy\n",
fc_frame_free(rx_fp); fc_rport_state(rdata));
goto out; mutex_unlock(&rdata->rp_mutex);
rjt_data.reason = ELS_RJT_BUSY;
rjt_data.explan = ELS_EXPL_NONE;
goto reject;
} }
/* /*
...@@ -1510,14 +1541,14 @@ static void fc_rport_recv_logo_req(struct fc_lport *lport, ...@@ -1510,14 +1541,14 @@ static void fc_rport_recv_logo_req(struct fc_lport *lport,
FC_RPORT_DBG(rdata, "Received LOGO request while in state %s\n", FC_RPORT_DBG(rdata, "Received LOGO request while in state %s\n",
fc_rport_state(rdata)); fc_rport_state(rdata));
fc_rport_enter_delete(rdata, RPORT_EV_LOGO);
/* /*
* If the remote port was created due to discovery, * If the remote port was created due to discovery, set state
* log back in. It may have seen a stale RSCN about us. * to log back in. It may have seen a stale RSCN about us.
*/ */
if (rdata->rp_state != RPORT_ST_DELETE && rdata->disc_id) if (rdata->disc_id)
fc_rport_enter_plogi(rdata); fc_rport_state_enter(rdata, RPORT_ST_RESTART);
else
fc_rport_enter_delete(rdata, RPORT_EV_LOGO);
mutex_unlock(&rdata->rp_mutex); mutex_unlock(&rdata->rp_mutex);
} else } else
FC_RPORT_ID_DBG(lport, sid, FC_RPORT_ID_DBG(lport, sid,
......
...@@ -145,6 +145,7 @@ enum fc_rport_state { ...@@ -145,6 +145,7 @@ enum fc_rport_state {
RPORT_ST_LOGO, /* port logout sent */ RPORT_ST_LOGO, /* port logout sent */
RPORT_ST_ADISC, /* Discover Address sent */ RPORT_ST_ADISC, /* Discover Address sent */
RPORT_ST_DELETE, /* port being deleted */ RPORT_ST_DELETE, /* port being deleted */
RPORT_ST_RESTART, /* remote port being deleted and will restart */
}; };
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment