aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/ata
diff options
context:
space:
mode:
authorNiklas Cassel <niklas.cassel@wdc.com>2022-09-21 17:57:52 +0200
committerDamien Le Moal <damien.lemoal@opensource.wdc.com>2022-09-24 15:31:00 +0900
commit71d7b6e51ad3370d850303b61b79528fb2872f0a (patch)
treeba8af8d436731e514cfc37c4bae1c3b684007210 /drivers/ata
parentata: libata: drop superfluous ata_eh_analyze_tf() parameter (diff)
downloadlinux-dev-71d7b6e51ad3370d850303b61b79528fb2872f0a.tar.xz
linux-dev-71d7b6e51ad3370d850303b61b79528fb2872f0a.zip
ata: libata-eh: avoid needless hard reset when revalidating link
Performing a revalidation on a AHCI controller supporting LPM, while using a lpm mode of e.g. med_power_with_dip (hipm + dipm) or medium_power (hipm), will currently always lead to a hard reset. The expected behavior is that a hard reset is only performed when revalidate fails, because the properties of the drive has changed. A revalidate performed after e.g. a NCQ error, or such a simple thing as disabling write-caching (hdparm -W 0 /dev/sda), should succeed on the first try (and should therefore not cause the link to be reset). This unwarranted hard reset happens because ata_phys_link_offline() returns true for a link that is in deep sleep. Thus the call to ata_phys_link_offline() in ata_eh_revalidate_and_attach() will cause the revalidation to fail, which causes ata_eh_handle_dev_fail() to be called, which will set ehc->i.action |= ATA_EH_RESET, such that the link is reset before retrying revalidation. When the link is reset, the link is reestablished, so when ata_eh_revalidate_and_attach() is called the second time, directly after the link has been reset, ata_phys_link_offline() will return false, and the revalidation will succeed. Looking at "8.3.1.3 HBA Initiated" in the AHCI 1.3.1 specification, it is clear the when host software writes a new command to memory, by setting a bit in the PxCI/PxSACT HBA port registers, the HBA will automatically bring back the link before sending out the Command FIS. However, simply reading a SCR (like ata_phys_link_offline() does), will not cause the HBA to automatically bring back the link. As long as hipm is enabled, the HBA will put an idle link into deep sleep. Avoid this needless hard reset on revalidation by temporarily disabling hipm, by setting the LPM mode to ATA_LPM_MAX_POWER. After revalidation is complete, ata_eh_recover() will restore the link policy by setting the LPM mode to ap->target_lpm_policy. Signed-off-by: Niklas Cassel <niklas.cassel@wdc.com> Signed-off-by: Damien Le Moal <damien.lemoal@opensource.wdc.com>
Diffstat (limited to 'drivers/ata')
-rw-r--r--drivers/ata/libata-eh.c19
1 files changed, 19 insertions, 0 deletions
diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
index 6432e0489dfb..1b82283f4b49 100644
--- a/drivers/ata/libata-eh.c
+++ b/drivers/ata/libata-eh.c
@@ -151,6 +151,8 @@ ata_eh_cmd_timeout_table[ATA_EH_CMD_TIMEOUT_TABLE_SIZE] = {
#undef CMDS
static void __ata_port_freeze(struct ata_port *ap);
+static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
+ struct ata_device **r_failed_dev);
#ifdef CONFIG_PM
static void ata_eh_handle_port_suspend(struct ata_port *ap);
static void ata_eh_handle_port_resume(struct ata_port *ap);
@@ -2934,6 +2936,23 @@ static int ata_eh_revalidate_and_attach(struct ata_link *link,
if ((action & ATA_EH_REVALIDATE) && ata_dev_enabled(dev)) {
WARN_ON(dev->class == ATA_DEV_PMP);
+ /*
+ * The link may be in a deep sleep, wake it up.
+ *
+ * If the link is in deep sleep, ata_phys_link_offline()
+ * will return true, causing the revalidation to fail,
+ * which leads to a (potentially) needless hard reset.
+ *
+ * ata_eh_recover() will later restore the link policy
+ * to ap->target_lpm_policy after revalidation is done.
+ */
+ if (link->lpm_policy > ATA_LPM_MAX_POWER) {
+ rc = ata_eh_set_lpm(link, ATA_LPM_MAX_POWER,
+ r_failed_dev);
+ if (rc)
+ goto err;
+ }
+
if (ata_phys_link_offline(ata_dev_phys_link(dev))) {
rc = -EIO;
goto err;