isci: filter broadcast change notifications during SMP phy resets
When resetting a sata device in the domain we have seen occasions where
libsas prematurely marks a device gone in the time it takes for the
device to re-establish the link. This plays badly with software raid
arrays. Other libsas drivers have non-uniform delays in their reset
handlers to try to cover this condition, but not sufficient to close the
hole. Given that a sata device can take many seconds to recover we
filter bcns and poll for the device reattach state before notifying
libsas that the port needs the domain to be rediscovered. Once this has
been proven out at the lldd level we can think about uplevelling this
feature to a common implementation in libsas.
Signed-off-by: Jeff Skirvin <jeffrey.d.skirvin@intel.com>
[ use kzalloc instead of kmem_cache ]
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
[ use eventq and time macros ]
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
diff --git a/drivers/scsi/isci/port.c b/drivers/scsi/isci/port.c
index 74f06f3..2946eee 100644
--- a/drivers/scsi/isci/port.c
+++ b/drivers/scsi/isci/port.c
@@ -152,6 +152,71 @@
return SCI_SUCCESS;
}
+static void scic_port_bcn_enable(struct scic_sds_port *sci_port)
+{
+ struct scic_sds_phy *sci_phy;
+ u32 val;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sci_port->phy_table); i++) {
+ sci_phy = sci_port->phy_table[i];
+ if (!sci_phy)
+ continue;
+ val = readl(&sci_phy->link_layer_registers->link_layer_control);
+ /* clear the bit by writing 1. */
+ writel(val, &sci_phy->link_layer_registers->link_layer_control);
+ }
+}
+
+/* called under scic_lock to stabilize phy:port associations */
+void isci_port_bcn_enable(struct isci_host *ihost, struct isci_port *iport)
+{
+ int i;
+
+ clear_bit(IPORT_BCN_BLOCKED, &iport->flags);
+ wake_up(&ihost->eventq);
+
+ if (!test_and_clear_bit(IPORT_BCN_PENDING, &iport->flags))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(iport->sci.phy_table); i++) {
+ struct scic_sds_phy *sci_phy = iport->sci.phy_table[i];
+ struct isci_phy *iphy = sci_phy_to_iphy(sci_phy);
+
+ if (!sci_phy)
+ continue;
+
+ ihost->sas_ha.notify_port_event(&iphy->sas_phy,
+ PORTE_BROADCAST_RCVD);
+ break;
+ }
+}
+
+void isci_port_bc_change_received(struct isci_host *ihost,
+ struct scic_sds_port *sci_port,
+ struct scic_sds_phy *sci_phy)
+{
+ struct isci_phy *iphy = sci_phy_to_iphy(sci_phy);
+ struct isci_port *iport = iphy->isci_port;
+
+ if (iport && test_bit(IPORT_BCN_BLOCKED, &iport->flags)) {
+ dev_dbg(&ihost->pdev->dev,
+ "%s: disabled BCN; isci_phy = %p, sas_phy = %p\n",
+ __func__, iphy, &iphy->sas_phy);
+ set_bit(IPORT_BCN_PENDING, &iport->flags);
+ atomic_inc(&iport->event);
+ wake_up(&ihost->eventq);
+ } else {
+ dev_dbg(&ihost->pdev->dev,
+ "%s: isci_phy = %p, sas_phy = %p\n",
+ __func__, iphy, &iphy->sas_phy);
+
+ ihost->sas_ha.notify_port_event(&iphy->sas_phy,
+ PORTE_BROADCAST_RCVD);
+ }
+ scic_port_bcn_enable(sci_port);
+}
+
static void isci_port_link_up(struct isci_host *isci_host,
struct scic_sds_port *port,
struct scic_sds_phy *phy)
@@ -240,13 +305,15 @@
if (isci_port) {
/* check to see if this is the last phy on this port. */
- if (isci_phy->sas_phy.port
- && isci_phy->sas_phy.port->num_phys == 1) {
+ if (isci_phy->sas_phy.port &&
+ isci_phy->sas_phy.port->num_phys == 1) {
+ atomic_inc(&isci_port->event);
+ isci_port_bcn_enable(isci_host, isci_port);
- /* change the state for all devices on this port.
- * The next task sent to this device will be returned
- * as SAS_TASK_UNDELIVERED, and the scsi mid layer
- * will remove the target
+ /* change the state for all devices on this port. The
+ * next task sent to this device will be returned as
+ * SAS_TASK_UNDELIVERED, and the scsi mid layer will
+ * remove the target
*/
list_for_each_entry(isci_device,
&isci_port->remote_dev_list,
@@ -1033,26 +1100,6 @@
return max_allowed_speed;
}
-static void scic_port_enable_broadcast_change_notification(struct scic_sds_port *port)
-{
- struct scic_sds_phy *phy;
- u32 register_value;
- u8 index;
-
- /* Loop through all of the phys to enable BCN. */
- for (index = 0; index < SCI_MAX_PHYS; index++) {
- phy = port->phy_table[index];
- if (phy != NULL) {
- register_value =
- readl(&phy->link_layer_registers->link_layer_control);
-
- /* clear the bit by writing 1. */
- writel(register_value,
- &phy->link_layer_registers->link_layer_control);
- }
- }
-}
-
/**
*
* @sci_port: This is the struct scic_sds_port object to suspend.
@@ -1838,6 +1885,7 @@
init_completion(&iport->start_complete);
iport->isci_host = ihost;
isci_port_change_state(iport, isci_freed);
+ atomic_set(&iport->event, 0);
}
/**
@@ -1852,19 +1900,6 @@
return isci_port->status;
}
-static void isci_port_bc_change_received(struct isci_host *ihost,
- struct scic_sds_port *sci_port,
- struct scic_sds_phy *sci_phy)
-{
- struct isci_phy *iphy = sci_phy_to_iphy(sci_phy);
-
- dev_dbg(&ihost->pdev->dev, "%s: iphy = %p, sas_phy = %p\n",
- __func__, iphy, &iphy->sas_phy);
-
- ihost->sas_ha.notify_port_event(&iphy->sas_phy, PORTE_BROADCAST_RCVD);
- scic_port_enable_broadcast_change_notification(sci_port);
-}
-
void scic_sds_port_broadcast_change_received(
struct scic_sds_port *sci_port,
struct scic_sds_phy *sci_phy)