[PATCH v2 2/2] ata: Use ACPI methods to power on ata ports

Markus Probst
2025-10-09T11:24:51Z

Some embedded devices, including many Synology NAS devices, have the
ability to control whether a ATA port has power or not.

Add a new function, ata_acpi_dev_manage_restart(), that will be used to
determine if a disk should be stopped before restarting the system. If a
usable ACPI power resource has been found, it is assumed that the disk
will lose power after a restart and should be stopped to avoid a power
failure. Also add a new function, ata_acpi_port_set_power_state(), that
will be used to power on an ata port if usable ACPI power resources are
found. It will be called right before probing the port, therefore the port
will be powered on just in time.

Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
drivers/ata/libata-acpi.c | 70 +++++++++++++++++++++++++++++++++++++++
drivers/ata/libata-core.c | 2 ++
drivers/ata/libata-scsi.c | 1 +
drivers/ata/libata.h | 4 +++
4 files changed, 77 insertions(+)

diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c
index f2140fc06ba0..bba5ef49f055 100644
--- a/drivers/ata/libata-acpi.c
+++ b/drivers/ata/libata-acpi.c
@@ -245,6 +245,76 @@ void ata_acpi_bind_dev(struct ata_device *dev)
ata_acpi_dev_uevent);
}

+/**
+ * ata_acpi_dev_manage_restart - if the disk should be stopped (spin down) on
+ * system restart.
+ * @dev: target ATA device
+ *
+ * RETURNS:
+ * true if the disk should be stopped, otherwise false
+ */
+bool ata_acpi_dev_manage_restart(struct ata_device *dev)
+{
+ // If the device is power manageable and we assume the disk loses power
+ // on reboot.
+ if (dev->link->ap->flags & ATA_FLAG_ACPI_SATA) {
+ if (!is_acpi_device_node(dev->tdev.fwnode))
+ return 0;
+ return acpi_bus_power_manageable(ACPI_HANDLE(&dev->tdev));
+ }
+
+ if (!is_acpi_device_node(dev->link->ap->tdev.fwnode))
+ return 0;
+ return acpi_bus_power_manageable(ACPI_HANDLE(&dev->link->ap->tdev));
+}
+
+/**
+ * ata_acpi_port_set_power_state - set the power state of the ata port
+ * @ap: target ATA port
+ *
+ * This function is called at the beginning of ata_port_probe.
+ */
+void ata_acpi_port_set_power_state(struct ata_port *ap, bool enable)
+{
+ acpi_handle handle;
+ unsigned char state;
+ int i;
+
+ if (libata_noacpi)
+ return;
+
+ if (enable)
+ state = ACPI_STATE_D0;
+ else
+ state = ACPI_STATE_D3_COLD;
+
+ if (ap->flags & ATA_FLAG_ACPI_SATA) {
+ for (i = 0; i < ATA_MAX_DEVICES; i++) {
+ if (!is_acpi_device_node(ap->link.device[i].tdev.fwnode))
+ continue;
+ handle = ACPI_HANDLE(&ap->link.device[i].tdev);
+ if (!acpi_bus_power_manageable(handle))
+ continue;
+ if (!acpi_bus_set_power(handle, state))
+ ata_dev_dbg(&ap->link.device[i], "acpi: power was %s\n",
+ enable ? "en" : "dis");
+ else
+ ata_dev_err(&ap->link.device[i], "acpi: failed to set power state\n");
+ }
+ return;
+ }
+ if (!is_acpi_device_node(ap->tdev.fwnode))
+ return;
+ handle = ACPI_HANDLE(&ap->tdev);
+ if (!acpi_bus_power_manageable(handle))
+ return;
+
+ if (!acpi_bus_set_power(handle, state))
+ ata_port_dbg(ap, "acpi: power %sabled\n", enable ? "en" : "dis");
+ else
+ ata_port_err(ap, "acpi: failed to set power state\n");
+}
+
/**
* ata_acpi_dissociate - dissociate ATA host from ACPI objects
* @host: target ATA host
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index ff53f5f029b4..ee8b504596b2 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -5904,6 +5904,8 @@ void ata_port_probe(struct ata_port *ap)
struct ata_eh_info *ehi = &ap->link.eh_info;
unsigned long flags;

+ ata_acpi_port_set_power_state(ap, true);
+
/* kick EH for boot probing */
spin_lock_irqsave(ap->lock, flags);

diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index 2ded5e476d6e..12dd305afe26 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -1095,6 +1095,7 @@ int ata_scsi_dev_config(struct scsi_device *sdev, struct queue_limits *lim,
*/
sdev->manage_runtime_start_stop = 1;
sdev->manage_shutdown = 1;
+ sdev->manage_restart = ata_acpi_dev_manage_restart(dev);
sdev->force_runtime_start_on_system_start = 1;
}

diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h
index e5b977a8d3e1..c6daa2b1d15f 100644
--- a/drivers/ata/libata.h
+++ b/drivers/ata/libata.h
@@ -130,6 +130,8 @@ extern void ata_acpi_on_disable(struct ata_device *dev);
extern void ata_acpi_set_state(struct ata_port *ap, pm_message_t state);
extern void ata_acpi_bind_port(struct ata_port *ap);
extern void ata_acpi_bind_dev(struct ata_device *dev);
+extern void ata_acpi_port_set_power_state(struct ata_port *ap, bool enable);
+extern bool ata_acpi_dev_manage_restart(struct ata_device *dev);
extern acpi_handle ata_dev_acpi_handle(struct ata_device *dev);
#else
static inline void ata_acpi_dissociate(struct ata_host *host) { }
@@ -140,6 +142,8 @@ static inline void ata_acpi_set_state(struct ata_port *ap,
pm_message_t state) { }
static inline void ata_acpi_bind_port(struct ata_port *ap) {}
static inline void ata_acpi_bind_dev(struct ata_device *dev) {}
+static inline void ata_acpi_port_set_power_state(struct ata_port *ap, bool enable) {}
+static inline bool ata_acpi_dev_manage_restart(struct ata_device *dev) { return 0; }
#endif

/* libata-scsi.c */
--
2.49.1