|
@@ -1580,9 +1580,9 @@ static void ata_eh_analyze_serror(struct ata_link *link)
|
|
|
* host links. For disabled PMP links, only N bit is
|
|
|
* considered as X bit is left at 1 for link plugging.
|
|
|
*/
|
|
|
- hotplug_mask = 0;
|
|
|
-
|
|
|
- if (!(link->flags & ATA_LFLAG_DISABLED) || ata_is_host_link(link))
|
|
|
+ if (link->lpm_policy != ATA_LPM_MAX_POWER)
|
|
|
+ hotplug_mask = 0; /* hotplug doesn't work w/ LPM */
|
|
|
+ else if (!(link->flags & ATA_LFLAG_DISABLED) || ata_is_host_link(link))
|
|
|
hotplug_mask = SERR_PHYRDY_CHG | SERR_DEV_XCHG;
|
|
|
else
|
|
|
hotplug_mask = SERR_PHYRDY_CHG;
|
|
@@ -2784,8 +2784,9 @@ int ata_eh_reset(struct ata_link *link, int classify,
|
|
|
ata_eh_done(link, NULL, ATA_EH_RESET);
|
|
|
if (slave)
|
|
|
ata_eh_done(slave, NULL, ATA_EH_RESET);
|
|
|
- ehc->last_reset = jiffies; /* update to completion time */
|
|
|
+ ehc->last_reset = jiffies; /* update to completion time */
|
|
|
ehc->i.action |= ATA_EH_REVALIDATE;
|
|
|
+ link->lpm_policy = ATA_LPM_UNKNOWN; /* reset LPM state */
|
|
|
|
|
|
rc = 0;
|
|
|
out:
|
|
@@ -3211,6 +3212,121 @@ static int ata_eh_maybe_retry_flush(struct ata_device *dev)
|
|
|
return rc;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * ata_eh_set_lpm - configure SATA interface power management
|
|
|
+ * @link: link to configure power management
|
|
|
+ * @policy: the link power management policy
|
|
|
+ * @r_failed_dev: out parameter for failed device
|
|
|
+ *
|
|
|
+ * Enable SATA Interface power management. This will enable
|
|
|
+ * Device Interface Power Management (DIPM) for min_power
|
|
|
+ * policy, and then call driver specific callbacks for
|
|
|
+ * enabling Host Initiated Power management.
|
|
|
+ *
|
|
|
+ * LOCKING:
|
|
|
+ * EH context.
|
|
|
+ *
|
|
|
+ * RETURNS:
|
|
|
+ * 0 on success, -errno on failure.
|
|
|
+ */
|
|
|
+static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
|
|
|
+ struct ata_device **r_failed_dev)
|
|
|
+{
|
|
|
+ struct ata_port *ap = link->ap;
|
|
|
+ struct ata_eh_context *ehc = &link->eh_context;
|
|
|
+ struct ata_device *dev, *link_dev = NULL, *lpm_dev = NULL;
|
|
|
+ unsigned int hints = ATA_LPM_EMPTY | ATA_LPM_HIPM;
|
|
|
+ unsigned int err_mask;
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ /* if the link or host doesn't do LPM, noop */
|
|
|
+ if ((link->flags & ATA_LFLAG_NO_LPM) || (ap && !ap->ops->set_lpm))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * DIPM is enabled only for MIN_POWER as some devices
|
|
|
+ * misbehave when the host NACKs transition to SLUMBER. Order
|
|
|
+ * device and link configurations such that the host always
|
|
|
+ * allows DIPM requests.
|
|
|
+ */
|
|
|
+ ata_for_each_dev(dev, link, ENABLED) {
|
|
|
+ bool hipm = ata_id_has_hipm(dev->id);
|
|
|
+ bool dipm = ata_id_has_dipm(dev->id);
|
|
|
+
|
|
|
+ /* find the first enabled and LPM enabled devices */
|
|
|
+ if (!link_dev)
|
|
|
+ link_dev = dev;
|
|
|
+
|
|
|
+ if (!lpm_dev && (hipm || dipm))
|
|
|
+ lpm_dev = dev;
|
|
|
+
|
|
|
+ hints &= ~ATA_LPM_EMPTY;
|
|
|
+ if (!hipm)
|
|
|
+ hints &= ~ATA_LPM_HIPM;
|
|
|
+
|
|
|
+ /* disable DIPM before changing link config */
|
|
|
+ if (policy != ATA_LPM_MIN_POWER && dipm) {
|
|
|
+ err_mask = ata_dev_set_feature(dev,
|
|
|
+ SETFEATURES_SATA_DISABLE, SATA_DIPM);
|
|
|
+ if (err_mask && err_mask != AC_ERR_DEV) {
|
|
|
+ ata_dev_printk(dev, KERN_WARNING,
|
|
|
+ "failed to disable DIPM, Emask 0x%x\n",
|
|
|
+ err_mask);
|
|
|
+ rc = -EIO;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ rc = ap->ops->set_lpm(link, policy, hints);
|
|
|
+ if (!rc && ap->slave_link)
|
|
|
+ rc = ap->ops->set_lpm(ap->slave_link, policy, hints);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Attribute link config failure to the first (LPM) enabled
|
|
|
+ * device on the link.
|
|
|
+ */
|
|
|
+ if (rc) {
|
|
|
+ if (rc == -EOPNOTSUPP) {
|
|
|
+ link->flags |= ATA_LFLAG_NO_LPM;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ dev = lpm_dev ? lpm_dev : link_dev;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* host config updated, enable DIPM if transitioning to MIN_POWER */
|
|
|
+ ata_for_each_dev(dev, link, ENABLED) {
|
|
|
+ if (policy == ATA_LPM_MIN_POWER && ata_id_has_dipm(dev->id)) {
|
|
|
+ err_mask = ata_dev_set_feature(dev,
|
|
|
+ SETFEATURES_SATA_ENABLE, SATA_DIPM);
|
|
|
+ if (err_mask && err_mask != AC_ERR_DEV) {
|
|
|
+ ata_dev_printk(dev, KERN_WARNING,
|
|
|
+ "failed to enable DIPM, Emask 0x%x\n",
|
|
|
+ err_mask);
|
|
|
+ rc = -EIO;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ link->lpm_policy = policy;
|
|
|
+ if (ap && ap->slave_link)
|
|
|
+ ap->slave_link->lpm_policy = policy;
|
|
|
+ return 0;
|
|
|
+
|
|
|
+fail:
|
|
|
+ /* if no device or only one more chance is left, disable LPM */
|
|
|
+ if (!dev || ehc->tries[dev->devno] <= 2) {
|
|
|
+ ata_link_printk(link, KERN_WARNING,
|
|
|
+ "disabling LPM on the link\n");
|
|
|
+ link->flags |= ATA_LFLAG_NO_LPM;
|
|
|
+ }
|
|
|
+ if (r_failed_dev)
|
|
|
+ *r_failed_dev = dev;
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
static int ata_link_nr_enabled(struct ata_link *link)
|
|
|
{
|
|
|
struct ata_device *dev;
|
|
@@ -3295,6 +3411,10 @@ static int ata_eh_schedule_probe(struct ata_device *dev)
|
|
|
ehc->saved_xfer_mode[dev->devno] = 0;
|
|
|
ehc->saved_ncq_enabled &= ~(1 << dev->devno);
|
|
|
|
|
|
+ /* the link maybe in a deep sleep, wake it up */
|
|
|
+ if (link->lpm_policy > ATA_LPM_MAX_POWER)
|
|
|
+ link->ap->ops->set_lpm(link, ATA_LPM_MAX_POWER, ATA_LPM_EMPTY);
|
|
|
+
|
|
|
/* Record and count probe trials on the ering. The specific
|
|
|
* error mask used is irrelevant. Because a successful device
|
|
|
* detection clears the ering, this count accumulates only if
|
|
@@ -3396,8 +3516,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
|
|
|
{
|
|
|
struct ata_link *link;
|
|
|
struct ata_device *dev;
|
|
|
- int nr_failed_devs;
|
|
|
- int rc;
|
|
|
+ int rc, nr_fails;
|
|
|
unsigned long flags, deadline;
|
|
|
|
|
|
DPRINTK("ENTER\n");
|
|
@@ -3438,7 +3557,6 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
|
|
|
|
|
|
retry:
|
|
|
rc = 0;
|
|
|
- nr_failed_devs = 0;
|
|
|
|
|
|
/* if UNLOADING, finish immediately */
|
|
|
if (ap->pflags & ATA_PFLAG_UNLOADING)
|
|
@@ -3523,13 +3641,17 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
|
|
|
}
|
|
|
|
|
|
/* the rest */
|
|
|
- ata_for_each_link(link, ap, EDGE) {
|
|
|
+ nr_fails = 0;
|
|
|
+ ata_for_each_link(link, ap, PMP_FIRST) {
|
|
|
struct ata_eh_context *ehc = &link->eh_context;
|
|
|
|
|
|
+ if (sata_pmp_attached(ap) && ata_is_host_link(link))
|
|
|
+ goto config_lpm;
|
|
|
+
|
|
|
/* revalidate existing devices and attach new ones */
|
|
|
rc = ata_eh_revalidate_and_attach(link, &dev);
|
|
|
if (rc)
|
|
|
- goto dev_fail;
|
|
|
+ goto rest_fail;
|
|
|
|
|
|
/* if PMP got attached, return, pmp EH will take care of it */
|
|
|
if (link->device->class == ATA_DEV_PMP) {
|
|
@@ -3541,7 +3663,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
|
|
|
if (ehc->i.flags & ATA_EHI_SETMODE) {
|
|
|
rc = ata_set_mode(link, &dev);
|
|
|
if (rc)
|
|
|
- goto dev_fail;
|
|
|
+ goto rest_fail;
|
|
|
ehc->i.flags &= ~ATA_EHI_SETMODE;
|
|
|
}
|
|
|
|
|
@@ -3554,7 +3676,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
|
|
|
continue;
|
|
|
rc = atapi_eh_clear_ua(dev);
|
|
|
if (rc)
|
|
|
- goto dev_fail;
|
|
|
+ goto rest_fail;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -3564,21 +3686,25 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
|
|
|
continue;
|
|
|
rc = ata_eh_maybe_retry_flush(dev);
|
|
|
if (rc)
|
|
|
- goto dev_fail;
|
|
|
+ goto rest_fail;
|
|
|
}
|
|
|
|
|
|
+ config_lpm:
|
|
|
/* configure link power saving */
|
|
|
- if (ehc->i.action & ATA_EH_LPM)
|
|
|
- ata_for_each_dev(dev, link, ALL)
|
|
|
- ata_dev_enable_pm(dev, ap->lpm_policy);
|
|
|
+ if (link->lpm_policy != ap->target_lpm_policy) {
|
|
|
+ rc = ata_eh_set_lpm(link, ap->target_lpm_policy, &dev);
|
|
|
+ if (rc)
|
|
|
+ goto rest_fail;
|
|
|
+ }
|
|
|
|
|
|
/* this link is okay now */
|
|
|
ehc->i.flags = 0;
|
|
|
continue;
|
|
|
|
|
|
-dev_fail:
|
|
|
- nr_failed_devs++;
|
|
|
- ata_eh_handle_dev_fail(dev, rc);
|
|
|
+ rest_fail:
|
|
|
+ nr_fails++;
|
|
|
+ if (dev)
|
|
|
+ ata_eh_handle_dev_fail(dev, rc);
|
|
|
|
|
|
if (ap->pflags & ATA_PFLAG_FROZEN) {
|
|
|
/* PMP reset requires working host port.
|
|
@@ -3590,7 +3716,7 @@ dev_fail:
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (nr_failed_devs)
|
|
|
+ if (nr_fails)
|
|
|
goto retry;
|
|
|
|
|
|
out:
|