diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c
index 110549030bcf..57b5446a3a21 100644
--- a/drivers/scsi/libsas/sas_discover.c
+++ b/drivers/scsi/libsas/sas_discover.c
@@ -164,6 +164,20 @@ static int sas_get_port_device(struct asd_sas_port *port)
 	return 0;
 }
 
+static void sas_check_port_linkrate(struct asd_sas_port *port)
+{
+	u32 link_rate = port->linkrate;
+	struct asd_sas_phy *phy;
+
+	list_for_each_entry(phy, &port->phy_list, port_phy_el)
+		link_rate = min(link_rate, phy->linkrate);
+
+	if (port->linkrate != link_rate) {
+		port->linkrate = link_rate;
+		sas_update_linkrate_root_expander(port->port_dev);
+	}
+}
+
 /* ---------- Discover and Revalidate ---------- */
 
 int sas_notify_lldd_dev_found(struct domain_device *dev)
@@ -434,8 +448,11 @@ static void sas_discover_domain(struct work_struct *work)
 
 	clear_bit(DISCE_DISCOVER_DOMAIN, &port->disc.pending);
 
-	if (port->port_dev)
+	if (port->port_dev) {
+		if (dev_is_expander(port->port_dev->dev_type))
+			sas_check_port_linkrate(port);
 		return;
+	}
 
 	error = sas_get_port_device(port);
 	if (error)
diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c
index 5ce251830104..6f067cc6f2e2 100644
--- a/drivers/scsi/libsas/sas_expander.c
+++ b/drivers/scsi/libsas/sas_expander.c
@@ -1951,6 +1951,75 @@ static int sas_discover_new(struct domain_device *dev, int phy_id)
 	return res;
 }
 
+static void sas_ex_update_linkrate(struct domain_device *parent)
+{
+	struct expander_device *ex = &parent->ex_dev;
+	int i = 0, end = ex->num_phys;
+
+	for ( ; i < end; i++) {
+		struct ex_phy *ex_phy = &ex->ex_phy[i];
+		struct domain_device *child;
+
+		list_for_each_entry(child, &parent->ex_dev.children, siblings)
+			if (SAS_ADDR(child->sas_addr) ==
+			    SAS_ADDR(ex_phy->attached_sas_addr))
+				break;
+
+		if (dev_is_sata(child)) {
+			if (child->linkrate > parent->min_linkrate) {
+				struct sas_phy_linkrates rates = {
+					.maximum_linkrate = parent->min_linkrate,
+					.minimum_linkrate = parent->min_linkrate,
+				};
+
+				sas_smp_phy_control(parent, i,
+						    PHY_FUNC_LINK_RESET, &rates);
+				ex_phy->phy_change_count = -1;
+			}
+		}
+
+		if (dev_is_expander(child->dev_type)) {
+			child->min_linkrate = min(parent->min_linkrate,
+						  ex_phy->linkrate);
+			child->max_linkrate = max(parent->max_linkrate,
+						  ex_phy->linkrate);
+			child->linkrate = min(ex_phy->linkrate,
+					      child->max_linkrate);
+			ex_phy->phy_change_count = -1;
+		}
+	}
+}
+
+static void sas_ex_level_update(struct asd_sas_port *port, const int level)
+{
+	struct domain_device *dev;
+
+	list_for_each_entry(dev, &port->dev_list, dev_list_node) {
+		if (dev_is_expander(dev->dev_type)) {
+			struct sas_expander_device *ex =
+				rphy_to_expander_device(dev->rphy);
+
+			if (level == ex->level)
+				sas_ex_update_linkrate(dev);
+		}
+	}
+}
+
+void sas_update_linkrate_root_expander(struct domain_device *dev)
+{
+	struct asd_sas_port *port = dev->port;
+	struct sas_expander_device *ex = rphy_to_expander_device(dev->rphy);
+	int level;
+
+	dev->linkrate = port->linkrate;
+	dev->min_linkrate = port->linkrate;
+	dev->max_linkrate = port->linkrate;
+	dev->ex_dev.ex_change_count = -1;
+
+	for (level = ex->level; level <= port->disc.max_level; level++)
+		sas_ex_level_update(port, level);
+}
+
 static bool dev_type_flutter(enum sas_device_type new, enum sas_device_type old)
 {
 	if (old == new)
@@ -2013,6 +2082,7 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id,
 	} else if (SAS_ADDR(sas_addr) == SAS_ADDR(phy->attached_sas_addr) &&
 		   dev_type_flutter(type, phy->attached_dev_type)) {
 		struct domain_device *ata_dev = sas_ex_to_ata(dev, phy_id);
+		enum sas_linkrate linkrate = phy->linkrate;
 		char *action = "";
 
 		sas_ex_phy_discover(dev, phy_id);
@@ -2021,6 +2091,15 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id,
 			action = ", needs recovery";
 		pr_debug("ex %016llx phy%02d broadcast flutter%s\n",
 			 SAS_ADDR(dev->sas_addr), phy_id, action);
+
+		if (linkrate != phy->linkrate) {
+			pr_debug("ex %016llx phy%d linkrate changed from %d to %d\n",
+				 SAS_ADDR(dev->sas_addr), phy_id,
+				 linkrate, phy->linkrate);
+			sas_unregister_devs_sas_addr(dev, phy_id, last);
+			phy->phy_change_count = -1;
+			ex->ex_change_count = -1;
+		}
 		return res;
 	}
 
diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h
index b54bcf3c9a9d..d26ef24370f6 100644
--- a/drivers/scsi/libsas/sas_internal.h
+++ b/drivers/scsi/libsas/sas_internal.h
@@ -48,6 +48,7 @@ int sas_show_oob_mode(enum sas_oob_mode oob_mode, char *buf);
 
 int  sas_register_phys(struct sas_ha_struct *sas_ha);
 void sas_unregister_phys(struct sas_ha_struct *sas_ha);
+void sas_update_linkrate_root_expander(struct domain_device *dev);
 
 struct asd_sas_event *sas_alloc_event(struct asd_sas_phy *phy, gfp_t gfp_flags);
 void sas_free_event(struct asd_sas_event *event);
