]> git.ipfire.org Git - thirdparty/kernel/linux.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)
committerDamien Le Moal <dlemoal@kernel.org>
Thu, 31 Jul 2025 03:56:21 +0000 (12:56 +0900)
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>
drivers/ata/ata_piix.c
drivers/ata/libahci.c
drivers/ata/libata-sata.c
include/linux/libata.h

index 229429ba5027b4d06ba04d477e932fd1081c5c0a..495fa096dd653a9a3c8f5edf78d5b77582e82290 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 b335fb7e5cb4c0edff22f1f4c4c38868a41df008..c79abdfcd7a9b05e05259f82cbb91df63dcc0ee2 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 4734465d3b1ef8e73168971bd617abd4a909f3cc..b2817a2995d6badb353f2dc36e8015339720fabe 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 912ace523880035cbc00aa4bcb56f77297a1afd0..0620dd67369f3359c61738fb2c37063e65ced635 100644 (file)
@@ -545,6 +545,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;