]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ata: libata-sata: Add link_power_management_supported sysfs attribute
authorDamien Le Moal <dlemoal@kernel.org>
Mon, 28 Jul 2025 04:04:29 +0000 (13:04 +0900)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 20 Aug 2025 16:41:43 +0000 (18:41 +0200)
commit 0060beec0bfa647c4b510df188b1c4673a197839 upstream.

A port link power management (LPM) policy can be controlled using the
link_power_management_policy sysfs host attribute. However, this
attribute exists also for hosts that do not support LPM and in such
case, attempting to change the LPM policy for the host (port) will fail
with -EOPNOTSUPP.

Introduce the new sysfs link_power_management_supported host attribute
to indicate to the user if a the port and the devices connected to the
port for the host support LPM, which implies that the
link_power_management_policy attribute can be used.

Since checking that a port and its devices support LPM is common between
the new ata_scsi_lpm_supported_show() function and the existing
ata_scsi_lpm_store() function, the new helper ata_scsi_lpm_supported()
is introduced.

Fixes: 413e800cadbf ("ata: libata-sata: Disallow changing LPM state if not supported")
Reported-by: Borah, Chaitanya Kumar <chaitanya.kumar.borah@intel.com>
Reported-by: kernel test robot <oliver.sang@intel.com>
Closes: https://lore.kernel.org/oe-lkp/202507251014.a5becc3b-lkp@intel.com
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/ata/ata_piix.c
drivers/ata/libahci.c
drivers/ata/libata-sata.c
include/linux/libata.h

index d441246fa357a162bf21604116b419c2791df55c..6e19ae97e6c87fba672cd1ff22e8bd53d9752f97 100644 (file)
@@ -1089,6 +1089,7 @@ static struct ata_port_operations ich_pata_ops = {
 };
 
 static struct attribute *piix_sidpr_shost_attrs[] = {
+       &dev_attr_link_power_management_supported.attr,
        &dev_attr_link_power_management_policy.attr,
        NULL
 };
index 4e9c82f36df17f37e17c5e7704f8477ebaa71ed3..41130abe90c4c20bb7e7adcc507fb665f1c78fdc 100644 (file)
@@ -111,6 +111,7 @@ static DEVICE_ATTR(em_buffer, S_IWUSR | S_IRUGO,
 static DEVICE_ATTR(em_message_supported, S_IRUGO, ahci_show_em_supported, NULL);
 
 static struct attribute *ahci_shost_attrs[] = {
+       &dev_attr_link_power_management_supported.attr,
        &dev_attr_link_power_management_policy.attr,
        &dev_attr_em_message_type.attr,
        &dev_attr_em_message.attr,
index 47169c469f437bf29016e8e125e63f1146b5d070..3d5cb38d8557d46d0e6d56f4d54612a011ebec6b 100644 (file)
@@ -900,14 +900,52 @@ static const char *ata_lpm_policy_names[] = {
        [ATA_LPM_MIN_POWER]             = "min_power",
 };
 
+/*
+ * Check if a port supports link power management.
+ * Must be called with the port locked.
+ */
+static bool ata_scsi_lpm_supported(struct ata_port *ap)
+{
+       struct ata_link *link;
+       struct ata_device *dev;
+
+       if (ap->flags & ATA_FLAG_NO_LPM)
+               return false;
+
+       ata_for_each_link(link, ap, EDGE) {
+               ata_for_each_dev(dev, &ap->link, ENABLED) {
+                       if (dev->quirks & ATA_QUIRK_NOLPM)
+                               return false;
+               }
+       }
+
+       return true;
+}
+
+static ssize_t ata_scsi_lpm_supported_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct Scsi_Host *shost = class_to_shost(dev);
+       struct ata_port *ap = ata_shost_to_port(shost);
+       unsigned long flags;
+       bool supported;
+
+       spin_lock_irqsave(ap->lock, flags);
+       supported = ata_scsi_lpm_supported(ap);
+       spin_unlock_irqrestore(ap->lock, flags);
+
+       return sysfs_emit(buf, "%d\n", supported);
+}
+DEVICE_ATTR(link_power_management_supported, S_IRUGO,
+           ata_scsi_lpm_supported_show, NULL);
+EXPORT_SYMBOL_GPL(dev_attr_link_power_management_supported);
+
 static ssize_t ata_scsi_lpm_store(struct device *device,
                                  struct device_attribute *attr,
                                  const char *buf, size_t count)
 {
        struct Scsi_Host *shost = class_to_shost(device);
        struct ata_port *ap = ata_shost_to_port(shost);
-       struct ata_link *link;
-       struct ata_device *dev;
        enum ata_lpm_policy policy;
        unsigned long flags;
 
@@ -924,20 +962,11 @@ static ssize_t ata_scsi_lpm_store(struct device *device,
 
        spin_lock_irqsave(ap->lock, flags);
 
-       if (ap->flags & ATA_FLAG_NO_LPM) {
+       if (!ata_scsi_lpm_supported(ap)) {
                count = -EOPNOTSUPP;
                goto out_unlock;
        }
 
-       ata_for_each_link(link, ap, EDGE) {
-               ata_for_each_dev(dev, &ap->link, ENABLED) {
-                       if (dev->quirks & ATA_QUIRK_NOLPM) {
-                               count = -EOPNOTSUPP;
-                               goto out_unlock;
-                       }
-               }
-       }
-
        ap->target_lpm_policy = policy;
        ata_port_schedule_eh(ap);
 out_unlock:
index 1e5aec839041de78b0b37ee4f9f7c6483911402b..e9d87cb1dbb7576c7ee172edce19644eae468dfa 100644 (file)
@@ -534,6 +534,7 @@ typedef void (*ata_postreset_fn_t)(struct ata_link *link, unsigned int *classes)
 
 extern struct device_attribute dev_attr_unload_heads;
 #ifdef CONFIG_SATA_HOST
+extern struct device_attribute dev_attr_link_power_management_supported;
 extern struct device_attribute dev_attr_link_power_management_policy;
 extern struct device_attribute dev_attr_ncq_prio_supported;
 extern struct device_attribute dev_attr_ncq_prio_enable;