diff -urN linux-2.6.22-gentoo-r5.orig/Documentation/scsi/link_power_management_policy.txt linux-2.6.22-gentoo-r5-alpm/Documentation/scsi/link_power_management_policy.txt --- linux-2.6.22-gentoo-r5.orig/Documentation/scsi/link_power_management_policy.txt 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.22-gentoo-r5-alpm/Documentation/scsi/link_power_management_policy.txt 2007-09-21 20:36:59.000000000 +0200 @@ -0,0 +1,19 @@ +This parameter allows the user to set the link (interface) power management. +There are 3 possible options: + +Value Effect +---------------------------------------------------------------------------- +min_power Tell the controller to try to make the link use the + least possible power when possible. This may + sacrifice some performance due to increased latency + when coming out of lower power states. + +max_performance Generally, this means no power management. Tell + the controller to have performance be a priority + over power management. + +medium_power Tell the controller to enter a lower power state + when possible, but do not enter the lowest power + state, thus improving latency over min_power setting. + + diff -urN linux-2.6.22-gentoo-r5.orig/drivers/ata/ahci.c linux-2.6.22-gentoo-r5-alpm/drivers/ata/ahci.c --- linux-2.6.22-gentoo-r5.orig/drivers/ata/ahci.c 2007-09-21 20:31:05.000000000 +0200 +++ linux-2.6.22-gentoo-r5-alpm/drivers/ata/ahci.c 2007-09-21 20:39:46.000000000 +0200 @@ -48,6 +48,9 @@ #define DRV_NAME "ahci" #define DRV_VERSION "2.2" +static int ahci_enable_alpm(struct ata_port *ap, + enum link_pm policy); +static int ahci_disable_alpm(struct ata_port *ap); enum { AHCI_PCI_BAR = 5, @@ -97,6 +100,7 @@ /* HOST_CAP bits */ HOST_CAP_SSC = (1 << 14), /* Slumber capable */ HOST_CAP_CLO = (1 << 24), /* Command List Override support */ + HOST_CAP_ALPM = (1 << 26), /* Aggressive Link PM support */ HOST_CAP_SSS = (1 << 27), /* Staggered Spin-up */ HOST_CAP_NCQ = (1 << 30), /* Native Command Queueing */ HOST_CAP_64 = (1 << 31), /* PCI DAC (64-bit DMA) support */ @@ -151,6 +155,8 @@ PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS, /* PORT_CMD bits */ + PORT_CMD_ASP = (1 << 27), /* Aggressive Slumber/Partial */ + PORT_CMD_ALPE = (1 << 26), /* Aggressive Link PM enable */ PORT_CMD_ATAPI = (1 << 24), /* Device is ATAPI */ PORT_CMD_LIST_ON = (1 << 15), /* cmd list DMA engine running */ PORT_CMD_FIS_ON = (1 << 14), /* FIS DMA engine running */ @@ -171,6 +177,7 @@ AHCI_FLAG_HONOR_PI = (1 << 26), /* honor PORTS_IMPL */ AHCI_FLAG_IGN_SERR_INTERNAL = (1 << 27), /* ignore SERR_INTERNAL */ AHCI_FLAG_32BIT_ONLY = (1 << 28), /* force 32bit */ + AHCI_FLAG_NO_HOTPLUG = (1 << 31), /* ignore PxSERR.DIAG.N */ AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | @@ -211,6 +218,7 @@ unsigned int ncq_saw_d2h:1; unsigned int ncq_saw_dmas:1; unsigned int ncq_saw_sdb:1; + u32 intr_mask; /* interrupts to enable */ }; static u32 ahci_scr_read (struct ata_port *ap, unsigned int sc_reg); @@ -235,6 +243,11 @@ static int ahci_pci_device_resume(struct pci_dev *pdev); #endif +static struct class_device_attribute *ahci_shost_attrs[] = { + &class_device_attr_link_power_management_policy, + NULL +}; + static struct scsi_host_template ahci_sht = { .module = THIS_MODULE, .name = DRV_NAME, @@ -252,6 +265,7 @@ .slave_configure = ata_scsi_slave_config, .slave_destroy = ata_scsi_slave_destroy, .bios_param = ata_std_bios_param, + .shost_attrs = ahci_shost_attrs, }; static const struct ata_port_operations ahci_ops = { @@ -283,6 +297,8 @@ .port_suspend = ahci_port_suspend, .port_resume = ahci_port_resume, #endif + .enable_pm = ahci_enable_alpm, + .disable_pm = ahci_disable_alpm, .port_start = ahci_port_start, .port_stop = ahci_port_stop, @@ -718,6 +734,156 @@ writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD); } +static int ahci_disable_alpm(struct ata_port *ap) +{ + void __iomem *port_mmio = ahci_port_base(ap); + u32 cmd, scontrol; + struct ahci_port_priv *pp = ap->private_data; + + /* + * disable Interface Power Management State Transitions + * This is accomplished by setting bits 8:11 of the + * SATA Control register + */ + scontrol = readl(port_mmio + PORT_SCR_CTL); + scontrol |= (0x3 << 8); + writel(scontrol, port_mmio + PORT_SCR_CTL); + + /* 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, SCR_ERROR, ((1 << 16) | (1 << 18))); + + /* + * Clear flag to indicate that we should ignore all PhyRdy + * state changes + */ + ap->flags &= ~AHCI_FLAG_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 + */ + return 0; +} + +static int ahci_enable_alpm(struct ata_port *ap, + enum link_pm policy) +{ + struct ahci_host_priv *hpriv = ap->host->private_data; + void __iomem *port_mmio = ahci_port_base(ap); + u32 cmd, scontrol, sstatus; + 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)) { + ap->pm_policy = NOT_AVAILABLE; + return -EINVAL; + } + + /* make sure we have a device attached */ + sstatus = readl(port_mmio + PORT_SCR_STAT); + if (!(sstatus & 0xf00)) { + ap->pm_policy = NOT_AVAILABLE; + return -EINVAL; + } + + switch (policy) { + case MAX_PERFORMANCE: + case NOT_AVAILABLE: + /* + * if we came here with NOT_AVAILABLE, + * 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. + */ + ahci_disable_alpm(ap); + ap->pm_policy = MAX_PERFORMANCE; + return 0; + case MIN_POWER: + /* configure HBA to enter SLUMBER */ + asp = PORT_CMD_ASP; + break; + case MEDIUM_POWER: + /* configure HBA to enter PARTIAL */ + asp = 0; + break; + default: + return -EINVAL; + } + ap->pm_policy = policy; + + /* + * 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); + + /* + * Set a flag to indicate that we should ignore all PhyRdy + * state changes since these can happen now whenever we + * change link state + */ + ap->flags |= AHCI_FLAG_NO_HOTPLUG; + + /* get the existing command bits */ + cmd = readl(port_mmio + PORT_CMD); + + /* + * enable Interface Power Management State Transitions + * This is accomplished by clearing bits 8:11 of the + * SATA Control register + */ + scontrol = readl(port_mmio + PORT_SCR_CTL); + scontrol &= ~(0x3 << 8); + writel(scontrol, port_mmio + PORT_SCR_CTL); + + /* + * Set ASP based on Policy + */ + 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); + cmd = readl(port_mmio + PORT_CMD); + return 0; +} + #ifdef CONFIG_PM static void ahci_power_down(struct ata_port *ap) { @@ -1243,6 +1409,17 @@ status = readl(port_mmio + PORT_IRQ_STAT); writel(status, port_mmio + PORT_IRQ_STAT); + /* 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 ((ap->flags & AHCI_FLAG_NO_HOTPLUG) && + (status & PORT_IRQ_PHYRDY)) { + status &= ~PORT_IRQ_PHYRDY; + ahci_scr_write(ap, SCR_ERROR, ((1 << 16) | (1 << 18))); + } + if (unlikely(status & PORT_IRQ_ERROR)) { ahci_error_intr(ap, status); return; @@ -1408,6 +1585,7 @@ void __iomem *mmio = ap->host->iomap[AHCI_PCI_BAR]; void __iomem *port_mmio = ahci_port_base(ap); u32 tmp; + struct ahci_port_priv *pp = ap->private_data; /* clear IRQ */ tmp = readl(port_mmio + PORT_IRQ_STAT); @@ -1415,7 +1593,7 @@ writel(1 << ap->port_no, mmio + HOST_IRQ_STAT); /* turn IRQ back on */ - writel(DEF_PORT_IRQ, port_mmio + PORT_IRQ_MASK); + writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); } static void ahci_error_handler(struct ata_port *ap) @@ -1571,6 +1749,12 @@ pp->cmd_tbl = mem; pp->cmd_tbl_dma = mem_dma; + /* + * Save off initial list of interrupts to be enabled. + * This could be changed later + */ + pp->intr_mask = DEF_PORT_IRQ; + ap->private_data = pp; /* power up port */ @@ -1749,6 +1933,9 @@ struct ata_port *ap = host->ports[i]; void __iomem *port_mmio = ahci_port_base(ap); + /* set initial link pm policy */ + ap->pm_policy = NOT_AVAILABLE; + ap->ioaddr.cmd_addr = port_mmio; ap->ioaddr.scr_addr = port_mmio + PORT_SCR; } else diff -urN linux-2.6.22-gentoo-r5.orig/drivers/ata/libata-core.c linux-2.6.22-gentoo-r5-alpm/drivers/ata/libata-core.c --- linux-2.6.22-gentoo-r5.orig/drivers/ata/libata-core.c 2007-09-21 20:31:05.000000000 +0200 +++ linux-2.6.22-gentoo-r5-alpm/drivers/ata/libata-core.c 2007-09-21 20:40:34.000000000 +0200 @@ -2021,6 +2021,9 @@ if (dev->flags & ATA_DFLAG_LBA48) dev->max_sectors = ATA_MAX_SECTORS_LBA48; + if (ata_id_has_hipm(dev->id) || ata_id_has_dipm(dev->id)) + dev->flags |= ATA_DFLAG_IPM; + if (dev->horkage & ATA_HORKAGE_DIAGNOSTIC) { /* Let the user know. We don't want to disallow opens for rescue purposes, or in case the vendor is just a blithering @@ -2046,6 +2049,13 @@ dev->max_sectors = min_t(unsigned int, ATA_MAX_SECTORS_128, dev->max_sectors); + if (ata_device_blacklisted(dev) & ATA_HORKAGE_IPM) { + dev->horkage |= ATA_HORKAGE_IPM; + + /* reset link pm_policy for this port to no pm */ + ap->pm_policy = MAX_PERFORMANCE; + } + if (ap->ops->dev_config) ap->ops->dev_config(dev); @@ -5807,6 +5817,27 @@ return 0; } +static void ata_host_disable_link_pm(struct ata_host *host) +{ + int i; + + for (i = 0; i < host->n_ports; i++) { + struct ata_port *ap = host->ports[i]; + if (ap->ops->disable_pm) + ap->ops->disable_pm(ap); + } +} + +static void ata_host_enable_link_pm(struct ata_host *host) +{ + int i; + + for (i = 0; i < host->n_ports; i++) { + struct ata_port *ap = host->ports[i]; + ata_scsi_set_link_pm_policy(ap, ap->pm_policy); + } +} + #ifdef CONFIG_PM static int ata_host_request_pm(struct ata_host *host, pm_message_t mesg, unsigned int action, unsigned int ehi_flags, @@ -5874,6 +5905,12 @@ { int rc; + /* + * disable link pm on all ports before requesting + * any pm activity + */ + ata_host_disable_link_pm(host); + rc = ata_host_request_pm(host, mesg, 0, ATA_EHI_QUIET, 1); if (rc == 0) host->dev->power.power_state = mesg; @@ -5896,6 +5933,9 @@ ata_host_request_pm(host, PMSG_ON, ATA_EH_SOFTRESET, ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET, 0); host->dev->power.power_state = PMSG_ON; + + /* reenable link pm */ + ata_host_enable_link_pm(host); } #endif @@ -6385,6 +6425,7 @@ struct ata_port *ap = host->ports[i]; ata_scsi_scan_host(ap); + ata_scsi_set_link_pm_policy(ap, ap->pm_policy); } return 0; diff -urN linux-2.6.22-gentoo-r5.orig/drivers/ata/libata-eh.c linux-2.6.22-gentoo-r5-alpm/drivers/ata/libata-eh.c --- linux-2.6.22-gentoo-r5.orig/drivers/ata/libata-eh.c 2007-09-21 20:31:05.000000000 +0200 +++ linux-2.6.22-gentoo-r5-alpm/drivers/ata/libata-eh.c 2007-09-21 20:36:59.000000000 +0200 @@ -1994,6 +1994,9 @@ ehc->i.flags &= ~ATA_EHI_SETMODE; } + if (ehc->i.action & ATA_EHI_LPM) + ap->ops->enable_pm(ap, ap->pm_policy); + goto out; dev_fail: diff -urN linux-2.6.22-gentoo-r5.orig/drivers/ata/libata-scsi.c linux-2.6.22-gentoo-r5-alpm/drivers/ata/libata-scsi.c --- linux-2.6.22-gentoo-r5.orig/drivers/ata/libata-scsi.c 2007-09-21 20:31:05.000000000 +0200 +++ linux-2.6.22-gentoo-r5-alpm/drivers/ata/libata-scsi.c 2007-09-21 20:36:59.000000000 +0200 @@ -111,6 +111,78 @@ }; +static const struct { + enum link_pm value; + char *name; +} link_pm_policy[] = { + { NOT_AVAILABLE, "max_performance" }, + { MIN_POWER, "min_power" }, + { MAX_PERFORMANCE, "max_performance" }, + { MEDIUM_POWER, "medium_power" }, +}; + +const char *ata_scsi_link_pm_policy(enum link_pm policy) +{ + int i; + char *name = NULL; + + for (i = 0; i < ARRAY_SIZE(link_pm_policy); i++) { + if (link_pm_policy[i].value == policy) { + name = link_pm_policy[i].name; + break; + } + } + return name; +} + +static ssize_t store_link_pm_policy(struct class_device *class_dev, + const char *buf, size_t count) +{ + struct Scsi_Host *shost = class_to_shost(class_dev); + struct ata_port *ap = ata_shost_to_port(shost); + enum link_pm policy = 0; + int i; + + /* + * we are skipping array location 0 on purpose - this + * is because a value of NOT_AVAILABLE is displayed + * to the user as max_performance, but when the user + * writes "max_performance", they actually want the + * value to match MAX_PERFORMANCE. + */ + for (i = 1; i < ARRAY_SIZE(link_pm_policy); i++) { + const int len = strlen(link_pm_policy[i].name); + if (strncmp(link_pm_policy[i].name, buf, len) == 0 && + buf[len] == '\n') { + policy = link_pm_policy[i].value; + break; + } + } + if (!policy) + return -EINVAL; + + if (ata_scsi_set_link_pm_policy(ap, policy)) + return -EINVAL; + return count; +} + +static ssize_t +show_link_pm_policy(struct class_device *class_dev, char *buf) +{ + struct Scsi_Host *shost = class_to_shost(class_dev); + struct ata_port *ap = ata_shost_to_port(shost); + const char *policy = + ata_scsi_link_pm_policy(ap->pm_policy); + + if (!policy) + return -EINVAL; + + return snprintf(buf, 23, "%s\n", policy); +} +CLASS_DEVICE_ATTR(link_power_management_policy, S_IRUGO | S_IWUSR, + show_link_pm_policy, store_link_pm_policy); +EXPORT_SYMBOL_GPL(class_device_attr_link_power_management_policy); + static void ata_scsi_invalid_field(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *)) { @@ -2905,6 +2977,52 @@ } } +int ata_scsi_set_link_pm_policy(struct ata_port *ap, + enum link_pm policy) +{ + int rc = -EINVAL; + int i; + + /* + * make sure no broken devices are on this port, + * and that all devices support interface power + * management + */ + for (i = 0; i < ATA_MAX_DEVICES; i++) { + struct ata_device *dev = &ap->device[i]; + + /* only check drives which exist */ + if (!ata_dev_enabled(dev)) + continue; + + /* + * do we need to handle the case where we've hotplugged + * a broken drive (since hotplug and ALPM are mutually + * exclusive) ? + * + * If so, if we detect a broken drive on a port with + * alpm already enabled, then we should reset the policy + * to off for the entire port. + */ + if ((dev->horkage & ATA_HORKAGE_IPM) || + !(dev->flags & ATA_DFLAG_IPM)) { + ata_dev_printk(dev, KERN_ERR, + "Unable to set Link PM policy\n"); + ap->pm_policy = MAX_PERFORMANCE; + } + } + + if (ap->ops->enable_pm) { + ap->pm_policy = policy; + ap->eh_info.action |= ATA_EHI_LPM; + ap->eh_info.flags |= ATA_EHI_NO_AUTOPSY; + ata_port_schedule_eh(ap); + rc = 0; + } + return rc; +} +EXPORT_SYMBOL_GPL(ata_scsi_set_link_pm_policy); + int ata_scsi_add_hosts(struct ata_host *host, struct scsi_host_template *sht) { int i, rc; diff -urN linux-2.6.22-gentoo-r5.orig/include/linux/ata.h linux-2.6.22-gentoo-r5-alpm/include/linux/ata.h --- linux-2.6.22-gentoo-r5.orig/include/linux/ata.h 2007-09-21 20:31:05.000000000 +0200 +++ linux-2.6.22-gentoo-r5-alpm/include/linux/ata.h 2007-09-21 20:36:59.000000000 +0200 @@ -321,6 +321,12 @@ ((u64) (id)[(n) + 0]) ) #define ata_id_cdb_intr(id) (((id)[0] & 0x60) == 0x20) +#define ata_id_has_hipm(id) \ + ( (((id)[76] != 0x0000) && ((id)[76] != 0xffff)) && \ + ((id)[76] & (1 << 9)) ) +#define ata_id_has_dipm(id) \ + ( (((id)[76] != 0x0000) && ((id)[76] != 0xffff)) && \ + ((id)[78] & (1 << 3)) ) static inline unsigned int ata_id_major_version(const u16 *id) { diff -urN linux-2.6.22-gentoo-r5.orig/include/linux/libata.h linux-2.6.22-gentoo-r5-alpm/include/linux/libata.h --- linux-2.6.22-gentoo-r5.orig/include/linux/libata.h 2007-09-21 20:31:05.000000000 +0200 +++ linux-2.6.22-gentoo-r5-alpm/include/linux/libata.h 2007-09-21 20:38:12.000000000 +0200 @@ -136,7 +136,8 @@ ATA_DFLAG_CDB_INTR = (1 << 2), /* device asserts INTRQ when ready for CDB */ ATA_DFLAG_NCQ = (1 << 3), /* device supports NCQ */ ATA_DFLAG_FLUSH_EXT = (1 << 4), /* do FLUSH_EXT instead of FLUSH */ - ATA_DFLAG_CFG_MASK = (1 << 8) - 1, + ATA_DFLAG_IPM = (1 << 8), /* device supports IPM */ + ATA_DFLAG_CFG_MASK = (1 << 12) - 1, ATA_DFLAG_PIO = (1 << 8), /* device limited to PIO mode */ ATA_DFLAG_NCQ_OFF = (1 << 9), /* device limited to non-NCQ mode */ @@ -274,6 +275,7 @@ ATA_EHI_RESUME_LINK = (1 << 1), /* resume link (reset modifier) */ ATA_EHI_NO_AUTOPSY = (1 << 2), /* no autopsy */ ATA_EHI_QUIET = (1 << 3), /* be quiet */ + ATA_EHI_LPM = (1 << 4), /* link power management action */ ATA_EHI_DID_SOFTRESET = (1 << 16), /* already soft-reset this port */ ATA_EHI_DID_HARDRESET = (1 << 17), /* already soft-reset this port */ @@ -298,6 +300,7 @@ ATA_HORKAGE_NODMA = (1 << 1), /* DMA problems */ ATA_HORKAGE_NONCQ = (1 << 2), /* Don't use NCQ */ ATA_HORKAGE_MAX_SEC_128 = (1 << 3), /* Limit max sects to 128 */ + ATA_HORKAGE_IPM = (1 << 4), /* LPM problems */ }; enum hsm_task_states { @@ -335,6 +338,18 @@ unsigned long deadline); typedef void (*ata_postreset_fn_t)(struct ata_port *ap, unsigned int *classes); +/* + * host pm policy: If you alter this, you also need to alter scsi_sysfs.c + * (for the ascii descriptions) + */ +enum link_pm { + NOT_AVAILABLE, + MIN_POWER, + MAX_PERFORMANCE, + MEDIUM_POWER, +}; +extern struct class_device_attribute class_device_attr_link_power_management_policy; + struct ata_ioports { void __iomem *cmd_addr; void __iomem *data_addr; @@ -546,6 +561,7 @@ pm_message_t pm_mesg; int *pm_result; + enum link_pm pm_policy; void *private_data; @@ -605,7 +621,8 @@ int (*port_suspend) (struct ata_port *ap, pm_message_t mesg); int (*port_resume) (struct ata_port *ap); - + int (*enable_pm) (struct ata_port *ap, enum link_pm policy); + int (*disable_pm) (struct ata_port *ap); int (*port_start) (struct ata_port *ap); void (*port_stop) (struct ata_port *ap); @@ -811,7 +828,7 @@ extern int ata_cable_80wire(struct ata_port *ap); extern int ata_cable_sata(struct ata_port *ap); extern int ata_cable_unknown(struct ata_port *ap); - +extern int ata_scsi_set_link_pm_policy(struct ata_port *ap, enum link_pm); /* * Timing helpers */