Commit 42e33148 authored by James.Smart@Emulex.Com's avatar James.Smart@Emulex.Com Committed by James Bottomley

[SCSI] fix for fc transport recursion problem.

In the scenario that a link was broken, the devloss timer for each
rport was expire at roughly the same time, causing lots of "delete"
workqueue items being queued. Depth is dependent upon the number of
rports that were on the link.

The rport target remove calls were calling flush_scheduled_work(),
which would interrupt the stream, and start the next workqueue item,
which did the same thing, and so on until recursion depth was large.

This fix stops the recursion in the initial delete path, and pushes it
off to a host-level work item that reaps the dead rports.
Signed-off-by: default avatarJames Bottomley <James.Bottomley@SteelEye.com>
parent 7116317d
...@@ -105,6 +105,7 @@ static struct { ...@@ -105,6 +105,7 @@ static struct {
{ FC_PORTSTATE_LINKDOWN, "Linkdown" }, { FC_PORTSTATE_LINKDOWN, "Linkdown" },
{ FC_PORTSTATE_ERROR, "Error" }, { FC_PORTSTATE_ERROR, "Error" },
{ FC_PORTSTATE_LOOPBACK, "Loopback" }, { FC_PORTSTATE_LOOPBACK, "Loopback" },
{ FC_PORTSTATE_DELETED, "Deleted" },
}; };
fc_enum_name_search(port_state, fc_port_state, fc_port_state_names) fc_enum_name_search(port_state, fc_port_state, fc_port_state_names)
#define FC_PORTSTATE_MAX_NAMELEN 20 #define FC_PORTSTATE_MAX_NAMELEN 20
...@@ -211,6 +212,7 @@ fc_bitfield_name_search(remote_port_roles, fc_remote_port_role_names) ...@@ -211,6 +212,7 @@ fc_bitfield_name_search(remote_port_roles, fc_remote_port_role_names)
#define FC_MGMTSRVR_PORTID 0x00000a #define FC_MGMTSRVR_PORTID 0x00000a
static void fc_shost_remove_rports(void *data);
static void fc_timeout_deleted_rport(void *data); static void fc_timeout_deleted_rport(void *data);
static void fc_scsi_scan_rport(void *data); static void fc_scsi_scan_rport(void *data);
static void fc_rport_terminate(struct fc_rport *rport); static void fc_rport_terminate(struct fc_rport *rport);
...@@ -318,6 +320,8 @@ static int fc_host_setup(struct transport_container *tc, struct device *dev, ...@@ -318,6 +320,8 @@ static int fc_host_setup(struct transport_container *tc, struct device *dev,
fc_host_next_rport_number(shost) = 0; fc_host_next_rport_number(shost) = 0;
fc_host_next_target_id(shost) = 0; fc_host_next_target_id(shost) = 0;
fc_host_flags(shost) = 0;
INIT_WORK(&fc_host_rport_del_work(shost), fc_shost_remove_rports, shost);
return 0; return 0;
} }
...@@ -387,6 +391,7 @@ show_fc_rport_##field (struct class_device *cdev, char *buf) \ ...@@ -387,6 +391,7 @@ show_fc_rport_##field (struct class_device *cdev, char *buf) \
struct fc_internal *i = to_fc_internal(shost->transportt); \ struct fc_internal *i = to_fc_internal(shost->transportt); \
if ((i->f->get_rport_##field) && \ if ((i->f->get_rport_##field) && \
!((rport->port_state == FC_PORTSTATE_BLOCKED) || \ !((rport->port_state == FC_PORTSTATE_BLOCKED) || \
(rport->port_state == FC_PORTSTATE_DELETED) || \
(rport->port_state == FC_PORTSTATE_NOTPRESENT))) \ (rport->port_state == FC_PORTSTATE_NOTPRESENT))) \
i->f->get_rport_##field(rport); \ i->f->get_rport_##field(rport); \
return snprintf(buf, sz, format_string, cast rport->field); \ return snprintf(buf, sz, format_string, cast rport->field); \
...@@ -402,6 +407,7 @@ store_fc_rport_##field(struct class_device *cdev, const char *buf, \ ...@@ -402,6 +407,7 @@ store_fc_rport_##field(struct class_device *cdev, const char *buf, \
struct Scsi_Host *shost = rport_to_shost(rport); \ struct Scsi_Host *shost = rport_to_shost(rport); \
struct fc_internal *i = to_fc_internal(shost->transportt); \ struct fc_internal *i = to_fc_internal(shost->transportt); \
if ((rport->port_state == FC_PORTSTATE_BLOCKED) || \ if ((rport->port_state == FC_PORTSTATE_BLOCKED) || \
(rport->port_state == FC_PORTSTATE_DELETED) || \
(rport->port_state == FC_PORTSTATE_NOTPRESENT)) \ (rport->port_state == FC_PORTSTATE_NOTPRESENT)) \
return -EBUSY; \ return -EBUSY; \
val = simple_strtoul(buf, NULL, 0); \ val = simple_strtoul(buf, NULL, 0); \
...@@ -519,6 +525,7 @@ store_fc_rport_dev_loss_tmo(struct class_device *cdev, const char *buf, ...@@ -519,6 +525,7 @@ store_fc_rport_dev_loss_tmo(struct class_device *cdev, const char *buf,
struct Scsi_Host *shost = rport_to_shost(rport); struct Scsi_Host *shost = rport_to_shost(rport);
struct fc_internal *i = to_fc_internal(shost->transportt); struct fc_internal *i = to_fc_internal(shost->transportt);
if ((rport->port_state == FC_PORTSTATE_BLOCKED) || if ((rport->port_state == FC_PORTSTATE_BLOCKED) ||
(rport->port_state == FC_PORTSTATE_DELETED) ||
(rport->port_state == FC_PORTSTATE_NOTPRESENT)) (rport->port_state == FC_PORTSTATE_NOTPRESENT))
return -EBUSY; return -EBUSY;
val = simple_strtoul(buf, NULL, 0); val = simple_strtoul(buf, NULL, 0);
...@@ -1769,7 +1776,7 @@ fc_timeout_deleted_rport(void *data) ...@@ -1769,7 +1776,7 @@ fc_timeout_deleted_rport(void *data)
rport->maxframe_size = -1; rport->maxframe_size = -1;
rport->supported_classes = FC_COS_UNSPECIFIED; rport->supported_classes = FC_COS_UNSPECIFIED;
rport->roles = FC_RPORT_ROLE_UNKNOWN; rport->roles = FC_RPORT_ROLE_UNKNOWN;
rport->port_state = FC_PORTSTATE_NOTPRESENT; rport->port_state = FC_PORTSTATE_DELETED;
/* remove the identifiers that aren't used in the consisting binding */ /* remove the identifiers that aren't used in the consisting binding */
switch (fc_host_tgtid_bind_type(shost)) { switch (fc_host_tgtid_bind_type(shost)) {
...@@ -1789,14 +1796,23 @@ fc_timeout_deleted_rport(void *data) ...@@ -1789,14 +1796,23 @@ fc_timeout_deleted_rport(void *data)
break; break;
} }
spin_unlock_irqrestore(shost->host_lock, flags);
/* /*
* As this only occurs if the remote port (scsi target) * As this only occurs if the remote port (scsi target)
* went away and didn't come back - we'll remove * went away and didn't come back - we'll remove
* all attached scsi devices. * all attached scsi devices.
*
* We'll schedule the shost work item to perform the actual removal
* to avoid recursion in the different flush calls if we perform
* the removal in each target - and there are lots of targets
* whose timeouts fire at the same time.
*/ */
fc_rport_tgt_remove(rport);
if ( !(fc_host_flags(shost) & FC_SHOST_RPORT_DEL_SCHEDULED)) {
fc_host_flags(shost) |= FC_SHOST_RPORT_DEL_SCHEDULED;
scsi_queue_work(shost, &fc_host_rport_del_work(shost));
}
spin_unlock_irqrestore(shost->host_lock, flags);
} }
/** /**
...@@ -1818,6 +1834,41 @@ fc_scsi_scan_rport(void *data) ...@@ -1818,6 +1834,41 @@ fc_scsi_scan_rport(void *data)
} }
/**
* fc_shost_remove_rports - called to remove all rports that are marked
* as in a deleted (not connected) state.
*
* @data: shost whose rports are to be looked at
**/
static void
fc_shost_remove_rports(void *data)
{
struct Scsi_Host *shost = (struct Scsi_Host *)data;
struct fc_rport *rport, *next_rport;
unsigned long flags;
spin_lock_irqsave(shost->host_lock, flags);
while (fc_host_flags(shost) & FC_SHOST_RPORT_DEL_SCHEDULED) {
fc_host_flags(shost) &= ~FC_SHOST_RPORT_DEL_SCHEDULED;
restart_search:
list_for_each_entry_safe(rport, next_rport,
&fc_host_rport_bindings(shost), peers) {
if (rport->port_state == FC_PORTSTATE_DELETED) {
rport->port_state = FC_PORTSTATE_NOTPRESENT;
spin_unlock_irqrestore(shost->host_lock, flags);
fc_rport_tgt_remove(rport);
spin_lock_irqsave(shost->host_lock, flags);
goto restart_search;
}
}
}
spin_unlock_irqrestore(shost->host_lock, flags);
}
MODULE_AUTHOR("Martin Hicks"); MODULE_AUTHOR("Martin Hicks");
MODULE_DESCRIPTION("FC Transport Attributes"); MODULE_DESCRIPTION("FC Transport Attributes");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
......
...@@ -79,6 +79,7 @@ enum fc_port_state { ...@@ -79,6 +79,7 @@ enum fc_port_state {
FC_PORTSTATE_LINKDOWN, FC_PORTSTATE_LINKDOWN,
FC_PORTSTATE_ERROR, FC_PORTSTATE_ERROR,
FC_PORTSTATE_LOOPBACK, FC_PORTSTATE_LOOPBACK,
FC_PORTSTATE_DELETED,
}; };
...@@ -325,8 +326,14 @@ struct fc_host_attrs { ...@@ -325,8 +326,14 @@ struct fc_host_attrs {
struct list_head rport_bindings; struct list_head rport_bindings;
u32 next_rport_number; u32 next_rport_number;
u32 next_target_id; u32 next_target_id;
u8 flags;
struct work_struct rport_del_work;
}; };
/* values for struct fc_host_attrs "flags" field: */
#define FC_SHOST_RPORT_DEL_SCHEDULED 0x01
#define fc_host_node_name(x) \ #define fc_host_node_name(x) \
(((struct fc_host_attrs *)(x)->shost_data)->node_name) (((struct fc_host_attrs *)(x)->shost_data)->node_name)
#define fc_host_port_name(x) \ #define fc_host_port_name(x) \
...@@ -365,6 +372,10 @@ struct fc_host_attrs { ...@@ -365,6 +372,10 @@ struct fc_host_attrs {
(((struct fc_host_attrs *)(x)->shost_data)->next_rport_number) (((struct fc_host_attrs *)(x)->shost_data)->next_rport_number)
#define fc_host_next_target_id(x) \ #define fc_host_next_target_id(x) \
(((struct fc_host_attrs *)(x)->shost_data)->next_target_id) (((struct fc_host_attrs *)(x)->shost_data)->next_target_id)
#define fc_host_flags(x) \
(((struct fc_host_attrs *)(x)->shost_data)->flags)
#define fc_host_rport_del_work(x) \
(((struct fc_host_attrs *)(x)->shost_data)->rport_del_work)
/* The functions by which the transport class and the driver communicate */ /* The functions by which the transport class and the driver communicate */
......
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