diff options
author | Tejun Heo <tj@kernel.org> | 2010-09-01 17:50:06 +0200 |
---|---|---|
committer | Jeff Garzik <jgarzik@redhat.com> | 2010-10-21 20:21:04 -0400 |
commit | 6b7ae9545ad9875a289f4191c0216b473e313cb9 (patch) | |
tree | 216b4db276202d727ba134d256144a6670497180 /drivers/ata | |
parent | 1152b2617a6e1943b6b82e07c962950e56f1000c (diff) |
libata: reimplement link power management
The current LPM implementation has the following issues.
* Operation order isn't well thought-out. e.g. HIPM should be
configured after IPM in SControl is properly configured. Not the
other way around.
* Suspend/resume paths call ata_lpm_enable/disable() which must only
be called from EH context directly. Also, ata_lpm_enable/disable()
were called whether LPM was in use or not.
* Implementation is per-port when it should be per-link. As a result,
it can't be used for controllers with slave links or PMP.
* LPM state isn't managed consistently. After a link reset for
whatever reason including suspend/resume the actual LPM state would
be reset leaving ap->lpm_policy inconsistent.
* Generic/driver-specific logic boundary isn't clear. Currently,
libahci has to mangle stuff which libata EH proper should be
handling. This makes the implementation unnecessarily complex and
fragile.
* Tied to ALPM. Doesn't consider DIPM only cases and doesn't check
whether the device allows HIPM.
* Error handling isn't implemented.
Given the extent of mismatch with the rest of libata, I don't think
trying to fix it piecewise makes much sense. This patch reimplements
LPM support.
* The new implementation is per-link. The target policy is still
port-wide (ap->target_lpm_policy) but all the mechanisms and states
are per-link and integrate well with the rest of link abstraction
and can work with slave and PMP links.
* Core EH has proper control of LPM state. LPM state is reconfigured
when and only when reconfiguration is necessary. It makes sure that
LPM state is reset when probing for new device on the link.
Controller agnostic logic is now implemented in libata EH proper and
driver implementation only has to deal with controller specifics.
* Proper error handling. LPM config failure is attributed to the
device on the link and LPM is disabled for the link if it fails
repeatedly.
* ops->enable/disable_pm() are replaced with single ops->set_lpm()
which takes @policy and @hints. This simplifies driver specific
implementation.
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
Diffstat (limited to 'drivers/ata')
-rw-r--r-- | drivers/ata/ahci.c | 3 | ||||
-rw-r--r-- | drivers/ata/ahci.h | 1 | ||||
-rw-r--r-- | drivers/ata/ahci_platform.c | 3 | ||||
-rw-r--r-- | drivers/ata/libahci.c | 158 | ||||
-rw-r--r-- | drivers/ata/libata-core.c | 201 | ||||
-rw-r--r-- | drivers/ata/libata-eh.c | 164 | ||||
-rw-r--r-- | drivers/ata/libata-scsi.c | 11 | ||||
-rw-r--r-- | drivers/ata/libata.h | 4 |
8 files changed, 195 insertions, 350 deletions
diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index 68f7b4216501..328826381a2d 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -1208,9 +1208,6 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) ata_port_pbar_desc(ap, AHCI_PCI_BAR, 0x100 + ap->port_no * 0x80, "port"); - /* set initial link pm policy */ - ap->lpm_policy = ATA_LPM_UNKNOWN; - /* set enclosure management message type */ if (ap->flags & ATA_FLAG_EM) ap->em_message_type = hpriv->em_msg_type; diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h index 93867d3f8dd3..1a2aacfdebc5 100644 --- a/drivers/ata/ahci.h +++ b/drivers/ata/ahci.h @@ -201,7 +201,6 @@ enum { AHCI_HFLAG_MV_PATA = (1 << 4), /* PATA port */ AHCI_HFLAG_NO_MSI = (1 << 5), /* no PCI MSI */ AHCI_HFLAG_NO_PMP = (1 << 6), /* no PMP */ - AHCI_HFLAG_NO_HOTPLUG = (1 << 7), /* ignore PxSERR.DIAG.N */ AHCI_HFLAG_SECT255 = (1 << 8), /* max 255 sectors */ AHCI_HFLAG_YES_NCQ = (1 << 9), /* force NCQ cap on */ AHCI_HFLAG_NO_SUSPEND = (1 << 10), /* don't suspend */ diff --git a/drivers/ata/ahci_platform.c b/drivers/ata/ahci_platform.c index 07556853c0d6..6fef1fa75c54 100644 --- a/drivers/ata/ahci_platform.c +++ b/drivers/ata/ahci_platform.c @@ -129,9 +129,6 @@ static int __init ahci_probe(struct platform_device *pdev) ata_port_desc(ap, "mmio %pR", mem); ata_port_desc(ap, "port 0x%x", 0x100 + ap->port_no * 0x80); - /* set initial link pm policy */ - ap->lpm_policy = ATA_LPM_UNKNOWN; - /* set enclosure management message type */ if (ap->flags & ATA_FLAG_EM) ap->em_message_type = hpriv->em_msg_type; diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index ed7803f2b4f1..437f92597788 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -56,8 +56,8 @@ MODULE_PARM_DESC(skip_host_reset, "skip global host reset (0=don't skip, 1=skip) module_param_named(ignore_sss, ahci_ignore_sss, int, 0444); MODULE_PARM_DESC(ignore_sss, "Ignore staggered spinup flag (0=don't ignore, 1=ignore)"); -static int ahci_enable_alpm(struct ata_port *ap, enum ata_lpm_policy policy); -static void ahci_disable_alpm(struct ata_port *ap); +static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, + unsigned hints); static ssize_t ahci_led_show(struct ata_port *ap, char *buf); static ssize_t ahci_led_store(struct ata_port *ap, const char *buf, size_t size); @@ -163,8 +163,7 @@ struct ata_port_operations ahci_ops = { .pmp_attach = ahci_pmp_attach, .pmp_detach = ahci_pmp_detach, - .enable_pm = ahci_enable_alpm, - .disable_pm = ahci_disable_alpm, + .set_lpm = ahci_set_lpm, .em_show = ahci_led_show, .em_store = ahci_led_store, .sw_activity_show = ahci_activity_show, @@ -641,126 +640,56 @@ static void ahci_power_up(struct ata_port *ap) writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD); } -static void ahci_disable_alpm(struct ata_port *ap) +static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, + unsigned int hints) { + struct ata_port *ap = link->ap; struct ahci_host_priv *hpriv = ap->host->private_data; - void __iomem *port_mmio = ahci_port_base(ap); - u32 cmd; struct ahci_port_priv *pp = ap->private_data; - - /* LPM bits should be disabled by libata-core */ - /* get the existing command bits */ - cmd = readl(port_mmio + PORT_CMD); - - /* disable ALPM and ASP */ - cmd &= ~PORT_CMD_ASP; - cmd &= ~PORT_CMD_ALPE; - - /* force the interface back to active */ - cmd |= PORT_CMD_ICC_ACTIVE; - - /* write out new cmd value */ - writel(cmd, port_mmio + PORT_CMD); - cmd = readl(port_mmio + PORT_CMD); - - /* wait 10ms to be sure we've come out of any low power state */ - msleep(10); - - /* clear out any PhyRdy stuff from interrupt status */ - writel(PORT_IRQ_PHYRDY, port_mmio + PORT_IRQ_STAT); - - /* go ahead and clean out PhyRdy Change from Serror too */ - ahci_scr_write(&ap->link, SCR_ERROR, ((1 << 16) | (1 << 18))); - - /* - * Clear flag to indicate that we should ignore all PhyRdy - * state changes - */ - hpriv->flags &= ~AHCI_HFLAG_NO_HOTPLUG; - - /* - * Enable interrupts on Phy Ready. - */ - pp->intr_mask |= PORT_IRQ_PHYRDY; - writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); - - /* - * don't change the link pm policy - we can be called - * just to turn of link pm temporarily - */ -} - -static int ahci_enable_alpm(struct ata_port *ap, enum ata_lpm_policy policy) -{ - struct ahci_host_priv *hpriv = ap->host->private_data; void __iomem *port_mmio = ahci_port_base(ap); - u32 cmd; - struct ahci_port_priv *pp = ap->private_data; - u32 asp; - - /* Make sure the host is capable of link power management */ - if (!(hpriv->cap & HOST_CAP_ALPM)) - return -EINVAL; - switch (policy) { - case ATA_LPM_MAX_POWER: - case ATA_LPM_UNKNOWN: + if (policy != ATA_LPM_MAX_POWER) { /* - * if we came here with ATA_LPM_UNKNOWN, - * it just means this is the first time we - * have tried to enable - default to max performance, - * and let the user go to lower power modes on request. + * Disable interrupts on Phy Ready. This keeps us from + * getting woken up due to spurious phy ready + * interrupts. */ - ahci_disable_alpm(ap); - return 0; - case ATA_LPM_MIN_POWER: - /* configure HBA to enter SLUMBER */ - asp = PORT_CMD_ASP; - break; - case ATA_LPM_MED_POWER: - /* configure HBA to enter PARTIAL */ - asp = 0; - break; - default: - return -EINVAL; + pp->intr_mask &= ~PORT_IRQ_PHYRDY; + writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); + + sata_link_scr_lpm(link, policy, false); } - /* - * Disable interrupts on Phy Ready. This keeps us from - * getting woken up due to spurious phy ready interrupts - * TBD - Hot plug should be done via polling now, is - * that even supported? - */ - pp->intr_mask &= ~PORT_IRQ_PHYRDY; - writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); + if (hpriv->cap & HOST_CAP_ALPM) { + u32 cmd = readl(port_mmio + PORT_CMD); - /* - * Set a flag to indicate that we should ignore all PhyRdy - * state changes since these can happen now whenever we - * change link state - */ - hpriv->flags |= AHCI_HFLAG_NO_HOTPLUG; + if (policy == ATA_LPM_MAX_POWER || !(hints & ATA_LPM_HIPM)) { + cmd &= ~(PORT_CMD_ASP | PORT_CMD_ALPE); + cmd |= PORT_CMD_ICC_ACTIVE; - /* get the existing command bits */ - cmd = readl(port_mmio + PORT_CMD); + writel(cmd, port_mmio + PORT_CMD); + readl(port_mmio + PORT_CMD); - /* - * Set ASP based on Policy - */ - cmd |= asp; + /* wait 10ms to be sure we've come out of LPM state */ + msleep(10); + } else { + cmd |= PORT_CMD_ALPE; + if (policy == ATA_LPM_MIN_POWER) + cmd |= PORT_CMD_ASP; - /* - * Setting this bit will instruct the HBA to aggressively - * enter a lower power link state when it's appropriate and - * based on the value set above for ASP - */ - cmd |= PORT_CMD_ALPE; + /* write out new cmd value */ + writel(cmd, port_mmio + PORT_CMD); + } + } - /* write out new cmd value */ - writel(cmd, port_mmio + PORT_CMD); - cmd = readl(port_mmio + PORT_CMD); + if (policy == ATA_LPM_MAX_POWER) { + sata_link_scr_lpm(link, policy, false); + + /* turn PHYRDY IRQ back on */ + pp->intr_mask |= PORT_IRQ_PHYRDY; + writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); + } - /* LPM bits should be set by libata-core */ return 0; } @@ -1658,15 +1587,10 @@ static void ahci_port_intr(struct ata_port *ap) if (unlikely(resetting)) status &= ~PORT_IRQ_BAD_PMP; - /* If we are getting PhyRdy, this is - * just a power state change, we should - * clear out this, plus the PhyRdy/Comm - * Wake bits from Serror - */ - if ((hpriv->flags & AHCI_HFLAG_NO_HOTPLUG) && - (status & PORT_IRQ_PHYRDY)) { + /* if LPM is enabled, PHYRDY doesn't mean anything */ + if (ap->link.lpm_policy > ATA_LPM_MAX_POWER) { status &= ~PORT_IRQ_PHYRDY; - ahci_scr_write(&ap->link, SCR_ERROR, ((1 << 16) | (1 << 18))); + ahci_scr_write(&ap->link, SCR_ERROR, SERR_PHYRDY_CHG); } if (unlikely(status & PORT_IRQ_ERROR)) { diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index b8024451234c..7c5538b9fa3b 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -1028,182 +1028,6 @@ const char *sata_spd_string(unsigned int spd) return spd_str[spd - 1]; } -static int ata_dev_set_dipm(struct ata_device *dev, enum ata_lpm_policy policy) -{ - struct ata_link *link = dev->link; - struct ata_port *ap = link->ap; - u32 scontrol; - unsigned int err_mask; - int rc; - - /* - * disallow DIPM for drivers which haven't set - * ATA_FLAG_LPM. This is because when DIPM is enabled, - * phy ready will be set in the interrupt status on - * state changes, which will cause some drivers to - * think there are errors - additionally drivers will - * need to disable hot plug. - */ - if (!(ap->flags & ATA_FLAG_LPM) || !ata_dev_enabled(dev)) { - ap->lpm_policy = ATA_LPM_UNKNOWN; - return -EINVAL; - } - - /* - * For DIPM, we will only enable it for the - * min_power setting. - * - * Why? Because Disks are too stupid to know that - * If the host rejects a request to go to SLUMBER - * they should retry at PARTIAL, and instead it - * just would give up. So, for medium_power to - * work at all, we need to only allow HIPM. - */ - rc = sata_scr_read(link, SCR_CONTROL, &scontrol); - if (rc) - return rc; - - switch (policy) { - case ATA_LPM_MIN_POWER: - /* no restrictions on LPM transitions */ - scontrol &= ~(0x3 << 8); - rc = sata_scr_write(link, SCR_CONTROL, scontrol); - if (rc) - return rc; - - /* enable DIPM */ - if (dev->flags & ATA_DFLAG_DIPM) - err_mask = ata_dev_set_feature(dev, - SETFEATURES_SATA_ENABLE, SATA_DIPM); - break; - case ATA_LPM_MED_POWER: - /* allow LPM to PARTIAL */ - scontrol &= ~(0x1 << 8); - scontrol |= (0x2 << 8); - rc = sata_scr_write(link, SCR_CONTROL, scontrol); - if (rc) - return rc; - - /* - * we don't have to disable DIPM since LPM flags - * disallow transitions to SLUMBER, which effectively - * disable DIPM if it does not support PARTIAL - */ - break; - case ATA_LPM_UNKNOWN: - case ATA_LPM_MAX_POWER: - /* disable all LPM transitions */ - scontrol |= (0x3 << 8); - rc = sata_scr_write(link, SCR_CONTROL, scontrol); - if (rc) - return rc; - - /* - * we don't have to disable DIPM since LPM flags - * disallow all transitions which effectively - * disable DIPM anyway. - */ - break; - } - - /* FIXME: handle SET FEATURES failure */ - (void) err_mask; - - return 0; -} - -/** - * ata_dev_enable_pm - enable SATA interface power management - * @dev: device to enable power management - * @policy: the link power management policy - * - * 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: Caller. - * Returns: -EINVAL if LPM is not supported, 0 otherwise. - */ -void ata_dev_enable_pm(struct ata_device *dev, enum ata_lpm_policy policy) -{ - int rc = 0; - struct ata_port *ap = dev->link->ap; - - /* set HIPM first, then DIPM */ - if (ap->ops->enable_pm) - rc = ap->ops->enable_pm(ap, policy); - if (rc) - goto enable_pm_out; - rc = ata_dev_set_dipm(dev, policy); - -enable_pm_out: - if (rc) - ap->lpm_policy = ATA_LPM_MAX_POWER; - else - ap->lpm_policy = policy; - return /* rc */; /* hopefully we can use 'rc' eventually */ -} - -#ifdef CONFIG_PM -/** - * ata_dev_disable_pm - disable SATA interface power management - * @dev: device to disable power management - * - * Disable SATA Interface power management. This will disable - * Device Interface Power Management (DIPM) without changing - * policy, call driver specific callbacks for disabling Host - * Initiated Power management. - * - * Locking: Caller. - * Returns: void - */ -static void ata_dev_disable_pm(struct ata_device *dev) -{ - struct ata_port *ap = dev->link->ap; - - ata_dev_set_dipm(dev, ATA_LPM_MAX_POWER); - if (ap->ops->disable_pm) - ap->ops->disable_pm(ap); -} -#endif /* CONFIG_PM */ - -void ata_lpm_schedule(struct ata_port *ap, enum ata_lpm_policy policy) -{ - ap->lpm_policy = policy; - ap->link.eh_info.action |= ATA_EH_LPM; - ap->link.eh_info.flags |= ATA_EHI_NO_AUTOPSY; - ata_port_schedule_eh(ap); -} - -#ifdef CONFIG_PM -static void ata_lpm_enable(struct ata_host *host) -{ - struct ata_link *link; - struct ata_port *ap; - struct ata_device *dev; - int i; - - for (i = 0; i < host->n_ports; i++) { - ap = host->ports[i]; - ata_for_each_link(link, ap, EDGE) { - ata_for_each_dev(dev, link, ALL) - ata_dev_disable_pm(dev); - } - } -} - -static void ata_lpm_disable(struct ata_host *host) -{ - int i; - - for (i = 0; i < host->n_ports; i++) { - struct ata_port *ap = host->ports[i]; - ata_lpm_schedule(ap, ap->lpm_policy); - } -} -#endif /* CONFIG_PM */ - /** * ata_dev_classify - determine device type based on ATA-spec signature * @tf: ATA taskfile register set for device to be identified @@ -2562,13 +2386,6 @@ int ata_dev_configure(struct ata_device *dev) if (dev->flags & ATA_DFLAG_LBA48) dev->max_sectors = ATA_MAX_SECTORS_LBA48; - if (!(dev->horkage & ATA_HORKAGE_LPM)) { - if (ata_id_has_hipm(dev->id)) - dev->flags |= ATA_DFLAG_HIPM; - if (ata_id_has_dipm(dev->id)) - dev->flags |= ATA_DFLAG_DIPM; - } - /* Limit PATA drive on SATA cable bridge transfers to udma5, 200 sectors */ if (ata_dev_knobble(dev)) { @@ -2589,13 +2406,6 @@ int ata_dev_configure(struct ata_device *dev) dev->max_sectors = min_t(unsigned int, ATA_MAX_SECTORS_128, dev->max_sectors); - if (ata_dev_blacklisted(dev) & ATA_HORKAGE_LPM) { - dev->horkage |= ATA_HORKAGE_LPM; - - /* reset link pm_policy for this port to no pm */ - ap->lpm_policy = ATA_LPM_MAX_POWER; - } - if (ap->ops->dev_config) ap->ops->dev_config(dev); @@ -5495,12 +5305,6 @@ int ata_host_suspend(struct ata_host *host, pm_message_t mesg) int rc; /* - * disable link pm on all ports before requesting - * any pm activity - */ - ata_lpm_enable(host); - - /* * On some hardware, device fails to respond after spun down * for suspend. As the device won't be used before being * resumed, we don't need to touch the device. Ask EH to skip @@ -5533,9 +5337,6 @@ void ata_host_resume(struct ata_host *host) ata_host_request_pm(host, PMSG_ON, ATA_EH_RESET, ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET, 0); host->dev->power.power_state = PMSG_ON; - - /* reenable link pm */ - ata_lpm_disable(host); } #endif @@ -6096,7 +5897,7 @@ static void async_port_probe(void *data, async_cookie_t cookie) spin_lock_irqsave(ap->lock, flags); ehi->probe_mask |= ATA_ALL_DEVICES; - ehi->action |= ATA_EH_RESET | ATA_EH_LPM; + ehi->action |= ATA_EH_RESET; ehi->flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET; ap->pflags &= ~ATA_PFLAG_INITIALIZING; diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 96aa75ddf87f..a645cd3ab163 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -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: diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index aa56681f68db..56f6224fd6e6 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -117,6 +117,7 @@ static ssize_t ata_scsi_lpm_store(struct device *dev, struct Scsi_Host *shost = class_to_shost(dev); struct ata_port *ap = ata_shost_to_port(shost); enum ata_lpm_policy policy; + unsigned long flags; /* UNKNOWN is internal state, iterate from MAX_POWER */ for (policy = ATA_LPM_MAX_POWER; @@ -129,7 +130,11 @@ static ssize_t ata_scsi_lpm_store(struct device *dev, if (policy == ARRAY_SIZE(ata_lpm_policy_names)) return -EINVAL; - ata_lpm_schedule(ap, policy); + spin_lock_irqsave(ap->lock, flags); + ap->target_lpm_policy = policy; + ata_port_schedule_eh(ap); + spin_unlock_irqrestore(ap->lock, flags); + return count; } @@ -139,11 +144,11 @@ static ssize_t ata_scsi_lpm_show(struct device *dev, struct Scsi_Host *shost = class_to_shost(dev); struct ata_port *ap = ata_shost_to_port(shost); - if (ap->lpm_policy >= ARRAY_SIZE(ata_lpm_policy_names)) + if (ap->target_lpm_policy >= ARRAY_SIZE(ata_lpm_policy_names)) return -EINVAL; return snprintf(buf, PAGE_SIZE, "%s\n", - ata_lpm_policy_names[ap->lpm_policy]); + ata_lpm_policy_names[ap->target_lpm_policy]); } DEVICE_ATTR(link_power_management_policy, S_IRUGO | S_IWUSR, ata_scsi_lpm_show, ata_scsi_lpm_store); diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 65b6a73c3ac7..55a6f413a550 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -102,10 +102,6 @@ extern int sata_link_init_spd(struct ata_link *link); extern int ata_task_ioctl(struct scsi_device *scsidev, void __user *arg); extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg); extern struct ata_port *ata_port_alloc(struct ata_host *host); -extern void ata_dev_enable_pm(struct ata_device *dev, - enum ata_lpm_policy policy); -extern void ata_lpm_schedule(struct ata_port *ap, - enum ata_lpm_policy policy); extern const char *sata_spd_string(unsigned int spd); /* libata-acpi.c */ |