ata: libata-sata: Add link_power_management_supported sysfs attribute

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: 413e800cad ("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>
This commit is contained in:
Damien Le Moal 2025-07-28 13:04:29 +09:00
parent d2be9ea9a7
commit 0060beec0b
4 changed files with 44 additions and 12 deletions

View File

@ -1089,6 +1089,7 @@ static struct ata_port_operations ich_pata_ops = {
}; };
static struct attribute *piix_sidpr_shost_attrs[] = { static struct attribute *piix_sidpr_shost_attrs[] = {
&dev_attr_link_power_management_supported.attr,
&dev_attr_link_power_management_policy.attr, &dev_attr_link_power_management_policy.attr,
NULL NULL
}; };

View 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 DEVICE_ATTR(em_message_supported, S_IRUGO, ahci_show_em_supported, NULL);
static struct attribute *ahci_shost_attrs[] = { static struct attribute *ahci_shost_attrs[] = {
&dev_attr_link_power_management_supported.attr,
&dev_attr_link_power_management_policy.attr, &dev_attr_link_power_management_policy.attr,
&dev_attr_em_message_type.attr, &dev_attr_em_message_type.attr,
&dev_attr_em_message.attr, &dev_attr_em_message.attr,

View File

@ -900,14 +900,52 @@ static const char *ata_lpm_policy_names[] = {
[ATA_LPM_MIN_POWER] = "min_power", [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, static ssize_t ata_scsi_lpm_store(struct device *device,
struct device_attribute *attr, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
struct Scsi_Host *shost = class_to_shost(device); struct Scsi_Host *shost = class_to_shost(device);
struct ata_port *ap = ata_shost_to_port(shost); struct ata_port *ap = ata_shost_to_port(shost);
struct ata_link *link;
struct ata_device *dev;
enum ata_lpm_policy policy; enum ata_lpm_policy policy;
unsigned long flags; unsigned long flags;
@ -924,20 +962,11 @@ static ssize_t ata_scsi_lpm_store(struct device *device,
spin_lock_irqsave(ap->lock, flags); spin_lock_irqsave(ap->lock, flags);
if (ap->flags & ATA_FLAG_NO_LPM) { if (!ata_scsi_lpm_supported(ap)) {
count = -EOPNOTSUPP; count = -EOPNOTSUPP;
goto out_unlock; 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; ap->target_lpm_policy = policy;
ata_port_schedule_eh(ap); ata_port_schedule_eh(ap);
out_unlock: out_unlock:

View 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; extern struct device_attribute dev_attr_unload_heads;
#ifdef CONFIG_SATA_HOST #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_link_power_management_policy;
extern struct device_attribute dev_attr_ncq_prio_supported; extern struct device_attribute dev_attr_ncq_prio_supported;
extern struct device_attribute dev_attr_ncq_prio_enable; extern struct device_attribute dev_attr_ncq_prio_enable;