[PATCH] scsi: libsas: handle linkrate change in sas_rediscover_dev
From: Xingui Yang
Date: Tue May 12 2026 - 03:14:20 EST
When a device attached to an expander phy experiences a linkrate change
(e.g., due to cable reconnection or negotiation), the current code in
sas_rediscover_dev() treats it as "broadcast flutter" and takes no action
if the SAS address and device type remain unchanged.
However, for drivers like hisi_sas, the ITCT entry needs to be updated to
reflect the new linkrate. Without this update, the hardware continues
using stale linkrate information, which can cause performance issues or
protocol errors.
This patch introduces a new LLDD callback lldd_dev_info_update() to notify
the low-level driver when a device's linkrate changes, allowing the driver
to update its hardware structures accordingly.
Additionally, refactor sas_ex_to_ata() to use a new helper function
sas_ex_to_dev() which returns any device type attached to an expander phy,
improving code reuse.
Signed-off-by: Xingui Yang <yangxingui@xxxxxxxxxx>
---
drivers/scsi/hisi_sas/hisi_sas_main.c | 16 ++++++++++++++++
drivers/scsi/libsas/sas_discover.c | 12 ++++++++++++
drivers/scsi/libsas/sas_expander.c | 25 +++++++++++++++++++------
drivers/scsi/libsas/sas_internal.h | 2 ++
include/scsi/libsas.h | 1 +
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/drivers/scsi/hisi_sas/hisi_sas_main.c b/drivers/scsi/hisi_sas/hisi_sas_main.c
index 944ce19ae2fc..459a71c3f8b0 100644
--- a/drivers/scsi/hisi_sas/hisi_sas_main.c
+++ b/drivers/scsi/hisi_sas/hisi_sas_main.c
@@ -900,6 +900,21 @@ static int hisi_sas_dev_found(struct domain_device *device)
return rc;
}
+static void hisi_sas_dev_info_update(struct domain_device *device)
+{
+ struct hisi_hba *hisi_hba = dev_to_hisi_hba(device);
+ struct hisi_sas_device *sas_dev = device->lldd_dev;
+ struct device *dev = hisi_hba->dev;
+
+ if (!sas_dev)
+ return;
+
+ dev_info(dev, "%016llx update itct\n",
+ SAS_ADDR(device->sas_addr));
+ hisi_hba->hw->clear_itct(hisi_hba, sas_dev);
+ hisi_hba->hw->setup_itct(hisi_hba, sas_dev);
+}
+
int hisi_sas_sdev_configure(struct scsi_device *sdev, struct queue_limits *lim)
{
struct domain_device *dev = sdev_to_domain_dev(sdev);
@@ -2168,6 +2183,7 @@ EXPORT_SYMBOL_GPL(hisi_sas_stt);
static struct sas_domain_function_template hisi_sas_transport_ops = {
.lldd_dev_found = hisi_sas_dev_found,
.lldd_dev_gone = hisi_sas_dev_gone,
+ .lldd_dev_info_update = hisi_sas_dev_info_update,
.lldd_execute_task = hisi_sas_queue_command,
.lldd_control_phy = hisi_sas_control_phy,
.lldd_abort_task = hisi_sas_abort_task,
diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c
index b07062db50b2..60be59c45508 100644
--- a/drivers/scsi/libsas/sas_discover.c
+++ b/drivers/scsi/libsas/sas_discover.c
@@ -204,6 +204,18 @@ void sas_notify_lldd_dev_gone(struct domain_device *dev)
}
}
+void sas_notify_lldd_dev_info_update(struct domain_device *dev)
+{
+ struct sas_ha_struct *sas_ha = dev->port->ha;
+ struct Scsi_Host *shost = sas_ha->shost;
+ struct sas_internal *i = to_sas_internal(shost->transportt);
+
+ if (!i->dft->lldd_dev_info_update)
+ return;
+
+ i->dft->lldd_dev_info_update(dev);
+}
+
static void sas_probe_devices(struct asd_sas_port *port)
{
struct domain_device *dev, *n;
diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c
index f471ab464a78..f1c6fdbcbe05 100644
--- a/drivers/scsi/libsas/sas_expander.c
+++ b/drivers/scsi/libsas/sas_expander.c
@@ -345,11 +345,9 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
SAS_ADDR(phy->attached_sas_addr), type);
}
-/* check if we have an existing attached ata device on this expander phy */
-struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id)
+struct domain_device *sas_ex_to_dev(struct domain_device *ex_dev, int phy_id)
{
struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy_id];
- struct domain_device *dev;
struct sas_rphy *rphy;
if (!ex_phy->port)
@@ -359,7 +357,13 @@ struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id)
if (!rphy)
return NULL;
- dev = sas_find_dev_by_rphy(rphy);
+ return sas_find_dev_by_rphy(rphy);
+}
+
+/* check if we have an existing attached ata device on this expander phy */
+struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id)
+{
+ struct domain_device *dev = sas_ex_to_dev(ex_dev, phy_id);
if (dev && dev_is_sata(dev))
return dev;
@@ -2013,13 +2017,22 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id,
goto out_free_resp;
} 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);
+ struct domain_device *child_dev = sas_ex_to_dev(dev, phy_id);
char *action = "";
sas_ex_phy_discover(dev, phy_id);
- if (ata_dev && phy->attached_dev_type == SAS_SATA_PENDING)
+ if (child_dev && dev_is_sata(child_dev) &&
+ phy->attached_dev_type == SAS_SATA_PENDING)
action = ", needs recovery";
+ else if (child_dev && child_dev->linkrate != phy->linkrate) {
+ pr_debug("ex %016llx phy%02d linkrate changed from %d to %d\n",
+ SAS_ADDR(dev->sas_addr), phy_id,
+ child_dev->linkrate, phy->linkrate);
+ child_dev->linkrate = phy->linkrate;
+ sas_notify_lldd_dev_info_update(child_dev);
+ }
+
pr_debug("ex %016llx phy%02d broadcast flutter%s\n",
SAS_ADDR(dev->sas_addr), phy_id, action);
goto out_free_resp;
diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h
index 7dce0f587149..9ee37b8abd78 100644
--- a/drivers/scsi/libsas/sas_internal.h
+++ b/drivers/scsi/libsas/sas_internal.h
@@ -82,6 +82,7 @@ bool sas_queue_work(struct sas_ha_struct *ha, struct sas_work *sw);
int sas_notify_lldd_dev_found(struct domain_device *);
void sas_notify_lldd_dev_gone(struct domain_device *);
+void sas_notify_lldd_dev_info_update(struct domain_device *dev);
void sas_smp_handler(struct bsg_job *job, struct Scsi_Host *shost,
struct sas_rphy *rphy);
@@ -91,6 +92,7 @@ int sas_smp_get_phy_events(struct sas_phy *phy);
void sas_device_set_phy(struct domain_device *dev, struct sas_port *port);
struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy);
+struct domain_device *sas_ex_to_dev(struct domain_device *ex_dev, int phy_id);
struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id);
int sas_ex_phy_discover(struct domain_device *dev, int single);
int sas_get_report_phy_sata(struct domain_device *dev, int phy_id,
diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h
index 163f23c92b41..973b4445b7e0 100644
--- a/include/scsi/libsas.h
+++ b/include/scsi/libsas.h
@@ -674,6 +674,7 @@ struct sas_domain_function_template {
/* GPIO support */
int (*lldd_write_gpio)(struct sas_ha_struct *, u8 reg_type,
u8 reg_index, u8 reg_count, u8 *write_data);
+ void (*lldd_dev_info_update)(struct domain_device *dev);
};
extern int sas_register_ha(struct sas_ha_struct *);
--
2.43.0